Home > Documentation > Marschner in Nalu Demo

Marschner in Nalu Demo

Before starting to implement anything I downloaded and installed the Nalu Demo from Nivida, that uses Marschner shader for hair rendering, just to have a closer look at this implementation.

Sadly enough this applications doesn’t provide any source code (except for the hlsl shaders) so I had to do some reverse engineering in order to use this shader as a test shader for hair rendering in CS. I set most of the parameters based on the application and configuration files and I used the lookup textures and computed some angles based on Chapter 23 from GPU Gems 2.

In order to keep things simple I chose to set all the application variables in shaders, but if you’d like to pass the light position for instance to the vertex shader you can just comment the line where I set the value for light position in the vs (float4 LightPos = float4 (9000, 0, 0, 0);).

These are the connectors I used:

struct a2vConnector {
  float4 objCoord : POSITION;
  float3 objNormal : NORMAL;
  float3 Tangent: TEXCOORD0;

struct v2fConnector {
  float4 projCoord : POSITION;
  float3 angles : TEXCOORD1;
  half4 diffuseColor : COLOR0;
  half3 ambientColor : COLOR1;

I only modified the a2vConnector in order to have the Tangent buffer too.

The vertex shader looks like this:

v2fConnector main(a2vConnector a2v,
                  uniform float4x4 modelViewProj : state.matrix.mvp,

                  // Light and eye directions in object space
                  uniform float3 objLightDir,
                  uniform float3 objEyePos,

                  // Reflectance model parameters
                  uniform float DiffuseCol,
                  uniform float AmbientCol,

                  uniform float3 worldPointLight0Pos,
                  uniform float3 PointLightColor,
                  uniform float4x4 ModelViewIT : state.matrix.modelview.invtrans)
  v2fConnector v2f;

  float4 LightPos = float4 (9000, 0, 0, 0);

  objLightDir = normalize(LightPos.xyz -  a2v.objCoord.xyz);
  objEyePos = ModelViewIT[3].xyz;

  AmbientCol = 0;
  DiffuseCol = 0.75;
  PointLightColor = 1;
  worldPointLight0Pos = LightPos.xyz;
  float4 objColor = float4(1,0,0,1);

  /* Compute the tangent from adjacent vertices */
  float3 objTangent = normalize(a2v.Tangent - a2v.objCoord.xyz );

  /* Project */
  float4 objCoord = a2v.objCoord;
  float4 projCoord = mul(modelViewProj, objCoord);
  v2f.projCoord = projCoord;

  float3 objEyeDir = normalize(objEyePos - objCoord.xyz);  

  /* Compute longitudinal angles */
  float2 uv1;
  uv1.x = dot(objLightDir, objTangent);
  uv1.y = dot(objEyeDir, objTangent);
  v2f.angles.xy = 0.5 + 0.5*uv1;

  /* Compute the azimuthal angle */
  float3 lightPerp = objLightDir - uv1.x * objTangent;
  float3 eyePerp = objEyeDir - uv1.y * objTangent;
  float cosPhi = dot(eyePerp, lightPerp) * rsqrt(dot(eyePerp, eyePerp) * dot(lightPerp, lightPerp));
  v2f.angles.z = 0.5*cosPhi + 0.5;

  /* Compute diffuse lighting with phi-dependent component */
  float diffuse = sqrt(max(0, 1 - uv1.x*uv1.x));

  /* Pass colors */
  v2f.diffuseColor.rgb = diffuse*objColor.rgb * DiffuseCol;
  v2f.diffuseColor.a = objColor.a;
  v2f.ambientColor = objColor.rgb * AmbientCol;

  // compute point light lighting  
  float3 Delta = worldPointLight0Pos-a2v.objCoord;
  float3 pointLightDir = normalize(Delta);
  float NDL = dot(objTangent, pointLightDir);

  float pointLightDist = sqrt(dot(Delta,Delta)) * (1.0/400.0);
  float att = min(1,max(0,pointLightDist));

  v2f.ambientColor = (1.0-att) * PointLightColor;

  return v2f;

The only thing I modified in the vertex shader is using Tangent instead of objNormal to determine the objTangent vector. I also added code to get the objEyePos and objLightDir. Here it is important to take into account that the hair geometry being recreated every frame doesn’t have any World (or Model) matrix (it is the Identity matrix).

Moving on to the pixel/fragment shader:

float4 main(v2fConnector v2f,
            // Parameters for the hair model
            uniform half Rcol,
            uniform half TTcol,

            // Lookup tables for the hair model (fixed point)
            uniform sampler2D lookup1fixed,
            uniform sampler2D lookup2fixed

            ) : COLOR
  Rcol = 1.87639;
  TTcol = 3.70201;

  /* Compute the longitudinal reflectance component */
  half2 uv1 = v2f.angles.xy;
  half4 m = h4tex2D(lookup1fixed, uv1);

  /* Compute the azimuthal reflectance component */
  half2 uv2;
  uv2.x = cos( (asin (2 * v2f.angles.x - 1) - asin (2 * v2f.angles.y - 1) ) / 2 ) * 0.5 + 0.5; //m.w;
  uv2.y = v2f.angles.z;
  half4 ntt = h4tex2D(lookup2fixed, uv2);

  /* Combine longitudinal and azimuthal reflectance */
  half3 lighting;
  lighting = (m.r * ntt.a * Rcol.r).xxx;      // Primary highlight  
  lighting = m.b * ntt.rgb * TTcol.r;      // Transmittance (using MTRT instead of MTT)
  lighting += v2f.diffuseColor.rgb;            // Diffuse lighting

  float4 COL;
  COL.rgb = lighting + v2f.diffuseColor.rgb*0.2 + v2f.ambientColor; 

  //COL.rgb = v2f.ambientColor;
  COL.a = v2f.diffuseColor.a;

  return COL;

For the pixel shader I had to compute the first cosinus angle (see GPU Gems Chapter 23) because the lookup texture I got doesn’t have an alpha channel. So uv2.x = cos( (asin (2 * v2f.angles.x - 1) - asin (2 * v2f.angles.y - 1) ) / 2 ) * 0.5 + 0.5; //not m.w;.

LE: There is still a bug regarding the usage of ntt.a (which is invalid) for the primary highlight.

And that’s pretty much it regarding the Nvidia Nalu Marschner shader.

Almost forgot, here are the lookup textures:

Next you can see a comparison between Krystal rendered with Phong, Kajiya and Kay and Marschner. If you have any questions about the Phong shading model (or per pixel lighting) you can look over these pdfs: Basics of GPU-Based Programmig and MathematicsOfPerPixelLighting.

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: