diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index b8a4e3ccea..74c9f8ba11 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -5,10 +5,6 @@ // 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 - struct DisplacementParams { float DisplacementScale; @@ -17,6 +13,10 @@ struct DisplacementParams float FlattenAmount; }; +#if defined(LANDSCAPE) +# include "Common/LandscapeLayers.hlsli" +#endif + namespace ExtendedMaterials { static const float ShadowIntensity = 2.0; @@ -36,291 +36,56 @@ 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) + // Shared by GetMipLevel and landscape parallax: one ddx/ddy(uv) per pixel when all mips are computed together. + // (ddx/ddy of uv × dims matches ddx/ddy of uv*dims when dims are uniform per draw.) + inline float GetMipLevelForTextureDims(float2 textureDims, float2 duvx, float2 duvy, float screenNoise) { - // Compute the current gradients: - float2 textureDims; - tex.GetDimensions(textureDims.x, textureDims.y); + float2 dims = textureDims; #if !defined(PARALLAX) && !defined(TRUE_PBR) - textureDims /= 2.0; + dims /= 2.0; #endif #if defined(VR) - textureDims /= 2.0; + dims /= 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 dxSize = duvx * dims; + float2 dySize = duvy * dims; - // 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 - // Stochastic mip selection: use screen noise to select between adjacent mip levels + mipLevel = max(mipLevel + SharedData::MipBias, 0.0); + mipLevel = floor(mipLevel) + (screenNoise < frac(mipLevel) ? 1.0 : 0.0); return mipLevel; } -#if defined(LANDSCAPE) -# 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) + inline float GetMipLevel(float2 coords, Texture2D tex, float screenNoise) { - 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]; - 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, float2 dx, float2 dy, -# 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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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); -# 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; - } -# 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, -# 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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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, dx, dy).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); -# 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; + float2 textureDims; + tex.GetDimensions(textureDims.x, textureDims.y); + return GetMipLevelForTextureDims(textureDims, ddx(coords), ddy(coords), screenNoise); } -# endif +#if defined(LANDSCAPE) +# include "ExtendedMaterials/ExtendedMaterialsLandscape.hlsli" #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], -# if defined(TERRAIN_VARIATION) - StochasticOffsets sharedOffset, float2 dx, float2 dy, -# endif - out float pixelOffset, out float weights[6]) + StochasticOffsets sharedOffset, out float pixelOffset, out uint activeMask, 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) #endif @@ -333,43 +98,92 @@ 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); - #if defined(LANDSCAPE) -# if defined(TRUE_PBR) - 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) 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; + float scalercp = 1; # endif + float maxHeight = 0.1 * scale; + activeMask = ComputeActiveMask(w1, w2); + // Highest parallax mip among contributing layers: large footprint (typically distance / grazing) → fewer POM steps. + float aggParallaxMip = 0.0; + [unroll] for (int emMipLi = 0; emMipLi < 6; ++emMipLi) + { + if (activeMask & (1u << emMipLi)) + aggParallaxMip = max(aggParallaxMip, mipLevels[emMipLi]); + } + float maxStepsF = 16.0; + maxStepsF = lerp(maxStepsF, 8.0, step(1.45, aggParallaxMip)); + maxStepsF = lerp(maxStepsF, 4.0, step(2.55, aggParallaxMip)); + float distLin = abs(distance); + // Distance POM: push toward minimum step count from ~0.65k (full) to several km (mostly 4-step march). + float pomFar = saturate((distLin - 650.0) / 5200.0); + maxStepsF = lerp(maxStepsF, 4.0, pomFar); + // Extra high-minification squeeze (continuous, branchless): beyond ~2.75 mip, converge rapidly to 4 steps. + float pomMinified = saturate((aggParallaxMip - 2.75) / 0.75); + maxStepsF = lerp(maxStepsF, 4.0, pomMinified); + // Very far / highly minified: allow 2-step POM to keep height while heavily trading quality for cost. + float pomUltraFar = saturate((distLin - 3600.0) / 2600.0); + float pomUltraMinified = saturate((aggParallaxMip - 3.3) / 0.9); + float pomTwoStep = max(pomUltraFar, pomUltraMinified); + maxStepsF = lerp(maxStepsF, 2.0, pomTwoStep); + maxStepsF = max(maxStepsF, 2.0); #else float scale = params.HeightScale; float maxHeight = 0.1 * scale; #endif float minHeight = maxHeight * 0.5; + { #if defined(LANDSCAPE) - if (nearBlendToFar < 1.0) { + uint numSteps = uint(maxStepsF + 0.5); + numSteps = clamp(numSteps, 2, max(2, uint(scale * maxStepsF + 0.5))); #else -# if defined(TRUE_PBR) - if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0 || nearBlendToFar < 1.0) -# else - if (nearBlendToFar < 1.0) -# endif - { -#endif const float maxSteps = 16; - uint numSteps = uint((maxSteps * (1.0 - nearBlendToFar)) + 0.5); + uint numSteps = uint(maxSteps + 0.5); numSteps = clamp(numSteps, 4, max(4, uint(scale * maxSteps))); +#endif +#if defined(LANDSCAPE) + [branch] if (numSteps <= 2) + { + const float stepSize2 = 0.5; + float2 offsetPerStep2 = viewDirTS.xy * float2(maxHeight, maxHeight) * stepSize2.xx; + float2 prevOffset2 = viewDirTS.xy * float2(minHeight, minHeight) + coords.xy; + float2 sample1Offset = prevOffset2 - offsetPerStep2; + float2 sample2Offset = sample1Offset - offsetPerStep2; + + float h1 = GetTerrainHeightShadowTap(sample1Offset, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; + float h2 = GetTerrainHeightShadowTap(sample2Offset, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; + + float2 pt1 = float2(0.0, h2); + float2 pt2 = float2(0.5, h1); + if (h1 >= 0.5) + { + pt1 = float2(0.5, h1); + pt2 = float2(1.0, 1.0); + } + + 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 = (pt1.x * delta2 - pt2.x * delta1) / denominator; + + float offset2 = (1.0 - parallaxAmount) * -maxHeight + minHeight; + pixelOffset = saturate(parallaxAmount); + float2 outCoords2 = viewDirTS.xy * offset2 + coords.xy; + if (SharedData::extendedMaterialSettings.EnableHeightBlending) + GetTerrainHeight(noise, input, outCoords2, mipLevels, params, blendFactor, w1, w2, activeMask, sharedOffset, weights); + return outCoords2; + } +#endif numSteps = (numSteps + 2) & ~3; float stepSize = rcp(numSteps); @@ -395,31 +209,38 @@ namespace ExtendedMaterials 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, 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; -# 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 + // max(layer SampleLevel) >= linear blended height (same scalar as GetTerrainHeightShadowTap / GetTerrainHeight total). + // With TV tiling fix on, stochastic parallax can exceed SampleLevel — coarse gate disabled (useParallaxCoarseGate). +# if defined(TERRAIN_VARIATION) + bool useParallaxCoarseGate = !SharedData::terrainVariationSettings.enableTilingFix; # 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; -# 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 + bool useParallaxCoarseGate = true; # endif + // When already minified, the 4× upper-bound prepass (many SampleLevels) often costs more than four shadow taps. + useParallaxCoarseGate = useParallaxCoarseGate && (aggParallaxMip < 2.75); + [branch] if (useParallaxCoarseGate) + { + float4 heightUpper = float4( + GetTerrainHeightUpperBoundNonStochastic(currentOffset[0].xy, mipLevels, params, activeMask), + GetTerrainHeightUpperBoundNonStochastic(currentOffset[0].zw, mipLevels, params, activeMask), + GetTerrainHeightUpperBoundNonStochastic(currentOffset[1].xy, mipLevels, params, activeMask), + GetTerrainHeightUpperBoundNonStochastic(currentOffset[1].zw, mipLevels, params, activeMask)); + float4 upperScaled = heightUpper * scalercp + 0.5; + bool4 coarseMayHit = upperScaled >= currentBound; + [branch] if (!any(coarseMayHit)) + { + currHeight.w = GetTerrainHeightShadowTap(currentOffset[1].zw, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; + prevOffset = currentOffset[1].zw; + prevBound = currentBound.w; + prevHeight = currHeight.w; + numSteps -= 4; + continue; + } + } + currHeight.x = GetTerrainHeightShadowTap(currentOffset[0].xy, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; + currHeight.y = GetTerrainHeightShadowTap(currentOffset[0].zw, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; + currHeight.z = GetTerrainHeightShadowTap(currentOffset[1].xy, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; + currHeight.w = GetTerrainHeightShadowTap(currentOffset[1].zw, mipLevels, params, w1, w2, activeMask, sharedOffset) * scalercp + 0.5; #else currHeight.x = tex.SampleLevel(texSampler, currentOffset[0].xy, mipLevel)[channel]; currHeight.y = tex.SampleLevel(texSampler, currentOffset[0].zw, mipLevel)[channel]; @@ -457,6 +278,20 @@ namespace ExtendedMaterials pt1 = float2(currentBound.x, currHeight.x); pt2 = float2(prevBound, prevHeight); } +#if defined(LANDSCAPE) + // One short refinement phase reduces residual stair stepping/stretching on terrain parallax hits. + if (contactRefinement) { + break; + } else { + contactRefinement = true; + prevOffset = outOffset; + prevBound = pt2.x; + numSteps = 4; + stepSize *= 0.25; + offsetPerStep *= 0.25; + continue; + } +#else if (contactRefinement) { break; } else { @@ -468,6 +303,7 @@ namespace ExtendedMaterials offsetPerStep /= (float)numSteps; continue; } +#endif } prevOffset = currentOffset[1].zw; @@ -490,28 +326,16 @@ namespace ExtendedMaterials parallaxAmount = (pt1.x * delta2 - pt2.x * delta1) / denominator; } -#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)); - return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar); - } - + pixelOffset = saturate(parallaxAmount); + float2 outCoords = 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; + // ProcessTerrainHeightWeights only when height blending is enabled; otherwise weights are unused. + if (SharedData::extendedMaterialSettings.EnableHeightBlending) + GetTerrainHeight(noise, input, outCoords, mipLevels, params, blendFactor, w1, w2, activeMask, sharedOffset, weights); #endif - - pixelOffset = 0.5; - return coords; + return outCoords; + } } // https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf @@ -522,7 +346,7 @@ namespace ExtendedMaterials { float2 rayDir = L.xy * 0.1 * params.HeightScale; float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh; + float4 sh = 0; sh = 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); @@ -535,67 +359,4 @@ namespace ExtendedMaterials return 1.0; } -#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) -# 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) { - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh; - float heights[6] = { 0, 0, 0, 0, 0, 0 }; - 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))))); - 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, dx, dy, 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); - 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); - 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); -# 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 - 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); - 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); - 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); - 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); -# 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 - return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * quality; -# endif - } - return 1.0; - } - -#endif // defined(LANDSCAPE) && defined(TERRAIN_VARIATION) } diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsLandscape.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsLandscape.hlsli new file mode 100644 index 0000000000..b01892e48f --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsLandscape.hlsli @@ -0,0 +1,275 @@ +// Landscape-only Extended Materials helpers. Included only when LANDSCAPE is defined (see ExtendedMaterials.hlsli) +// so non-terrain EMAT permutations skip lexing/parsing this entire file. + +#define HEIGHT_POWER 2 +#define HEIGHT_MULT 8 + +// Stochastic blending averages multiple offset samples, flattening the height range. +// These compensate for that energy loss in parallax height and shadow contrast. +static const float STOCHASTIC_HEIGHT_BOOST = 1.3; +static const float STOCHASTIC_SHADOW_GAMMA = 0.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; + + float logHB = log2(heightBlend) * HEIGHT_MULT; + float wsum = 0; + totalHeight = 0; + + [unroll] for (int i = 0; i < 6; i++) + { + totalHeight += heights[i] * weights[i]; + weights[i] *= exp2(logHB * heights[i]); + weights[i] = min(100.0, exp2(log2(abs(weights[i])) * heightBlend)); + wsum += weights[i]; + } + + float invwsum = rcp(wsum); + [unroll] for (int j = 0; j < 6; j++) + { + weights[j] *= invwsum; + } +} + +inline float4 SampleHeightUnified(Texture2D tex, SamplerState samp, float2 coords, float mipLevel, StochasticOffsets offsets) +{ +#if defined(TERRAIN_VARIATION) + if (!SharedData::terrainVariationSettings.enableTilingFix) + return tex.SampleLevel(samp, coords, mipLevel); + return StochasticEffectParallax(tex, samp, coords, mipLevel, offsets); +#else + return tex.SampleLevel(samp, coords, mipLevel); +#endif +} + +inline uint ComputeActiveMask(float4 w1, float2 w2) +{ + uint mask = 0; + mask |= (w1.x > 0.01) ? 1u : 0u; + mask |= (w1.y > 0.01) ? 2u : 0u; + mask |= (w1.z > 0.01) ? 4u : 0u; + mask |= (w1.w > 0.01) ? 8u : 0u; + mask |= (w2.x > 0.01) ? 16u : 0u; + mask |= (w2.y > 0.01) ? 32u : 0u; + return mask; +} + +// Parallax height mips: per-layer GetDimensions (displacement vs diffuse sizes can differ per tile). +// Shared mip from tile 0 only was faster but broke soft shadows when layer mips did not match. +void ComputeLandscapeParallaxMipLevels(float2 uv, float screenNoise, out float mipLevels[6]) +{ + float2 duvx = ddx(uv); + float2 duvy = ddy(uv); + float2 dims; + float representativeMip; + +#if defined(TRUE_PBR) + if (LandscapeLayers::PbrTileHasDisplacement(0)) + TexLandDisplacement0Sampler.GetDimensions(dims.x, dims.y); + else + TexColorSampler.GetDimensions(dims.x, dims.y); +#else + if (LandscapeLayers::ThTileHasDisplacement(0)) + TexLandTHDisp0Sampler.GetDimensions(dims.x, dims.y); + else + TexColorSampler.GetDimensions(dims.x, dims.y); +#endif + representativeMip = GetMipLevelForTextureDims(dims, duvx, duvy, screenNoise); + + // Far/minified landscape: shared mip avoids per-layer GetDimensions + mip math. + // This keeps full-distance parallax height while accepting extra blur/artifacts in the far field. + [branch] if (representativeMip >= 3.1) + { + mipLevels[0] = representativeMip; + mipLevels[1] = representativeMip; + mipLevels[2] = representativeMip; + mipLevels[3] = representativeMip; + mipLevels[4] = representativeMip; + mipLevels[5] = representativeMip; + return; + } + +#if defined(TRUE_PBR) +# define EMAT_LAND_PBR_MIPDIMS_FOREACH(i, DISPTEX, COLTEX) \ + if (LandscapeLayers::PbrTileHasDisplacement(i)) { \ + DISPTEX.GetDimensions(dims.x, dims.y); \ + mipLevels[i] = GetMipLevelForTextureDims(dims, duvx, duvy, screenNoise); \ + } else { \ + COLTEX.GetDimensions(dims.x, dims.y); \ + mipLevels[i] = GetMipLevelForTextureDims(dims, duvx, duvy, screenNoise); \ + } + LANDSCAPE_PBR_LAYER_FOREACH(EMAT_LAND_PBR_MIPDIMS_FOREACH) +# undef EMAT_LAND_PBR_MIPDIMS_FOREACH +#else +# define EMAT_LAND_TH_MIPDIMS_FOREACH(i, THDISP, COLTEX) \ + if (LandscapeLayers::ThTileHasDisplacement(i)) { \ + THDISP.GetDimensions(dims.x, dims.y); \ + mipLevels[i] = GetMipLevelForTextureDims(dims, duvx, duvy, screenNoise); \ + } else { \ + COLTEX.GetDimensions(dims.x, dims.y); \ + mipLevels[i] = GetMipLevelForTextureDims(dims, duvx, duvy, screenNoise); \ + } + LANDSCAPE_TH_LAYER_FOREACH(EMAT_LAND_TH_MIPDIMS_FOREACH) +# undef EMAT_LAND_TH_MIPDIMS_FOREACH +#endif +} + +// Single SampleLevel per active layer (no stochastic). max(layer) upper-bounds linear blend height. +void SampleTerrainLayerHeightsNonStochastic(float2 coords, float mipLevels[6], DisplacementParams params[6], uint activeMask, out float heights[6]) +{ + heights[0] = heights[1] = heights[2] = heights[3] = heights[4] = heights[5] = 0; + +#if defined(TRUE_PBR) +# define EMAT_LAND_PBR_HEIGHT_NS_FOREACH(i, DISPTEX, COLTEX) \ + [branch] if ((activeMask & (1u << i)) && LandscapeLayers::PbrTileHasDisplacement(i)) \ + heights[i] = ScaleDisplacement(DISPTEX.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[i]).x, params[i]); + LANDSCAPE_PBR_LAYER_FOREACH(EMAT_LAND_PBR_HEIGHT_NS_FOREACH) +# undef EMAT_LAND_PBR_HEIGHT_NS_FOREACH +#else +# define EMAT_LAND_TH_HEIGHT_NS_FOREACH(i, THDISP, COLTEX) \ + if (activeMask & (1u << i)) { \ + [branch] if (LandscapeLayers::ThTileHasDisplacement(i)) \ + heights[i] = ScaleDisplacement(THDISP.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[i]).x, params[i]); \ + else heights[i] = ScaleDisplacement(COLTEX.SampleLevel(SampTerrainParallaxSampler, coords, mipLevels[i]).w, params[i]); \ + } + LANDSCAPE_TH_LAYER_FOREACH(EMAT_LAND_TH_HEIGHT_NS_FOREACH) +# undef EMAT_LAND_TH_HEIGHT_NS_FOREACH +#endif +} + +inline float GetTerrainHeightUpperBoundNonStochastic(float2 coords, float mipLevels[6], DisplacementParams params[6], uint activeMask) +{ + float heights[6]; + SampleTerrainLayerHeightsNonStochastic(coords, mipLevels, params, activeMask, heights); + float m = 0; + [unroll] for (int li = 0; li < 6; ++li) + { + if (activeMask & (1u << li)) + m = max(m, heights[li]); + } + return m; +} + +void SampleTerrainLayerHeights(float2 coords, float mipLevels[6], DisplacementParams params[6], uint activeMask, StochasticOffsets sharedOffset, out float heights[6]) +{ + heights[0] = heights[1] = heights[2] = heights[3] = heights[4] = heights[5] = 0; + +#if defined(TRUE_PBR) +# define EMAT_LAND_PBR_HEIGHT_S_FOREACH(i, DISPTEX, COLTEX) \ + [branch] if ((activeMask & (1u << i)) && LandscapeLayers::PbrTileHasDisplacement(i)) \ + heights[i] = ScaleDisplacement(SampleHeightUnified(DISPTEX, SampTerrainParallaxSampler, coords, mipLevels[i], sharedOffset).x, params[i]); + LANDSCAPE_PBR_LAYER_FOREACH(EMAT_LAND_PBR_HEIGHT_S_FOREACH) +# undef EMAT_LAND_PBR_HEIGHT_S_FOREACH +#else +# define EMAT_LAND_TH_HEIGHT_S_FOREACH(i, THDISP, COLTEX) \ + if (activeMask & (1u << i)) { \ + [branch] if (LandscapeLayers::ThTileHasDisplacement(i)) \ + heights[i] = ScaleDisplacement(SampleHeightUnified(THDISP, SampTerrainParallaxSampler, coords, mipLevels[i], sharedOffset).x, params[i]); \ + else heights[i] = ScaleDisplacement(SampleHeightUnified(COLTEX, SampTerrainParallaxSampler, coords, mipLevels[i], sharedOffset).w, params[i]); \ + } + LANDSCAPE_TH_LAYER_FOREACH(EMAT_LAND_TH_HEIGHT_S_FOREACH) +# undef EMAT_LAND_TH_HEIGHT_S_FOREACH +#endif +} + +// Linear blend of layer heights × boost (same scalar GetTerrainHeight returns before nonlinear weight remap). +// Used for soft shadows and POM ray height; ProcessTerrainHeightWeights only affects out-weights, not this total. +float GetTerrainHeightShadowTap(float2 coords, float mipLevels[6], DisplacementParams params[6], float4 w1, float2 w2, uint activeMask, StochasticOffsets sharedOffset) +{ + float heights[6]; + SampleTerrainLayerHeights(coords, mipLevels, params, activeMask, sharedOffset, heights); + + if (countbits(activeMask) == 1) { + uint layerIdx = firstbitlow(activeMask); + float total = heights[layerIdx]; +#if defined(TERRAIN_VARIATION) + if (SharedData::terrainVariationSettings.enableTilingFix) + total *= STOCHASTIC_HEIGHT_BOOST; +#endif + return total; + } + + float total = w1.x * heights[0] + w1.y * heights[1] + w1.z * heights[2] + w1.w * heights[3] + w2.x * heights[4] + w2.y * heights[5]; +#if defined(TERRAIN_VARIATION) + if (SharedData::terrainVariationSettings.enableTilingFix) + total *= STOCHASTIC_HEIGHT_BOOST; +#endif + return total; +} + +float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + uint activeMask, StochasticOffsets sharedOffset, out float weights[6]) +{ + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float heights[6]; + SampleTerrainLayerHeights(coords, mipLevels, params, activeMask, sharedOffset, heights); + + //Single active layer fast path, skips expensive weight processing + if (countbits(activeMask) == 1) { + uint layerIdx = firstbitlow(activeMask); + weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0; + weights[layerIdx] = 1.0; + float total = heights[layerIdx]; +#if defined(TERRAIN_VARIATION) + if (SharedData::terrainVariationSettings.enableTilingFix) + total *= STOCHASTIC_HEIGHT_BOOST; +#endif + return total; + } + + float total = 0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); +#if defined(TERRAIN_VARIATION) + if (SharedData::terrainVariationSettings.enableTilingFix) + total *= STOCHASTIC_HEIGHT_BOOST; +#endif + return total; +} + +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) { + float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); + float4 sh = 0; + float2 rayDir = L.xy * 0.1; + uint activeMask = ComputeActiveMask(input.LandBlendWeights1, input.LandBlendWeights2.xy); + +#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; + float scaleRcp = rcp(scale); +#else + float scaleRcp = 1; +#endif + + // Shadow only needs the same scalar as GetTerrainHeight return; ProcessTerrainHeightWeights is redundant (totalHeight is linear in w1/w2 before nonlinear remap). + sh.x = GetTerrainHeightShadowTap(coords + rayDir * multipliers.x, mipLevel, params, input.LandBlendWeights1, input.LandBlendWeights2.xy, activeMask, sharedOffset); + if (quality > 0.25) + sh.y = GetTerrainHeightShadowTap(coords + rayDir * multipliers.y, mipLevel, params, input.LandBlendWeights1, input.LandBlendWeights2.xy, activeMask, sharedOffset); + if (quality > 0.5) + sh.z = GetTerrainHeightShadowTap(coords + rayDir * multipliers.z, mipLevel, params, input.LandBlendWeights1, input.LandBlendWeights2.xy, activeMask, sharedOffset); + if (quality > 0.75) + sh.w = GetTerrainHeightShadowTap(coords + rayDir * multipliers.w, mipLevel, params, input.LandBlendWeights1, input.LandBlendWeights2.xy, activeMask, sharedOffset); + +#if defined(TERRAIN_VARIATION) + if (SharedData::terrainVariationSettings.enableTilingFix) { + float shadowIntensity = saturate(dot(max(0, sh - sh0), 1.0)) * quality; + shadowIntensity = exp2(log2(shadowIntensity) * STOCHASTIC_SHADOW_GAMMA); + float invShadow = 1.0 - shadowIntensity; + return invShadow * invShadow; + } +#endif + float shadowParallaxTerm = 1.0 - saturate(dot(max(0, sh - sh0) * scaleRcp, 1.0)) * quality; + return shadowParallaxTerm * shadowParallaxTerm; + } + return 1.0; +} diff --git a/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini b/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini index 7c4d5a2a34..1f0c0722b9 100644 --- a/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini +++ b/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini @@ -1,2 +1,2 @@ [Info] -Version = 1-2-0 \ No newline at end of file +Version = 2-0-0 \ No newline at end of file diff --git a/features/Terrain Variation/Shaders/Features/TerrainVariation.ini b/features/Terrain Variation/Shaders/Features/TerrainVariation.ini index 735cfd23a9..1f0c0722b9 100644 --- a/features/Terrain Variation/Shaders/Features/TerrainVariation.ini +++ b/features/Terrain Variation/Shaders/Features/TerrainVariation.ini @@ -1,2 +1,2 @@ [Info] -Version = 1-0-1 \ No newline at end of file +Version = 2-0-0 \ No newline at end of file diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 1a34cd0df6..b87ae5a709 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,157 @@ 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); - // 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); + 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 barycentric blend without height influence - float3 weights = NormalizeWeights(saturate(offsets.weights)); - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; + w1 *= (1.0 + heightInfluence * s1.a); + w2 *= (1.0 + heightInfluence * s2.a); + + 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/LandscapeLayers.hlsli b/package/Shaders/Common/LandscapeLayers.hlsli new file mode 100644 index 0000000000..3eb9a5f708 --- /dev/null +++ b/package/Shaders/Common/LandscapeLayers.hlsli @@ -0,0 +1,123 @@ +#ifndef __LANDSCAPE_LAYERS_HLSLI__ +#define __LANDSCAPE_LAYERS_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 (legacy). +// --------------------------------------------------------------------------- +# if defined(TRUE_PBR) +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(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_LEGACY_SNOW_ACCUM(SNOW_COMPONENT) \ + landSnowMask += (SNOW_COMPONENT) * weight * GetLandSnowMaskValue(landColor.w); +# else +# define LIGHTING_LAND_LEGACY_SNOW_ACCUM(SNOW_COMPONENT) +# endif +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(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_LEGACY_SNOW_ACCUM(SNOW_COMPONENT) \ + } +# endif + +#endif // LANDSCAPE + +#endif // __LANDSCAPE_LAYERS_HLSLI__ diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index ed40111591..ec5de01264 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -179,13 +179,6 @@ namespace SharedData uint3 pad; }; - struct TerrainVariationSettings - { - uint enableTilingFix; - uint enableLODTerrainTilingFix; - float2 pad0; - }; - struct IBLSettings { uint EnableIBL; @@ -263,6 +256,13 @@ namespace SharedData float3 pad; }; + struct TerrainVariationSettings + { + uint enableTilingFix; + uint enableLODTerrainTilingFix; + uint2 pad; + }; + cbuffer FeatureData : register(b6) { GrassLightingSettings grassLightingSettings; @@ -275,12 +275,12 @@ namespace SharedData CloudShadowsSettings cloudShadowsSettings; LODBlendingSettings lodBlendingSettings; HairSpecularSettings hairSpecularSettings; - TerrainVariationSettings terrainVariationSettings; IBLSettings iblSettings; ExtendedTranslucencySettings extendedTranslucencySettings; LinearLightingSettings linearLightingSettings; TerrainBlendingSettings terrainBlendingSettings; ExponentialHeightFogSettings exponentialHeightFogSettings; + TerrainVariationSettings terrainVariationSettings; }; Texture2D DepthTexture : register(t17); diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 28e51b59ec..5f276e095a 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -878,10 +878,43 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "Common/PBR.hlsli" # endif +# if defined(TERRAIN_VARIATION) && (defined(LANDSCAPE) || defined(LODLANDSCAPE) || defined(LOD_LAND_BLEND)) +# include "TerrainVariation/TerrainVariation.hlsli" +# elif defined(LANDSCAPE) || defined(LODLANDSCAPE) || defined(LOD_LAND_BLEND) +# if !defined(TERRAIN_VARIATION_HLSLI) +struct StochasticOffsets +{ + float2 offset1; + float2 offset2; + float3 weights; +}; +inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) { return (StochasticOffsets)0; } +inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) { return (StochasticOffsets)0; } +inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets) { return tex.SampleBias(samp, uv, SharedData::MipBias); } +inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { return tex.SampleLevel(samp, uv, mipLevel); } +inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float extraLandMipBias) +{ + return tex.SampleBias(samp, uv, SharedData::MipBias + extraLandMipBias); +} +# endif +# endif + +# if defined(LANDSCAPE) +# include "Common/LandscapeLayers.hlsli" +# endif + # if defined(EMAT) # include "ExtendedMaterials/ExtendedMaterials.hlsli" # endif +# if defined(LANDSCAPE) && defined(EMAT) +# if defined(TRUE_PBR) +# define LAND_EMAT_PARALLAX_ACTIVE (SharedData::extendedMaterialSettings.EnableParallax) +# else +# define LAND_EMAT_PARALLAX_ACTIVE (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement))) +# endif +# endif + # if defined(SCREEN_SPACE_SHADOWS) # include "ScreenSpaceShadows/ScreenSpaceShadows.hlsli" # endif @@ -918,10 +951,6 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "Hair/Hair.hlsli" # endif -# if defined(TERRAIN_VARIATION) -# include "TerrainVariation/TerrainVariation.hlsli" -# endif - # if defined(EXTENDED_TRANSLUCENCY) && !(defined(LOD) || defined(SKIN) || defined(HAIR) || defined(EYE) || defined(TREE_ANIM) || defined(LODOBJECTSHD) || defined(LODOBJECTS) || defined(DEPTH_WRITE_DECALS)) # include "ExtendedTranslucency/ExtendedTranslucency.hlsli" # define ANISOTROPIC_ALPHA @@ -949,9 +978,23 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WorldPosition.xyz, 1)).xyz; float3 viewDirection = -normalize(input.WorldPosition.xyz); +# if defined(LANDSCAPE) + // Softer landscape albedo/normals at distance (SampleBias); parallax mips get a separate bump in EMAT path. + float landDistanceTexMipBias = saturate((abs(viewPosition.z) - 380.0) / 3400.0) * 0.72; + float landParallaxTexMipBias = 0.0; +# endif + float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); float screenNoise = Random::InterleavedGradientNoise(input.Position.xy, SharedData::FrameCount); +# if defined(LOD_BLENDING) || defined(LOD_LAND_BLEND) +# if defined(TERRAIN_VARIATION) && (defined(LANDSCAPE) || defined(LODLANDSCAPE) || defined(LOD_LAND_BLEND)) + float2 lodStochasticJitter = StochasticSampleLODJitter(screenNoise); +# else + float2 lodStochasticJitter = (float2)0; +# endif +# endif + # if defined(DEFERRED) const bool inWorld = true; # else @@ -959,7 +1002,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif const bool inReflection = Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InReflection; - float nearFactor = smoothstep(4096.0 * 2.5, 0.0, viewPosition.z); + float nearFactor = 1.0; # if defined(SKINNED) || !defined(MODELSPACENORMALS) float3x3 tbn = float3x3(input.TBN0.xyz, input.TBN1.xyz, input.TBN2.xyz); @@ -1009,12 +1052,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float2 uv = input.TexCoord0.xy; float2 uvOriginal = uv; -# if defined(EMAT) - float parallaxShadowQuality = sqrt(1.0 - saturate(viewPosition.z / 2048.0)); -# endif - # if defined(LANDSCAPE) float mipLevels[6]; +# if defined(EMAT) + float landParallaxShadowQuality = 1.0; +# endif # else float mipLevel = 0; # endif // LANDSCAPE @@ -1077,7 +1119,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) 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); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + if (SharedData::extendedMaterialSettings.EnableShadows) sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x; } # endif // PARALLAX @@ -1105,7 +1147,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) complexMaterialParallax = true; mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, screenNoise); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + if (SharedData::extendedMaterialSettings.EnableShadows) sh0 = TexEnvMaskSampler.SampleLevel(SampEnvMaskSampler, uv, mipLevel).w; complexMaterialColor = TexEnvMaskSampler.Sample(SampEnvMaskSampler, uv); } else { @@ -1152,7 +1194,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + if (SharedData::extendedMaterialSettings.EnableShadows) sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x; } # endif // !FACEGEN @@ -1185,48 +1227,36 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float totalWeight = input.LandBlendWeights1.x + input.LandBlendWeights1.y + input.LandBlendWeights1.z + input.LandBlendWeights1.w + input.LandBlendWeights2.x + input.LandBlendWeights2.y; if (totalWeight > 0.0) { - input.LandBlendWeights1 /= totalWeight; - input.LandBlendWeights2.xy /= totalWeight; + float invTotalWeight = rcp(totalWeight); + input.LandBlendWeights1 *= invTotalWeight; + input.LandBlendWeights2.xy *= invTotalWeight; } float3 blendedRGB = 0; float blendedAlpha = 0; float3 blendedNormalRGB = 0; float blendedNormalAlpha = 0; + float4 landscapeBlendWeights1 = input.LandBlendWeights1; + float2 landscapeBlendWeights2 = input.LandBlendWeights2.xy; # if defined(TRUE_PBR) float4 blendedRMAOS = 0; # endif - // Compute stochastic offsets and derivatives once for all layers (only when terrain variation is enabled) + // Stochastic offsets shared across terrain layer samples; skip compute when TV tiling fix is disabled. + StochasticOffsets sharedOffset = (StochasticOffsets)0; # 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); + if (SharedData::terrainVariationSettings.enableTilingFix) sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); - } # 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 - 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 (LAND_EMAT_PARALLAX_ACTIVE) { + ExtendedMaterials::ComputeLandscapeParallaxMipLevels(uv, screenNoise, mipLevels); + float landParallaxMipAgg = max(max(max(max(max(mipLevels[0], mipLevels[1]), mipLevels[2]), mipLevels[3]), mipLevels[4]), mipLevels[5]); + float vzdParallax = abs(viewPosition.z); + landParallaxShadowQuality = saturate((1.0 - saturate((vzdParallax - 850.0) / 4600.0) * 0.94) * saturate(1.58 - 0.41 * landParallaxMipAgg)); + // Keep shadows always present, but push distant terrain into very low quality (mostly single-tap path). + landParallaxShadowQuality = max(0.06, landParallaxShadowQuality * landParallaxShadowQuality); displacementParams[1] = displacementParams[0]; displacementParams[2] = displacementParams[0]; @@ -1243,13 +1273,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif 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, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, dx, dy, pixelOffset, weights); -# else - uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset, weights); -# endif + uint landParallaxActiveMask; + uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, landParallaxActiveMask, weights); if (SharedData::extendedMaterialSettings.EnableHeightBlending) { input.LandBlendWeights1.x = weights[0]; input.LandBlendWeights1.y = weights[1]; @@ -1258,31 +1284,47 @@ 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 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); -# else - sh0 = ExtendedMaterials::GetTerrainHeight(screenNoise, input, uv, mipLevels, displacementParams, parallaxShadowQuality, input.LandBlendWeights1, input.LandBlendWeights2.xy, weights); -# endif + float landParallaxShadowEvalQuality = SharedData::extendedMaterialSettings.ExtendShadows ? 1.0 : landParallaxShadowQuality; + if (SharedData::extendedMaterialSettings.EnableShadows) { + // Reuse POM layer mask unless height blending rewrote weights (then mask must match new weights). + uint sh0ActiveMask = SharedData::extendedMaterialSettings.EnableHeightBlending ? ExtendedMaterials::ComputeActiveMask(input.LandBlendWeights1, input.LandBlendWeights2.xy) : landParallaxActiveMask; + // For very low quality, use cheaper non-stochastic upper-bound baseline; keeps shadows "on" while reducing cost. + [branch] if (landParallaxShadowEvalQuality < 0.26) + sh0 = ExtendedMaterials::GetTerrainHeightUpperBoundNonStochastic(uv, mipLevels, displacementParams, sh0ActiveMask); + else + sh0 = ExtendedMaterials::GetTerrainHeightShadowTap(uv, mipLevels, displacementParams, input.LandBlendWeights1, input.LandBlendWeights2.xy, sh0ActiveMask, sharedOffset); } + + // Stronger parallax displaces UVs more and can look artificially over-sharp versus flatter materials. + // Apply a small extra SampleBias so terrain mip response stays consistent across parallax scales. + float landParallaxWeightedScale = max(displacementParams[0].HeightScale * input.LandBlendWeights1.x, + max(displacementParams[1].HeightScale * input.LandBlendWeights1.y, + max(displacementParams[2].HeightScale * input.LandBlendWeights1.z, + max(displacementParams[3].HeightScale * input.LandBlendWeights1.w, + max(displacementParams[4].HeightScale * input.LandBlendWeights2.x, + displacementParams[5].HeightScale * input.LandBlendWeights2.y))))); + landParallaxTexMipBias = saturate((landParallaxWeightedScale - 0.05) / 0.25) * 0.35; } -# if defined(TERRAIN_VARIATION) - else if (useTerrainVariation) { - // 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); - } -# endif + # else // Initialize mip levels for non-EMAT case mipLevels[0] = mipLevels[1] = mipLevels[2] = mipLevels[3] = mipLevels[4] = mipLevels[5] = 0.0; # endif // EMAT -# endif // LANDSCAPE + landDistanceTexMipBias += landParallaxTexMipBias; + landscapeBlendWeights1 = input.LandBlendWeights1; + landscapeBlendWeights2 = input.LandBlendWeights2.xy; + // Cull tiny layers when minified/distant and renormalize branchlessly to preserve total energy. + float landTinyWeightCutoff = lerp(0.012, 0.05, saturate(landDistanceTexMipBias * 1.35)); + float4 culledWeights1 = landscapeBlendWeights1 * step(landTinyWeightCutoff.xxxx, landscapeBlendWeights1); + float2 culledWeights2 = landscapeBlendWeights2 * step(landTinyWeightCutoff.xx, landscapeBlendWeights2); + float culledTotalWeight = culledWeights1.x + culledWeights1.y + culledWeights1.z + culledWeights1.w + culledWeights2.x + culledWeights2.y; + float useCulledWeights = step(1e-4, culledTotalWeight); + float invCulledTotalWeight = rcp(max(culledTotalWeight, 1e-4)); + float4 renormCulledWeights1 = culledWeights1 * invCulledTotalWeight; + float2 renormCulledWeights2 = culledWeights2 * invCulledTotalWeight; + landscapeBlendWeights1 = lerp(landscapeBlendWeights1, renormCulledWeights1, useCulledWeights); + landscapeBlendWeights2 = lerp(landscapeBlendWeights2, renormCulledWeights2, useCulledWeights); +# endif // LANDSCAPE # if defined(SPARKLE) diffuseUv = ProjectedUVParams2.yy * (input.TexCoord0.zw + (uv - uvOriginal)); @@ -1308,488 +1350,21 @@ 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); -# 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(0u, TexColorSampler, SampColorSampler, TexNormalSampler, SampNormalSampler, TexRMAOSSampler, SampRMAOSSampler, PBRParams1, LandscapeTexture1GlintParameters, landscapeBlendWeights1.x); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(1u, TexLandColor2Sampler, SampLandColor2Sampler, TexLandNormal2Sampler, SampLandNormal2Sampler, TexLandRMAOS2Sampler, SampLandRMAOS2Sampler, LandscapeTexture2PBRParams, LandscapeTexture2GlintParameters, landscapeBlendWeights1.y); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(2u, TexLandColor3Sampler, SampLandColor3Sampler, TexLandNormal3Sampler, SampLandNormal3Sampler, TexLandRMAOS3Sampler, SampLandRMAOS3Sampler, LandscapeTexture3PBRParams, LandscapeTexture3GlintParameters, landscapeBlendWeights1.z); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(3u, TexLandColor4Sampler, SampLandColor4Sampler, TexLandNormal4Sampler, SampLandNormal4Sampler, TexLandRMAOS4Sampler, SampLandRMAOS4Sampler, LandscapeTexture4PBRParams, LandscapeTexture4GlintParameters, landscapeBlendWeights1.w); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(4u, TexLandColor5Sampler, SampLandColor5Sampler, TexLandNormal5Sampler, SampLandNormal5Sampler, TexLandRMAOS5Sampler, SampLandRMAOS5Sampler, LandscapeTexture5PBRParams, LandscapeTexture5GlintParameters, landscapeBlendWeights2.x); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(5u, TexLandColor6Sampler, SampLandColor6Sampler, TexLandNormal6Sampler, SampLandNormal6Sampler, TexLandRMAOS6Sampler, SampLandRMAOS6Sampler, LandscapeTexture6PBRParams, LandscapeTexture6GlintParameters, landscapeBlendWeights2.y); # else - float4 landColor6 = TexLandColor6Sampler.SampleBias(SampLandColor6Sampler, uv, SharedData::MipBias); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(TexColorSampler, SampColorSampler, TexNormalSampler, SampNormalSampler, landscapeBlendWeights1.x, LandscapeTexture1to4IsSnow.x); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(TexLandColor2Sampler, SampLandColor2Sampler, TexLandNormal2Sampler, SampLandNormal2Sampler, landscapeBlendWeights1.y, LandscapeTexture1to4IsSnow.y); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(TexLandColor3Sampler, SampLandColor3Sampler, TexLandNormal3Sampler, SampLandNormal3Sampler, landscapeBlendWeights1.z, LandscapeTexture1to4IsSnow.z); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(TexLandColor4Sampler, SampLandColor4Sampler, TexLandNormal4Sampler, SampLandNormal4Sampler, landscapeBlendWeights1.w, LandscapeTexture1to4IsSnow.w); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(TexLandColor5Sampler, SampLandColor5Sampler, TexLandNormal5Sampler, SampLandNormal5Sampler, landscapeBlendWeights2.x, LandscapeTexture5to6IsSnow.x); + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_LEGACY(TexLandColor6Sampler, SampLandColor6Sampler, TexLandNormal6Sampler, SampLandNormal6Sampler, landscapeBlendWeights2.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); - } -# 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); @@ -1814,18 +1389,9 @@ 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 -# 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); - } -# endif + StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); + float4 lodStochasticColor = StochasticSampleLOD(lodStochasticJitter, TexColorSampler, SampColorSampler, uv, lodOffset); + baseColor.xyz = Color::Diffuse(lodStochasticColor.rgb); baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODTerrainGamma) * SharedData::lodBlendingSettings.LODTerrainBrightness; # endif # endif // LOD_BLENDING @@ -1919,20 +1485,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(LOD_LAND_BLEND) 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); - } -# else - lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); -# endif + float2 blendColorUV = input.TexCoord0.zw; + StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); + lodLandColor = StochasticSampleLOD(lodStochasticJitter, TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); lodLandColor.xyz = Color::ColorToLinear(lodLandColor.xyz) * Color::VanillaDiffuseColorMult(); # if defined(LOD_BLENDING) @@ -2467,33 +2022,18 @@ 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) { -# else - if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) { -# endif -# 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); -# else - // Standard terrain parallax shadow without stochastic sampling - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, dirLightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); -# endif + if (LAND_EMAT_PARALLAX_ACTIVE) { + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, dirLightDirectionTS, sh0, SharedData::extendedMaterialSettings.ExtendShadows ? 1.0 : landParallaxShadowQuality, screenNoise, displacementParams, sharedOffset); } # 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, 1.0, 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, 1.0, 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, 1.0, screenNoise, displacementParams); # endif // LANDSCAPE } # endif // defined(EMAT) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) @@ -2678,24 +2218,16 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); # if defined(PARALLAX) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, 1.0, 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 defined(TERRAIN_VARIATION) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams, sharedOffset, dx, dy); -# else - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, parallaxShadowQuality, screenNoise, displacementParams); -# endif + if (LAND_EMAT_PARALLAX_ACTIVE) + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, mipLevels, lightDirectionTS, sh0, SharedData::extendedMaterialSettings.ExtendShadows ? 1.0 : landParallaxShadowQuality, screenNoise, displacementParams, sharedOffset); # elif defined(EMAT_ENVMAP) [branch] if (complexMaterialParallax) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, parallaxShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, 1.0, screenNoise, displacementParams); # elif defined(TRUE_PBR) && !defined(LODLANDSCAPE) && !defined(FACEGEN) [branch] if (PBRParallax) - parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, 1.0, screenNoise, displacementParams); # endif } # endif diff --git a/src/FeatureBuffer.cpp b/src/FeatureBuffer.cpp index 813fa75027..6e6367a219 100644 --- a/src/FeatureBuffer.cpp +++ b/src/FeatureBuffer.cpp @@ -48,10 +48,10 @@ std::pair GetFeatureBufferData(bool a_inWorld) globals::features::cloudShadows.settings, globals::features::lodBlending.settings, globals::features::hairSpecular.settings, - globals::features::terrainVariation.settings, globals::features::ibl.GetCommonBufferData(), globals::features::extendedTranslucency.GetCommonBufferData(), globals::features::linearLighting.GetCommonBufferData(), globals::features::terrainBlending.settings, - globals::features::exponentialHeightFog.settings); + globals::features::exponentialHeightFog.settings, + globals::features::terrainVariation.settings); } \ No newline at end of file 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..7fcf9385c7 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,15 @@ 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 +48,4 @@ struct TerrainVariation : Feature virtual void RestoreDefaultSettings() override; virtual void PostPostLoad() override; - void UpdateShaderSettings(); }; \ No newline at end of file