diff --git a/CodeWalker.Shaders/BasicPS.hlsl b/CodeWalker.Shaders/BasicPS.hlsl index 1dfe4e83a..5567a1dfe 100644 --- a/CodeWalker.Shaders/BasicPS.hlsl +++ b/CodeWalker.Shaders/BasicPS.hlsl @@ -3,15 +3,43 @@ float4 main(VS_OUTPUT input) : SV_TARGET { + // Calculate parallax offset if height mapping is enabled + float2 parallaxTexOffset = float2(0, 0); + float parallaxSelfShadow = 1.0; + if (EnableHeightMap && RenderMode == 0) + { + float3 viewDir = -normalize(input.CamRelPos); // Negate to get direction FROM surface TO camera + float3 norm0 = normalize(input.Normal); + float3 tang0 = normalize(input.Tangent.xyz); + float3 bitang0 = normalize(input.Bitangent.xyz); + parallaxTexOffset = ParallaxOffset( + Heightmap, TextureSS, input.Texcoord0, + viewDir, norm0, tang0, bitang0, + heightScale, heightBias); + + // Parallax self-shadow, transform light dir to tangent space and trace + float3 tanLightDir; + tanLightDir.x = dot(tang0, GlobalLights.LightDir.xyz); + tanLightDir.y = dot(bitang0, GlobalLights.LightDir.xyz); + tanLightDir.z = dot(norm0, GlobalLights.LightDir.xyz); + float shadowAmount = TraceSelfShadow(Heightmap, TextureSS, + input.Texcoord0 + parallaxTexOffset, + tanLightDir, 1.0, heightScale); + parallaxSelfShadow = 1.0 - shadowAmount * PARALLAX_SELF_SHADOW_AMOUNT; + } + + // Apply parallax offset to base texture coordinates + float2 texc0 = input.Texcoord0 + parallaxTexOffset; + float4 c = float4(0.5, 0.5, 0.5, 1); if (RenderMode == 0) c = float4(1, 1, 1, 1); if (EnableTexture > 0) { - float2 texc = input.Texcoord0; + float2 texc = texc0; if (RenderMode >= 5) { - if (RenderSamplerCoord == 2) texc = input.Texcoord1; - else if (RenderSamplerCoord == 3) texc = input.Texcoord2; + if (RenderSamplerCoord == 2) texc = input.Texcoord1 + parallaxTexOffset; + else if (RenderSamplerCoord == 3) texc = input.Texcoord2 + parallaxTexOffset; } c = Colourmap.Sample(TextureSS, texc); @@ -80,8 +108,8 @@ float4 main(VS_OUTPUT input) : SV_TARGET if (RenderMode == 0) { - float4 nv = Bumpmap.Sample(TextureSS, input.Texcoord0); //sample r1.xyzw, v2.xyxx, t3.xyzw, s3 (BumpSampler) - float4 sv = Specmap.Sample(TextureSS, input.Texcoord0); //sample r2.xyzw, v2.xyxx, t4.xyzw, s4 (SpecSampler) + float4 nv = Bumpmap.Sample(TextureSS, texc0); //sample r1.xyzw, v2.xyxx, t3.xyzw, s3 (BumpSampler) + float4 sv = Specmap.Sample(TextureSS, texc0); //sample r2.xyzw, v2.xyxx, t4.xyzw, s4 (SpecSampler) float2 nmv = nv.xy; @@ -92,7 +120,7 @@ float4 main(VS_OUTPUT input) : SV_TARGET if (EnableDetailMap) { //detail normalmapp - r0.xy = input.Texcoord0 * detailSettings.zw; //mul r0.xy, v2.xyxx, detailSettings.zwzz + r0.xy = texc0 * detailSettings.zw; //mul r0.xy, v2.xyxx, detailSettings.zwzz r0.zw = r0.xy * 3.17; //mul r0.zw, r0.xxxy, l(0.000000, 0.000000, 3.170000, 3.170000) r0.xy = Detailmap.Sample(TextureSS, r0.xy).xy - 0.5; //sample r1.xyzw, r0.xyxx, t2.xyzw, s2 (DetailSampler) //mad r0.xy, r1.xyxx, l(2.000000, 2.000000, 0.000000, 0.000000), l(-1.000000, -1.000000, 0.000000, 0.000000) r0.zw = Detailmap.Sample(TextureSS, r0.zw).xy - 0.5; //sample r1.xyzw, r0.zwzz, t2.xyzw, s2 (DetailSampler) //mad r0.zw, r1.xxxy, l(0.000000, 0.000000, 2.000000, 2.000000), l(0.000000, 0.000000, -1.000000, -1.000000) //r0.zw = r0.zw*0.5; //mul r0.zw, r0.zzzw, l(0.000000, 0.000000, 0.500000, 0.500000) @@ -161,7 +189,7 @@ float4 main(VS_OUTPUT input) : SV_TARGET float4 fc = c; - c.rgb = FullLighting(c.rgb, spec, norm, input.Colour0, GlobalLights, EnableShadows, input.Shadows.x, input.LightShadow); + c.rgb = FullLighting(c.rgb, spec, norm, input.Colour0, GlobalLights, EnableShadows, input.Shadows.x, input.LightShadow, parallaxSelfShadow); if (IsEmissive==1) diff --git a/CodeWalker.Shaders/BasicPS.hlsli b/CodeWalker.Shaders/BasicPS.hlsli index 6b6b55964..c42fd9bda 100644 --- a/CodeWalker.Shaders/BasicPS.hlsli +++ b/CodeWalker.Shaders/BasicPS.hlsli @@ -6,6 +6,7 @@ Texture2D Specmap : register(t3); Texture2D Detailmap : register(t4); Texture2D Colourmap2 : register(t5); Texture2D TintPalette : register(t6); +Texture2D Heightmap : register(t7); SamplerState TextureSS : register(s0); @@ -39,6 +40,10 @@ cbuffer PSGeomVars : register(b2) float wetnessMultiplier; uint SpecOnly; float4 TextureAlphaMask; + uint EnableHeightMap; + float heightScale; + float heightBias; + float Pad0; } diff --git a/CodeWalker.Shaders/BasicPS_Deferred.hlsl b/CodeWalker.Shaders/BasicPS_Deferred.hlsl index bf6c7c395..d9bca27c1 100644 --- a/CodeWalker.Shaders/BasicPS_Deferred.hlsl +++ b/CodeWalker.Shaders/BasicPS_Deferred.hlsl @@ -3,17 +3,32 @@ PS_OUTPUT main(VS_OUTPUT input) { + // Calculate parallax offset if height mapping is enabled + float2 parallaxTexOffset = float2(0, 0); + if (EnableHeightMap && RenderMode == 0) + { + float3 viewDir = -normalize(input.CamRelPos); // Negate to get direction FROM surface TO camera + parallaxTexOffset = ParallaxOffset( + Heightmap, TextureSS, input.Texcoord0, + viewDir, normalize(input.Normal), + normalize(input.Tangent.xyz), normalize(input.Bitangent.xyz), + heightScale, heightBias); + } + + // Apply parallax offset to base texture coordinates + float2 texc0 = input.Texcoord0 + parallaxTexOffset; + float4 c = float4(0.5, 0.5, 0.5, 1); if (RenderMode == 0) c = float4(1, 1, 1, 1); if (EnableTexture > 0) { - float2 texc = input.Texcoord0; + float2 texc = texc0; if (RenderMode >= 5) { if (RenderSamplerCoord == 2) - texc = input.Texcoord1; + texc = input.Texcoord1 + parallaxTexOffset; else if (RenderSamplerCoord == 3) - texc = input.Texcoord2; + texc = input.Texcoord2 + parallaxTexOffset; } c = Colourmap.Sample(TextureSS, texc); @@ -85,8 +100,8 @@ PS_OUTPUT main(VS_OUTPUT input) if (RenderMode == 0) { - float4 nv = Bumpmap.Sample(TextureSS, input.Texcoord0); - float4 sv = Specmap.Sample(TextureSS, input.Texcoord0); + float4 nv = Bumpmap.Sample(TextureSS, texc0); + float4 sv = Specmap.Sample(TextureSS, texc0); float2 nmv = nv.xy; @@ -97,7 +112,7 @@ PS_OUTPUT main(VS_OUTPUT input) if (EnableDetailMap) { //detail normalmapp - r0.xy = input.Texcoord0 * detailSettings.zw; + r0.xy = texc0 * detailSettings.zw; r0.zw = r0.xy * 3.17; r0.xy = Detailmap.Sample(TextureSS, r0.xy).xy - 0.5; r0.zw = Detailmap.Sample(TextureSS, r0.zw).xy - 0.5; diff --git a/CodeWalker.Shaders/Common.hlsli b/CodeWalker.Shaders/Common.hlsli index 8f4747e2a..726fb2082 100644 --- a/CodeWalker.Shaders/Common.hlsli +++ b/CodeWalker.Shaders/Common.hlsli @@ -119,6 +119,163 @@ float3 NormalMap(float2 nmv, float bumpinezz, float3 norm, float3 tang, float3 b } +// POM constants +#define POM_MIN_STEPS 3 +#define POM_MAX_STEPS 16 +#define POM_VDOTN_BLEND_FACTOR 0.25f +#define POM_HEIGHT_SCALE 0.1f // Global parallax strength multiplier (1.0 = full, 0.5 = half) + +// Distance-based POM fade constants (reduces noise at steep angles/distance) +#define POM_DISTANCE_START 5.0f // Distance where fade begins +#define POM_DISTANCE_END 50.0f // Distance where POM is fully disabled + +// Binary search refinement for more precise intersection (reduces ring artifacts at close range) +#define POM_BINARY_SEARCH_STEPS 5 // Number of binary search iterations after linear search +#define POM_CLOSE_DISTANCE 2.0f // Distance threshold for close-range step boost +#define POM_CLOSE_STEP_MULTIPLIER 2.0f // Step multiplier when very close to surface + +// Distance fade lookup table (5 control points for smooth non-linear falloff) +// Based on GTA V pomWeights table +#define NUM_POM_CTRL_POINTS 5 +static const float pomWeights[NUM_POM_CTRL_POINTS] = { + 1.0f, // Full quality at close range + 0.9f, + 0.5f, // 50% at mid distance + 0.1f, + 0.0f // Disabled at far distance +}; + +// Compute smooth distance-based fade for POM steps +float ComputePOMDistanceFade(float distanceBlend) +{ + if (distanceBlend >= 1.0f) + return 0.0f; + + // Find the nearest control points and interpolate + int startPoint = clamp(int(distanceBlend * (NUM_POM_CTRL_POINTS - 1)), 0, NUM_POM_CTRL_POINTS - 2); + int endPoint = startPoint + 1; + + float t = distanceBlend * (NUM_POM_CTRL_POINTS - 1) - float(startPoint); + return lerp(pomWeights[startPoint], pomWeights[endPoint], t); +} + +// Performs relief mapping by tracing through the height field +float TraceHeight(Texture2D heightMapSampler, SamplerState samplerState, float2 texCoords, float2 direction, float2 bias, int maxNumberOfSteps) +{ + if (maxNumberOfSteps == 0) + { + return 0.0f; + } + + float heightStep = 1.0f / float(maxNumberOfSteps); + float2 offsetPerStep = direction * heightStep; + + float currentBound = 1.0f; + float previousBound = currentBound; + + float2 texCoordOffset = bias; + + // Use derivatives for proper mip selection to avoid aliasing + float2 ddx0 = ddx(texCoords.xy); + float2 ddy0 = ddy(texCoords.xy); + + float currentHeight = heightMapSampler.SampleGrad(samplerState, texCoords.xy, ddx0, ddy0).r + 1e-6f; + float previousHeight = currentHeight; + + [unroll(POM_MAX_STEPS)] + for (int s = 0; s < maxNumberOfSteps; ++s) + { + if (currentHeight < currentBound) + { + previousBound = currentBound; + previousHeight = currentHeight; + + currentBound -= heightStep; + texCoordOffset += offsetPerStep; + currentHeight = heightMapSampler.SampleGrad(samplerState, texCoords + texCoordOffset, ddx0, ddy0).r; + } + else + { + break; + } + } + + // Interpolate between the two points to find a more precise height + float currentDelta = currentBound - currentHeight; + float previousDelta = previousBound - previousHeight; + float denominator = previousDelta - currentDelta; + + float finalHeight = currentHeight; + + if (denominator > 0) + { + finalHeight = ((currentBound * previousDelta) - (previousBound * currentDelta)) / denominator; + } + + return clamp(finalHeight, 0.0, 1.0f); +} + +// Parallax self-shadow: traces through heightmap in light direction to find occlusion +// Returns shadow factor (0 = fully lit, 1 = fully in shadow) +float TraceSelfShadow(Texture2D heightMapSampler, SamplerState samplerState, float2 texCoords, float3 tanLightDir, float edgeWeight, float hScale) +{ + float2 inXY = (tanLightDir.xy * hScale * edgeWeight) / max(tanLightDir.z, 0.01f); + + // Sample base height at current (displaced) position + float sh0 = heightMapSampler.SampleLevel(samplerState, texCoords, 0).r; + + // Trace 7 samples along light direction with increasing weight for closer occlusion + float shA = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.88, 0).r - sh0 - 0.88) * 1; + float sh9 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.77, 0).r - sh0 - 0.77) * 2; + float sh8 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.66, 0).r - sh0 - 0.66) * 4; + float sh7 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.55, 0).r - sh0 - 0.55) * 6; + float sh6 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.44, 0).r - sh0 - 0.44) * 8; + float sh5 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.33, 0).r - sh0 - 0.33) * 10; + float sh4 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.22, 0).r - sh0 - 0.22) * 12; + + float finalHeight = max(max(max(max(max(max(shA, sh9), sh8), sh7), sh6), sh5), sh4); + return saturate(finalHeight); +} + +#define PARALLAX_SELF_SHADOW_AMOUNT 0.95f + +// Calculate parallax texture coordinate offset +float2 ParallaxOffset(Texture2D heightMapSampler, SamplerState samplerState, float2 texCoords, + float3 viewDir, float3 normal, float3 tangent, float3 bitangent, + float inHeightScale, float inHeightBias) +{ + // Transform view direction to tangent space + float3 tanEyePos; + tanEyePos.x = dot(tangent.xyz, viewDir.xyz); + tanEyePos.y = dot(bitangent.xyz, viewDir.xyz); + tanEyePos.z = dot(normal.xyz, viewDir.xyz); + tanEyePos = normalize(tanEyePos); + + // Clamp Z to avoid division issues at grazing angles + float zLimit = 0.1f; + float clampedZ = max(zLimit, tanEyePos.z); + + // Calculate view-dependent step count for quality/performance balance + float VdotN = abs(dot(normalize(viewDir.xyz), normalize(normal.xyz))); + float numberOfSteps = lerp(POM_MAX_STEPS, POM_MIN_STEPS, VdotN); + + // Apply global scale based on view angle for smooth falloff + float globalScale = saturate(numberOfSteps - 1.0f) * saturate(VdotN / POM_VDOTN_BLEND_FACTOR); + + // Calculate max parallax offset and bias offset + float2 maxParallaxOffset = (-tanEyePos.xy / clampedZ) * inHeightScale * globalScale; + float2 heightBiasOffset = (tanEyePos.xy / clampedZ) * inHeightBias * globalScale; + + // Trace through height field + float height = TraceHeight(heightMapSampler, samplerState, texCoords, maxParallaxOffset, heightBiasOffset, (int)numberOfSteps); + + // Calculate final texture coordinate offset + float2 texCoordOffset = heightBiasOffset + (maxParallaxOffset * (1.0f - height)); + + return texCoordOffset; +} + + float3 BasicLighting(float4 lightcolour, float4 ambcolour, float pclit) diff --git a/CodeWalker.Shaders/Shadowmap.hlsli b/CodeWalker.Shaders/Shadowmap.hlsli index 1dba4edb1..92c61557a 100644 --- a/CodeWalker.Shaders/Shadowmap.hlsli +++ b/CodeWalker.Shaders/Shadowmap.hlsli @@ -225,7 +225,7 @@ float ShadowAmount(float4 shadowcoord, float shadowdepth)//, inout float4 colour -float3 FullLighting(float3 diff, float3 spec, float3 norm, float4 vc0, uniform ShaderGlobalLightParams globalLights, uint enableShadows, float shadowdepth, float4 shadowcoord) +float3 FullLighting(float3 diff, float3 spec, float3 norm, float4 vc0, uniform ShaderGlobalLightParams globalLights, uint enableShadows, float shadowdepth, float4 shadowcoord, float selfShadow = 1.0) { float lf = saturate(dot(norm, globalLights.LightDir.xyz)); @@ -241,6 +241,9 @@ float3 FullLighting(float3 diff, float3 spec, float3 norm, float4 vc0, uniform S } } + // Apply parallax self-shadow to cascade shadow + shadowlit *= selfShadow; + lf *= shadowlit; float3 speclit = spec*shadowlit; diff --git a/CodeWalker.Shaders/TerrainPS.hlsl b/CodeWalker.Shaders/TerrainPS.hlsl index d11a6cdcf..91f1b21d0 100644 --- a/CodeWalker.Shaders/TerrainPS.hlsl +++ b/CodeWalker.Shaders/TerrainPS.hlsl @@ -1,6 +1,193 @@ #include "TerrainPS.hlsli" +// Sample and blend terrain height maps based on layer weights +// Returns inverted height (1.0 - sample) for correct POM ray marching +float BlendTerrainHeight(float4 layerBlends, float2 texCoord) +{ + float result = 0.0f; + result += layerBlends.x * Heightmap0.SampleLevel(TextureSS, texCoord, 0).r; + result += layerBlends.y * Heightmap1.SampleLevel(TextureSS, texCoord, 0).r; + result += layerBlends.z * Heightmap2.SampleLevel(TextureSS, texCoord, 0).r; + result += layerBlends.w * Heightmap3.SampleLevel(TextureSS, texCoord, 0).r; + // Invert height: GTA V height maps use 1.0=raised, 0.0=base + // POM ray march expects 0.0=raised (hit early), 1.0=base (hit late) + return 1.0f - result; +} + +// Get blended height scale and bias based on layer weights +float2 GetBlendedScaleBias(float4 layerBlends) +{ + float2 result = float2(0.0f, 0.0f); + result += layerBlends.x * float2(heightScale.x, heightBias.x); + result += layerBlends.y * float2(heightScale.y, heightBias.y); + result += layerBlends.z * float2(heightScale.z, heightBias.z); + result += layerBlends.w * float2(heightScale.w, heightBias.w); + return result; +} + +// Calculate terrain parallax offset using blended heights +// Includes distance-based fade and vertex edge weight to reduce noise at steep angles and mesh edges +float2 CalculateTerrainParallaxOffset(float4 layerBlends, float2 texCoord, float3 viewDir, float3 normal, float3 tangent, float3 bitangent, float viewDistance, float2 edgeWeightData) +{ + // Get blended scale and bias + float2 scaleBias = GetBlendedScaleBias(layerBlends); + float hScale = scaleBias.x * POM_HEIGHT_SCALE; // Apply global strength multiplier + float hBias = scaleBias.y * POM_HEIGHT_SCALE; + + if (hScale == 0.0f) + return float2(0.0f, 0.0f); + + // Vertex edge weight from mesh data + // edgeWeightData.x: 0 = full POM, 1 = no POM (at mesh edges) + // edgeWeightData.y: controls dynamic zLimit adjustment + float vertexEdgeWeight = 1.0f - saturate(edgeWeightData.x); + + // Early out if vertex says no POM at this point + if (vertexEdgeWeight <= 0.0f) + return float2(0.0f, 0.0f); + + // Transform view direction to tangent space + float3 tanEyePos; + tanEyePos.x = dot(tangent.xyz, viewDir.xyz); + tanEyePos.y = dot(bitangent.xyz, viewDir.xyz); + tanEyePos.z = dot(normal.xyz, viewDir.xyz); + tanEyePos = normalize(tanEyePos); + + // Dynamic zLimit from vertex edge weight + // Higher edgeWeightData.y = lower zLimit = allow steeper angles + float zLimit = 1.0f - clamp(edgeWeightData.y, 0.1f, 1.0f); + zLimit = max(zLimit, 0.1f); // Ensure minimum zLimit + float clampedZ = max(zLimit, tanEyePos.z); + + // Calculate view-dependent step count + float VdotN = abs(dot(normalize(viewDir.xyz), normalize(normal.xyz))); + float numberOfSteps = lerp(POM_MAX_STEPS, POM_MIN_STEPS, VdotN); + + // Close-range step boost - increase precision when very close to surface (reduces ring artifacts) + float closeBoost = saturate(1.0f - viewDistance / POM_CLOSE_DISTANCE); + numberOfSteps *= lerp(1.0f, POM_CLOSE_STEP_MULTIPLIER, closeBoost); + + // Distance-based fade - reduces noise at steep angles/far distances + float distanceBlend = saturate((viewDistance - POM_DISTANCE_START) / (POM_DISTANCE_END - POM_DISTANCE_START)); + float distanceFade = ComputePOMDistanceFade(distanceBlend); + + // Reduce steps over distance - artifacts become less noticeable at distance + numberOfSteps *= distanceFade; + + // Early out if steps reduced to nearly zero + if (numberOfSteps < 1.0f) + return float2(0.0f, 0.0f); + + // Calculate weight distance blend for smooth fade-out near zero steps + float scaleOutRange = (POM_DISTANCE_END - POM_DISTANCE_START) * 0.35f; + float weightDistanceBlend = saturate(((viewDistance - POM_DISTANCE_START) - (POM_DISTANCE_END - POM_DISTANCE_START) + scaleOutRange) / scaleOutRange); + + // Combined edge weight: vertex edge * view angle fade * step count fade * distance fade + float edgeWeight = vertexEdgeWeight * (1.0f - weightDistanceBlend); + edgeWeight *= saturate(numberOfSteps - 1.0f) * saturate(VdotN / POM_VDOTN_BLEND_FACTOR); + + // Apply combined scale + float globalScale = edgeWeight; + + float2 maxParallaxOffset = (-tanEyePos.xy / clampedZ) * hScale * globalScale; + float2 heightBiasOffset = (tanEyePos.xy / clampedZ) * hBias * globalScale; + + float heightStep = 1.0f / max(numberOfSteps, 1.0f); + float2 offsetPerStep = maxParallaxOffset * heightStep; + + float currentHeight = 1.0f; + float previousHeight = currentHeight; + + float2 texCoordOffset = heightBiasOffset; + + float terrainHeight = BlendTerrainHeight(layerBlends, texCoord) + 1e-6f; + float previousTerrainHeight = terrainHeight; + + // Ray march through the height field + int maxSteps = (int)numberOfSteps; + for (int i = 0; i < maxSteps; ++i) + { + if (terrainHeight < currentHeight) + { + previousHeight = currentHeight; + previousTerrainHeight = terrainHeight; + + currentHeight -= heightStep; + texCoordOffset += offsetPerStep; + terrainHeight = BlendTerrainHeight(layerBlends, texCoord + texCoordOffset); + } + else + { + break; + } + } + + // Binary search refinement for more precise intersection (reduces ring artifacts at close range) + float2 prevOffset = texCoordOffset - offsetPerStep; + float2 currOffset = texCoordOffset; + float prevHeight = previousHeight; + float currHeight = currentHeight; + + [unroll(POM_BINARY_SEARCH_STEPS)] + for (int j = 0; j < POM_BINARY_SEARCH_STEPS; ++j) + { + float2 midOffset = (prevOffset + currOffset) * 0.5f; + float midHeight = (prevHeight + currHeight) * 0.5f; + float midTerrainHeight = BlendTerrainHeight(layerBlends, texCoord + midOffset); + + if (midTerrainHeight < midHeight) + { + // Intersection is in second half + prevOffset = midOffset; + prevHeight = midHeight; + } + else + { + // Intersection is in first half + currOffset = midOffset; + currHeight = midHeight; + } + } + + // Final interpolation between the refined bracket + float finalTerrainHeight = BlendTerrainHeight(layerBlends, texCoord + currOffset); + float currentDelta = currHeight - finalTerrainHeight; + float previousDelta = prevHeight - BlendTerrainHeight(layerBlends, texCoord + prevOffset); + float denominator = previousDelta - currentDelta; + + float refinedHeight = 1.0f; + if (abs(denominator) > 1e-6f) + { + refinedHeight = (currHeight * previousDelta - prevHeight * currentDelta) / denominator; + } + else + { + refinedHeight = 1.0f - (currOffset.x / maxParallaxOffset.x); + } + + return heightBiasOffset + (maxParallaxOffset * (1.0f - saturate(refinedHeight))); +} + +// Terrain-specific self-shadow using blended heightmaps +float TraceTerrainSelfShadow(float4 layerBlends, float2 texCoords, float3 tanLightDir, float edgeWeight, float hScale) +{ + float2 inXY = (tanLightDir.xy * hScale * edgeWeight) / max(tanLightDir.z, 0.01f); + + float sh0 = 1.0f - BlendTerrainHeight(layerBlends, texCoords); + + float shA = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.88) - sh0 - 0.88) * 1; + float sh9 = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.77) - sh0 - 0.77) * 2; + float sh8 = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.66) - sh0 - 0.66) * 4; + float sh7 = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.55) - sh0 - 0.55) * 6; + float sh6 = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.44) - sh0 - 0.44) * 8; + float sh5 = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.33) - sh0 - 0.33) * 10; + float sh4 = (1.0f - BlendTerrainHeight(layerBlends, texCoords + inXY * 0.22) - sh0 - 0.22) * 12; + + float finalHeight = max(max(max(max(max(max(shA, sh9), sh8), sh7), sh6), sh5), sh4); + return saturate(finalHeight); +} + float4 main(VS_OUTPUT input) : SV_TARGET { float4 vc0 = input.Colour0; @@ -16,24 +203,46 @@ float4 main(VS_OUTPUT input) : SV_TARGET float2 sc4 = tc0; float2 scm = tc1; - ////switch (ShaderName) - ////{ - //// case 3965214311: //terrain_cb_w_4lyr_cm_pxm_tnt vt: PNCTTTX_3 //vb_35_beache - //// case 4186046662: //terrain_cb_w_4lyr_cm_pxm vt: PNCTTTX_3 //cs6_08_struct08 - //// //vc1 = vc0; - //// //sc1 = tc0*25; - //// //sc2 = sc1; - //// //sc3 = sc1; - //// //sc4 = sc1; - //// //scm = tc0; - //// break; - ////} + // Calculate layer blend weights from vertex colors (4-layer blending) + // Layer weights: x=(1-g)*(1-b), y=(1-g)*b, z=g*(1-b), w=g*b + float4 layerBlends; + layerBlends.x = (1.0f - vc1.g) * (1.0f - vc1.b); + layerBlends.y = (1.0f - vc1.g) * vc1.b; + layerBlends.z = vc1.g * (1.0f - vc1.b); + layerBlends.w = vc1.g * vc1.b; + + // Calculate single parallax offset using blended heights + float parallaxSelfShadow = 1.0; + if (EnableHeightMap && RenderMode == 0) + { + float3 viewDir = -normalize(input.CamRelPos); // Negate to get direction FROM surface TO camera + float3 norm = normalize(input.Normal); + float3 tang = normalize(input.Tangent.xyz); + float3 bitang = normalize(input.Bitangent.xyz); + + // Calculate single offset from blended height values (with distance fade and edge weight) + float2 parallaxOffset = CalculateTerrainParallaxOffset(layerBlends, tc0, viewDir, norm, tang, bitang, input.ViewDistance, input.EdgeWeight); + + // Apply same offset to all texture coordinates + sc0 += parallaxOffset; + sc1 += parallaxOffset; + sc2 += parallaxOffset; + sc3 += parallaxOffset; + sc4 += parallaxOffset; + + // Parallax self-shadow, transform light dir to tangent space and trace blended heights + float3 tanLightDir; + tanLightDir.x = dot(tang, GlobalLights.LightDir.xyz); + tanLightDir.y = dot(bitang, GlobalLights.LightDir.xyz); + tanLightDir.z = dot(norm, GlobalLights.LightDir.xyz); + float2 blendedScaleBias = GetBlendedScaleBias(layerBlends); + float blendedHScale = blendedScaleBias.x * POM_HEIGHT_SCALE; + float edgeWeight = 1.0f - saturate(input.EdgeWeight.x); + float shadowAmount = TraceTerrainSelfShadow(layerBlends, sc0, tanLightDir, edgeWeight, blendedHScale); + parallaxSelfShadow = 1.0 - shadowAmount * PARALLAX_SELF_SHADOW_AMOUNT; + } float4 bc0 = float4(0.5, 0.5, 0.5, 1); - //if (EnableVertexColour) - //{ - // bc0 = vc0; - //} if (RenderMode == 8) //direct texture - choose texcoords { @@ -214,7 +423,7 @@ float4 main(VS_OUTPUT input) : SV_TARGET float3 spec = 0; - tv.rgb = FullLighting(tv.rgb, spec, norm, vc0, GlobalLights, EnableShadows, input.Shadows.x, input.LightShadow); + tv.rgb = FullLighting(tv.rgb, spec, norm, vc0, GlobalLights, EnableShadows, input.Shadows.x, input.LightShadow, parallaxSelfShadow); return float4(tv.rgb, saturate(tv.a)); diff --git a/CodeWalker.Shaders/TerrainPS.hlsli b/CodeWalker.Shaders/TerrainPS.hlsli index b8cefdd4a..d9503c340 100644 --- a/CodeWalker.Shaders/TerrainPS.hlsli +++ b/CodeWalker.Shaders/TerrainPS.hlsli @@ -11,6 +11,10 @@ Texture2D Normalmap1 : register(t8); Texture2D Normalmap2 : register(t9); Texture2D Normalmap3 : register(t10); Texture2D Normalmap4 : register(t11); +Texture2D Heightmap0 : register(t12); +Texture2D Heightmap1 : register(t13); +Texture2D Heightmap2 : register(t14); +Texture2D Heightmap3 : register(t15); SamplerState TextureSS : register(s0); @@ -35,7 +39,9 @@ cbuffer PSEntityVars : register(b2) uint EnableTint; uint EnableVertexColour; float bumpiness; - uint Pad102; + uint EnableHeightMap; + float4 heightScale; // x=layer0, y=layer1, z=layer2, w=layer3 + float4 heightBias; // x=layer0, y=layer1, z=layer2, w=layer3 } @@ -54,6 +60,8 @@ struct VS_OUTPUT float4 Tangent : TEXCOORD6; float4 Bitangent : TEXCOORD7; float3 CamRelPos : TEXCOORD8; + float ViewDistance : TEXCOORD9; // Distance from camera for POM fade + float2 EdgeWeight : TEXCOORD10; // x=POM edge fade, y=zLimit adjustment (GTA V style) }; struct PS_OUTPUT diff --git a/CodeWalker.Shaders/TerrainPS_Deferred.hlsl b/CodeWalker.Shaders/TerrainPS_Deferred.hlsl index aeb1ab3a6..766c58c3f 100644 --- a/CodeWalker.Shaders/TerrainPS_Deferred.hlsl +++ b/CodeWalker.Shaders/TerrainPS_Deferred.hlsl @@ -1,6 +1,174 @@ #include "TerrainPS.hlsli" +// Sample and blend terrain height maps based on layer weights +// Returns inverted height (1.0 - sample) for correct POM ray marching +float BlendTerrainHeight(float4 layerBlends, float2 texCoord) +{ + float result = 0.0f; + result += layerBlends.x * Heightmap0.SampleLevel(TextureSS, texCoord, 0).r; + result += layerBlends.y * Heightmap1.SampleLevel(TextureSS, texCoord, 0).r; + result += layerBlends.z * Heightmap2.SampleLevel(TextureSS, texCoord, 0).r; + result += layerBlends.w * Heightmap3.SampleLevel(TextureSS, texCoord, 0).r; + // height maps use 1.0=raised, 0.0=base + // POM ray march expects 0.0=raised (hit early), 1.0=base (hit late) + return 1.0f - result; +} + +// Get blended height scale and bias based on layer weights +float2 GetBlendedScaleBias(float4 layerBlends) +{ + float2 result = float2(0.0f, 0.0f); + result += layerBlends.x * float2(heightScale.x, heightBias.x); + result += layerBlends.y * float2(heightScale.y, heightBias.y); + result += layerBlends.z * float2(heightScale.z, heightBias.z); + result += layerBlends.w * float2(heightScale.w, heightBias.w); + return result; +} + +// Calculate terrain parallax offset using blended heights +// Includes distance-based fade and vertex edge weight to reduce noise at steep angles and mesh edges +float2 CalculateTerrainParallaxOffset(float4 layerBlends, float2 texCoord, float3 viewDir, float3 normal, float3 tangent, float3 bitangent, float viewDistance, float2 edgeWeightData) +{ + // Get blended scale and bias + float2 scaleBias = GetBlendedScaleBias(layerBlends); + float hScale = scaleBias.x * POM_HEIGHT_SCALE; // Apply global strength multiplier + float hBias = scaleBias.y * POM_HEIGHT_SCALE; + + if (hScale == 0.0f) + return float2(0.0f, 0.0f); + + // Vertex edge weight from mesh data + // edgeWeightData.x: 0 = full POM, 1 = no POM (at mesh edges) + // edgeWeightData.y: controls dynamic zLimit adjustment + float vertexEdgeWeight = 1.0f - saturate(edgeWeightData.x); + + // Early out if vertex says no POM at this point + if (vertexEdgeWeight <= 0.0f) + return float2(0.0f, 0.0f); + + // Transform view direction to tangent space + float3 tanEyePos; + tanEyePos.x = dot(tangent.xyz, viewDir.xyz); + tanEyePos.y = dot(bitangent.xyz, viewDir.xyz); + tanEyePos.z = dot(normal.xyz, viewDir.xyz); + tanEyePos = normalize(tanEyePos); + + // Dynamic zLimit from vertex edge weight (GTA V style) + // Higher edgeWeightData.y = lower zLimit = allow steeper angles + float zLimit = 1.0f - clamp(edgeWeightData.y, 0.1f, 1.0f); + zLimit = max(zLimit, 0.1f); // Ensure minimum zLimit + float clampedZ = max(zLimit, tanEyePos.z); + + // Calculate view-dependent step count + float VdotN = abs(dot(normalize(viewDir.xyz), normalize(normal.xyz))); + float numberOfSteps = lerp(POM_MAX_STEPS, POM_MIN_STEPS, VdotN); + + // Close-range step boost - increase precision when very close to surface (reduces ring artifacts) + float closeBoost = saturate(1.0f - viewDistance / POM_CLOSE_DISTANCE); + numberOfSteps *= lerp(1.0f, POM_CLOSE_STEP_MULTIPLIER, closeBoost); + + // Distance-based fade (GTA V style) - reduces noise at steep angles/far distances + float distanceBlend = saturate((viewDistance - POM_DISTANCE_START) / (POM_DISTANCE_END - POM_DISTANCE_START)); + float distanceFade = ComputePOMDistanceFade(distanceBlend); + + // Reduce steps over distance - artifacts become less noticeable at distance + numberOfSteps *= distanceFade; + + // Early out if steps reduced to nearly zero + if (numberOfSteps < 1.0f) + return float2(0.0f, 0.0f); + + // Calculate weight distance blend for smooth fade-out near zero steps + float scaleOutRange = (POM_DISTANCE_END - POM_DISTANCE_START) * 0.35f; + float weightDistanceBlend = saturate(((viewDistance - POM_DISTANCE_START) - (POM_DISTANCE_END - POM_DISTANCE_START) + scaleOutRange) / scaleOutRange); + + // Combined edge weight: vertex edge * view angle fade * step count fade * distance fade + float edgeWeight = vertexEdgeWeight * (1.0f - weightDistanceBlend); + edgeWeight *= saturate(numberOfSteps - 1.0f) * saturate(VdotN / POM_VDOTN_BLEND_FACTOR); + + // Apply combined scale + float globalScale = edgeWeight; + + float2 maxParallaxOffset = (-tanEyePos.xy / clampedZ) * hScale * globalScale; + float2 heightBiasOffset = (tanEyePos.xy / clampedZ) * hBias * globalScale; + + float heightStep = 1.0f / max(numberOfSteps, 1.0f); + float2 offsetPerStep = maxParallaxOffset * heightStep; + + float currentHeight = 1.0f; + float previousHeight = currentHeight; + + float2 texCoordOffset = heightBiasOffset; + + float terrainHeight = BlendTerrainHeight(layerBlends, texCoord) + 1e-6f; + float previousTerrainHeight = terrainHeight; + + // Ray march through the height field + int maxSteps = (int)numberOfSteps; + for (int i = 0; i < maxSteps; ++i) + { + if (terrainHeight < currentHeight) + { + previousHeight = currentHeight; + previousTerrainHeight = terrainHeight; + + currentHeight -= heightStep; + texCoordOffset += offsetPerStep; + terrainHeight = BlendTerrainHeight(layerBlends, texCoord + texCoordOffset); + } + else + { + break; + } + } + + // Binary search refinement for more precise intersection (reduces ring artifacts at close range) + float2 prevOffset = texCoordOffset - offsetPerStep; + float2 currOffset = texCoordOffset; + float prevHeight = previousHeight; + float currHeight = currentHeight; + + [unroll(POM_BINARY_SEARCH_STEPS)] + for (int j = 0; j < POM_BINARY_SEARCH_STEPS; ++j) + { + float2 midOffset = (prevOffset + currOffset) * 0.5f; + float midHeight = (prevHeight + currHeight) * 0.5f; + float midTerrainHeight = BlendTerrainHeight(layerBlends, texCoord + midOffset); + + if (midTerrainHeight < midHeight) + { + // Intersection is in second half + prevOffset = midOffset; + prevHeight = midHeight; + } + else + { + // Intersection is in first half + currOffset = midOffset; + currHeight = midHeight; + } + } + + // Final interpolation between the refined bracket + float finalTerrainHeight = BlendTerrainHeight(layerBlends, texCoord + currOffset); + float currentDelta = currHeight - finalTerrainHeight; + float previousDelta = prevHeight - BlendTerrainHeight(layerBlends, texCoord + prevOffset); + float denominator = previousDelta - currentDelta; + + float refinedHeight = 1.0f; + if (abs(denominator) > 1e-6f) + { + refinedHeight = (currHeight * previousDelta - prevHeight * currentDelta) / denominator; + } + else + { + refinedHeight = 1.0f - (currOffset.x / maxParallaxOffset.x); + } + + return heightBiasOffset + (maxParallaxOffset * (1.0f - saturate(refinedHeight))); +} + PS_OUTPUT main(VS_OUTPUT input) { float4 vc0 = input.Colour0; @@ -16,24 +184,34 @@ PS_OUTPUT main(VS_OUTPUT input) float2 sc4 = tc0; float2 scm = tc1; - ////switch (ShaderName) - ////{ - //// case 3965214311: //terrain_cb_w_4lyr_cm_pxm_tnt vt: PNCTTTX_3 //vb_35_beache - //// case 4186046662: //terrain_cb_w_4lyr_cm_pxm vt: PNCTTTX_3 //cs6_08_struct08 - //// //vc1 = vc0; - //// //sc1 = tc0*25; - //// //sc2 = sc1; - //// //sc3 = sc1; - //// //sc4 = sc1; - //// //scm = tc0; - //// break; - ////} + // Calculate layer blend weights from vertex colors (4-layer blending) + // Layer weights: x=(1-g)*(1-b), y=(1-g)*b, z=g*(1-b), w=g*b + float4 layerBlends; + layerBlends.x = (1.0f - vc1.g) * (1.0f - vc1.b); + layerBlends.y = (1.0f - vc1.g) * vc1.b; + layerBlends.z = vc1.g * (1.0f - vc1.b); + layerBlends.w = vc1.g * vc1.b; + + // Calculate single parallax offset using blended heights (GTA V approach) + if (EnableHeightMap && RenderMode == 0) + { + float3 viewDir = -normalize(input.CamRelPos); // Negate to get direction FROM surface TO camera + float3 norm = normalize(input.Normal); + float3 tang = normalize(input.Tangent.xyz); + float3 bitang = normalize(input.Bitangent.xyz); + + // Calculate single offset from blended height values (with distance fade and edge weight) + float2 parallaxOffset = CalculateTerrainParallaxOffset(layerBlends, tc0, viewDir, norm, tang, bitang, input.ViewDistance, input.EdgeWeight); + + // Apply same offset to all texture coordinates + sc0 += parallaxOffset; + sc1 += parallaxOffset; + sc2 += parallaxOffset; + sc3 += parallaxOffset; + sc4 += parallaxOffset; + } float4 bc0 = float4(0.5, 0.5, 0.5, 1); - //if (EnableVertexColour) - //{ - // bc0 = vc0; - //} if (RenderMode == 8) //direct texture - choose texcoords { diff --git a/CodeWalker.Shaders/TerrainVS.hlsli b/CodeWalker.Shaders/TerrainVS.hlsli index 454760035..d69409643 100644 --- a/CodeWalker.Shaders/TerrainVS.hlsli +++ b/CodeWalker.Shaders/TerrainVS.hlsli @@ -46,6 +46,8 @@ struct VS_OUTPUT float4 Tangent : TEXCOORD6; float4 Bitangent : TEXCOORD7; float3 CamRelPos : TEXCOORD8; + float ViewDistance : TEXCOORD9; // Distance from camera for POM fade + float2 EdgeWeight : TEXCOORD10; // x=POM edge fade, y=zLimit adjustment (GTA V style) }; Texture2D TintPalette : register(t0); diff --git a/CodeWalker.Shaders/TerrainVS_PNCCT.hlsl b/CodeWalker.Shaders/TerrainVS_PNCCT.hlsl index af7ee7b2f..711e84c1b 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCCT.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCCT.hlsl @@ -35,5 +35,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tint = tnt; output.Tangent = float4(btang, 0); output.Bitangent = float4(cross(btang, bnorm), 0); + output.ViewDistance = length(opos); + output.EdgeWeight = float2(0, 0.9); // Default: full POM, zLimit=0.1 return output; } diff --git a/CodeWalker.Shaders/TerrainVS_PNCCTT.hlsl b/CodeWalker.Shaders/TerrainVS_PNCCTT.hlsl index a850e0bb4..5f9d9c36c 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCCTT.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCCTT.hlsl @@ -36,5 +36,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tint = tnt; output.Tangent = float4(btang, 0); output.Bitangent = float4(cross(btang, bnorm), 0); + output.ViewDistance = length(opos); + output.EdgeWeight = float2(0, 0.9); // Default: full POM, zLimit=0.1 return output; } diff --git a/CodeWalker.Shaders/TerrainVS_PNCCTTTX.hlsl b/CodeWalker.Shaders/TerrainVS_PNCCTTTX.hlsl index 585a06c00..2edadfa13 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCCTTTX.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCCTTTX.hlsl @@ -38,5 +38,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tangent = float4(btang, input.Tangent.w); output.Bitangent = float4(cross(btang, bnorm) * input.Tangent.w, 0); output.Tint = tnt; + output.ViewDistance = length(opos); + output.EdgeWeight = input.Texcoord2; // Edge weight from vertex data (GTA V style) return output; } diff --git a/CodeWalker.Shaders/TerrainVS_PNCCTTX.hlsl b/CodeWalker.Shaders/TerrainVS_PNCCTTX.hlsl index 15907a35e..f1b1ac32c 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCCTTX.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCCTTX.hlsl @@ -37,5 +37,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tangent = float4(btang, input.Tangent.w); output.Bitangent = float4(cross(btang, bnorm) * input.Tangent.w, 0); output.Tint = tnt; + output.ViewDistance = length(opos); + output.EdgeWeight = float2(0, 0.9); // Default: full POM, zLimit=0.1 return output; } diff --git a/CodeWalker.Shaders/TerrainVS_PNCCTX.hlsl b/CodeWalker.Shaders/TerrainVS_PNCCTX.hlsl index 66810197b..6df483a89 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCCTX.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCCTX.hlsl @@ -36,5 +36,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tangent = float4(btang, input.Tangent.w); output.Bitangent = float4(cross(btang, bnorm) * input.Tangent.w, 0); output.Tint = tnt; + output.ViewDistance = length(opos); + output.EdgeWeight = float2(0, 0.9); // Default: full POM, zLimit=0.1 return output; } diff --git a/CodeWalker.Shaders/TerrainVS_PNCTTTX.hlsl b/CodeWalker.Shaders/TerrainVS_PNCTTTX.hlsl index d1e1b561b..540cb5eea 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCTTTX.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCTTTX.hlsl @@ -37,5 +37,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tangent = float4(btang, input.Tangent.w); output.Bitangent = float4(cross(btang, bnorm) * input.Tangent.w, 0); output.Tint = tnt; + output.ViewDistance = length(opos); + output.EdgeWeight = input.Texcoord2; // Edge weight from vertex data (GTA V style) return output; } diff --git a/CodeWalker.Shaders/TerrainVS_PNCTTX.hlsl b/CodeWalker.Shaders/TerrainVS_PNCTTX.hlsl index b44b868e7..94ef8250f 100644 --- a/CodeWalker.Shaders/TerrainVS_PNCTTX.hlsl +++ b/CodeWalker.Shaders/TerrainVS_PNCTTX.hlsl @@ -36,5 +36,7 @@ VS_OUTPUT main(VS_INPUT input) output.Tangent = float4(btang, input.Tangent.w); output.Bitangent = float4(cross(btang, bnorm) * input.Tangent.w, 0); output.Tint = tnt; + output.ViewDistance = length(opos); + output.EdgeWeight = float2(0, 0.9); // Default: full POM, zLimit=0.1 return output; } diff --git a/CodeWalker/Rendering/Renderable.cs b/CodeWalker/Rendering/Renderable.cs index f27eba1e8..367fce0dd 100644 --- a/CodeWalker/Rendering/Renderable.cs +++ b/CodeWalker/Rendering/Renderable.cs @@ -845,6 +845,16 @@ public class RenderableGeometry public float RippleSpeed { get; set; } = 1.0f; public float RippleScale { get; set; } = 1.0f; public float RippleBumpiness { get; set; } = 1.0f; + public float heightScale { get; set; } = 0.03f; + public float heightBias { get; set; } = 0.015f; + public float heightScale0 { get; set; } = 0.03f; + public float heightScale1 { get; set; } = 0.03f; + public float heightScale2 { get; set; } = 0.03f; + public float heightScale3 { get; set; } = 0.03f; + public float heightBias0 { get; set; } = 0.015f; + public float heightBias1 { get; set; } = 0.015f; + public float heightBias2 { get; set; } = 0.015f; + public float heightBias3 { get; set; } = 0.015f; public Vector4 WindGlobalParams { get; set; } = Vector4.Zero; public Vector4 WindOverrideParams { get; set; } = Vector4.One; public Vector4 globalAnimUV0 { get; set; } = new Vector4(1.0f, 0.0f, 0.0f, 0.0f); @@ -1041,6 +1051,36 @@ public void Init(DrawableGeometry dgeom) case ShaderParamNames.RippleBumpiness: RippleBumpiness = ((Vector4)param.Data).X; break; + case ShaderParamNames.heightScale: + heightScale = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightBias: + heightBias = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightScale0: + heightScale0 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightScale1: + heightScale1 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightScale2: + heightScale2 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightScale3: + heightScale3 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightBias0: + heightBias0 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightBias1: + heightBias1 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightBias2: + heightBias2 = ((Vector4)param.Data).X; + break; + case ShaderParamNames.heightBias3: + heightBias3 = ((Vector4)param.Data).X; + break; case ShaderParamNames.globalAnimUV0: globalAnimUV0 = (Vector4)param.Data; globalAnimUVEnable = true; diff --git a/CodeWalker/Rendering/Shaders/BasicShader.cs b/CodeWalker/Rendering/Shaders/BasicShader.cs index b01914ea8..4a411b984 100644 --- a/CodeWalker/Rendering/Shaders/BasicShader.cs +++ b/CodeWalker/Rendering/Shaders/BasicShader.cs @@ -76,6 +76,10 @@ public struct BasicShaderPSGeomVars public float wetnessMultiplier; public uint SpecOnly; public Vector4 TextureAlphaMask; + public uint EnableHeightMap; + public float heightScale; + public float heightBias; + public float Pad0; } public struct BasicShaderInstGlobalMatrix { @@ -609,6 +613,7 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) RenderableTexture bumptex = null; RenderableTexture spectex = null; RenderableTexture detltex = null; + RenderableTexture heighttex = null; bool isdistmap = false; float tntpalind = 0.0f; @@ -664,6 +669,8 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) texture2 = itex; break; case ShaderParamNames.heightSampler: + heighttex = itex; + break; case ShaderParamNames.EnvironmentSampler: //case MetaName.SnowSampler0: //case MetaName.SnowSampler1: @@ -704,6 +711,7 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) bool usespec = ((spectex != null) && (spectex.ShaderResourceView != null)); bool usedetl = ((detltex != null) && (detltex.ShaderResourceView != null)); bool usetint = ((tintpal != null) && (tintpal.ShaderResourceView != null)); + bool useheight = ((heighttex != null) && (heighttex.ShaderResourceView != null)); uint tintflag = 0; if (usetint) tintflag = 1; @@ -785,6 +793,28 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) PSGeomVars.Vars.wetnessMultiplier = geom.wetnessMultiplier; PSGeomVars.Vars.SpecOnly = geom.SpecOnly ? 1u : 0u; PSGeomVars.Vars.TextureAlphaMask = textureAlphaMask; + // Disable POM for wind-displaced geometry - wind moves vertices without + // updating the tangent frame, causing POM to displace in the wrong direction + if (windflag != 0) + { + useheight = false; + } + PSGeomVars.Vars.EnableHeightMap = useheight ? 1u : 0u; + + // DPM shaders store heightScale/heightBias calibrated for tessellation vertex + // displacement (default 0.4 / -0.5, range -10 to 10). POM needs much smaller + // positive values (default 0.03 / 0.015, range 0.01-0.05). Detect tessellation- + // range values and convert them to POM range to prevent excessive UV warping. + float hs = geom.heightScale; + float hb = geom.heightBias; + if (Math.Abs(hs) > 0.1f || hb < 0.0f) + { + hs = Math.Abs(hs) * 0.075f; // DPM(0.4) -> POM(0.03) + hb = hs * 0.5f; // POM convention: bias = scale/2 + } + PSGeomVars.Vars.heightScale = hs; + PSGeomVars.Vars.heightBias = hb; + PSGeomVars.Vars.Pad0 = 0.0f; PSGeomVars.Update(context); PSGeomVars.SetPSCBuffer(context, 2); @@ -828,6 +858,10 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) { tintpal.SetPSResource(context, 6); } + if (useheight) + { + heighttex.SetPSResource(context, 7); + } if (geom.BoneTransforms != null) diff --git a/CodeWalker/Rendering/Shaders/TerrainShader.cs b/CodeWalker/Rendering/Shaders/TerrainShader.cs index 1e894193a..93bf383c3 100644 --- a/CodeWalker/Rendering/Shaders/TerrainShader.cs +++ b/CodeWalker/Rendering/Shaders/TerrainShader.cs @@ -63,7 +63,9 @@ public struct TerrainShaderPSGeomVars public uint EnableTint; public uint EnableVertexColour; public float bumpiness; - public uint Pad102; + public uint EnableHeightMap; + public Vector4 heightScale; // x=layer0, y=layer1, z=layer2, w=layer3 + public Vector4 heightBias; // x=layer0, y=layer1, z=layer2, w=layer3 } public class TerrainShader : Shader, IDisposable @@ -372,6 +374,10 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) RenderableTexture normals2 = null; RenderableTexture normals3 = null; RenderableTexture normals4 = null; + RenderableTexture heightmap0 = null; + RenderableTexture heightmap1 = null; + RenderableTexture heightmap2 = null; + RenderableTexture heightmap3 = null; float tntpalind = 0.0f; bool usevc = true; @@ -432,6 +438,18 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) tntpalind = (VSEntityVars.Vars.TintPaletteIndex + 0.5f) / tintpal.Key.Height; } break; + case ShaderParamNames.heightMapSamplerLayer0: + heightmap0 = itex; + break; + case ShaderParamNames.heightMapSamplerLayer1: + heightmap1 = itex; + break; + case ShaderParamNames.heightMapSamplerLayer2: + heightmap2 = itex; + break; + case ShaderParamNames.heightMapSamplerLayer3: + heightmap3 = itex; + break; } } @@ -531,6 +549,11 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) bool usemask = ((texturemask != null) && (texturemask.ShaderResourceView != null)); bool usetint = ((tintpal != null) && (tintpal.ShaderResourceView != null)); bool usenm = (((normals0 != null) && (normals0.ShaderResourceView != null)) || ((normals1 != null) && (normals1.ShaderResourceView != null))); + bool useheight0 = ((heightmap0 != null) && (heightmap0.ShaderResourceView != null)); + bool useheight1 = ((heightmap1 != null) && (heightmap1.ShaderResourceView != null)); + bool useheight2 = ((heightmap2 != null) && (heightmap2.ShaderResourceView != null)); + bool useheight3 = ((heightmap3 != null) && (heightmap3.ShaderResourceView != null)); + bool useheight = useheight0 || useheight1 || useheight2 || useheight3; float bumpiness = 1.0f; @@ -550,7 +573,10 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) PSGeomVars.Vars.ShaderName = geom.DrawableGeom.Shader.Name.Hash; PSGeomVars.Vars.EnableTint = usetint ? 1u : 0u; PSGeomVars.Vars.EnableVertexColour = usevc ? 1u : 0u; - PSGeomVars.Vars.bumpiness = bumpiness;// + PSGeomVars.Vars.bumpiness = bumpiness; + PSGeomVars.Vars.EnableHeightMap = useheight ? 1u : 0u; + PSGeomVars.Vars.heightScale = new Vector4(geom.heightScale0, geom.heightScale1, geom.heightScale2, geom.heightScale3); + PSGeomVars.Vars.heightBias = new Vector4(geom.heightBias0, geom.heightBias1, geom.heightBias2, geom.heightBias3); PSGeomVars.Update(context); PSGeomVars.SetPSCBuffer(context, 2); @@ -575,6 +601,10 @@ public override void SetGeomVars(DeviceContext context, RenderableGeometry geom) if (normals2 != null) normals2.SetPSResource(context, 9); if (normals3 != null) normals3.SetPSResource(context, 10); if (normals4 != null) normals4.SetPSResource(context, 11); + if (useheight0) heightmap0.SetPSResource(context, 12); + if (useheight1) heightmap1.SetPSResource(context, 13); + if (useheight2) heightmap2.SetPSResource(context, 14); + if (useheight3) heightmap3.SetPSResource(context, 15); } @@ -604,6 +634,10 @@ public override void UnbindResources(DeviceContext context) context.PixelShader.SetShaderResource(9, null); context.PixelShader.SetShaderResource(10, null); context.PixelShader.SetShaderResource(11, null); + context.PixelShader.SetShaderResource(12, null); + context.PixelShader.SetShaderResource(13, null); + context.PixelShader.SetShaderResource(14, null); + context.PixelShader.SetShaderResource(15, null); context.VertexShader.Set(null); context.PixelShader.Set(null); } diff --git a/Shaders/BasicPS.cso b/Shaders/BasicPS.cso index 266b52deb..c8752c11f 100644 Binary files a/Shaders/BasicPS.cso and b/Shaders/BasicPS.cso differ diff --git a/Shaders/BasicPS_Deferred.cso b/Shaders/BasicPS_Deferred.cso index dd7ffd093..3e459d4f5 100644 Binary files a/Shaders/BasicPS_Deferred.cso and b/Shaders/BasicPS_Deferred.cso differ diff --git a/Shaders/TerrainPS.cso b/Shaders/TerrainPS.cso index 4629d7d17..4fcee40d1 100644 Binary files a/Shaders/TerrainPS.cso and b/Shaders/TerrainPS.cso differ diff --git a/Shaders/TerrainPS_Deferred.cso b/Shaders/TerrainPS_Deferred.cso index b007a86f5..c753996fb 100644 Binary files a/Shaders/TerrainPS_Deferred.cso and b/Shaders/TerrainPS_Deferred.cso differ diff --git a/Shaders/TerrainVS_PNCCT.cso b/Shaders/TerrainVS_PNCCT.cso index 97924d343..cdba70a93 100644 Binary files a/Shaders/TerrainVS_PNCCT.cso and b/Shaders/TerrainVS_PNCCT.cso differ diff --git a/Shaders/TerrainVS_PNCCTT.cso b/Shaders/TerrainVS_PNCCTT.cso index 6e41f2933..e00931694 100644 Binary files a/Shaders/TerrainVS_PNCCTT.cso and b/Shaders/TerrainVS_PNCCTT.cso differ diff --git a/Shaders/TerrainVS_PNCCTTTX.cso b/Shaders/TerrainVS_PNCCTTTX.cso index 2ad21bca6..5f449155b 100644 Binary files a/Shaders/TerrainVS_PNCCTTTX.cso and b/Shaders/TerrainVS_PNCCTTTX.cso differ diff --git a/Shaders/TerrainVS_PNCCTTX.cso b/Shaders/TerrainVS_PNCCTTX.cso index 77641ede1..375fe6590 100644 Binary files a/Shaders/TerrainVS_PNCCTTX.cso and b/Shaders/TerrainVS_PNCCTTX.cso differ diff --git a/Shaders/TerrainVS_PNCCTX.cso b/Shaders/TerrainVS_PNCCTX.cso index 9639d3b1b..da0f636e8 100644 Binary files a/Shaders/TerrainVS_PNCCTX.cso and b/Shaders/TerrainVS_PNCCTX.cso differ diff --git a/Shaders/TerrainVS_PNCTTTX.cso b/Shaders/TerrainVS_PNCTTTX.cso index 293ade2b8..342b7a4c0 100644 Binary files a/Shaders/TerrainVS_PNCTTTX.cso and b/Shaders/TerrainVS_PNCTTTX.cso differ diff --git a/Shaders/TerrainVS_PNCTTX.cso b/Shaders/TerrainVS_PNCTTX.cso index ae6007628..f160160d7 100644 Binary files a/Shaders/TerrainVS_PNCTTX.cso and b/Shaders/TerrainVS_PNCTTX.cso differ