
Hello,
It has been a long time since I posted anything on this blog, I am so sorry about it.
I will try to diversify my blog, I’ll talk about C++, Rendering (always <3), and Qt, a framework I love.
So, in this last part, we will talk about photons.
What exactly are Photons ?
A photon is a quantum of light. It carries the light straightforwardly on the medium. Thanks to it, we can see objects etc.
How could we transform photons in a “visible value” like RGB color ?
We saw that the eyes only “see” the radiance !
So we have to transform our photons in radiance.
From physic of photons
We know that one photon have for energy :
where is the wavelength in nm and
in Joules.
Say we have photons of
each.
We can lay the luminous energy right now :
The luminous flux is just the temporal derivative about the luminous energy :
The idea is great, but we have a luminous flux function of the wavelength, but the radiance is waiting for a general luminous flux
So, we want to have a general flux which is the integral over all wavelengths in the visible spectrum of the luminous flux.
Now, we have the radiance
Using the rendering equation, we get two forms :
The first one take care about dispersion since the second doesn’t.
In this post, I am not going to use the first one, but I could write an article about it latter.
Let’s make our Photon Mapper
What do we need ?
We need a Light which emits photons, so we could add a function “emitPhotons” .
/**
* @brief Interface for a light
*/
class AbstractLight {
public:
AbstractLight(glm::vec3 const &flux);
virtual glm::vec3 getIrradiance(glm::vec3 const &position, glm::vec3 const &normal) = 0;
virtual void emitPhotons(std::size_t number) = 0;
virtual ~AbstractLight() = default;
protected:
glm::vec3 mTotalFlux;
};
We also need a material which bounces photons :
class AbstractMaterial {
public:
AbstractMaterial(float albedo);
virtual glm::vec3 getReflectedRadiance(Ray const &ray, AbstractShape const &shape) = 0;
virtual void bouncePhoton(Photon const &photon, AbstractShape const &shape) = 0;
virtual ~AbstractMaterial() = default;
float albedo;
protected:
virtual float brdf(glm::vec3 const &ingoing, glm::vec3 const &outgoing, glm::vec3 const &normal) = 0;
};
Obviously, we also need a structure for our photons. This structure should be able to store photons and compute irradiance at a given position.
class AbstractPhotonMap {
public:
AbstractPhotonMap() = default;
virtual glm::vec3 gatherIrradiance(glm::vec3 position, glm::vec3 normal, float radius) = 0;
virtual void addPhoton(Photon const &photon) = 0;
virtual void clear() = 0;
virtual ~AbstractPhotonMap() = default;
private:
};
How could we do this ?
Photon emitting is really easy :
void SpotLight::emitPhotons(std::size_t number) {
Photon photon;
photon.flux = mTotalFlux / (float)number;
photon.position = mPosition;
for(auto i(0u); i < number; ++i) {
vec3 directionPhoton;
do
directionPhoton = Random::random.getSphereDirection();
while(dot(directionPhoton, mDirection) < mCosCutoff);
photon.direction = directionPhoton;
tracePhoton(photon);
}
}
We divide the total flux by the number of photons and we compute a random direction, then we could trace the photon
Bouncing a photon depends on your material :
void UniformLambertianMaterial::bouncePhoton(const Photon &_photon, const AbstractShape &shape) {
Photon photon = _photon;
float xi = Random::random.xi();
float d = brdf(vec3(), vec3(), vec3());
if(photon.recursionDeep > 0) {
// Photon is absorbed
if(xi > d) {
World::world.addPhoton(_photon);
return;
}
}
if(++photon.recursionDeep > MAX_BOUNCES)
return;
photon.flux *= color;
photon.direction = Random::random.getHemisphereDirection(shape.getNormal(photon.position));
tracePhoton(photon);
}
To take care about conservation of energy, we play Russian roulette.
Obviously, to take care about conservation of energy, we have to modify the direct lighting as well ^^.
vec3 UniformLambertianMaterial::getReflectedRadiance(Ray const &ray, AbstractShape const &shape) {
vec3 directLighting = getIrradianceFromDirectLighting(ray.origin, shape.getNormal(ray.origin));
float f = brdf(vec3(), vec3(), vec3());
return color * (1.f - f) * f * (directLighting + World::world.gatherIrradiance(ray.origin, shape.getNormal(ray.origin), 0.5f));
}
Finally, we need to compute the irradiance at a given position :
It is only :
So we could easily write :
vec3 SimplePhotonMap::gatherIrradiance(glm::vec3 position, glm::vec3 normal, float radius) {
float radiusSquare = radius * radius;
vec3 irradiance;
for(auto &photon : mPhotons)
if(dot(photon.position - position, photon.position - position) < radiusSquare)
if(dot(photon.direction, normal) < 0.0)
irradiance += photon.flux;
return irradiance / ((float)M_PI * radiusSquare);
}
To have shadows, you could emit shadow photons like this :
void traceShadowPhoton(const Photon &_photon) {
Photon photon = _photon;
Ray ray(photon.position + photon.direction * RAY_EPSILON, photon.direction);
photon.flux = -photon.flux;
auto nearest = World::world.findNearest(ray);
while(get<0>(nearest) != nullptr) {
ray.origin += ray.direction * get<1>(nearest);
photon.position = ray.origin;
if(dot(ray.direction, get<0>(nearest)->getNormal(ray.origin)) < 0.f)
World::world.addPhoton(photon);
ray.origin += RAY_EPSILON * ray.direction;
nearest = World::world.findNearest(ray);
}
}
That’s all ! If you have any question, please, let me know !
Leave a Reply