top of page

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

Please reload

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();
    }
}

Please reload

© 2023 by Sphere Construction. Proudly created with Wix.com

  • Facebook - Grey Circle
  • LinkedIn - Grey Circle
  • Google+ - Grey Circle
bottom of page