Implementing parallax occlusion mapping

rated by 0 users
This post has 6 Replies | 2 Followers

Top 100 Contributor
Posts 24
SunBurn_Pro_Licensee
style88 Posted: 01-02-2010 2:25 PM

I'm having a little trouble implementing parallax occlusion mapping through the text editor.

Basically, I need to alter the shader itself in order to implement the silhouette rendering. However, I need to understand how to implement the shader and how it works before I can do this.

I apologize in advance if the shader technology is proprietary, though in that case, I could also use some advice in implementing my own modified parallax occlusion shader.

Thank you in advance for your help,

Style

Top 10 Contributor
Posts 4,546
Employee
SunBurn_Studio_Licensee

Hi style88,

SunBurn's built-in shaders are compiled into the assembly and cannot be edited, however adding custom shaders is relatively easy.

It looks like the parallax implementation you're working on offsets the coords used to sample the diffuse and normal map and uses "clip" to cut out the silhouette edges.  This can be added to a custom shader (see the Custom Effect example) as a function run at the beginning of the main pixel shader:

// pseudo code
void MainPS(..)
{
    float2 uvs = ParallaxWithSilhouetteEdges(input.originalUV);

    // normal shader code, now using the new uvs.
}

Let me know if this helps!

Follow me on Twitter – development and personal tweets
Awesome XNA Videos – Lighting, Rendering, and game videos

Top 100 Contributor
Posts 24
SunBurn_Pro_Licensee

Forgive me, I'm fairly new to shader programming.

I have been able to get a shader program I found online to work in the Sunburn rendering framework, but I can't seem to implement the clip () functions.

Within the .fx file, I've implemented a function which I found in a forum. The clip() functions do work in another C# program - the program would be very difficult to port into a working XNA framework. Perhaps Sunburn alters the UV coords?

Here's a link to my work so far:

http://www.4shared.com/file/191787002/240a8620/wobble-deferred.html

