


PBR SHADER
September 14, 2016
PBR Shader using fresnel, poisson, light scattering
Shader compatile with 3dsmax DirectX 9 shader UI widgets
Shader compatible with XNA

SSAO
August 14, 2016
dynamic SSAO:
The ambient occlusion direction reacts to general light average center position
The Ambient occlusion works in additive way with shading and with a different blending with linear shadows

DYNAMIC LIGHTS
August 14, 2016
Sun Light : Direction light casting linear shadows
Spheric light : dynamic runtime bulb
Additional lights backed in HDR cube map reflection content

C# CODE
/// <summary>
/// Helper function to draw a model
/// </summary>
/// <param name="isBasic">To draw the model with Basic Effect ( set the asset to use BasicEffect )</param>
/// <param name="model">The model to draw</param>
/// <param name="technique">The technique to use</param>
/// <param name="myTexture">Albedo Texture</param>
/// <param name="myNormal">Normal Texture</param>
/// <param name="myMaterial">Material Texture</param>
void DrawModel(bool isBasic, Model model, string techniqueName, Vector3 position, Vector3 scale, Texture2D myTexture, Texture2D myNormal, Texture2D myMaterial)
{
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
// Loop over meshs in the model
if (isBasic)
{
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.View = viewMatrix;
effect.Projection = projectionMatrix;
effect.World = (worldMatrix
* Matrix.CreateScale(scale))
* transforms[mesh.ParentBone.Index]
* Matrix.CreateTranslation(position);
}
// Draw the mesh
mesh.Draw();
}
}
else if (techniqueName.Equals("CreateShadowMap"))
{
foreach (ModelMesh mesh in model.Meshes)
{
// Loop over effects in the mesh
foreach (Effect effect in mesh.Effects)
{
Matrix World = (worldMatrix
* Matrix.CreateScale(scale))
* transforms[mesh.ParentBone.Index]
* Matrix.CreateTranslation(position);
effect.CurrentTechnique = effect.Techniques[techniqueName];
effect.Parameters["World"].SetValue(World);
effect.Parameters["LightViewProj"].SetValue(lightViewProjection);
}
// Draw the mesh
mesh.Draw();
}
}
else if (techniqueName.Equals("CreateSSAOMap"))
{
foreach (ModelMesh mesh in model.Meshes)
{
// Loop over effects in the mesh
foreach (Effect effect in mesh.Effects)
{
Matrix World = (worldMatrix
* Matrix.CreateScale(scale))
* transforms[mesh.ParentBone.Index]
* Matrix.CreateTranslation(position);
effect.CurrentTechnique = effect.Techniques[techniqueName];
effect.Parameters["FarPlane"].SetValue(NearFarPlane);
effect.Parameters["WorldViewProjection"].SetValue(World * viewMatrix * projectionMatrix);
}
// Draw the mesh
mesh.Draw();
}
}
else if (techniqueName.Equals("CookTorranceRemottiVariation"))
{
foreach (ModelMesh mesh in model.Meshes)
{
// Loop over effects in the mesh
foreach (Effect effect in mesh.Effects)
{
Matrix World = (worldMatrix
* Matrix.CreateScale(scale))
* transforms[mesh.ParentBone.Index]
* Matrix.CreateTranslation(position);
effect.CurrentTechnique = effect.Techniques[techniqueName];
effect.Parameters["World"].SetValue( World );
effect.Parameters["View"].SetValue( ( viewMatrix ) );
effect.Parameters["Projection"].SetValue( projectionMatrix );
effect.Parameters["LightViewProj"].SetValue( lightViewProjection );
effect.Parameters["LightDirection"].SetValue( LightDirection );
effect.Parameters["LightPosition"].SetValue( new Vector4( LightPosition, 1 ) );
effect.Parameters["LightIntensity"].SetValue( 3.0f );
effect.Parameters["EyePosition"].SetValue( cameraPosition );
effect.Parameters["ColorMap"].SetValue( myTexture );
effect.Parameters["NormalMap"].SetValue( myNormal );
effect.Parameters["MaterialMap"].SetValue( myMaterial );
effect.Parameters["TexCubemapIrradiance"].SetValue( TexCubemapIrradiance );
effect.Parameters["TexCubemapSpecular"].SetValue( TexCubemapSpecular );
effect.Parameters["ShadowMap"].SetValue( shadowRenderTarget );
}
// Draw the mesh
mesh.Draw();
}
}
}
​
​
/// <summary>
/// Render the shadow map texture to the screen
/// </summary>
void DrawShadowMapToScreen()
{
spriteBatch.Begin( 0, BlendState.Opaque, SamplerState.PointClamp, null, null );
spriteBatch.Draw( shadowRenderTarget, new Rectangle( 0, 0, 128, 128 ), Color.White );
spriteBatch.End();
GraphicsDevice.Textures[0] = null;
GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
}

