PerPixel Lighting II         Multipass and One-pass per-pixel lighting shaders

Intro

Hello!

Apart from posting these right-out-of-the-box working Ogre Cg shaders, I will write some notes about how I think to use them, their limitations, etc. Since I am no shader pro by any means, feel free to add your comments. Also I must add that some of the lines here, and their theory is grabbed from the OgreDemos, with modifications.

What will you find here, and details

First, there are two categories:

Multipass

This supports limitless lights, however, is always slower than 1-pass solutions. Also, as you will see, it needs an ambient pass before any lighting can occur (without it, some models will get unwanted transparency, try it with knot.mesh for example to see this effect/issue).
Also, when used with textures, it either needs to calculate texture operations every pass (for.ex. diffuse lighting modulation), or just once, as the last pass.

Doing texture lookups only in the last pass in faster than doing it every pass, but results are far less correct.

It will modulate ALL lighting from ALL previous passes, thus ambient and specular components will also be damped!

As another example, offset-mapping needs normal-texture lookup every pass to achieve at least minimal offset-effect for lighting, but if you decide to use the diffuse-lookup only at last pass, (as a no-lighting modulation with a simple UV passing vertex shader) diffuse UVs wont be perturbed by eyeposition. So you need additional fragment shader in the last pass as well, to calculate new UVs for diffuse texture modulation.

(Though I think that diffuse UV offsetting is less important than undamped specular highlights).

Finally, what really breaks multipass methods here, is that you have to recompute costly things which are the same for every light, since no way to pass them between passes.

--> someone please verify this, can data be passed between passes, and how?

1-pass-multilight


This is limited to support about 3 lights in all shaders, because of shader instruction count limit, and also needs a way to decide when NOT to compute lighting for the maximum it supports (if only 1 light is active in scene, for ex.)

This decision can be made with if/else statements when running the shader, but as we all know, perpixel decisions are costly. Much better if you compute in Ogre how many lights are affecting the object (based on distance or activeness), and use #define, #ifdef, etc. This way, it will beat multipass in every case up to 3 lights (which is enough with lightmaps, etc. in most cases).

And #define can be changed quite infrequently as well, in a material-generator, seen in deferred-shading OgreDemo.

Also there is a subcategory, just to make everyone happy :-)

compute light-direction, and eye-direction in vertex shader

This is cheaper, but less accurate on flat and very few poly surfaces, due to the fact that the rasterizer has to interpolate direction calculated at vertex-level to fragment-level. This is nonlinear, thus produces deviated lights, when lights is at distance of poly-size above a single poly.
I emphasize, that this is only visible when light source is very close to an untessellated surface. (very close means distance is about polygon size).

However, if you allow lights that close, you can switch from this to the costy one, below. This version is not an option if you have a large, 1 poly wall,or something like this quad:

Perpixel_quad_not_correct

Compute both in pixel shader

Quite costly, and vertex shader has no real job, all is done in fragment level.

This way, fragment shader gets interpolated light position, and can compute exact light direction. Produces very accurate results:

Best_quad

To finish, both per-vertex light-dir and eye-dir calculations are enough for even low-poly meshes if lights are not very close to the mesh. See pictures.

Fast method:
Mid_robot
Nice method:
Best_robot
See any difference?

Now, on to the meat:

Shaders:

  • Multipass:
    • per_vertex, per_pixel_fast, per_pixel_fine
  • 1-pass-always-3light:
    • per_pixel_fast_lim3

  • fast vs. fine: lightdir/eyedir calculus in vertex/fragment shader
  • creating fine versions based for offset based on per_pixel_fine and per_pixel_fast
  • differences should be easy

Materials you can get from this page

  • PerVertex - standard fixed-func. lighting
  • Perpixel - fast perpixel, less accurate, multipass, unlimited lights
  • Perpixel_Limited_3 - fast perpixel, less accurate, 1-pass, always 3 lights
  • Simple_Perpixel - nice, slow, multipass, unlimited lights (all supports diffuse, ambient, and specular coefficients, no texturing though)

Shaders: Basic stuff

