Showing posts with label spherical harmonics. Show all posts
Showing posts with label spherical harmonics. Show all posts

Friday, February 28, 2014

The importance of global illumination

Every modern graphics engine needs a global illumination solution. Rage AFAIK uses some kind of path tracing + some static lighting baked into textures(kinda like the good old lightmapping) as they have "inlimited" amount of texture memory. Unreal and other big boys, as DICE use Enlighten, CryTek have their Light Propagation Volumes.
My solution isn't  that advanced, but can be tweaked to look OK. The main drawback of it is that the lighting is static.


Wednesday, February 19, 2014

Global Illumination - lifting the curtain

As promised, I will elaborate on the topic of global illumination and provide a bit of source code to illustrate the method, mentioned in a previous post.
Light probes are spread across critical areas, manually or automatically. Incoming lighting at these points in space is captured in cube maps which are then converted to spherical harmonics.
A short video shows these probes in action




The HLSL code for sampling the spherical harmonics coefficients looks like this :

You provide a normal direction and the function returns the illumination coming from that direction.
 
// 'lightingSH' is the lighting environment projected onto SH (3rd order in this case),

// and 'n' is the surface normal

float3 ProjectOntoSH9(in float3 lightingSH[9], in float3 n)

{

    float3 result = 0.0f;

   

    // Cosine kernel

    const float A0 = 1.0f;

    const float A1 = 2.0f / 3.0f;

    const float A2 = 0.25f;



    // Band 0

    result += lightingSH[0] * 0.282095f * A0;



    // Band 1

    result += lightingSH[1] * 0.488603f * n.y * A1;

    result += lightingSH[2] * 0.488603f * n.z * A1;

    result += lightingSH[3] * 0.488603f * n.x * A1;



    // Band 2

    result += lightingSH[4] * 1.092548f * n.x * n.y * A2;

    result += lightingSH[5] * 1.092548f * n.y * n.z * A2;

    result += lightingSH[6] * 0.315392f * (3.0f * n.z * n.z - 1.0f) * A2;

    result += lightingSH[7] * 1.092548f * n.x * n.z * A2;

    result += lightingSH[8] * 0.546274f * (n.x * n.x - n.y * n.y) * A2;



    return result;

}


Here is the code for rendering the light probes (for debug purpose)
technique RenderSH
{
    pass p0
 {
  VertexShader = compile vs_3_0 SimpleVSTransformed();
  PixelShader = compile ps_3_0 psLightingRenderSH();
        CullMode = CCW;
  FillMode = solid;
  Zenable = true;
  StencilEnable = true;  
  AlphaBlendEnable = false;
  AlphaTestEnable = false; 
  ZWriteEnable = true; 
 }
};
void SimpleVSTransformed(in float4 inPos: POSITION, in float2 inTex: TEXCOORD0, 
out float4 outPos: POSITION, out float2 outTex: TEXCOORD0, out float4 wPos : TEXCOORD1)
{
outPos = inPos;
outTex = inTex;


outPos = mul(float4(inPos.xyz, 1), c_mViewProjection);
wPos = mul(float4(inPos.xyz, 1), c_mWorld); ; 


}
float4 psLightingRenderSH(PS_INPUT_LIGHT i, in float4 wPos : TEXCOORD1 ) : COLOR0
{

 
 
float4 color = 1.0 ;

  
    
 float3 vLightDir = normalize(   wPos.xyz - lightProbePos ) ;

   float4 probeCol = float4(ProjectOntoSH9(SHarmonicsCoefficients,-vLightDir) , 1.0) ; 
            return float4(probeCol.xyz  , 1.0);



}
As you can see, a sphere mesh is rendered and this is what is happening, briefly :
 Running through the vertex shader, world space vertex positions (yep, a sphere mesh has vertices spread around the center) are send to the pixel shader via TEXCOORD1 slot. Pixel shader then runs through every pixel, gets the light probe position we are currently rendering, gets the pixel position in world space, subtracts those to form direction and samples the spherical harmonics coefficients to obtain the pixel color.

Tuesday, February 18, 2014

Deferred irradiance light probes

For a real-time graphics simulation, global illumination has always been a very difficult problem to solve. Interaction of the surfaces in a scene based on the light sources, diffuse reflections of the surfaces and light energy bouncing off geometry according it's material properties is usually not a trivial task to solve in real time. Several approximation solution exists, but only a small handful of them are practically applicable for performance-critical applications, such as 3D games and simulations.
I always feel nostalgic for the beautiful views of the old indoor games, lit with performant and convincing technique called "light mapping". There are several issues and limitations with it as well, but at least it was fast, and looked great for static environments. Static regarding light properties (color, range, movement and light animation of any kind.) and the scene geometry.
They eat up a lot of texture memory as well, and do not combine well with normal mapping and other per-pixel effects. Lighting is also not per-pixel perfect, but rather per-texel. Anyways, I really needed a real-time solution for my game, so I decided to experiment with light probes, rendered as deferred lights, spread across critical areas. Those probes are generated using cubemaps, rendered at probe's positions that are further converted and stored as spherical harmonics coefficients. I basically spread light probes automatically across an area (or only on key areas) calculate the incoming light at those points, send this data and shade affected pixels
This is only one bounce of indirect light. It could be extended to several bounces, which would look much better on complex scenes, and the real-time cost (performance) should be the same.
The following screen shots show the famous Sponza model, lit with that technique with GI turned on and off for comparison.


I will describe the technique in more details and provide source snippets in further posts.