Using the Custom Effects Example (http://www.synapsegaming.com/media/g/custom_effects_example/default.aspx) and then unzipping the .zip file to the following path: 

\SunBurn-CustomEffects-Example\Content\Models\wobble-deferred

Should get the custom POM shader working.

The custom POM shader is of itself useful - it allows such things as the number of iterations to be altered, with a few changes to the .mat file, and seems to render lighting nicely. However, I don't know about the number of lights or the accuracy yet.

If anyone needs help, don't hesitate to ask! I'll keep working on this and keep you updated on my progress.

Edit:

The file is a .x file, so you'll also need to exclude the .fbx file and include the .x file.

Top 100 Contributor
Posts 24
SunBurn_Pro_Licensee

If you replace the included .fx file with the following, you should get the shader running with silhouettes.

 

texture g_DiffuseTexture : DIFFUSE
<
    string Name = "D.png";
    string UIName = "DiffuseTexture";
    string TextureType = "2D";
>;
sampler2D tBase = sampler_state
{
    Texture = <g_DiffuseTexture>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
};
 
texture g_NormalTexture : NORMAL
<
    string Name = "N.png";
    string UIName = "NormalTexture";
    string TextureType = "2D";
>;
sampler2D tNormalHeightMap = sampler_state
{
    Texture = <g_NormalTexture>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
};
 
float4x4 m_world : World < string UIWidget="None"; >;
float4x4 m_worldInv : WorldInverse < string UIWidget="None"; >;
float4x4 m_wvp : WorldViewProjection < string UIWidget="None"; >;
float4x4 m_worldView : WorldView < string UIWidget="None"; >;
float4x4 m_viewInv    :   ViewInverse < string UIWidget="None"; >;
 
float4 g_materialAmbientColor <
    string UIWidget = "mat Amb Color";
> = {0.0f, 0.0f, 0.0f, 0.0f};     // Material's ambient color
 
float4 g_materialDiffuseColor <
    string UIWidget = "mat Diff Color";
> = {1.0f, 1.0f, 1.0f, 1.0f};      // Material's diffuse color
 
float4 g_materialSpecularColor <
    string UIWidget = "mat Spec Color";
> = {1.0f, 1.0f, 1.0f, 1.0f};     // Material's specular color
 
float  g_fSpecularExponent <
    string UIWidget = "slider";
    float UIMin = 1;
    float UIMax = 256;
    float UIStep = 1;
    string UIName = "Glossiness";
> = 32;         // Material's specular exponent
 
float3 g_LightDir : POSITION
<
    string UIName = "Light Position";
    string Object = "PointLight";
    string Space = "World";
    int refID = 1;
> = {100.0f, 100.0f, 100.0f};
 
float4 g_LightColor : LIGHTCOLOR
<
int LightRef = 1;
string UIType = "None";
> = { 1.0f, 1.0f, 1.0f, 1.0f};
 
float    g_fHeightMapScale <
    string UIWidget = "slider";
    float UIMin = 0;
    float UIMax = 1;
    float UIStep = .01;
    string UIName = "height scale";
> = 0;
 
int    g_nMinSamples <
    string UIWidget = "slider";
    int UIMin = 1;
    int UIMax = 200;
    int UIStep = 1;
    string UIName = "min samples";
> = 20;
 
int    g_nMaxSamples <
    string UIWidget = "slider";
    int UIMin = 2;
    int UIMax = 201;
    int UIStep = 1;
    string UIName = "max samples";
> = 60;
 
/////STRUCTS///////////
struct VS_IN
{
    float4 Position : POSITION0;
    float2 texCoord      : TEXCOORD0;
    float3 Normal   : NORMAL0;
    float3 Binormal : BINORMAL0;
    float3 Tangent  : TANGENT0;
};
 
struct VS_OUT
{
    float4 position          : POSITION;
    float2 texCoord          : TEXCOORD0;
    float3 vLightTS          : TEXCOORD1;   // light vector in tangent space, denormalized
    float3 vViewTS           : TEXCOORD2;   // view vector in tangent space, denormalized
    float2 vParallaxOffsetTS : TEXCOORD3;   // Parallax offset vector in tangent space
    float3 vNormalWS         : TEXCOORD4;   // Normal vector in world space
    float3 vViewWS           : TEXCOORD5;   // View vector in world space
 
};
 
/////VERTEX SHADER////////////
VS_OUT pomVS( VS_IN In)
{
    VS_OUT Out;
 
    // Transform and output input position
    Out.position = mul( In.Position, m_wvp );
 
    // Propagate texture coordinate through:
    Out.texCoord = In.texCoord;
 
    // Transform the normal, tangent and binormal vectors from object space to homogeneous projection space:
    float3 vNormalWS   = mul( In.Normal,   m_world );
    float3 vTangentWS  = mul( In.Tangent,  m_world );
    float3 vBinormalWS = mul( In.Binormal, m_world );
 
    // Propagate the world space vertex normal through:   
    Out.vNormalWS = vNormalWS;
 
    vNormalWS   = normalize( vNormalWS );
    vTangentWS  = normalize( vTangentWS );
    vBinormalWS = normalize( vBinormalWS );
 
    // Compute position in world space:
    float4 vPositionWS = mul( In.Position, m_world );
 
    // Compute and output the world view vector (unnormalized):
    float3 g_vEye = m_viewInv[3].xyz;
    float3 vViewWS = g_vEye - vPositionWS;
    Out.vViewWS = vViewWS;
 
    // Compute denormalized light vector in world space:
    float3 vLightWS = g_LightDir;
 
    // Normalize the light and view vectors and transform it to the tangent space:
    float3x3 mWorldToTangent = float3x3( vTangentWS, vBinormalWS, vNormalWS );
 
    // Propagate the view and the light vectors (in tangent space):
    Out.vLightTS = mul( vLightWS, mWorldToTangent );
    Out.vViewTS  = mul( mWorldToTangent, vViewWS  );
 
    // Compute the ray direction for intersecting the height field profile with
    // current view ray. See the above paper for derivation of this computation.
 
    // Compute initial parallax displacement direction:
    float2 vParallaxDirection = normalize(  Out.vViewTS.xy );
 
    // The length of this vector determines the furthest amount of displacement:
    float fLength         = length( Out.vViewTS );
    float fParallaxLength = sqrt( fLength * fLength - Out.vViewTS.z * Out.vViewTS.z ) / Out.vViewTS.z;
 
    // Compute the actual reverse parallax displacement vector:
    Out.vParallaxOffsetTS = vParallaxDirection * fParallaxLength;
 
    // Need to scale the amount of displacement to account for different height ranges
    // in height maps. This is controlled by an artist-editable parameter:
    Out.vParallaxOffsetTS *= g_fHeightMapScale;
 
   return Out;
}
 
//--------------------------------------------------------------------------------------
// Function:    ComputeIllumination
//
// Description: Computes phong illumination for the given pixel using its attribute
//              textures and a light vector.
//--------------------------------------------------------------------------------------
float4 ComputeIllumination( float2 texCoord, float3 vLightTS, float3 vViewTS, float fOcclusionShadow )
{
   // Sample the normal from the normal map for the given texture sample:
   float3 vNormalTS = normalize( tex2D( tNormalHeightMap, texCoord ) * 2 - 1 );
 
   // Sample base map:
   float4 cBaseColor = tex2D( tBase, texCoord );
 
   // Compute diffuse color component:
   float3 vLightTSAdj = float3( vLightTS.x, -vLightTS.y, vLightTS.z );
 
   float4 cDiffuse = saturate( dot( vNormalTS, vLightTSAdj )) * g_materialDiffuseColor;
 
   // Compute the specular component if desired:  
   float4 cSpecular = 0;
    float3 vReflectionTS = normalize( 2 * dot( vViewTS, vNormalTS ) * vNormalTS - vViewTS );
 
    float fRdotL = saturate( dot( vReflectionTS, vLightTSAdj ));
    cSpecular = saturate( pow( fRdotL, g_fSpecularExponent )) * g_materialSpecularColor;
 
   // Composite the final color:
   float4 cFinalColor = (( g_materialAmbientColor + cDiffuse ) * cBaseColor + cSpecular ) * fOcclusionShadow;
 
   return cFinalColor;  
}   
 
//--------------------------------------------------------------------------------------
// Pixel shader output structure
//--------------------------------------------------------------------------------------
struct PS_OUTPUT
{
    float4 RGBColor : COLOR0;  // Pixel color    
};
 
struct PS_INPUT
{
   float2 texCoord          : TEXCOORD0;
   float3 vLightTS          : TEXCOORD1;   // light vector in tangent space, denormalized
   float3 vViewTS           : TEXCOORD2;   // view vector in tangent space, denormalized
   float2 vParallaxOffsetTS : TEXCOORD3;   // Parallax offset vector in tangent space
   float3 vNormalWS         : TEXCOORD4;   // Normal vector in world space
   float3 vViewWS           : TEXCOORD5;   // View vector in world space
};
 
//--------------------------------------------------------------------------------------
// Parallax occlusion mapping pixel shader
//
// Note: this shader contains several educational modes that would not be in the final
//       game or other complicated scene rendering. The blocks of code in various "if"
//       statements for turning off visual qualities (such as visual level of detail
//       or specular or shadows, etc), can be handled differently, and more optimally.
//       It is implemented here purely for educational purposes.
//--------------------------------------------------------------------------------------
float4 RenderSceneParallaxMappingPS( PS_INPUT i ) : COLOR0
{
 
   //  Normalize the interpolated vectors:
   float3 vViewTS   = normalize( i.vViewTS  );
   float3 vViewWS   = normalize( i.vViewWS  );
   float3 vLightTS  = normalize( i.vLightTS );
   float3 vNormalWS = normalize( i.vNormalWS );
 
   float4 cResultColor = float4( 0, 0, 0, 1 );
 
   // Adaptive in-shader level-of-detail system implementation. Compute the
   // current mip level explicitly in the pixel shader and use this information
   // to transition between different levels of detail from the full effect to
   // simple bump mapping. See the above paper for more discussion of the approach
   // and its benefits.
 
   // Compute the current gradients:
   float2 g_vTextureDims = float2(1,1);
   float2 fTexCoordsPerSize = i.texCoord * g_vTextureDims;
 
   // Compute all 4 derivatives in x and y in a single instruction to optimize:
   float2 dxSize, dySize;
   float2 dx, dy;
 
   float4( dxSize, dx ) = ddx( float4( fTexCoordsPerSize, i.texCoord ) );
   float4( dySize, dy ) = ddy( float4( fTexCoordsPerSize, i.texCoord ) );
 
   float  fMipLevel;      
   float  fMipLevelInt;    // mip level integer portion
   float  fMipLevelFrac;   // mip level fractional amount for blending in between levels
 
   float  fMinTexCoordDelta;
   float2 dTexCoords;
 
 
   // Find min of change in u and v across quad: compute du and dv magnitude across quad
   dTexCoords = dxSize * dxSize + dySize * dySize;
 
   // Standard mipmapping uses max here
   fMinTexCoordDelta = max( dTexCoords.x, dTexCoords.y );
 
   // Compute the current mip level  (* 0.5 is effectively computing a square root before )
   fMipLevel = max( 0.5 * log2( fMinTexCoordDelta ), 0 );
 
   // Start the current sample located at the input texture coordinate, which would correspond
   // to computing a bump mapping result:
   float2 texSample = i.texCoord;
 
   // Multiplier for visualizing the level of detail (see notes for 'nLODThreshold' variable
   // for how that is done visually)
   float4 cLODColoring = float4( 1, 1, 3, 1 );
 
   float fOcclusionShadow = 1.0;
 
   float g_nLODThreshold = 4000;
   if ( fMipLevel <= (float) g_nLODThreshold )
   {
      //===============================================//
      // Parallax occlusion mapping offset computation //
      //===============================================//
 
      // Utilize dynamic flow control to change the number of samples per ray
      // depending on the viewing angle for the surface. Oblique angles require
      // smaller step sizes to achieve more accurate precision for computing displacement.
      // We express the sampling rate as a linear function of the angle between
      // the geometric normal and the view direction ray:
      int nNumSteps = (int) lerp( g_nMaxSamples, g_nMinSamples, dot( vViewWS, vNormalWS ) );
 
      // Intersect the view ray with the height field profile along the direction of
      // the parallax offset ray (computed in the vertex shader. Note that the code is
      // designed specifically to take advantage of the dynamic flow control constructs
      // in HLSL and is very sensitive to specific syntax. When converting to other examples,
      // if still want to use dynamic flow control in the resulting assembly shader,
      // care must be applied.
      //
      // In the below steps we approximate the height field profile as piecewise linear
      // curve. We find the pair of endpoints between which the intersection between the
      // height field profile and the view ray is found and then compute line segment
      // intersection for the view ray and the line segment formed by the two endpoints.
      // This intersection is the displacement offset from the original texture coordinate.
      // See the above paper for more details about the process and derivation.
      //
 
      float fCurrHeight = 0.0;
      float fStepSize   = 1.0 / (float) nNumSteps;
      float fPrevHeight = 1.0;
      float fNextHeight = 0.0;
 
      int    nStepIndex = 0;
      bool   bCondition = true;
 
      float2 vTexOffsetPerStep = fStepSize * i.vParallaxOffsetTS;
      float2 vTexCurrentOffset = i.texCoord;
      float  fCurrentBound     = 1.0;
      float  fParallaxAmount   = 0.0;
 
      float2 pt1 = 0;
      float2 pt2 = 0;
 
      float2 texOffset2 = 0;
 
      while ( nStepIndex < nNumSteps )
      {
         vTexCurrentOffset -= vTexOffsetPerStep;
 
         // Sample height map which in this case is stored in the alpha channel of the normal map:
         fCurrHeight = tex2Dgrad( tNormalHeightMap, vTexCurrentOffset, dx, dy ).a;
 
         fCurrentBound -= fStepSize;
 
         if ( fCurrHeight > fCurrentBound )
         {   
            pt1 = float2( fCurrentBound, fCurrHeight );
            pt2 = float2( fCurrentBound + fStepSize, fPrevHeight );
 
            texOffset2 = vTexCurrentOffset - vTexOffsetPerStep;
 
            nStepIndex = nNumSteps + 1;
            fPrevHeight = fCurrHeight;
         }
         else
         {
            nStepIndex++;
            fPrevHeight = fCurrHeight;
         }
      }   
 
      float fDelta2 = pt2.x - pt2.y;
      float fDelta1 = pt1.x - pt1.y;
 
      float fDenominator = fDelta2 - fDelta1;
 
      // SM 3.0 requires a check for divide by zero, since that operation will generate
      // an 'Inf' number instead of 0, as previous models (conveniently) did:
      if ( fDenominator == 0.0f )
      {
         fParallaxAmount = 0.0f;
      }
      else
      {
         fParallaxAmount = (pt1.x * fDelta2 - pt2.x * fDelta1 ) / fDenominator;
      }
 
      float2 vParallaxOffset = i.vParallaxOffsetTS * (1 - fParallaxAmount );
 
      // The computed texture offset for the displaced point on the pseudo-extruded surface:
      float2 texSampleBase = i.texCoord - vParallaxOffset;
      texSample = texSampleBase;
      clip (1+texSample);
      clip (-texSample);
 
   }   
 
   // Compute resulting color for the pixel:
   cResultColor = ComputeIllumination( texSample, vLightTS, vViewTS, fOcclusionShadow );
 
   // If using HDR rendering, make sure to tonemap the resuld color prior to outputting it.
   // But since this example isn't doing that, we just output the computed result color here:
 
   clip (cResultColor);
 
   return cResultColor;
}   
 
float4 bumpPS (VS_OUT In) : COLOR
{
   //  Normalize the interpolated vectors:
   float3 vViewTS   = normalize( In.vViewTS  );
   float3 vLightTS  = normalize( In.vLightTS );
 
   float4 cResultColor = float4( 0, 0, 0, 1 );
 
   // Start the current sample located at the input texture coordinate, which would correspond
   // to computing a bump mapping result:
   float2 texSample = In.texCoord;
 
   // Compute resulting color for the pixel:
   cResultColor = ComputeIllumination( texSample, vLightTS, vViewTS, 1.0f );
 
   // If using HDR rendering, make sure to tonemap the resuld color prior to outputting it.
   // But since this example isn't doing that, we just output the computed result color here:
 
   return cResultColor;
}
 
technique tileRenderer
{
    pass P0
    {          
        VertexShader = compile vs_3_0 pomVS();
        PixelShader  = compile ps_3_0 RenderSceneParallaxMappingPS();
    }
}

Top 10 Contributor
Posts 4,546
Employee
SunBurn_Studio_Licensee

Hi style88,

Cool, is it working correctly for you now?

Follow me on Twitter – development and personal tweets
Awesome XNA Videos – Lighting, Rendering, and game videos

Top 100 Contributor
Posts 24
SunBurn_Pro_Licensee

Yes. I've also added a coarse blend effect, for terrain blending, and transparency support. I've been working on just making the demo now.

Top 10 Contributor
Posts 4,546
Employee
SunBurn_Studio_Licensee

Awesome work!  I can't wait to see what you're working on.  :)

Follow me on Twitter – development and personal tweets
Awesome XNA Videos – Lighting, Rendering, and game videos

Page 1 of 1 (7 items) | RSS