//**************************************************************************************//
 //                                            //
 //                BASIC STUFF                        //
 //        SOME WITH A + D + S (PER-VERTEX)                    //
 //                                            //
 //**************************************************************************************//
 
 
 void Tiny_vp    (float4 position : POSITION,
       
           out float4 oPosition : POSITION, 
 
           uniform float4x4 worldViewProj)
 {
     oPosition = mul(worldViewProj, position);
 }
 
 void OneTexture_vp(float4 position : POSITION,
           float2 uv          : TEXCOORD0,
       
           out float4 oPosition : POSITION,
           out float2 oUv       : TEXCOORD0, 
 
           uniform float4x4 worldViewProj,
           uniform float scale)
 {
     oPosition = mul(worldViewProj, position);
     oUv = uv * scale;
 }
 
 void Ambient_vp(float4 position : POSITION, 
 
           out float4 oPosition : POSITION,
           out float4 colour    : COLOR,
 
           uniform float4x4 worldViewProj,
           uniform float4 ambient)
 {
     oPosition = mul(worldViewProj, position);
     colour = ambient;
 } 
 
 void AmbientOneTexture_vp(float4 position : POSITION,
               float2 uv          : TEXCOORD0,
                       
               out float4 oPosition : POSITION,
               out float2 oUv       : TEXCOORD0,
               out float4 colour    : COLOR,
 
               uniform float4x4 worldViewProj,
               uniform float4 ambient)
 {
     oPosition = mul(worldViewProj, position);
     oUv = uv;
     colour = ambient;
 }
 
 void PerVertex_Vert (    float4 position : POSITION,
             float3 normal   : NORMAL,
 
             uniform float4 lightPosition,
             uniform float3 eyePosition,
             uniform float4x4 worldviewproj,
 
             uniform float4 lightDiffuse,
             uniform float4 lightSpecular,
             uniform float exponent, 
 
             out float4 oColor : COLOR,
             out float4 oPos : POSITION
         ) 
 {
     oPos = mul(worldviewproj, position);
     
     float3 EyeDir = normalize(eyePosition - position.xyz);
 
     float3 LightDir = normalize(lightPosition.xyz -  (position.xyz * lightPosition.w)); 
 
     float3 HalfAngle   = normalize(LightDir + EyeDir);
 
     float3 N = normalize(normal);
 
     float NdotL = dot(N, LightDir);
     float NdotH = dot(N, HalfAngle);
     float4 Lit = lit(NdotL,NdotH,exponent);
     oColor = lightDiffuse * Lit.y + lightSpecular * Lit.z;    
 }

Materials: Basic stuff

//**************************************************************************************//
 //                                            //
 //                    BASIC STUFF                    //
 //                                            //
 //**************************************************************************************//
  
 
 vertex_program Tiny cg
 {
     source ARNOLD.cg
     entry_point Tiny_vp
     profiles vs_1_1 arbvp1
 }
 
 vertex_program OneTexture cg
 {
     source ARNOLD.cg
 
     default_params
     {
         param_named_auto worldViewProj worldviewproj_matrix
     }
 
     entry_point OneTexture_vp
     profiles vs_1_1 arbvp1
 }
 
 vertex_program Ambient cg
 {
     source ARNOLD.cg 
 
     default_params
     {
         param_named_auto worldViewProj worldviewproj_matrix
         param_named ambient float4 0.0 0.0 0.0 1.0
     }
 
     entry_point Ambient_vp
     profiles vs_1_1 arbvp1
 }
 
 vertex_program Ogre/BasicVertexPrograms/AmbientOneTexture cg
 {
     source ARNOLD.cg
     entry_point AmbientOneTexture_vp
     profiles vs_1_1 arbvp1
 }
  
 
 material WhiteVertex
 {
     technique
     {
         pass
         {
             lighting on 
 
             ambient  0.0 0.0 0.0 1
             diffuse  1 1 1 1
              specular 1 1 1 1 127
         }
     }
 } 
 
 vertex_program PerVertex_Vert cg 
 { 
       source ARNOLD.cg 
 
        default_params 
        { 
                  param_named_auto lightPosition light_position_object_space 0
 
         param_named_auto lightDiffuse light_diffuse_colour 0
         param_named_auto lightSpecular light_specular_colour 0 
 
                  param_named_auto worldviewproj worldviewproj_matrix 
 
         param_named_auto eyePosition camera_position_object_space
         param_named exponent float 127
        } 
 
        entry_point PerVertex_Vert 
        profiles vs_1_1 arbvp1 
 }
 
 material PerVertex
 {
     technique 
        {
          // Base ambient pass
         pass
          {
             
             vertex_program_ref Ambient
             {
             }
         }
         pass 
         { 
             // do this for each light
             iteration once_per_light
             scene_blend add  
 
                      vertex_program_ref PerVertex_Vert
                      { 
                      } 
               } 
        }
 }

Shaders: Perpixel stuff