HLSL CODE
​
​
float4x4 World;
float4x4 View;
float4x4 Projection;
float4x4 LightViewProj;
float4x4 WorldViewProjection;
// Light related
float LightIntensity = 2.0f;
float3 LightDirection;
float bias = -0.0015f;
float FarPlane = 45.0f;
float3 EyePosition;
float4 LightPosition : POSITION
<
string UIName = "Light Position";
string Object = "PointLight";
string Space = "World";
int refID = 0;
> = float4( 1.0f, 1.0f, 1.0f ,0.0f );
float3 LightDiffuseColor : LightDiffuseColor
<
int LightRef = 0;
string UIWidget = "ColorSwatch";
> = float3( 1.0f, 1.0f, 1.0f ); // intensity color multiplier
texture2D ColorMap;
sampler2D ColorMapSampler = sampler_state
{
Texture = <ColorMap>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
texture2D NormalMap;
sampler2D NormalMapSampler = sampler_state
{
Texture = <NormalMap>;
MinFilter = ANISOTROPIC;
MagFilter = ANISOTROPIC;
MipFilter = LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
texture2D MaterialMap;
sampler2D MaterialMapSampler = sampler_state
{
Texture = <MaterialMap>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
texture2D ShadowMap;
sampler2D ShadowMapSampler = sampler_state
{
Texture = <ShadowMap>;
MinFilter = POINT;
MagFilter = POINT;
MipFilter = POINT;
AddressU = CLAMP;
AddressV = CLAMP;
};
TextureCube TexCubemapSpecular;
SamplerState TexCubemapSpecularSampler = sampler_state
{
Texture = <TexCubemapSpecular>;
MinFilter = ANISOTROPIC;
MagFilter = ANISOTROPIC;
MipFilter = LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
TextureCube TexCubemapIrradiance;
SamplerState TexCubemapIrradianceSampler = sampler_state
{
Texture = <TexCubemapIrradiance>;
MinFilter = ANISOTROPIC;
MagFilter = ANISOTROPIC;
MipFilter = LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
// Constants
static const float Pi = 3.14159265359;
static const float OneDivPi = 1/Pi;
float2 poisson_disk[16] = {
float2( -0.94201624, -0.39906216 ),
float2( 0.94558609, -0.76890725 ),
float2( -0.094184101, -0.92938870 ),
float2( 0.34495938, 0.29387760 ),
float2( -0.91588581, 0.45771432 ),
float2( -0.81544232, -0.87912464 ),
float2( -0.38277543, 0.27676845 ),
float2( 0.97484398, 0.75648379 ),
float2( 0.44323325, -0.97511554 ),
float2( 0.53742981, -0.47373420 ),
float2( -0.26496911, -0.41893023 ),
float2( 0.79197514, 0.19090188 ),
float2( -0.24188840, 0.99706507 ),
float2( -0.81409955, 0.91437590 ),
float2( 0.19984126, 0.78641367 ),
float2( 0.14383161, -0.14100790 )
};
​
// Lys constants
static const float k0 = 0.00098, k1 = 0.9921, fUserMaxSPow = 0.2425;
static const float g_fMaxT = ( exp2(-10.0/sqrt( fUserMaxSPow )) - k0)/k1;
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float3 Normal : NORMAL0;
float3 Binormal : BINORMAL0;
float3 Tangent : TANGENT0;
};
​
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float3 LightVector : TEXCOORD1;
float3x3 WorldToTangentSpace : TEXCOORD2;
float3 View : COLOR1;
float4 wPosition : COLOR2;
};
​
float random( float3 seed, int i )
{
float4 seed4 = float4( seed, i );
float dot_prod = dot( seed4, float4( 12.9898, 78.233, 45.164, 94.673 ) );
return frac( sin( dot_prod ) * 43758.5453 );
}
​
// Poission blur based on multiple samples
float shadowPoisson( float light_space_depth, float2 shadow_coord )
{
float samples = 8;
float visibility = 1.0f;
//samples
for( int i = 0; i < samples; i++ )
{
visibility -= ( 1 / samples ) * ( light_space_depth < tex2D( ShadowMapSampler, shadow_coord + poisson_disk[i] / 1000 ).r );
}
return 1.0 - visibility;
}
​
// Multisample PCF shadow blur
float calcShadowPCF( float light_space_depth, float2 shadow_coord )
{
float shadow_term = 0;
float shadow_map_size = 2048;
float2 v_lerps = frac( shadow_map_size * shadow_coord );
//safe to assume it's a square
float size = 1.0 / shadow_map_size.x;
float samples[4];
samples[0] = ( light_space_depth-bias < tex2D( ShadowMapSampler, shadow_coord ).r );
samples[1] = ( light_space_depth-bias < tex2D( ShadowMapSampler, shadow_coord + float2( size, 0 ) ).r );
samples[2] = ( light_space_depth-bias < tex2D( ShadowMapSampler, shadow_coord + float2( 0, size ) ).r );
samples[3] = ( light_space_depth-bias < tex2D( ShadowMapSampler, shadow_coord + float2( size, size ) ).r );
shadow_term = lerp( lerp( samples[0], samples[1], v_lerps.x ), lerp( samples[2], samples[3], v_lerps.x ), v_lerps.y );
return shadow_term;
}
​
VertexShaderOutput VertexShaderFunction( VertexShaderInput input, float3 Normal : NORMAL )
{
VertexShaderOutput output;
// convert position and view to world space
output.wPosition = mul( input.Position, World );
float4 viewPosition = mul( output.wPosition, View );
output.Position = mul( viewPosition, Projection );
output.TexCoord = input.TexCoord;
// store Tanget/Bitangent/Normal in world space
output.WorldToTangentSpace[0] = mul( normalize( input.Tangent ), World );
output.WorldToTangentSpace[1] = mul( normalize( input.Binormal), World );
output.WorldToTangentSpace[2] = mul( normalize( input.Normal), World );
// set view vector and full lenght light vector ( we don't normalize the light vector to use it for light fall off )
output.View = normalize( float4( EyePosition, 1.0 ) - output.wPosition );
output.LightVector = ( LightPosition - output.wPosition );
return output;
}
​
// remap values from low1/high1 range to low2/high2
float map( float value, float low1, float high1, float low2, float high2 )
{
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
​
// Lys function
float GetSpecPowToMip( float fSpecPow, int nMips )
{
fSpecPow = 1 - pow(1 - fSpecPow, 8);
// Default curve - Inverse of Toolbag 2 curve with adjusted constants.
float fSmulMaxT = ( exp2( -10.0 / sqrt( fSpecPow ) ) - k0 ) / k1;
return float( nMips - 1 ) * ( 1.0 - clamp( fSmulMaxT / g_fMaxT, 0.0, 1.0 ) );
}
​
float4 psCookTorranceRemottiVariation( VertexShaderOutput input ) : COLOR0
{
// Sample the textures
// NORMAL the Tangent/Normal/Binormal are mized with the texture normals to create final normals
float3 Normal = normalize( ( 2.0f * tex2D( NormalMapSampler, input.TexCoord ).xyz ) - 1.0f );
Normal = normalize(mul(Normal, input.WorldToTangentSpace));
// DIFFUSE texture comes with Albedo Color + some AO backed for some cases of pre occlusion
float3 Diffuse = tex2D( ColorMapSampler, input.TexCoord ).rgb;
// ROUGHNESS = Material.r ( how much blurry is the reflection )
// METALNESS = Material.g ( how much the material reflects )
// OCCUSION = Material.b ( how much occlusion )
float3 Material = tex2D( MaterialMapSampler, input.TexCoord ).rgb;
// Cubemap reflections UVW Coords are created with reflection of world and eye position
float3 vecWorldProjection = input.wPosition + float3(0,1.5,0) - EyePosition;
vecWorldProjection = normalize( vecWorldProjection );
float3 cubeSampCoords = reflect( vecWorldProjection , normalize( Normal + input.WorldToTangentSpace[2] ) );// Normal are reblended with model normals
// Cubamep inversion for reflection
cubeSampCoords.xz = -cubeSampCoords.xz;
float4 cubemapSampleAmbient = texCUBE( TexCubemapSpecularSampler, cubeSampCoords );
float4 cubemapSampleSpec = texCUBE( TexCubemapIrradianceSampler, cubeSampCoords );
float luminance = ( cubemapSampleSpec.r + cubemapSampleSpec.g + cubemapSampleSpec.b) / 3.0;
// Creating specular color and intensity, this needs to be done before gamma correction
float3 specularColor = float3( lerp( 0.04f.rrr, Diffuse.rgb, Material.g) ) * Material.b;
Diffuse.rgb = lerp( Diffuse.rgb, 0.0f.rrr, luminance * Material.g );
// Gamma Correction
cubemapSampleAmbient = pow( abs( cubemapSampleAmbient ), 1.2);
cubemapSampleSpec = pow( abs( cubemapSampleSpec ), 2.2);
Material = pow( abs( Material ), 2.2);
// Normalize input
float3 vecViewDir = normalize( input.View );
float3 vecLightDir = normalize( input.LightVector );
float3 vecHalf = normalize( vecLightDir + vecViewDir );
// Compute Aliases
float NormalDotHalf = dot( Normal, vecHalf );
float vecViewDotHalf = dot( vecHalf, vecViewDir );
float NormalDotView = dot( Normal, vecViewDir );
float NormalDotLight = dot( Normal, vecLightDir );
// Compute the geometric term
float G1 = ( 2.0f * NormalDotHalf * NormalDotView ) / vecViewDotHalf;
float G2 = ( 2.0f * NormalDotHalf * NormalDotLight ) / vecViewDotHalf;
float G = min( 1.0f, max( 0.0f, min( G1, G2 ) ) );
// Compute the fresnel term
float F = Material.g + ( 1.0f - Material.g ) * pow( 1.0f - NormalDotView, 5.0f );
// Compute the roughness term
float R_2 = Material.r * Material.r;
float NDotH_2 = NormalDotHalf * NormalDotHalf;
float A = 1.0f / ( 4.0f * R_2 * NDotH_2 * NDotH_2 );
float B = exp( -( 1.0f - NDotH_2 ) / ( R_2 * NDotH_2 ) );
float R = A * B;
// Compute the final term
float3 S = specularColor * ( G * F * R ) / ( NormalDotLight * NormalDotView );
// Introduce concept of Point Light Falloff
float distance = length( input.LightVector );
// Distance cap
float lightMaxDist = 18.0;
if ( distance > lightMaxDist )distance = lightMaxDist;
distance = LightIntensity - map(distance, 0.0, lightMaxDist, 0.0, LightIntensity);
float diffuseLighting = saturate( dot( Normal, vecLightDir ) ); // per pixel diffuse lighting
diffuseLighting *= distance / dot( vecLightDir, vecLightDir ); // Distance from light
diffuseLighting = clamp( diffuseLighting, 0.0, 1.0 );
// Composite
float3 Final = LightDiffuseColor.rgb * diffuseLighting * ( Diffuse + S );
// Use roughness to mediate between low and high res reflection map ( could be done with mipmaps in Shader Model 4.0 )
float mediator = ( ( GetSpecPowToMip( Material.r, 256 ) ) / 256.0 );
float3 addCube = lerp( cubemapSampleAmbient, cubemapSampleSpec, mediator );
addCube *= Material.g * 2;
// add the diffuse ( consider that the Diffuse has already been mediated by the Metalness value before gamma correction )
addCube += saturate( Diffuse * 0.5 + Diffuse * luminance );
Final += addCube;
// Add in occlusion
float occlusion = 1 - ( 1 - Material.b ) * 0.75f;
Final = saturate( Final * occlusion );
// Find the position of this pixel in light space
float4 lightingPosition = mul( input.wPosition, LightViewProj );
// Find the position in the shadow map for this pixel
float2 ShadowTexCoord = 0.5 * lightingPosition.xy / lightingPosition.w + float2( 0.5, 0.5 );
ShadowTexCoord.y = 1.0f - ShadowTexCoord.y;
// Get the current depth stored in the shadow map
float shadowdepth = tex2D( ShadowMapSampler, ShadowTexCoord ).r;
// Calculate the current pixel depth
float ourdepth = ( lightingPosition.z / lightingPosition.w ) - bias;
// Check to see if this pixel is in front or behind the value in the shadow map
if ( shadowdepth < ourdepth )
{
// Shadow the pixel by lowering the intensity
float intensity = saturate( ( log( distance ) ) );
// Blur the shadows using multiple neighbour pixel depths
float2 screen_pos = 0.5 + float2( lightingPosition.x, -lightingPosition.y ) * 0.5;
float light_space_depth = lightingPosition.z;
float shadow_term = shadowPoisson( light_space_depth, screen_pos);
Final -= Final * ( 1 - saturate( ( 0.15 + intensity * 0.85 ) + shadow_term ) );
};
float diffuseIntensity = saturate( dot( LightDirection, Normal ) );
Final = saturate( Final + ( Final * diffuseIntensity ) );
return float4( saturate( Final ).rgb, 1.0f );
}
struct CreateShadowMap_VSIn
{
float4 Position : POSITION0;
};
struct CreateShadowMap_VSOut
{
float4 Position : POSITION0;
float Depth : TEXCOORD0;
};
​
// Transforms the model into light space and outputs the depth of the object
CreateShadowMap_VSOut CreateShadowMap_VertexShader( CreateShadowMap_VSIn input )
{
CreateShadowMap_VSOut Out;
Out.Position = mul( input.Position, mul( World, LightViewProj ) );
Out.Depth = Out.Position.z / Out.Position.w;
return Out;
}
​
// Saves the depth value out to the 32bit floating point texture
float4 CreateShadowMap_PixelShader( CreateShadowMap_VSOut input ) : COLOR
{
return float4( input.Depth, 0, 0, 0 );
}
​
struct CreateSSAOMap_VSIn
{
float4 Position : POSITION0;
};
​
struct CreateSSAOMap_VSOut
{
float4 Position : POSITION0;
float SSAODepth : TEXCOORD0;
};
​
// Transforms the model into light space and outputs the depth of the object
CreateSSAOMap_VSOut CreateSSAOMap_VertexShader( CreateSSAOMap_VSIn input )
{
CreateSSAOMap_VSOut Out;
Out.Position = mul( input.Position, WorldViewProjection );
Out.SSAODepth = Out.Position.z / FarPlane;
return Out;
}
​
// Saves the depth value out to the 32bit floating point texture
float4 CreateSSAOMap_PixelShader( CreateSSAOMap_VSOut input ) : COLOR
{
return float4( 1 - input.SSAODepth, 0, 0, 0 );
}
​
technique CookTorranceRemottiVariation
{
pass Pass1
{
// TODO: set renderstates here.
CullMode = CCW;
AlphaBlendEnable = FALSE;
ZEnable = TRUE;
ZWriteEnable = TRUE;
VertexShader = compile vs_3_0 VertexShaderFunction();
PixelShader = compile ps_3_0 psCookTorranceRemottiVariation();
}
}
​
// Technique to create the shadow map
technique CreateShadowMap
{
pass Pass1
{
CullMode = CW;
VertexShader = compile vs_3_0 CreateShadowMap_VertexShader();
PixelShader = compile ps_3_0 CreateShadowMap_PixelShader();
}
}
​
// Technique to create the shadow map
technique CreateSSAOMap
{
pass Pass1
{
CullMode = CCW;
VertexShader = compile vs_3_0 CreateSSAOMap_VertexShader();
PixelShader = compile ps_3_0 CreateSSAOMap_PixelShader();
}
}