download build: lpv_build.7z
download source: lpv_source.7z
Injection
After generating a set of secondary light sources, using Reflective Shadow Mapping, that will emit indirect light into the scene, I use an intensity-based downsampling technique to reduce the size of our Virtual Point Light(VPL) data set. This will make the injection stage a lot lighter. The primary idea of this pass is to transform the VPL cloud into an initial radiance distribution. I use SH coefficients to achieve this. Storing 4 coefficients per color channel in a set of volumetric textures. Do note that this is an approximation, but due to the scattered and diffuse nature of indirect lighting, I feel that this is sufficient. The input for this stage will be our Reflective Shadow Map (RSM). I evaluate all VPLs in an area and use an intensity-based evaluation to determine what VPLs will most likely contribute. Selected VPLs are added together and normalized. I generate a 1024x1024 RSM and down-sample it to 512x512. I use a vertex shader for the downsampling, a geometry shader to output the down-sampled VPL into the correct slice of our 3D texture, and a pixel shader for calculating the SH coefficients and writing the final result. Geometry shaders in DirectX12 have access to the system value semantic SV_RenderTargetArrayIndex. This semantic can be used to tell the Output Merger in what depth slice of a 3D texture to render the result of the pixel shader. Although this is an easy way of rendering our radiance volume, I propose an optimization using a gathering algorithm in a compute shader.Spherical Harmonics
SH can be used for approximating a signal integrated over a sphere. This is very useful when trying to simulate scattering phenomena. Various previous uses in computer graphics include precomputed radiance transfer and radiance probes.Real-valued SH are defined as: \begin{equation} Y_{l}^{m}(\theta, \phi) = \Phi^{m}(\phi) N_{l}^{|m|} P_{l}^{|m|}(cos \theta) \end{equation} Where: \begin{equation} \Phi^{m}(\phi)=\begin{cases} \sqrt{2}cos(mx), & \text{m $>$ 0}.\\ 1, & \text{m = 0}. \\ \sqrt{2}sin(|m|x) & \text{m $<$ 0} \end{cases} \end{equation} \begin{equation} N_{l}^{|m|} = \sqrt{\frac{2l + 1}{4\pi} \frac{(l-m)!}{(l + m)!}} \end{equation} And \(P_{l}^{|m|}\) are the associated Legendre polynomials: \begin{align*} P_{0}^{0} & = 1\\ P_{1}^{0} & = x\\ P_{1}^{1} & = -\sqrt{1 - x^2}\\ P_{0}^{2} & = \frac{1}{2}(3x^2-1)\\ P_{1}^{2} & = -3x\sqrt{1 - x^2}\\ P_{2}^{2} & = 3(1 - x^2)\\ ... \end{align*} I will assume points on a unit sphere are defined in cartesian coordinates as: \begin{align*} x & = sin(\theta) cos(\phi)\\ y & = sin(\theta) sin(\phi)\\ z & = cos(theta)\\ \end{align*} solving these equations for the first two bands gives: \begin{align*} Y_{0}^{0}(\theta, \phi) & = \sqrt{\frac{1}{4\pi}} \\ Y_{-1}^{1}(\theta, \phi) & = -\sqrt{\frac{3}{4\pi}} y \\ Y_{0}^{1}(\theta, \phi) & = \sqrt{\frac{3}{4\pi}} z \\ Y_{1}^{1}(\theta, \phi) & = -\sqrt{\frac{3}{4\pi}} x \\ \end{align*}