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” .

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/** * @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 :

1 2 3 4 5 6 7 8 9 10 11 12 13 |
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.

1 2 3 4 5 6 7 8 9 10 11 |
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 :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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 ^^.

1 2 3 4 5 6 |
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 :

1 2 3 4 5 6 7 8 9 10 |
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 :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 !