diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 07631d0038..2faf5bb6a9 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -1,13 +1,34 @@ // https://github.com/tgjones/slimshader-cpp/blob/master/src/Shaders/Sdk/Direct3D11/DetailTessellation11/POM.hlsl // https://github.com/alandtse/SSEShaderTools/blob/main/shaders_vr/ParallaxEffect.h - // https://github.com/marselas/Zombie-Direct3D-Samples/blob/5f53dc2d6f7deb32eb2e5e438d6b6644430fe9ee/Direct3D/ParallaxOcclusionMapping/ParallaxOcclusionMapping.fx // http://www.diva-portal.org/smash/get/diva2:831762/FULLTEXT01.pdf // https://bartwronski.files.wordpress.com/2014/03/ac4_gdc.pdf -#if defined(TERRAIN_VARIATION) && defined(LANDSCAPE) -# include "TerrainVariation/TerrainVariation.hlsli" -#endif +// Extended Materials: split for faster compiles on non-landscape Lighting permutations. +// Lighting.hlsl includes this header only when EMAT is defined; LANDSCAPE further gates terrain-only code. +// - Terrain helpers: ExtendedMaterialsTerrain.hlsli (EMAT implied; included only when LANDSCAPE) +// - Parallax core: ExtendedMaterialsParallaxCore.hlsli (GetParallaxCoords + mesh soft shadows) + +#ifndef EXTENDED_MATERIALS_HLSLI +#define EXTENDED_MATERIALS_HLSLI + +// Terrain variation: optional feature pack — include only when macro + headers ship together. +// When absent, stub `StochasticOffsets` so EMAT terrain APIs stay unified (offsets ignored on SampleLevel path). +# if defined(LANDSCAPE) +# if defined(TERRAIN_VARIATION) +# include "TerrainVariation/TerrainVariation.hlsli" +# else +struct StochasticOffsets +{ + float2 offset1; + float2 offset2; + float2 offset3; + float3 weights; + float w1Contrast; + float w2Contrast; +}; +# endif +# endif struct DisplacementParams { @@ -20,6 +41,22 @@ struct DisplacementParams namespace ExtendedMaterials { static const float ShadowIntensity = 2.0; + static const float ParallaxCheapDistance = 1024.0; + static const float ParallaxNearShadowQuality = 1.0; + static const float ParallaxFarShadowQuality = 0.76; + static const float TerrainParallaxShadowMaxMipLevel = 0.5; + + inline uint ParallaxShadowTapCount(float quality) + { + uint taps = 1; + if (quality > 0.25) + taps++; + if (quality > 0.5) + taps++; + if (quality > 0.75) + taps++; + return taps; + } float ScaleDisplacement(float displacement, DisplacementParams params) { @@ -36,558 +73,37 @@ namespace ExtendedMaterials return float4(AdjustDisplacementNormalized(displacement.x, params), AdjustDisplacementNormalized(displacement.y, params), AdjustDisplacementNormalized(displacement.z, params), AdjustDisplacementNormalized(displacement.w, params)); } - float GetMipLevel(float2 coords, Texture2D tex, float screenNoise) + float GetMipLevel(float2 coords, Texture2D tex) { - // Compute the current gradients: float2 textureDims; tex.GetDimensions(textureDims.x, textureDims.y); -#if !defined(PARALLAX) && !defined(TRUE_PBR) +# if !defined(PARALLAX) && !defined(TRUE_PBR) textureDims /= 2.0; -#endif +# endif 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; - - // 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) +# if !defined(PARALLAX) && !defined(TRUE_PBR) mipLevel++; -#endif - - // Stochastic mip selection: use screen noise to select between adjacent mip levels - 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) - { - 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; - } -# endif - -#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]) -#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 - { - pixelOffset = 0.0; - float3 viewDirTS = normalize(mul(tbn, viewDir)); -#if defined(LANDSCAPE) - viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params[0].FlattenAmount; // Fix for objects at extreme viewing angles -#else - viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles -#endif - - 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; - 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 = max(params[0].HeightScale * w1.x, max(params[1].HeightScale * w1.y, max(params[2].HeightScale * w1.z, max(params[3].HeightScale * w1.w, max(params[4].HeightScale * w2.x, params[5].HeightScale * w2.y))))); - float scalercp = rcp(scale); - float maxHeight = 0.1 * scale; -# else - float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? sqrt(saturate(1 - nearBlendToFar)) : 0; - float4 w1 = lerp(input.LandBlendWeights1, smoothstep(0, 1, input.LandBlendWeights1), blendFactor); - float2 w2 = lerp(input.LandBlendWeights2.xy, smoothstep(0, 1, input.LandBlendWeights2.xy), blendFactor); - float scale = 1; - float maxHeight = 0.1 * scale; -# endif -#else - float scale = params.HeightScale; - float maxHeight = 0.1 * scale; -#endif - float minHeight = maxHeight * 0.5; - -#if defined(LANDSCAPE) - if (nearBlendToFar < 1.0) { -#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); - numSteps = clamp(numSteps, 4, max(4, uint(scale * maxSteps))); - numSteps = (numSteps + 2) & ~3; - float stepSize = rcp(numSteps); - float2 offsetPerStep = viewDirTS.xy * float2(maxHeight, maxHeight) * stepSize.xx; - float2 prevOffset = viewDirTS.xy * float2(minHeight, minHeight) + coords.xy; - float prevBound = 1.0; - float prevHeight = 1.0; - - float2 pt1 = 0; - float2 pt2 = 0; - - uint numStepsTemp = numSteps; - bool contactRefinement = false; - - [loop] while (numSteps > 0) - { - float4 currentOffset[2]; - currentOffset[0] = prevOffset.xyxy - float4(1, 1, 2, 2) * offsetPerStep.xyxy; - currentOffset[1] = prevOffset.xyxy - float4(3, 3, 4, 4) * offsetPerStep.xyxy; - float4 currentBound = prevBound.xxxx - float4(1, 2, 3, 4) * stepSize; - - float4 currHeight; -#if defined(LANDSCAPE) -# if defined(TRUE_PBR) -# if defined(TERRAIN_VARIATION) - currHeight.x = GetTerrainHeight(noise, input, currentOffset[0].xy, mipLevels, params, blendFactor, w1, w2, sharedOffset, 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 -# 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 -# endif -#else - currHeight.x = tex.SampleLevel(texSampler, currentOffset[0].xy, mipLevel)[channel]; - currHeight.y = tex.SampleLevel(texSampler, currentOffset[0].zw, mipLevel)[channel]; - currHeight.z = tex.SampleLevel(texSampler, currentOffset[1].xy, mipLevel)[channel]; - currHeight.w = tex.SampleLevel(texSampler, currentOffset[1].zw, mipLevel)[channel]; - - currHeight = AdjustDisplacementNormalized(currHeight, params); -#endif - - bool4 testResult = currHeight >= currentBound; - [branch] if (any(testResult)) - { - float2 outOffset = 0; - [flatten] if (testResult.w) - { - outOffset = currentOffset[1].xy; - pt1 = float2(currentBound.w, currHeight.w); - pt2 = float2(currentBound.z, currHeight.z); - } - [flatten] if (testResult.z) - { - outOffset = currentOffset[0].zw; - pt1 = float2(currentBound.z, currHeight.z); - pt2 = float2(currentBound.y, currHeight.y); - } - [flatten] if (testResult.y) - { - outOffset = currentOffset[0].xy; - pt1 = float2(currentBound.y, currHeight.y); - pt2 = float2(currentBound.x, currHeight.x); - } - [flatten] if (testResult.x) - { - outOffset = prevOffset; - pt1 = float2(currentBound.x, currHeight.x); - pt2 = float2(prevBound, prevHeight); - } - if (contactRefinement) { - break; - } else { - contactRefinement = true; - prevOffset = outOffset; - prevBound = pt2.x; - numSteps = numStepsTemp; - stepSize /= (float)numSteps; - offsetPerStep /= (float)numSteps; - continue; - } - } - - prevOffset = currentOffset[1].zw; - prevBound = currentBound.w; - prevHeight = currHeight.w; - numSteps -= 4; - } - - float delta2 = pt2.x - pt2.y; - float delta1 = pt1.x - pt1.y; - float denominator = delta2 - delta1; - - float parallaxAmount = 0.0; - [flatten] if (denominator == 0.0) - { - parallaxAmount = 0.0; - } - else - { - 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); - } - -#if defined(LANDSCAPE) - weights[0] = input.LandBlendWeights1.x; - weights[1] = input.LandBlendWeights1.y; - weights[2] = input.LandBlendWeights1.z; - weights[3] = input.LandBlendWeights1.w; - weights[4] = input.LandBlendWeights2.x; - weights[5] = input.LandBlendWeights2.y; -#endif - - pixelOffset = 0.0; - return coords; + return floor(mipLevel); } - // https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf - // Cheap method of creating shadows using height for a given light source - float GetParallaxSoftShadowMultiplier(float2 coords, float mipLevel, float3 L, float sh0, Texture2D tex, SamplerState texSampler, uint channel, float quality, float noise, DisplacementParams params) - { - [branch] if (quality > 0.0) - { - float2 rayDir = L.xy * 0.1 * params.HeightScale; - float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); - float4 sh; - sh = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.x, mipLevel)[channel], params); - if (quality > 0.25) - sh.y = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.y, mipLevel)[channel], params); - if (quality > 0.5) - sh.z = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.z, mipLevel)[channel], params); - if (quality > 0.75) - sh.w = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.w, mipLevel)[channel], params); - return 1.0 - saturate(dot(max(0, sh - sh0), ShadowIntensity)) * quality; - } - return 1.0; - } - -#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]) +# if defined(LANDSCAPE) +# include "ExtendedMaterials/ExtendedMaterialsTerrain.hlsli" # 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) +# include "ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli" } + +#endif // EXTENDED_MATERIALS_HLSLI diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli new file mode 100644 index 0000000000..27cd55292c --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -0,0 +1,319 @@ +#ifndef EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI +#define EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI + +// Body included inside `namespace ExtendedMaterials` from ExtendedMaterials.hlsli. + +#if defined(LANDSCAPE) + float2 GetParallaxCoords(PS_INPUT input, float2 coords, float mipLevels[6], float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], + StochasticOffsets sharedOffset, + out float pixelOffset, +# if defined(VR_STEREO_OPT) + out bool hasPOM, +# endif + out float weights[6]) +#else + float2 GetParallaxCoords(float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset +# if defined(VR_STEREO_OPT) + , + out bool hasPOM +# endif + ) +#endif + { + pixelOffset = 0.0; +#if defined(VR_STEREO_OPT) + hasPOM = false; +#endif + float3 viewDirTS = normalize(mul(tbn, viewDir)); + float invViewLen = rsqrt(max(dot(viewDirTS, viewDirTS), 1e-6)); + float ndotv = saturate(viewDirTS.z * invViewLen); // 1 = looking "down", 0 = grazing + + // UV stride along the height slab. Meshes keep the flatten hack for warping/swim reduction; + // terrain must use xy/z or grazing rays barely move in UV while depth bounds advance + // (step count cannot fix that — features get stepped over regardless). +#if defined(LANDSCAPE) + float parallaxZ = max(abs(viewDirTS.z), 0.0625); + float2 parallaxDir = viewDirTS.xy / parallaxZ; +#else + viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params.FlattenAmount; // Fix for objects at extreme viewing angles + float2 parallaxDir = viewDirTS.xy; +#endif + +#if defined(LANDSCAPE) + // Ray-march displacement always uses vertex weights + linear height combine (blendFactor 0). + // Height-biased layer weights for albedo/normal are computed once at the hit UV when enabled. + float4 w1 = input.LandBlendWeights1; + float2 w2 = input.LandBlendWeights2.xy; + const float marchHeightBlendFactor = 0.0; +# if defined(TRUE_PBR) + float scale = max(params[0].HeightScale * w1.x, max(params[1].HeightScale * w1.y, max(params[2].HeightScale * w1.z, max(params[3].HeightScale * w1.w, max(params[4].HeightScale * w2.x, params[5].HeightScale * w2.y))))); + float scalercp = rcp(max(scale, 1e-4)); + float terrainHeightNormMul = scalercp; + float maxHeight = 0.1 * scale; +# else + float scale = 1; + float terrainHeightNormMul = 1.0; + float maxHeight = 0.1 * scale; +# endif +#else + float scale = params.HeightScale; + float maxHeight = 0.1 * scale; +#endif + float minHeight = maxHeight * 0.5; + +#if defined(LANDSCAPE) +# if defined(TRUE_PBR) + if (scale <= 0.001) { + weights[0] = input.LandBlendWeights1.x; + weights[1] = input.LandBlendWeights1.y; + weights[2] = input.LandBlendWeights1.z; + weights[3] = input.LandBlendWeights1.w; + weights[4] = input.LandBlendWeights2.x; + weights[5] = input.LandBlendWeights2.y; + pixelOffset = 0.0; + return coords; + } +# endif +#else + if (scale <= 0.001) { + pixelOffset = 0.0; + return coords; + } +#endif + +#if defined(LANDSCAPE) + // Default out weights for static analysis; ray-march / secant paths overwrite. + weights[0] = input.LandBlendWeights1.x; + weights[1] = input.LandBlendWeights1.y; + weights[2] = input.LandBlendWeights1.z; + weights[3] = input.LandBlendWeights1.w; + weights[4] = input.LandBlendWeights2.x; + weights[5] = input.LandBlendWeights2.y; +#endif + + { + const float baseMaxSteps = 8; + const uint minSteps = 4; +#if defined(LANDSCAPE) + const uint maxStepsCap = 128; +#else + const uint maxStepsCap = 64; +#endif + + float angleStepMul = clamp(0.5 * rcp(max(ndotv, 0.0625)), 0.5, 8.0); + +#if defined(LANDSCAPE) + float parallaxLODMip = mipLevels[0]; +#else + float parallaxLODMip = mipLevel; +#endif + float distStepScale = lerp(0.35, 1.0, saturate((4.0 - parallaxLODMip) * (1.0 / 3.0))); + +#if defined(LANDSCAPE) + // Size the march from UV span in texels — the only reliable metric once xy/z is restored. + float2 texDim; + TexColorSampler.GetDimensions(texDim.x, texDim.y); + float uvMarchSpan = dot(abs(parallaxDir), maxHeight + minHeight); + float texelsPerStep = lerp(4.0, 1.5, saturate((0.4 - ndotv) * (1.0 / 0.4))); + uint numSteps = max(minSteps, (uint)(uvMarchSpan * max(texDim.x, texDim.y) * rcp(texelsPerStep) * distStepScale + 0.5)); + numSteps = max(numSteps, (uint)(scale * baseMaxSteps * angleStepMul * distStepScale)); + numSteps = min(numSteps, maxStepsCap); + // 4-wide coarse stride skips narrow peaks; single-step near camera + grazing only. + bool useDenseMarch = (ndotv < 0.45) && (distStepScale > 0.8); + if (!useDenseMarch) + numSteps = (numSteps + 2) & ~3; +#else + uint numSteps = max(minSteps, (uint)(scale * baseMaxSteps * angleStepMul * distStepScale)); + numSteps = min(numSteps, maxStepsCap); + numSteps = (numSteps + 2) & ~3; +#endif + + uint secantIters = (uint)(lerp(2.0, 5.0, distStepScale) + 0.5); + + float stepSize = rcp((float)numSteps); + + float2 offsetPerStep = parallaxDir * maxHeight * stepSize; + float2 prevOffset = parallaxDir * minHeight + coords.xy; + + float prevBound = 1.0; + float prevHeight = 1.0; + + float2 pt1 = 0; + float2 pt2 = 0; + bool intersectionFound = false; + +#if defined(LANDSCAPE) + if (useDenseMarch) + { + [loop] while (numSteps > 0) + { + float2 currentOffset = prevOffset - offsetPerStep; + float currentBound = prevBound - stepSize; + + float currHeight = GetTerrainHeight(noise, input, currentOffset, mipLevels, params, marchHeightBlendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; + + [branch] if (currHeight >= currentBound) + { + intersectionFound = true; + pt1 = float2(currentBound, currHeight); + pt2 = float2(prevBound, prevHeight); + prevOffset = currentOffset; + break; + } + + prevOffset = currentOffset; + prevBound = currentBound; + prevHeight = currHeight; + numSteps--; + } + } + else +#endif + [loop] while (numSteps > 0) + { + float4 currentOffset[2]; + currentOffset[0] = prevOffset.xyxy - float4(1, 1, 2, 2) * offsetPerStep.xyxy; + currentOffset[1] = prevOffset.xyxy - float4(3, 3, 4, 4) * offsetPerStep.xyxy; + float4 currentBound = prevBound.xxxx - float4(1, 2, 3, 4) * stepSize; + + float4 currHeight; +#if defined(LANDSCAPE) + currHeight = GetTerrainHeightQuadRayMarch(noise, input, currentOffset[0].xy, currentOffset[0].zw, currentOffset[1].xy, currentOffset[1].zw, mipLevels, params, marchHeightBlendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + currHeight.x = tex.SampleLevel(texSampler, currentOffset[0].xy, mipLevel)[channel]; + currHeight.y = tex.SampleLevel(texSampler, currentOffset[0].zw, mipLevel)[channel]; + currHeight.z = tex.SampleLevel(texSampler, currentOffset[1].xy, mipLevel)[channel]; + currHeight.w = tex.SampleLevel(texSampler, currentOffset[1].zw, mipLevel)[channel]; + + currHeight = AdjustDisplacementNormalized(currHeight, params); +#endif + + bool4 testResult = currHeight >= currentBound; + [branch] if (any(testResult)) + { + intersectionFound = true; + float2 outOffset; + [branch] if (testResult.x) + { + outOffset = prevOffset; + pt1 = float2(currentBound.x, currHeight.x); + pt2 = float2(prevBound, prevHeight); + } + else if (testResult.y) + { + outOffset = currentOffset[0].xy; + pt1 = float2(currentBound.y, currHeight.y); + pt2 = float2(currentBound.x, currHeight.x); + } + else if (testResult.z) + { + outOffset = currentOffset[0].zw; + pt1 = float2(currentBound.z, currHeight.z); + pt2 = float2(currentBound.y, currHeight.y); + } + else + { + outOffset = currentOffset[1].xy; + pt1 = float2(currentBound.w, currHeight.w); + pt2 = float2(currentBound.z, currHeight.z); + } + prevOffset = outOffset; + break; + } + + prevOffset = currentOffset[1].zw; + prevBound = currentBound.w; + prevHeight = currHeight.w; + numSteps -= 4; + } + + float parallaxAmount = 0.0; + [branch] if (intersectionFound) + { + // Refine coarse hit interval with secant iterations: + // f(t) = sampledHeight(t) - t, t in [0,1] where t is ray depth bound. + float tNear = pt1.x; + float hNear = pt1.y; + float fNear = hNear - tNear; + float tFar = pt2.x; + float hFar = pt2.y; + float fFar = hFar - tFar; + + [loop] for (uint i = 0; i < secantIters; i++) + { + float denominator = fNear - fFar; + float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; + float tSecant = lerp(tNear, tFar, r); + float2 secantCoords = coords.xy + parallaxDir * (((1.0 - tSecant) * -maxHeight) + minHeight); + + float hSecant = 0.0; +#if defined(LANDSCAPE) + hSecant = GetTerrainHeight(noise, input, secantCoords, mipLevels, params, marchHeightBlendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + hSecant = tex.SampleLevel(texSampler, secantCoords, mipLevel)[channel]; + hSecant = AdjustDisplacementNormalized(hSecant, params); +#endif + + float fSecant = hSecant - tSecant; + [branch] if (fSecant >= 0.0) + { + tNear = tSecant; + hNear = hSecant; + fNear = fSecant; + } + else + { + tFar = tSecant; + hFar = hSecant; + fFar = fSecant; + } + } + + float denominator = fNear - fFar; + float r = abs(denominator) > EPSILON_DIVISION ? saturate(fNear / denominator) : 0.5; + parallaxAmount = lerp(tNear, tFar, r); + } + + float offset = (1.0 - parallaxAmount) * -maxHeight + minHeight; + pixelOffset = saturate(parallaxAmount); + float2 finalCoords = parallaxDir * offset + coords.xy; +#if defined(LANDSCAPE) + if (SharedData::extendedMaterialSettings.EnableHeightBlending) { + float unusedHeight; + unusedHeight = GetTerrainHeight(noise, input, finalCoords, mipLevels, params, 1.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, weights); + } +#endif +#if defined(VR_STEREO_OPT) + hasPOM = true; +#endif + return finalCoords; + } + } + +# if !defined(LANDSCAPE) + // https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf + // Cheap method of creating shadows using height for a given light source + float GetParallaxSoftShadowMultiplier(float2 coords, float mipLevel, float3 L, float sh0, Texture2D tex, SamplerState texSampler, uint channel, float quality, float noise, DisplacementParams params) + { + [branch] if (quality > 0.0) + { + uint tapCount = ParallaxShadowTapCount(quality); + float shadowStrength = ShadowIntensity * (4.0 / tapCount); + float2 rayDir = L.xy * 0.1 * params.HeightScale; + float4 multipliers = rcp((float4(1, 2, 3, 4) + noise)); + float4 sh = sh0.xxxx; + sh.x = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.x, mipLevel)[channel], params); + if (quality > 0.25) + sh.y = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.y, mipLevel)[channel], params); + if (quality > 0.5) + sh.z = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.z, mipLevel)[channel], params); + if (quality > 0.75) + sh.w = AdjustDisplacementNormalized(tex.SampleLevel(texSampler, coords + rayDir * multipliers.w, mipLevel)[channel], params); + return 1.0 - saturate(dot(max(0, sh - sh0), shadowStrength)); + } + return 1.0; + } + +# endif + +#endif // EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli new file mode 100644 index 0000000000..c683b826fb --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -0,0 +1,409 @@ +#ifndef EXTENDED_MATERIALS_TERRAIN_HLSLI +#define EXTENDED_MATERIALS_TERRAIN_HLSLI + +// Included only for LANDSCAPE Lighting permutations; parent ExtendedMaterials.hlsli is EMAT-only (see Lighting.hlsl). + + void InitializeTerrainMipLevels(float2 coords, out float mipLevels[6]) + { + mipLevels[0] = GetMipLevel(coords, TexColorSampler); + mipLevels[1] = GetMipLevel(coords, TexLandColor2Sampler); + mipLevels[2] = GetMipLevel(coords, TexLandColor3Sampler); + mipLevels[3] = GetMipLevel(coords, TexLandColor4Sampler); + mipLevels[4] = GetMipLevel(coords, TexLandColor5Sampler); + mipLevels[5] = GetMipLevel(coords, TexLandColor6Sampler); + } + + // Parallax/height sampling: dual-tap stochastic blend, matching the albedo/normal path so the + // displaced height field stays aligned with the de-tiled surface being shaded. StochasticEffectParallax + // is deliberately branchless (this wrapper is inlined 24+ times across the unrolled ray-march/secant/ + // soft-shadow paths, where duplicated control flow is what explodes FXC compile time). Offset is + // ignored when TERRAIN_VARIATION is unset (plain SampleLevel path). + inline float4 TerrainParallaxTexSample(Texture2D tex, float2 uv, float mipLevel, StochasticOffsets sharedOffset, uint layerIndex) + { +# if defined(TERRAIN_VARIATION) + return StochasticEffectParallax(tex, SampTerrainParallaxSampler, uv, mipLevel, sharedOffset, + g_terrainParallaxSecondSampleFade[layerIndex], g_terrainParallaxHeightInfluence[layerIndex]); +# else + return tex.SampleLevel(SampTerrainParallaxSampler, uv, mipLevel); +# endif + } + +# define HEIGHT_POWER 2 +# define HEIGHT_MULT 8 + + // [loop] on fixed-6: less compile/optimizer blow-up than [unroll] here (tiny runtime cost). + float TerrainWeightedHeightSum(float heights[6], float weights[6]) + { + float totalHeight = 0; + [loop] for (int i = 0; i < 6; i++) + { + totalHeight += heights[i] * weights[i]; + } + return totalHeight; + } + + void ProcessTerrainHeightWeights(float heightBlend, float4 w1, float2 w2, float heights[6], inout float weights[6], out float totalHeight) + { + totalHeight = 0.0; + weights[0] = w1.x; + weights[1] = w1.y; + weights[2] = w1.z; + weights[3] = w1.w; + weights[4] = w2.x; + weights[5] = w2.y; + + if (heightBlend <= 1.0) { + float wsum = 0; + [loop] for (int j = 0; j < 6; j++) + { + wsum += weights[j]; + } + + float invwsum = rcp(wsum); + [loop] for (int k = 0; k < 6; k++) + { + weights[k] *= invwsum; + totalHeight += heights[k] * weights[k]; + } + } else { + // pow(base, k) == exp2(k * log2(base)); base is loop-invariant here, so hoist its + // log2 out of the 6-tap loop (FXC keeps it inside the pow intrinsic otherwise). + float logHeightBlend = log2(max(abs(heightBlend), 0.0001)); + [loop] for (int hbIdx = 0; hbIdx < 6; hbIdx++) + { + weights[hbIdx] *= exp2((HEIGHT_MULT * heights[hbIdx]) * logHeightBlend); + } + + [loop] for (int j = 0; j < 6; j++) + { + weights[j] = min(100, pow(abs(weights[j]), max(abs(heightBlend), 0.0001))); + } + + float wsum = 0; + [loop] for (int k = 0; k < 6; k++) + { + wsum += weights[k]; + } + + float invwsum = rcp(wsum); + [loop] for (int l = 0; l < 6; l++) + { + weights[l] *= invwsum; + totalHeight += heights[l] * weights[l]; + } + } + } + + // Blend four per-tap height vectors like four sequential GetTerrainHeight calls; weights output matches tap 3 (last UV). + float4 FinishTerrainHeightQuadBlend(float heightBlend, float4 w1, float2 w2, + float qh0[6], float qh1[6], float qh2[6], float qh3[6], out float weights[6]) + { + float4 result = 0.0; + // heightBlend <= 1: weights depend only on w1/w2 (not per-tap heights) — one normalize + four dots. + if (heightBlend <= 1.0) { + float t3 = 0.0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh3, weights, t3); + result = float4(TerrainWeightedHeightSum(qh0, weights), TerrainWeightedHeightSum(qh1, weights), TerrainWeightedHeightSum(qh2, weights), t3); + } else { + float wTmp[6]; + float t0 = 0.0, t1 = 0.0, t2 = 0.0, t3 = 0.0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh0, wTmp, t0); + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh1, wTmp, t1); + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh2, wTmp, t2); + ProcessTerrainHeightWeights(heightBlend, w1, w2, qh3, weights, t3); + result = float4(t0, t1, t2, t3); + } + return result; + } + +# if defined(TRUE_PBR) + +// FXC does not substitute macro args inside `::EnumParam`; pass full scoped flags as TILEFLAG. +#define EM_PBR_DISP_LAYER_SCALAR(N, TILEFLAG, TEX, WGT) \ + [branch] if ((PBRFlags & (TILEFLAG)) != 0 && (WGT) > 0.01) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(TEX, coords, mipLevels[N], sharedOffset, N).x, params[N]); \ + } + +#define EM_PBR_DISP_LAYER_QUAD(N, TILEFLAG, TEX, WGT) \ + [branch] if ((PBRFlags & (TILEFLAG)) != 0 && (WGT) > 0.01) \ + { \ + [loop] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(TEX, uvs[k], mipLevels[N], sharedOffset, N).x, params[N]); \ + } + +#define EM_PBR_DISP_FOREACH(M) \ + M(0, PBR::TerrainFlags::LandTile0HasDisplacement, TexLandDisplacement0Sampler, w1.x) \ + M(1, PBR::TerrainFlags::LandTile1HasDisplacement, TexLandDisplacement1Sampler, w1.y) \ + M(2, PBR::TerrainFlags::LandTile2HasDisplacement, TexLandDisplacement2Sampler, w1.z) \ + M(3, PBR::TerrainFlags::LandTile3HasDisplacement, TexLandDisplacement3Sampler, w1.w) \ + M(4, PBR::TerrainFlags::LandTile4HasDisplacement, TexLandDisplacement4Sampler, w2.x) \ + M(5, PBR::TerrainFlags::LandTile5HasDisplacement, TexLandDisplacement5Sampler, w2.y) + + float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + + EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_SCALAR) + + float total = 0.0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); + return total; + } + + // Ray-march coarse step: one branch tree per layer; inner [loop] fans out four UVs. Kept as a runtime + // [loop] (not [unroll]) so FXC compiles one sampler body per layer instead of four -> large compile-time + // cut for the TERRAIN_VARIATION dual-tap path. Result is value-identical (independent per-UV iterations). + float4 GetTerrainHeightQuadRayMarch(float screenNoise, PS_INPUT input, + float2 u0, float2 u1, float2 u2, float2 u3, + float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float2 uvs[4] = { u0, u1, u2, u3 }; + float h4[4][6]; + [loop] for (uint qi = 0; qi < 4; qi++) + [loop] for (uint lj = 0; lj < 6; lj++) + h4[qi][lj] = 0; + + EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_QUAD) + + return FinishTerrainHeightQuadBlend(heightBlend, w1, w2, h4[0], h4[1], h4[2], h4[3], weights); + } + +#undef EM_PBR_DISP_LAYER_SCALAR +#undef EM_PBR_DISP_LAYER_QUAD +#undef EM_PBR_DISP_FOREACH + +# else + +#define EM_LEGACY_LAYER012_SCALAR(N, THFLAG, THSAMPLER, COLSAMPLER, WGT) \ + if ((WGT) > 0.01) { \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, coords, mipLevels[N], sharedOffset, N).x, params[N]); \ + } \ + else \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, coords, mipLevels[N], sharedOffset, N).w, params[N]); \ + } \ + } + +#define EM_LEGACY_LAYER345_SCALAR(N, THFLAG, THSAMPLER, COLSAMPLER, WPRIMARY, WELSE) \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0 && (WPRIMARY) > 0.01) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, coords, mipLevels[N], sharedOffset, N).x, params[N]); \ + } \ + else if ((WELSE) > 0.01) \ + { \ + heights[N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, coords, mipLevels[N], sharedOffset, N).w, params[N]); \ + } + +#define EM_LEGACY_LAYER012_QUAD(N, THFLAG, THSAMPLER, COLSAMPLER, WGT) \ + if ((WGT) > 0.01) { \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0) \ + { \ + [loop] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, uvs[k], mipLevels[N], sharedOffset, N).x, params[N]); \ + } \ + else \ + { \ + [loop] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, uvs[k], mipLevels[N], sharedOffset, N).w, params[N]); \ + } \ + } + +#define EM_LEGACY_LAYER345_QUAD(N, THFLAG, THSAMPLER, COLSAMPLER, WPRIMARY, WELSE) \ + [branch] if ((Permutation::ExtraFeatureDescriptor & (THFLAG)) != 0 && (WPRIMARY) > 0.01) \ + { \ + [loop] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(THSAMPLER, uvs[k], mipLevels[N], sharedOffset, N).x, params[N]); \ + } \ + else if ((WELSE) > 0.01) \ + { \ + [loop] for (uint k = 0; k < 4; k++) \ + h4[k][N] = ScaleDisplacement(TerrainParallaxTexSample(COLSAMPLER, uvs[k], mipLevels[N], sharedOffset, N).w, params[N]); \ + } + + float GetTerrainHeight(float screenNoise, PS_INPUT input, float2 coords, float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + + EM_LEGACY_LAYER012_SCALAR(0, Permutation::ExtraFeatureFlags::THLand0HasDisplacement, TexLandTHDisp0Sampler, TexColorSampler, w1.x) + EM_LEGACY_LAYER012_SCALAR(1, Permutation::ExtraFeatureFlags::THLand1HasDisplacement, TexLandTHDisp1Sampler, TexLandColor2Sampler, w1.y) + EM_LEGACY_LAYER012_SCALAR(2, Permutation::ExtraFeatureFlags::THLand2HasDisplacement, TexLandTHDisp2Sampler, TexLandColor3Sampler, w1.z) + EM_LEGACY_LAYER345_SCALAR(3, Permutation::ExtraFeatureFlags::THLand3HasDisplacement, TexLandTHDisp3Sampler, TexLandColor4Sampler, w1.w, w1.w) + EM_LEGACY_LAYER345_SCALAR(4, Permutation::ExtraFeatureFlags::THLand4HasDisplacement, TexLandTHDisp4Sampler, TexLandColor5Sampler, w2.x, w2.x) + EM_LEGACY_LAYER345_SCALAR(5, Permutation::ExtraFeatureFlags::THLand5HasDisplacement, TexLandTHDisp5Sampler, TexLandColor6Sampler, w2.y, w2.y) + + float total = 0.0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); + return total; + } + + // Legacy TH/color paths: same branching as GetTerrainHeight; [loop] fans out four UVs per branch + // (runtime loop keeps FXC from inlining four sampler copies per layer). + float4 GetTerrainHeightQuadRayMarch(float screenNoise, PS_INPUT input, + float2 u0, float2 u1, float2 u2, float2 u3, + float mipLevels[6], DisplacementParams params[6], float blendFactor, float4 w1, float2 w2, + StochasticOffsets sharedOffset, + out float weights[6]) + { + float heightBlend = 1 + blendFactor * HEIGHT_POWER; + float2 uvs[4] = { u0, u1, u2, u3 }; + float h4[4][6]; + [loop] for (uint qi = 0; qi < 4; qi++) + [loop] for (uint lj = 0; lj < 6; lj++) + h4[qi][lj] = 0; + + EM_LEGACY_LAYER012_QUAD(0, Permutation::ExtraFeatureFlags::THLand0HasDisplacement, TexLandTHDisp0Sampler, TexColorSampler, w1.x) + EM_LEGACY_LAYER012_QUAD(1, Permutation::ExtraFeatureFlags::THLand1HasDisplacement, TexLandTHDisp1Sampler, TexLandColor2Sampler, w1.y) + EM_LEGACY_LAYER012_QUAD(2, Permutation::ExtraFeatureFlags::THLand2HasDisplacement, TexLandTHDisp2Sampler, TexLandColor3Sampler, w1.z) + EM_LEGACY_LAYER345_QUAD(3, Permutation::ExtraFeatureFlags::THLand3HasDisplacement, TexLandTHDisp3Sampler, TexLandColor4Sampler, w1.w, w1.w) + EM_LEGACY_LAYER345_QUAD(4, Permutation::ExtraFeatureFlags::THLand4HasDisplacement, TexLandTHDisp4Sampler, TexLandColor5Sampler, w2.x, w2.x) + EM_LEGACY_LAYER345_QUAD(5, Permutation::ExtraFeatureFlags::THLand5HasDisplacement, TexLandTHDisp5Sampler, TexLandColor6Sampler, w2.y, w2.y) + + return FinishTerrainHeightQuadBlend(heightBlend, w1, w2, h4[0], h4[1], h4[2], h4[3], weights); + } + +#undef EM_LEGACY_LAYER012_SCALAR +#undef EM_LEGACY_LAYER345_SCALAR +#undef EM_LEGACY_LAYER012_QUAD +#undef EM_LEGACY_LAYER345_QUAD + +# endif +# if defined(TRUE_PBR) + static const uint TERRAIN_DISPLACEMENT_MASK = (1u << 6u) | (1u << 7u) | (1u << 8u) | (1u << 9u) | (1u << 10u) | (1u << 11u); +# endif +# define TERRAIN_HEIGHT_AT(COORDS, MIP, QUALITY, WEIGHTS) \ + GetTerrainHeight(noise, input, COORDS, MIP, params, 0.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, WEIGHTS) + + inline bool TerrainHasSignificantBlend(float4 w1, float2 w2) + { + return (w1.x + w1.y + w1.z + w1.w + w2.x + w2.y) > 0.01; + } + + inline bool TerrainHasAnyDisplacement() + { +# if defined(TRUE_PBR) + return (PBRFlags & TERRAIN_DISPLACEMENT_MASK) != 0; +# else + // Some distant landscape permutations can lose THLandHasDisplacement even though + // legacy terrain parallax still uses alpha-based displacement. + return SharedData::extendedMaterialSettings.EnableTerrainParallax || + (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; +# endif + } + + inline float TerrainMaxWeightedHeightScale(PS_INPUT input, DisplacementParams params[6]) + { + return max(params[0].HeightScale * input.LandBlendWeights1.x, max(params[1].HeightScale * input.LandBlendWeights1.y, max(params[2].HeightScale * input.LandBlendWeights1.z, + max(params[3].HeightScale * input.LandBlendWeights1.w, max(params[4].HeightScale * input.LandBlendWeights2.x, params[5].HeightScale * input.LandBlendWeights2.y))))); + } + + inline uint TerrainDirectionalShadowTapCount(float quality) + { + // Directional terrain shadows are capped to reduce cost. + if (quality > 0.7) + return 2; + if (quality > 0.0) + return 1; + return 0; + } + + bool ComputeTerrainParallaxShadowBaseHeight(PS_INPUT input, float2 coords, float mipLevels[6], float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) + { + sh0 = 0.0; + if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) + return false; + if (!TerrainHasAnyDisplacement()) + return false; + + float weights[6] = { 0, 0, 0, 0, 0, 0 }; + sh0 = TERRAIN_HEIGHT_AT(coords, mipLevels, quality, weights); + return true; + } + + float GetParallaxSoftShadowMultiplierTerrain(PS_INPUT input, float2 coords, float mipLevel[6], float3 L, float sh0, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset) + { + if (quality > 0.0) { + uint tapCount = ParallaxShadowTapCount(quality); + float shadowStrength = ShadowIntensity * (4.0 / tapCount); + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = L.xy * 0.1; + float shadowScaleInv = 1.0; + +# if defined(TRUE_PBR) + float scale = TerrainMaxWeightedHeightScale(input, params); + if (scale < 0.01) + return 1.0; + rayDir *= scale; + shadowScaleInv = rcp(scale); +# endif + // Accumulate per-tap occlusion in a scalar. A float4 is NOT a natively addressable + // l-value, so sh[i] = ... force-unrolls the [loop] (X3531) — the opposite of the goal. + // multipliers[i] == rcp((i+1)+noise); tapCount already matches the q>0.25/0.5/0.75 + // ladder, so the same taps run. Sum matches the old + // dot(max(0, sh-sh0)*shadowScaleInv, shadowStrength) up to FP associativity. + // FXC now inlines ONE GetTerrainHeight body instead of up to four. + float shadowAccum = 0.0; + [loop] for (uint i = 0; i < tapCount; i++) + { + float shi = TERRAIN_HEIGHT_AT(coords + rayDir * rcp((float)(i + 1) + noise), mipLevel, quality, heights); + shadowAccum += max(0, shi - sh0) * shadowScaleInv; + } + return 1.0 - saturate(shadowAccum * shadowStrength); + } + return 1.0; + } + + float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, float sh0) + { + uint tapCount = TerrainDirectionalShadowTapCount(quality); + if (tapCount == 0) + return 1.0; + float shadowStrength = ShadowIntensity * (2.0 / tapCount); + if (!TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy)) + return 1.0; + + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = lightDirection.xy * 0.1; + float shadowScaleInv = 1.0; + +# if defined(TRUE_PBR) + float scale = TerrainMaxWeightedHeightScale(input, params); + if (scale < 0.01) + return 1.0; + rayDir *= scale; + shadowScaleInv = rcp(scale); +# endif + + // Scalar accumulate: float4 sh[i] l-value would force-unroll the [loop] (X3531). + // multipliers[i] == rcp((i+1)+noise); one inlined GetTerrainHeight body instead of two. + float shadowAccum = 0.0; + [loop] for (uint i = 0; i < tapCount; i++) + { + float shi = TERRAIN_HEIGHT_AT(coords + rayDir * rcp((float)(i + 1) + noise), mipLevels, quality, heights); + shadowAccum += max(0, shi - sh0) * shadowScaleInv; + } + + return 1.0 - saturate(shadowAccum * shadowStrength); + } + + float EvaluateTerrainParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, out float sh0) + { + if (!ComputeTerrainParallaxShadowBaseHeight(input, coords, mipLevels, quality, noise, params, sharedOffset, sh0)) + return 1.0; + return GetParallaxSoftShadowMultiplierTerrain(input, coords, mipLevels, lightDirection, sh0, quality, noise, params, sharedOffset); + } + +# undef TERRAIN_HEIGHT_AT + +#endif // EXTENDED_MATERIALS_TERRAIN_HLSLI \ No newline at end of file diff --git a/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini b/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini index efa84cac75..91efaf62bc 100644 --- a/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini +++ b/features/Extended Materials/Shaders/Features/ExtendedMaterials.ini @@ -1,2 +1,5 @@ [Info] -Version = 1-2-0 +Version = 1-3-0 + +[Nexus] +autoupload = false \ 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 1b41d34dd1..d4a36a7606 100644 --- a/features/Terrain Variation/Shaders/Features/TerrainVariation.ini +++ b/features/Terrain Variation/Shaders/Features/TerrainVariation.ini @@ -1,5 +1,5 @@ [Info] -Version = 1-0-1 +Version = 1-1-0 [Nexus] nexusmodid = 148123 diff --git a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli index 1a34cd0df6..ccb125f758 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -5,47 +5,65 @@ #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 +// Height-vs-stochastic weight (unchanged). Stochastic vertex weights use HEIGHT_INFLUENCE only via heightInfluence below. +static const float HEIGHT_INFLUENCE = 0.3; +static const float3 LUMINANCE_WEIGHTS = float3(0.2126, 0.7152, 0.0722); +static const float HEIGHT_BLEND_FADE_MIP_START = 1.6; +static const float HEIGHT_BLEND_FADE_MIP_RANGE = 2.2; +// TV distant/minified fallback: single sample with primary offset (skip dual-sample blend only). +// The second sample's weight fades to zero across [START - FADE_RANGE, START] so the single-sample +// branch is reached only once it contributes nothing -> no pop/seam at the transition. +static const float TV_SINGLE_SAMPLE_MIP_START = 3.0; +static const float TV_SINGLE_SAMPLE_FADE_RANGE = 0.6; +static const float TV_SINGLE_SAMPLE_FADE_RCP = 1.0 / TV_SINGLE_SAMPLE_FADE_RANGE; +// Golden ratio for frac(rnd * φ) low-discrepancy jitter; precompute once per pixel at callsite when possible. +static const float STOCHASTIC_LOD_PHI = 1.618; + +// --------------------- STRUCTURES --------------------- // struct StochasticOffsets { float2 offset1; float2 offset2; float2 offset3; float3 weights; + // Contrast on the two highest barycentric weights — same for every layer on a pixel. + float w1Contrast; + float w2Contrast; }; -// --------------------- 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); +// Per-pixel landscape UV gradients. Set once in Lighting.hlsl before the six-way blend so +// StochasticEffect does not duplicate ddx/ddy per layer (mip still uses each tex's dimensions). +struct TerrainGradients +{ + float2 gradDx; + float2 gradDy; +}; -// --------------------- COMPUTE FUNCTIONS --------------------- // +static TerrainGradients g_terrainStochasticGrad; +// Per-tile values computed once before the six-way blend / parallax march (FXC: keeps GetDimensions +// and mip/fade math out of every inlined StochasticEffect / StochasticEffectParallax copy). +static float g_terrainStochasticMips[6]; +static float g_terrainStochasticSecondSampleFade[6]; +static float g_terrainStochasticHeightInfluence[6]; +static float g_terrainParallaxSecondSampleFade[6]; +static float g_terrainParallaxHeightInfluence[6]; + +// 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 +72,207 @@ 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 float StochasticHeightFadeFromMip(float mipLevel) { - 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; + return saturate((mipLevel - HEIGHT_BLEND_FADE_MIP_START) / HEIGHT_BLEND_FADE_MIP_RANGE); } -// Common barycentric coordinate calculation for stochastic sampling -inline float4x3 ComputeBarycentricVerts(float2 landscapeUV) +inline float StochasticContrastWeight(float weight) { - float2 scaledUV = landscapeUV * (WORLD_SCALE); - float2 skewUV = mul(SKEW_MATRIX, scaledUV); - float2 vxID = floor(skewUV); - float2 frac_uv = frac(skewUV); - - float barry_z = 1.0 - frac_uv.x - frac_uv.y; - float3 barry = float3(frac_uv, barry_z); + float w = saturate(weight); + float w2 = w * w; + float w4 = w2 * w2; + return w4 * w4; +} - 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)); +inline float StochasticHeightBlendInfluence(float mipLevel) +{ + float heightFade = StochasticHeightFadeFromMip(mipLevel); + return HEIGHT_INFLUENCE * (1.0 - heightFade); } -inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) +inline StochasticOffsets ZeroStochasticOffsets() { - float4x3 BW_vx = ComputeBarycentricVerts(landscapeUV); + StochasticOffsets o; + o.offset1 = 0; + o.offset2 = 0; + o.offset3 = 0; + o.weights = 0; + o.w1Contrast = 0; + o.w2Contrast = 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]; +inline TerrainGradients ComputeTerrainGradients(float2 uv) +{ + TerrainGradients g; + g.gradDx = ddx(uv); + g.gradDy = ddy(uv); + return g; +} - return offsets; +inline float StochasticMipFromGradients(TerrainGradients g, float2 texDim, float extraLandMipBias) +{ + float2 dxT = g.gradDx * texDim; + float2 dyT = g.gradDy * texDim; + return max(0.5 * log2(max(dot(dxT, dxT), dot(dyT, dyT))), 0.0) + SharedData::MipBias + extraLandMipBias; } -inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) +inline float StochasticSecondSampleFade(float mipLevel) { - // Precomputed scaling: (WORLD_SCALE / 0.010416667) * 8.0 = ~255437 - static const float LOD_SCALE = 255437.0; + return saturate((TV_SINGLE_SAMPLE_MIP_START - mipLevel) * TV_SINGLE_SAMPLE_FADE_RCP); +} - float2 scaledUV = landscapeUV * LOD_SCALE; - float2 cellID = floor(scaledUV); +inline void InitTerrainStochasticMip(uint tile, Texture2D tex, float extraLandMipBias) +{ + float2 texDim; + tex.GetDimensions(texDim.x, texDim.y); + float mipLevel = StochasticMipFromGradients(g_terrainStochasticGrad, texDim, extraLandMipBias); + g_terrainStochasticMips[tile] = mipLevel; + g_terrainStochasticSecondSampleFade[tile] = StochasticSecondSampleFade(mipLevel); + g_terrainStochasticHeightInfluence[tile] = StochasticHeightBlendInfluence(mipLevel); +} - StochasticOffsets offsetsLOD; - // Generate both offsets from single hash to reduce calls - float2 hash1 = hashLOD(cellID); - float2 hash2 = hashLOD(cellID + 127.0); +inline void InitTerrainParallaxStochasticFade(uint tile, float mipLevel) +{ + g_terrainParallaxSecondSampleFade[tile] = StochasticSecondSampleFade(mipLevel); + g_terrainParallaxHeightInfluence[tile] = StochasticHeightBlendInfluence(mipLevel); +} - offsetsLOD.offset1 = hash1 * 0.08; - offsetsLOD.offset2 = hash2 * 0.08; +inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) +{ + float2 skewUV = mul(SKEW_MATRIX, landscapeUV * WORLD_SCALE); + float2 vxID = floor(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; + } - // Simplified weights since we only use 2 samples now - offsetsLOD.weights = float3(0.65, 0.35, 0.0); + // 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 offsetsLOD; + StochasticOffsets o; + o.offset1 = hash2D2D(c0.cell); + o.offset2 = hash2D2D(c1.cell); + o.offset3 = 0; + o.weights = float3(c0.w, c1.w, c2.w); + o.w1Contrast = StochasticContrastWeight(c0.w); + o.w2Contrast = StochasticContrastWeight(c1.w); + return o; } -// --------------------- 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) +inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) { - float offsetScale = 0.01; - - // 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 lodOn = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; + + float2 cellID = floor(landscapeUV * 255437.0); + float2 h1 = hashLOD(cellID); + float2 h2 = hashLOD(cellID + 127.0); + + StochasticOffsets o; + o.offset1 = h1 * 0.08 * lodOn; + o.offset2 = h2 * 0.08 * lodOn; + o.offset3 = 0; + o.weights = float3(0.65, 0.35, 0.0) * lodOn; + o.w1Contrast = StochasticContrastWeight(o.weights.x); + o.w2Contrast = StochasticContrastWeight(o.weights.y); + return o; +} - // 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); +// --------------------- SAMPLING FUNCTIONS --------------------- // - // Simple 2-sample blend weighted toward first sample - return lerp(sample2, sample1, 0.65); +inline float2 StochasticSampleLODJitter(float rnd) +{ + return float2(rnd - 0.5, frac(rnd * STOCHASTIC_LOD_PHI) - 0.5); } -// Main stochastic sampling function -inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float2 dx, float2 dy) +// secondSampleScale fades the second tap's contribution to zero near the single-sample cutoff so the +// branch boundary is continuous (no pop). At scale 0 the result is exactly s1. +inline float4 StochasticBlendTwoSamples(float4 s1, float4 s2, float w1Contrast, float w2Contrast, float heightInfluence, float blendFactor1, float blendFactor2, float secondSampleScale) { - // 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); + float w1 = w1Contrast * (1.0 + heightInfluence * blendFactor1); + float w2 = w2Contrast * secondSampleScale * (1.0 + heightInfluence * blendFactor2); + float denom = max(w1 + w2, 1e-8); + return lerp(s2, s1, w1 / denom); +} - // 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)); +// LOD terrain stochastic sampling — 2 SampleBias, fixed blend (pass jitter from StochasticSampleLODJitter(screenNoise)). +inline float4 StochasticSampleLOD(float2 jitter, Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsetsLOD) +{ + float lodOn = SharedData::terrainVariationSettings.enableLODTerrainTilingFix ? 1.0 : 0.0; + float2 j1 = (offsetsLOD.offset1 + jitter) * 0.01; + float2 j2 = (offsetsLOD.offset2 + float2(jitter.y, -jitter.x)) * 0.01; + float4 s1 = tex.SampleBias(samp, uv + j1 * lodOn, SharedData::MipBias); + float4 s2 = tex.SampleBias(samp, uv + j2 * lodOn, SharedData::MipBias); + float blendW = lerp(0.5, offsetsLOD.weights.x, lodOn); + return lerp(s2, s1, blendW); +} - float3 alphaValues = float3(sample1.a, sample2.a, sample3.a); - float3 alphaMask = step(0.001, alphaValues); - float3 heights = lerp(luminanceHeights, alphaValues, alphaMask); +// 2-sample height-blended stochastic sampling. Uses one shared gradient (SampleGrad) for both taps so +// filtering stays consistent and anisotropy is preserved; the second tap fades out with distance. +// Sorting in ComputeStochasticOffsets guarantees offset1/offset2 are the two +// highest-weight barycentric vertices, so dropping offset3 loses minimal quality. +inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float secondSampleFade, float heightInfluence) +{ + TerrainGradients g = g_terrainStochasticGrad; + float4 s1 = tex.SampleGrad(samp, uv + offsets.offset1, g.gradDx, g.gradDy); + float4 s2 = tex.SampleGrad(samp, uv + offsets.offset2, g.gradDx, g.gradDy); - // Combined weight calculation and normalization - float3 weights = NormalizeWeights(blendWeights * (1.0 + HEIGHT_INFLUENCE * heights)); + 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)); - // Final blend - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; + return StochasticBlendTwoSamples(s1, s2, offsets.w1Contrast, offsets.w2Contrast, heightInfluence, h1, h2, secondSampleFade); } -// 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/height sampling. MUST use the same dual-tap stochastic blend (same offsets/weights) +// as StochasticEffect so the displaced height field stays aligned with the de-tiled albedo/normal — +// otherwise parallax is computed against a different surface than the one being shaded. +// Deliberately BRANCHLESS: this is inlined dozens of times across the unrolled ray-march / secant / +// soft-shadow paths, and it's the duplicated control flow (not the second fetch) that explodes FXC +// compile time. The second tap fades with distance via secondSampleScale, mirroring StochasticEffect. +inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets, float secondSampleFade, float heightInfluence) { - // Early exit for disabled terrain variation - avoid all other computations - if (!SharedData::terrainVariationSettings.enableTilingFix) { - return tex.SampleLevel(samp, uv, 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); - } - - // 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); + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + return StochasticBlendTwoSamples(s1, s2, offsets.w1Contrast, offsets.w2Contrast, heightInfluence, s1.a, s2.a, secondSampleFade); +} - // Simple barycentric blend without height influence - float3 weights = NormalizeWeights(saturate(offsets.weights)); - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; +inline float4 SampleTerrain(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, uint tileIndex) +{ + return StochasticEffect(tex, samp, uv, offsets, g_terrainStochasticSecondSampleFade[tileIndex], g_terrainStochasticHeightInfluence[tileIndex]); } -#pragma warning(pop) #endif // TERRAIN_VARIATION_HLSLI \ No newline at end of file diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index d6c294423c..7a7a852a07 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -621,8 +621,6 @@ "feature.extended_materials.enable_parallax_warping_fix_tooltip": "Enables a fix reducing parallax scale on curved and smooth normal triangles.", "feature.extended_materials.enable_shadows": "Enable Shadows", "feature.extended_materials.enable_shadows_tooltip": "Enables cheap soft shadows when using parallax. This applies to all directional and point lights. ", - "feature.extended_materials.extend_shadows": "Extend Shadows", - "feature.extended_materials.extend_shadows_tooltip": "Extends parallax shadows beyond the range of parallax. Small performance impact.", "feature.extended_materials.key_feature_1": "Parallax occlusion mapping for depth", "feature.extended_materials.key_feature_2": "Complex material blending", "feature.extended_materials.key_feature_3": "Terrain heightmap support", @@ -1255,8 +1253,6 @@ "feature.terrain_variation.apply_to_lod_terrain": "Apply to LOD Terrain", "feature.terrain_variation.apply_to_lod_terrain_tooltip": "Applies the tiling fix to LOD terrain objects.\nThis helps reduce the visible tiling effect on distant terrain.", "feature.terrain_variation.description": "Terrain Variation reduces the repeating pattern effect on terrain textures.\nThis technique creates more natural-looking terrain by adding variation to texture sampling.", - "feature.terrain_variation.enable_tiling_fix": "Enable Terrain Tiling Fix", - "feature.terrain_variation.enable_tiling_fix_tooltip": "Reduces the repeating pattern effect on terrain textures.\nThis technique creates more natural-looking terrain by adding variation to texture sampling.", "feature.terrain_variation.key_feature_1": "Reduces terrain texture tiling", "feature.terrain_variation.key_feature_2": "Adjustable distance-based blending", "feature.terrain_variation.key_feature_3": "Improved terrain visual quality", diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json index a5ce43434a..326c5b733d 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json @@ -620,8 +620,6 @@ "feature.extended_materials.enable_parallax_warping_fix_tooltip": "启用修复,减少弯曲和平滑法线三角形上的视差缩放。", "feature.extended_materials.enable_shadows": "启用阴影", "feature.extended_materials.enable_shadows_tooltip": "使用视差时启用廉价软阴影。适用于所有方向光和点光源。", - "feature.extended_materials.extend_shadows": "扩展阴影", - "feature.extended_materials.extend_shadows_tooltip": "将视差阴影扩展到视差范围之外。对性能影响较小。", "feature.extended_materials.key_feature_1": "视差遮蔽映射,增加深度感", "feature.extended_materials.key_feature_2": "复杂材质混合", "feature.extended_materials.key_feature_3": "地形高度图支持", @@ -1254,8 +1252,6 @@ "feature.terrain_variation.apply_to_lod_terrain": "应用到 LOD 地形", "feature.terrain_variation.apply_to_lod_terrain_tooltip": "将该平铺修复应用到 LOD 地形对象。\n这有助于减少远处地形上可见的重复平铺效果。", "feature.terrain_variation.description": "减少地形纹理的重复图案效果。\n通过为纹理采样添加变化来创造更自然的地形外观。", - "feature.terrain_variation.enable_tiling_fix": "启用地形平铺修复", - "feature.terrain_variation.enable_tiling_fix_tooltip": "减少地形纹理的重复平铺感。\n该技术通过在纹理采样中加入变化,让地形看起来更自然。", "feature.terrain_variation.key_feature_1": "减少地形纹理的平铺感", "feature.terrain_variation.key_feature_2": "可调节的基于距离的混合", "feature.terrain_variation.key_feature_3": "提升地形视觉质量", diff --git a/package/Shaders/Common/LightingLandscape.hlsli b/package/Shaders/Common/LightingLandscape.hlsli new file mode 100644 index 0000000000..b187905d17 --- /dev/null +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -0,0 +1,131 @@ +#ifndef __LIGHTING_LANDSCAPE_HLSLI__ +#define __LIGHTING_LANDSCAPE_HLSLI__ + +// Shared terrain layer indexing for TRUE_PBR (PBRFlags terrain bits) and non-PBR displacement +// (Permutation::ExtraFeatureDescriptor TH land bits). HLSL cannot index texture registers by loop +// variable; use LANDSCAPE_*_LAYER_FOREACH (X-macros) for per-layer texture bodies. +// +// PBR terrain bit layout must stay aligned with PBR::TerrainFlags in PBRMath.hlsli. + +#if defined(LANDSCAPE) + +# if !defined(__PERMUTATION_DEPENDENCY_HLSL__) +# include "Common/Permutation.hlsli" +# endif + +namespace LandscapeLayers +{ +# if defined(TRUE_PBR) + // Tiles 0..5: PBR, displacement, glint packed contiguously (see PBRMath.hlsli). + inline bool PbrTileUsesFullPBR(uint tileIndex) + { + return (PBRFlags & (1u << tileIndex)) != 0; + } + inline bool PbrTileHasDisplacement(uint tileIndex) + { + return (PBRFlags & (1u << (tileIndex + 6u))) != 0; + } + inline bool PbrTileHasGlint(uint tileIndex) + { + return (PBRFlags & (1u << (tileIndex + 12u))) != 0; + } +# else + // Matches Permutation::ExtraFeatureFlags::THLand0HasDisplacement .. THLand5HasDisplacement + inline bool ThTileHasDisplacement(uint tileIndex) + { + return (Permutation::ExtraFeatureDescriptor & (1u << tileIndex)) != 0; + } +# endif +} + +// tileIndex, displacementTexture, diffuseOrAlphaHeightTexture (mip dims / non-PBR height) +# if defined(TRUE_PBR) +# define LANDSCAPE_PBR_LAYER_FOREACH(X) \ + X(0, TexLandDisplacement0Sampler, TexColorSampler) \ + X(1, TexLandDisplacement1Sampler, TexLandColor2Sampler) \ + X(2, TexLandDisplacement2Sampler, TexLandColor3Sampler) \ + X(3, TexLandDisplacement3Sampler, TexLandColor4Sampler) \ + X(4, TexLandDisplacement4Sampler, TexLandColor5Sampler) \ + X(5, TexLandDisplacement5Sampler, TexLandColor6Sampler) +# else +# define LANDSCAPE_TH_LAYER_FOREACH(X) \ + X(0, TexLandTHDisp0Sampler, TexColorSampler) \ + X(1, TexLandTHDisp1Sampler, TexLandColor2Sampler) \ + X(2, TexLandTHDisp2Sampler, TexLandColor3Sampler) \ + X(3, TexLandTHDisp3Sampler, TexLandColor4Sampler) \ + X(4, TexLandTHDisp4Sampler, TexLandColor5Sampler) \ + X(5, TexLandTHDisp5Sampler, TexLandColor6Sampler) +# endif + +// --------------------------------------------------------------------------- +// Lighting.hlsl: six-way landscape diffuse / normal / RMAOS blend. +// Requires: SampleTerrain, input, uv, sharedOffset, landDistanceTexMipBias, glossiness, blendedRGB, blendedAlpha, +// blendedNormalRGB, blendedNormalAlpha, glintParameters, Color::*, GetLandSnowMaskValue (non-PBR path). +// --------------------------------------------------------------------------- +# if defined(TERRAIN_VARIATION) +# define LANDSCAPE_SAMPLE_ARG(TILE) TILE +# else +# define LANDSCAPE_SAMPLE_ARG(TILE) landDistanceTexMipBias +# endif +# if defined(TRUE_PBR) +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, RMAOS_TEX, RMAOS_SAMP, PBR_PARAMS3, GLINT_PARAMS, WEIGHT) \ + [branch] if ((WEIGHT) > 0.01) \ + { \ + float weight = WEIGHT; \ + float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, LANDSCAPE_SAMPLE_ARG(TILE)); \ + 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, LANDSCAPE_SAMPLE_ARG(TILE)); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + float4 landRMAOS; \ + [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, LANDSCAPE_SAMPLE_ARG(TILE)) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ + [branch] if (LandscapeLayers::PbrTileHasGlint(TILE)) \ + { \ + glintParameters += weight * (GLINT_PARAMS); \ + } \ + } \ + else \ + { \ + landRMAOS = float4(1 - glossiness.x, 0, 1, 0); \ + } \ + blendedRMAOS += landRMAOS * weight; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ + } +# else +# if defined(SNOW) +# define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) \ + landSnowMask += (SNOW_COMPONENT) * weight * GetLandSnowMaskValue(landColor.w); +# else +# define LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) +# endif +# define LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(TILE, COLOR_TEX, COLOR_SAMP, NORM_TEX, NORM_SAMP, WEIGHT, SNOW_COMPONENT) \ + [branch] if ((WEIGHT) > 0.01) \ + { \ + float weight = WEIGHT; \ + float4 landColor = SampleTerrain(COLOR_TEX, COLOR_SAMP, uv, sharedOffset, LANDSCAPE_SAMPLE_ARG(TILE)); \ + float3 landColorRGB = landColor.rgb; \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, LANDSCAPE_SAMPLE_ARG(TILE)); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ + LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) \ + } +# endif + +#endif // LANDSCAPE + +#endif // __LIGHTING_LANDSCAPE_HLSLI__ diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index f7221a063f..41d29cf7ff 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -57,9 +57,8 @@ namespace SharedData bool EnableTerrainParallax; bool EnableHeightBlending; bool EnableShadows; - bool ExtendShadows; bool EnableParallaxWarpingFix; - bool pad0; + uint2 pad0; }; struct CubemapCreatorSettings @@ -187,9 +186,8 @@ namespace SharedData struct TerrainVariationSettings { - uint enableTilingFix; - uint enableLODTerrainTilingFix; - float2 pad0; + uint enableLODTerrainTilingFix; // 1 = apply variation to LOD terrain, 0 = near terrain only (variation always applies near terrain when TERRAIN_VARIATION is defined). + uint3 pad; // Unused; mirrors TerrainVariation::Settings padding in the native plugin. }; struct IBLSettings diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index cab9fee122..f25db54d0a 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -852,7 +852,11 @@ float GetSnowParameterY(float texProjTmp, float alpha) # include "Hair/Hair.hlsli" # endif -# if defined(TERRAIN_VARIATION) +# if defined(LANDSCAPE) +# include "Common/LightingLandscape.hlsli" +# endif + +# if defined(TERRAIN_VARIATION) && (defined(LANDSCAPE) || defined(LOD_LAND_BLEND) || (defined(LOD_BLENDING) && defined(LODLANDSCAPE))) # include "TerrainVariation/TerrainVariation.hlsli" # endif @@ -947,11 +951,37 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float2 uvOriginal = uv; # if defined(EMAT) - float parallaxShadowQuality = sqrt(1.0 - saturate(viewPosition.z / 2048.0)); + float parallaxShadowQuality = viewPosition.z < ExtendedMaterials::ParallaxCheapDistance ? ExtendedMaterials::ParallaxNearShadowQuality : ExtendedMaterials::ParallaxFarShadowQuality; + float terrainDirectionalShadowQuality = parallaxShadowQuality; +# if defined(LANDSCAPE) + terrainDirectionalShadowQuality = ExtendedMaterials::ParallaxNearShadowQuality; +# endif +# define COMPUTE_TERRAIN_SHADOW_BASE(OUT_SH0) ExtendedMaterials::ComputeTerrainParallaxShadowBaseHeight(input, uv, terrainShadowMipLevels, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, OUT_SH0) +# define EVAL_TERRAIN_DIR_SHADOW(BASE_SH0, DIR_TS) ExtendedMaterials::EvaluateTerrainDirectionalParallaxShadowMultiplier(input, uv, terrainShadowMipLevels, DIR_TS, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset, BASE_SH0) +# if defined(LANDSCAPE) +# if defined(TRUE_PBR) +# define LANDSCAPE_PARALLAX_ENABLED (SharedData::extendedMaterialSettings.EnableParallax) +# else +# define LANDSCAPE_PARALLAX_ENABLED \ + (SharedData::extendedMaterialSettings.EnableTerrainParallax || \ + (SharedData::extendedMaterialSettings.EnableParallax && ((Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0))) +# endif +# endif # endif # if defined(LANDSCAPE) float mipLevels[6]; +# if defined(EMAT) + float terrainShadowMipLevels[6]; +# if defined(TERRAIN_VARIATION) + StochasticOffsets sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); +# else + StochasticOffsets sharedOffset = (StochasticOffsets)0; +# endif + float cachedDirectionalTerrainParallaxShadow = 1.0; + bool hasCachedDirectionalTerrainParallaxShadow = false; + bool hasCachedTerrainShadowBaseHeight = false; +# endif # else float mipLevel = 0; # endif // LANDSCAPE @@ -982,7 +1012,11 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 vertexNormal = tbnTr[2]; # if defined(EMAT) - if (SharedData::extendedMaterialSettings.EnableParallaxWarpingFix) { + if (SharedData::extendedMaterialSettings.EnableParallaxWarpingFix +# if defined(LANDSCAPE) + && LANDSCAPE_PARALLAX_ENABLED +# endif + ) { float3 ndx = ddx(vertexNormal); float3 ndy = ddy(vertexNormal); float3 fdx = ddx(input.WorldPosition.xyz); @@ -1012,9 +1046,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(EMAT) # if defined(PARALLAX) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) if (SharedData::extendedMaterialSettings.EnableParallax) { - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); - uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler); + uv = ExtendedMaterials::GetParallaxCoords(uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset); + if (SharedData::extendedMaterialSettings.EnableShadows && parallaxShadowQuality > 0.0) sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x; } # endif // defined(PARALLAX) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) @@ -1045,9 +1079,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (complexMaterial) { if (envMaskSample.w > kMaskEpsilon && envMaskSample.w < (1.0 - kMaskEpsilon)) { 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)) + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler); + uv = ExtendedMaterials::GetParallaxCoords(uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset); + if (SharedData::extendedMaterialSettings.EnableShadows && parallaxShadowQuality > 0.0) sh0 = TexEnvMaskSampler.SampleLevel(SampEnvMaskSampler, uv, mipLevel).w; complexMaterialColor = TexEnvMaskSampler.Sample(SampEnvMaskSampler, uv); } else { @@ -1092,9 +1126,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) { displacementParams.HeightScale *= PBRParams1.y; } - mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise); - uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset); - if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows)) + mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler); + uv = ExtendedMaterials::GetParallaxCoords(uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset); + if (SharedData::extendedMaterialSettings.EnableShadows && parallaxShadowQuality > 0.0) sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x; } # endif // !FACEGEN @@ -1139,36 +1173,16 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float4 blendedRMAOS = 0; # endif - // Compute stochastic offsets and derivatives once for all layers (only when terrain variation is enabled) -# if defined(TERRAIN_VARIATION) - bool useTerrainVariation = SharedData::terrainVariationSettings.enableTilingFix; - // Initialise dx, dy, and sharedOffset for when Terrain Variation is disabled via enableTilingFix but still #defined - float2 dx = 0, dy = 0; - StochasticOffsets sharedOffset; - sharedOffset.offset1 = float2(0, 0); - sharedOffset.offset2 = float2(0, 0); - sharedOffset.offset3 = float2(0, 0); - sharedOffset.weights = float3(0, 0, 0); - [branch] if (useTerrainVariation) - { - dx = ddx(input.TexCoord0.zw); - dy = ddy(input.TexCoord0.zw); - sharedOffset = ComputeStochasticOffsets(input.TexCoord0.zw); - } -# 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)) { + if (LANDSCAPE_PARALLAX_ENABLED) { + ExtendedMaterials::InitializeTerrainMipLevels(uv, mipLevels); + [loop] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) + { + terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); +# if defined(TERRAIN_VARIATION) + InitTerrainParallaxStochasticFade(terrainMipIndex, mipLevels[terrainMipIndex]); # 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); + } displacementParams[1] = displacementParams[0]; displacementParams[2] = displacementParams[0]; @@ -1187,13 +1201,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float weights[6]; // Initialize weights array weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0; -# if defined(TERRAIN_VARIATION) - uv = ExtendedMaterials::GetParallaxCoords(input, 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 + uv = ExtendedMaterials::GetParallaxCoords(input, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, weights); if (SharedData::extendedMaterialSettings.EnableHeightBlending) { input.LandBlendWeights1.x = weights[0]; input.LandBlendWeights1.y = weights[1]; @@ -1202,26 +1210,14 @@ 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 + if (SharedData::extendedMaterialSettings.EnableShadows && terrainDirectionalShadowQuality > 0.0) { + float3 dirLightDirectionTS = mul(DirLightDirection, tbn).xyz; + hasCachedTerrainShadowBaseHeight = COMPUTE_TERRAIN_SHADOW_BASE(sh0); + if (hasCachedTerrainShadowBaseHeight) + cachedDirectionalTerrainParallaxShadow = EVAL_TERRAIN_DIR_SHADOW(sh0, dirLightDirectionTS); + hasCachedDirectionalTerrainParallaxShadow = hasCachedTerrainShadowBaseHeight; } } -# 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; @@ -1263,488 +1259,37 @@ 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 + float landDistanceTexMipBias = 0.0; # 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); - } + g_terrainStochasticGrad = ComputeTerrainGradients(uv); + InitTerrainStochasticMip(0, TexColorSampler, landDistanceTexMipBias); + InitTerrainStochasticMip(1, TexLandColor2Sampler, landDistanceTexMipBias); + InitTerrainStochasticMip(2, TexLandColor3Sampler, landDistanceTexMipBias); + InitTerrainStochasticMip(3, TexLandColor4Sampler, landDistanceTexMipBias); + InitTerrainStochasticMip(4, TexLandColor5Sampler, landDistanceTexMipBias); + InitTerrainStochasticMip(5, TexLandColor6Sampler, landDistanceTexMipBias); # 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); +# if !defined(TERRAIN_VARIATION) +# define SampleTerrain(TEX, SAMP, UV, OFFSET, EXTRA_BIAS) TEX.SampleBias(SAMP, UV, SharedData::MipBias + EXTRA_BIAS) # 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); - } + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(0, TexColorSampler, SampColorSampler, TexNormalSampler, SampNormalSampler, TexRMAOSSampler, SampRMAOSSampler, PBRParams1, LandscapeTexture1GlintParameters, input.LandBlendWeights1.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(1, TexLandColor2Sampler, SampLandColor2Sampler, TexLandNormal2Sampler, SampLandNormal2Sampler, TexLandRMAOS2Sampler, SampLandRMAOS2Sampler, LandscapeTexture2PBRParams, LandscapeTexture2GlintParameters, input.LandBlendWeights1.y) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(2, TexLandColor3Sampler, SampLandColor3Sampler, TexLandNormal3Sampler, SampLandNormal3Sampler, TexLandRMAOS3Sampler, SampLandRMAOS3Sampler, LandscapeTexture3PBRParams, LandscapeTexture3GlintParameters, input.LandBlendWeights1.z) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(3, TexLandColor4Sampler, SampLandColor4Sampler, TexLandNormal4Sampler, SampLandNormal4Sampler, TexLandRMAOS4Sampler, SampLandRMAOS4Sampler, LandscapeTexture4PBRParams, LandscapeTexture4GlintParameters, input.LandBlendWeights1.w) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(4, TexLandColor5Sampler, SampLandColor5Sampler, TexLandNormal5Sampler, SampLandNormal5Sampler, TexLandRMAOS5Sampler, SampLandRMAOS5Sampler, LandscapeTexture5PBRParams, LandscapeTexture5GlintParameters, input.LandBlendWeights2.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER_PBR(5, TexLandColor6Sampler, SampLandColor6Sampler, TexLandNormal6Sampler, SampLandNormal6Sampler, TexLandRMAOS6Sampler, SampLandRMAOS6Sampler, LandscapeTexture6PBRParams, LandscapeTexture6GlintParameters, input.LandBlendWeights2.y) # else - float4 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); - } + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(0, TexColorSampler, SampColorSampler, TexNormalSampler, SampNormalSampler, input.LandBlendWeights1.x, LandscapeTexture1to4IsSnow.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(1, TexLandColor2Sampler, SampLandColor2Sampler, TexLandNormal2Sampler, SampLandNormal2Sampler, input.LandBlendWeights1.y, LandscapeTexture1to4IsSnow.y) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(2, TexLandColor3Sampler, SampLandColor3Sampler, TexLandNormal3Sampler, SampLandNormal3Sampler, input.LandBlendWeights1.z, LandscapeTexture1to4IsSnow.z) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(3, TexLandColor4Sampler, SampLandColor4Sampler, TexLandNormal4Sampler, SampLandNormal4Sampler, input.LandBlendWeights1.w, LandscapeTexture1to4IsSnow.w) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(4, TexLandColor5Sampler, SampLandColor5Sampler, TexLandNormal5Sampler, SampLandNormal5Sampler, input.LandBlendWeights2.x, LandscapeTexture5to6IsSnow.x) + LIGHTING_LANDSCAPE_BLEND_ONE_LAYER(5, TexLandColor6Sampler, SampLandColor6Sampler, TexLandNormal6Sampler, SampLandNormal6Sampler, input.LandBlendWeights2.y, LandscapeTexture5to6IsSnow.y) # 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); +# if !defined(TERRAIN_VARIATION) +# undef SampleTerrain # 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); - } -# else - float4 landColor6 = TexLandColor6Sampler.SampleBias(SampLandColor6Sampler, uv, SharedData::MipBias); -# 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); @@ -1769,15 +1314,11 @@ 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); + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + { StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); - float4 lodStochasticColor = StochasticSampleLOD(screenNoise, TexColorSampler, SampColorSampler, uv, lodOffset, dx, dy); - - // Apply the stochastic result directly + float4 lodStochasticColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexColorSampler, SampColorSampler, uv, lodOffset); baseColor.xyz = Color::Diffuse(lodStochasticColor.rgb); } # endif @@ -1922,16 +1463,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float4 lodLandColor; # if defined(TERRAIN_VARIATION) - if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { - // Apply stochastic sampling to LOD_LAND_BLEND color texture - float2 blendColorUV = input.TexCoord0.zw; - float2 dx = ddx(blendColorUV); - float2 dy = ddy(blendColorUV); - StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); - lodLandColor = StochasticSampleLOD(screenNoise, TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset, dx, dy); - } else { - lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); - } + float2 blendColorUV = input.TexCoord0.zw; + StochasticOffsets lodBlendColorOffset = ComputeStochasticOffsetsLOD(blendColorUV); + lodLandColor = StochasticSampleLOD(StochasticSampleLODJitter(screenNoise), TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV, lodBlendColorOffset); # else lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); # endif @@ -2588,33 +2122,25 @@ 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 (LANDSCAPE_PARALLAX_ENABLED && dirLightAngle > 0.0) { + if (hasCachedDirectionalTerrainParallaxShadow) { + dirDetailedShadow *= cachedDirectionalTerrainParallaxShadow; + } else { + float sh0; + bool hasShadowBase = COMPUTE_TERRAIN_SHADOW_BASE(sh0); + if (hasShadowBase) + dirDetailedShadow *= EVAL_TERRAIN_DIR_SHADOW(sh0, dirLightDirectionTS); + } } # elif defined(PARALLAX) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, lerp(parallaxShadowQuality, 1.0, SharedData::extendedMaterialSettings.ExtendShadows), screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(EMAT_ENVMAP) [branch] if (complexMaterialParallax) - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, lerp(parallaxShadowQuality, 1.0, SharedData::extendedMaterialSettings.ExtendShadows), screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(TRUE_PBR) && !defined(LODLANDSCAPE) && !defined(FACEGEN) [branch] if (PBRParallax) - dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, lerp(parallaxShadowQuality, 1.0, SharedData::extendedMaterialSettings.ExtendShadows), screenNoise, displacementParams); + dirDetailedShadow *= ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, dirLightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # endif // LANDSCAPE } # endif // defined(EMAT) && (defined(SKINNED) || !defined(MODELSPACENORMALS)) @@ -2803,16 +2329,15 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) [branch] if (SharedData::extendedMaterialSettings.EnableParallax) parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexParallaxSampler, SampParallaxSampler, 0, parallaxShadowQuality, screenNoise, displacementParams); # elif defined(LANDSCAPE) -# if defined(TRUE_PBR) - if (SharedData::extendedMaterialSettings.EnableParallax) -# else - if (SharedData::extendedMaterialSettings.EnableTerrainParallax || (SharedData::extendedMaterialSettings.EnableParallax && Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement)) -# endif -# if 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 (LANDSCAPE_PARALLAX_ENABLED) { + if (ExtendedMaterials::TerrainHasSignificantBlend(input.LandBlendWeights1, input.LandBlendWeights2.xy) && ExtendedMaterials::TerrainHasAnyDisplacement()) { + float terrainHeightScale = ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams); + if (terrainHeightScale > 0.01) { + float3 lightDirectionTS = normalize(mul(refractedLightDirection, tbn).xyz); + parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplierTerrain(input, uv, terrainShadowMipLevels, lightDirectionTS, sh0, terrainDirectionalShadowQuality, screenNoise, displacementParams, sharedOffset); + } + } + } # elif defined(EMAT_ENVMAP) [branch] if (complexMaterialParallax) parallaxShadow = ExtendedMaterials::GetParallaxSoftShadowMultiplier(uv, mipLevel, lightDirectionTS, sh0, TexEnvMaskSampler, SampEnvMaskSampler, 3, parallaxShadowQuality, screenNoise, displacementParams); @@ -3393,6 +2918,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) } # endif +# if defined(EMAT) +# undef COMPUTE_TERRAIN_SHADOW_BASE +# undef EVAL_TERRAIN_DIR_SHADOW +# undef LANDSCAPE_PARALLAX_ENABLED +# endif + return psout; } #endif // PSHADER diff --git a/src/Features/ExtendedMaterials.cpp b/src/Features/ExtendedMaterials.cpp index 98df52c36d..6eade07216 100644 --- a/src/Features/ExtendedMaterials.cpp +++ b/src/Features/ExtendedMaterials.cpp @@ -10,7 +10,6 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( EnableTerrain, EnableHeightBlending, EnableShadows, - ExtendShadows, EnableParallaxWarpingFix) void ExtendedMaterials::DataLoaded() @@ -78,11 +77,6 @@ void ExtendedMaterials::DrawSettings() "Enables cheap soft shadows when using parallax. " "This applies to all directional and point lights. ")); } - ImGui::Checkbox(T(TKEY("extend_shadows"), "Extend Shadows"), (bool*)&settings.ExtendShadows); - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("%s", T(TKEY("extend_shadows_tooltip"), - "Extends parallax shadows beyond the range of parallax. Small performance impact.")); - } ImGui::Spacing(); ImGui::Spacing(); diff --git a/src/Features/ExtendedMaterials.h b/src/Features/ExtendedMaterials.h index 7254daf12e..c52444abd6 100644 --- a/src/Features/ExtendedMaterials.h +++ b/src/Features/ExtendedMaterials.h @@ -31,10 +31,9 @@ struct ExtendedMaterials : Feature uint EnableHeightBlending = 1; uint EnableShadows = 1; - uint ExtendShadows = 1; uint EnableParallaxWarpingFix = 1; - float pad[1]; + uint pad[2]; }; STATIC_ASSERT_ALIGNAS_16(Settings); diff --git a/src/Features/TerrainVariation.cpp b/src/Features/TerrainVariation.cpp index e383d0152c..653a0c4507 100644 --- a/src/Features/TerrainVariation.cpp +++ b/src/Features/TerrainVariation.cpp @@ -1,38 +1,29 @@ #include "TerrainVariation.h" -#include "../FeatureBuffer.h" -#include "../Globals.h" -#include "../I18n/I18n.h" -#include "../State.h" +#include "I18n/I18n.h" +#include "Menu.h" +#include "Menu/Fonts.h" #include "../Util.h" #define I18N_KEY_PREFIX "feature.terrain_variation." NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( TerrainVariation::Settings, - enableTilingFix, enableLODTerrainTilingFix) void TerrainVariation::DrawSettings() { - bool oldEnabled = settings.enableTilingFix; - ImGui::Checkbox(T(TKEY("enable_tiling_fix"), "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); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("%s", T(TKEY("enable_tiling_fix_tooltip"), - "Reduces the repeating pattern effect on terrain textures.\nThis technique creates more natural-looking terrain by adding variation to texture sampling.")); + { + MenuFonts::FontRoleGuard bodyGuard(Menu::FontRole::Body); + ImGui::TextWrapped( + "Terrain variation is always enabled when installed. Use disable at boot to turn off."); } - ImGui::Separator(); + ImGui::Spacing(); - bool oldLODEnabled = settings.enableLODTerrainTilingFix; - ImGui::Checkbox(T(TKEY("apply_to_lod_terrain"), "Apply to LOD Terrain"), (bool*)&settings.enableLODTerrainTilingFix); - if (oldLODEnabled != (bool)settings.enableLODTerrainTilingFix) { - UpdateShaderSettings(); - logger::info("TerrainVariation LOD setting changed to: {}", settings.enableLODTerrainTilingFix); + bool lodTilingFix = settings.enableLODTerrainTilingFix != 0; + if (ImGui::Checkbox(T(TKEY("apply_to_lod_terrain"), "Apply to LOD Terrain"), &lodTilingFix)) { + settings.enableLODTerrainTilingFix = lodTilingFix ? 1u : 0u; + logger::info("TerrainVariation LOD setting changed to: {}", settings.enableLODTerrainTilingFix != 0); } if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("%s", T(TKEY("apply_to_lod_terrain_tooltip"), @@ -42,28 +33,14 @@ void TerrainVariation::DrawSettings() #undef I18N_KEY_PREFIX -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 66dc538276..1b319eded4 100644 --- a/src/Features/TerrainVariation.h +++ b/src/Features/TerrainVariation.h @@ -13,7 +13,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; LOD terrain variation remains configurable via enableLODTerrainTilingFix. + return loaded && shaderType == RE::BSShader::Type::Lighting; } virtual bool IsCore() const override { return false; }; virtual std::string_view GetCategory() const override { return FeatureCategories::kLandscapeAndTextures; } @@ -27,12 +28,15 @@ struct TerrainVariation : Feature T("feature.terrain_variation.key_feature_4", "Compatible with Extended Materials parallax") } }; }; - struct Settings + struct alignas(16) Settings { - uint enableTilingFix = true; - uint enableLODTerrainTilingFix = true; - float pad0[2]; - } settings; + uint32_t enableLODTerrainTilingFix = 1; + uint32_t pad[3]{}; + }; + + STATIC_ASSERT_ALIGNAS_16(Settings); + + Settings settings; virtual void DrawSettings() override; virtual bool DrawFailLoadMessage() const override; @@ -41,5 +45,4 @@ struct TerrainVariation : Feature virtual void RestoreDefaultSettings() override; virtual void PostPostLoad() override; - void UpdateShaderSettings(); }; \ No newline at end of file