//**************************************************************************************//
 //                                            //
 //                PERPIXEL LIGHTING                    //
 //                    A + D + S                        //
 //                                            //
 //**************************************************************************************//
 
 
 
 void Simple_Perpixel_Vert(
                     float4 position : POSITION, 
                     float3 normal   : NORMAL, 
 
 
                          uniform float4 lightPosition0,
 //            uniform float4 lightPosition1,
 //            uniform float4 lightPosition2,
             
                          uniform float4x4 worldviewproj, 
 //            uniform float3 eyePosition,
 
                          out float4 oClipPos    : POSITION, 
 
                         out float3 oNorm    : TEXCOORD0, 
 
                         out float4 oLightPos0    : TEXCOORD1,
 //            out float4 oLightPos1    : TEXCOORD2,
 //            out float4 oLightPos2    : TEXCOORD3,
             
                  out float4 oPos        : TEXCOORD4,
             out float3 EyeDir    : TEXCOORD5
              ) 
 { 
     oClipPos = mul(worldviewproj, position); 
 
     oLightPos0 = lightPosition0;
 //    oLightPos1 = lightPosition1;
 //    oLightPos2 = lightPosition2;
 
     oPos = position;
 
     
 
     oNorm     = normal; 
 } 
 
 void Simple_PerPixel_Frag(
             float3 normal        : TEXCOORD0, 
                          float4 LightPos0    : TEXCOORD1,
 //            float4 LightPos1    : TEXCOORD2,
 //            float4 LightPos2    : TEXCOORD3,
 
                  float4 position     : TEXCOORD4,
 
             uniform float3 eyePosition, 
 
             uniform float4 lightDiffuse0,
 //            uniform float4 lightDiffuse1,
 //            uniform float4 lightDiffuse2,
 
             uniform float4 lightSpecular0,
 //            uniform float4 lightSpecular1,
 //            uniform float4 lightSpecular2,
 
             uniform float exponent0,
 //            uniform float exponent1,
 //            uniform float exponent2,
 
             uniform float4 ambient,
 
                          out float4 oColor : COLOR 
 ) 
 { 
     
         //could I do this, or whole halfangle calculus in vertex shader?
     float3 N = normalize(normal);
 
     float3 EyeDir = normalize(eyePosition - position.xyz);
 
     float3 LightDir = normalize(LightPos0.xyz -  (position * LightPos0.w));
     float3 HalfAngle = normalize(LightDir + EyeDir);
     float NdotL = dot(LightDir, N);
     float NdotH = dot(HalfAngle, N);
     float4 Lit = lit(NdotL,NdotH,exponent0);
     oColor = lightDiffuse0 * Lit.y + lightSpecular0 * Lit.z + ambient;
 
 
 //    LightDir = normalize(LightPos1.xyz -  (position * LightPos1.w));
 //    HalfAngle = normalize(LightDir + EyeDir);
 //    NdotL = dot(LightDir, N);
 //    NdotH = dot(HalfAngle, N);
 //    Lit = lit(NdotL,NdotH,exponent0);
 //    oColor += lightDiffuse1 * Lit.y + lightSpecular1 * Lit.z;
 
 
 //    LightDir = normalize(LightPos2.xyz -  (position * LightPos2.w));
 //    HalfAngle = normalize(LightDir + EyeDir);
 //    NdotL = dot(LightDir, N);
 //    NdotH = dot(HalfAngle, N);
 //    Lit = lit(NdotL,NdotH,exponent0);
 //    oColor += lightDiffuse2 * Lit.y + lightSpecular2 * Lit.z + ambient;
 
         //do I need to normalize here the normal?
 }  
 
 void PerPixel_Vert (    float4 position : POSITION,
             float3 normal   : NORMAL, 
 
             uniform float4 lightPosition,
             uniform float3 eyePosition,
             uniform float4x4 worldviewproj, 
 
             out float4 oPos : POSITION,
             out float3 oNorm: TEXCOORD0,
             out float3 oLightDir: TEXCOORD1,
             out float3 oHalfAngle: TEXCOORD2
         ) 
 {
     oPos = mul(worldviewproj, position);
    
     float3 EyeDir = normalize(eyePosition - position.xyz);
 
     oLightDir = normalize(lightPosition.xyz -  (position.xyz * lightPosition.w)); 
 
     oHalfAngle   = normalize(oLightDir + EyeDir); 
 
     oNorm = normal;
 
     //what if I leave normalization of normal, and oHalfAngle to Fragshader?
 }
  
 
 void PerPixel_Frag (    float3 normal: TEXCOORD0,
             float3 LightDir  : TEXCOORD1,
             float3 HalfAngle : TEXCOORD2, 
 
             uniform float4 lightDiffuse,
             uniform float4 lightSpecular,
             uniform float exponent,
 
             out float4 oColor : COLOR      
            )
 {  
     float3 N = normalize(normal);
 
     float NdotL = dot(normalize(LightDir), N);
     float NdotH = dot(normalize(HalfAngle), N);
 
     float4 Lit = lit(NdotL,NdotH,exponent);
     
     oColor = lightDiffuse * Lit.y + lightSpecular * Lit.z;
         //do I need to normalize here the normal? 
 
 //    oColor = float4(normal,1);
 }
 
 
 void PerPixel_Lim3_Vert(float4 position : POSITION,
             float3 normal   : NORMAL, 
 
              uniform float4 lightPosition0,
             uniform float4 lightPosition1,
             uniform float4 lightPosition2,
             uniform float3 eyePosition,
             uniform float4x4 worldviewproj,
       
             out float4 oPos : POSITION,
             out float3 oNorm: TEXCOORD0,
             out float3 oLightDir0: TEXCOORD1,
             out float3 oLightDir1: TEXCOORD2,
             out float3 oLightDir2: TEXCOORD3,
             out float3 oHalfAngle0: TEXCOORD4,
             out float3 oHalfAngle1: TEXCOORD5,
             out float3 oHalfAngle2: TEXCOORD6
         ) 
 {
         oPos = mul(worldviewproj, position);
    
     oLightDir0 = normalize(lightPosition0.xyz -  (position * lightPosition0.w));
     oLightDir1 = normalize(lightPosition1.xyz -  (position * lightPosition1.w));
     oLightDir2 = normalize(lightPosition2.xyz -  (position * lightPosition2.w));
 
     float3 EyeDir = normalize(eyePosition - position.xyz);
 
     oHalfAngle0   = normalize(oLightDir0 + EyeDir);
     oHalfAngle1   = normalize(oLightDir1 + EyeDir);
     oHalfAngle2   = normalize(oLightDir2 + EyeDir);
 
     oNorm     = normal;
 
     //what if I leave normalization of normal and oHalfAngle to Fragshader?
 }
  
 
 void PerPixel_Lim3_Frag(float3 normal: TEXCOORD0,
                 float3 LightDir0  : TEXCOORD1,
             float3 LightDir1  : TEXCOORD2,
             float3 LightDir2  : TEXCOORD3,
             float3 HalfAngle0: TEXCOORD4,
             float3 HalfAngle1: TEXCOORD5,
             float3 HalfAngle2: TEXCOORD6, 
 
             uniform float4 lightDiffuse0,
             uniform float4 lightDiffuse1,
             uniform float4 lightDiffuse2, 
 
             uniform float4 lightSpecular0,
             uniform float4 lightSpecular1,
             uniform float4 lightSpecular2,
 
             uniform float exponent0,
 //            uniform float exponent1,
 //            uniform float exponent2,
 
             uniform float4 ambient, 
 
                out float4 oColor : COLOR
            )
 { 
     float3 N = normalize(normal);
 
     float NdotL = dot(normalize(LightDir0), N);
     float NdotH = dot(normalize(HalfAngle0), N);
  
      float4 Lit = lit(NdotL,NdotH,exponent0);
      oColor = lightDiffuse0 * Lit.y + lightSpecular0 * Lit.z;
 
     NdotL = dot(normalize(LightDir1), N);
     NdotH = dot(normalize(HalfAngle1), N);
 
     Lit = lit(NdotL,NdotH,exponent0);
      oColor += lightDiffuse1 * Lit.y + lightSpecular1 * Lit.z; 
  
      NdotL = dot(normalize(LightDir2), N);
      NdotH = dot(normalize(HalfAngle2), N);
 
     Lit = lit(NdotL,NdotH,exponent0);
     oColor += lightDiffuse2 * Lit.y + lightSpecular2 * Lit.z + ambient; 
  
         //do I need to normalize here the normal?
 }

