I’ve been working on this effect for sometime now , here’s a video that demonstrates POM in GFXMaker
here’s the HLSL code for this technique:
////////////////////////////////////////////
struct MATERIAL
{
float4 Diffuse;
float4 Ambient;
float4 Specular;
float4 Emissive;
float Shininess;
};
////////////////////////////////////////////
struct LIGHT
{
unsigned int Type; /* Type of light source */
float4 Diffuse; /* Diffuse color of light */
float4 Specular; /* Specular color of light */
float4 Ambient; /* Ambient color of light */
float3 Position; /* Position in world space */
float3 Direction; /* Direction in world space */
float Range; /* Cutoff range */
float Falloff; /* Falloff */
float Attenuation0; /* Constant attenuation */
float Attenuation1; /* Linear attenuation */
float Attenuation2; /* Quadratic attenuation */
float Theta; /* Inner angle of spotlight cone */
float Phi; /* Outer angle of spotlight cone */
};
////////////////////////////////////////////////////////////////////////////////////////
MATERIAL Material; // The Current Material
bool HasColorMap ,
HasNormalMap,
HasSpecularMap,
HasHeightMap;
texture ColorMap, // Diffuse Map
NormalMap, // Normal Map , used for PerPixel Lighting
SpecularMap,
GlossMap,
HeightMap;
sampler ColorMap_Sampler = sampler_state
{
Texture = ColorMap;
};
sampler SpecularMap_Sampler = sampler_state
{
Texture = SpecularMap;
};
sampler NormalMap_Sampler = sampler_state
{
Texture = NormalMap;
};
sampler GlossMap_Sampler = sampler_state
{
Texture = GlossMap;
};
sampler HeightMap_Sampler = sampler_state
{
Texture = HeightMap;
};
///////////////////////////////////////////////
unsigned int MaxLights = 16,numLights = 0;
LIGHT Lights[16];
////////////////////////////////////////////////////////////////////////////////////////
/////// Shared Vars
////////////////////////////////////////////////////////////////////////////////////////
float4x4 matView ,
matProjection ,
matWorld ,
matWorldInverseTranspose;
float3 ViewerPosition,ViewerDirection;
float3 MeshPivot = float3(0,0,0); // The current mesh’s Pivot/Origin
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Parallax Occlusion Mapping Rendering
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct VS_POMR_OUT
{
float4 Position : POSITION;
float2 UV : TEXCOORD0;
float3 ViewDirTS : TEXCOORD1;
float3 LightDir : TEXCOORD2;
float3 NormalWS : TEXCOORD3;
float3 ViewDirWS : TEXCOORD4;
float2 ParallaxOffset : TEXCOORD5;
};
float HeightMapScale = 0.03f;
int LODThreshold = 3; // The mip level id for transitioning between the full computation
// for parallax occlusion mapping and the bump mapping computation
float ShadowSoftening = 0.12;
int MinimumSamples = 10 , MaximumSamples = 40;
VS_POMR_OUT Parallax_Ocllusion_Mapping_Rendering_VS_Main(VS_INPUT input)
{
VS_POMR_OUT output = (VS_POMR_OUT)0 ;
output.Position = mul(input.Position,mul(mul(matWorld,matView),matProjection));
float3 NormalWS = (mul(input.Normal ,(float3x3)matWorld));
float3 TangentWS = (mul(input.Tangent ,(float3x3)matWorld));
float3 BinormalWS = (mul(input.Binormal,(float3x3)matWorld));
output.NormalWS = NormalWS;
NormalWS = normalize(NormalWS);
TangentWS = normalize(TangentWS);
BinormalWS = normalize(BinormalWS);
float3x3 TangentSpaceMatrix = (float3x3(TangentWS,BinormalWS,NormalWS));
float3 dir;
switch(Lights[0].Type)
{
case 3:
dir = Lights[0].Direction;
break;
default:
dir = Lights[0].Position – mul(input.Position,matWorld);
break;
}
float3 eyedir = ViewerPosition – mul(input.Position,matWorld);
output.UV = input.UV;
output.LightDir = mul(TangentSpaceMatrix,dir);
output.ViewDirWS = eyedir;
output.ViewDirTS = mul(TangentSpaceMatrix,output.ViewDirWS);
float2 ParallaxDirection = normalize(output.ViewDirTS.xy);
float len = length(output.ViewDirTS);
float ParallaxLen = sqrt(len*len-output.ViewDirTS.z*output.ViewDirTS.z)/output.ViewDirTS.z;
output.ParallaxOffset = ParallaxDirection * ParallaxLen;
output.ParallaxOffset *= HeightMapScale;
return output;
}
float4 ComputeIllumination(float3 LightDirTS,float2 TexCoord,float3 ViewDirTS)
{
float3 Normal = normalize(tex2D(NormalMap_Sampler,TexCoord).xyz*2-1);
float3 LightDir = normalize(LightDirTS );
float3 Reflection = normalize(-reflect(LightDir,Normal));
float4 totalSpecular = pow(max(0.0,saturate(dot(Reflection,ViewDirTS))),Material.Shininess) * Material.Specular * Lights[0].Specular;
float4 totalAmbient = Material.Ambient;
float4 totalDiffuse = Material.Diffuse * saturate(dot(LightDir,Normal));
float4 final = saturate((totalAmbient + totalDiffuse)*tex2D(ColorMap_Sampler,TexCoord) + totalSpecular);
return final;
}
float4 Parallax_Ocllusion_Mapping_Rendering_PS_Main(VS_POMR_OUT input) : COLOR
{
float3 ViewDirWS = normalize(input.ViewDirWS);
float3 NormalWS = normalize(input.NormalWS);
float3 ViewDirTS = normalize(input.ViewDirTS);
float2 Offset = input.ParallaxOffset;
float3 NormalTS = normalize(tex2D(NormalMap_Sampler,input.UV).xyz*2-1);
float OcclusionShadow = 1;
float2 fTexCoordsPerSize = input.UV * float2(256,256);
float2 dxSize, dySize;
float2 dx, dy;
float4( dxSize, dx ) = ddx( float4( fTexCoordsPerSize, input.UV ) );
float4( dySize, dy ) = ddy( float4( fTexCoordsPerSize, input.UV ) );
float fMipLevel;
float fMipLevelInt;
float fMipLevelFrac;
float fMinTexCoordDelta;
float2 dTexCoords;
dTexCoords = dxSize * dxSize + dySize * dySize;
fMinTexCoordDelta = max( dTexCoords.x, dTexCoords.y );
fMipLevel = max( 0.5 * log2( fMinTexCoordDelta ), 0 );
float2 texSample = input.UV;
if(fMipLevel<=(float)LODThreshold)
{
int Samples = (int)lerp(MaximumSamples,MinimumSamples,saturate(dot(NormalWS,ViewDirWS)));
float StepSize = 1.0/(float)Samples;
float2 OffsetStep = Offset * StepSize;
float2 CurrentOffset = input.UV;
float CurrentHeight = 0.0, LastHeight = 1.0;
float CurrentBound = 1.0, ParallaxAmount = 0.0;
float2 pt1 = 0, pt2 = 0;
int CurrentSampleID = 0;
float2 texOffset2 = 0;
while(CurrentSampleID<Samples)
{
CurrentOffset -= OffsetStep;
CurrentBound -= StepSize;
CurrentHeight = tex2Dgrad(NormalMap_Sampler,CurrentOffset,dx,dy).a;
if(CurrentHeight > CurrentBound)
{
pt1 = float2( CurrentBound, CurrentHeight );
pt2 = float2( CurrentBound + StepSize, LastHeight );
texOffset2 = CurrentOffset – OffsetStep;
CurrentSampleID = Samples + 1;
}
else
{
CurrentSampleID ++;
LastHeight = CurrentHeight;
}
}
float fDelta2 = pt2.x – pt2.y;
float fDelta1 = pt1.x – pt1.y;
float fDenominator = fDelta2 – fDelta1;
if ( fDenominator == 0.0f )
{
ParallaxAmount = 0.0f;
}
else
{
ParallaxAmount = (pt1.x * fDelta2 – pt2.x * fDelta1 ) / fDenominator;
}
float2 ParallaxOffset = Offset * (1.0 – ParallaxAmount);
float2 TextureSample = input.UV – ParallaxOffset;
texSample = TextureSample;
if(fMipLevel>(float)(LODThreshold-1))
{
fMipLevelFrac = modf( fMipLevel, fMipLevelInt );
texSample = lerp( TextureSample,input.UV,fMipLevelFrac );
}
float2 vLightRayTS = input.LightDir.xy * HeightMapScale;
float sh0 = tex2Dgrad( NormalMap_Sampler, TextureSample, dx, dy ).a;
float shA = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.88, dx, dy ).a – sh0 – 0.88 ) * 1 * ShadowSoftening;
float sh9 = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.77, dx, dy ).a – sh0 – 0.77 ) * 2 * ShadowSoftening;
float sh8 = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.66, dx, dy ).a – sh0 – 0.66 ) * 4 * ShadowSoftening;
float sh7 = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.55, dx, dy ).a – sh0 – 0.55 ) * 6 * ShadowSoftening;
float sh6 = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.44, dx, dy ).a – sh0 – 0.44 ) * 8 * ShadowSoftening;
float sh5 = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.33, dx, dy ).a – sh0 – 0.33 ) * 10 * ShadowSoftening;
float sh4 = (tex2Dgrad( NormalMap_Sampler, TextureSample + vLightRayTS * 0.22, dx, dy ).a – sh0 – 0.22 ) * 12 * ShadowSoftening;
OcclusionShadow = 1 – max( max( max( max( max( max( shA, sh9 ), sh8 ), sh7 ), sh6 ), sh5 ), sh4 );
OcclusionShadow = OcclusionShadow * 0.6 + 0.4;
}
float4 final = ComputeIllumination(input.LightDir,texSample,ViewDirTS)*OcclusionShadow;
return final;
}
technique ParallaxOcclusionMappingRendering
{
pass Pass0
{
VertexShader = compile vs_3_0 Parallax_Ocllusion_Mapping_Rendering_VS_Main();
PixelShader = compile ps_3_0 Parallax_Ocllusion_Mapping_Rendering_PS_Main();
}
}