ATOM-14495 POM Height Bias

Added parallax "Height Offset" properties to StandardPBR and EnhancedPBR.

- Refactored depth pass and shadow pass shaders to use the GetParallaxInput utility.
- Updated EnhancedPBR.materialtype to match the parallax controls and functors of StandardPBR.materialtype.
- Updated EnhancedPBR's shadow pass shader to apply a ShadowMapDepthBias because I noticed StandardPBR is doing this, so I made them match.
- Updated StandardPBR and EnhancedPBR to both use the depth offset property, as well as heightmap clipping debug view.
- Note that depth offset is not supported in StandardMultilayerPBR yet, it's hard-coded to 0 for now.
- Note that I plan to rename a lot of the "depth" terms to "heightmap" or "displacement" in an upcoming commit.
This commit is contained in:
Chris Santora
2021-04-30 00:39:48 -07:00
parent 92199d9f2e
commit d1801bbe47
15 changed files with 253 additions and 200 deletions
@@ -946,19 +946,6 @@
"type": "Bool",
"defaultValue": false
},
{
"id": "factor",
"displayName": "Factor",
"description": "Strength factor for scaling the depth values",
"type": "Float",
"defaultValue": 0.0,
"min": 0.0,
"softMax": 0.1,
"connection": {
"type": "ShaderInput",
"id": "m_depthFactor"
}
},
{
"id": "textureMap",
"displayName": "Texture Map",
@@ -981,6 +968,32 @@
"id": "m_parallaxUvIndex"
}
},
{
"id": "factor",
"displayName": "Heightmap Scale",
"description": "The total height of the heightmap in local model units.",
"type": "Float",
"defaultValue": 0.0,
"min": 0.0,
"softMax": 0.1,
"connection": {
"type": "ShaderInput",
"id": "m_depthFactor"
}
},
{
"id": "offset",
"displayName": "Offset",
"description": "Adjusts the overall displacement amount in local model units.",
"type": "Float",
"defaultValue": 0.0,
"softMin": -0.1,
"softMax": 0.1,
"connection": {
"type": "ShaderInput",
"id": "m_depthOffset"
}
},
{
"id": "invert",
"displayName": "Invert",
@@ -1026,6 +1039,17 @@
"type": "ShaderOption",
"id": "o_parallax_enablePixelDepthOffset"
}
},
{
"id": "showClipping",
"displayName": "Show Clipping",
"description": "Highlight areas where the heightmap is clipped by the mesh surface.",
"type": "Bool",
"defaultValue": false,
"connection": {
"type": "ShaderOption",
"id": "o_parallax_highlightClipping"
}
}
],
"subsurfaceScattering": [
@@ -1714,22 +1738,6 @@
"shaderOption": "o_emissive_useTexture"
}
},
{
// See the comment above for details.
"type": "UseTexture",
"args": {
"textureProperty": "parallax.textureMap",
"dependentProperties": ["parallax.textureMapUv"],
"useTextureProperty": "parallax.enable",
"shaderTags": [
"ForwardPass",
"ForwardPass_EDS",
"Shadowmap_WithPS",
"DepthPass_WithPS"
],
"shaderOption": "o_parallax_feature_enabled"
}
},
{
// See the comment above for details.
"type": "UseTexture",
@@ -1813,34 +1821,6 @@
]
}
},
{
// Controls visibility for properties in the editor.
// @param actions - a list of actions that are executed in order. visibility will be set when triggerProperty hits the triggerValue.
// @param affectedProperties - the properties that are affected by actions.
"type": "UpdatePropertyVisibility",
"args": {
"actions": [
{
"triggerProperty": "parallax.enable",
"triggerValue": true,
"visibility": "Enabled"
},
{
"triggerProperty": "parallax.enable",
"triggerValue": false,
"visibility": "Hidden"
}
],
"affectedProperties": [
"parallax.factor",
"parallax.textureMap",
"parallax.invert",
"parallax.algorithm",
"parallax.quality",
"parallax.pdo"
]
}
},
{
"type": "UpdatePropertyVisibility",
"args": {
@@ -2076,6 +2056,12 @@
]
}
},
{
"type": "Lua",
"args": {
"file": "StandardPBR_ParallaxState.lua"
}
},
{
"type": "Lua",
"args": {
@@ -90,30 +90,12 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex];
float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex];
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor,
IN.m_uv[MaterialSrg::m_parallaxUvIndex],
ViewSrg::m_worldPosition.xyz - IN.m_worldPosition,
tangent,
bitangent,
IN.m_normal,
uvMatrix);
PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor,
tangentOffset,
IN.m_worldPosition,
tangent,
bitangent,
IN.m_normal,
uvMatrixInverse,
ObjectSrg::GetWorldMatrix(),
ViewSrg::m_viewProjectionMatrix);
OUT.m_depth = pdo.m_depth;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
}
return OUT;
}
@@ -115,6 +115,8 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
// ------- Depth & Parallax -------
depth = IN.m_position.z;
bool displacementIsClipped = false;
// Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled
if(!o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap)
@@ -126,9 +128,9 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor,
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, displacementIsClipped);
// Apply second part of the offset to the detail UV (see comment above)
IN.m_detailUv[MaterialSrg::m_parallaxUvIndex] -= IN.m_uv[MaterialSrg::m_parallaxUvIndex];
@@ -186,6 +188,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
float3 baseColor = GetDetailedBaseColorInput(
MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, o_baseColor_useTexture, MaterialSrg::m_baseColor, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode,
MaterialSrg::m_detail_baseColor_texture, MaterialSrg::m_sampler, detailUv, o_detail_baseColor_useTexture, detailLayerBaseColorFactor);
if(o_parallax_highlightClipping && displacementIsClipped)
{
baseColor = lerp(baseColor, float3(1.0,0.0,1.0), 0.5);
}
// ------- Metallic -------
@@ -98,35 +98,21 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
if(o_parallax_feature_enabled && o_parallax_enablePixelDepthOffset)
{
static const float ShadowMapDepthBias = 0.000001;
// We support two UV streams, but only a single stream of tangent/bitangent. So for UV[1+] we generated the tangent/bitangent in screen-space.
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex];
float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex];
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor,
IN.m_uv[MaterialSrg::m_parallaxUvIndex],
dirToCamera,
tangent,
bitangent,
IN.m_normal,
uvMatrix);
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor,
tangentOffset,
IN.m_worldPosition,
tangent,
bitangent,
IN.m_normal,
uvMatrixInverse,
ObjectSrg::GetWorldMatrix(),
ViewSrg::m_viewProjectionMatrix);
OUT.m_depth = pdo.m_depth;
OUT.m_depth += ShadowMapDepthBias;
}
return OUT;
}
@@ -24,16 +24,15 @@
#define COMMON_SRG_INPUTS_PARALLAX(prefix) \
Texture2D prefix##m_depthMap; \
float prefix##m_depthFactor; \
float prefix##m_depthOffset; \
bool prefix##m_depthInverted;
#define COMMON_OPTIONS_PARALLAX(prefix) \
option bool prefix##o_useDepthMap;
option bool o_parallax_feature_enabled;
void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor,
void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset,
float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse,
inout float2 uv, inout float3 worldPosition, inout float depth)
inout float2 uv, inout float3 worldPosition, inout float depth, out bool isClipped)
{
if(o_parallax_feature_enabled)
{
@@ -49,20 +48,22 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep
dirToCamera = ViewSrg::m_worldPosition.xyz - worldPosition;
}
float3 tangentOffset = GetParallaxOffset( depthFactor,
uv,
dirToCamera,
tangent,
bitangent,
normal,
uvMatrix);
ParallaxOffset tangentOffset = GetParallaxOffset( depthFactor,
depthOffset,
uv,
dirToCamera,
tangent,
bitangent,
normal,
uvMatrix);
uv += tangentOffset.xy;
uv += tangentOffset.m_offsetTS.xy;
isClipped = tangentOffset.m_isClipped;
if(o_parallax_enablePixelDepthOffset)
{
PixelDepthOffset pdo = CalcPixelDepthOffset(depthFactor,
tangentOffset,
tangentOffset.m_offsetTS,
worldPosition,
tangent,
bitangent,
@@ -70,9 +71,20 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep
uvMatrixInverse,
objectWorldMatrix,
ViewSrg::m_viewProjectionMatrix);
depth = pdo.m_depth;
worldPosition = pdo.m_worldPosition;
}
}
}
void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset,
float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse,
inout float2 uv, inout float3 worldPosition, inout float depth)
{
bool isClipped;
GetParallaxInput(normal, tangent, bitangent, depthFactor, depthOffset, objectWorldMatrix, uvMatrix, uvMatrixInverse, uv, worldPosition, depth, isClipped);
}
@@ -126,7 +126,8 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor,
float parallaxMainDepthOffset = 0.0;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor, parallaxMainDepthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
@@ -163,8 +163,9 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor,
float parallaxMainDepthOffset = 0.0;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor, parallaxMainDepthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
@@ -124,8 +124,9 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor,
float parallaxMainDepthOffset = 0.0;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_parallaxMainDepthFactor, parallaxMainDepthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
@@ -910,8 +910,8 @@
},
{
"id": "factor",
"displayName": "Factor",
"description": "Strength factor for scaling the depth values",
"displayName": "Heightmap Scale",
"description": "The total height of the heightmap in local model units.",
"type": "Float",
"defaultValue": 0.0,
"min": 0.0,
@@ -921,6 +921,19 @@
"id": "m_depthFactor"
}
},
{
"id": "offset",
"displayName": "Offset",
"description": "Adjusts the overall displacement amount in local model units.",
"type": "Float",
"defaultValue": 0.0,
"softMin": -0.1,
"softMax": 0.1,
"connection": {
"type": "ShaderInput",
"id": "m_depthOffset"
}
},
{
"id": "invert",
"displayName": "Invert",
@@ -966,6 +979,17 @@
"type": "ShaderOption",
"id": "o_parallax_enablePixelDepthOffset"
}
},
{
"id": "showClipping",
"displayName": "Show Clipping",
"description": "Highlight areas where the heightmap is clipped by the mesh surface.",
"type": "Bool",
"defaultValue": false,
"connection": {
"type": "ShaderOption",
"id": "o_parallax_highlightClipping"
}
}
],
"subsurfaceScattering": [
@@ -91,31 +91,15 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace)
float3 tangents[UvSetCount] = { IN.m_tangent.xyz, float3(0, 0, 0) };
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex];
float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex];
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor,
IN.m_uv[MaterialSrg::m_parallaxUvIndex],
ViewSrg::m_worldPosition.xyz - IN.m_worldPosition,
tangent,
bitangent,
IN.m_normal,
uvMatrix);
PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor,
tangentOffset,
IN.m_worldPosition,
tangent,
bitangent,
IN.m_normal,
uvMatrixInverse,
ObjectSrg::GetWorldMatrix(),
ViewSrg::m_viewProjectionMatrix);
OUT.m_depth = pdo.m_depth;
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
}
return OUT;
}
@@ -106,15 +106,18 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
// ------- Depth & Parallax -------
depth = IN.m_position.z;
bool displacementIsClipped = false;
// Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled
if(!o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap)
{
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor,
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth);
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, displacementIsClipped);
// Adjust directional light shadow coorinates for parallax correction
if(o_parallax_enablePixelDepthOffset)
@@ -150,6 +153,11 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float
float3 sampledColor = GetBaseColorInput(MaterialSrg::m_baseColorMap, MaterialSrg::m_sampler, baseColorUv, MaterialSrg::m_baseColor.rgb, o_baseColor_useTexture);
float3 baseColor = BlendBaseColor(sampledColor, MaterialSrg::m_baseColor.rgb, MaterialSrg::m_baseColorFactor, o_baseColorTextureBlendMode, o_baseColor_useTexture);
if(o_parallax_highlightClipping && displacementIsClipped)
{
baseColor = lerp(baseColor, float3(1.0,0.0,1.0), 0.5);
}
// ------- Metallic -------
float metallic = 0;
@@ -41,8 +41,10 @@ function ProcessEditor(context)
if(not enable or textureMap == nil) then
visibility = MaterialPropertyVisibility_Hidden
end
context:SetMaterialPropertyVisibility("parallax.factor", visibility)
context:SetMaterialPropertyVisibility("parallax.offset", visibility)
context:SetMaterialPropertyVisibility("parallax.showClipping", visibility)
context:SetMaterialPropertyVisibility("parallax.invert", visibility)
context:SetMaterialPropertyVisibility("parallax.algorithm", visibility)
context:SetMaterialPropertyVisibility("parallax.quality", visibility)
@@ -107,31 +107,14 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace)
float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, float3(0, 0, 0) };
PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents, 1);
float3 tangent = tangents[MaterialSrg::m_parallaxUvIndex];
float3 bitangent = bitangents[MaterialSrg::m_parallaxUvIndex];
float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3();
float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3();
float3 tangentOffset = GetParallaxOffset( MaterialSrg::m_depthFactor,
IN.m_uv[MaterialSrg::m_parallaxUvIndex],
dirToCamera,
tangent,
bitangent,
IN.m_normal,
uvMatrix);
GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset,
ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse,
IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth);
PixelDepthOffset pdo = CalcPixelDepthOffset(MaterialSrg::m_depthFactor,
tangentOffset,
IN.m_worldPosition,
tangent,
bitangent,
IN.m_normal,
uvMatrixInverse,
ObjectSrg::GetWorldMatrix(),
ViewSrg::m_viewProjectionMatrix);
OUT.m_depth = pdo.m_depth + ShadowMapDepthBias;
OUT.m_depth += ShadowMapDepthBias;
}
return OUT;
}
@@ -18,6 +18,9 @@ option bool o_parallax_enablePixelDepthOffset;
option enum class ParallaxAlgorithm {Basic, Steep, POM, Relief, Contact} o_parallax_algorithm;
option enum class ParallaxQuality {Low, Medium, High, Ultra} o_parallax_quality;
option bool o_parallax_feature_enabled;
option bool o_parallax_highlightClipping;
option bool o_parallax_shadow;
//! The client shader must define this function.
@@ -34,23 +37,49 @@ float SampleDepthOrHeightMap(bool isHeightmap, Texture2D map, sampler mapSampler
return abs((isHeightmap * 1.0) - map.SampleGrad(mapSampler, uv, uv_ddx, uv_ddy).r);
}
float GetClampedDepth(float minSampledDepth, float2 uv, float2 uv_ddx, float2 uv_ddy)
{
float sampledDepthValue = GetDepth(uv, uv_ddx, uv_ddy);
sampledDepthValue = max(sampledDepthValue, minSampledDepth);
return sampledDepthValue;
}
struct ParallaxOffset
{
float3 m_offsetTS; //!< represents the intersection point relative to the geometry surface, in tangent space.
bool m_isClipped; //!< Indicates whether the result is being clipped by the geometry surface, mainly for debug rendering. Only set when o_parallax_highlightClipping is true.
};
// dirToCameraTS should be in tangent space and normalized
// From Reat-Time Rendering 3rd edition, p.192
float3 BasicParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraTS)
ParallaxOffset BasicParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraTS)
{
// the amount to shift
float2 delta = dirToCameraTS.xy * GetDepth(uv, ddx_fine(uv), ddy_fine(uv)) * depthFactor;
float3 offset = float3(0,0,0);
offset.xy -= delta;
return offset;
ParallaxOffset result;
result.m_offsetTS = float3(0,0,0);
result.m_offsetTS.xy -= delta;
result.m_isClipped = false;
return result;
}
// dirToCameraTS and dirToLightTS should be in tangent space and normalized
// Adapt from CryEngine shader shadelib.cfi and POM function in https://github.com/a-riccardi/shader-toy
// Performs ray intersection against a surface with a heightmap.
// Adapted from CryEngine shader shadelib.cfi and POM function in https://github.com/a-riccardi/shader-toy
// check https://github.com/UPBGE/blender/issues/1009 for more details.
float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, int numSteps, inout float parallaxShadowAttenuation)
// @param depthFactor - scales the heightmap in tangent space units (which normally ends up being world units).
// @param depthOffset - offsets the heighmap up or down in tangent space units (which normally ends up being world units).
// @param uv - the UV coordinates on the surface, where the search will begin, used to sample the heightmap.
// @param dirToCameraTS - normalized direction to the camera, in tangent space.
// @param dirToLightTS - normalized direction to a light source, in tangent space, for self-shadowing (if enabled via o_parallax_shadow).
// @param numSteps - the number of steps to take when marching along the ray searching for intersection.
// @param parallaxShadowAttenuation - returns a factor for attenuating a light source, for self-shadowing (if enabled via o_parallax_shadow).
ParallaxOffset AdvancedParallaxMapping(float depthFactor, float depthOffset, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, int numSteps, inout float parallaxShadowAttenuation)
{
ParallaxOffset result;
result.m_isClipped = false;
float dirToCameraZInverse = 1.0 / dirToCameraTS.z;
float step = 1.0 / numSteps;
float currentStep = 0.0;
@@ -60,22 +89,41 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT
float2 ddx_uv = ddx_fine(uv);
float2 ddy_uv = ddy_fine(uv);
// This is the relative position at which we begin searching for intersection.
// It is adjusted according to the depthOffset, raising or lowering the whole surface by depthOffset units.
float3 parallaxOffset = dirToCameraTS.xyz * dirToCameraZInverse * depthOffset;
// Note that depthOffset can raise the heightmap toward (and potentially above) the surface of the mesh.
// We will clamp the heightmap samples to prevent displacements that lie above the surface, which would cause various
// problems especially when PDO is enabled, like parallax surfaces clipping through foreground geometry, and parallax
// surfaces disappearing at low angles.
float minSampledDepth = depthOffset / depthFactor;
minSampledDepth = clamp(minSampledDepth, 0, 1);
float currentSample = GetDepth(uv, ddx_uv, ddy_uv);
// Get an initial heightmap sample to start the intersection search, starting at our initial parallaxOffset position.
float currentSample = GetClampedDepth(minSampledDepth, uv + parallaxOffset.xy, ddx_uv, ddy_uv);
float prevSample;
float3 parallaxOffset = float3(0,0,0);
// Note that when depthOffset > 0, we could actually narrow the search so that instead of going through the entire [0,1] range
// of the heightmap, we could go through the range [minSampledDepth,1]. This would give more accurate results and fewer artifacts
// in case where depthOffset is significant. But for the sake of simplicity we currently search the whole range in all cases.
// find the intersect step
// Do a basic search for the intersect step
while(currentSample > currentStep)
{
currentStep += step;
parallaxOffset += delta;
prevSample = currentSample;
currentSample = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv);
currentSample = GetClampedDepth(minSampledDepth, uv + parallaxOffset.xy, ddx_uv, ddy_uv);
}
// Depending on the algorithm, we refine the result of the above search
switch(o_parallax_algorithm)
{
case ParallaxAlgorithm::Steep:
break; // This algorithm just relies on the course intersection test loop above
case ParallaxAlgorithm::POM:
{
if(currentStep > 0.0)
@@ -108,7 +156,7 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT
parallaxOffset += reliefDelta * depthSign;
currentStep += reliefStep * depthSign;
currentSample = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv);
currentSample = GetClampedDepth(minSampledDepth, uv + parallaxOffset.xy, ddx_uv, ddy_uv);
}
}
break;
@@ -136,7 +184,7 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT
parallaxOffset += adjustedDelta;
prevSample = currentSample;
currentSample = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv);
currentSample = GetClampedDepth(minSampledDepth, uv + parallaxOffset.xy, ddx_uv, ddy_uv);
}
}
break;
@@ -144,6 +192,23 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT
default:
break;
}
// Even though we do a bunch of clamping above when calling GetClampedDepth(), there are still cases where the parallax offset
// can be noticeably above the surface and still needs to be clamped here. The main case is when depthFactor==0 and depthOffset>1.
if(parallaxOffset.z > 0.0)
{
result.m_isClipped = o_parallax_highlightClipping;
parallaxOffset = float3(0,0,0);
}
// Extra check to report whether the heightmap is clipped. Inaccuracies in the intersection search make it difficult to rely on
// parallaxOffset.z to determine whether clipping has occurred. The most accurate way to report clipping is to sample the
// heightmap one last time at the final adjusted UV. Since that's expensive, we only do it when the o_parallax_highlightClipping
// option is set.
else if (o_parallax_highlightClipping)
{
float sampledDepthValue = GetDepth(uv + parallaxOffset.xy, ddx_uv, ddy_uv);
result.m_isClipped = sampledDepthValue < minSampledDepth;
}
if(o_parallax_shadow && any(dirToLightTS))
{
@@ -168,7 +233,7 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT
}
shadowUV += shadowDelta;
currentSample = GetDepth(shadowUV, ddx_uv, ddy_uv);
currentSample = GetClampedDepth(minSampledDepth, shadowUV, ddx_uv, ddy_uv);
currentStep -= step;
}
@@ -181,12 +246,13 @@ float3 AdvancedParallaxMapping(float depthFactor, float2 uv, float3 dirToCameraT
parallaxShadowAttenuation = 1;
}
}
return parallaxOffset;
result.m_offsetTS = parallaxOffset;
return result;
}
// return offset in tangent space
float3 CalculateParallaxOffset(float depthFactor, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, inout float parallaxShadowAttenuation)
ParallaxOffset CalculateParallaxOffset(float depthFactor, float depthOffset, float2 uv, float3 dirToCameraTS, float3 dirToLightTS, inout float parallaxShadowAttenuation)
{
if(o_parallax_algorithm == ParallaxAlgorithm::Basic)
{
@@ -194,27 +260,34 @@ float3 CalculateParallaxOffset(float depthFactor, float2 uv, float3 dirToCameraT
}
else
{
float3 parallaxOffset;
ParallaxOffset parallaxOffset;
switch(o_parallax_quality)
{
case ParallaxQuality::Low:
parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 16, parallaxShadowAttenuation);
parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 16, parallaxShadowAttenuation);
break;
case ParallaxQuality::Medium:
parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 32, parallaxShadowAttenuation);
parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 32, parallaxShadowAttenuation);
break;
case ParallaxQuality::High:
parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 64, parallaxShadowAttenuation);
parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 64, parallaxShadowAttenuation);
break;
case ParallaxQuality::Ultra:
parallaxOffset = AdvancedParallaxMapping(depthFactor, uv, dirToCameraTS, dirToLightTS, 128, parallaxShadowAttenuation);
parallaxOffset = AdvancedParallaxMapping(depthFactor, depthOffset, uv, dirToCameraTS, dirToLightTS, 128, parallaxShadowAttenuation);
break;
}
return parallaxOffset;
}
}
float3 GetParallaxOffset( float depthFactor,
// Performs ray intersection against a surface with a heightmap, to determine an offset amount required for a parallax effect.
// @param depthFactor - scales the heightmap in tangent space units (which normally ends up being world units).
// @param depthOffset - offsets the heighmap up or down in tangent space units (which normally ends up being world units).
// @param uv - the UV coordinates on the surface, where the search will begin, used to sample the heightmap.
// @param dirToCameraTS - normalized direction to the camera, in tangent space.
// @param dirToLightTS - normalized direction to a light source, in tangent space, for self-shadowing (if enabled via o_parallax_shadow).
ParallaxOffset GetParallaxOffset( float depthFactor,
float depthOffset,
float2 uv,
float3 dirToCameraWS,
float3 tangentWS,
@@ -236,7 +309,7 @@ float3 GetParallaxOffset( float depthFactor,
float4 dirToCameraTransformed = mul(uv3DTransform, float4(dirToCameraTS, 0.0));
float dummy = 1;
return CalculateParallaxOffset(depthFactor, uv, normalize(dirToCameraTransformed.xyz), float3(0,0,0), dummy);
return CalculateParallaxOffset(depthFactor, depthOffset, uv, normalize(dirToCameraTransformed.xyz), float3(0,0,0), dummy);
}
struct PixelDepthOffset
@@ -136,15 +136,18 @@ ForwardPassOutput AutoBrick_ForwardPassPS(VSOutput IN)
0,1,0,
0,0,1 };
float3 tangentOffset = GetParallaxOffset( AutoBrickSrg::m_lineDepth,
IN.m_uv,
ViewSrg::m_worldPosition.xyz - IN.m_worldPosition,
IN.m_tangent,
IN.m_bitangent,
IN.m_normal,
identityUvMatrix);
float depthOffset = 0.0;
ParallaxOffset tangentOffset = GetParallaxOffset( AutoBrickSrg::m_lineDepth,
depthOffset,
IN.m_uv,
ViewSrg::m_worldPosition.xyz - IN.m_worldPosition,
IN.m_tangent,
IN.m_bitangent,
IN.m_normal,
identityUvMatrix);
IN.m_uv += tangentOffset.xy;
IN.m_uv += tangentOffset.m_offsetTS.xy;
float3 baseColor = float3(1,1,1);
const float noise = AutoBrickSrg::m_noise.Sample(AutoBrickSrg::m_sampler, IN.m_uv).r;