Materials: Perpixel stuff

//**************************************************************************************//
 //                                            //
 //                PERPIXEL LIGHTING                    //
 //                    A + D + S                        //
 //                (NO TEXTURING ADDED)                    //
 //**************************************************************************************//
 
 vertex_program Simple_Perpixel_Vert cg 
 { 
        source ARNOLD.cg  
 
        default_params 
        { 
                   param_named_auto lightPosition0 light_position_object_space 0
 //        param_named_auto lightPosition1 light_position_object_space 1
 //        param_named_auto lightPosition2 light_position_object_space 2 
 
                   param_named_auto worldviewproj worldviewproj_matrix
        } 
 
        entry_point Simple_Perpixel_Vert 
        profiles vs_1_1 arbvp1 
 } 
 
 fragment_program Simple_PerPixel_Frag cg 
 { 
        source ARNOLD.cg  
 
        default_params 
        {
         param_named_auto eyePosition camera_position_object_space
 
                   param_named_auto lightDiffuse0 light_diffuse_colour 0
 //        param_named_auto lightDiffuse1 light_diffuse_colour 1
 //        param_named_auto lightDiffuse2 light_diffuse_colour 2
 
         param_named_auto lightSpecular0 light_specular_colour 0
 //        param_named_auto lightSpecular1 light_specular_colour 1
 //        param_named_auto lightSpecular2 light_specular_colour 2 
 
         param_named exponent0 float 127
 //        param_named exponent1 float 127
 //        param_named exponent2 float 127
         //If changed, needs adjusting in Fragshader's every lit!!!!! 
 
         param_named ambient float4 0.0 0.0 0.0 1.0
        }  
 
        entry_point Simple_PerPixel_Frag 
        profiles ps_2_0 arbfp1 
 } 
 
 vertex_program PerPixel_Vert cg
 {
     source ARNOLD.cg 
 
     default_params
     {
         param_named_auto lightPosition light_position_object_space 0
         param_named_auto eyePosition camera_position_object_space
         param_named_auto worldviewproj worldviewproj_matrix
     } 
 
      entry_point PerPixel_Vert
     profiles vs_1_1 arbvp1
 } 
 
 fragment_program PerPixel_Frag cg
 {
     source ARNOLD.cg
 
     default_params
     {
         param_named_auto lightDiffuse light_diffuse_colour 0
         param_named_auto lightSpecular light_specular_colour 0
         param_named exponent float 127
     }
 
     entry_point PerPixel_Frag
     profiles ps_2_0 arbfp1
 } 
 
 vertex_program PerPixel_Lim3_Vert cg
 {
     source ARNOLD.cg
 
     default_params
     {
         param_named_auto lightPosition0 light_position_object_space 0
         param_named_auto lightPosition1 light_position_object_space 1
         param_named_auto lightPosition2 light_position_object_space 2
         param_named_auto eyePosition camera_position_object_space
         param_named_auto worldviewproj worldviewproj_matrix
     } 
 
     entry_point PerPixel_Lim3_Vert
     profiles vs_1_1 arbvp1
 }
 
 fragment_program PerPixel_Lim3_Frag cg
 {
     source ARNOLD.cg 
 
     default_params
     {
         param_named_auto lightDiffuse0 light_diffuse_colour 0
         param_named_auto lightDiffuse1 light_diffuse_colour 1
         param_named_auto lightDiffuse2 light_diffuse_colour 2
 
         param_named_auto lightSpecular0 light_specular_colour 0
         param_named_auto lightSpecular1 light_specular_colour 1
         param_named_auto lightSpecular2 light_specular_colour 2 
 
         param_named exponent0 float 127
 //        param_named exponent1 float 127
 //        param_named exponent2 float 127
         //If changed, needs adjusting in Fragshader's every lit!!!!!
 
         param_named ambient float4 0.0 0.0 0.0 1.0
     } 
 
     entry_point PerPixel_Lim3_Frag
     profiles ps_2_0 arbfp1
 } 
 
 material Simple_Perpixel 
 { 
        technique 
        {
          // Base ambient pass
         pass
         {
             
             vertex_program_ref Ambient
             {
             }
         }
         pass 
         { 
             // do this for each light
             iteration once_per_light
             scene_blend add  
 
                      vertex_program_ref Simple_Perpixel_Vert
                      { 
                      }   
 
                      fragment_program_ref Simple_PerPixel_Frag
                      { 
                      } 
               } 
        } 
 }
 
 material Perpixel
 { 
     technique 
     { 
         // Base ambient pass
         pass
         {
             
             vertex_program_ref Ambient
             {
             }
         }
         pass 
         { 
             // do this for each light
             iteration once_per_light
             scene_blend add  
 
             vertex_program_ref PerPixel_Vert
             { 
             } 
 
             fragment_program_ref PerPixel_Frag 
             {
             } 
         }   
 
     }   
 
 }  
 
 //The limitation worth us ~20..100 FPS.It worth it.
 material Perpixel_Limited_3
 { 
     technique 
     { 
         pass 
         {
             vertex_program_ref PerPixel_Lim3_Vert
             { 
             }   
 
             fragment_program_ref PerPixel_Lim3_Frag
             {
             } 
         }  
 
     }  
 
 }


Alias: PerPixel_Lighting_II