Parallax Occlusion Mapping
Posted: Tue Feb 18, 2014 12:25 pm
Here's a little example showing Parallax Occlusion Mapping. I found it on the Ogre forums several months ago and unfortunately I don't remember the link, but I do remember that it is free to use.
It's a HLSL shader meaning it will only work under DirectX, but unlike my past shaders the cg.dll is not required.
If people want OpenGL support it should be easy to setup, but I'm busy at the moment. So, you'll have to be patient until I find the time to set it up.
Supported graphic cards are from : ATI Radeon HD 2000+, nVidia GeForce FX 6 series or better.
I'd post a picture of the effect, but the site I used for image hosting is now charging a fee. So, I'm going to find a new one.
I did include the picture in the download. Kinda pointless I know, but it's there.
I'm in a bit of a hurry. So, for the time being you will have to use my download to get the source and example.
Later today or tomorrow I'll post the code with a better explanation about the shader.
Let me know if you have any problems.
ParallaxOcclusionMapping.zip
EDIT:
With POM shading you need to use a normal map with the height set as alpha. Otherwise it won't look right.
You can look at BeachStoneNH.png in my download for an example. Just open it in gimp or something that supports alpha and you'll
see what I'm talking about.
Here's the source. If you want the textures you'll have to get them from my download or create your own for the time being.
Purebasic code. You'll need to set the 3D Archive to the directory containing the textures and shader files.
POM.material
POM.program
POM.hlsl
It's a HLSL shader meaning it will only work under DirectX, but unlike my past shaders the cg.dll is not required.
If people want OpenGL support it should be easy to setup, but I'm busy at the moment. So, you'll have to be patient until I find the time to set it up.
Supported graphic cards are from : ATI Radeon HD 2000+, nVidia GeForce FX 6 series or better.
I'd post a picture of the effect, but the site I used for image hosting is now charging a fee. So, I'm going to find a new one.
I did include the picture in the download. Kinda pointless I know, but it's there.
I'm in a bit of a hurry. So, for the time being you will have to use my download to get the source and example.
Later today or tomorrow I'll post the code with a better explanation about the shader.
Let me know if you have any problems.
ParallaxOcclusionMapping.zip
EDIT:
With POM shading you need to use a normal map with the height set as alpha. Otherwise it won't look right.
You can look at BeachStoneNH.png in my download for an example. Just open it in gimp or something that supports alpha and you'll
see what I'm talking about.
Here's the source. If you want the textures you'll have to get them from my download or create your own for the time being.
Purebasic code. You'll need to set the 3D Archive to the directory containing the textures and shader files.
Code: Select all
UsePNGImageDecoder()
If InitEngine3D(#PB_Engine3D_DebugLog)
InitSprite()
InitKeyboard()
Enumeration
#Window
#Font
#Help
#Camera
#Light
#Node0
#Node1
#Texture
#Material
#ScriptMaterial
#PlaneMesh
#CubeMesh
#PlaneEntity
#CubeEntity
EndEnumeration
ExamineDesktops()
DeskTopW = DesktopWidth(0)
DeskTopH = DesktopHeight(0)
Wireframe=0
Material=0
ShowHelp=0
If LoadFont(#Font, "Courier New", 10,#PB_Font_Bold)
SetGadgetFont(#PB_Default, FontID(#Font))
EndIf
CPU$ = CPUName()
OpenWindow(#Window, 0, 0, DeskTopW, DeskTopH, "Parallax")
OpenWindowedScreen(WindowID(0), 0, 0, DeskTopW, DeskTopH)
Add3DArchive("/", #PB_3DArchive_FileSystem)
Parse3DScripts()
CreateSprite(#Help, 280, 180)
StartDrawing(SpriteOutput(#Help))
Box(0,0,280,180,RGB(0,0,0))
StopDrawing()
CreateCube(#CubeMesh,12)
BuildMeshTangents(#CubeMesh)
CreatePlane(#PlaneMesh,500,500,1,1,20,20)
BuildMeshTangents(#PlaneMesh)
LoadTexture(#Texture,"BeachStones.png")
CreateMaterial(#Material,TextureID(#Texture))
SetMaterialColor(#Material, #PB_Material_SelfIlluminationColor, RGB(20,20,20))
SetMaterialColor(#Material, #PB_Material_SpecularColor, RGB(255,255,255))
MaterialShininess(#Material,30)
ScaleMaterial(#Material, 0.5, 0.5)
GetScriptMaterial(#ScriptMaterial,"MaterialPOM")
CreateEntity(#CubeEntity,MeshID(#CubeMesh),MaterialID(#ScriptMaterial),0,0,0)
CreateEntity(#PlaneEntity,MeshID(#PlaneMesh),MaterialID(#ScriptMaterial),0,-10,0)
CreateCamera(#Camera, 0, 0,100,100)
MoveCamera(#Camera, 0, 0, 30)
CameraLookAt(#Camera, 0, 0, 0)
CameraBackColor(#Camera, RGB(100, 100, 100))
CreateLight(#Light, RGB(50,0,0), 0,4,8)
SetLightColor(#Light, #PB_Light_DiffuseColor, RGB(250,250,250))
SetLightColor(#Light, #PB_Light_SpecularColor, RGB(255,255,255))
AmbientColor(RGB(0,0,0))
CreateNode(#Node0,0,0,0)
AttachNodeObject(#Node0, LightID(#Light))
CreateNode(#Node1,0,0,0)
AttachNodeObject(#Node1,CameraID(#Camera))
Repeat
If ExamineKeyboard()
If KeyboardPushed(#PB_Key_Up)
MoveCamera(#Camera, 0, 0, -0.2)
ElseIf KeyboardPushed(#PB_Key_Down)
MoveCamera(#Camera, 0, 0, 0.2)
EndIf
If KeyboardPushed(#PB_Key_Left)
RotateNode(#Node1,0,-1,0,#PB_Relative)
CameraLookAt(#Camera, 0, 0, 0)
ElseIf KeyboardPushed(#PB_Key_Right)
RotateNode(#Node1,0,1,0,#PB_Relative)
CameraLookAt(#Camera, 0, 0, 0)
EndIf
If KeyboardReleased(#PB_Key_W)
If Wireframe=0
CameraRenderMode(#Camera, #PB_Camera_Wireframe)
Wireframe=1
Else
CameraRenderMode(#Camera, #PB_Camera_Textured)
Wireframe=0
EndIf
EndIf
If KeyboardReleased(#PB_Key_E)
If Material=0
SetEntityMaterial(#CubeEntity, MaterialID(#Material))
SetEntityMaterial(#PlaneEntity, MaterialID(#Material))
Material=1
Else
SetEntityMaterial(#CubeEntity, MaterialID(#ScriptMaterial))
SetEntityMaterial(#PlaneEntity, MaterialID(#ScriptMaterial))
Material=0
EndIf
EndIf
If KeyboardReleased(#PB_Key_H)
If ShowHelp=0
ShowHelp=1
Else
ShowHelp=0
EndIf
EndIf
EndIf
RotateNode(#Node0,0,0,1,#PB_Relative)
RenderWorld()
If ShowHelp=0
CurrentFPS = Engine3DFrameRate(#PB_Engine3D_Current)
AverageFPS = Engine3DFrameRate(#PB_Engine3D_Average)
MaximumFPS = Engine3DFrameRate(#PB_Engine3D_Maximum)
MinimumFPS = Engine3DFrameRate(#PB_Engine3D_Minimum)
CountTris=CountRenderedTriangles()
StartDrawing(SpriteOutput(#Help))
Box(0,0,280,180,RGB(40,40,40))
DrawingFont(FontID(#Font))
DrawText(2,2,CPU$,RGB(255,0,0),RGB(40,40,40))
DrawText(2,22,"Current FPS : "+Str(CurrentFPS),RGB(0,255,255),RGB(40,40,40))
DrawText(2,42,"Average FPS : "+Str(AverageFPS),RGB(0,255,255),RGB(40,40,40))
DrawText(2,62,"Maximum FPS : "+Str(MaximumFPS),RGB(0,255,255),RGB(40,40,40))
DrawText(2,82,"Minimum FPS : "+Str(MinimumFPS),RGB(0,255,255),RGB(40,40,40))
DrawText(2,102,"Rendered Triangles : "+Str(CountTris),RGB(0,255,0),RGB(40,40,40))
DrawText(2,122,"Press W for wireframe",RGB(200,200,200),RGB(40,40,40))
DrawText(2,142,"Press E to view plain material",RGB(255,255,0),RGB(40,40,40))
DrawText(2,162,"Press H to hide help",RGB(255,200,255),RGB(40,40,40))
StopDrawing()
DisplayTransparentSprite(#Help,20,20)
If FirstFrame=0
Engine3DFrameRate(#PB_Engine3D_Reset)
FirstFrame=1
EndIf
EndIf
FlipBuffers()
Until WindowEvent() = #PB_Event_CloseWindow Or KeyboardPushed(#PB_Key_Escape)
EndIf
End
Code: Select all
abstract material POM
{
technique
{
pass
{
vertex_program_ref POM_Vert_hlsl
{
param_named scale float $scale
param_named fHeightMapScale float $depth
}
fragment_program_ref POM_Frag_hlsl
{
param_named spec_exponent float $specular_exponent
param_named spec_factor float $specular_factor
}
texture_unit
{
texture $bump_map
tex_coord_set 0
}
texture_unit
{
texture $diffuse_map
tex_coord_set 0
}
}
}
technique
{
pass
{
texture_unit
{
texture $diffuse_map
}
}
}
}
material MaterialPOM : POM
{
set $scale 2
set $depth 0.1
set $specular_exponent 128
set $specular_factor 0.6
set $bump_map BeachStonesNH.png
set $diffuse_map BeachStones.png
}
Code: Select all
vertex_program POM_Vert_hlsl hlsl
{
source POM.hlsl
entry_point POM_Vert
target vs_3_0
default_params
{
param_named scale float 1
param_named_auto lightPosition light_position_object_space 0
param_named_auto eyePosition camera_position_object_space
param_named_auto worldViewProj worldviewproj_matrix
param_named_auto lightAttenuation light_attenuation 0
}
}
fragment_program POM_Frag_hlsl hlsl
{
source POM.hlsl
entry_point POM_Frag
target ps_3_0
default_params
{
param_named_auto lightDiffuse light_diffuse_colour 0
param_named_auto lightSpecular light_specular_colour 0
param_named_auto lightAmbient ambient_light_colour 0
param_named spec_exponent float 127
param_named spec_factor float 0.5
}
}
Code: Select all
float3 expand(float3 v)
{
return (v - 0.5) * 2;
}
void POM_Vert(float4 position : POSITION,
float3 normal : NORMAL,
float2 uv : TEXCOORD0,
float3 tangent : TANGENT0,
// outputs
out float4 oPosition : POSITION,
out float2 oUv : TEXCOORD0,
out float3 oLightDir : TEXCOORD1,
out float3 oEyeDir : TEXCOORD2,
out float3 oNormal : TEXCOORD3,
out float oAttenuation: TEXCOORD4,
out float2 oParallaxOffsetTS : TEXCOORD5,
// parameters
uniform float fHeightMapScale,
uniform float scale,
uniform float4 lightPosition,
uniform float3 eyePosition,
uniform float4x4 worldViewProj,
uniform float4 lightAttenuation)
{
// calculate output position
oPosition = mul(worldViewProj, position);
// pass the main uvs straight through unchanged
oUv = uv * scale;
float Dist = distance(mul(worldViewProj, lightPosition), mul(worldViewProj, position));
oAttenuation = 1/(lightAttenuation.y + lightAttenuation.z * Dist + lightAttenuation.w * Dist * Dist);
// calculate tangent space light vector
// Get object space light direction
float3 lightDir = normalize(lightPosition.xyz - (position * lightPosition.w));
float3 eyeDir = eyePosition - position.xyz;
// Calculate the binormal (NB we assume both normal and tangent are
// already normalised)
// NB looks like nvidia cross params are BACKWARDS to what you'd expect
// this equates to NxT, not TxN
float3 binormal = cross(tangent, normal);
// Form a rotation matrix out of the vectors
float3x3 rotation = float3x3(tangent, binormal, normal);
// Transform the light vector according to this matrix
lightDir = (mul(rotation, lightDir));
eyeDir = (mul(rotation, eyeDir));
oNormal = (mul(rotation, normal));
oLightDir = lightDir;
oEyeDir = eyeDir;
// 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( oEyeDir.xy );
// The length of this vector determines the furthest amount of displacement:
float fLength = length( oEyeDir );
float fParallaxLength = sqrt( fLength * fLength - oEyeDir.z * oEyeDir.z ) / oEyeDir.z;
// Compute the actual reverse parallax displacement vector:
oParallaxOffsetTS = 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:
oParallaxOffsetTS *= fHeightMapScale;
}
void POM_Frag(
float2 uv : TEXCOORD0,
float3 lightVec : TEXCOORD1,
float3 eyeDir : TEXCOORD2,
float3 iNormal: TEXCOORD3,
float Attenuation: TEXCOORD4,
float2 vParallaxOffsetTS : TEXCOORD5,
out float4 oColor : COLOR,
uniform float4 lightDiffuse,
uniform float4 lightAmbient,
uniform float4 lightSpecular,
uniform float spec_exponent,
uniform float spec_factor,
uniform float fHeightMapScale,
uniform sampler2D normalHeightMap : register(s0),
uniform sampler2D diffuseMap : register(s1)
)
{
eyeDir = normalize(eyeDir);
lightVec = normalize(lightVec);
float3 halfAngle = normalize(eyeDir + lightVec);
//nMinSamples = 12
//nMaxSamples = 60
float nMinSamples = 30;
float nMaxSamples = 60;
float3 N = normalize( iNormal );
int nNumSamples = (int)lerp( nMinSamples, nMaxSamples, dot( eyeDir, N ) );
float fStepSize = 1.0 / (float)nNumSamples;
float2 dx, dy;
dx = ddx( uv );
dy = ddy( uv );
float fCurrHeight = 0.0;
float fPrevHeight = 1.0;
float fNextHeight = 0.0;
int nStepIndex = 0;
float2 vTexOffsetPerStep = fStepSize * vParallaxOffsetTS;
float2 vTexCurrentOffset = uv;
float fCurrentBound = 1.0;
float fParallaxAmount = 0.0;
float2 pt1 = 0;
float2 pt2 = 0;
float2 texOffset2 = 0;
while ( nStepIndex < nNumSamples )
{
vTexCurrentOffset -= vTexOffsetPerStep;
// Sample height map which in this case is stored in the alpha channel of the normal map:
fCurrHeight = tex2Dgrad( normalHeightMap, vTexCurrentOffset, dx, dy ).a;
fCurrentBound -= fStepSize;
if ( fCurrHeight > fCurrentBound )
{
pt1 = float2( fCurrentBound, fCurrHeight );
pt2 = float2( fCurrentBound + fStepSize, fPrevHeight );
texOffset2 = vTexCurrentOffset - vTexOffsetPerStep;
nStepIndex = nNumSamples + 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 = vParallaxOffsetTS * (1 - fParallaxAmount );
// The computed texture offset for the displaced point on the pseudo-extruded surface:
float2 newTexCoord = uv - vParallaxOffset;
float3 PixelNormal = expand(tex2D(normalHeightMap, newTexCoord).xyz);
PixelNormal = normalize(PixelNormal);
float3 diffuse = tex2D(diffuseMap, newTexCoord).xyz;
float NdotL = dot(normalize(lightVec), PixelNormal);
float NdotH = dot(normalize(halfAngle), PixelNormal);
float4 Lit = lit(NdotL,NdotH,spec_exponent);
//oColor = float4(diffuse, 1);
float3 col = lightAmbient * diffuse + ((diffuse * Lit.y * lightDiffuse + lightSpecular * Lit.z * spec_factor) * Attenuation);
oColor = float4(col, 1);
}



