From fb1ad41e58b37f0f44adc25bf59932a531c31981 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 27 Apr 2026 21:23:53 +1000 Subject: [PATCH 01/29] v1 --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 108 ++-- .../Shaders/Features/TerrainVariation.ini | 2 +- .../TerrainVariation/TerrainVariation.hlsli | 280 +++++---- .../Shaders/Common/LightingLandscape.hlsli | 123 ++++ package/Shaders/Lighting.hlsl | 591 ++---------------- src/Features/TerrainVariation.cpp | 36 +- src/Features/TerrainVariation.h | 18 +- 7 files changed, 406 insertions(+), 752 deletions(-) create mode 100644 package/Shaders/Common/LightingLandscape.hlsli diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index f66859c9da..4900cd0377 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -80,6 +80,20 @@ namespace ExtendedMaterials } #if defined(LANDSCAPE) +# if defined(TERRAIN_VARIATION) + void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6], StochasticOffsets sharedOffset) +# else + void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6]) +# endif + { + mipLevels[0] = GetMipLevel(coords, TexColorSampler, screenNoise); + mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, screenNoise); + mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler, screenNoise); + mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler, screenNoise); + mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler, screenNoise); + mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler, screenNoise); + } + # define HEIGHT_POWER 2 # define HEIGHT_MULT 8 @@ -120,7 +134,7 @@ namespace ExtendedMaterials # if defined(TRUE_PBR) float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, # if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, float2 dx, float2 dy, + StochasticOffsets sharedOffset, # endif out float weights[6]) { @@ -130,7 +144,7 @@ namespace ExtendedMaterials [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile0HasDisplacement) != 0 && w1.x > 0.01) { # if defined(TERRAIN_VARIATION) - heights[0] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement0Sampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset, dx, dy).x, params[0]); + heights[0] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement0Sampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset).x, params[0]); # else heights[0] = ScaleDisplacement(TexLandDisplacement0Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[0]).x, params[0]); # endif @@ -138,7 +152,7 @@ namespace ExtendedMaterials [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile1HasDisplacement) != 0 && w1.y > 0.01) { # if defined(TERRAIN_VARIATION) - heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement1Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset, dx, dy).x, params[1]); + heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement1Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset).x, params[1]); # else heights[1] = ScaleDisplacement(TexLandDisplacement1Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[1]).x, params[1]); # endif @@ -146,7 +160,7 @@ namespace ExtendedMaterials [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile2HasDisplacement) != 0 && w1.z > 0.01) { # if defined(TERRAIN_VARIATION) - heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement2Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset, dx, dy).x, params[2]); + heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement2Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset).x, params[2]); # else heights[2] = ScaleDisplacement(TexLandDisplacement2Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[2]).x, params[2]); # endif @@ -154,7 +168,7 @@ namespace ExtendedMaterials [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile3HasDisplacement) != 0 && w1.w > 0.01) { # if defined(TERRAIN_VARIATION) - heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement3Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset, dx, dy).x, params[3]); + heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement3Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset).x, params[3]); # else heights[3] = ScaleDisplacement(TexLandDisplacement3Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[3]).x, params[3]); # endif @@ -162,7 +176,7 @@ namespace ExtendedMaterials [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile4HasDisplacement) != 0 && w2.x > 0.01) { # if defined(TERRAIN_VARIATION) - heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement4Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset, dx, dy).x, params[4]); + heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement4Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset).x, params[4]); # else heights[4] = ScaleDisplacement(TexLandDisplacement4Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[4]).x, params[4]); # endif @@ -170,7 +184,7 @@ namespace ExtendedMaterials [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile5HasDisplacement) != 0 && w2.y > 0.01) { # if defined(TERRAIN_VARIATION) - heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement5Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset, dx, dy).x, params[5]); + heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement5Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset).x, params[5]); # else heights[5] = ScaleDisplacement(TexLandDisplacement5Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[5]).x, params[5]); # endif @@ -187,10 +201,11 @@ namespace ExtendedMaterials # endif return total; } + # else float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, # if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, float2 dx, float2 dy, + StochasticOffsets sharedOffset, # endif out float weights[6]) { @@ -201,7 +216,7 @@ namespace ExtendedMaterials [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand0HasDisplacement) != 0) { # if defined(TERRAIN_VARIATION) - heights[0] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp0Sampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset, dx, dy).x, params[0]); + heights[0] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp0Sampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset).x, params[0]); # else heights[0] = ScaleDisplacement(TexLandTHDisp0Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[0]).x, params[0]); # endif @@ -209,7 +224,7 @@ namespace ExtendedMaterials else { # if defined(TERRAIN_VARIATION) - heights[0] = ScaleDisplacement(StochasticEffectParallax(TexColorSampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset, dx, dy).w, params[0]); + heights[0] = ScaleDisplacement(StochasticEffectParallax(TexColorSampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset).w, params[0]); # else heights[0] = ScaleDisplacement(TexColorSampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[0]).w, params[0]); # endif @@ -219,7 +234,7 @@ namespace ExtendedMaterials [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand1HasDisplacement) != 0) { # if defined(TERRAIN_VARIATION) - heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp1Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset, dx, dy).x, params[1]); + heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp1Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset).x, params[1]); # else heights[1] = ScaleDisplacement(TexLandTHDisp1Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[1]).x, params[1]); # endif @@ -227,7 +242,7 @@ namespace ExtendedMaterials else { # if defined(TERRAIN_VARIATION) - heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandColor2Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset, dx, dy).w, params[1]); + heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandColor2Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset).w, params[1]); # else heights[1] = ScaleDisplacement(TexLandColor2Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[1]).w, params[1]); # endif @@ -237,7 +252,7 @@ namespace ExtendedMaterials [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand2HasDisplacement) != 0) { # if defined(TERRAIN_VARIATION) - heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp2Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset, dx, dy).x, params[2]); + heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp2Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset).x, params[2]); # else heights[2] = ScaleDisplacement(TexLandTHDisp2Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[2]).x, params[2]); # endif @@ -245,7 +260,7 @@ namespace ExtendedMaterials else { # if defined(TERRAIN_VARIATION) - heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandColor3Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset, dx, dy).w, params[2]); + heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandColor3Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset).w, params[2]); # else heights[2] = ScaleDisplacement(TexLandColor3Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[2]).w, params[2]); # endif @@ -254,7 +269,7 @@ namespace ExtendedMaterials [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand3HasDisplacement) != 0 && w1.w > 0.01) { # if defined(TERRAIN_VARIATION) - heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp3Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset, dx, dy).x, params[3]); + heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp3Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset).x, params[3]); # else heights[3] = ScaleDisplacement(TexLandTHDisp3Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[3]).x, params[3]); # endif @@ -262,7 +277,7 @@ namespace ExtendedMaterials else if (w1.w > 0.01) { # if defined(TERRAIN_VARIATION) - heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandColor4Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset, dx, dy).w, params[3]); + heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandColor4Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset).w, params[3]); # else heights[3] = ScaleDisplacement(TexLandColor4Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[3]).w, params[3]); # endif @@ -270,7 +285,7 @@ namespace ExtendedMaterials [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand4HasDisplacement) != 0 && w2.x > 0.01) { # if defined(TERRAIN_VARIATION) - heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp4Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset, dx, dy).x, params[4]); + heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp4Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset).x, params[4]); # else heights[4] = ScaleDisplacement(TexLandTHDisp4Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[4]).x, params[4]); # endif @@ -278,7 +293,7 @@ namespace ExtendedMaterials else if (w2.x > 0.01) { # if defined(TERRAIN_VARIATION) - heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandColor5Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset, dx, dy).w, params[4]); + heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandColor5Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset).w, params[4]); # else heights[4] = ScaleDisplacement(TexLandColor5Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[4]).w, params[4]); # endif @@ -286,7 +301,7 @@ namespace ExtendedMaterials [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand5HasDisplacement) != 0 && w2.y > 0.01) { # if defined(TERRAIN_VARIATION) - heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp5Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset, dx, dy).x, params[5]); + heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp5Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset).x, params[5]); # else heights[5] = ScaleDisplacement(TexLandTHDisp5Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[5]).x, params[5]); # endif @@ -294,7 +309,7 @@ namespace ExtendedMaterials else if (w2.y > 0.01) { # if defined(TERRAIN_VARIATION) - heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandColor6Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset, dx, dy).w, params[5]); + heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandColor6Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset).w, params[5]); # else heights[5] = ScaleDisplacement(TexLandColor6Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[5]).w, params[5]); # endif @@ -311,6 +326,7 @@ namespace ExtendedMaterials # endif return total; } + # endif #endif @@ -318,7 +334,7 @@ namespace ExtendedMaterials #if defined(LANDSCAPE) float2 GetParallaxCoords(PS_INPUT input, float distance, float2 coords, float mipLevels[6], float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], # if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, float2 dx, float2 dy, + StochasticOffsets sharedOffset, # endif out float pixelOffset, # if defined(VR_STEREO_OPT) @@ -409,10 +425,10 @@ namespace ExtendedMaterials #if defined(LANDSCAPE) # if defined(TRUE_PBR) # if defined(TERRAIN_VARIATION) - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) * scalercp + 0.5; - currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) * scalercp + 0.5; - currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) * scalercp + 0.5; - currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) * scalercp + 0.5; + currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; + currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; + currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; + currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; # else currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; @@ -421,10 +437,10 @@ namespace ExtendedMaterials # endif # else # if defined(TERRAIN_VARIATION) - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) + 0.5; - currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) + 0.5; - currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) + 0.5; - currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, dx, dy, weights) + 0.5; + currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; + currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; + currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; + currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; # else currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; @@ -552,7 +568,7 @@ namespace ExtendedMaterials #if defined(LANDSCAPE) # if defined(TERRAIN_VARIATION) - float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, float2 dx, float2 dy) + float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset) # else float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6]) # endif @@ -571,13 +587,13 @@ namespace ExtendedMaterials rayDir *= scale; # if defined(TERRAIN_VARIATION) - sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); if (quality > 0.25) - sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); if (quality > 0.5) - sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); if (quality > 0.75) - sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); # else sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); if (quality > 0.25) @@ -590,13 +606,13 @@ namespace ExtendedMaterials return 1.0 - saturate(dot(max(0, sh - sh0) / scale, ShadowIntensity)) * quality; # else # if defined(TERRAIN_VARIATION) - sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); if (quality > 0.25) - sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); if (quality > 0.5) - sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); if (quality > 0.75) - sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, heights); + sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); # else sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); if (quality > 0.25) @@ -612,5 +628,21 @@ namespace ExtendedMaterials return 1.0; } +# if defined(TERRAIN_VARIATION) + float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) + { + float weights[6] = { 0, 0, 0, 0, 0, 0 }; + sh0 = GetTerrainHeight(noise, input, coords, mipLevels, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, weights); + return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params, sharedOffset); + } +# else + float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], out float sh0) + { + float weights[6] = { 0, 0, 0, 0, 0, 0 }; + sh0 = GetTerrainHeight(noise, input, coords, mipLevels, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, weights); + return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params); + } +# endif + #endif // defined(LANDSCAPE) && defined(TERRAIN_VARIATION) } diff --git a/features/Terrain Variation/Shaders/Features/TerrainVariation.ini b/features/Terrain Variation/Shaders/Features/TerrainVariation.ini index f69060ffde..c9e4205955 100644 --- a/features/Terrain Variation/Shaders/Features/TerrainVariation.ini +++ b/features/Terrain Variation/Shaders/Features/TerrainVariation.ini @@ -1,5 +1,5 @@ [Info] -Version = 1-0-1 +Version = 1-1-0 [Nexus] nexusmodid = 148123 diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 1a34cd0df6..998740e654 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -5,28 +5,25 @@ #ifndef TERRAIN_VARIATION_HLSLI #define TERRAIN_VARIATION_HLSLI -#include "Common/Math.hlsli" -#include "Common/Random.hlsli" #include "Common/SharedData.hlsli" -// --------------------- CONSTANTS AND STRUCTURES --------------------- // -// Height blend operator settings - DO NOT CHANGE THESE VALUES. -static const float HEIGHT_BLEND_CONTRAST = 12.0; // Controls sharpness of height-based transitions (reduced from 16.0 for performance) -static const float HEIGHT_INFLUENCE = 0.3; // How much height affects blending (0=pure stochastic, 1=pure height) -// Pre-computed constants to avoid runtime calculations +// --------------------- CONSTANTS --------------------- // static const float2x2 SKEW_MATRIX = float2x2(1.0, 0.0, -0.57735027, 1.15470054); static const float WORLD_SCALE = 332.54; -// Blending constants -static const float3 DEFAULT_WEIGHTS = float3(0.33, 0.33, 0.34); -static const float3 LUMINANCE_WEIGHTS = float3(0.2126, 0.7152, 0.0722); -// Hash constants static const float2 HASH_MULTIPLIER = float2(1271.5151, 3337.8237); -// Performance optimization constants -static const float MIP_LEVEL_INCREASE = 0.5; // Additional mip level increase for distance optimization -static const float DISTANCE_SAMPLE_REDUCTION = 2.0; // Mip level where we reduce to 2 samples -static const float FAR_DISTANCE_THRESHOLD = 4.0; // Mip level where we use single sample with higher mip level - -// Structure to hold stochastic sampling offsets and weights +static const float HEIGHT_BLEND_CONTRAST = 12.0; +static const float HEIGHT_INFLUENCE = 0.3; +static const float CONTRAST_FACTOR = HEIGHT_BLEND_CONTRAST * (1.0 - HEIGHT_INFLUENCE); +static const float3 LUMINANCE_WEIGHTS = float3(0.2126, 0.7152, 0.0722); +static const float HEIGHT_BLEND_FADE_MIP_START = 1.6; +static const float HEIGHT_BLEND_FADE_MIP_RANGE = 2.2; +// TV distant/minified fallback: bypass stochastic blend and use one sample. +static const float TV_SINGLE_SAMPLE_MIP_START = 2.9; +static const float TV_SINGLE_SAMPLE_PARALLAX_MIP_START = 2.6; +// Golden ratio for frac(rnd * φ) low-discrepancy jitter; precompute once per pixel at callsite when possible. +static const float STOCHASTIC_LOD_PHI = 1.618; + +// --------------------- STRUCTURES --------------------- // struct StochasticOffsets { float2 offset1; @@ -35,17 +32,16 @@ struct StochasticOffsets float3 weights; }; -// --------------------- FUNCTION DECLARATIONS --------------------- // -float4 StochasticSampleLOD(float rnd, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD, float2 dx, float2 dy); -float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float2 dx, float2 dy); -float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets, float2 dx, float2 dy); - -// --------------------- COMPUTE FUNCTIONS --------------------- // +// Triangle corner for barycentric sort: pack cell id + weight so each swap updates both. +struct StochasticCorner +{ + float2 cell; + float w; +}; -// Hash function for stochastic sampling +// --------------------- HASH FUNCTIONS --------------------- // inline float2 hash2D2D(float2 s) { - // More efficient hash using frac and multiply operations s = frac(s * HASH_MULTIPLIER); s += dot(s, s.yx + 19.19); return frac((s.xx + s.yy) * s.yx); @@ -54,150 +50,160 @@ inline float2 hash2D2D(float2 s) inline float2 hashLOD(float2 p) { p = frac(p * 0.318); - return frac(p.x + p.y * float2(17.0, 23.0)); + return frac(float2(dot(p, float2(1.0, 17.0)), dot(p, float2(1.0, 23.0)))); } -inline float3 NormalizeWeights(float3 weights) +// --------------------- COMPUTE FUNCTIONS --------------------- // +inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) { - float weightSum = weights.x + weights.y + weights.z; - // Skip expensive division if already normalized - if (abs(weightSum - 1.0) < 0.01) - return weights; - float rcpWeightSum = rcp(max(weightSum, EPSILON_DIVISION)); - return weights * rcpWeightSum; -} + if (!SharedData::terrainVariationSettings.enableTilingFix) + return (StochasticOffsets)0; -// Common barycentric coordinate calculation for stochastic sampling -inline float4x3 ComputeBarycentricVerts(float2 landscapeUV) -{ - float2 scaledUV = landscapeUV * (WORLD_SCALE); - float2 skewUV = mul(SKEW_MATRIX, scaledUV); + float2 skewUV = mul(SKEW_MATRIX, landscapeUV * WORLD_SCALE); float2 vxID = floor(skewUV); - float2 frac_uv = frac(skewUV); + float2 f = frac(skewUV); + float bz = 1.0 - f.x - f.y; + + StochasticCorner c0, c1, c2; + if (bz > 0) { + c0.cell = vxID; + c0.w = bz; + c1.cell = vxID + float2(0, 1); + c1.w = f.y; + c2.cell = vxID + float2(1, 0); + c2.w = f.x; + } else { + c0.cell = vxID + 1.0; + c0.w = -bz; + c1.cell = vxID + float2(1, 0); + c1.w = 1.0 - f.y; + c2.cell = vxID + float2(0, 1); + c2.w = 1.0 - f.x; + } - float barry_z = 1.0 - frac_uv.x - frac_uv.y; - float3 barry = float3(frac_uv, barry_z); + // Sort by weight descending (3-comparator network). Only c0/c1 are hashed; weights.xy must be the two largest. + if (c1.w > c0.w) { + StochasticCorner t = c0; + c0 = c1; + c1 = t; + } + if (c2.w > c0.w) { + StochasticCorner t = c0; + c0 = c2; + c2 = t; + } + if (c2.w > c1.w) { + StochasticCorner t = c1; + c1 = c2; + c2 = t; + } - return (barry.z > 0) ? - float4x3(float3(vxID, 0), float3(vxID + float2(0, 1), 0), float3(vxID + float2(1, 0), 0), barry.zyx) : - float4x3(float3(vxID + float2(1, 1), 0), float3(vxID + float2(1, 0), 0), float3(vxID + float2(0, 1), 0), float3(-barry.z, 1.0 - barry.y, 1.0 - barry.x)); + StochasticOffsets o; + o.offset1 = hash2D2D(c0.cell); + o.offset2 = hash2D2D(c1.cell); + o.offset3 = 0; + o.weights = float3(c0.w, c1.w, c2.w); + return o; } -inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) +inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) { - float4x3 BW_vx = ComputeBarycentricVerts(landscapeUV); + if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + return (StochasticOffsets)0; + + float2 cellID = floor(landscapeUV * 255437.0); + float2 h1 = hashLOD(cellID); + float2 h2 = hashLOD(cellID + 127.0); + + StochasticOffsets o; + o.offset1 = h1 * 0.08; + o.offset2 = h2 * 0.08; + o.offset3 = 0; + o.weights = float3(0.65, 0.35, 0.0); + return o; +} - StochasticOffsets offsets; - offsets.offset1 = hash2D2D(BW_vx[0].xy); - offsets.offset2 = hash2D2D(BW_vx[1].xy); - offsets.offset3 = hash2D2D(BW_vx[2].xy); - offsets.weights = BW_vx[3]; +// --------------------- SAMPLING FUNCTIONS --------------------- // - return offsets; +inline float2 StochasticSampleLODJitter(float rnd) +{ + return float2(rnd - 0.5, frac(rnd * STOCHASTIC_LOD_PHI) - 0.5); } -inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) +inline float StochasticHeightFadeFromMip(float mipLevel) { - // Precomputed scaling: (WORLD_SCALE / 0.010416667) * 8.0 = ~255437 - static const float LOD_SCALE = 255437.0; - - float2 scaledUV = landscapeUV * LOD_SCALE; - float2 cellID = floor(scaledUV); - - StochasticOffsets offsetsLOD; - // Generate both offsets from single hash to reduce calls - float2 hash1 = hashLOD(cellID); - float2 hash2 = hashLOD(cellID + 127.0); - - offsetsLOD.offset1 = hash1 * 0.08; - offsetsLOD.offset2 = hash2 * 0.08; + return saturate((mipLevel - HEIGHT_BLEND_FADE_MIP_START) / HEIGHT_BLEND_FADE_MIP_RANGE); +} - // Simplified weights since we only use 2 samples now - offsetsLOD.weights = float3(0.65, 0.35, 0.0); +// LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). +inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) +{ + if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + return tex.SampleBias(samp, uv, SharedData::MipBias); - return offsetsLOD; + float4 s1 = tex.SampleBias(samp, uv + (offsetsLOD.offset1 + jitter) * 0.01, SharedData::MipBias); + float4 s2 = tex.SampleBias(samp, uv + (offsetsLOD.offset2 + float2(jitter.y, -jitter.x)) * 0.01, SharedData::MipBias); + return lerp(s2, s1, offsetsLOD.weights.x); } -// --------------------- STOCHASTIC SAMPLING FUNCTIONS --------------------- // -// Stochastic sampling function for Terrain LOD & LOD Mask. -inline float4 StochasticSampleLOD(float rnd, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD, float2 dx, float2 dy) +// 2-sample height-blended stochastic sampling — branchless, no wavefront divergence. +// Sorting in ComputeStochasticOffsets guarantees offset1/offset2 are the two +// highest-weight barycentric vertices, so dropping offset3 loses minimal quality. +inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) { - float offsetScale = 0.01; + if (!SharedData::terrainVariationSettings.enableTilingFix) + return tex.SampleBias(samp, uv, SharedData::MipBias + extraLandMipBias); - // Cheap pseudo-rotation using simple transforms - float2 dir1 = float2(rnd - 0.5, frac(rnd * 1.618) - 0.5); - float2 dir2 = float2(dir1.y, -dir1.x); + float mipLevel = tex.CalculateLevelOfDetail(samp, uv) + SharedData::MipBias + extraLandMipBias; + // Far/minified: skip TV blending math + second sample. + if (mipLevel >= TV_SINGLE_SAMPLE_MIP_START) + return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); - // Apply simple scaled offsets - float2 microOffset1 = (offsetsLOD.offset1 + dir1) * offsetScale; - float2 microOffset2 = (offsetsLOD.offset2 + dir2) * offsetScale; - float4 sample1 = tex.SampleBias(samp, uv + microOffset1, SharedData::MipBias); - float4 sample2 = tex.SampleBias(samp, uv + microOffset2, SharedData::MipBias); + float w1 = exp2(log2(saturate(offsets.weights.x)) * CONTRAST_FACTOR); + float w2 = exp2(log2(saturate(offsets.weights.y)) * CONTRAST_FACTOR); + float heightFade = StochasticHeightFadeFromMip(mipLevel); + float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); - // Simple 2-sample blend weighted toward first sample - return lerp(sample2, sample1, 0.65); -} + float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a)); + float h2 = lerp(dot(s2.rgb, LUMINANCE_WEIGHTS), s2.a, step(0.001, s2.a)); -// Main stochastic sampling function -inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float2 dx, float2 dy) -{ - // Calculate custom mip level from original UVs. - float mipLevel = tex.CalculateLevelOfDetail(samp, uv); - float adjustedMipLevel = mipLevel + SharedData::MipBias; - - // 3 Sample Blend - float4 sample1 = tex.SampleLevel(samp, uv + offsets.offset1, adjustedMipLevel); - float4 sample2 = tex.SampleLevel(samp, uv + offsets.offset2, adjustedMipLevel); - float4 sample3 = tex.SampleLevel(samp, uv + offsets.offset3, adjustedMipLevel); - - // Full height-based blending for terrain - float contrastFactor = HEIGHT_BLEND_CONTRAST * (1.0 - HEIGHT_INFLUENCE); - float3 blendWeights = pow(saturate(offsets.weights), contrastFactor); - - // Height calculation - use luminance for RGB data, alpha when available - float3 luminanceHeights = float3( - dot(sample1.rgb, LUMINANCE_WEIGHTS), - dot(sample2.rgb, LUMINANCE_WEIGHTS), - dot(sample3.rgb, LUMINANCE_WEIGHTS)); - - float3 alphaValues = float3(sample1.a, sample2.a, sample3.a); - float3 alphaMask = step(0.001, alphaValues); - float3 heights = lerp(luminanceHeights, alphaValues, alphaMask); - - // Combined weight calculation and normalization - float3 weights = NormalizeWeights(blendWeights * (1.0 + HEIGHT_INFLUENCE * heights)); - - // Final blend - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; + w1 *= (1.0 + heightInfluence * h1); + w2 *= (1.0 + heightInfluence * h2); + + return lerp(s2, s1, w1 * rcp(w1 + w2)); } -// Stochastic sampling function without height blending for better performance -// Disable X4000 warning: FXC incorrectly reports potentially uninitialized variables due to complex control flow with early returns and conditional sampling -#pragma warning(push) -#pragma warning(disable: 4000) -inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets, float2 dx, float2 dy) + +// 2-sample parallax sampling — uses heightmap (alpha) only for blend weights. +inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { - // Early exit for disabled terrain variation - avoid all other computations - if (!SharedData::terrainVariationSettings.enableTilingFix) { + if (!SharedData::terrainVariationSettings.enableTilingFix) return tex.SampleLevel(samp, uv, mipLevel); - } + // Keep parallax height active, but cut TV to one sample once heavily minified. + if (mipLevel >= TV_SINGLE_SAMPLE_PARALLAX_MIP_START) + return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - // Use progressive mip level increase for better performance in parallax - float adjustedMipLevel = mipLevel; - if (mipLevel > 1.0) { - adjustedMipLevel = mipLevel + (MIP_LEVEL_INCREASE * 0.5); - } + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + + float w1 = exp2(log2(saturate(offsets.weights.x)) * CONTRAST_FACTOR); + float w2 = exp2(log2(saturate(offsets.weights.y)) * CONTRAST_FACTOR); + float heightFade = StochasticHeightFadeFromMip(mipLevel); + float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); - // Take three samples for blending at the adjusted mip level - float4 sample1 = tex.SampleLevel(samp, uv + offsets.offset1, adjustedMipLevel); - float4 sample2 = tex.SampleLevel(samp, uv + offsets.offset2, adjustedMipLevel); - float4 sample3 = tex.SampleLevel(samp, uv + offsets.offset3, adjustedMipLevel); + w1 *= (1.0 + heightInfluence * s1.a); + w2 *= (1.0 + heightInfluence * s2.a); - // Simple barycentric blend without height influence - float3 weights = NormalizeWeights(saturate(offsets.weights)); - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; + return lerp(s2, s1, w1 * rcp(w1 + w2)); +} + + +inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) +{ + return StochasticEffect(tex, samp, uv, offsets, extraLandMipBias); } -#pragma warning(pop) #endif // TERRAIN_VARIATION_HLSLI \ No newline at end of file diff --git a/package/Shaders/Common/LightingLandscape.hlsli b/package/Shaders/Common/LightingLandscape.hlsli new file mode 100644 index 0000000000..97d9d8701d --- /dev/null +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -0,0 +1,123 @@ +#ifndef __LIGHTING_LANDSCAPE_HLSLI__ +#define __LIGHTING_LANDSCAPE_HLSLI__ + +// Shared terrain layer indexing for TRUE_PBR (PBRFlags terrain bits) and non-PBR displacement +// (Permutation::ExtraFeatureDescriptor TH land bits). HLSL cannot index texture registers by loop +// variable; use LANDSCAPE_*_LAYER_FOREACH (X-macros) for per-layer texture bodies. +// +// PBR terrain bit layout must stay aligned with PBR::TerrainFlags in PBRMath.hlsli. + +#if defined(LANDSCAPE) + +# if !defined(__PERMUTATION_DEPENDENCY_HLSL__) +# include "Common/Permutation.hlsli" +# endif + +namespace LandscapeLayers +{ +# if defined(TRUE_PBR) + // Tiles 0..5: PBR, displacement, glint packed contiguously (see PBRMath.hlsli). + inline bool PbrTileUsesFullPBR(uint tileIndex) + { + return (PBRFlags & (1u << tileIndex)) != 0; + } + inline bool PbrTileHasDisplacement(uint tileIndex) + { + return (PBRFlags & (1u << (tileIndex + 6u))) != 0; + } + inline bool PbrTileHasGlint(uint tileIndex) + { + return (PBRFlags & (1u << (tileIndex + 12u))) != 0; + } +# else + // Matches Permutation::ExtraFeatureFlags::THLand0HasDisplacement .. THLand5HasDisplacement + inline bool ThTileHasDisplacement(uint tileIndex) + { + return (Permutation::ExtraFeatureDescriptor & (1u << tileIndex)) != 0; + } +# endif +} + +// tileIndex, displacementTexture, diffuseOrAlphaHeightTexture (mip dims / non-PBR height) +# if defined(TRUE_PBR) +# define LANDSCAPE_PBR_LAYER_FOREACH(X) \ + X(0, TexLandDisplacement0Sampler, TexColorSampler) \ + X(1, TexLandDisplacement1Sampler, TexLandColor2Sampler) \ + X(2, TexLandDisplacement2Sampler, TexLandColor3Sampler) \ + X(3, TexLandDisplacement3Sampler, TexLandColor4Sampler) \ + X(4, TexLandDisplacement4Sampler, TexLandColor5Sampler) \ + X(5, TexLandDisplacement5Sampler, TexLandColor6Sampler) +# else +# define LANDSCAPE_TH_LAYER_FOREACH(X) \ + X(0, TexLandTHDisp0Sampler, TexColorSampler) \ + X(1, TexLandTHDisp1Sampler, TexLandColor2Sampler) \ + X(2, TexLandTHDisp2Sampler, TexLandColor3Sampler) \ + X(3, TexLandTHDisp3Sampler, TexLandColor4Sampler) \ + X(4, TexLandTHDisp4Sampler, TexLandColor5Sampler) \ + X(5, TexLandTHDisp5Sampler, TexLandColor6Sampler) +# endif + +// --------------------------------------------------------------------------- +// Lighting.hlsl: six-way landscape diffuse / normal / RMAOS blend. +// Requires: SampleTerrain, input, uv, sharedOffset, landDistanceTexMipBias, glossiness, blendedRGB, blendedAlpha, +// blendedNormalRGB, blendedNormalAlpha, glintParameters, Color::*, GetLandSnowMaskValue (non-PBR path). +// --------------------------------------------------------------------------- +# if defined(TRUE_PBR) +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, RMAOS_TEX, RMAOS_SAMP, PBR_PARAMS3, GLINT_PARAMS, WEIGHT) \ + if (WEIGHT > 0.01) { \ + float weight = WEIGHT; \ + float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landColorRGB = landColor.rgb; \ + [branch] if (!LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landColorRGB = Color::SrgbToLinear(landColorRGB / Color::PBRLightingScale); \ + } \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + float4 landRMAOS; \ + [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, landDistanceTexMipBias) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ + if (LandscapeLayers::PbrTileHasGlint(TILE)) { \ + glintParameters += weight * (GLINT_PARAMS); \ + } \ + } \ + else \ + { \ + landRMAOS = weight * float4(1 - glossiness.x, 0, 1, 0); \ + } \ + blendedRMAOS += landRMAOS * weight; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ + } +# else +# if defined(SNOW) +# define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) \ + landSnowMask += (SNOW_COMPONENT) * weight * GetLandSnowMaskValue(landColor.w); +# else +# define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) +# endif +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, WEIGHT, SNOW_COMPONENT) \ + if (WEIGHT > 0.01) { \ + float weight = WEIGHT; \ + float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landColorRGB = landColor.rgb; \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ + LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) \ + } +# endif + +#endif // LANDSCAPE + +#endif // __LANDSCAPE_LAYERS_HLSLI__ diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 5e55b03ed3..a2526838ff 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -926,6 +926,10 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "Hair/Hair.hlsli" # endif +# if defined(LANDSCAPE) +# include "Common/LightingLandscape.hlsli" +# endif + # if defined(TERRAIN_VARIATION) # include "TerrainVariation/TerrainVariation.hlsli" # endif @@ -1223,22 +1227,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float4 blendedRMAOS = 0; # endif - // Compute stochastic offsets and derivatives once for all layers (only when terrain variation is enabled) + // Terrain Variation data is optional and only exists when plugin shaders are installed. # if defined(TERRAIN_VARIATION) - bool useTerrainVariation = SharedData::terrainVariationSettings.enableTilingFix; - // Initialise dx, dy, and sharedOffset for when Terrain Variation is disabled via enableTilingFix but still #defined - float2 dx = 0, dy = 0; - StochasticOffsets sharedOffset; - sharedOffset.offset1 = float2(0, 0); - sharedOffset.offset2 = float2(0, 0); - sharedOffset.offset3 = float2(0, 0); - sharedOffset.weights = float3(0, 0, 0); - [branch] if (useTerrainVariation) - { - dx = ddx(input.TexCoord0.zw); - dy = ddy(input.TexCoord0.zw); - sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); - } + StochasticOffsets sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); # endif # if defined(EMAT) @@ -1247,12 +1238,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # else if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { # endif - mipLevels[0] = ExtendedMaterials::GetMipLevel(uv, TexColorSampler, screenNoise); - mipLevels[1] = ExtendedMaterials::GetMipLevel(uv, TexLandColor2Sampler, screenNoise); - mipLevels[2] = ExtendedMaterials::GetMipLevel(uv, TexLandColor3Sampler, screenNoise); - mipLevels[3] = ExtendedMaterials::GetMipLevel(uv, TexLandColor4Sampler, screenNoise); - mipLevels[4] = ExtendedMaterials::GetMipLevel(uv, TexLandColor5Sampler, screenNoise); - mipLevels[5] = ExtendedMaterials::GetMipLevel(uv, TexLandColor6Sampler, screenNoise); +# if defined(TERRAIN_VARIATION) + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels, sharedOffset); +# else + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); +# endif displacementParams[1] = displacementParams[0]; displacementParams[2] = displacementParams[0]; @@ -1272,7 +1262,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) // Initialize weights array weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0; # if defined(TERRAIN_VARIATION) - uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, dx, dy, pixelOffset, + uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, # if defined(VR_STEREO_OPT) && !defined(SNOW) hasPOM, # endif @@ -1294,24 +1284,21 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) { # if defined(TERRAIN_VARIATION) - sh0 = ExtendedMaterials::GetTerrainHeight(screenNoise, input, uv, mipLevels, displacementParams, parallaxShadowQuality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, weights); - float shadowMultiplier = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, DirLightDirection, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, dx, dy); + float shadowMultiplier = ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, DirLightDirection, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, sh0); # else - sh0 = ExtendedMaterials::GetTerrainHeight(screenNoise, input, uv, mipLevels, displacementParams, parallaxShadowQuality, input.LandBlendWeights1, input.LandBlendWeights2.xy, weights); + float shadowMultiplier = ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, DirLightDirection, parallaxShadowQuality, screenNoise, displacementParams, sh0); # endif + sh0 *= shadowMultiplier; } } -# if defined(TERRAIN_VARIATION) - else if (useTerrainVariation) { + else { // Calculate proper mip levels for terrain variation when parallax is disabled but EMAT is available - mipLevels[0] = ExtendedMaterials::GetMipLevel(uv, TexColorSampler, screenNoise); - mipLevels[1] = ExtendedMaterials::GetMipLevel(uv, TexLandColor2Sampler, screenNoise); - mipLevels[2] = ExtendedMaterials::GetMipLevel(uv, TexLandColor3Sampler, screenNoise); - mipLevels[3] = ExtendedMaterials::GetMipLevel(uv, TexLandColor4Sampler, screenNoise); - mipLevels[4] = ExtendedMaterials::GetMipLevel(uv, TexLandColor5Sampler, screenNoise); - mipLevels[5] = ExtendedMaterials::GetMipLevel(uv, TexLandColor6Sampler, screenNoise); - } +# if defined(TERRAIN_VARIATION) + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels, sharedOffset); +# else + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); # endif + } # else // Initialize mip levels for non-EMAT case mipLevels[0] = mipLevels[1] = mipLevels[2] = mipLevels[3] = mipLevels[4] = mipLevels[5] = 0.0; @@ -1342,488 +1329,28 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # if defined(LANDSCAPE) - // Layer 1 (LandBlendWeights1.x) - if (input.LandBlendWeights1.x > 0.01) { - float weight = input.LandBlendWeights1.x; - - // Sample diffuse texture for layer 1 -# if defined(TERRAIN_VARIATION) - float4 landColor1; - [branch] if (useTerrainVariation) - { - landColor1 = StochasticEffect(TexColorSampler, SampColorSampler, uv, sharedOffset, dx, dy); - } - else - { - landColor1 = TexColorSampler.SampleBias(SampColorSampler, uv, SharedData::MipBias); - } -# else - float4 landColor1 = TexColorSampler.SampleBias(SampColorSampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB1 = landColor1.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile0PBR) == 0) - { - landColorRGB1 = Color::SrgbToLinear(landColorRGB1 / Color::PBRLightingScale); - } -# endif - float landAlpha1 = landColor1.a; - float landSnowMask1 = GetLandSnowMaskValue(landColor1.w); - - // Sample normal texture for layer 1 -# if defined(TERRAIN_VARIATION) - float4 landNormal1; - [branch] if (useTerrainVariation) - { - landNormal1 = StochasticEffect(TexNormalSampler, SampNormalSampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal1 = TexNormalSampler.SampleBias(SampNormalSampler, uv, SharedData::MipBias); - } -# else - float4 landNormal1 = TexNormalSampler.SampleBias(SampNormalSampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB1 = landNormal1.rgb; - float landNormalAlpha1 = landNormal1.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.x * input.LandBlendWeights1.x * landSnowMask1; -# endif // SNOW - - // Sample RMAOS texture for layer 1 -# if defined(TRUE_PBR) - float4 landRMAOS1; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile0PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS1 = StochasticEffect(TexRMAOSSampler, SampRMAOSSampler, uv, sharedOffset, dx, dy) * float4(PBRParams1.x, 1, 1, PBRParams1.z); - } - else - { - landRMAOS1 = TexRMAOSSampler.SampleBias(SampRMAOSSampler, uv, SharedData::MipBias) * float4(PBRParams1.x, 1, 1, PBRParams1.z); - } -# else - landRMAOS1 = TexRMAOSSampler.SampleBias(SampRMAOSSampler, uv, SharedData::MipBias) * float4(PBRParams1.x, 1, 1, PBRParams1.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile0HasGlint) != 0) { - glintParameters += weight * LandscapeTexture1GlintParameters; - } - } - else - { - landRMAOS1 = input.LandBlendWeights1.x * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS1 * weight; -# endif - blendedRGB += landColorRGB1 * weight; - blendedAlpha += landAlpha1 * weight; - blendedNormalRGB += landNormalRGB1 * weight; - blendedNormalAlpha += landNormalAlpha1 * weight; - } - - // Layer 2 (LandBlendWeights1.y) - if (input.LandBlendWeights1.y > 0.01) { - float weight = input.LandBlendWeights1.y; - - // Sample diffuse texture for layer 2 -# if defined(TERRAIN_VARIATION) - float4 landColor2; - [branch] if (useTerrainVariation) - { - landColor2 = StochasticEffect(TexLandColor2Sampler, SampLandColor2Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor2 = TexLandColor2Sampler.SampleBias(SampLandColor2Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor2 = TexLandColor2Sampler.SampleBias(SampLandColor2Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB2 = landColor2.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile1PBR) == 0) - { - landColorRGB2 = Color::SrgbToLinear(landColorRGB2 / Color::PBRLightingScale); - } -# endif - float landAlpha2 = landColor2.a; - float landSnowMask2 = GetLandSnowMaskValue(landColor2.w); - - // Sample normal texture for layer 2 -# if defined(TERRAIN_VARIATION) - float4 landNormal2; - [branch] if (useTerrainVariation) - { - landNormal2 = StochasticEffect(TexLandNormal2Sampler, SampLandNormal2Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal2 = TexLandNormal2Sampler.SampleBias(SampLandNormal2Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal2 = TexLandNormal2Sampler.SampleBias(SampLandNormal2Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB2 = landNormal2.rgb; - float landNormalAlpha2 = landNormal2.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.y * input.LandBlendWeights1.y * landSnowMask2; -# endif // SNOW - - // Sample RMAOS texture for layer 2 -# if defined(TRUE_PBR) - float4 landRMAOS2; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile1PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS2 = StochasticEffect(TexLandRMAOS2Sampler, SampLandRMAOS2Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture2PBRParams.x, 1, 1, LandscapeTexture2PBRParams.z); - } - else - { - landRMAOS2 = TexLandRMAOS2Sampler.SampleBias(SampLandRMAOS2Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture2PBRParams.x, 1, 1, LandscapeTexture2PBRParams.z); - } -# else - landRMAOS2 = TexLandRMAOS2Sampler.SampleBias(SampLandRMAOS2Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture2PBRParams.x, 1, 1, LandscapeTexture2PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile1HasGlint) != 0) { - glintParameters += weight * LandscapeTexture2GlintParameters; - } - } - else - { - landRMAOS2 = input.LandBlendWeights1.y * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS2 * weight; -# endif - blendedRGB += landColorRGB2 * weight; - blendedAlpha += landAlpha2 * weight; - blendedNormalRGB += landNormalRGB2 * weight; - blendedNormalAlpha += landNormalAlpha2 * weight; - } - - // Layer 3 (LandBlendWeights1.z) - if (input.LandBlendWeights1.z > 0.01) { - float weight = input.LandBlendWeights1.z; - // Sample diffuse texture for layer 3 -# if defined(TERRAIN_VARIATION) - float4 landColor3; - [branch] if (useTerrainVariation) - { - landColor3 = StochasticEffect(TexLandColor3Sampler, SampLandColor3Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor3 = TexLandColor3Sampler.SampleBias(SampLandColor3Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor3 = TexLandColor3Sampler.SampleBias(SampLandColor3Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB3 = landColor3.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile2PBR) == 0) - { - landColorRGB3 = Color::SrgbToLinear(landColorRGB3 / Color::PBRLightingScale); - } -# endif - float landAlpha3 = landColor3.a; - float landSnowMask3 = GetLandSnowMaskValue(landColor3.w); - - // Sample normal texture for layer 3 -# if defined(TERRAIN_VARIATION) - float4 landNormal3; - [branch] if (useTerrainVariation) - { - landNormal3 = StochasticEffect(TexLandNormal3Sampler, SampLandNormal3Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal3 = TexLandNormal3Sampler.SampleBias(SampLandNormal3Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal3 = TexLandNormal3Sampler.SampleBias(SampLandNormal3Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB3 = landNormal3.rgb; - float landNormalAlpha3 = landNormal3.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.z * input.LandBlendWeights1.z * landSnowMask3; -# endif // SNOW - - // Sample RMAOS texture for layer 3 -# if defined(TRUE_PBR) - float4 landRMAOS3; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile2PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS3 = StochasticEffect(TexLandRMAOS3Sampler, SampLandRMAOS3Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture3PBRParams.x, 1, 1, LandscapeTexture3PBRParams.z); - } - else - { - landRMAOS3 = TexLandRMAOS3Sampler.SampleBias(SampLandRMAOS3Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture3PBRParams.x, 1, 1, LandscapeTexture3PBRParams.z); - } -# else - landRMAOS3 = TexLandRMAOS3Sampler.SampleBias(SampLandRMAOS3Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture3PBRParams.x, 1, 1, LandscapeTexture3PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile2HasGlint) != 0) { - glintParameters += weight * LandscapeTexture3GlintParameters; - } - } - else - { - landRMAOS3 = input.LandBlendWeights1.z * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS3 * weight; -# endif - blendedRGB += landColorRGB3 * weight; - blendedAlpha += landAlpha3 * weight; - blendedNormalRGB += landNormalRGB3 * weight; - blendedNormalAlpha += landNormalAlpha3 * weight; - } - // Layer 4 (LandBlendWeights1.w) - if (input.LandBlendWeights1.w > 0.01) { - float weight = input.LandBlendWeights1.w; - - // Sample diffuse texture for layer 4 -# if defined(TERRAIN_VARIATION) - float4 landColor4; - [branch] if (useTerrainVariation) - { - landColor4 = StochasticEffect(TexLandColor4Sampler, SampLandColor4Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor4 = TexLandColor4Sampler.SampleBias(SampLandColor4Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor4 = TexLandColor4Sampler.SampleBias(SampLandColor4Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB4 = landColor4.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile3PBR) == 0) - { - landColorRGB4 = Color::SrgbToLinear(landColorRGB4 / Color::PBRLightingScale); - } -# endif - float landAlpha4 = landColor4.a; - float landSnowMask4 = GetLandSnowMaskValue(landColor4.w); - - // Sample normal texture for layer 4 -# if defined(TERRAIN_VARIATION) - float4 landNormal4; - [branch] if (useTerrainVariation) - { - landNormal4 = StochasticEffect(TexLandNormal4Sampler, SampLandNormal4Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal4 = TexLandNormal4Sampler.SampleBias(SampLandNormal4Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal4 = TexLandNormal4Sampler.SampleBias(SampLandNormal4Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB4 = landNormal4.rgb; - float landNormalAlpha4 = landNormal4.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.w * input.LandBlendWeights1.w * landSnowMask4; -# endif // SNOW - - // Sample RMAOS texture for layer 4 -# if defined(TRUE_PBR) - float4 landRMAOS4; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile3PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS4 = StochasticEffect(TexLandRMAOS4Sampler, SampLandRMAOS4Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture4PBRParams.x, 1, 1, LandscapeTexture4PBRParams.z); - } - else - { - landRMAOS4 = TexLandRMAOS4Sampler.SampleBias(SampLandRMAOS4Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture4PBRParams.x, 1, 1, LandscapeTexture4PBRParams.z); - } -# else - landRMAOS4 = TexLandRMAOS4Sampler.SampleBias(SampLandRMAOS4Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture4PBRParams.x, 1, 1, LandscapeTexture4PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile3HasGlint) != 0) { - glintParameters += weight * LandscapeTexture4GlintParameters; - } - } - else - { - landRMAOS4 = input.LandBlendWeights1.w * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS4 * weight; -# endif - blendedRGB += landColorRGB4 * weight; - blendedAlpha += landAlpha4 * weight; - blendedNormalRGB += landNormalRGB4 * weight; - blendedNormalAlpha += landNormalAlpha4 * weight; - } - - // Layer 5 (LandBlendWeights2.x) - if (input.LandBlendWeights2.x > 0.01) { - float weight = input.LandBlendWeights2.x; - // Sample diffuse texture for layer 5 -# if defined(TERRAIN_VARIATION) - float4 landColor5; - [branch] if (useTerrainVariation) - { - landColor5 = StochasticEffect(TexLandColor5Sampler, SampLandColor5Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor5 = TexLandColor5Sampler.SampleBias(SampLandColor5Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor5 = TexLandColor5Sampler.SampleBias(SampLandColor5Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB5 = landColor5.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile4PBR) == 0) - { - landColorRGB5 = Color::SrgbToLinear(landColorRGB5 / Color::PBRLightingScale); - } -# endif - float landAlpha5 = landColor5.a; - float landSnowMask5 = GetLandSnowMaskValue(landColor5.w); - - // Sample normal texture for layer 5 -# if defined(TERRAIN_VARIATION) - float4 landNormal5; - [branch] if (useTerrainVariation) - { - landNormal5 = StochasticEffect(TexLandNormal5Sampler, SampLandNormal5Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal5 = TexLandNormal5Sampler.SampleBias(SampLandNormal5Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal5 = TexLandNormal5Sampler.SampleBias(SampLandNormal5Sampler, uv, SharedData::MipBias); + float landDistanceTexMipBias = 0.0; +# if !defined(TERRAIN_VARIATION) +# define SampleTerrain(TEX, SAMP, UV, OFFSET, EXTRA_BIAS) TEX.SampleBias(SAMP, UV, SharedData::MipBias + EXTRA_BIAS) # endif - float3 landNormalRGB5 = landNormal5.rgb; - float landNormalAlpha5 = landNormal5.a; - -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture5to6IsSnow.x * input.LandBlendWeights2.x * landSnowMask5; -# endif // SNOW - - // Sample RMAOS texture for layer 5 # if defined(TRUE_PBR) - float4 landRMAOS5; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile4PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS5 = StochasticEffect(TexLandRMAOS5Sampler, SampLandRMAOS5Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture5PBRParams.x, 1, 1, LandscapeTexture5PBRParams.z); - } - else - { - landRMAOS5 = TexLandRMAOS5Sampler.SampleBias(SampLandRMAOS5Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture5PBRParams.x, 1, 1, LandscapeTexture5PBRParams.z); - } -# else - landRMAOS5 = TexLandRMAOS5Sampler.SampleBias(SampLandRMAOS5Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture5PBRParams.x, 1, 1, LandscapeTexture5PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile4HasGlint) != 0) { - glintParameters += weight * LandscapeTexture5GlintParameters; - } - } - else - { - landRMAOS5 = input.LandBlendWeights2.x * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS5 * weight; -# endif - blendedRGB += landColorRGB5 * weight; - blendedAlpha += landAlpha5 * weight; - blendedNormalRGB += landNormalRGB5 * weight; - blendedNormalAlpha += landNormalAlpha5 * weight; - } - // Layer 6 (LandBlendWeights2.y) - if (input.LandBlendWeights2.y > 0.01) { - float weight = input.LandBlendWeights2.y; - - // Sample layer 6 textures -# if defined(TERRAIN_VARIATION) - float4 landColor6; - [branch] if (useTerrainVariation) - { - landColor6 = StochasticEffect(TexLandColor6Sampler, SampLandColor6Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor6 = TexLandColor6Sampler.SampleBias(SampLandColor6Sampler, uv, SharedData::MipBias); - } + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(0, TexColorSampler, SampColorSampler, TexNormalSampler, SampNormalSampler, TexRMAOSSampler, SampRMAOSSampler, PBRParams1, LandscapeTexture1GlintParameters, input.LandBlendWeights1.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(1, TexLandColor2Sampler, SampLandColor2Sampler, TexLandNormal2Sampler, SampLandNormal2Sampler, TexLandRMAOS2Sampler, SampLandRMAOS2Sampler, LandscapeTexture2PBRParams, LandscapeTexture2GlintParameters, input.LandBlendWeights1.y) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(2, TexLandColor3Sampler, SampLandColor3Sampler, TexLandNormal3Sampler, SampLandNormal3Sampler, TexLandRMAOS3Sampler, SampLandRMAOS3Sampler, LandscapeTexture3PBRParams, LandscapeTexture3GlintParameters, input.LandBlendWeights1.z) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(3, TexLandColor4Sampler, SampLandColor4Sampler, TexLandNormal4Sampler, SampLandNormal4Sampler, TexLandRMAOS4Sampler, SampLandRMAOS4Sampler, LandscapeTexture4PBRParams, LandscapeTexture4GlintParameters, input.LandBlendWeights1.w) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(4, TexLandColor5Sampler, SampLandColor5Sampler, TexLandNormal5Sampler, SampLandNormal5Sampler, TexLandRMAOS5Sampler, SampLandRMAOS5Sampler, LandscapeTexture5PBRParams, LandscapeTexture5GlintParameters, input.LandBlendWeights2.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(5, TexLandColor6Sampler, SampLandColor6Sampler, TexLandNormal6Sampler, SampLandNormal6Sampler, TexLandRMAOS6Sampler, SampLandRMAOS6Sampler, LandscapeTexture6PBRParams, LandscapeTexture6GlintParameters, input.LandBlendWeights2.y) # else - float4 landColor6 = TexLandColor6Sampler.SampleBias(SampLandColor6Sampler, uv, SharedData::MipBias); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TexColorSampler, SampColorSampler, TexNormalSampler, SampNormalSampler, input.LandBlendWeights1.x, LandscapeTexture1to4IsSnow.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TexLandColor2Sampler, SampLandColor2Sampler, TexLandNormal2Sampler, SampLandNormal2Sampler, input.LandBlendWeights1.y, LandscapeTexture1to4IsSnow.y) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TexLandColor3Sampler, SampLandColor3Sampler, TexLandNormal3Sampler, SampLandNormal3Sampler, input.LandBlendWeights1.z, LandscapeTexture1to4IsSnow.z) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TexLandColor4Sampler, SampLandColor4Sampler, TexLandNormal4Sampler, SampLandNormal4Sampler, input.LandBlendWeights1.w, LandscapeTexture1to4IsSnow.w) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TexLandColor5Sampler, SampLandColor5Sampler, TexLandNormal5Sampler, SampLandNormal5Sampler, input.LandBlendWeights2.x, LandscapeTexture5to6IsSnow.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TexLandColor6Sampler, SampLandColor6Sampler, TexLandNormal6Sampler, SampLandNormal6Sampler, input.LandBlendWeights2.y, LandscapeTexture5to6IsSnow.y) # endif - float3 landColorRGB6 = landColor6.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile5PBR) == 0) - { - landColorRGB6 = Color::SrgbToLinear(landColorRGB6 / Color::PBRLightingScale); - } +# if !defined(TERRAIN_VARIATION) +# undef SampleTerrain # endif - float landAlpha6 = landColor6.a; - float landSnowMask6 = GetLandSnowMaskValue(landColor6.w); - - // Sample normal texture for layer 6 -# if defined(TERRAIN_VARIATION) - float4 landNormal6; - [branch] if (useTerrainVariation) - { - landNormal6 = StochasticEffect(TexLandNormal6Sampler, SampLandNormal6Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal6 = TexLandNormal6Sampler.SampleBias(SampLandNormal6Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal6 = TexLandNormal6Sampler.SampleBias(SampLandNormal6Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB6 = landNormal6.rgb; - float landNormalAlpha6 = landNormal6.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture5to6IsSnow.y * input.LandBlendWeights2.y * landSnowMask6; -# endif // SNOW - - // Sample RMAOS texture for layer 6 -# if defined(TRUE_PBR) - float4 landRMAOS6; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile5PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS6 = StochasticEffect(TexLandRMAOS6Sampler, SampLandRMAOS6Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture6PBRParams.x, 1, 1, LandscapeTexture6PBRParams.z); - } - else - { - landRMAOS6 = TexLandRMAOS6Sampler.SampleBias(SampLandRMAOS6Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture6PBRParams.x, 1, 1, LandscapeTexture6PBRParams.z); - } -# else - landRMAOS6 = TexLandRMAOS6Sampler.SampleBias(SampLandRMAOS6Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture6PBRParams.x, 1, 1, LandscapeTexture6PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile5HasGlint) != 0) { - glintParameters += weight * LandscapeTexture6GlintParameters; - } - } - else - { - landRMAOS6 = input.LandBlendWeights2.y * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS6 * weight; -# endif - blendedRGB += landColorRGB6 * weight; - blendedAlpha += landAlpha6 * weight; - blendedNormalRGB += landNormalRGB6 * weight; - blendedNormalAlpha += landNormalAlpha6 * weight; - } float4 rawBaseColor = float4(blendedRGB, blendedAlpha); baseColor = float4(Color::Diffuse(blendedRGB), blendedAlpha); @@ -1848,17 +1375,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(LODOBJECTS) || defined(LODOBJECTSHD) baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODObjectGamma) * SharedData::lodBlendingSettings.LODObjectBrightness; # elif defined(LODLANDSCAPE) -// First apply terrain variation if enabled +// Apply TV as a single-path runtime blend to avoid duplicated branches. # if defined(TERRAIN_VARIATION) - if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { - float2 dx = ddx(uv); - float2 dy = ddy(uv); - StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); - float4 lodStochasticColor = StochasticSampleLOD(screenNoise, TexColorSampler, SampColorSampler, uv, lodOffset, dx, dy); - - // Apply the stochastic result directly - baseColor.xyz = Color::Diffuse(lodStochasticColor.rgb); - } + StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); + float4 lodStochasticColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexColorSampler, SampColorSampler, uv, lodOffset); + float tvLodEnable = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; + baseColor.xyz = lerp(baseColor.xyz, Color::Diffuse(lodStochasticColor.rgb), tvLodEnable); # endif baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODTerrainGamma) * SharedData::lodBlendingSettings.LODTerrainBrightness; # endif @@ -1954,16 +1476,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float4 lodLandColor; # if defined(TERRAIN_VARIATION) - if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { - // Apply stochastic sampling to LOD_LAND_BLEND color texture - float2 blendColorUV = input.TexCoord0.zw; - float2 dx = ddx(blendColorUV); - float2 dy = ddy(blendColorUV); - StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); - lodLandColor = StochasticSampleLOD(screenNoise, TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset, dx, dy); - } else { - lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); - } + float2 blendColorUV = input.TexCoord0.zw; + StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); + lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); # else lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); # endif @@ -2519,17 +2034,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # else if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { # endif + float sh0; # if defined(TERRAIN_VARIATION) - float weights[6]; - // Initialize weights array - weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0; - - float sh0 = ExtendedMaterials::GetTerrainHeight(screenNoise, input, uv, mipLevels, displacementParams, parallaxShadowQuality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, dx, dy, weights); - - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, dirLightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, dx, dy); + dirDetailedShadow *= ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, dirLightDirectionTS, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, sh0); # else - // Standard terrain parallax shadow without stochastic sampling - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, dirLightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, dirLightDirectionTS, parallaxShadowQuality, screenNoise, displacementParams, sh0); # endif } # elif defined(PARALLAX) @@ -2735,7 +2244,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) # endif # if defined(TERRAIN_VARIATION) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, dx, dy); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset); # else parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); # endif diff --git a/src/Features/TerrainVariation.cpp b/src/Features/TerrainVariation.cpp index fa76cae5ee..04d6eb0a54 100644 --- a/src/Features/TerrainVariation.cpp +++ b/src/Features/TerrainVariation.cpp @@ -1,7 +1,4 @@ #include "TerrainVariation.h" -#include "../FeatureBuffer.h" -#include "../Globals.h" -#include "../State.h" #include "../Util.h" NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( @@ -11,12 +8,10 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( void TerrainVariation::DrawSettings() { - bool oldEnabled = settings.enableTilingFix; - ImGui::Checkbox("Enable Terrain Tiling Fix", (bool*)&settings.enableTilingFix); - if (oldEnabled != (bool)settings.enableTilingFix) { - // Update the shader settings when the checkbox is toggled - UpdateShaderSettings(); - logger::info("TerrainVariation setting changed to: {}", settings.enableTilingFix); + bool tilingFix = settings.enableTilingFix != 0; + if (ImGui::Checkbox("Enable Terrain Tiling Fix", &tilingFix)) { + settings.enableTilingFix = tilingFix ? 1u : 0u; + logger::info("TerrainVariation setting changed to: {}", settings.enableTilingFix != 0); } if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text( @@ -26,11 +21,10 @@ void TerrainVariation::DrawSettings() ImGui::Separator(); - bool oldLODEnabled = settings.enableLODTerrainTilingFix; - ImGui::Checkbox("Apply to LOD Terrain", (bool*)&settings.enableLODTerrainTilingFix); - if (oldLODEnabled != (bool)settings.enableLODTerrainTilingFix) { - UpdateShaderSettings(); - logger::info("TerrainVariation LOD setting changed to: {}", settings.enableLODTerrainTilingFix); + bool lodTilingFix = settings.enableLODTerrainTilingFix != 0; + if (ImGui::Checkbox("Apply to LOD Terrain", &lodTilingFix)) { + settings.enableLODTerrainTilingFix = lodTilingFix ? 1u : 0u; + logger::info("TerrainVariation LOD setting changed to: {}", settings.enableLODTerrainTilingFix != 0); } if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text( @@ -39,28 +33,14 @@ void TerrainVariation::DrawSettings() } } -void TerrainVariation::UpdateShaderSettings() -{ - if (!globals::state) { - return; - } - - // Mark the vertex descriptor as dirty to trigger an update - if (globals::game::stateUpdateFlags) { - globals::game::stateUpdateFlags->set(RE::BSGraphics::DIRTY_VERTEX_DESC); - } -} - void TerrainVariation::PostPostLoad() { logger::info("TerrainVariation: Feature initialized"); - UpdateShaderSettings(); } void TerrainVariation::LoadSettings(json& o_json) { settings = o_json; - UpdateShaderSettings(); } void TerrainVariation::SaveSettings(json& o_json) diff --git a/src/Features/TerrainVariation.h b/src/Features/TerrainVariation.h index 1434b8d4de..b7c852358a 100644 --- a/src/Features/TerrainVariation.h +++ b/src/Features/TerrainVariation.h @@ -12,7 +12,8 @@ struct TerrainVariation : Feature virtual inline std::string_view GetShaderDefineName() override { return "TERRAIN_VARIATION"; } virtual inline bool HasShaderDefine(RE::BSShader::Type shaderType) override { - return (shaderType == RE::BSShader::Type::Lighting); + // Always compile the TERRAIN_VARIATION path when the feature is loaded; runtime on/off uses FeatureData (b6). + return loaded && shaderType == RE::BSShader::Type::Lighting; } virtual bool IsCore() const override { return false; }; virtual bool SupportsVR() override { return true; } @@ -30,12 +31,16 @@ struct TerrainVariation : Feature }; } - struct Settings + struct alignas(16) Settings { - uint enableTilingFix = true; - uint enableLODTerrainTilingFix = true; - float pad0[2]; - } settings; + uint32_t enableTilingFix = 1; + uint32_t enableLODTerrainTilingFix = 1; + uint32_t pad[2]{}; + }; + + STATIC_ASSERT_ALIGNAS_16(Settings); + + Settings settings; virtual void DrawSettings() override; virtual bool DrawFailLoadMessage() const override; @@ -44,5 +49,4 @@ struct TerrainVariation : Feature virtual void RestoreDefaultSettings() override; virtual void PostPostLoad() override; - void UpdateShaderSettings(); }; \ No newline at end of file From 8ec7e2b4688e2d506e38fa99b8e0cf1e00b5fd6d Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 27 Apr 2026 21:26:05 +1000 Subject: [PATCH 02/29] Update Lighting.hlsl --- package/Shaders/Lighting.hlsl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index a2526838ff..c2f253b9cc 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1375,12 +1375,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(LODOBJECTS) || defined(LODOBJECTSHD) baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODObjectGamma) * SharedData::lodBlendingSettings.LODObjectBrightness; # elif defined(LODLANDSCAPE) -// Apply TV as a single-path runtime blend to avoid duplicated branches. # if defined(TERRAIN_VARIATION) - StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); - float4 lodStochasticColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexColorSampler, SampColorSampler, uv, lodOffset); - float tvLodEnable = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; - baseColor.xyz = lerp(baseColor.xyz, Color::Diffuse(lodStochasticColor.rgb), tvLodEnable); + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { + StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); + float4 lodStochasticColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexColorSampler, SampColorSampler, uv, lodOffset); + baseColor.xyz = Color::Diffuse(lodStochasticColor.rgb); + } # endif baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODTerrainGamma) * SharedData::lodBlendingSettings.LODTerrainBrightness; # endif @@ -1477,8 +1477,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(TERRAIN_VARIATION) float2 blendColorUV = input.TexCoord0.zw; - StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); - lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { + StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); + lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); + } else { + lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, blendColorUV); + } # else lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); # endif From 7f1c49b4d643ee6c4e5e3622ac2129acacd08b77 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 27 Apr 2026 21:37:53 +1000 Subject: [PATCH 03/29] perf --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 93 ++++++++++++------- package/Shaders/Lighting.hlsl | 4 +- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 4900cd0377..0d102f530f 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -80,11 +80,7 @@ namespace ExtendedMaterials } #if defined(LANDSCAPE) -# if defined(TERRAIN_VARIATION) - void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6], StochasticOffsets sharedOffset) -# else void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6]) -# endif { mipLevels[0] = GetMipLevel(coords, TexColorSampler, screenNoise); mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, screenNoise); @@ -365,17 +361,14 @@ namespace ExtendedMaterials float nearBlendToFar = smoothstep(1024.0 * 1024.0, 2048.0 * 2048.0, distSq); #if defined(LANDSCAPE) -# if defined(TRUE_PBR) float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? sqrt(saturate(1 - nearBlendToFar)) : 0; float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); +# if defined(TRUE_PBR) float scale = max(params[0].HeightScale * w1.x, max(params[1].HeightScale * w1.y, max(params[2].HeightScale * w1.z, max(params[3].HeightScale * w1.w, max(params[4].HeightScale * w2.x, params[5].HeightScale * w2.y))))); float scalercp = rcp(scale); float maxHeight = 0.1 * scale; # else - float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? sqrt(saturate(1 - nearBlendToFar)) : 0; - float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); - float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); float scale = 1; float maxHeight = 0.1 * scale; # endif @@ -410,9 +403,7 @@ namespace ExtendedMaterials float2 pt1 = 0; float2 pt2 = 0; - - uint numStepsTemp = numSteps; - bool contactRefinement = false; + bool intersectionFound = false; [loop] while (numSteps > 0) { @@ -461,6 +452,7 @@ namespace ExtendedMaterials [branch] if (any(testResult)) { float2 outOffset = 0; + intersectionFound = true; [flatten] if (testResult.w) { outOffset = currentOffset[1].xy; @@ -485,17 +477,8 @@ namespace ExtendedMaterials pt1 = float2(currentBound.x, currHeight.x); pt2 = float2(prevBound, prevHeight); } - if (contactRefinement) { - break; - } else { - contactRefinement = true; - prevOffset = outOffset; - prevBound = pt2.x; - numSteps = numStepsTemp; - stepSize /= (float)numSteps; - offsetPerStep /= (float)numSteps; - continue; - } + prevOffset = outOffset; + break; } prevOffset = currentOffset[1].zw; @@ -504,18 +487,64 @@ namespace ExtendedMaterials numSteps -= 4; } - float delta2 = pt2.x - pt2.y; - float delta1 = pt1.x - pt1.y; - float denominator = delta2 - delta1; - float parallaxAmount = 0.0; - [flatten] if (denominator == 0.0) - { - parallaxAmount = 0.0; - } - else + [branch] if (intersectionFound) { - parallaxAmount = (pt1.x * delta2 - pt2.x * delta1) / denominator; + // Refine coarse hit interval with secant iterations: + // f(t) = sampledHeight(t) - t, t in [0,1] where t is ray depth bound. + float tNear = pt1.x; + float hNear = pt1.y; + float fNear = hNear - tNear; + float tFar = pt2.x; + float hFar = pt2.y; + float fFar = hFar - tFar; + + const uint secantIterations = 3; + [unroll] for (uint i = 0; i < secantIterations; i++) + { + float denominator = fNear - fFar; + float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; + float tSecant = lerp(tNear, tFar, r); + float2 secantCoords = coords.xy + viewDirTS.xy * (((1.0 - tSecant) * -maxHeight) + minHeight); + + float hSecant; +#if defined(LANDSCAPE) +# if defined(TRUE_PBR) +# if defined(TERRAIN_VARIATION) + hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; +# else + hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; +# endif +# else +# if defined(TERRAIN_VARIATION) + hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; +# else + hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; +# endif +# endif +#else + hSecant = tex.SampleLevel(texSampler, secantCoords, mipLevel)[channel]; + hSecant = AdjustDisplacementNormalized(hSecant, params); +#endif + + float fSecant = hSecant - tSecant; + [branch] if (fSecant >= 0.0) + { + tNear = tSecant; + hNear = hSecant; + fNear = fSecant; + } + else + { + tFar = tSecant; + hFar = hSecant; + fFar = fSecant; + } + } + + float denominator = fNear - fFar; + float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; + parallaxAmount = lerp(tNear, tFar, r); } #if defined(TRUE_PBR) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index c2f253b9cc..842c9a53f0 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1239,7 +1239,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { # endif # if defined(TERRAIN_VARIATION) - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels, sharedOffset); + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); # else ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); # endif @@ -1294,7 +1294,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) else { // Calculate proper mip levels for terrain variation when parallax is disabled but EMAT is available # if defined(TERRAIN_VARIATION) - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels, sharedOffset); + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); # else ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); # endif From a7a1e13a2eb89a9909b102bc005ca10b780f4e75 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 27 Apr 2026 21:41:58 +1000 Subject: [PATCH 04/29] Update ExtendedMaterials.hlsli --- .../Shaders/ExtendedMaterials/ExtendedMaterials.hlsli | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 0d102f530f..1523319233 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -388,7 +388,7 @@ namespace ExtendedMaterials # endif { #endif - const float maxSteps = 16; + const float maxSteps = 12; uint numSteps = uint((maxSteps * (1.0 - nearBlendToFar)) + 0.5); numSteps = clamp(numSteps, 4, max(4, uint(scale * maxSteps))); numSteps = (numSteps + 2) & ~3; @@ -499,9 +499,13 @@ namespace ExtendedMaterials float hFar = pt2.y; float fFar = hFar - tFar; - const uint secantIterations = 3; - [unroll] for (uint i = 0; i < secantIterations; i++) + // Fewer secant refinements as we approach far-blended POM, where precision matters less. + uint secantIterations = nearBlendToFar > 0.6 ? 1 : (nearBlendToFar > 0.25 ? 2 : 3); + [unroll] for (uint i = 0; i < 3; i++) { + if (i >= secantIterations) + break; + float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; float tSecant = lerp(tNear, tFar, r); From 9b311b7e0e9cf85a6575fcf47854cc40dbc65af7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 11:46:22 +0000 Subject: [PATCH 05/29] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- .../TerrainVariation/TerrainVariation.hlsli | 3 - .../Shaders/Common/LightingLandscape.hlsli | 84 +++++++++---------- package/Shaders/Lighting.hlsl | 13 +-- src/Features/TerrainVariation.h | 2 +- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 998740e654..b87ae5a709 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -146,7 +146,6 @@ inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState sam return lerp(s2, s1, offsetsLOD.weights.x); } - // 2-sample height-blended stochastic sampling — branchless, no wavefront divergence. // Sorting in ComputeStochasticOffsets guarantees offset1/offset2 are the two // highest-weight barycentric vertices, so dropping offset3 loses minimal quality. @@ -176,7 +175,6 @@ inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, Stoc return lerp(s2, s1, w1 * rcp(w1 + w2)); } - // 2-sample parallax sampling — uses heightmap (alpha) only for blend weights. inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { @@ -200,7 +198,6 @@ inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 return lerp(s2, s1, w1 * rcp(w1 + w2)); } - inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) { return StochasticEffect(tex, samp, uv, offsets, extraLandMipBias); diff --git a/package/Shaders/Common/LightingLandscape.hlsli b/package/Shaders/Common/LightingLandscape.hlsli index 97d9d8701d..b59a87aee3 100644 --- a/package/Shaders/Common/LightingLandscape.hlsli +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -63,36 +63,36 @@ namespace LandscapeLayers // blendedNormalRGB, blendedNormalAlpha, glintParameters, Color::*, GetLandSnowMaskValue (non-PBR path). // --------------------------------------------------------------------------- # if defined(TRUE_PBR) -# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, RMAOS_TEX, RMAOS_SAMP, PBR_PARAMS3, GLINT_PARAMS, WEIGHT) \ - if (WEIGHT > 0.01) { \ - float weight = WEIGHT; \ - float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ - float3 landColorRGB = landColor.rgb; \ - [branch] if (!LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ - { \ - landColorRGB = Color::SrgbToLinear(landColorRGB / Color::PBRLightingScale); \ - } \ - float landAlpha = landColor.a; \ - float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ - float3 landNormalRGB = landNormal.rgb; \ - float landNormalAlpha = landNormal.a; \ - float4 landRMAOS; \ - [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ - { \ - landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, landDistanceTexMipBias) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ - if (LandscapeLayers::PbrTileHasGlint(TILE)) { \ - glintParameters += weight * (GLINT_PARAMS); \ - } \ - } \ - else \ - { \ - landRMAOS = weight * float4(1 - glossiness.x, 0, 1, 0); \ - } \ - blendedRMAOS += landRMAOS * weight; \ - blendedRGB += landColorRGB * weight; \ - blendedAlpha += landAlpha * weight; \ - blendedNormalRGB += landNormalRGB * weight; \ - blendedNormalAlpha += landNormalAlpha * weight; \ +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, RMAOS_TEX, RMAOS_SAMP, PBR_PARAMS3, GLINT_PARAMS, WEIGHT) \ + if (WEIGHT > 0.01) { \ + float weight = WEIGHT; \ + float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landColorRGB = landColor.rgb; \ + [branch] if (!LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landColorRGB = Color::SrgbToLinear(landColorRGB / Color::PBRLightingScale); \ + } \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + float4 landRMAOS; \ + [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, landDistanceTexMipBias) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ + if (LandscapeLayers::PbrTileHasGlint(TILE)) { \ + glintParameters += weight * (GLINT_PARAMS); \ + } \ + } \ + else \ + { \ + landRMAOS = weight * float4(1 - glossiness.x, 0, 1, 0); \ + } \ + blendedRMAOS += landRMAOS * weight; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ } # else # if defined(SNOW) @@ -102,18 +102,18 @@ namespace LandscapeLayers # define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) # endif # define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, WEIGHT, SNOW_COMPONENT) \ - if (WEIGHT > 0.01) { \ - float weight = WEIGHT; \ - float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ - float3 landColorRGB = landColor.rgb; \ - float landAlpha = landColor.a; \ - float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ - float3 landNormalRGB = landNormal.rgb; \ - float landNormalAlpha = landNormal.a; \ - blendedRGB += landColorRGB * weight; \ - blendedAlpha += landAlpha * weight; \ - blendedNormalRGB += landNormalRGB * weight; \ - blendedNormalAlpha += landNormalAlpha * weight; \ + if (WEIGHT > 0.01) { \ + float weight = WEIGHT; \ + float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landColorRGB = landColor.rgb; \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) \ } # endif diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 842c9a53f0..ad4ef5e4ef 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1290,8 +1290,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif sh0 *= shadowMultiplier; } - } - else { + } else { // Calculate proper mip levels for terrain variation when parallax is disabled but EMAT is available # if defined(TERRAIN_VARIATION) ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); @@ -1376,7 +1375,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODObjectGamma) * SharedData::lodBlendingSettings.LODObjectBrightness; # elif defined(LODLANDSCAPE) # if defined(TERRAIN_VARIATION) - [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + { StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); float4 lodStochasticColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexColorSampler, SampColorSampler, uv, lodOffset); baseColor.xyz = Color::Diffuse(lodStochasticColor.rgb); @@ -1477,10 +1477,13 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(TERRAIN_VARIATION) float2 blendColorUV = input.TexCoord0.zw; - [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + { StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); - } else { + } + else + { lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, blendColorUV); } # else diff --git a/src/Features/TerrainVariation.h b/src/Features/TerrainVariation.h index b7c852358a..d194590346 100644 --- a/src/Features/TerrainVariation.h +++ b/src/Features/TerrainVariation.h @@ -37,7 +37,7 @@ struct TerrainVariation : Feature uint32_t enableLODTerrainTilingFix = 1; uint32_t pad[2]{}; }; - + STATIC_ASSERT_ALIGNAS_16(Settings); Settings settings; From 5159df9d7cadf53c3405c86bedd73af0985495be Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 27 Apr 2026 22:37:03 +1000 Subject: [PATCH 06/29] more --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 140 +++++++++++++----- package/Shaders/Lighting.hlsl | 60 ++++++-- 2 files changed, 152 insertions(+), 48 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 1523319233..f056b87293 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -388,7 +388,7 @@ namespace ExtendedMaterials # endif { #endif - const float maxSteps = 12; + const float maxSteps = 4; uint numSteps = uint((maxSteps * (1.0 - nearBlendToFar)) + 0.5); numSteps = clamp(numSteps, 4, max(4, uint(scale * maxSteps))); numSteps = (numSteps + 2) & ~3; @@ -600,6 +600,59 @@ namespace ExtendedMaterials } #if defined(LANDSCAPE) +# if defined(TRUE_PBR) + static const uint TERRAIN_DISPLACEMENT_MASK = (1u << 6u) | (1u << 7u) | (1u << 8u) | (1u << 9u) | (1u << 10u) | (1u << 11u); +# endif +# if defined(TERRAIN_VARIATION) +# define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ + GetTerrainHeight(noise, input, COORDS, MIP, params, QUALITY, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, WEIGHTS) +# else +# define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ + GetTerrainHeight(noise, input, COORDS, MIP, params, QUALITY, input.LandBlendWeights1, input.LandBlendWeights2.xy, WEIGHTS) +# endif + + inline bool TerrainHasSignificantBlend(float4 w1, float2 w2) + { + return (w1.x + w1.y + w1.z + w1.w + w2.x + w2.y) > 0.01; + } + + inline bool TerrainHasAnyDisplacement() + { +# if defined(TRUE_PBR) + return (PBRFlags & TERRAIN_DISPLACEMENT_MASK) != 0; +# else + return (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; +# endif + } + + inline uint TerrainDirectionalShadowTapCount(float quality) + { + // Directional terrain shadows are capped to reduce cost: + // near: 2 taps, mid: 1 tap, far: 0 taps. + if (quality > 0.7) + return 2; + if (quality > 0.3) + return 1; + return 0; + } + +# if defined(TERRAIN_VARIATION) + bool ComputeTerrainParallaxShadowBaseHeight(PS_INPUT input, float2 coords, float mipLevels[6], float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) +# else + bool ComputeTerrainParallaxShadowBaseHeight(PS_INPUT input, float2 coords, float mipLevels[6], float quality, float noise, DisplacementParams params[6], out float sh0) +# endif + { + sh0 = 0.0; + if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) + return false; + if (!TerrainHasAnyDisplacement()) + return false; + + float weights[6] = { 0, 0, 0, 0, 0, 0 }; + sh0 = TERRAIN_HEIGHT_AT(coords, mipLevels, quality, weights); + return true; + } + # if defined(TERRAIN_VARIATION) float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset) # else @@ -618,64 +671,83 @@ namespace ExtendedMaterials if (scale < 0.01) return 1.0; rayDir *= scale; - -# if defined(TERRAIN_VARIATION) - sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); - if (quality > 0.25) - sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); - if (quality > 0.5) - sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); - if (quality > 0.75) - sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); -# else - sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); +# endif + sh = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); if (quality > 0.25) - sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); + sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); if (quality > 0.5) - sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); + sh.z = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.z, mipLevel, quality, heights); if (quality > 0.75) - sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); -# endif + sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); +# if defined(TRUE_PBR) return 1.0 - saturate(dot(max(0, sh - sh0) / scale, ShadowIntensity)) * quality; # else -# if defined(TERRAIN_VARIATION) - sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); + sh = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); if (quality > 0.25) - sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); + sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); if (quality > 0.5) - sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); + sh.z = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.z, mipLevel, quality, heights); if (quality > 0.75) - sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, heights); -# else - sh = GetTerrainHeight(noise, input, coords + rayDir * multipliers.x, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); - if (quality > 0.25) - sh.y = GetTerrainHeight(noise, input, coords + rayDir * multipliers.y, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); - if (quality > 0.5) - sh.z = GetTerrainHeight(noise, input, coords + rayDir * multipliers.z, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); - if (quality > 0.75) - sh.w = GetTerrainHeight(noise, input, coords + rayDir * multipliers.w, mipLevel, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, heights); -# endif + sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * quality; # endif } return 1.0; } +# if defined(TERRAIN_VARIATION) + float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, float sh0) +# else + float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], float sh0) +# endif + { + uint tapCount = TerrainDirectionalShadowTapCount(quality); + if (tapCount == 0) + return 1.0; + if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) + return 1.0; + + float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); + float4 sh = sh0.xxxx; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = lightDirection.xy * 0.1; + +# if defined(TRUE_PBR) + float scale = max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + if (scale < 0.01) + return 1.0; + rayDir *= scale; +# endif + + sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevels, quality, heights); + if (tapCount > 1) + sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevels, quality, heights); + + float qualityWeight = tapCount > 1 ? 1.0 : 0.6; +# if defined(TRUE_PBR) + return 1.0 - saturate(dot(max(0, sh - sh0) / scale, ShadowIntensity)) * qualityWeight; +# else + return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * qualityWeight; +# endif + } + # if defined(TERRAIN_VARIATION) float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) { - float weights[6] = { 0, 0, 0, 0, 0, 0 }; - sh0 = GetTerrainHeight(noise, input, coords, mipLevels, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, weights); + if (!ComputeTerrainParallaxShadowBaseHeight(input, coords, mipLevels, quality, noise, params, sharedOffset, sh0)) + return 1.0; return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params, sharedOffset); } # else float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], out float sh0) { - float weights[6] = { 0, 0, 0, 0, 0, 0 }; - sh0 = GetTerrainHeight(noise, input, coords, mipLevels, params, quality, input.LandBlendWeights1, input.LandBlendWeights2.xy, weights); + if (!ComputeTerrainParallaxShadowBaseHeight(input, coords, mipLevels, quality, noise, params, sh0)) + return 1.0; return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params); } # endif +# undef TERRAIN_HEIGHT_AT #endif // defined(LANDSCAPE) && defined(TERRAIN_VARIATION) } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 842c9a53f0..abd769f8d1 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1023,10 +1023,23 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) float parallaxShadowQuality = sqrt(1.0 - saturate(viewPosition.z / 2048.0)); + float terrainDirectionalShadowQuality = sqrt(1.0 - saturate(viewPosition.z / 1536.0)); +# if defined(TERRAIN_VARIATION) +# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, mipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) +# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, mipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) +# else +# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, mipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, OUT_SH0) +# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, mipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, BASE_SH0) +# endif # endif # if defined(LANDSCAPE) float mipLevels[6]; +# if defined(EMAT) + float cachedDirectionalTerrainParallaxShadow = 1.0; + bool hasCachedDirectionalTerrainParallaxShadow = false; + bool hasCachedTerrainShadowBaseHeight = false; +# endif # else float mipLevel = 0; # endif // LANDSCAPE @@ -1283,12 +1296,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) input.LandBlendWeights2.y = weights[5]; } if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) { -# if defined(TERRAIN_VARIATION) - float shadowMultiplier = ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, DirLightDirection, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, sh0); -# else - float shadowMultiplier = ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, DirLightDirection, parallaxShadowQuality, screenNoise, displacementParams, sh0); -# endif - sh0 *= shadowMultiplier; + float3 dirLightDirectionTS = mul(DirLightDirection, tbn).xyz; + hasCachedTerrainShadowBaseHeight = COMPUTE_TERRAIN_SHADOW_BASE(sh0); + if (hasCachedTerrainShadowBaseHeight) + cachedDirectionalTerrainParallaxShadow = EVAL_TERRAIN_DIR_SHADOW(sh0, dirLightDirectionTS); + hasCachedDirectionalTerrainParallaxShadow = hasCachedTerrainShadowBaseHeight; } } else { @@ -2038,12 +2050,14 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # else if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { # endif - float sh0; -# if defined(TERRAIN_VARIATION) - dirDetailedShadow *= ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, dirLightDirectionTS, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, sh0); -# else - dirDetailedShadow *= ExtendedMaterials::EvaluateTerrainParallaxShadowMultiplier(input, uv, mipLevels, dirLightDirectionTS, parallaxShadowQuality, screenNoise, displacementParams, sh0); -# endif + if (hasCachedDirectionalTerrainParallaxShadow) { + dirDetailedShadow *= cachedDirectionalTerrainParallaxShadow; + } else { + float sh0; + bool hasShadowBase = COMPUTE_TERRAIN_SHADOW_BASE(sh0); + if (hasShadowBase) + dirDetailedShadow *= EVAL_TERRAIN_DIR_SHADOW(sh0, dirLightDirectionTS); + } } # elif defined(PARALLAX) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) @@ -2247,11 +2261,24 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # else if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) # endif + { + // Aggressive, cheap distance-only gate for terrain point-light parallax shadows. + const float TERRAIN_POINT_SHADOW_NEAR = 256.0; + const float TERRAIN_POINT_SHADOW_FAR = 896.0; + float terrainPointShadowQuality = 1.0 - saturate((lightDist - TERRAIN_POINT_SHADOW_NEAR) / (TERRAIN_POINT_SHADOW_FAR - TERRAIN_POINT_SHADOW_NEAR)); + if (terrainPointShadowQuality > 0.0) { + // Stochastic update: evaluate a subset per frame (more samples near lights). + float updateProbability = lerp(0.2, 1.0, terrainPointShadowQuality); + float temporalJitter = frac(screenNoise + (float(lightIndex) * 0.6180339) + (float(SharedData::FrameCount & 7u) * 0.1732051)); + if (temporalJitter < updateProbability) { # if defined(TERRAIN_VARIATION) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams, sharedOffset); # else - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams); # endif + } + } + } # elif defined(EMAT_ENVMAP) [branch] if (complexMaterialParallax) parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, parallaxShadowQuality, screenNoise, displacementParams); @@ -2804,6 +2831,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } # endif +# if defined(EMAT) +# undef COMPUTE_TERRAIN_SHADOW_BASE +# undef EVAL_TERRAIN_DIR_SHADOW +# endif + return psout; } #endif // PSHADER From 3a802f72692cbb3d594822d3a9a03ecb1318abaf Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 27 Apr 2026 22:37:54 +1000 Subject: [PATCH 07/29] moree --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 17 ++++++++--- package/Shaders/Lighting.hlsl | 30 ++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index f056b87293..613f2a3a9b 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -625,6 +625,17 @@ namespace ExtendedMaterials # endif } + inline float TerrainMaxWeightedHeightScale(PS_INPUT input, DisplacementParams params[6]) + { +# if defined(TRUE_PBR) + return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); +# else + return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); +# endif + } + inline uint TerrainDirectionalShadowTapCount(float quality) { // Directional terrain shadows are capped to reduce cost: @@ -666,8 +677,7 @@ namespace ExtendedMaterials float2 rayDir = L.xy * 0.1; # if defined(TRUE_PBR) - float scale = max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, - max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + float scale = TerrainMaxWeightedHeightScale(input, params); if (scale < 0.01) return 1.0; rayDir *= scale; @@ -713,8 +723,7 @@ namespace ExtendedMaterials float2 rayDir = lightDirection.xy * 0.1; # if defined(TRUE_PBR) - float scale = max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, - max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + float scale = TerrainMaxWeightedHeightScale(input, params); if (scale < 0.01) return 1.0; rayDir *= scale; diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index abd769f8d1..534b95e02f 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -2046,9 +2046,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 dirLightDirectionTS = mul(refractedDirLightDirection, tbn).xyz; # if defined(LANDSCAPE) # if defined(TRUE_PBR) - if (SharedData::extendedMaterialSettings.EnableParallax) { + if (SharedData::extendedMaterialSettings.EnableParallax && dirLightAngle > 0.0) { # else - if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { + if ((SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) && dirLightAngle > 0.0) { # endif if (hasCachedDirectionalTerrainParallaxShadow) { dirDetailedShadow *= cachedDirectionalTerrainParallaxShadow; @@ -2263,19 +2263,27 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif { // Aggressive, cheap distance-only gate for terrain point-light parallax shadows. - const float TERRAIN_POINT_SHADOW_NEAR = 256.0; - const float TERRAIN_POINT_SHADOW_FAR = 896.0; + const float TERRAIN_POINT_SHADOW_NEAR = 192.0; + const float TERRAIN_POINT_SHADOW_FAR = 640.0; float terrainPointShadowQuality = 1.0 - saturate((lightDist - TERRAIN_POINT_SHADOW_NEAR) / (TERRAIN_POINT_SHADOW_FAR - TERRAIN_POINT_SHADOW_NEAR)); - if (terrainPointShadowQuality > 0.0) { - // Stochastic update: evaluate a subset per frame (more samples near lights). - float updateProbability = lerp(0.2, 1.0, terrainPointShadowQuality); - float temporalJitter = frac(screenNoise + (float(lightIndex) * 0.6180339) + (float(SharedData::FrameCount & 7u) * 0.1732051)); - if (temporalJitter < updateProbability) { + if (terrainPointShadowQuality > 0.0 && ExtendedMaterials::TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy) && ExtendedMaterials::TerrainHasAnyDisplacement()) { +# if defined(TRUE_PBR) + float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); +# else + float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); +# endif + if (terrainHeightScale > 0.01) { + // Stochastic update: evaluate a subset per frame (more samples near lights). + float updateProbability = lerp(0.1, 1.0, terrainPointShadowQuality); + float temporalJitter = frac(screenNoise + (float(lightIndex) * 0.6180339) + (float(SharedData::FrameCount & 7u) * 0.1732051)); + if (temporalJitter < updateProbability) { + float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); # if defined(TERRAIN_VARIATION) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams, sharedOffset); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams, sharedOffset); # else - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams); # endif + } } } } From a9d90cd7e60701651fe6b72919819ec7e7cfd651 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:39:30 +0000 Subject: [PATCH 08/29] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- .../Shaders/ExtendedMaterials/ExtendedMaterials.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 613f2a3a9b..fdc4315428 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -629,10 +629,10 @@ namespace ExtendedMaterials { # if defined(TRUE_PBR) return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, - max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); # else return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, - max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); # endif } From 7d576a679a86941898fb055d3d09567ec585f820 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Tue, 28 Apr 2026 11:10:57 +1000 Subject: [PATCH 09/29] chat changes --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 66 ++++++++++++++----- package/Shaders/Lighting.hlsl | 49 +++++--------- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index fdc4315428..4e7c1ea5b9 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -106,6 +106,25 @@ namespace ExtendedMaterials [unroll] for (int i = 0; i < 6; i++) { totalHeight += heights[i] * weights[i]; + } + + if (heightBlend <= 1.0) { + float wsum = 0; + [unroll] for (int j = 0; j < 6; j++) + { + wsum += weights[j]; + } + + float invwsum = rcp(wsum); + [unroll] for (int k = 0; k < 6; k++) + { + weights[k] *= invwsum; + } + return; + } + + [unroll] for (int i = 0; i < 6; i++) + { weights[i] *= pow(heightBlend, HEIGHT_MULT * heights[i]); } @@ -366,7 +385,7 @@ namespace ExtendedMaterials float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); # if defined(TRUE_PBR) float scale = max(params[0].HeightScale * w1.x, max(params[1].HeightScale * w1.y, max(params[2].HeightScale * w1.z, max(params[3].HeightScale * w1.w, max(params[4].HeightScale * w2.x, params[5].HeightScale * w2.y))))); - float scalercp = rcp(scale); + float scalercp = rcp(max(scale, 1e-4)); float maxHeight = 0.1 * scale; # else float scale = 1; @@ -379,7 +398,36 @@ namespace ExtendedMaterials float minHeight = maxHeight * 0.5; #if defined(LANDSCAPE) - if (nearBlendToFar < 1.0) { + if (nearBlendToFar < 1.0 && scale > 0.001) { + float terrainWeights0[6] = { 0, 0, 0, 0, 0, 0 }; +# if defined(TRUE_PBR) +# if defined(TERRAIN_VARIATION) +# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) * scalercp + 0.5) +# else +# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) * scalercp + 0.5) +# endif +# else +# if defined(TERRAIN_VARIATION) +# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) + 0.5) +# else +# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) + 0.5) +# endif +# endif + float height0 = saturate(LANDSCAPE_RELIEF_HEIGHT_AT(coords, terrainWeights0)); + float offset0 = (1.0 - height0) * -maxHeight + minHeight; + float2 refinedCoords = coords.xy + viewDirTS.xy * offset0; + float height1 = saturate(LANDSCAPE_RELIEF_HEIGHT_AT(refinedCoords, weights)); +# undef LANDSCAPE_RELIEF_HEIGHT_AT + + float height = lerp(height0, height1, 0.75); + nearBlendToFar *= nearBlendToFar; + float offset = (1.0 - height) * -maxHeight + minHeight; + pixelOffset = saturate(lerp(height, 0.5, nearBlendToFar)); +# if defined(VR_STEREO_OPT) + hasPOM = true; +# endif + return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar); + } #else # if defined(TRUE_PBR) if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0 || nearBlendToFar < 1.0) @@ -387,7 +435,6 @@ namespace ExtendedMaterials if (nearBlendToFar < 1.0) # endif { -#endif const float maxSteps = 4; uint numSteps = uint((maxSteps * (1.0 - nearBlendToFar)) + 0.5); numSteps = clamp(numSteps, 4, max(4, uint(scale * maxSteps))); @@ -564,6 +611,7 @@ namespace ExtendedMaterials #endif return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar); } +#endif #if defined(LANDSCAPE) weights[0] = input.LandBlendWeights1.x; @@ -627,13 +675,8 @@ namespace ExtendedMaterials inline float TerrainMaxWeightedHeightScale(PS_INPUT input, DisplacementParams params[6]) { -# if defined(TRUE_PBR) return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); -# else - return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, - max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); -# endif } inline uint TerrainDirectionalShadowTapCount(float quality) @@ -692,13 +735,6 @@ namespace ExtendedMaterials # if defined(TRUE_PBR) return 1.0 - saturate(dot(max(0, sh - sh0) / scale, ShadowIntensity)) * quality; # else - sh = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); - if (quality > 0.25) - sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); - if (quality > 0.5) - sh.z = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.z, mipLevel, quality, heights); - if (quality > 0.75) - sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * quality; # endif } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 68c75a61e2..20d82d0960 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1031,6 +1031,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, mipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, OUT_SH0) # define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, mipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, BASE_SH0) # endif +# if defined(LANDSCAPE) +# if defined(TRUE_PBR) +# define LANDSCAPE_PARALLAX_ENABLED (SharedData::extendedMaterialSettings.EnableParallax) +# else +# define LANDSCAPE_PARALLAX_ENABLED \ + (SharedData::extendedMaterialSettings.EnableTerrainParallax || \ + (SharedData::extendedMaterialSettings.EnableParallax && ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0))) +# endif +# endif # endif # if defined(LANDSCAPE) @@ -1073,7 +1082,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 vertexNormal = tbnTr[2]; # if defined(EMAT) - if (SharedData::extendedMaterialSettings.EnableParallaxWarpingFix) { + if (SharedData::extendedMaterialSettings.EnableParallaxWarpingFix +# if defined(LANDSCAPE) + && LANDSCAPE_PARALLAX_ENABLED +# endif + ) { float3 ndx = ddx(vertexNormal); float3 ndy = ddy(vertexNormal); float3 fdx = ddx(input.WorldPosition.xyz); @@ -1246,16 +1259,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # if defined(EMAT) -# if defined(TRUE_PBR) - if (SharedData::extendedMaterialSettings.EnableParallax) { -# else - if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { -# endif -# if defined(TERRAIN_VARIATION) + if (LANDSCAPE_PARALLAX_ENABLED) { ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); -# else - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); -# endif displacementParams[1] = displacementParams[0]; displacementParams[2] = displacementParams[0]; @@ -1302,13 +1307,6 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) cachedDirectionalTerrainParallaxShadow = EVAL_TERRAIN_DIR_SHADOW(sh0, dirLightDirectionTS); hasCachedDirectionalTerrainParallaxShadow = hasCachedTerrainShadowBaseHeight; } - } else { - // Calculate proper mip levels for terrain variation when parallax is disabled but EMAT is available -# if defined(TERRAIN_VARIATION) - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); -# else - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); -# endif } # else // Initialize mip levels for non-EMAT case @@ -2048,11 +2046,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) { float3 dirLightDirectionTS = mul(refractedDirLightDirection, tbn).xyz; # if defined(LANDSCAPE) -# if defined(TRUE_PBR) - if (SharedData::extendedMaterialSettings.EnableParallax && dirLightAngle > 0.0) { -# else - if ((SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) && dirLightAngle > 0.0) { -# endif + if (LANDSCAPE_PARALLAX_ENABLED && dirLightAngle > 0.0) { if (hasCachedDirectionalTerrainParallaxShadow) { dirDetailedShadow *= cachedDirectionalTerrainParallaxShadow; } else { @@ -2259,22 +2253,14 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(LANDSCAPE) -# if defined(TRUE_PBR) - if (SharedData::extendedMaterialSettings.EnableParallax) -# else - if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) -# endif + if (LANDSCAPE_PARALLAX_ENABLED) { // Aggressive, cheap distance-only gate for terrain point-light parallax shadows. const float TERRAIN_POINT_SHADOW_NEAR = 192.0; const float TERRAIN_POINT_SHADOW_FAR = 640.0; float terrainPointShadowQuality = 1.0 - saturate((lightDist - TERRAIN_POINT_SHADOW_NEAR) / (TERRAIN_POINT_SHADOW_FAR - TERRAIN_POINT_SHADOW_NEAR)); if (terrainPointShadowQuality > 0.0 && ExtendedMaterials::TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy) && ExtendedMaterials::TerrainHasAnyDisplacement()) { -# if defined(TRUE_PBR) float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); -# else - float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); -# endif if (terrainHeightScale > 0.01) { // Stochastic update: evaluate a subset per frame (more samples near lights). float updateProbability = lerp(0.1, 1.0, terrainPointShadowQuality); @@ -2845,6 +2831,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) # undef COMPUTE_TERRAIN_SHADOW_BASE # undef EVAL_TERRAIN_DIR_SHADOW +# undef LANDSCAPE_PARALLAX_ENABLED # endif return psout; From 6b5248ae0152a39f6a2d9dba06fdba4fa15035a9 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Tue, 28 Apr 2026 13:24:47 +1000 Subject: [PATCH 10/29] shadow fixes --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 111 ++++++++++-------- package/Shaders/Lighting.hlsl | 37 +++--- src/Features/ExtendedMaterials.cpp | 5 - 3 files changed, 77 insertions(+), 76 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 4e7c1ea5b9..ec5e766bf9 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -20,6 +20,21 @@ struct DisplacementParams namespace ExtendedMaterials { static const float ShadowIntensity = 2.0; + static const float ParallaxCheapDistance = 512.0; + static const float ParallaxNearShadowQuality = 1.0; + static const float ParallaxFarShadowQuality = 0.25; + + inline uint ParallaxShadowTapCount(float quality) + { + uint taps = 1; + if (quality > 0.25) + taps++; + if (quality > 0.5) + taps++; + if (quality > 0.75) + taps++; + return taps; + } float ScaleDisplacement(float displacement, DisplacementParams params) { @@ -376,11 +391,13 @@ namespace ExtendedMaterials viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles #endif - float distSq = dot(distance, distance); - float nearBlendToFar = smoothstep(1024.0 * 1024.0, 2048.0 * 2048.0, distSq); + bool useCheapParallax = distance >= ParallaxCheapDistance; +#if defined(LANDSCAPE) && defined(LANDSCAPE_HEIGHT_APPROX) + useCheapParallax = true; +#endif #if defined(LANDSCAPE) - float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? sqrt(saturate(1 - nearBlendToFar)) : 0; + float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0; float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); # if defined(TRUE_PBR) @@ -397,47 +414,52 @@ namespace ExtendedMaterials #endif float minHeight = maxHeight * 0.5; + if (useCheapParallax && scale > 0.001) { #if defined(LANDSCAPE) - if (nearBlendToFar < 1.0 && scale > 0.001) { float terrainWeights0[6] = { 0, 0, 0, 0, 0, 0 }; # if defined(TRUE_PBR) # if defined(TERRAIN_VARIATION) -# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) * scalercp + 0.5) +# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) * scalercp + 0.5) # else -# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) * scalercp + 0.5) +# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) * scalercp + 0.5) # endif # else # if defined(TERRAIN_VARIATION) -# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) + 0.5) +# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) + 0.5) # else -# define LANDSCAPE_RELIEF_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) + 0.5) +# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) + 0.5) # endif # endif - float height0 = saturate(LANDSCAPE_RELIEF_HEIGHT_AT(coords, terrainWeights0)); + float height0 = saturate(LANDSCAPE_HEIGHT_AT(coords, terrainWeights0)); + float offset0 = (1.0 - height0) * -maxHeight + minHeight; + float2 refinedCoords = coords.xy + viewDirTS.xy * offset0; + float height1 = saturate(LANDSCAPE_HEIGHT_AT(refinedCoords, weights)); +# undef LANDSCAPE_HEIGHT_AT +#else + float height0 = saturate(AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords, mipLevel)[channel], params)); float offset0 = (1.0 - height0) * -maxHeight + minHeight; float2 refinedCoords = coords.xy + viewDirTS.xy * offset0; - float height1 = saturate(LANDSCAPE_RELIEF_HEIGHT_AT(refinedCoords, weights)); -# undef LANDSCAPE_RELIEF_HEIGHT_AT + float height1 = saturate(AdjustDisplacementNormalized(tex.SampleLevel(texSampler, refinedCoords, mipLevel)[channel], params)); +#endif - float height = lerp(height0, height1, 0.75); - nearBlendToFar *= nearBlendToFar; + float heightDelta = abs(height1 - height0); + float heightStability = saturate(1.0 - heightDelta * 2.0); + float height = lerp(lerp(height0, height1, 0.5), 0.5, (1.0 - heightStability) * 0.35); float offset = (1.0 - height) * -maxHeight + minHeight; - pixelOffset = saturate(lerp(height, 0.5, nearBlendToFar)); + pixelOffset = saturate(height); # if defined(VR_STEREO_OPT) hasPOM = true; # endif - return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar); + return viewDirTS.xy * offset + coords.xy; } -#else # if defined(TRUE_PBR) - if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0 || nearBlendToFar < 1.0) + if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0 || !useCheapParallax) # else - if (nearBlendToFar < 1.0) + if (!useCheapParallax) # endif { const float maxSteps = 4; - uint numSteps = uint((maxSteps * (1.0 - nearBlendToFar)) + 0.5); - numSteps = clamp(numSteps, 4, max(4, uint(scale * maxSteps))); + uint numSteps = max(4, uint(scale * maxSteps)); numSteps = (numSteps + 2) & ~3; float stepSize = rcp(numSteps); @@ -546,13 +568,8 @@ namespace ExtendedMaterials float hFar = pt2.y; float fFar = hFar - tFar; - // Fewer secant refinements as we approach far-blended POM, where precision matters less. - uint secantIterations = nearBlendToFar > 0.6 ? 1 : (nearBlendToFar > 0.25 ? 2 : 3); [unroll] for (uint i = 0; i < 3; i++) { - if (i >= secantIterations) - break; - float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; float tSecant = lerp(tNear, tFar, r); @@ -598,20 +615,13 @@ namespace ExtendedMaterials parallaxAmount = lerp(tNear, tFar, r); } -#if defined(TRUE_PBR) - if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0) - nearBlendToFar = 0; - else -#endif - nearBlendToFar *= nearBlendToFar; float offset = (1.0 - parallaxAmount) * -maxHeight + minHeight; - pixelOffset = saturate(lerp(parallaxAmount, 0.5, nearBlendToFar)); + pixelOffset = saturate(parallaxAmount); #if defined(VR_STEREO_OPT) hasPOM = true; #endif - return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar); + return viewDirTS.xy * offset + coords.xy; } -#endif #if defined(LANDSCAPE) weights[0] = input.LandBlendWeights1.x; @@ -632,17 +642,19 @@ namespace ExtendedMaterials { [branch] if (quality > 0.0) { + uint tapCount = ParallaxShadowTapCount(quality); + float shadowStrength = ShadowIntensity * (4.0 / tapCount); float2 rayDir = L.xy * 0.1 * params.HeightScale; float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh; - sh = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.x, mipLevel)[channel], params); + float4 sh = sh0.xxxx; + sh.x = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.x, mipLevel)[channel], params); if (quality > 0.25) sh.y = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.y, mipLevel)[channel], params); if (quality > 0.5) sh.z = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.z, mipLevel)[channel], params); if (quality > 0.75) sh.w = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.w, mipLevel)[channel], params); - return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * quality; + return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); } return 1.0; } @@ -653,10 +665,10 @@ namespace ExtendedMaterials # endif # if defined(TERRAIN_VARIATION) # define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ - GetTerrainHeight(noise, input, COORDS, MIP, params, QUALITY, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, WEIGHTS) + GetTerrainHeight(noise, input, COORDS, MIP, params, SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, WEIGHTS) # else # define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ - GetTerrainHeight(noise, input, COORDS, MIP, params, QUALITY, input.LandBlendWeights1, input.LandBlendWeights2.xy, WEIGHTS) + GetTerrainHeight(noise, input, COORDS, MIP, params, SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, WEIGHTS) # endif inline bool TerrainHasSignificantBlend(float4 w1, float2 w2) @@ -681,11 +693,10 @@ namespace ExtendedMaterials inline uint TerrainDirectionalShadowTapCount(float quality) { - // Directional terrain shadows are capped to reduce cost: - // near: 2 taps, mid: 1 tap, far: 0 taps. + // Directional terrain shadows are capped to reduce cost. if (quality > 0.7) return 2; - if (quality > 0.3) + if (quality > 0.0) return 1; return 0; } @@ -714,8 +725,10 @@ namespace ExtendedMaterials # endif { if (quality > 0.0) { + uint tapCount = ParallaxShadowTapCount(quality); + float shadowStrength = ShadowIntensity * (4.0 / tapCount); float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh; + float4 sh = sh0.xxxx; float heights[6] = { 0, 0, 0, 0, 0, 0 }; float2 rayDir = L.xy * 0.1; @@ -725,7 +738,7 @@ namespace ExtendedMaterials return 1.0; rayDir *= scale; # endif - sh = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); + sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); if (quality > 0.25) sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); if (quality > 0.5) @@ -733,9 +746,9 @@ namespace ExtendedMaterials if (quality > 0.75) sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); # if defined(TRUE_PBR) - return 1.0 - saturate(dot(max(0, sh - sh0) / scale, ShadowIntensity)) * quality; + return 1.0 - saturate(dot(max(0, sh - sh0) / scale, shadowStrength)); # else - return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * quality; + return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); # endif } return 1.0; @@ -750,6 +763,7 @@ namespace ExtendedMaterials uint tapCount = TerrainDirectionalShadowTapCount(quality); if (tapCount == 0) return 1.0; + float shadowStrength = ShadowIntensity * (2.0 / tapCount); if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) return 1.0; @@ -769,11 +783,10 @@ namespace ExtendedMaterials if (tapCount > 1) sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevels, quality, heights); - float qualityWeight = tapCount > 1 ? 1.0 : 0.6; # if defined(TRUE_PBR) - return 1.0 - saturate(dot(max(0, sh - sh0) / scale, ShadowIntensity)) * qualityWeight; + return 1.0 - saturate(dot(max(0, sh - sh0) / scale, shadowStrength)); # else - return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * qualityWeight; + return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); # endif } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 20d82d0960..6d7ac49091 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1022,8 +1022,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float2 uvOriginal = uv; # if defined(EMAT) - float parallaxShadowQuality = sqrt(1.0 - saturate(viewPosition.z / 2048.0)); - float terrainDirectionalShadowQuality = sqrt(1.0 - saturate(viewPosition.z / 1536.0)); + float parallaxShadowQuality = viewPosition.z < ExtendedMaterials::ParallaxCheapDistance + ? ExtendedMaterials::ParallaxNearShadowQuality + : ExtendedMaterials::ParallaxFarShadowQuality; + float terrainDirectionalShadowQuality = parallaxShadowQuality; # if defined(TERRAIN_VARIATION) # define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, mipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) # define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, mipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) @@ -1123,7 +1125,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) hasPOM # endif ); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + if (SharedData::extendedMaterialSettings.EnableShadows && parallaxShadowQuality > 0.0) sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x; } # endif // PARALLAX @@ -1156,7 +1158,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) hasPOM # endif ); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + if (SharedData::extendedMaterialSettings.EnableShadows && parallaxShadowQuality > 0.0) sh0 = TexEnvMaskSampler.SampleLevel(SampEnvMaskSampler, uv, mipLevel).w; complexMaterialColor = TexEnvMaskSampler.Sample(SampEnvMaskSampler, uv); } else { @@ -1208,7 +1210,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) hasPOM # endif ); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + if (SharedData::extendedMaterialSettings.EnableShadows && parallaxShadowQuality > 0.0) sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x; } # endif // !FACEGEN @@ -1300,7 +1302,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) input.LandBlendWeights2.x = weights[4]; input.LandBlendWeights2.y = weights[5]; } - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) { + if (SharedData::extendedMaterialSettings.EnableShadows && terrainDirectionalShadowQuality > 0.0) { float3 dirLightDirectionTS = mul(DirLightDirection, tbn).xyz; hasCachedTerrainShadowBaseHeight = COMPUTE_TERRAIN_SHADOW_BASE(sh0); if (hasCachedTerrainShadowBaseHeight) @@ -2058,13 +2060,13 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } # elif defined(PARALLAX) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, lerp(parallaxShadowQuality, 1.0, SharedData::extendedMaterialSettings.ExtendShadows), screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(EMAT_ENVMAP) [branch] if (complexMaterialParallax) - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, lerp(parallaxShadowQuality, 1.0, SharedData::extendedMaterialSettings.ExtendShadows), screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(TRUE_PBR) && !defined(LODLANDSCAPE) && !defined(FACEGEN) [branch] if (PBRParallax) - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, lerp(parallaxShadowQuality, 1.0, SharedData::extendedMaterialSettings.ExtendShadows), screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # endif // LANDSCAPE } # endif // defined(EMAT) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) @@ -2255,24 +2257,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # elif defined(LANDSCAPE) if (LANDSCAPE_PARALLAX_ENABLED) { - // Aggressive, cheap distance-only gate for terrain point-light parallax shadows. - const float TERRAIN_POINT_SHADOW_NEAR = 192.0; - const float TERRAIN_POINT_SHADOW_FAR = 640.0; - float terrainPointShadowQuality = 1.0 - saturate((lightDist - TERRAIN_POINT_SHADOW_NEAR) / (TERRAIN_POINT_SHADOW_FAR - TERRAIN_POINT_SHADOW_NEAR)); - if (terrainPointShadowQuality > 0.0 && ExtendedMaterials::TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy) && ExtendedMaterials::TerrainHasAnyDisplacement()) { + if (ExtendedMaterials::TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy) && ExtendedMaterials::TerrainHasAnyDisplacement()) { float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); if (terrainHeightScale > 0.01) { - // Stochastic update: evaluate a subset per frame (more samples near lights). - float updateProbability = lerp(0.1, 1.0, terrainPointShadowQuality); - float temporalJitter = frac(screenNoise + (float(lightIndex) * 0.6180339) + (float(SharedData::FrameCount & 7u) * 0.1732051)); - if (temporalJitter < updateProbability) { - float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); + float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); # if defined(TERRAIN_VARIATION) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams, sharedOffset); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset); # else - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, terrainPointShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); # endif - } } } } diff --git a/src/Features/ExtendedMaterials.cpp b/src/Features/ExtendedMaterials.cpp index 5bab1f88f2..5ca7dd790d 100644 --- a/src/Features/ExtendedMaterials.cpp +++ b/src/Features/ExtendedMaterials.cpp @@ -75,11 +75,6 @@ void ExtendedMaterials::DrawSettings() "Enables cheap soft shadows when using parallax. " "This applies to all directional and point lights. "); } - ImGui::Checkbox("Extend Shadows", (bool*)&settings.ExtendShadows); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Extends parallax shadows beyond the range of parallax. Small performance impact."); - } ImGui::Spacing(); ImGui::Spacing(); From bc884022f03e0b00bebb734d491861bd7f39e704 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Tue, 28 Apr 2026 15:22:01 +1000 Subject: [PATCH 11/29] more changes --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 8 ++++++-- .../Shaders/Common/LightingLandscape.hlsli | 6 +++--- package/Shaders/Common/SharedData.hlsli | 3 +-- package/Shaders/Lighting.hlsl | 20 +++++++++++++------ src/Features/ExtendedMaterials.cpp | 1 - src/Features/ExtendedMaterials.h | 3 +-- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index ec5e766bf9..49bcd554e7 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -22,7 +22,8 @@ namespace ExtendedMaterials static const float ShadowIntensity = 2.0; static const float ParallaxCheapDistance = 512.0; static const float ParallaxNearShadowQuality = 1.0; - static const float ParallaxFarShadowQuality = 0.25; + static const float ParallaxFarShadowQuality = 0.5; + static const float TerrainParallaxShadowMaxMipLevel = 1.0; inline uint ParallaxShadowTapCount(float quality) { @@ -681,7 +682,10 @@ namespace ExtendedMaterials # if defined(TRUE_PBR) return (PBRFlags & TERRAIN_DISPLACEMENT_MASK) != 0; # else - return (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; + // Some distant landscape permutations can lose THLandHasDisplacement even though + // legacy terrain parallax still uses alpha-based displacement. + return SharedData::extendedMaterialSettings.EnableTerrainParallax || + (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; # endif } diff --git a/package/Shaders/Common/LightingLandscape.hlsli b/package/Shaders/Common/LightingLandscape.hlsli index b59a87aee3..4220345310 100644 --- a/package/Shaders/Common/LightingLandscape.hlsli +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -64,7 +64,7 @@ namespace LandscapeLayers // --------------------------------------------------------------------------- # if defined(TRUE_PBR) # define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, RMAOS_TEX, RMAOS_SAMP, PBR_PARAMS3, GLINT_PARAMS, WEIGHT) \ - if (WEIGHT > 0.01) { \ + [branch] if ((WEIGHT) > 0.01) { \ float weight = WEIGHT; \ float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ float3 landColorRGB = landColor.rgb; \ @@ -80,7 +80,7 @@ namespace LandscapeLayers [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ { \ landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, landDistanceTexMipBias) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ - if (LandscapeLayers::PbrTileHasGlint(TILE)) { \ + [branch] if (LandscapeLayers::PbrTileHasGlint(TILE)) { \ glintParameters += weight * (GLINT_PARAMS); \ } \ } \ @@ -102,7 +102,7 @@ namespace LandscapeLayers # define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) # endif # define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, WEIGHT, SNOW_COMPONENT) \ - if (WEIGHT > 0.01) { \ + [branch] if ((WEIGHT) > 0.01) { \ float weight = WEIGHT; \ float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ float3 landColorRGB = landColor.rgb; \ diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 16dbfeda65..5537b39f15 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -51,9 +51,8 @@ namespace SharedData bool EnableTerrainParallax; bool EnableHeightBlending; bool EnableShadows; - bool ExtendShadows; bool EnableParallaxWarpingFix; - bool pad0; + uint2 pad0; }; struct CubemapCreatorSettings diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 6d7ac49091..18351984b7 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1026,12 +1026,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) ? ExtendedMaterials::ParallaxNearShadowQuality : ExtendedMaterials::ParallaxFarShadowQuality; float terrainDirectionalShadowQuality = parallaxShadowQuality; +# if defined(LANDSCAPE) + terrainDirectionalShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; +# endif # if defined(TERRAIN_VARIATION) -# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, mipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) -# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, mipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) +# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, terrainShadowMipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) +# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, terrainShadowMipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) # else -# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, mipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, OUT_SH0) -# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, mipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, BASE_SH0) +# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, terrainShadowMipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, OUT_SH0) +# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, terrainShadowMipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, BASE_SH0) # endif # if defined(LANDSCAPE) # if defined(TRUE_PBR) @@ -1047,6 +1050,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(LANDSCAPE) float mipLevels[6]; # if defined(EMAT) + float terrainShadowMipLevels[6]; float cachedDirectionalTerrainParallaxShadow = 1.0; bool hasCachedDirectionalTerrainParallaxShadow = false; bool hasCachedTerrainShadowBaseHeight = false; @@ -1263,6 +1267,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) if (LANDSCAPE_PARALLAX_ENABLED) { ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); + [unroll] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) + { + terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); + } displacementParams[1] = displacementParams[0]; displacementParams[2] = displacementParams[0]; @@ -2262,9 +2270,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (terrainHeightScale > 0.01) { float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); # if defined(TERRAIN_VARIATION) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, terrainShadowMipLevels, lightDirectionTS, sh0, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset); # else - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, terrainShadowMipLevels, lightDirectionTS, sh0, terrainDirectionalShadowQuality, screenNoise, displacementParams); # endif } } diff --git a/src/Features/ExtendedMaterials.cpp b/src/Features/ExtendedMaterials.cpp index 5ca7dd790d..d419f4eedd 100644 --- a/src/Features/ExtendedMaterials.cpp +++ b/src/Features/ExtendedMaterials.cpp @@ -7,7 +7,6 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( EnableTerrain, EnableHeightBlending, EnableShadows, - ExtendShadows, EnableParallaxWarpingFix) void ExtendedMaterials::DataLoaded() diff --git a/src/Features/ExtendedMaterials.h b/src/Features/ExtendedMaterials.h index 10519a9a4f..be6ec5c294 100644 --- a/src/Features/ExtendedMaterials.h +++ b/src/Features/ExtendedMaterials.h @@ -33,10 +33,9 @@ struct ExtendedMaterials : Feature uint EnableHeightBlending = 1; uint EnableShadows = 1; - uint ExtendShadows = 1; uint EnableParallaxWarpingFix = 1; - float pad[1]; + uint pad[2]; }; STATIC_ASSERT_ALIGNAS_16(Settings); From 2a8cb8f4a056c76f59f9f591daee812a10ed4675 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:22:40 +0000 Subject: [PATCH 12/29] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- .../Shaders/ExtendedMaterials/ExtendedMaterials.hlsli | 10 +++++----- package/Shaders/Common/LightingLandscape.hlsli | 9 ++++++--- package/Shaders/Lighting.hlsl | 9 +++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 49bcd554e7..a370ab2aab 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -448,16 +448,16 @@ namespace ExtendedMaterials float height = lerp(lerp(height0, height1, 0.5), 0.5, (1.0 - heightStability) * 0.35); float offset = (1.0 - height) * -maxHeight + minHeight; pixelOffset = saturate(height); -# if defined(VR_STEREO_OPT) +#if defined(VR_STEREO_OPT) hasPOM = true; -# endif +#endif return viewDirTS.xy * offset + coords.xy; } -# if defined(TRUE_PBR) +#if defined(TRUE_PBR) if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0 || !useCheapParallax) -# else +#else if (!useCheapParallax) -# endif +#endif { const float maxSteps = 4; uint numSteps = max(4, uint(scale * maxSteps)); diff --git a/package/Shaders/Common/LightingLandscape.hlsli b/package/Shaders/Common/LightingLandscape.hlsli index 4220345310..383219fcb2 100644 --- a/package/Shaders/Common/LightingLandscape.hlsli +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -64,7 +64,8 @@ namespace LandscapeLayers // --------------------------------------------------------------------------- # if defined(TRUE_PBR) # define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, RMAOS_TEX, RMAOS_SAMP, PBR_PARAMS3, GLINT_PARAMS, WEIGHT) \ - [branch] if ((WEIGHT) > 0.01) { \ + [branch] if ((WEIGHT) > 0.01) \ + { \ float weight = WEIGHT; \ float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ float3 landColorRGB = landColor.rgb; \ @@ -80,7 +81,8 @@ namespace LandscapeLayers [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ { \ landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, landDistanceTexMipBias) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ - [branch] if (LandscapeLayers::PbrTileHasGlint(TILE)) { \ + [branch] if (LandscapeLayers::PbrTileHasGlint(TILE)) \ + { \ glintParameters += weight * (GLINT_PARAMS); \ } \ } \ @@ -102,7 +104,8 @@ namespace LandscapeLayers # define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) # endif # define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, WEIGHT, SNOW_COMPONENT) \ - [branch] if ((WEIGHT) > 0.01) { \ + [branch] if ((WEIGHT) > 0.01) \ + { \ float weight = WEIGHT; \ float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ float3 landColorRGB = landColor.rgb; \ diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 18351984b7..e114e737ff 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1022,9 +1022,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float2 uvOriginal = uv; # if defined(EMAT) - float parallaxShadowQuality = viewPosition.z < ExtendedMaterials::ParallaxCheapDistance - ? ExtendedMaterials::ParallaxNearShadowQuality - : ExtendedMaterials::ParallaxFarShadowQuality; + float parallaxShadowQuality = viewPosition.z < ExtendedMaterials::ParallaxCheapDistance ? ExtendedMaterials::ParallaxNearShadowQuality : ExtendedMaterials::ParallaxFarShadowQuality; float terrainDirectionalShadowQuality = parallaxShadowQuality; # if defined(LANDSCAPE) terrainDirectionalShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; @@ -1040,7 +1038,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(TRUE_PBR) # define LANDSCAPE_PARALLAX_ENABLED (SharedData::extendedMaterialSettings.EnableParallax) # else -# define LANDSCAPE_PARALLAX_ENABLED \ +# define LANDSCAPE_PARALLAX_ENABLED \ (SharedData::extendedMaterialSettings.EnableTerrainParallax || \ (SharedData::extendedMaterialSettings.EnableParallax && ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0))) # endif @@ -2263,8 +2261,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(LANDSCAPE) - if (LANDSCAPE_PARALLAX_ENABLED) - { + if (LANDSCAPE_PARALLAX_ENABLED) { if (ExtendedMaterials::TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy) && ExtendedMaterials::TerrainHasAnyDisplacement()) { float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); if (terrainHeightScale > 0.01) { From 5fd29b5f71d59bffc6e3b47154171e428ebf9993 Mon Sep 17 00:00:00 2001 From: doodlum <15017472+doodlum@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:11:43 +0100 Subject: [PATCH 13/29] perf: consistent world texel density --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 57 ++++++------------- package/Shaders/Lighting.hlsl | 8 +-- 2 files changed, 21 insertions(+), 44 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index a370ab2aab..5850449f4e 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -24,6 +24,7 @@ namespace ExtendedMaterials static const float ParallaxNearShadowQuality = 1.0; static const float ParallaxFarShadowQuality = 0.5; static const float TerrainParallaxShadowMaxMipLevel = 1.0; + static const float TargetTexelDensity = 4.0; inline uint ParallaxShadowTapCount(float quality) { @@ -52,58 +53,34 @@ namespace ExtendedMaterials return float4(AdjustDisplacementNormalized(displacement.x, params), AdjustDisplacementNormalized(displacement.y, params), AdjustDisplacementNormalized(displacement.z, params), AdjustDisplacementNormalized(displacement.w, params)); } - float GetMipLevel(float2 coords, Texture2D tex, float screenNoise) + float GetMipLevel(float2 coords, Texture2D tex, float3 worldPos) { - // Compute the current gradients: float2 textureDims; tex.GetDimensions(textureDims.x, textureDims.y); -#if !defined(PARALLAX) && !defined(TRUE_PBR) - textureDims /= 2.0; -#endif - -#if defined(VR) - textureDims /= 2.0; -#endif - - float2 texCoordsPerSize = coords * textureDims; - - float2 dxSize = ddx(texCoordsPerSize); - float2 dySize = ddy(texCoordsPerSize); - - // Find min of change in u and v across quad: compute du and dv magnitude across quad - //float2 dTexCoords = dxSize * dxSize + dySize * dySize; + float2 dxUV = ddx(coords); + float2 dyUV = ddy(coords); + float3 dxWorld = ddx(worldPos); + float3 dyWorld = ddy(worldPos); - // Standard mipmapping uses max here - float minTexCoordDelta = min(dot(dxSize, dxSize), dot(dySize, dySize)); - - // Compute the current mip level (* 0.5 is effectively computing a square root before ) - float mipLevel = max(0.5 * log2(minTexCoordDelta), 0); - -#if !defined(PARALLAX) && !defined(TRUE_PBR) - mipLevel++; -#endif - -// VR: Apply more conservative mipmap level adjustments to reduce over-blurring and shimmering -#if defined(VR) - mipLevel++; -#endif + float ratioX = dot(dxUV, dxUV) / max(dot(dxWorld, dxWorld), EPSILON_DIVISION); + float ratioY = dot(dyUV, dyUV) / max(dot(dyWorld, dyWorld), EPSILON_DIVISION); - // Stochastic mip selection: use screen noise to select between adjacent mip levels - mipLevel = floor(mipLevel) + (screenNoise < frac(mipLevel) ? 1.0 : 0.0); + float mipLevel = 0.5 * log2(min(ratioX, ratioY)) + log2(max(textureDims.x, textureDims.y) / EPSILON_DIVISION); + mipLevel = max(mipLevel, 0); return mipLevel; } #if defined(LANDSCAPE) - void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6]) + void InitializeTerrainMipLevels(float2 coords, float3 worldPos, out float mipLevels[6]) { - mipLevels[0] = GetMipLevel(coords, TexColorSampler, screenNoise); - mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, screenNoise); - mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler, screenNoise); - mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler, screenNoise); - mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler, screenNoise); - mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler, screenNoise); + mipLevels[0] = GetMipLevel(coords, TexColorSampler, worldPos); + mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, worldPos); + mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler, worldPos); + mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler, worldPos); + mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler, worldPos); + mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler, worldPos); } # define HEIGHT_POWER 2 diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index e114e737ff..f8d1e57f90 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1120,7 +1120,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) # if defined(PARALLAX) if (SharedData::extendedMaterialSettings.EnableParallax) { - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, input.WorldPosition.xyz); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , @@ -1153,7 +1153,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (complexMaterial) { if (envMaskSample.w > kMaskEpsilon) { complexMaterialParallax = true; - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, screenNoise); + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, input.WorldPosition.xyz); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , @@ -1205,7 +1205,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) { displacementParams.HeightScale *= PBRParams1.y; } - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, input.WorldPosition.xyz); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , @@ -1264,7 +1264,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) if (LANDSCAPE_PARALLAX_ENABLED) { - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); + ExtendedMaterials::InitializeTerrainMipLevels(uv, input.WorldPosition.xyz, mipLevels); [unroll] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) { terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); From a19d072ced65efa463a4d74e3190cc758864b8d9 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 4 May 2026 14:14:42 +1000 Subject: [PATCH 14/29] fixes a bajillion --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 63 ++++++++++--------- .../TerrainVariation/TerrainVariation.hlsli | 21 +++---- package/Shaders/Common/SharedData.hlsli | 5 +- package/Shaders/Lighting.hlsl | 19 ++---- src/Features/TerrainVariation.cpp | 20 +++--- src/Features/TerrainVariation.h | 5 +- 6 files changed, 57 insertions(+), 76 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 5850449f4e..e20f8158ff 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -24,7 +24,6 @@ namespace ExtendedMaterials static const float ParallaxNearShadowQuality = 1.0; static const float ParallaxFarShadowQuality = 0.5; static const float TerrainParallaxShadowMaxMipLevel = 1.0; - static const float TargetTexelDensity = 4.0; inline uint ParallaxShadowTapCount(float quality) { @@ -53,34 +52,50 @@ namespace ExtendedMaterials return float4(AdjustDisplacementNormalized(displacement.x, params), AdjustDisplacementNormalized(displacement.y, params), AdjustDisplacementNormalized(displacement.z, params), AdjustDisplacementNormalized(displacement.w, params)); } - float GetMipLevel(float2 coords, Texture2D tex, float3 worldPos) + float GetMipLevel(float2 coords, Texture2D tex, float screenNoise) { float2 textureDims; tex.GetDimensions(textureDims.x, textureDims.y); - float2 dxUV = ddx(coords); - float2 dyUV = ddy(coords); - float3 dxWorld = ddx(worldPos); - float3 dyWorld = ddy(worldPos); +#if !defined(PARALLAX) && !defined(TRUE_PBR) + textureDims /= 2.0; +#endif + +#if defined(VR) + textureDims /= 2.0; +#endif + + float2 texCoordsPerSize = coords * textureDims; + + float2 dxSize = ddx(texCoordsPerSize); + float2 dySize = ddy(texCoordsPerSize); + + float minTexCoordDelta = min(dot(dxSize, dxSize), dot(dySize, dySize)); + + float mipLevel = max(0.5 * log2(minTexCoordDelta), 0); + +#if !defined(PARALLAX) && !defined(TRUE_PBR) + mipLevel++; +#endif - float ratioX = dot(dxUV, dxUV) / max(dot(dxWorld, dxWorld), EPSILON_DIVISION); - float ratioY = dot(dyUV, dyUV) / max(dot(dyWorld, dyWorld), EPSILON_DIVISION); +#if defined(VR) + mipLevel++; +#endif - float mipLevel = 0.5 * log2(min(ratioX, ratioY)) + log2(max(textureDims.x, textureDims.y) / EPSILON_DIVISION); - mipLevel = max(mipLevel, 0); + mipLevel = floor(mipLevel) + (screenNoise < frac(mipLevel) ? 1.0 : 0.0); return mipLevel; } #if defined(LANDSCAPE) - void InitializeTerrainMipLevels(float2 coords, float3 worldPos, out float mipLevels[6]) + void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6]) { - mipLevels[0] = GetMipLevel(coords, TexColorSampler, worldPos); - mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, worldPos); - mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler, worldPos); - mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler, worldPos); - mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler, worldPos); - mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler, worldPos); + mipLevels[0] = GetMipLevel(coords, TexColorSampler, screenNoise); + mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, screenNoise); + mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler, screenNoise); + mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler, screenNoise); + mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler, screenNoise); + mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler, screenNoise); } # define HEIGHT_POWER 2 @@ -200,13 +215,6 @@ namespace ExtendedMaterials float total; ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); -# if defined(TERRAIN_VARIATION) - // Boost height by 30% when terrain variation is enabled to enhance depth perception - [branch] if (SharedData::terrainVariationSettings.enableTilingFix) - { - total *= 1.3; - } -# endif return total; } @@ -325,13 +333,6 @@ namespace ExtendedMaterials float total; ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); -# if defined(TERRAIN_VARIATION) - // Boost height by 30% when terrain variation is enabled to enhance depth perception - [branch] if (SharedData::terrainVariationSettings.enableTilingFix) - { - total *= 1.3; - } -# endif return total; } diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index b87ae5a709..ada0a82b53 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -56,9 +56,6 @@ inline float2 hashLOD(float2 p) // --------------------- COMPUTE FUNCTIONS --------------------- // inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) { - if (!SharedData::terrainVariationSettings.enableTilingFix) - return (StochasticOffsets)0; - float2 skewUV = mul(SKEW_MATRIX, landscapeUV * WORLD_SCALE); float2 vxID = floor(skewUV); float2 f = frac(skewUV); @@ -138,12 +135,13 @@ inline float StochasticHeightFadeFromMip(float mipLevel) // LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) { - if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) - return tex.SampleBias(samp, uv, SharedData::MipBias); - - float4 s1 = tex.SampleBias(samp, uv + (offsetsLOD.offset1 + jitter) * 0.01, SharedData::MipBias); - float4 s2 = tex.SampleBias(samp, uv + (offsetsLOD.offset2 + float2(jitter.y, -jitter.x)) * 0.01, SharedData::MipBias); - return lerp(s2, s1, offsetsLOD.weights.x); + float lodOn = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; + float2 j1 = (offsetsLOD.offset1 + jitter) * 0.01; + float2 j2 = (offsetsLOD.offset2 + float2(jitter.y, -jitter.x)) * 0.01; + float4 s1 = tex.SampleBias(samp, uv + j1 * lodOn, SharedData::MipBias); + float4 s2 = tex.SampleBias(samp, uv + j2 * lodOn, SharedData::MipBias); + float blendW = lerp(0.5, offsetsLOD.weights.x, lodOn); + return lerp(s2, s1, blendW); } // 2-sample height-blended stochastic sampling — branchless, no wavefront divergence. @@ -151,9 +149,6 @@ inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState sam // highest-weight barycentric vertices, so dropping offset3 loses minimal quality. inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) { - if (!SharedData::terrainVariationSettings.enableTilingFix) - return tex.SampleBias(samp, uv, SharedData::MipBias + extraLandMipBias); - float mipLevel = tex.CalculateLevelOfDetail(samp, uv) + SharedData::MipBias + extraLandMipBias; // Far/minified: skip TV blending math + second sample. if (mipLevel >= TV_SINGLE_SAMPLE_MIP_START) @@ -178,8 +173,6 @@ inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, Stoc // 2-sample parallax sampling — uses heightmap (alpha) only for blend weights. inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { - if (!SharedData::terrainVariationSettings.enableTilingFix) - return tex.SampleLevel(samp, uv, mipLevel); // Keep parallax height active, but cut TV to one sample once heavily minified. if (mipLevel >= TV_SINGLE_SAMPLE_PARALLAX_MIP_START) return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 5537b39f15..2083fd9860 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -180,9 +180,8 @@ namespace SharedData struct TerrainVariationSettings { - uint enableTilingFix; - uint enableLODTerrainTilingFix; - float2 pad0; + uint enableLODTerrainTilingFix; // 1 = apply variation to LOD terrain, 0 = near terrain only (variation always applies near terrain when TERRAIN_VARIATION is defined). + uint3 pad; // Unused; mirrors TerrainVariation::Settings padding in the native plugin. }; struct IBLSettings diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 81ddbb5601..93e1ac1426 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1120,7 +1120,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) # if defined(PARALLAX) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) if (SharedData::extendedMaterialSettings.EnableParallax) { - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, input.WorldPosition.xyz); + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , @@ -1153,7 +1153,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (complexMaterial) { if (envMaskSample.w > kMaskEpsilon) { complexMaterialParallax = true; - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, input.WorldPosition.xyz); + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, screenNoise); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , @@ -1205,7 +1205,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) { displacementParams.HeightScale *= PBRParams1.y; } - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, input.WorldPosition.xyz); + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , @@ -1264,7 +1264,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) if (LANDSCAPE_PARALLAX_ENABLED) { - ExtendedMaterials::InitializeTerrainMipLevels(uv, input.WorldPosition.xyz, mipLevels); + ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); [unroll] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) { terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); @@ -1495,15 +1495,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(TERRAIN_VARIATION) float2 blendColorUV = input.TexCoord0.zw; - [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) - { - StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); - lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); - } - else - { - lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, blendColorUV); - } + StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); + lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); # else lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); # endif diff --git a/src/Features/TerrainVariation.cpp b/src/Features/TerrainVariation.cpp index 04d6eb0a54..9c4fdce891 100644 --- a/src/Features/TerrainVariation.cpp +++ b/src/Features/TerrainVariation.cpp @@ -1,25 +1,21 @@ #include "TerrainVariation.h" -#include "../Util.h" +#include "Menu.h" +#include "Menu/Fonts.h" +#include "Util.h" NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( TerrainVariation::Settings, - enableTilingFix, enableLODTerrainTilingFix) void TerrainVariation::DrawSettings() { - bool tilingFix = settings.enableTilingFix != 0; - if (ImGui::Checkbox("Enable Terrain Tiling Fix", &tilingFix)) { - settings.enableTilingFix = tilingFix ? 1u : 0u; - logger::info("TerrainVariation setting changed to: {}", settings.enableTilingFix != 0); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Reduces the repeating pattern effect on terrain textures.\n" - "This technique creates more natural-looking terrain by adding variation to texture sampling."); + { + MenuFonts::FontRoleGuard bodyGuard(Menu::FontRole::Body); + ImGui::TextWrapped( + "Terrain variation is always enabled when installed. Use disable at boot to turn off."); } - ImGui::Separator(); + ImGui::Spacing(); bool lodTilingFix = settings.enableLODTerrainTilingFix != 0; if (ImGui::Checkbox("Apply to LOD Terrain", &lodTilingFix)) { diff --git a/src/Features/TerrainVariation.h b/src/Features/TerrainVariation.h index d194590346..2be3c8f1a5 100644 --- a/src/Features/TerrainVariation.h +++ b/src/Features/TerrainVariation.h @@ -12,7 +12,7 @@ struct TerrainVariation : Feature virtual inline std::string_view GetShaderDefineName() override { return "TERRAIN_VARIATION"; } virtual inline bool HasShaderDefine(RE::BSShader::Type shaderType) override { - // Always compile the TERRAIN_VARIATION path when the feature is loaded; runtime on/off uses FeatureData (b6). + // Always compile the TERRAIN_VARIATION path when the feature is loaded; LOD terrain variation remains configurable via enableLODTerrainTilingFix. return loaded && shaderType == RE::BSShader::Type::Lighting; } virtual bool IsCore() const override { return false; }; @@ -33,9 +33,8 @@ struct TerrainVariation : Feature struct alignas(16) Settings { - uint32_t enableTilingFix = 1; uint32_t enableLODTerrainTilingFix = 1; - uint32_t pad[2]{}; + uint32_t pad[3]{}; }; STATIC_ASSERT_ALIGNAS_16(Settings); From 0e17ca6b34c0a7e4e6dde60a220614b42a408019 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 4 May 2026 14:24:10 +1000 Subject: [PATCH 15/29] remove distance shenanigans --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 84 ++++--------------- package/Shaders/Lighting.hlsl | 2 +- 2 files changed, 15 insertions(+), 71 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index e20f8158ff..8d7d504e22 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -20,10 +20,13 @@ struct DisplacementParams namespace ExtendedMaterials { static const float ShadowIntensity = 2.0; - static const float ParallaxCheapDistance = 512.0; static const float ParallaxNearShadowQuality = 1.0; - static const float ParallaxFarShadowQuality = 0.5; - static const float TerrainParallaxShadowMaxMipLevel = 1.0; + static const float TerrainParallaxShadowMaxMipLevel = 8.0; + + static const uint ParallaxRayStepsScale = 32; + static const uint ParallaxRayStepsMin = 16; + static const uint ParallaxRayStepsMax = 96; + static const uint ParallaxSecantIterations = 8; inline uint ParallaxShadowTapCount(float quality) { @@ -370,11 +373,6 @@ namespace ExtendedMaterials viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles #endif - bool useCheapParallax = distance >= ParallaxCheapDistance; -#if defined(LANDSCAPE) && defined(LANDSCAPE_HEIGHT_APPROX) - useCheapParallax = true; -#endif - #if defined(LANDSCAPE) float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0; float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); @@ -393,53 +391,10 @@ namespace ExtendedMaterials #endif float minHeight = maxHeight * 0.5; - if (useCheapParallax && scale > 0.001) { -#if defined(LANDSCAPE) - float terrainWeights0[6] = { 0, 0, 0, 0, 0, 0 }; -# if defined(TRUE_PBR) -# if defined(TERRAIN_VARIATION) -# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) * scalercp + 0.5) -# else -# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) * scalercp + 0.5) -# endif -# else -# if defined(TERRAIN_VARIATION) -# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, sharedOffset, OUT_WEIGHTS) + 0.5) -# else -# define LANDSCAPE_HEIGHT_AT(COORDS, OUT_WEIGHTS) (GetTerrainHeight(noise, input, COORDS, mipLevels, params, blendFactor, w1, w2, OUT_WEIGHTS) + 0.5) -# endif -# endif - float height0 = saturate(LANDSCAPE_HEIGHT_AT(coords, terrainWeights0)); - float offset0 = (1.0 - height0) * -maxHeight + minHeight; - float2 refinedCoords = coords.xy + viewDirTS.xy * offset0; - float height1 = saturate(LANDSCAPE_HEIGHT_AT(refinedCoords, weights)); -# undef LANDSCAPE_HEIGHT_AT -#else - float height0 = saturate(AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords, mipLevel)[channel], params)); - float offset0 = (1.0 - height0) * -maxHeight + minHeight; - float2 refinedCoords = coords.xy + viewDirTS.xy * offset0; - float height1 = saturate(AdjustDisplacementNormalized(tex.SampleLevel(texSampler, refinedCoords, mipLevel)[channel], params)); -#endif - - float heightDelta = abs(height1 - height0); - float heightStability = saturate(1.0 - heightDelta * 2.0); - float height = lerp(lerp(height0, height1, 0.5), 0.5, (1.0 - heightStability) * 0.35); - float offset = (1.0 - height) * -maxHeight + minHeight; - pixelOffset = saturate(height); -#if defined(VR_STEREO_OPT) - hasPOM = true; -#endif - return viewDirTS.xy * offset + coords.xy; - } -#if defined(TRUE_PBR) - if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0 || !useCheapParallax) -#else - if (!useCheapParallax) -#endif { - const float maxSteps = 4; - uint numSteps = max(4, uint(scale * maxSteps)); - numSteps = (numSteps + 2) & ~3; + uint numSteps = min(ParallaxRayStepsMax, + max(ParallaxRayStepsMin, uint(scale * float(ParallaxRayStepsScale)))); + numSteps = (numSteps + 2u) & ~3u; float stepSize = rcp(numSteps); @@ -547,7 +502,7 @@ namespace ExtendedMaterials float hFar = pt2.y; float fFar = hFar - tFar; - [unroll] for (uint i = 0; i < 3; i++) + [unroll] for (uint i = 0; i < ParallaxSecantIterations; i++) { float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; @@ -601,18 +556,6 @@ namespace ExtendedMaterials #endif return viewDirTS.xy * offset + coords.xy; } - -#if defined(LANDSCAPE) - weights[0] = input.LandBlendWeights1.x; - weights[1] = input.LandBlendWeights1.y; - weights[2] = input.LandBlendWeights1.z; - weights[3] = input.LandBlendWeights1.w; - weights[4] = input.LandBlendWeights2.x; - weights[5] = input.LandBlendWeights2.y; -#endif - - pixelOffset = 0.0; - return coords; } // https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf @@ -675,11 +618,12 @@ namespace ExtendedMaterials inline uint TerrainDirectionalShadowTapCount(float quality) { - // Directional terrain shadows are capped to reduce cost. if (quality > 0.7) - return 2; + return 4; + if (quality > 0.25) + return 3; if (quality > 0.0) - return 1; + return 2; return 0; } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 93e1ac1426..4564fc07e9 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1022,7 +1022,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float2 uvOriginal = uv; # if defined(EMAT) - float parallaxShadowQuality = viewPosition.z < ExtendedMaterials::ParallaxCheapDistance ? ExtendedMaterials::ParallaxNearShadowQuality : ExtendedMaterials::ParallaxFarShadowQuality; + float parallaxShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; float terrainDirectionalShadowQuality = parallaxShadowQuality; # if defined(LANDSCAPE) terrainDirectionalShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; From b7164bd965c51ff96b09a0093537a61fa971e2c1 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 4 May 2026 15:13:21 +1000 Subject: [PATCH 16/29] fix --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 68 +++++++++++-------- package/Shaders/Lighting.hlsl | 22 +++--- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 8d7d504e22..2c34b6a958 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -20,13 +20,10 @@ struct DisplacementParams namespace ExtendedMaterials { static const float ShadowIntensity = 2.0; + static const float ParallaxCheapDistance = 512.0; static const float ParallaxNearShadowQuality = 1.0; - static const float TerrainParallaxShadowMaxMipLevel = 8.0; - - static const uint ParallaxRayStepsScale = 32; - static const uint ParallaxRayStepsMin = 16; - static const uint ParallaxRayStepsMax = 96; - static const uint ParallaxSecantIterations = 8; + static const float ParallaxFarShadowQuality = 0.5; + static const float TerrainParallaxShadowMaxMipLevel = 1.0; inline uint ParallaxShadowTapCount(float quality) { @@ -55,7 +52,7 @@ namespace ExtendedMaterials return float4(AdjustDisplacementNormalized(displacement.x, params), AdjustDisplacementNormalized(displacement.y, params), AdjustDisplacementNormalized(displacement.z, params), AdjustDisplacementNormalized(displacement.w, params)); } - float GetMipLevel(float2 coords, Texture2D tex, float screenNoise) + float GetMipLevel(float2 coords, Texture2D tex) { float2 textureDims; tex.GetDimensions(textureDims.x, textureDims.y); @@ -85,20 +82,18 @@ namespace ExtendedMaterials mipLevel++; #endif - mipLevel = floor(mipLevel) + (screenNoise < frac(mipLevel) ? 1.0 : 0.0); - - return mipLevel; + return floor(mipLevel); } #if defined(LANDSCAPE) - void InitializeTerrainMipLevels(float2 coords, float screenNoise, out float mipLevels[6]) + void InitializeTerrainMipLevels(float2 coords, out float mipLevels[6]) { - mipLevels[0] = GetMipLevel(coords, TexColorSampler, screenNoise); - mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler, screenNoise); - mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler, screenNoise); - mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler, screenNoise); - mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler, screenNoise); - mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler, screenNoise); + mipLevels[0] = GetMipLevel(coords, TexColorSampler); + mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler); + mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler); + mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler); + mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler); + mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler); } # define HEIGHT_POWER 2 @@ -344,7 +339,7 @@ namespace ExtendedMaterials #endif #if defined(LANDSCAPE) - float2 GetParallaxCoords(PS_INPUT input, float distance, float2 coords, float mipLevels[6], float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], + float2 GetParallaxCoords(PS_INPUT input, float2 coords, float mipLevels[6], float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], # if defined(TERRAIN_VARIATION) StochasticOffsets sharedOffset, # endif @@ -354,7 +349,7 @@ namespace ExtendedMaterials # endif out float weights[6]) #else - float2 GetParallaxCoords(float distance, float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset + float2 GetParallaxCoords(float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset # if defined(VR_STEREO_OPT) , out bool hasPOM @@ -391,10 +386,30 @@ namespace ExtendedMaterials #endif float minHeight = maxHeight * 0.5; +#if defined(LANDSCAPE) +# if defined(TRUE_PBR) + if (scale <= 0.001) { + weights[0] = input.LandBlendWeights1.x; + weights[1] = input.LandBlendWeights1.y; + weights[2] = input.LandBlendWeights1.z; + weights[3] = input.LandBlendWeights1.w; + weights[4] = input.LandBlendWeights2.x; + weights[5] = input.LandBlendWeights2.y; + pixelOffset = 0.0; + return coords; + } +# endif +#else + if (scale <= 0.001) { + pixelOffset = 0.0; + return coords; + } +#endif + { - uint numSteps = min(ParallaxRayStepsMax, - max(ParallaxRayStepsMin, uint(scale * float(ParallaxRayStepsScale)))); - numSteps = (numSteps + 2u) & ~3u; + const float maxSteps = 4; + uint numSteps = max(4, uint(scale * maxSteps)); + numSteps = (numSteps + 2) & ~3; float stepSize = rcp(numSteps); @@ -502,7 +517,7 @@ namespace ExtendedMaterials float hFar = pt2.y; float fFar = hFar - tFar; - [unroll] for (uint i = 0; i < ParallaxSecantIterations; i++) + [unroll] for (uint i = 0; i < 3; i++) { float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; @@ -618,12 +633,11 @@ namespace ExtendedMaterials inline uint TerrainDirectionalShadowTapCount(float quality) { + // Directional terrain shadows are capped to reduce cost. if (quality > 0.7) - return 4; - if (quality > 0.25) - return 3; - if (quality > 0.0) return 2; + if (quality > 0.0) + return 1; return 0; } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 4564fc07e9..f5af535776 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1022,7 +1022,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float2 uvOriginal = uv; # if defined(EMAT) - float parallaxShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; + float parallaxShadowQuality = viewPosition.z < ExtendedMaterials::ParallaxCheapDistance ? ExtendedMaterials::ParallaxNearShadowQuality : ExtendedMaterials::ParallaxFarShadowQuality; float terrainDirectionalShadowQuality = parallaxShadowQuality; # if defined(LANDSCAPE) terrainDirectionalShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; @@ -1120,8 +1120,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) # if defined(PARALLAX) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) if (SharedData::extendedMaterialSettings.EnableParallax) { - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); - uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler); + uv = ExtendedMaterials::GetParallaxCoords(uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , hasPOM @@ -1153,8 +1153,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (complexMaterial) { if (envMaskSample.w > kMaskEpsilon) { complexMaterialParallax = true; - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, screenNoise); - uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler); + uv = ExtendedMaterials::GetParallaxCoords(uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , hasPOM @@ -1205,8 +1205,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) { displacementParams.HeightScale *= PBRParams1.y; } - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); - uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler); + uv = ExtendedMaterials::GetParallaxCoords(uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset # if defined(VR_STEREO_OPT) && !defined(SNOW) , hasPOM @@ -1264,7 +1264,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) if (LANDSCAPE_PARALLAX_ENABLED) { - ExtendedMaterials::InitializeTerrainMipLevels(uv, screenNoise, mipLevels); + ExtendedMaterials::InitializeTerrainMipLevels(uv, mipLevels); [unroll] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) { terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); @@ -1288,13 +1288,13 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) // Initialize weights array weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0; # if defined(TERRAIN_VARIATION) - uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, + uv = ExtendedMaterials::GetParallaxCoords(input, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, # if defined(VR_STEREO_OPT) && !defined(SNOW) hasPOM, # endif weights); # else - uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset, + uv = ExtendedMaterials::GetParallaxCoords(input, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset, # if defined(VR_STEREO_OPT) && !defined(SNOW) hasPOM, # endif @@ -2809,8 +2809,6 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) psout.Masks = float4(0, 0, masksZ, psout.Diffuse.w); # endif - float stochasticBlend = (screenNoise * screenNoise) < psout.Diffuse.w ? 1.0 : 0.0; - psout.NormalGlossiness.w = stochasticBlend; # endif # if !defined(HDR_OUTPUT) // Do not apply gamma correction before we pass to ISHDR. From 806f5f4c8486210a0f6b3ecc6360b307d1a3e70a Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 4 May 2026 17:08:48 +1000 Subject: [PATCH 17/29] shader refactors --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 709 +----------------- .../ExtendedMaterialsParallaxCore.hlsli | 231 ++++++ .../ExtendedMaterialsTerrain.hlsli | 377 ++++++++++ package/Shaders/Lighting.hlsl | 33 +- 4 files changed, 651 insertions(+), 699 deletions(-) create mode 100644 features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli create mode 100644 features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 2c34b6a958..8d7e9310ec 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -1,13 +1,31 @@ // https://github.com/tgjones/slimshader-cpp/blob/master/src/Shaders/Sdk/Direct3D11/DetailTessellation11/POM.hlsl // https://github.com/alandtse/SSEShaderTools/blob/main/shaders_vr/ParallaxEffect.h - // https://github.com/marselas/Zombie-Direct3D-Samples/blob/5f53dc2d6f7deb32eb2e5e438d6b6644430fe9ee/Direct3D/ParallaxOcclusionMapping/ParallaxOcclusionMapping.fx // http://www.diva-portal.org/smash/get/diva2:831762/FULLTEXT01.pdf // https://bartwronski.files.wordpress.com/2014/03/ac4_gdc.pdf -#if defined(TERRAIN_VARIATION) && defined(LANDSCAPE) -# include "TerrainVariation/TerrainVariation.hlsli" -#endif +// Extended Materials: split for faster compiles on non-landscape Lighting permutations. +// - Terrain helpers: ExtendedMaterialsTerrain.hlsli (only when LANDSCAPE) +// - Parallax core: ExtendedMaterialsParallaxCore.hlsli (GetParallaxCoords + mesh soft shadows) + +#ifndef EXTENDED_MATERIALS_HLSLI +#define EXTENDED_MATERIALS_HLSLI + +// Terrain variation: optional feature pack — include only when macro + headers ship together. +// When absent, stub `StochasticOffsets` so EMAT terrain APIs stay unified (offsets ignored on SampleLevel path). +# if defined(LANDSCAPE) +# if defined(TERRAIN_VARIATION) +# include "TerrainVariation/TerrainVariation.hlsli" +# else +struct StochasticOffsets +{ + float2 offset1; + float2 offset2; + float2 offset3; + float3 weights; +}; +# endif +# endif struct DisplacementParams { @@ -57,13 +75,13 @@ namespace ExtendedMaterials float2 textureDims; tex.GetDimensions(textureDims.x, textureDims.y); -#if !defined(PARALLAX) && !defined(TRUE_PBR) +# if !defined(PARALLAX) && !defined(TRUE_PBR) textureDims /= 2.0; -#endif +# endif -#if defined(VR) +# if defined(VR) textureDims /= 2.0; -#endif +# endif float2 texCoordsPerSize = coords * textureDims; @@ -74,678 +92,21 @@ namespace ExtendedMaterials float mipLevel = max(0.5 * log2(minTexCoordDelta), 0); -#if !defined(PARALLAX) && !defined(TRUE_PBR) - mipLevel++; -#endif - -#if defined(VR) +# if !defined(PARALLAX) && !defined(TRUE_PBR) mipLevel++; -#endif - - return floor(mipLevel); - } - -#if defined(LANDSCAPE) - void InitializeTerrainMipLevels(float2 coords, out float mipLevels[6]) - { - mipLevels[0] = GetMipLevel(coords, TexColorSampler); - mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler); - mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler); - mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler); - mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler); - mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler); - } - -# define HEIGHT_POWER 2 -# define HEIGHT_MULT 8 - - void ProcessTerrainHeightWeights(float heightBlend, float4 w1, float2 w2, float heights[6], inout float weights[6], out float totalHeight) - { - weights[0] = w1.x; - weights[1] = w1.y; - weights[2] = w1.z; - weights[3] = w1.w; - weights[4] = w2.x; - weights[5] = w2.y; - - totalHeight = 0; - [unroll] for (int i = 0; i < 6; i++) - { - totalHeight += heights[i] * weights[i]; - } - - if (heightBlend <= 1.0) { - float wsum = 0; - [unroll] for (int j = 0; j < 6; j++) - { - wsum += weights[j]; - } - - float invwsum = rcp(wsum); - [unroll] for (int k = 0; k < 6; k++) - { - weights[k] *= invwsum; - } - return; - } - - [unroll] for (int i = 0; i < 6; i++) - { - weights[i] *= pow(heightBlend, HEIGHT_MULT * heights[i]); - } - - [unroll] for (int j = 0; j < 6; j++) - { - weights[j] = min(100, pow(abs(weights[j]), heightBlend)); - } - - float wsum = 0; - [unroll] for (int k = 0; k < 6; k++) - { - wsum += weights[k]; - } - - float invwsum = rcp(wsum); - [unroll] for (int l = 0; l < 6; l++) - { - weights[l] *= invwsum; - } - } - -# if defined(TRUE_PBR) - float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, -# if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, -# endif - out float weights[6]) - { - float heightBlend = 1 + blendFactor * HEIGHT_POWER; - float heights[6] = { 0, 0, 0, 0, 0, 0 }; - - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile0HasDisplacement) != 0 && w1.x > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[0] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement0Sampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset).x, params[0]); -# else - heights[0] = ScaleDisplacement(TexLandDisplacement0Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[0]).x, params[0]); -# endif - } - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile1HasDisplacement) != 0 && w1.y > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement1Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset).x, params[1]); -# else - heights[1] = ScaleDisplacement(TexLandDisplacement1Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[1]).x, params[1]); -# endif - } - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile2HasDisplacement) != 0 && w1.z > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement2Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset).x, params[2]); -# else - heights[2] = ScaleDisplacement(TexLandDisplacement2Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[2]).x, params[2]); -# endif - } - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile3HasDisplacement) != 0 && w1.w > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement3Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset).x, params[3]); -# else - heights[3] = ScaleDisplacement(TexLandDisplacement3Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[3]).x, params[3]); -# endif - } - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile4HasDisplacement) != 0 && w2.x > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement4Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset).x, params[4]); -# else - heights[4] = ScaleDisplacement(TexLandDisplacement4Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[4]).x, params[4]); -# endif - } - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile5HasDisplacement) != 0 && w2.y > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandDisplacement5Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset).x, params[5]); -# else - heights[5] = ScaleDisplacement(TexLandDisplacement5Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[5]).x, params[5]); -# endif - } - - float total; - ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); - return total; - } - -# else - float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, -# if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, -# endif - out float weights[6]) - { - float heightBlend = 1 + blendFactor * HEIGHT_POWER; - float heights[6] = { 0, 0, 0, 0, 0, 0 }; - - if (w1.x > 0.01) { - [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand0HasDisplacement) != 0) - { -# if defined(TERRAIN_VARIATION) - heights[0] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp0Sampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset).x, params[0]); -# else - heights[0] = ScaleDisplacement(TexLandTHDisp0Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[0]).x, params[0]); -# endif - } - else - { -# if defined(TERRAIN_VARIATION) - heights[0] = ScaleDisplacement(StochasticEffectParallax(TexColorSampler, SampTerrainParallaxSampler, coords, mipLevels[0], sharedOffset).w, params[0]); -# else - heights[0] = ScaleDisplacement(TexColorSampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[0]).w, params[0]); -# endif - } - } - if (w1.y > 0.01) { - [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand1HasDisplacement) != 0) - { -# if defined(TERRAIN_VARIATION) - heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp1Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset).x, params[1]); -# else - heights[1] = ScaleDisplacement(TexLandTHDisp1Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[1]).x, params[1]); -# endif - } - else - { -# if defined(TERRAIN_VARIATION) - heights[1] = ScaleDisplacement(StochasticEffectParallax(TexLandColor2Sampler, SampTerrainParallaxSampler, coords, mipLevels[1], sharedOffset).w, params[1]); -# else - heights[1] = ScaleDisplacement(TexLandColor2Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[1]).w, params[1]); -# endif - } - } - if (w1.z > 0.01) { - [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand2HasDisplacement) != 0) - { -# if defined(TERRAIN_VARIATION) - heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp2Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset).x, params[2]); -# else - heights[2] = ScaleDisplacement(TexLandTHDisp2Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[2]).x, params[2]); -# endif - } - else - { -# if defined(TERRAIN_VARIATION) - heights[2] = ScaleDisplacement(StochasticEffectParallax(TexLandColor3Sampler, SampTerrainParallaxSampler, coords, mipLevels[2], sharedOffset).w, params[2]); -# else - heights[2] = ScaleDisplacement(TexLandColor3Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[2]).w, params[2]); -# endif - } - } - [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand3HasDisplacement) != 0 && w1.w > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp3Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset).x, params[3]); -# else - heights[3] = ScaleDisplacement(TexLandTHDisp3Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[3]).x, params[3]); -# endif - } - else if (w1.w > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[3] = ScaleDisplacement(StochasticEffectParallax(TexLandColor4Sampler, SampTerrainParallaxSampler, coords, mipLevels[3], sharedOffset).w, params[3]); -# else - heights[3] = ScaleDisplacement(TexLandColor4Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[3]).w, params[3]); -# endif - } - [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand4HasDisplacement) != 0 && w2.x > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp4Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset).x, params[4]); -# else - heights[4] = ScaleDisplacement(TexLandTHDisp4Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[4]).x, params[4]); -# endif - } - else if (w2.x > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[4] = ScaleDisplacement(StochasticEffectParallax(TexLandColor5Sampler, SampTerrainParallaxSampler, coords, mipLevels[4], sharedOffset).w, params[4]); -# else - heights[4] = ScaleDisplacement(TexLandColor5Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[4]).w, params[4]); -# endif - } - [branch] if ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLand5HasDisplacement) != 0 && w2.y > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandTHDisp5Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset).x, params[5]); -# else - heights[5] = ScaleDisplacement(TexLandTHDisp5Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[5]).x, params[5]); -# endif - } - else if (w2.y > 0.01) - { -# if defined(TERRAIN_VARIATION) - heights[5] = ScaleDisplacement(StochasticEffectParallax(TexLandColor6Sampler, SampTerrainParallaxSampler, coords, mipLevels[5], sharedOffset).w, params[5]); -# else - heights[5] = ScaleDisplacement(TexLandColor6Sampler.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[5]).w, params[5]); -# endif - } - - float total; - ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); - return total; - } - -# endif - -#endif - -#if defined(LANDSCAPE) - float2 GetParallaxCoords(PS_INPUT input, float2 coords, float mipLevels[6], float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], -# if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, -# endif - out float pixelOffset, -# if defined(VR_STEREO_OPT) - out bool hasPOM, -# endif - out float weights[6]) -#else - float2 GetParallaxCoords(float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset -# if defined(VR_STEREO_OPT) - , - out bool hasPOM -# endif - ) -#endif - { - pixelOffset = 0.0; -#if defined(VR_STEREO_OPT) - hasPOM = false; -#endif - float3 viewDirTS = normalize(mul(tbn, viewDir)); -#if defined(LANDSCAPE) - viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params[0].FlattenAmount; // Fix for objects at extreme viewing angles -#else - viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles -#endif - -#if defined(LANDSCAPE) - float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0; - float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); - float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); -# if defined(TRUE_PBR) - float scale = max(params[0].HeightScale * w1.x, max(params[1].HeightScale * w1.y, max(params[2].HeightScale * w1.z, max(params[3].HeightScale * w1.w, max(params[4].HeightScale * w2.x, params[5].HeightScale * w2.y))))); - float scalercp = rcp(max(scale, 1e-4)); - float maxHeight = 0.1 * scale; -# else - float scale = 1; - float maxHeight = 0.1 * scale; -# endif -#else - float scale = params.HeightScale; - float maxHeight = 0.1 * scale; -#endif - float minHeight = maxHeight * 0.5; - -#if defined(LANDSCAPE) -# if defined(TRUE_PBR) - if (scale <= 0.001) { - weights[0] = input.LandBlendWeights1.x; - weights[1] = input.LandBlendWeights1.y; - weights[2] = input.LandBlendWeights1.z; - weights[3] = input.LandBlendWeights1.w; - weights[4] = input.LandBlendWeights2.x; - weights[5] = input.LandBlendWeights2.y; - pixelOffset = 0.0; - return coords; - } -# endif -#else - if (scale <= 0.001) { - pixelOffset = 0.0; - return coords; - } -#endif - - { - const float maxSteps = 4; - uint numSteps = max(4, uint(scale * maxSteps)); - numSteps = (numSteps + 2) & ~3; - - float stepSize = rcp(numSteps); - - float2 offsetPerStep = viewDirTS.xy * float2(maxHeight, maxHeight) * stepSize.xx; - float2 prevOffset = viewDirTS.xy * float2(minHeight, minHeight) + coords.xy; - - float prevBound = 1.0; - float prevHeight = 1.0; - - float2 pt1 = 0; - float2 pt2 = 0; - bool intersectionFound = false; - - [loop] while (numSteps > 0) - { - float4 currentOffset[2]; - currentOffset[0] = prevOffset.xyxy - float4(1, 1, 2, 2) * offsetPerStep.xyxy; - currentOffset[1] = prevOffset.xyxy - float4(3, 3, 4, 4) * offsetPerStep.xyxy; - float4 currentBound = prevBound.xxxx - float4(1, 2, 3, 4) * stepSize; - - float4 currHeight; -#if defined(LANDSCAPE) -# if defined(TRUE_PBR) -# if defined(TERRAIN_VARIATION) - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; - currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; - currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; - currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; -# else - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; - currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; - currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; - currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; -# endif -# else -# if defined(TERRAIN_VARIATION) - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; - currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; - currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; - currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; -# else - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; - currHeight.y = GetTerrainHeight(noise, input, currentOffset[0].zw, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; - currHeight.z = GetTerrainHeight(noise, input, currentOffset[1].xy, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; - currHeight.w = GetTerrainHeight(noise, input, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; -# endif -# endif -#else - currHeight.x = tex.SampleLevel(texSampler, currentOffset[0].xy, mipLevel)[channel]; - currHeight.y = tex.SampleLevel(texSampler, currentOffset[0].zw, mipLevel)[channel]; - currHeight.z = tex.SampleLevel(texSampler, currentOffset[1].xy, mipLevel)[channel]; - currHeight.w = tex.SampleLevel(texSampler, currentOffset[1].zw, mipLevel)[channel]; - - currHeight = AdjustDisplacementNormalized(currHeight, params); -#endif - - bool4 testResult = currHeight >= currentBound; - [branch] if (any(testResult)) - { - float2 outOffset = 0; - intersectionFound = true; - [flatten] if (testResult.w) - { - outOffset = currentOffset[1].xy; - pt1 = float2(currentBound.w, currHeight.w); - pt2 = float2(currentBound.z, currHeight.z); - } - [flatten] if (testResult.z) - { - outOffset = currentOffset[0].zw; - pt1 = float2(currentBound.z, currHeight.z); - pt2 = float2(currentBound.y, currHeight.y); - } - [flatten] if (testResult.y) - { - outOffset = currentOffset[0].xy; - pt1 = float2(currentBound.y, currHeight.y); - pt2 = float2(currentBound.x, currHeight.x); - } - [flatten] if (testResult.x) - { - outOffset = prevOffset; - pt1 = float2(currentBound.x, currHeight.x); - pt2 = float2(prevBound, prevHeight); - } - prevOffset = outOffset; - break; - } - - prevOffset = currentOffset[1].zw; - prevBound = currentBound.w; - prevHeight = currHeight.w; - numSteps -= 4; - } - - float parallaxAmount = 0.0; - [branch] if (intersectionFound) - { - // Refine coarse hit interval with secant iterations: - // f(t) = sampledHeight(t) - t, t in [0,1] where t is ray depth bound. - float tNear = pt1.x; - float hNear = pt1.y; - float fNear = hNear - tNear; - float tFar = pt2.x; - float hFar = pt2.y; - float fFar = hFar - tFar; - - [unroll] for (uint i = 0; i < 3; i++) - { - float denominator = fNear - fFar; - float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; - float tSecant = lerp(tNear, tFar, r); - float2 secantCoords = coords.xy + viewDirTS.xy * (((1.0 - tSecant) * -maxHeight) + minHeight); - - float hSecant; -#if defined(LANDSCAPE) -# if defined(TRUE_PBR) -# if defined(TERRAIN_VARIATION) - hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * scalercp + 0.5; -# else - hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, weights) * scalercp + 0.5; -# endif -# else -# if defined(TERRAIN_VARIATION) - hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) + 0.5; -# else - hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, weights) + 0.5; -# endif -# endif -#else - hSecant = tex.SampleLevel(texSampler, secantCoords, mipLevel)[channel]; - hSecant = AdjustDisplacementNormalized(hSecant, params); -#endif - - float fSecant = hSecant - tSecant; - [branch] if (fSecant >= 0.0) - { - tNear = tSecant; - hNear = hSecant; - fNear = fSecant; - } - else - { - tFar = tSecant; - hFar = hSecant; - fFar = fSecant; - } - } - - float denominator = fNear - fFar; - float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; - parallaxAmount = lerp(tNear, tFar, r); - } - - float offset = (1.0 - parallaxAmount) * -maxHeight + minHeight; - pixelOffset = saturate(parallaxAmount); -#if defined(VR_STEREO_OPT) - hasPOM = true; -#endif - return viewDirTS.xy * offset + coords.xy; - } - } - - // https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf - // Cheap method of creating shadows using height for a given light source - float GetParallaxSoftShadowMultiplier(float2 coords, float mipLevel, float3 L, float sh0, Texture2D tex, SamplerState texSampler, uint channel, float quality, float noise, DisplacementParams params) - { - [branch] if (quality > 0.0) - { - uint tapCount = ParallaxShadowTapCount(quality); - float shadowStrength = ShadowIntensity * (4.0 / tapCount); - float2 rayDir = L.xy * 0.1 * params.HeightScale; - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh = sh0.xxxx; - sh.x = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.x, mipLevel)[channel], params); - if (quality > 0.25) - sh.y = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.y, mipLevel)[channel], params); - if (quality > 0.5) - sh.z = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.z, mipLevel)[channel], params); - if (quality > 0.75) - sh.w = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.w, mipLevel)[channel], params); - return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); - } - return 1.0; - } - -#if defined(LANDSCAPE) -# if defined(TRUE_PBR) - static const uint TERRAIN_DISPLACEMENT_MASK = (1u << 6u) | (1u << 7u) | (1u << 8u) | (1u << 9u) | (1u << 10u) | (1u << 11u); -# endif -# if defined(TERRAIN_VARIATION) -# define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ - GetTerrainHeight(noise, input, COORDS, MIP, params, SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, WEIGHTS) -# else -# define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ - GetTerrainHeight(noise, input, COORDS, MIP, params, SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, WEIGHTS) -# endif - - inline bool TerrainHasSignificantBlend(float4 w1, float2 w2) - { - return (w1.x + w1.y + w1.z + w1.w + w2.x + w2.y) > 0.01; - } - - inline bool TerrainHasAnyDisplacement() - { -# if defined(TRUE_PBR) - return (PBRFlags & TERRAIN_DISPLACEMENT_MASK) != 0; -# else - // Some distant landscape permutations can lose THLandHasDisplacement even though - // legacy terrain parallax still uses alpha-based displacement. - return SharedData::extendedMaterialSettings.EnableTerrainParallax || - (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; -# endif - } - - inline float TerrainMaxWeightedHeightScale(PS_INPUT input, DisplacementParams params[6]) - { - return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, - max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); - } - - inline uint TerrainDirectionalShadowTapCount(float quality) - { - // Directional terrain shadows are capped to reduce cost. - if (quality > 0.7) - return 2; - if (quality > 0.0) - return 1; - return 0; - } - -# if defined(TERRAIN_VARIATION) - bool ComputeTerrainParallaxShadowBaseHeight(PS_INPUT input, float2 coords, float mipLevels[6], float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) -# else - bool ComputeTerrainParallaxShadowBaseHeight(PS_INPUT input, float2 coords, float mipLevels[6], float quality, float noise, DisplacementParams params[6], out float sh0) -# endif - { - sh0 = 0.0; - if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) - return false; - if (!TerrainHasAnyDisplacement()) - return false; - - float weights[6] = { 0, 0, 0, 0, 0, 0 }; - sh0 = TERRAIN_HEIGHT_AT(coords, mipLevels, quality, weights); - return true; - } - -# if defined(TERRAIN_VARIATION) - float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset) -# else - float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6]) -# endif - { - if (quality > 0.0) { - uint tapCount = ParallaxShadowTapCount(quality); - float shadowStrength = ShadowIntensity * (4.0 / tapCount); - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh = sh0.xxxx; - float heights[6] = { 0, 0, 0, 0, 0, 0 }; - float2 rayDir = L.xy * 0.1; - -# if defined(TRUE_PBR) - float scale = TerrainMaxWeightedHeightScale(input, params); - if (scale < 0.01) - return 1.0; - rayDir *= scale; -# endif - sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); - if (quality > 0.25) - sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); - if (quality > 0.5) - sh.z = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.z, mipLevel, quality, heights); - if (quality > 0.75) - sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); -# if defined(TRUE_PBR) - return 1.0 - saturate(dot(max(0, sh - sh0) / scale, shadowStrength)); -# else - return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); -# endif - } - return 1.0; - } - -# if defined(TERRAIN_VARIATION) - float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, float sh0) -# else - float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], float sh0) # endif - { - uint tapCount = TerrainDirectionalShadowTapCount(quality); - if (tapCount == 0) - return 1.0; - float shadowStrength = ShadowIntensity * (2.0 / tapCount); - if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) - return 1.0; - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh = sh0.xxxx; - float heights[6] = { 0, 0, 0, 0, 0, 0 }; - float2 rayDir = lightDirection.xy * 0.1; - -# if defined(TRUE_PBR) - float scale = TerrainMaxWeightedHeightScale(input, params); - if (scale < 0.01) - return 1.0; - rayDir *= scale; +# if defined(VR) + mipLevel++; # endif - sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevels, quality, heights); - if (tapCount > 1) - sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevels, quality, heights); - -# if defined(TRUE_PBR) - return 1.0 - saturate(dot(max(0, sh - sh0) / scale, shadowStrength)); -# else - return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); -# endif + return floor(mipLevel); } -# if defined(TERRAIN_VARIATION) - float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) - { - if (!ComputeTerrainParallaxShadowBaseHeight(input, coords, mipLevels, quality, noise, params, sharedOffset, sh0)) - return 1.0; - return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params, sharedOffset); - } -# else - float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], out float sh0) - { - if (!ComputeTerrainParallaxShadowBaseHeight(input, coords, mipLevels, quality, noise, params, sh0)) - return 1.0; - return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params); - } +# if defined(LANDSCAPE) +# include "ExtendedMaterials/ExtendedMaterialsTerrain.hlsli" # endif - -# undef TERRAIN_HEIGHT_AT -#endif // defined(LANDSCAPE) && defined(TERRAIN_VARIATION) +# include "ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli" } + +#endif // EXTENDED_MATERIALS_HLSLI diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli new file mode 100644 index 0000000000..8b4c1fde0a --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -0,0 +1,231 @@ +#ifndef EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI +#define EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI + +// Body included inside `namespace ExtendedMaterials` from ExtendedMaterials.hlsli. + +#if defined(LANDSCAPE) + float2 GetParallaxCoords(PS_INPUT input, float2 coords, float mipLevels[6], float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], + StochasticOffsets sharedOffset, + out float pixelOffset, +# if defined(VR_STEREO_OPT) + out bool hasPOM, +# endif + out float weights[6]) +#else + float2 GetParallaxCoords(float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset +# if defined(VR_STEREO_OPT) + , + out bool hasPOM +# endif + ) +#endif + { + pixelOffset = 0.0; +#if defined(VR_STEREO_OPT) + hasPOM = false; +#endif + float3 viewDirTS = normalize(mul(tbn, viewDir)); +#if defined(LANDSCAPE) + viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params[0].FlattenAmount; // Fix for objects at extreme viewing angles +#else + viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles +#endif + +#if defined(LANDSCAPE) + float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0; + float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); + float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); +# if defined(TRUE_PBR) + float scale = max(params[0].HeightScale * w1.x, max(params[1].HeightScale * w1.y, max(params[2].HeightScale * w1.z, max(params[3].HeightScale * w1.w, max(params[4].HeightScale * w2.x, params[5].HeightScale * w2.y))))); + float scalercp = rcp(max(scale, 1e-4)); + float terrainHeightNormMul = scalercp; + float maxHeight = 0.1 * scale; +# else + float scale = 1; + float terrainHeightNormMul = 1.0; + float maxHeight = 0.1 * scale; +# endif +#else + float scale = params.HeightScale; + float maxHeight = 0.1 * scale; +#endif + float minHeight = maxHeight * 0.5; + +#if defined(LANDSCAPE) +# if defined(TRUE_PBR) + if (scale <= 0.001) { + weights[0] = input.LandBlendWeights1.x; + weights[1] = input.LandBlendWeights1.y; + weights[2] = input.LandBlendWeights1.z; + weights[3] = input.LandBlendWeights1.w; + weights[4] = input.LandBlendWeights2.x; + weights[5] = input.LandBlendWeights2.y; + pixelOffset = 0.0; + return coords; + } +# endif +#else + if (scale <= 0.001) { + pixelOffset = 0.0; + return coords; + } +#endif + + { + const float maxSteps = 4; + uint numSteps = max(4, uint(scale * maxSteps)); + numSteps = (numSteps + 2) & ~3; + + float stepSize = rcp(numSteps); + + float2 offsetPerStep = viewDirTS.xy * float2(maxHeight, maxHeight) * stepSize.xx; + float2 prevOffset = viewDirTS.xy * float2(minHeight, minHeight) + coords.xy; + + float prevBound = 1.0; + float prevHeight = 1.0; + + float2 pt1 = 0; + float2 pt2 = 0; + bool intersectionFound = false; + + [loop] while (numSteps > 0) + { + float4 currentOffset[2]; + currentOffset[0] = prevOffset.xyxy - float4(1, 1, 2, 2) * offsetPerStep.xyxy; + currentOffset[1] = prevOffset.xyxy - float4(3, 3, 4, 4) * offsetPerStep.xyxy; + float4 currentBound = prevBound.xxxx - float4(1, 2, 3, 4) * stepSize; + + float4 currHeight; +#if defined(LANDSCAPE) + currHeight = GetTerrainHeightQuadRayMarch(noise, input, currentOffset[0].xy, currentOffset[0].zw, currentOffset[1].xy, currentOffset[1].zw, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + currHeight.x = tex.SampleLevel(texSampler, currentOffset[0].xy, mipLevel)[channel]; + currHeight.y = tex.SampleLevel(texSampler, currentOffset[0].zw, mipLevel)[channel]; + currHeight.z = tex.SampleLevel(texSampler, currentOffset[1].xy, mipLevel)[channel]; + currHeight.w = tex.SampleLevel(texSampler, currentOffset[1].zw, mipLevel)[channel]; + + currHeight = AdjustDisplacementNormalized(currHeight, params); +#endif + + bool4 testResult = currHeight >= currentBound; + [branch] if (any(testResult)) + { + float2 outOffset = 0; + intersectionFound = true; + [flatten] if (testResult.w) + { + outOffset = currentOffset[1].xy; + pt1 = float2(currentBound.w, currHeight.w); + pt2 = float2(currentBound.z, currHeight.z); + } + [flatten] if (testResult.z) + { + outOffset = currentOffset[0].zw; + pt1 = float2(currentBound.z, currHeight.z); + pt2 = float2(currentBound.y, currHeight.y); + } + [flatten] if (testResult.y) + { + outOffset = currentOffset[0].xy; + pt1 = float2(currentBound.y, currHeight.y); + pt2 = float2(currentBound.x, currHeight.x); + } + [flatten] if (testResult.x) + { + outOffset = prevOffset; + pt1 = float2(currentBound.x, currHeight.x); + pt2 = float2(prevBound, prevHeight); + } + prevOffset = outOffset; + break; + } + + prevOffset = currentOffset[1].zw; + prevBound = currentBound.w; + prevHeight = currHeight.w; + numSteps -= 4; + } + + float parallaxAmount = 0.0; + [branch] if (intersectionFound) + { + // Refine coarse hit interval with secant iterations: + // f(t) = sampledHeight(t) - t, t in [0,1] where t is ray depth bound. + float tNear = pt1.x; + float hNear = pt1.y; + float fNear = hNear - tNear; + float tFar = pt2.x; + float hFar = pt2.y; + float fFar = hFar - tFar; + + [loop] for (uint i = 0; i < 3; i++) + { + float denominator = fNear - fFar; + float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; + float tSecant = lerp(tNear, tFar, r); + float2 secantCoords = coords.xy + viewDirTS.xy * (((1.0 - tSecant) * -maxHeight) + minHeight); + + float hSecant; +#if defined(LANDSCAPE) + hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + hSecant = tex.SampleLevel(texSampler, secantCoords, mipLevel)[channel]; + hSecant = AdjustDisplacementNormalized(hSecant, params); +#endif + + float fSecant = hSecant - tSecant; + [branch] if (fSecant >= 0.0) + { + tNear = tSecant; + hNear = hSecant; + fNear = fSecant; + } + else + { + tFar = tSecant; + hFar = hSecant; + fFar = fSecant; + } + } + + float denominator = fNear - fFar; + float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; + parallaxAmount = lerp(tNear, tFar, r); + } + + float offset = (1.0 - parallaxAmount) * -maxHeight + minHeight; + pixelOffset = saturate(parallaxAmount); +#if defined(VR_STEREO_OPT) + hasPOM = true; +#endif + return viewDirTS.xy * offset + coords.xy; + } + } + +# if !defined(LANDSCAPE) + // https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf + // Cheap method of creating shadows using height for a given light source + float GetParallaxSoftShadowMultiplier(float2 coords, float mipLevel, float3 L, float sh0, Texture2D tex, SamplerState texSampler, uint channel, float quality, float noise, DisplacementParams params) + { + [branch] if (quality > 0.0) + { + uint tapCount = ParallaxShadowTapCount(quality); + float shadowStrength = ShadowIntensity * (4.0 / tapCount); + float2 rayDir = L.xy * 0.1 * params.HeightScale; + float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); + float4 sh = sh0.xxxx; + sh.x = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.x, mipLevel)[channel], params); + if (quality > 0.25) + sh.y = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.y, mipLevel)[channel], params); + if (quality > 0.5) + sh.z = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.z, mipLevel)[channel], params); + if (quality > 0.75) + sh.w = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.w, mipLevel)[channel], params); + return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); + } + return 1.0; + } + +# endif + +#endif // EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli new file mode 100644 index 0000000000..19a51fce85 --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -0,0 +1,377 @@ +#ifndef EXTENDED_MATERIALS_TERRAIN_HLSLI +#define EXTENDED_MATERIALS_TERRAIN_HLSLI + +// Included only for LANDSCAPE Lighting permutations (see ExtendedMaterials.hlsli). + + void InitializeTerrainMipLevels(float2 coords, out float mipLevels[6]) + { + mipLevels[0] = GetMipLevel(coords, TexColorSampler); + mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler); + mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler); + mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler); + mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler); + mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler); + } + + // Offsets are ignored when TERRAIN_VARIATION is unset (SampleLevel path). + inline float4 TerrainParallaxTexSample(Texture2D tex, float2 uv, float mipLevel, StochasticOffsets sharedOffset) + { +# if defined(TERRAIN_VARIATION) + return StochasticEffectParallax(tex, SampTerrainParallaxSampler, uv, mipLevel, sharedOffset); +# else + return tex.SampleLevel(SampTerrainParallaxSampler, uv, mipLevel); +# endif + } + +# define HEIGHT_POWER 2 +# define HEIGHT_MULT 8 + + // [loop] on fixed-6: less compile/optimizer blow-up than [unroll] here (tiny runtime cost). + void ProcessTerrainHeightWeights(float heightBlend, float4 w1, float2 w2, float heights[6], inout float weights[6], out float totalHeight) + { + weights[0] = w1.x; + weights[1] = w1.y; + weights[2] = w1.z; + weights[3] = w1.w; + weights[4] = w2.x; + weights[5] = w2.y; + + totalHeight = 0; + [loop] for (int i = 0; i < 6; i++) + { + totalHeight += heights[i] * weights[i]; + } + + if (heightBlend <= 1.0) { + float wsum = 0; + [loop] for (int j = 0; j < 6; j++) + { + wsum += weights[j]; + } + + float invwsum = rcp(wsum); + [loop] for (int k = 0; k < 6; k++) + { + weights[k] *= invwsum; + } + return; + } + + [loop] for (int hbIdx = 0; hbIdx < 6; hbIdx++) + { + weights[hbIdx] *= pow(heightBlend, HEIGHT_MULT * heights[hbIdx]); + } + + [loop] for (int j = 0; j < 6; j++) + { + weights[j] = min(100, pow(abs(weights[j]), heightBlend)); + } + + float wsum = 0; + [loop] for (int k = 0; k < 6; k++) + { + wsum += weights[k]; + } + + float invwsum = rcp(wsum); + [loop] for (int l = 0; l < 6; l++) + { + weights[l] *= invwsum; + } + } + + // Blend four per-tap height vectors like four sequential GetTerrainHeight calls; weights output matches tap 3 (last UV). + float4 FinishTerrainHeightQuadBlend(float heightBlend, float4 w1, float2 w2, + float qh0[6], float qh1[6], float qh2[6], float qh3[6], out float weights[6]) + { + float wTmp[6]; + float t0, t1, t2, t3; + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh0, wTmp, t0); + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh1, wTmp, t1); + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh2, wTmp, t2); + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh3, weights, t3); + return float4(t0, t1, t2, t3); + } + +# if defined(TRUE_PBR) + +// FXC does not substitute macro args inside `::EnumParam`; pass full scoped flags as TILEFLAG. +#define EM_PBR_DISP_LAYER_SCALAR(N, TILEFLAG, TEX, WGT) \ + [branch] if ((PBRFlags & (TILEFLAG)) != 0 && (WGT) > 0.01) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(TEX, coords, mipLevels[N], sharedOffset).x, params[N]); \ + } + +#define EM_PBR_DISP_LAYER_QUAD(N, TILEFLAG, TEX, WGT) \ + [branch] if ((PBRFlags & (TILEFLAG)) != 0 && (WGT) > 0.01) \ + { \ + [unroll] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(TEX, uvs[k], mipLevels[N], sharedOffset).x, params[N]); \ + } + +#define EM_PBR_DISP_FOREACH(M) \ + M(0, PBR::TerrainFlags::LandTile0HasDisplacement, TexLandDisplacement0Sampler, w1.x) \ + M(1, PBR::TerrainFlags::LandTile1HasDisplacement, TexLandDisplacement1Sampler, w1.y) \ + M(2, PBR::TerrainFlags::LandTile2HasDisplacement, TexLandDisplacement2Sampler, w1.z) \ + M(3, PBR::TerrainFlags::LandTile3HasDisplacement, TexLandDisplacement3Sampler, w1.w) \ + M(4, PBR::TerrainFlags::LandTile4HasDisplacement, TexLandDisplacement4Sampler, w2.x) \ + M(5, PBR::TerrainFlags::LandTile5HasDisplacement, TexLandDisplacement5Sampler, w2.y) + + float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + + EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_SCALAR) + + float total; + ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); + return total; + } + + // Ray-march coarse step: one branch tree per layer; inner [unroll] fans out four UVs (same IR as manual unroll). + float4 GetTerrainHeightQuadRayMarch(float screenNoise, PS_INPUT input, + float2 u0, float2 u1, float2 u2, float2 u3, + float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float2 uvs[4] = { u0, u1, u2, u3 }; + float h4[4][6]; + [unroll] for (uint qi = 0; qi < 4; qi++) + [unroll] for (uint lj = 0; lj < 6; lj++) + h4[qi][lj] = 0; + + EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_QUAD) + + return FinishTerrainHeightQuadBlend(heightBlend, w1, w2, h4[0], h4[1], h4[2], h4[3], weights); + } + +#undef EM_PBR_DISP_LAYER_SCALAR +#undef EM_PBR_DISP_LAYER_QUAD +#undef EM_PBR_DISP_FOREACH + +# else + +#define EM_LEGACY_LAYER012_SCALAR(N, THFLAG, THSAMPLER, COLSAMPLER, WGT) \ + if ((WGT) > 0.01) { \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, coords, mipLevels[N], sharedOffset).x, params[N]); \ + } \ + else \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, coords, mipLevels[N], sharedOffset).w, params[N]); \ + } \ + } + +#define EM_LEGACY_LAYER345_SCALAR(N, THFLAG, THSAMPLER, COLSAMPLER, WPRIMARY, WELSE) \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0 && (WPRIMARY) > 0.01) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, coords, mipLevels[N], sharedOffset).x, params[N]); \ + } \ + else if ((WELSE) > 0.01) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, coords, mipLevels[N], sharedOffset).w, params[N]); \ + } + +#define EM_LEGACY_LAYER012_QUAD(N, THFLAG, THSAMPLER, COLSAMPLER, WGT) \ + if ((WGT) > 0.01) { \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0) \ + { \ + [unroll] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, uvs[k], mipLevels[N], sharedOffset).x, params[N]); \ + } \ + else \ + { \ + [unroll] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, uvs[k], mipLevels[N], sharedOffset).w, params[N]); \ + } \ + } + +#define EM_LEGACY_LAYER345_QUAD(N, THFLAG, THSAMPLER, COLSAMPLER, WPRIMARY, WELSE) \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0 && (WPRIMARY) > 0.01) \ + { \ + [unroll] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, uvs[k], mipLevels[N], sharedOffset).x, params[N]); \ + } \ + else if ((WELSE) > 0.01) \ + { \ + [unroll] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, uvs[k], mipLevels[N], sharedOffset).w, params[N]); \ + } + + float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + + EM_LEGACY_LAYER012_SCALAR(0, Permutation::ExtraFeatureFlags::THLand0HasDisplacement, TexLandTHDisp0Sampler, TexColorSampler, w1.x) + EM_LEGACY_LAYER012_SCALAR(1, Permutation::ExtraFeatureFlags::THLand1HasDisplacement, TexLandTHDisp1Sampler, TexLandColor2Sampler, w1.y) + EM_LEGACY_LAYER012_SCALAR(2, Permutation::ExtraFeatureFlags::THLand2HasDisplacement, TexLandTHDisp2Sampler, TexLandColor3Sampler, w1.z) + EM_LEGACY_LAYER345_SCALAR(3, Permutation::ExtraFeatureFlags::THLand3HasDisplacement, TexLandTHDisp3Sampler, TexLandColor4Sampler, w1.w, w1.w) + EM_LEGACY_LAYER345_SCALAR(4, Permutation::ExtraFeatureFlags::THLand4HasDisplacement, TexLandTHDisp4Sampler, TexLandColor5Sampler, w2.x, w2.x) + EM_LEGACY_LAYER345_SCALAR(5, Permutation::ExtraFeatureFlags::THLand5HasDisplacement, TexLandTHDisp5Sampler, TexLandColor6Sampler, w2.y, w2.y) + + float total; + ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); + return total; + } + + // Legacy TH/color paths: same branching as GetTerrainHeight; fan out four UVs per branch. + float4 GetTerrainHeightQuadRayMarch(float screenNoise, PS_INPUT input, + float2 u0, float2 u1, float2 u2, float2 u3, + float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float2 uvs[4] = { u0, u1, u2, u3 }; + float h4[4][6]; + [unroll] for (uint qi = 0; qi < 4; qi++) + [unroll] for (uint lj = 0; lj < 6; lj++) + h4[qi][lj] = 0; + + EM_LEGACY_LAYER012_QUAD(0, Permutation::ExtraFeatureFlags::THLand0HasDisplacement, TexLandTHDisp0Sampler, TexColorSampler, w1.x) + EM_LEGACY_LAYER012_QUAD(1, Permutation::ExtraFeatureFlags::THLand1HasDisplacement, TexLandTHDisp1Sampler, TexLandColor2Sampler, w1.y) + EM_LEGACY_LAYER012_QUAD(2, Permutation::ExtraFeatureFlags::THLand2HasDisplacement, TexLandTHDisp2Sampler, TexLandColor3Sampler, w1.z) + EM_LEGACY_LAYER345_QUAD(3, Permutation::ExtraFeatureFlags::THLand3HasDisplacement, TexLandTHDisp3Sampler, TexLandColor4Sampler, w1.w, w1.w) + EM_LEGACY_LAYER345_QUAD(4, Permutation::ExtraFeatureFlags::THLand4HasDisplacement, TexLandTHDisp4Sampler, TexLandColor5Sampler, w2.x, w2.x) + EM_LEGACY_LAYER345_QUAD(5, Permutation::ExtraFeatureFlags::THLand5HasDisplacement, TexLandTHDisp5Sampler, TexLandColor6Sampler, w2.y, w2.y) + + return FinishTerrainHeightQuadBlend(heightBlend, w1, w2, h4[0], h4[1], h4[2], h4[3], weights); + } + +#undef EM_LEGACY_LAYER012_SCALAR +#undef EM_LEGACY_LAYER345_SCALAR +#undef EM_LEGACY_LAYER012_QUAD +#undef EM_LEGACY_LAYER345_QUAD + +# endif +# if defined(TRUE_PBR) + static const uint TERRAIN_DISPLACEMENT_MASK = (1u << 6u) | (1u << 7u) | (1u << 8u) | (1u << 9u) | (1u << 10u) | (1u << 11u); +# endif +# define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ + GetTerrainHeight(noise, input, COORDS, MIP, params, SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, WEIGHTS) + + inline bool TerrainHasSignificantBlend(float4 w1, float2 w2) + { + return (w1.x + w1.y + w1.z + w1.w + w2.x + w2.y) > 0.01; + } + + inline bool TerrainHasAnyDisplacement() + { +# if defined(TRUE_PBR) + return (PBRFlags & TERRAIN_DISPLACEMENT_MASK) != 0; +# else + // Some distant landscape permutations can lose THLandHasDisplacement even though + // legacy terrain parallax still uses alpha-based displacement. + return SharedData::extendedMaterialSettings.EnableTerrainParallax || + (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; +# endif + } + + inline float TerrainMaxWeightedHeightScale(PS_INPUT input, DisplacementParams params[6]) + { + return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + } + + inline uint TerrainDirectionalShadowTapCount(float quality) + { + // Directional terrain shadows are capped to reduce cost. + if (quality > 0.7) + return 2; + if (quality > 0.0) + return 1; + return 0; + } + + bool ComputeTerrainParallaxShadowBaseHeight(PS_INPUT input, float2 coords, float mipLevels[6], float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) + { + sh0 = 0.0; + if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) + return false; + if (!TerrainHasAnyDisplacement()) + return false; + + float weights[6] = { 0, 0, 0, 0, 0, 0 }; + sh0 = TERRAIN_HEIGHT_AT(coords, mipLevels, quality, weights); + return true; + } + + float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset) + { + if (quality > 0.0) { + uint tapCount = ParallaxShadowTapCount(quality); + float shadowStrength = ShadowIntensity * (4.0 / tapCount); + float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); + float4 sh = sh0.xxxx; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = L.xy * 0.1; + float shadowScaleInv = 1.0; + +# if defined(TRUE_PBR) + float scale = TerrainMaxWeightedHeightScale(input, params); + if (scale < 0.01) + return 1.0; + rayDir *= scale; + shadowScaleInv = rcp(scale); +# endif + sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); + if (quality > 0.25) + sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); + if (quality > 0.5) + sh.z = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.z, mipLevel, quality, heights); + if (quality > 0.75) + sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); + return 1.0 - saturate(dot(max(0, sh - sh0) * shadowScaleInv, shadowStrength)); + } + return 1.0; + } + + float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, float sh0) + { + uint tapCount = TerrainDirectionalShadowTapCount(quality); + if (tapCount == 0) + return 1.0; + float shadowStrength = ShadowIntensity * (2.0 / tapCount); + if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) + return 1.0; + + float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); + float4 sh = sh0.xxxx; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = lightDirection.xy * 0.1; + float shadowScaleInv = 1.0; + +# if defined(TRUE_PBR) + float scale = TerrainMaxWeightedHeightScale(input, params); + if (scale < 0.01) + return 1.0; + rayDir *= scale; + shadowScaleInv = rcp(scale); +# endif + + sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevels, quality, heights); + if (tapCount > 1) + sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevels, quality, heights); + + return 1.0 - saturate(dot(max(0, sh - sh0) * shadowScaleInv, shadowStrength)); + } + + float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) + { + if (!ComputeTerrainParallaxShadowBaseHeight(input, coords, mipLevels, quality, noise, params, sharedOffset, sh0)) + return 1.0; + return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params, sharedOffset); + } + +# undef TERRAIN_HEIGHT_AT + +#endif // EXTENDED_MATERIALS_TERRAIN_HLSLI \ No newline at end of file diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index f5af535776..94edaa0bfc 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1027,13 +1027,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(LANDSCAPE) terrainDirectionalShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; # endif -# if defined(TERRAIN_VARIATION) -# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, terrainShadowMipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) -# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, terrainShadowMipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) -# else -# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, terrainShadowMipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, OUT_SH0) -# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, terrainShadowMipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, BASE_SH0) -# endif +# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, terrainShadowMipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) +# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, terrainShadowMipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) # if defined(LANDSCAPE) # if defined(TRUE_PBR) # define LANDSCAPE_PARALLAX_ENABLED (SharedData::extendedMaterialSettings.EnableParallax) @@ -1049,6 +1044,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float mipLevels[6]; # if defined(EMAT) float terrainShadowMipLevels[6]; +# if defined(TERRAIN_VARIATION) + StochasticOffsets sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); +# else + StochasticOffsets sharedOffset = (StochasticOffsets)0; +# endif float cachedDirectionalTerrainParallaxShadow = 1.0; bool hasCachedDirectionalTerrainParallaxShadow = false; bool hasCachedTerrainShadowBaseHeight = false; @@ -1257,15 +1257,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float4 blendedRMAOS = 0; # endif - // Terrain Variation data is optional and only exists when plugin shaders are installed. -# if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); -# endif - # if defined(EMAT) if (LANDSCAPE_PARALLAX_ENABLED) { ExtendedMaterials::InitializeTerrainMipLevels(uv, mipLevels); - [unroll] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) + [loop] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) { terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); } @@ -1287,19 +1282,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float weights[6]; // Initialize weights array weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0; -# if defined(TERRAIN_VARIATION) uv = ExtendedMaterials::GetParallaxCoords(input, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, # if defined(VR_STEREO_OPT) && !defined(SNOW) hasPOM, # endif weights); -# else - uv = ExtendedMaterials::GetParallaxCoords(input, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset, -# if defined(VR_STEREO_OPT) && !defined(SNOW) - hasPOM, -# endif - weights); -# endif if (SharedData::extendedMaterialSettings.EnableHeightBlending) { input.LandBlendWeights1.x = weights[0]; input.LandBlendWeights1.y = weights[1]; @@ -2259,11 +2246,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); if (terrainHeightScale > 0.01) { float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); -# if defined(TERRAIN_VARIATION) parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, terrainShadowMipLevels, lightDirectionTS, sh0, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset); -# else - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, terrainShadowMipLevels, lightDirectionTS, sh0, terrainDirectionalShadowQuality, screenNoise, displacementParams); -# endif } } } From 04297e0833549ece29ed67d2d5b562958ac0ccf0 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 4 May 2026 17:53:30 +1000 Subject: [PATCH 18/29] bit better quality --- .../Shaders/ExtendedMaterials/ExtendedMaterials.hlsli | 6 +++--- .../ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli | 4 ++-- package/Shaders/Common/LightingLandscape.hlsli | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 8d7e9310ec..338c588b3d 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -38,10 +38,10 @@ struct DisplacementParams namespace ExtendedMaterials { static const float ShadowIntensity = 2.0; - static const float ParallaxCheapDistance = 512.0; + static const float ParallaxCheapDistance = 1024.0; static const float ParallaxNearShadowQuality = 1.0; - static const float ParallaxFarShadowQuality = 0.5; - static const float TerrainParallaxShadowMaxMipLevel = 1.0; + static const float ParallaxFarShadowQuality = 0.76; + static const float TerrainParallaxShadowMaxMipLevel = 0.5; inline uint ParallaxShadowTapCount(float quality) { diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli index 8b4c1fde0a..f877daca11 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -72,7 +72,7 @@ #endif { - const float maxSteps = 4; + const float maxSteps = 8; uint numSteps = max(4, uint(scale * maxSteps)); numSteps = (numSteps + 2) & ~3; @@ -158,7 +158,7 @@ float hFar = pt2.y; float fFar = hFar - tFar; - [loop] for (uint i = 0; i < 3; i++) + [loop] for (uint i = 0; i < 5; i++) { float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; diff --git a/package/Shaders/Common/LightingLandscape.hlsli b/package/Shaders/Common/LightingLandscape.hlsli index 383219fcb2..2161df1e5f 100644 --- a/package/Shaders/Common/LightingLandscape.hlsli +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -88,7 +88,7 @@ namespace LandscapeLayers } \ else \ { \ - landRMAOS = weight * float4(1 - glossiness.x, 0, 1, 0); \ + landRMAOS = float4(1 - glossiness.x, 0, 1, 0); \ } \ blendedRMAOS += landRMAOS * weight; \ blendedRGB += landColorRGB * weight; \ @@ -123,4 +123,4 @@ namespace LandscapeLayers #endif // LANDSCAPE -#endif // __LANDSCAPE_LAYERS_HLSLI__ +#endif // __LIGHTING_LANDSCAPE_HLSLI__ From 0c51a7d8b3a50c956d62b66dcf5cfdb2caadff2d Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 4 May 2026 18:43:42 +1000 Subject: [PATCH 19/29] fixes --- .../ExtendedMaterialsParallaxCore.hlsli | 45 +++++++----- .../ExtendedMaterialsTerrain.hlsli | 6 +- .../TerrainVariation/TerrainVariation.hlsli | 69 ++++++++++++------- 3 files changed, 74 insertions(+), 46 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli index f877daca11..d5b6809889 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -71,6 +71,16 @@ } #endif +#if defined(LANDSCAPE) + // Default out weights for static analysis; ray-march / secant paths overwrite. + weights[0] = input.LandBlendWeights1.x; + weights[1] = input.LandBlendWeights1.y; + weights[2] = input.LandBlendWeights1.z; + weights[3] = input.LandBlendWeights1.w; + weights[4] = input.LandBlendWeights2.x; + weights[5] = input.LandBlendWeights2.y; +#endif + { const float maxSteps = 8; uint numSteps = max(4, uint(scale * maxSteps)); @@ -110,31 +120,32 @@ bool4 testResult = currHeight >= currentBound; [branch] if (any(testResult)) { - float2 outOffset = 0; intersectionFound = true; - [flatten] if (testResult.w) + // Priority matches former [flatten] chain: x overwrites y overwrites z overwrites w. + float2 outOffset; + [branch] if (testResult.x) { - outOffset = currentOffset[1].xy; - pt1 = float2(currentBound.w, currHeight.w); - pt2 = float2(currentBound.z, currHeight.z); - } - [flatten] if (testResult.z) - { - outOffset = currentOffset[0].zw; - pt1 = float2(currentBound.z, currHeight.z); - pt2 = float2(currentBound.y, currHeight.y); + outOffset = prevOffset; + pt1 = float2(currentBound.x, currHeight.x); + pt2 = float2(prevBound, prevHeight); } - [flatten] if (testResult.y) + else if (testResult.y) { outOffset = currentOffset[0].xy; pt1 = float2(currentBound.y, currHeight.y); pt2 = float2(currentBound.x, currHeight.x); } - [flatten] if (testResult.x) + else if (testResult.z) { - outOffset = prevOffset; - pt1 = float2(currentBound.x, currHeight.x); - pt2 = float2(prevBound, prevHeight); + outOffset = currentOffset[0].zw; + pt1 = float2(currentBound.z, currHeight.z); + pt2 = float2(currentBound.y, currHeight.y); + } + else + { + outOffset = currentOffset[1].xy; + pt1 = float2(currentBound.w, currHeight.w); + pt2 = float2(currentBound.z, currHeight.z); } prevOffset = outOffset; break; @@ -165,7 +176,7 @@ float tSecant = lerp(tNear, tFar, r); float2 secantCoords = coords.xy + viewDirTS.xy * (((1.0 - tSecant) * -maxHeight) + minHeight); - float hSecant; + float hSecant = 0.0; #if defined(LANDSCAPE) hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; #else diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli index 19a51fce85..d9a6ca0b65 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -85,7 +85,7 @@ float qh0[6], float qh1[6], float qh2[6], float qh3[6], out float weights[6]) { float wTmp[6]; - float t0, t1, t2, t3; + float t0 = 0.0, t1 = 0.0, t2 = 0.0, t3 = 0.0; ProcessTerrainHeightWeights(heightBlend, w1, w2, qh0, wTmp, t0); ProcessTerrainHeightWeights(heightBlend, w1, w2, qh1, wTmp, t1); ProcessTerrainHeightWeights(heightBlend, w1, w2, qh2, wTmp, t2); @@ -126,7 +126,7 @@ EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_SCALAR) - float total; + float total = 0.0; ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); return total; } @@ -218,7 +218,7 @@ EM_LEGACY_LAYER345_SCALAR(4, Permutation::ExtraFeatureFlags::THLand4HasDisplacement, TexLandTHDisp4Sampler, TexLandColor5Sampler, w2.x, w2.x) EM_LEGACY_LAYER345_SCALAR(5, Permutation::ExtraFeatureFlags::THLand5HasDisplacement, TexLandTHDisp5Sampler, TexLandColor6Sampler, w2.y, w2.y) - float total; + float total = 0.0; ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); return total; } diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index ada0a82b53..0d4e16f6e3 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -11,9 +11,8 @@ static const float2x2 SKEW_MATRIX = float2x2(1.0, 0.0, -0.57735027, 1.15470054); static const float WORLD_SCALE = 332.54; static const float2 HASH_MULTIPLIER = float2(1271.5151, 3337.8237); -static const float HEIGHT_BLEND_CONTRAST = 12.0; +// Height-vs-stochastic weight (unchanged). Stochastic vertex weights use HEIGHT_INFLUENCE only via heightInfluence below. static const float HEIGHT_INFLUENCE = 0.3; -static const float CONTRAST_FACTOR = HEIGHT_BLEND_CONTRAST * (1.0 - HEIGHT_INFLUENCE); static const float3 LUMINANCE_WEIGHTS = float3(0.2126, 0.7152, 0.0722); static const float HEIGHT_BLEND_FADE_MIP_START = 1.6; static const float HEIGHT_BLEND_FADE_MIP_RANGE = 2.2; @@ -132,6 +131,15 @@ inline float StochasticHeightFadeFromMip(float mipLevel) return saturate((mipLevel - HEIGHT_BLEND_FADE_MIP_START) / HEIGHT_BLEND_FADE_MIP_RANGE); } +// Contrast sharpening on barycentric weights. +inline float StochasticContrastWeight(float weight) +{ + float w = saturate(weight); + float w2 = w * w; + float w4 = w2 * w2; + return w4 * w4; +} + // LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) { @@ -144,51 +152,60 @@ inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState sam return lerp(s2, s1, blendW); } -// 2-sample height-blended stochastic sampling — branchless, no wavefront divergence. +// 2-sample height-blended stochastic sampling; mip threshold avoids wasteful distant work. // Sorting in ComputeStochasticOffsets guarantees offset1/offset2 are the two // highest-weight barycentric vertices, so dropping offset3 loses minimal quality. inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) { float mipLevel = tex.CalculateLevelOfDetail(samp, uv) + SharedData::MipBias + extraLandMipBias; + float4 blended; // Far/minified: skip TV blending math + second sample. if (mipLevel >= TV_SINGLE_SAMPLE_MIP_START) - return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + blended = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + else { + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); - float w1 = exp2(log2(saturate(offsets.weights.x)) * CONTRAST_FACTOR); - float w2 = exp2(log2(saturate(offsets.weights.y)) * CONTRAST_FACTOR); - float heightFade = StochasticHeightFadeFromMip(mipLevel); - float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); + float w1 = StochasticContrastWeight(offsets.weights.x); + float w2 = StochasticContrastWeight(offsets.weights.y); + float heightFade = StochasticHeightFadeFromMip(mipLevel); + float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); - float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a)); - float h2 = lerp(dot(s2.rgb, LUMINANCE_WEIGHTS), s2.a, step(0.001, s2.a)); + float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a)); + float h2 = lerp(dot(s2.rgb, LUMINANCE_WEIGHTS), s2.a, step(0.001, s2.a)); - w1 *= (1.0 + heightInfluence * h1); - w2 *= (1.0 + heightInfluence * h2); + w1 *= (1.0 + heightInfluence * h1); + w2 *= (1.0 + heightInfluence * h2); - return lerp(s2, s1, w1 * rcp(w1 + w2)); + float denom = max(w1 + w2, 1e-8); + blended = lerp(s2, s1, w1 / denom); + } + return blended; } // 2-sample parallax sampling — uses heightmap (alpha) only for blend weights. inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { + float4 blended; // Keep parallax height active, but cut TV to one sample once heavily minified. if (mipLevel >= TV_SINGLE_SAMPLE_PARALLAX_MIP_START) - return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - - float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + blended = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + else { + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); - float w1 = exp2(log2(saturate(offsets.weights.x)) * CONTRAST_FACTOR); - float w2 = exp2(log2(saturate(offsets.weights.y)) * CONTRAST_FACTOR); - float heightFade = StochasticHeightFadeFromMip(mipLevel); - float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); + float w1 = StochasticContrastWeight(offsets.weights.x); + float w2 = StochasticContrastWeight(offsets.weights.y); + float heightFade = StochasticHeightFadeFromMip(mipLevel); + float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); - w1 *= (1.0 + heightInfluence * s1.a); - w2 *= (1.0 + heightInfluence * s2.a); + w1 *= (1.0 + heightInfluence * s1.a); + w2 *= (1.0 + heightInfluence * s2.a); - return lerp(s2, s1, w1 * rcp(w1 + w2)); + float denom = max(w1 + w2, 1e-8); + blended = lerp(s2, s1, w1 / denom); + } + return blended; } inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) From 85ad499b1cff02e2d154b5d46ae647e4cc16a8b1 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Thu, 7 May 2026 19:33:05 +1000 Subject: [PATCH 20/29] Update ExtendedMaterials.ini --- .../Extended Materials/Shaders/Features/ExtendedMaterials.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini b/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini index 9e325f8475..0a7412150c 100644 --- a/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini +++ b/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini @@ -1,5 +1,5 @@ [Info] -Version = 1-2-0 +Version = 1-3-0 [Nexus] autoupload = false From ad3e62eedffec41a1c11d91fca070fe6ce86adf4 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Thu, 7 May 2026 19:40:24 +1000 Subject: [PATCH 21/29] x4000 fix --- .../Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli index d9a6ca0b65..e47ba0911d 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -59,12 +59,12 @@ [loop] for (int hbIdx = 0; hbIdx < 6; hbIdx++) { - weights[hbIdx] *= pow(heightBlend, HEIGHT_MULT * heights[hbIdx]); + weights[hbIdx] *= pow(max(abs(heightBlend), 0.0001), HEIGHT_MULT * heights[hbIdx]); } [loop] for (int j = 0; j < 6; j++) { - weights[j] = min(100, pow(abs(weights[j]), heightBlend)); + weights[j] = min(100, pow(abs(weights[j]), max(abs(heightBlend), 0.0001))); } float wsum = 0; From 362f57b196dc3e16f6161535ce2c7f32d0999c9d Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Thu, 7 May 2026 20:01:18 +1000 Subject: [PATCH 22/29] better stepping math --- .../ExtendedMaterialsParallaxCore.hlsli | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli index d5b6809889..8f8075a8a9 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -82,8 +82,21 @@ #endif { - const float maxSteps = 8; - uint numSteps = max(4, uint(scale * maxSteps)); + // Step count scales with height scale *and* viewing angle. + // Keep it extremely cheap: use TS normal (0,0,1) so N·V is just Vz after renormalization. + const float baseMaxSteps = 8; + const uint minSteps = 4; + const uint maxStepsCap = 32; + + float invViewLen = rsqrt(max(dot(viewDirTS, viewDirTS), 1e-6)); + float ndotv = saturate(viewDirTS.z * invViewLen); // 1 = looking "down", 0 = grazing + float grazing = 1.0 - ndotv; + float grazing2 = grazing * grazing; // bias towards keeping steps low until fairly grazing + + // Straight down: ~0.5x (-> typically clamps to 4). Grazing: up to ~2x. + float angleStepMul = lerp(0.5, 2.0, grazing2); + uint numSteps = max(minSteps, (uint)(scale * baseMaxSteps * angleStepMul)); + numSteps = min(numSteps, maxStepsCap); numSteps = (numSteps + 2) & ~3; float stepSize = rcp(numSteps); From c807e3e64f674d1986088662c93650d730057185 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Fri, 15 May 2026 19:13:00 +1000 Subject: [PATCH 23/29] fixes minor --- .../ExtendedMaterials/ExtendedMaterials.hlsli | 3 +- .../ExtendedMaterialsTerrain.hlsli | 2 +- .../TerrainVariation/TerrainVariation.hlsli | 72 +++++++++---------- package/Shaders/Lighting.hlsl | 2 +- 4 files changed, 37 insertions(+), 42 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 338c588b3d..fd618b95ff 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -5,7 +5,8 @@ // https://bartwronski.files.wordpress.com/2014/03/ac4_gdc.pdf // Extended Materials: split for faster compiles on non-landscape Lighting permutations. -// - Terrain helpers: ExtendedMaterialsTerrain.hlsli (only when LANDSCAPE) +// Lighting.hlsl includes this header only when EMAT is defined; LANDSCAPE further gates terrain-only code. +// - Terrain helpers: ExtendedMaterialsTerrain.hlsli (EMAT implied; included only when LANDSCAPE) // - Parallax core: ExtendedMaterialsParallaxCore.hlsli (GetParallaxCoords + mesh soft shadows) #ifndef EXTENDED_MATERIALS_HLSLI diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli index e47ba0911d..1f7b54bbd7 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -1,7 +1,7 @@ #ifndef EXTENDED_MATERIALS_TERRAIN_HLSLI #define EXTENDED_MATERIALS_TERRAIN_HLSLI -// Included only for LANDSCAPE Lighting permutations (see ExtendedMaterials.hlsli). +// Included only for LANDSCAPE Lighting permutations; parent ExtendedMaterials.hlsli is EMAT-only (see Lighting.hlsl). void InitializeTerrainMipLevels(float2 coords, out float mipLevels[6]) { diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 0d4e16f6e3..28706383ba 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -104,7 +104,7 @@ inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) { - if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + [branch] if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) return (StochasticOffsets)0; float2 cellID = floor(landscapeUV * 255437.0); @@ -140,6 +140,24 @@ inline float StochasticContrastWeight(float weight) return w4 * w4; } +// Shared height-vs-stochastic weighting for diffuse + parallax (single IR body vs duplicating below). +inline float StochasticHeightBlendInfluence(float mipLevel) +{ + float heightFade = StochasticHeightFadeFromMip(mipLevel); + return HEIGHT_INFLUENCE * (1.0 - heightFade); +} + +inline float4 StochasticBlendTwoSamples(float mipLevel, float4 s1, float4 s2, float3 weights, float blendFactor1, float blendFactor2) +{ + float w1 = StochasticContrastWeight(weights.x); + float w2 = StochasticContrastWeight(weights.y); + float hi = StochasticHeightBlendInfluence(mipLevel); + w1 *= (1.0 + hi * blendFactor1); + w2 *= (1.0 + hi * blendFactor2); + float denom = max(w1 + w2, 1e-8); + return lerp(s2, s1, w1 / denom); +} + // LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) { @@ -158,54 +176,30 @@ inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState sam inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) { float mipLevel = tex.CalculateLevelOfDetail(samp, uv) + SharedData::MipBias + extraLandMipBias; - float4 blended; // Far/minified: skip TV blending math + second sample. - if (mipLevel >= TV_SINGLE_SAMPLE_MIP_START) - blended = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - else { - float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + [branch] if (mipLevel >= TV_SINGLE_SAMPLE_MIP_START) + return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float w1 = StochasticContrastWeight(offsets.weights.x); - float w2 = StochasticContrastWeight(offsets.weights.y); - float heightFade = StochasticHeightFadeFromMip(mipLevel); - float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); - float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a)); - float h2 = lerp(dot(s2.rgb, LUMINANCE_WEIGHTS), s2.a, step(0.001, s2.a)); + float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a)); + float h2 = lerp(dot(s2.rgb, LUMINANCE_WEIGHTS), s2.a, step(0.001, s2.a)); - w1 *= (1.0 + heightInfluence * h1); - w2 *= (1.0 + heightInfluence * h2); - - float denom = max(w1 + w2, 1e-8); - blended = lerp(s2, s1, w1 / denom); - } - return blended; + return StochasticBlendTwoSamples(mipLevel, s1, s2, offsets.weights, h1, h2); } // 2-sample parallax sampling — uses heightmap (alpha) only for blend weights. inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { - float4 blended; // Keep parallax height active, but cut TV to one sample once heavily minified. - if (mipLevel >= TV_SINGLE_SAMPLE_PARALLAX_MIP_START) - blended = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - else { - float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); - - float w1 = StochasticContrastWeight(offsets.weights.x); - float w2 = StochasticContrastWeight(offsets.weights.y); - float heightFade = StochasticHeightFadeFromMip(mipLevel); - float heightInfluence = HEIGHT_INFLUENCE * (1.0 - heightFade); - - w1 *= (1.0 + heightInfluence * s1.a); - w2 *= (1.0 + heightInfluence * s2.a); - - float denom = max(w1 + w2, 1e-8); - blended = lerp(s2, s1, w1 / denom); - } - return blended; + [branch] if (mipLevel >= TV_SINGLE_SAMPLE_PARALLAX_MIP_START) + return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + + return StochasticBlendTwoSamples(mipLevel, s1, s2, offsets.weights, s1.a, s2.a); } inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index cd4b704870..b9cbd74950 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -930,7 +930,7 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "Common/LightingLandscape.hlsli" # endif -# if defined(TERRAIN_VARIATION) +# if defined(TERRAIN_VARIATION) && (defined(LANDSCAPE) || defined(LOD_LAND_BLEND) || (defined(LOD_BLENDING) && defined(LODLANDSCAPE))) # include "TerrainVariation/TerrainVariation.hlsli" # endif From 2c45e9b7e977820d018fb8c22e0c692c957dbca8 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 1 Jun 2026 19:27:36 +1000 Subject: [PATCH 24/29] fixes --- .../ExtendedMaterialsTerrain.hlsli | 6 +- .../TerrainVariation/TerrainVariation.hlsli | 82 +++++++++++++------ 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli index 1f7b54bbd7..6d7e816fd2 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -13,7 +13,11 @@ mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler); } - // Offsets are ignored when TERRAIN_VARIATION is unset (SampleLevel path). + // Parallax/height sampling: dual-tap stochastic blend, matching the albedo/normal path so the + // displaced height field stays aligned with the de-tiled surface being shaded. StochasticEffectParallax + // is deliberately branchless (this wrapper is inlined 24+ times across the unrolled ray-march/secant/ + // soft-shadow paths, where duplicated control flow is what explodes FXC compile time). Offset is + // ignored when TERRAIN_VARIATION is unset (plain SampleLevel path). inline float4 TerrainParallaxTexSample(Texture2D tex, float2 uv, float mipLevel, StochasticOffsets sharedOffset) { # if defined(TERRAIN_VARIATION) diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 28706383ba..15605dda7b 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -16,9 +16,12 @@ static const float HEIGHT_INFLUENCE = 0.3; static const float3 LUMINANCE_WEIGHTS = float3(0.2126, 0.7152, 0.0722); static const float HEIGHT_BLEND_FADE_MIP_START = 1.6; static const float HEIGHT_BLEND_FADE_MIP_RANGE = 2.2; -// TV distant/minified fallback: bypass stochastic blend and use one sample. -static const float TV_SINGLE_SAMPLE_MIP_START = 2.9; -static const float TV_SINGLE_SAMPLE_PARALLAX_MIP_START = 2.6; +// TV distant/minified fallback: single sample with primary offset (skip dual-sample blend only). +// The second sample's weight fades to zero across [START - FADE_RANGE, START] so the single-sample +// branch is reached only once it contributes nothing -> no pop/seam at the transition. +static const float TV_SINGLE_SAMPLE_MIP_START = 3.0; +static const float TV_SINGLE_SAMPLE_FADE_RANGE = 0.6; +static const float TV_SINGLE_SAMPLE_FADE_RCP = 1.0 / TV_SINGLE_SAMPLE_FADE_RANGE; // Golden ratio for frac(rnd * φ) low-discrepancy jitter; precompute once per pixel at callsite when possible. static const float STOCHASTIC_LOD_PHI = 1.618; @@ -53,6 +56,16 @@ inline float2 hashLOD(float2 p) } // --------------------- COMPUTE FUNCTIONS --------------------- // +inline StochasticOffsets ZeroStochasticOffsets() +{ + StochasticOffsets o; + o.offset1 = 0; + o.offset2 = 0; + o.offset3 = 0; + o.weights = 0; + return o; +} + inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) { float2 skewUV = mul(SKEW_MATRIX, landscapeUV * WORLD_SCALE); @@ -105,7 +118,7 @@ inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) { [branch] if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) - return (StochasticOffsets)0; + return ZeroStochasticOffsets(); float2 cellID = floor(landscapeUV * 255437.0); float2 h1 = hashLOD(cellID); @@ -147,10 +160,12 @@ inline float StochasticHeightBlendInfluence(float mipLevel) return HEIGHT_INFLUENCE * (1.0 - heightFade); } -inline float4 StochasticBlendTwoSamples(float mipLevel, float4 s1, float4 s2, float3 weights, float blendFactor1, float blendFactor2) +// secondSampleScale fades the second tap's contribution to zero near the single-sample cutoff so the +// branch boundary is continuous (no pop). At scale 0 the result is exactly s1. +inline float4 StochasticBlendTwoSamples(float mipLevel, float4 s1, float4 s2, float3 weights, float blendFactor1, float blendFactor2, float secondSampleScale) { float w1 = StochasticContrastWeight(weights.x); - float w2 = StochasticContrastWeight(weights.y); + float w2 = StochasticContrastWeight(weights.y) * secondSampleScale; float hi = StochasticHeightBlendInfluence(mipLevel); w1 *= (1.0 + hi * blendFactor1); w2 *= (1.0 + hi * blendFactor2); @@ -161,45 +176,60 @@ inline float4 StochasticBlendTwoSamples(float mipLevel, float4 s1, float4 s2, fl // LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) { - float lodOn = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; + // Feature off: a single plain fetch (offsets are zero anyway) instead of two identical reads + lerp. + [branch] if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + return tex.SampleBias(samp, uv, SharedData::MipBias); + float2 j1 = (offsetsLOD.offset1 + jitter) * 0.01; float2 j2 = (offsetsLOD.offset2 + float2(jitter.y, -jitter.x)) * 0.01; - float4 s1 = tex.SampleBias(samp, uv + j1 * lodOn, SharedData::MipBias); - float4 s2 = tex.SampleBias(samp, uv + j2 * lodOn, SharedData::MipBias); - float blendW = lerp(0.5, offsetsLOD.weights.x, lodOn); - return lerp(s2, s1, blendW); + float4 s1 = tex.SampleBias(samp, uv + j1, SharedData::MipBias); + float4 s2 = tex.SampleBias(samp, uv + j2, SharedData::MipBias); + return lerp(s2, s1, offsetsLOD.weights.x); } -// 2-sample height-blended stochastic sampling; mip threshold avoids wasteful distant work. +// 2-sample height-blended stochastic sampling. Uses one shared gradient (SampleGrad) for both taps so +// filtering stays consistent and anisotropy is preserved; the second tap fades out with distance. // Sorting in ComputeStochasticOffsets guarantees offset1/offset2 are the two // highest-weight barycentric vertices, so dropping offset3 loses minimal quality. inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) { - float mipLevel = tex.CalculateLevelOfDetail(samp, uv) + SharedData::MipBias + extraLandMipBias; - // Far/minified: skip TV blending math + second sample. - [branch] if (mipLevel >= TV_SINGLE_SAMPLE_MIP_START) - return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + // One gradient pair, reused for the mip estimate and both taps (no separate CalculateLevelOfDetail op). + float2 dUVdx = ddx(uv); + float2 dUVdy = ddy(uv); - float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + float2 texDim; + tex.GetDimensions(texDim.x, texDim.y); + float2 dxT = dUVdx * texDim; + float2 dyT = dUVdy * texDim; + float mipLevel = max(0.5 * log2(max(dot(dxT, dxT), dot(dyT, dyT))), 0.0) + SharedData::MipBias + extraLandMipBias; + + float secondSampleFade = saturate((TV_SINGLE_SAMPLE_MIP_START - mipLevel) * TV_SINGLE_SAMPLE_FADE_RCP); + + float4 s1 = tex.SampleGrad(samp, uv + offsets.offset1, dUVdx, dUVdy); + // Far/minified: second tap has fully faded out -> one anisotropic sample (still offset, still breaks tiling). + [branch] if (secondSampleFade <= 0.0) + return s1; + + float4 s2 = tex.SampleGrad(samp, uv + offsets.offset2, dUVdx, dUVdy); float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a)); float h2 = lerp(dot(s2.rgb, LUMINANCE_WEIGHTS), s2.a, step(0.001, s2.a)); - return StochasticBlendTwoSamples(mipLevel, s1, s2, offsets.weights, h1, h2); + return StochasticBlendTwoSamples(mipLevel, s1, s2, offsets.weights, h1, h2, secondSampleFade); } -// 2-sample parallax sampling — uses heightmap (alpha) only for blend weights. +// 2-sample parallax/height sampling. MUST use the same dual-tap stochastic blend (same offsets/weights) +// as StochasticEffect so the displaced height field stays aligned with the de-tiled albedo/normal — +// otherwise parallax is computed against a different surface than the one being shaded. +// Deliberately BRANCHLESS: this is inlined dozens of times across the unrolled ray-march / secant / +// soft-shadow paths, and it's the duplicated control flow (not the second fetch) that explodes FXC +// compile time. The second tap fades with distance via secondSampleScale, mirroring StochasticEffect. inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { - // Keep parallax height active, but cut TV to one sample once heavily minified. - [branch] if (mipLevel >= TV_SINGLE_SAMPLE_PARALLAX_MIP_START) - return tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); - + float secondSampleFade = saturate((TV_SINGLE_SAMPLE_MIP_START - mipLevel) * TV_SINGLE_SAMPLE_FADE_RCP); float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); - - return StochasticBlendTwoSamples(mipLevel, s1, s2, offsets.weights, s1.a, s2.a); + return StochasticBlendTwoSamples(mipLevel, s1, s2, offsets.weights, s1.a, s2.a, secondSampleFade); } inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) From 8e33d9e989b2850f4212f7453cdaee9a89a96b17 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 1 Jun 2026 20:12:09 +1000 Subject: [PATCH 25/29] perf --- .../ExtendedMaterialsParallaxCore.hlsli | 19 +++++- .../ExtendedMaterialsTerrain.hlsli | 68 +++++++++++-------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli index 8f8075a8a9..3ec1d5af89 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -95,10 +95,25 @@ // Straight down: ~0.5x (-> typically clamps to 4). Grazing: up to ~2x. float angleStepMul = lerp(0.5, 2.0, grazing2); - uint numSteps = max(minSteps, (uint)(scale * baseMaxSteps * angleStepMul)); + + // Distance LOD: parallax displacement is sub-texel once the surface is minified, so + // ramp step + secant counts down with mip. Bit-identical near camera (mip <= 1 -> scale 1). + // Ramp only (no hard switch) keeps the transition pop-free; runtime [loop]s so no extra + // compiled code / FXC cost. +#if defined(LANDSCAPE) + float parallaxLODMip = mipLevels[0]; +#else + float parallaxLODMip = mipLevel; +#endif + float distStepScale = lerp(0.35, 1.0, saturate((4.0 - parallaxLODMip) * (1.0 / 3.0))); + + uint numSteps = max(minSteps, (uint)(scale * baseMaxSteps * angleStepMul * distStepScale)); numSteps = min(numSteps, maxStepsCap); numSteps = (numSteps + 2) & ~3; + // 5 secant iterations near camera, down to ~3 at distance (matches step ramp). + uint secantIters = (uint)(lerp(2.0, 5.0, distStepScale) + 0.5); + float stepSize = rcp(numSteps); float2 offsetPerStep = viewDirTS.xy * float2(maxHeight, maxHeight) * stepSize.xx; @@ -182,7 +197,7 @@ float hFar = pt2.y; float fFar = hFar - tFar; - [loop] for (uint i = 0; i < 5; i++) + [loop] for (uint i = 0; i < secantIters; i++) { float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli index 6d7e816fd2..e343600e12 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -61,9 +61,12 @@ return; } + // pow(base, k) == exp2(k * log2(base)); base is loop-invariant here, so hoist its + // log2 out of the 6-tap loop (FXC keeps it inside the pow intrinsic otherwise). + float logHeightBlend = log2(max(abs(heightBlend), 0.0001)); [loop] for (int hbIdx = 0; hbIdx < 6; hbIdx++) { - weights[hbIdx] *= pow(max(abs(heightBlend), 0.0001), HEIGHT_MULT * heights[hbIdx]); + weights[hbIdx] *= exp2((HEIGHT_MULT * heights[hbIdx]) * logHeightBlend); } [loop] for (int j = 0; j < 6; j++) @@ -109,7 +112,7 @@ #define EM_PBR_DISP_LAYER_QUAD(N, TILEFLAG, TEX, WGT) \ [branch] if ((PBRFlags & (TILEFLAG)) != 0 && (WGT) > 0.01) \ { \ - [unroll] for (uint k = 0; k < 4; k++) \ + [loop] for (uint k = 0; k < 4; k++) \ h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(TEX, uvs[k], mipLevels[N], sharedOffset).x, params[N]); \ } @@ -135,7 +138,9 @@ return total; } - // Ray-march coarse step: one branch tree per layer; inner [unroll] fans out four UVs (same IR as manual unroll). + // Ray-march coarse step: one branch tree per layer; inner [loop] fans out four UVs. Kept as a runtime + // [loop] (not [unroll]) so FXC compiles one sampler body per layer instead of four -> large compile-time + // cut for the TERRAIN_VARIATION dual-tap path. Result is value-identical (independent per-UV iterations). float4 GetTerrainHeightQuadRayMarch(float screenNoise, PS_INPUT input, float2 u0, float2 u1, float2 u2, float2 u3, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, @@ -145,8 +150,8 @@ float heightBlend = 1 + blendFactor * HEIGHT_POWER; float2 uvs[4] = { u0, u1, u2, u3 }; float h4[4][6]; - [unroll] for (uint qi = 0; qi < 4; qi++) - [unroll] for (uint lj = 0; lj < 6; lj++) + [loop] for (uint qi = 0; qi < 4; qi++) + [loop] for (uint lj = 0; lj < 6; lj++) h4[qi][lj] = 0; EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_QUAD) @@ -186,12 +191,12 @@ if ((WGT) > 0.01) { \ [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0) \ { \ - [unroll] for (uint k = 0; k < 4; k++) \ + [loop] for (uint k = 0; k < 4; k++) \ h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, uvs[k], mipLevels[N], sharedOffset).x, params[N]); \ } \ else \ { \ - [unroll] for (uint k = 0; k < 4; k++) \ + [loop] for (uint k = 0; k < 4; k++) \ h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, uvs[k], mipLevels[N], sharedOffset).w, params[N]); \ } \ } @@ -199,12 +204,12 @@ #define EM_LEGACY_LAYER345_QUAD(N, THFLAG, THSAMPLER, COLSAMPLER, WPRIMARY, WELSE) \ [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0 && (WPRIMARY) > 0.01) \ { \ - [unroll] for (uint k = 0; k < 4; k++) \ + [loop] for (uint k = 0; k < 4; k++) \ h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, uvs[k], mipLevels[N], sharedOffset).x, params[N]); \ } \ else if ((WELSE) > 0.01) \ { \ - [unroll] for (uint k = 0; k < 4; k++) \ + [loop] for (uint k = 0; k < 4; k++) \ h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, uvs[k], mipLevels[N], sharedOffset).w, params[N]); \ } @@ -227,7 +232,8 @@ return total; } - // Legacy TH/color paths: same branching as GetTerrainHeight; fan out four UVs per branch. + // Legacy TH/color paths: same branching as GetTerrainHeight; [loop] fans out four UVs per branch + // (runtime loop keeps FXC from inlining four sampler copies per layer). float4 GetTerrainHeightQuadRayMarch(float screenNoise, PS_INPUT input, float2 u0, float2 u1, float2 u2, float2 u3, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, @@ -237,8 +243,8 @@ float heightBlend = 1 + blendFactor * HEIGHT_POWER; float2 uvs[4] = { u0, u1, u2, u3 }; float h4[4][6]; - [unroll] for (uint qi = 0; qi < 4; qi++) - [unroll] for (uint lj = 0; lj < 6; lj++) + [loop] for (uint qi = 0; qi < 4; qi++) + [loop] for (uint lj = 0; lj < 6; lj++) h4[qi][lj] = 0; EM_LEGACY_LAYER012_QUAD(0, Permutation::ExtraFeatureFlags::THLand0HasDisplacement, TexLandTHDisp0Sampler, TexColorSampler, w1.x) @@ -314,8 +320,6 @@ if (quality > 0.0) { uint tapCount = ParallaxShadowTapCount(quality); float shadowStrength = ShadowIntensity * (4.0 / tapCount); - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh = sh0.xxxx; float heights[6] = { 0, 0, 0, 0, 0, 0 }; float2 rayDir = L.xy * 0.1; float shadowScaleInv = 1.0; @@ -327,14 +331,19 @@ rayDir *= scale; shadowScaleInv = rcp(scale); # endif - sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevel, quality, heights); - if (quality > 0.25) - sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevel, quality, heights); - if (quality > 0.5) - sh.z = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.z, mipLevel, quality, heights); - if (quality > 0.75) - sh.w = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.w, mipLevel, quality, heights); - return 1.0 - saturate(dot(max(0, sh - sh0) * shadowScaleInv, shadowStrength)); + // Accumulate per-tap occlusion in a scalar. A float4 is NOT a natively addressable + // l-value, so sh[i] = ... force-unrolls the [loop] (X3531) — the opposite of the goal. + // multipliers[i] == rcp((i+1)+noise); tapCount already matches the q>0.25/0.5/0.75 + // ladder, so the same taps run. Sum matches the old + // dot(max(0, sh-sh0)*shadowScaleInv, shadowStrength) up to FP associativity. + // FXC now inlines ONE GetTerrainHeight body instead of up to four. + float shadowAccum = 0.0; + [loop] for (uint i = 0; i < tapCount; i++) + { + float shi = TERRAIN_HEIGHT_AT(coords + rayDir * rcp((float)(i + 1) + noise), mipLevel, quality, heights); + shadowAccum += max(0, shi - sh0) * shadowScaleInv; + } + return 1.0 - saturate(shadowAccum * shadowStrength); } return 1.0; } @@ -348,8 +357,6 @@ if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) return 1.0; - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh = sh0.xxxx; float heights[6] = { 0, 0, 0, 0, 0, 0 }; float2 rayDir = lightDirection.xy * 0.1; float shadowScaleInv = 1.0; @@ -362,11 +369,16 @@ shadowScaleInv = rcp(scale); # endif - sh.x = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.x, mipLevels, quality, heights); - if (tapCount > 1) - sh.y = TERRAIN_HEIGHT_AT(coords + rayDir * multipliers.y, mipLevels, quality, heights); + // Scalar accumulate: float4 sh[i] l-value would force-unroll the [loop] (X3531). + // multipliers[i] == rcp((i+1)+noise); one inlined GetTerrainHeight body instead of two. + float shadowAccum = 0.0; + [loop] for (uint i = 0; i < tapCount; i++) + { + float shi = TERRAIN_HEIGHT_AT(coords + rayDir * rcp((float)(i + 1) + noise), mipLevels, quality, heights); + shadowAccum += max(0, shi - sh0) * shadowScaleInv; + } - return 1.0 - saturate(dot(max(0, sh - sh0) * shadowScaleInv, shadowStrength)); + return 1.0 - saturate(shadowAccum * shadowStrength); } float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) From 92665357c1551dc5e867a190160c7a16586dd1ee Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 1 Jun 2026 20:48:12 +1000 Subject: [PATCH 26/29] Update ExtendedMaterialsParallaxCore.hlsli --- .../ExtendedMaterialsParallaxCore.hlsli | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli index 3ec1d5af89..386cd29db3 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -86,15 +86,18 @@ // Keep it extremely cheap: use TS normal (0,0,1) so N·V is just Vz after renormalization. const float baseMaxSteps = 8; const uint minSteps = 4; - const uint maxStepsCap = 32; + const uint maxStepsCap = 64; // headroom for grazing; only true grazing rays reach it float invViewLen = rsqrt(max(dot(viewDirTS, viewDirTS), 1e-6)); float ndotv = saturate(viewDirTS.z * invViewLen); // 1 = looking "down", 0 = grazing - float grazing = 1.0 - ndotv; - float grazing2 = grazing * grazing; // bias towards keeping steps low until fairly grazing - // Straight down: ~0.5x (-> typically clamps to 4). Grazing: up to ~2x. - float angleStepMul = lerp(0.5, 2.0, grazing2); + // A grazing ray's path length through the height slab grows as 1/cos = 1/ndotv, so the + // step count must too or the per-step UV stride overshoots a texel and the march steps + // OVER thin/tall features (stair-step / swim / flattening artifacts). The old quadratic + // curve capped the boost at 2x (~16 steps at grazing) which badly under-sampled it. + // 0.5x looking straight down -> up to 8x near the horizon; clamp at ndotv 0.0625 (~86.4°) + // bounds cost. baseMaxSteps(8) * 8 == maxStepsCap(64), so grazing rays fill the budget. + float angleStepMul = clamp(0.5 * rcp(max(ndotv, 0.0625)), 0.5, 8.0); // Distance LOD: parallax displacement is sub-texel once the surface is minified, so // ramp step + secant counts down with mip. Bit-identical near camera (mip <= 1 -> scale 1). From 0331f04cf6f81266d8483e68b8fd344d07654262 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Thu, 4 Jun 2026 20:44:25 +1000 Subject: [PATCH 27/29] Update en.json --- package/SKSE/Plugins/CommunityShaders/Translations/en.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index 4e6dc83057..841fd9a5cc 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -624,8 +624,6 @@ "feature.extended_materials.enable_parallax_warping_fix_tooltip": "Enables a fix reducing parallax scale on curved and smooth normal triangles.", "feature.extended_materials.enable_shadows": "Enable Shadows", "feature.extended_materials.enable_shadows_tooltip": "Enables cheap soft shadows when using parallax. This applies to all directional and point lights. ", - "feature.extended_materials.extend_shadows": "Extend Shadows", - "feature.extended_materials.extend_shadows_tooltip": "Extends parallax shadows beyond the range of parallax. Small performance impact.", "feature.extended_materials.key_feature_1": "Parallax occlusion mapping for depth", "feature.extended_materials.key_feature_2": "Complex material blending", "feature.extended_materials.key_feature_3": "Terrain heightmap support", @@ -1266,8 +1264,6 @@ "feature.terrain_variation.apply_to_lod_terrain": "Apply to LOD Terrain", "feature.terrain_variation.apply_to_lod_terrain_tooltip": "Applies the tiling fix to LOD terrain objects.\nThis helps reduce the visible tiling effect on distant terrain.", "feature.terrain_variation.description": "Terrain Variation reduces the repeating pattern effect on terrain textures.\nThis technique creates more natural-looking terrain by adding variation to texture sampling.", - "feature.terrain_variation.enable_tiling_fix": "Enable Terrain Tiling Fix", - "feature.terrain_variation.enable_tiling_fix_tooltip": "Reduces the repeating pattern effect on terrain textures.\nThis technique creates more natural-looking terrain by adding variation to texture sampling.", "feature.terrain_variation.key_feature_1": "Reduces terrain texture tiling", "feature.terrain_variation.key_feature_2": "Adjustable distance-based blending", "feature.terrain_variation.key_feature_3": "Improved terrain visual quality", From cc1615b6ccac165f760c686598391e71b1b13aad Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 8 Jun 2026 20:28:07 +1000 Subject: [PATCH 28/29] fix: cn translation & localisation merge --- .../CommunityShaders/Translations/zh_CN.json | 4 ---- src/Features/TerrainVariation.cpp | 18 +++++++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json index a5ce43434a..326c5b733d 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json @@ -620,8 +620,6 @@ "feature.extended_materials.enable_parallax_warping_fix_tooltip": "启用修复,减少弯曲和平滑法线三角形上的视差缩放。", "feature.extended_materials.enable_shadows": "启用阴影", "feature.extended_materials.enable_shadows_tooltip": "使用视差时启用廉价软阴影。适用于所有方向光和点光源。", - "feature.extended_materials.extend_shadows": "扩展阴影", - "feature.extended_materials.extend_shadows_tooltip": "将视差阴影扩展到视差范围之外。对性能影响较小。", "feature.extended_materials.key_feature_1": "视差遮蔽映射,增加深度感", "feature.extended_materials.key_feature_2": "复杂材质混合", "feature.extended_materials.key_feature_3": "地形高度图支持", @@ -1254,8 +1252,6 @@ "feature.terrain_variation.apply_to_lod_terrain": "应用到 LOD 地形", "feature.terrain_variation.apply_to_lod_terrain_tooltip": "将该平铺修复应用到 LOD 地形对象。\n这有助于减少远处地形上可见的重复平铺效果。", "feature.terrain_variation.description": "减少地形纹理的重复图案效果。\n通过为纹理采样添加变化来创造更自然的地形外观。", - "feature.terrain_variation.enable_tiling_fix": "启用地形平铺修复", - "feature.terrain_variation.enable_tiling_fix_tooltip": "减少地形纹理的重复平铺感。\n该技术通过在纹理采样中加入变化,让地形看起来更自然。", "feature.terrain_variation.key_feature_1": "减少地形纹理的平铺感", "feature.terrain_variation.key_feature_2": "可调节的基于距离的混合", "feature.terrain_variation.key_feature_3": "提升地形视觉质量", diff --git a/src/Features/TerrainVariation.cpp b/src/Features/TerrainVariation.cpp index 28ddf6b549..653a0c4507 100644 --- a/src/Features/TerrainVariation.cpp +++ b/src/Features/TerrainVariation.cpp @@ -1,8 +1,7 @@ #include "TerrainVariation.h" -#include "../FeatureBuffer.h" -#include "../Globals.h" -#include "../I18n/I18n.h" -#include "../State.h" +#include "I18n/I18n.h" +#include "Menu.h" +#include "Menu/Fonts.h" #include "../Util.h" #define I18N_KEY_PREFIX "feature.terrain_variation." @@ -21,11 +20,10 @@ void TerrainVariation::DrawSettings() ImGui::Spacing(); - bool oldLODEnabled = settings.enableLODTerrainTilingFix; - ImGui::Checkbox(T(TKEY("apply_to_lod_terrain"), "Apply to LOD Terrain"), (bool*)&settings.enableLODTerrainTilingFix); - if (oldLODEnabled != (bool)settings.enableLODTerrainTilingFix) { - UpdateShaderSettings(); - logger::info("TerrainVariation LOD setting changed to: {}", settings.enableLODTerrainTilingFix); + bool lodTilingFix = settings.enableLODTerrainTilingFix != 0; + if (ImGui::Checkbox(T(TKEY("apply_to_lod_terrain"), "Apply to LOD Terrain"), &lodTilingFix)) { + settings.enableLODTerrainTilingFix = lodTilingFix ? 1u : 0u; + logger::info("TerrainVariation LOD setting changed to: {}", settings.enableLODTerrainTilingFix != 0); } if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("%s", T(TKEY("apply_to_lod_terrain_tooltip"), @@ -33,6 +31,8 @@ void TerrainVariation::DrawSettings() } } +#undef I18N_KEY_PREFIX + void TerrainVariation::PostPostLoad() { logger::info("TerrainVariation: Feature initialized"); From 4b091ab735cbac0dccebdccf549eeb1d37211dbb Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 8 Jun 2026 20:28:30 +1000 Subject: [PATCH 29/29] fix: steps at grazing angles, higher quality --- .../ExtendedMaterialsParallaxCore.hlsli | 85 +++++++++++++------ .../ExtendedMaterialsTerrain.hlsli | 29 +++++-- .../TerrainVariation/TerrainVariation.hlsli | 25 ++---- 3 files changed, 92 insertions(+), 47 deletions(-) diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli index 386cd29db3..8933a7e257 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -25,10 +25,18 @@ hasPOM = false; #endif float3 viewDirTS = normalize(mul(tbn, viewDir)); + float invViewLen = rsqrt(max(dot(viewDirTS, viewDirTS), 1e-6)); + float ndotv = saturate(viewDirTS.z * invViewLen); // 1 = looking "down", 0 = grazing + + // UV stride along the height slab. Meshes keep the flatten hack for warping/swim reduction; + // terrain must use xy/z or grazing rays barely move in UV while depth bounds advance + // (step count cannot fix that — features get stepped over regardless). #if defined(LANDSCAPE) - viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params[0].FlattenAmount; // Fix for objects at extreme viewing angles + float parallaxZ = max(abs(viewDirTS.z), 0.0625); + float2 parallaxDir = viewDirTS.xy / parallaxZ; #else viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles + float2 parallaxDir = viewDirTS.xy; #endif #if defined(LANDSCAPE) @@ -82,27 +90,16 @@ #endif { - // Step count scales with height scale *and* viewing angle. - // Keep it extremely cheap: use TS normal (0,0,1) so N·V is just Vz after renormalization. const float baseMaxSteps = 8; const uint minSteps = 4; - const uint maxStepsCap = 64; // headroom for grazing; only true grazing rays reach it - - float invViewLen = rsqrt(max(dot(viewDirTS, viewDirTS), 1e-6)); - float ndotv = saturate(viewDirTS.z * invViewLen); // 1 = looking "down", 0 = grazing +#if defined(LANDSCAPE) + const uint maxStepsCap = 128; +#else + const uint maxStepsCap = 64; +#endif - // A grazing ray's path length through the height slab grows as 1/cos = 1/ndotv, so the - // step count must too or the per-step UV stride overshoots a texel and the march steps - // OVER thin/tall features (stair-step / swim / flattening artifacts). The old quadratic - // curve capped the boost at 2x (~16 steps at grazing) which badly under-sampled it. - // 0.5x looking straight down -> up to 8x near the horizon; clamp at ndotv 0.0625 (~86.4°) - // bounds cost. baseMaxSteps(8) * 8 == maxStepsCap(64), so grazing rays fill the budget. float angleStepMul = clamp(0.5 * rcp(max(ndotv, 0.0625)), 0.5, 8.0); - // Distance LOD: parallax displacement is sub-texel once the surface is minified, so - // ramp step + secant counts down with mip. Bit-identical near camera (mip <= 1 -> scale 1). - // Ramp only (no hard switch) keeps the transition pop-free; runtime [loop]s so no extra - // compiled code / FXC cost. #if defined(LANDSCAPE) float parallaxLODMip = mipLevels[0]; #else @@ -110,17 +107,31 @@ #endif float distStepScale = lerp(0.35, 1.0, saturate((4.0 - parallaxLODMip) * (1.0 / 3.0))); +#if defined(LANDSCAPE) + // Size the march from UV span in texels — the only reliable metric once xy/z is restored. + float2 texDim; + TexColorSampler.GetDimensions(texDim.x, texDim.y); + float uvMarchSpan = dot(abs(parallaxDir), maxHeight + minHeight); + float texelsPerStep = lerp(4.0, 1.5, saturate((0.4 - ndotv) * (1.0 / 0.4))); + uint numSteps = max(minSteps, (uint)(uvMarchSpan * max(texDim.x, texDim.y) * rcp(texelsPerStep) * distStepScale + 0.5)); + numSteps = max(numSteps, (uint)(scale * baseMaxSteps * angleStepMul * distStepScale)); + numSteps = min(numSteps, maxStepsCap); + // 4-wide coarse stride skips narrow peaks; single-step near camera + grazing only. + bool useDenseMarch = (ndotv < 0.45) && (distStepScale > 0.8); + if (!useDenseMarch) + numSteps = (numSteps + 2) & ~3; +#else uint numSteps = max(minSteps, (uint)(scale * baseMaxSteps * angleStepMul * distStepScale)); numSteps = min(numSteps, maxStepsCap); numSteps = (numSteps + 2) & ~3; +#endif - // 5 secant iterations near camera, down to ~3 at distance (matches step ramp). uint secantIters = (uint)(lerp(2.0, 5.0, distStepScale) + 0.5); - float stepSize = rcp(numSteps); + float stepSize = rcp((float)numSteps); - float2 offsetPerStep = viewDirTS.xy * float2(maxHeight, maxHeight) * stepSize.xx; - float2 prevOffset = viewDirTS.xy * float2(minHeight, minHeight) + coords.xy; + float2 offsetPerStep = parallaxDir * maxHeight * stepSize; + float2 prevOffset = parallaxDir * minHeight + coords.xy; float prevBound = 1.0; float prevHeight = 1.0; @@ -129,6 +140,33 @@ float2 pt2 = 0; bool intersectionFound = false; +#if defined(LANDSCAPE) + if (useDenseMarch) + { + [loop] while (numSteps > 0) + { + float2 currentOffset = prevOffset - offsetPerStep; + float currentBound = prevBound - stepSize; + + float currHeight = GetTerrainHeight(noise, input, currentOffset, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; + + [branch] if (currHeight >= currentBound) + { + intersectionFound = true; + pt1 = float2(currentBound, currHeight); + pt2 = float2(prevBound, prevHeight); + prevOffset = currentOffset; + break; + } + + prevOffset = currentOffset; + prevBound = currentBound; + prevHeight = currHeight; + numSteps--; + } + } + else +#endif [loop] while (numSteps > 0) { float4 currentOffset[2]; @@ -152,7 +190,6 @@ [branch] if (any(testResult)) { intersectionFound = true; - // Priority matches former [flatten] chain: x overwrites y overwrites z overwrites w. float2 outOffset; [branch] if (testResult.x) { @@ -205,7 +242,7 @@ float denominator = fNear - fFar; float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; float tSecant = lerp(tNear, tFar, r); - float2 secantCoords = coords.xy + viewDirTS.xy * (((1.0 - tSecant) * -maxHeight) + minHeight); + float2 secantCoords = coords.xy + parallaxDir * (((1.0 - tSecant) * -maxHeight) + minHeight); float hSecant = 0.0; #if defined(LANDSCAPE) @@ -240,7 +277,7 @@ #if defined(VR_STEREO_OPT) hasPOM = true; #endif - return viewDirTS.xy * offset + coords.xy; + return parallaxDir * offset + coords.xy; } } diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli index e343600e12..da8bef90ef 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -31,6 +31,16 @@ # define HEIGHT_MULT 8 // [loop] on fixed-6: less compile/optimizer blow-up than [unroll] here (tiny runtime cost). + float TerrainWeightedHeightSum(float heights[6], float weights[6]) + { + float totalHeight = 0; + [loop] for (int i = 0; i < 6; i++) + { + totalHeight += heights[i] * weights[i]; + } + return totalHeight; + } + void ProcessTerrainHeightWeights(float heightBlend, float4 w1, float2 w2, float heights[6], inout float weights[6], out float totalHeight) { weights[0] = w1.x; @@ -40,13 +50,7 @@ weights[4] = w2.x; weights[5] = w2.y; - totalHeight = 0; - [loop] for (int i = 0; i < 6; i++) - { - totalHeight += heights[i] * weights[i]; - } - - if (heightBlend <= 1.0) { + [branch] if (heightBlend <= 1.0) { float wsum = 0; [loop] for (int j = 0; j < 6; j++) { @@ -54,9 +58,11 @@ } float invwsum = rcp(wsum); + totalHeight = 0; [loop] for (int k = 0; k < 6; k++) { weights[k] *= invwsum; + totalHeight += heights[k] * weights[k]; } return; } @@ -81,9 +87,11 @@ } float invwsum = rcp(wsum); + totalHeight = 0; [loop] for (int l = 0; l < 6; l++) { weights[l] *= invwsum; + totalHeight += heights[l] * weights[l]; } } @@ -91,6 +99,13 @@ float4 FinishTerrainHeightQuadBlend(float heightBlend, float4 w1, float2 w2, float qh0[6], float qh1[6], float qh2[6], float qh3[6], out float weights[6]) { + // heightBlend <= 1: weights depend only on w1/w2 (not per-tap heights) — one normalize + four dots. + [branch] if (heightBlend <= 1.0) { + float t3 = 0.0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh3, weights, t3); + return float4(TerrainWeightedHeightSum(qh0, weights), TerrainWeightedHeightSum(qh1, weights), TerrainWeightedHeightSum(qh2, weights), t3); + } + float wTmp[6]; float t0 = 0.0, t1 = 0.0, t2 = 0.0, t3 = 0.0; ProcessTerrainHeightWeights(heightBlend, w1, w2, qh0, wTmp, t0); diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 15605dda7b..30d99c1d0f 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -117,18 +117,17 @@ inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) { - [branch] if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) - return ZeroStochasticOffsets(); + float lodOn = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; float2 cellID = floor(landscapeUV * 255437.0); float2 h1 = hashLOD(cellID); float2 h2 = hashLOD(cellID + 127.0); StochasticOffsets o; - o.offset1 = h1 * 0.08; - o.offset2 = h2 * 0.08; + o.offset1 = h1 * 0.08 * lodOn; + o.offset2 = h2 * 0.08 * lodOn; o.offset3 = 0; - o.weights = float3(0.65, 0.35, 0.0); + o.weights = float3(0.65, 0.35, 0.0) * lodOn; return o; } @@ -176,15 +175,13 @@ inline float4 StochasticBlendTwoSamples(float mipLevel, float4 s1, float4 s2, fl // LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) { - // Feature off: a single plain fetch (offsets are zero anyway) instead of two identical reads + lerp. - [branch] if (!SharedData::terrainVariationSettings.enableLODTerrainTilingFix) - return tex.SampleBias(samp, uv, SharedData::MipBias); - + float lodOn = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; float2 j1 = (offsetsLOD.offset1 + jitter) * 0.01; float2 j2 = (offsetsLOD.offset2 + float2(jitter.y, -jitter.x)) * 0.01; - float4 s1 = tex.SampleBias(samp, uv + j1, SharedData::MipBias); - float4 s2 = tex.SampleBias(samp, uv + j2, SharedData::MipBias); - return lerp(s2, s1, offsetsLOD.weights.x); + float4 s1 = tex.SampleBias(samp, uv + j1 * lodOn, SharedData::MipBias); + float4 s2 = tex.SampleBias(samp, uv + j2 * lodOn, SharedData::MipBias); + float blendW = lerp(0.5, offsetsLOD.weights.x, lodOn); + return lerp(s2, s1, blendW); } // 2-sample height-blended stochastic sampling. Uses one shared gradient (SampleGrad) for both taps so @@ -206,10 +203,6 @@ inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, Stoc float secondSampleFade = saturate((TV_SINGLE_SAMPLE_MIP_START - mipLevel) * TV_SINGLE_SAMPLE_FADE_RCP); float4 s1 = tex.SampleGrad(samp, uv + offsets.offset1, dUVdx, dUVdy); - // Far/minified: second tap has fully faded out -> one anisotropic sample (still offset, still breaks tiling). - [branch] if (secondSampleFade <= 0.0) - return s1; - float4 s2 = tex.SampleGrad(samp, uv + offsets.offset2, dUVdx, dUVdy); float h1 = lerp(dot(s1.rgb, LUMINANCE_WEIGHTS), s1.a, step(0.001, s1.a));