diff --git a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli index 07631d0038..3ca8434d84 100644 --- a/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli @@ -1,593 +1,143 @@ -// 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 +/** + * @file ExtendedMaterials.hlsli + * @brief Extended Materials parallax entry (EMAT). + * @details Includes @ref ExtendedMaterialsTerrain.hlsli (LANDSCAPE) and + * @ref ExtendedMaterialsParallaxCore.hlsli. + * @see https://github.com/tgjones/slimshader-cpp/blob/master/src/Shaders/Sdk/Direct3D11/DetailTessellation11/POM.hlsl + * @see https://github.com/alandtse/SSEShaderTools/blob/main/shaders_vr/ParallaxEffect.h + * @see https://github.com/marselas/Zombie-Direct3D-Samples/blob/5f53dc2d6f7deb32eb2e5e438d6b6644430fe9ee/Direct3D/ParallaxOcclusionMapping/ParallaxOcclusionMapping.fx + * @see http://www.diva-portal.org/smash/get/diva2:831762/FULLTEXT01.pdf + * @see https://bartwronski.files.wordpress.com/2014/03/ac4_gdc.pdf + */ + +#ifndef EXTENDED_MATERIALS_HLSLI +#define EXTENDED_MATERIALS_HLSLI + +/** + * @brief Terrain Variation offsets, or a stub when TERRAIN_VARIATION is unset. + */ +# if defined(LANDSCAPE) +# if defined(TERRAIN_VARIATION) +# include "TerrainVariation/TerrainVariation.hlsli" +# else +struct StochasticOffsets +{ + float2 offset1; + float2 offset2; + float w1Contrast; + float w2Contrast; +}; +# endif +# endif +/** @brief Per-material displacement / parallax scale parameters. */ struct DisplacementParams { - float DisplacementScale; - float DisplacementOffset; - float HeightScale; - float FlattenAmount; + float DisplacementScale; /**< Multiplier around the 0.5 mid-level. */ + float DisplacementOffset; /**< Additive offset after scale. */ + float HeightScale; /**< Height slab scale for POM / shadows. */ + float FlattenAmount; /**< Extra view-ray flatten (parallax warping fix). */ }; 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.5; + static const float TerrainParallaxShadowMaxMipLevel = 2.0; + + /** + * @brief Coarse height-map mip for POM march, contact, and secant. + * @details Applies a distance bias and floors the result. Height-blend weight + * rewrite still uses @ref GetMipLevel. Grazing bias is omitted — + * it creates discontinuous hits. + * @param baseMip Floored mip from @ref GetMipLevel (includes MipBias). + * @param viewDist Camera distance; pass 0 to derive a far floor from @p baseMip. + */ + inline float ComputeParallaxMarchMip(float baseMip, float viewDist) + { + float m = baseMip + 1.0; + m += min(2.0, baseMip * 0.5); + float farFloor = viewDist > 0.0 + ? lerp(0.0, 3.0, saturate((viewDist - 512.0) * rcp(1536.0))) + : saturate(baseMip - 1.0); + m = max(m, farFloor); + return floor(m); + } + + /** + * @brief Number of soft-shadow height taps for the given quality tier. + */ + 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; + } + /** @brief Centers displacement around 0.5 and scales by HeightScale. */ float ScaleDisplacement(float displacement, DisplacementParams params) { return (displacement - 0.5) * params.HeightScale; } + /** @brief Applies DisplacementScale / DisplacementOffset in [0,1] height space. */ float AdjustDisplacementNormalized(float displacement, DisplacementParams params) { return (displacement - 0.5) * params.DisplacementScale + 0.5 + params.DisplacementOffset; } + /** @brief Per-component @ref AdjustDisplacementNormalized. */ float4 AdjustDisplacementNormalized(float4 displacement, DisplacementParams params) { 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) + /** + * @brief Floored anisotropic mip from UV derivatives and known texture size. + */ + float GetMipLevelFromDims(float2 coords, float2 textureDims) { - // 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(max(mipLevel + SharedData::MipBias, 0)); } - // 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) + /** + * @brief Floored anisotropic mip from UV derivatives, plus SharedData::MipBias. + */ + float GetMipLevel(float2 coords, Texture2D tex) { - [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; + float2 textureDims; + tex.GetDimensions(textureDims.x, textureDims.y); + return GetMipLevelFromDims(coords, textureDims); } -#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..2d47bacc0a --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsParallaxCore.hlsli @@ -0,0 +1,323 @@ +/** + * @file ExtendedMaterialsParallaxCore.hlsli + * @brief Parallax occlusion march and refine (namespace ExtendedMaterials). + */ + +#ifndef EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI +#define EXTENDED_MATERIALS_PARALLAX_CORE_HLSLI + +/** + * @brief Computes parallax-occlusion displaced UVs. + * @details Transforms the view into tangent space, marches the height slab in + * steps of four, then refines the hit with binary search and secant. + * Landscape uses a softened view-Z denominator (with FlattenAmount) to + * limit silhouette swim, sizes the step count from UV span in texels, + * and samples height on a coarse march mip for march / contact / secant. + * @param pixelOffset Hit depth along the height slab in [0,1]. + * @param weights [LANDSCAPE] Layer weights after optional height blending. + * @return Displaced texture coordinates. + */ +#if defined(LANDSCAPE) + float2 GetParallaxCoords(PS_INPUT input, float2 coords, float mipLevels[6], float maxTexDim, float3 viewDir, float3x3 tbn, float noise, DisplacementParams params[6], + StochasticOffsets sharedOffset, + out float pixelOffset, + 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) +#endif + { + pixelOffset = 0.0; + float3 viewDirTS = normalize(mul(tbn, viewDir)); + float ndotv = saturate(viewDirTS.z); + +#if defined(LANDSCAPE) + /** Softened view-Z with FlattenAmount; abs + floor limit silhouette stretch. */ + float parallaxZ = max(abs(viewDirTS.z) * 0.7 + 0.3 + params[0].FlattenAmount, 0.0625); + float2 parallaxDir = viewDirTS.xy / parallaxZ; +#else + /** Same soft denom as landscape; abs avoids negative-TS-Z blowups on curved meshes. */ + float parallaxZ = max(abs(viewDirTS.z) * 0.7 + 0.3 + params.FlattenAmount, 0.0625); + float2 parallaxDir = viewDirTS.xy / parallaxZ; +#endif + +#if defined(LANDSCAPE) + float viewDist = length(input.WorldPosition.xyz); + float blendFactor = SharedData::extendedMaterialSettings.EnableHeightBlending ? 1.0 : 0.0; + float4 w1 = input.LandBlendWeights1; + float2 w2 = input.LandBlendWeights2.xy; + const float marchHeightBlendFactor = 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 defined(TRUE_PBR) + float scale = TerrainMaxWeightedHeightScaleW(w1, w2, params); + float terrainHeightNormMul = rcp(max(scale, 1e-4)); + 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; + float2 resultCoords = coords; + +#if defined(LANDSCAPE) && defined(TRUE_PBR) + [branch] if (scale <= 0.001) { + pixelOffset = 0.0; + if (SharedData::extendedMaterialSettings.EnableHeightBlending) { + float unusedHeight = GetTerrainHeight(noise, input, coords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights); + } + } else +#elif !defined(LANDSCAPE) + [branch] if (scale <= 0.001) { + pixelOffset = 0.0; + } else +#endif + { + const uint minSteps = 4; +#if defined(LANDSCAPE) + const uint maxStepsCap = 32; +#else + const uint maxStepsCap = 32; + const float baseMaxSteps = 8; +#endif + + /** Squared grazing factor; near head-on stays cheap. */ + float grazing = (1.0 - ndotv); + grazing *= grazing; + +#if defined(LANDSCAPE) + float marchMip = ComputeParallaxMarchMip(mipLevels[0], viewDist); + float marchMipLevels[6]; + float parallaxLODMip = mipLevels[0]; + [unroll] for (uint mi = 0; mi < 6; mi++) + marchMipLevels[mi] = marchMip; +#else + float parallaxLODMip = mipLevel; + float marchMipLevel = ComputeParallaxMarchMip(mipLevel, 0.0); +#endif + float distStepScale = lerp(0.25, 1.0, saturate((3.0 - parallaxLODMip) * (1.0 / 3.0))); + +#if defined(LANDSCAPE) + /** + * Step count from UV travel in texels (and a grazing angle floor), + * so grazing rays do not skip height features between samples. + */ + float uvMarchSpan = dot(abs(parallaxDir), maxHeight + minHeight); + float texelsPerStep = lerp(3.5, 1.75, saturate(grazing)); + uint uvSteps = (uint)(uvMarchSpan * maxTexDim * rcp(texelsPerStep) * distStepScale + 0.5); + uint angleSteps = (uint)(lerp((float)minSteps, (float)maxStepsCap, grazing) * distStepScale + 0.5); + uint numSteps = max(minSteps, max(uvSteps, angleSteps)); + numSteps = min(numSteps, maxStepsCap); + numSteps = (numSteps + 2) & ~3; +#else + float grazingStepBoost = lerp(1.0, 1.65, grazing); + float angleStepMul = clamp(0.5 * rcp(max(ndotv, 0.0625)), 0.5, 2.5); + uint numSteps = max(minSteps, (uint)(scale * baseMaxSteps * angleStepMul * distStepScale * grazingStepBoost)); + numSteps = min(numSteps, maxStepsCap); + numSteps = (numSteps + 2) & ~3; +#endif + + uint contactIters = grazing > 0.2 ? 4u : 2u; + uint secantIters = grazing > 0.25 ? 2u : 1u; + + float stepSize = rcp((float)numSteps); + + float2 offsetPerStep = parallaxDir * maxHeight * stepSize; +#if defined(LANDSCAPE) + /** Full-step ray-start dither breaks residual step bands on terrain. */ + float rayDither = saturate(noise); + float2 prevOffset = parallaxDir * minHeight + coords.xy - offsetPerStep * rayDither; + float prevBound = 1.0 - rayDither * stepSize; +#else + float2 prevOffset = parallaxDir * minHeight + coords.xy; + float prevBound = 1.0; +#endif + float prevHeight = 1.0; + + float2 pt1 = 0; + float2 pt2 = 0; + bool intersectionFound = false; + + [loop] while (numSteps > 0) + { + float4 currentOffset[2]; + currentOffset[0] = prevOffset.xyxy - float4(1, 1, 2, 2) * offsetPerStep.xyxy; + currentOffset[1] = prevOffset.xyxy - float4(3, 3, 4, 4) * offsetPerStep.xyxy; + float4 currentBound = prevBound.xxxx - float4(1, 2, 3, 4) * stepSize; + + float4 currHeight; +#if defined(LANDSCAPE) + currHeight = GetTerrainHeightQuadRayMarch(noise, input, currentOffset[0].xy, currentOffset[0].zw, currentOffset[1].xy, currentOffset[1].zw, marchMipLevels, params, marchHeightBlendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + currHeight.x = tex.SampleLevel(texSampler, currentOffset[0].xy, marchMipLevel)[channel]; + currHeight.y = tex.SampleLevel(texSampler, currentOffset[0].zw, marchMipLevel)[channel]; + currHeight.z = tex.SampleLevel(texSampler, currentOffset[1].xy, marchMipLevel)[channel]; + currHeight.w = tex.SampleLevel(texSampler, currentOffset[1].zw, marchMipLevel)[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) + { + float tNear = pt1.x; + float hNear = pt1.y; + float fNear = hNear - tNear; + float tFar = pt2.x; + float hFar = pt2.y; + float fFar = hFar - tFar; + + /** Binary search on f(t) = h(t) - t before secant. */ + [loop] for (uint c = 0; c < contactIters; c++) + { + float tMid = 0.5 * (tNear + tFar); + float2 midCoords = coords.xy + parallaxDir * (((1.0 - tMid) * -maxHeight) + minHeight); + float hMid = 0.0; +#if defined(LANDSCAPE) + hMid = GetTerrainHeight(noise, input, midCoords, marchMipLevels, params, marchHeightBlendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + hMid = tex.SampleLevel(texSampler, midCoords, marchMipLevel)[channel]; + hMid = AdjustDisplacementNormalized(hMid, params); +#endif + float fMid = hMid - tMid; + [branch] if (fMid >= 0.0) + { + tNear = tMid; + hNear = hMid; + fNear = fMid; + } + else + { + tFar = tMid; + hFar = hMid; + fFar = fMid; + } + } + + /** Secant iterations on f(t) = h(t) - t. */ + [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, marchMipLevels, params, marchHeightBlendFactor, w1, w2, sharedOffset, weights) * terrainHeightNormMul + 0.5; +#else + hSecant = tex.SampleLevel(texSampler, secantCoords, marchMipLevel)[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 = GetTerrainHeight(noise, input, finalCoords, mipLevels, params, blendFactor, w1, w2, sharedOffset, weights); + } +#endif + resultCoords = finalCoords; + } + return resultCoords; + } + +# if !defined(LANDSCAPE) + /** + * @brief Soft self-shadow from a height map along light direction L. + * @see https://advances.realtimerendering.com/s2006/Tatarchuk-POM.pdf + */ + 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..be861bb4e4 --- /dev/null +++ b/features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterialsTerrain.hlsli @@ -0,0 +1,388 @@ +/** + * @file ExtendedMaterialsTerrain.hlsli + * @brief Landscape height sampling and blending (namespace ExtendedMaterials, LANDSCAPE). + */ + +#ifndef EXTENDED_MATERIALS_TERRAIN_HLSLI +#define EXTENDED_MATERIALS_TERRAIN_HLSLI + + /** + * @brief Shared mip for all six landscape layers from color-map UV derivatives. + * @param maxTexDim Max of color-map width/height for POM step sizing (one GetDimensions). + */ + void InitializeTerrainMipLevels(float2 coords, out float mipLevels[6], out float maxTexDim) + { + float2 textureDims; + TexColorSampler.GetDimensions(textureDims.x, textureDims.y); + maxTexDim = max(textureDims.x, textureDims.y); + float mip = GetMipLevelFromDims(coords, textureDims); + [unroll] for (uint i = 0; i < 6; i++) + mipLevels[i] = mip; + } + + /** + * @brief Height / displacement texel; uses StochasticEffectParallax when TERRAIN_VARIATION is set. + */ + inline float4 TerrainParallaxTexSample(Texture2D tex, float2 uv, float mipLevel, StochasticOffsets sharedOffset, uint layerIndex) + { +# if defined(TERRAIN_VARIATION) + return StochasticEffectParallax(tex, SampTerrainParallaxSampler, uv, mipLevel, sharedOffset); +# else + return tex.SampleLevel(SampTerrainParallaxSampler, uv, mipLevel); +# endif + } + +# define HEIGHT_POWER 2 +# define HEIGHT_MULT 8 + + /** + * @brief Relative weight threshold below which a layer is skipped during linear height blends. + * @details Compared against weight × HeightScale as a fraction of the dominant layer. + * Vertex weights change smoothly, so layers fade out before the gate trips. + */ + static const float TERRAIN_LAYER_GATE_EPS = 0.02; + + /** @brief Maximum of weight × HeightScale across the six layers. */ + inline float TerrainMaxWeightedHeightScaleW(float4 w1, float2 w2, DisplacementParams params[6]) + { + return 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))))); + } + + /** + * @brief Layer gate for one height evaluation. + * @details Applied when @p heightBlend ≤ 1 (march / secant / shadows). Height-sharpened + * weight passes (@p heightBlend > 1) keep every layer so small weights can still rise. + */ + inline float TerrainLayerGateThreshold(float heightBlend, float4 w1, float2 w2, DisplacementParams params[6]) + { + return heightBlend <= 1.0 ? TERRAIN_LAYER_GATE_EPS * TerrainMaxWeightedHeightScaleW(w1, w2, params) : 0.0; + } + + /** @brief Dot product of per-layer heights and weights. */ + float TerrainWeightedHeightSum(float heights[6], float weights[6]) + { + float totalHeight = 0; + [unroll] for (int i = 0; i < 6; i++) + { + totalHeight += heights[i] * weights[i]; + } + return totalHeight; + } + + /** + * @brief Normalizes landscape weights and optionally height-sharpens them. + * @param heightBlend 1 = linear weights; greater than 1 enables height power blending. + * @details Height sharpening is skipped when all layer heights are nearly equal, so + * pow() is not applied to vertex weights alone (which hardens triangle borders). + */ + 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; + + float wsum = 0; + [unroll] for (int j = 0; j < 6; j++) + { + wsum += weights[j]; + } + float invwsum = rcp(max(wsum, 1e-6)); + [unroll] for (int k = 0; k < 6; k++) + { + weights[k] *= invwsum; + } + + bool sharpen = heightBlend > 1.0; + [branch] if (sharpen) + { + float hMin = heights[0]; + float hMax = heights[0]; + [unroll] for (int hi = 1; hi < 6; hi++) + { + hMin = min(hMin, heights[hi]); + hMax = max(hMax, heights[hi]); + } + sharpen = (hMax - hMin) > 1e-3; + } + + [branch] if (sharpen) + { + float logHeightBlend = log2(max(abs(heightBlend), 0.0001)); + [unroll] for (int hbIdx = 0; hbIdx < 6; hbIdx++) + { + weights[hbIdx] *= exp2((HEIGHT_MULT * heights[hbIdx]) * logHeightBlend); + } + + [unroll] for (int j = 0; j < 6; j++) + { + weights[j] = min(100, pow(abs(weights[j]), max(abs(heightBlend), 0.0001))); + } + + wsum = 0; + [unroll] for (int k = 0; k < 6; k++) + { + wsum += weights[k]; + } + invwsum = rcp(max(wsum, 1e-6)); + [unroll] for (int l = 0; l < 6; l++) + { + weights[l] *= invwsum; + } + } + + [unroll] for (int t = 0; t < 6; t++) + { + totalHeight += heights[t] * weights[t]; + } + } + + /** + * @brief Blends four per-layer height vectors equivalently to four @ref GetTerrainHeight calls. + * @param weights Weights from the last UV (tap 3). + */ + 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; + 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) + +/** @note Pass fully scoped PBR::TerrainFlags; FXC does not expand macros inside `::`. */ +#define EM_PBR_DISP_LAYER_SCALAR(N, TILEFLAG, TEX, WGT) \ + [branch] if ((PBRFlags & (TILEFLAG)) != 0 && (WGT) > 0.01 && (WGT)*params[N].HeightScale >= layerGateThreshold) \ + { \ + 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 && (WGT)*params[N].HeightScale >= layerGateThreshold) \ + { \ + [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) + + /** @brief Weighted terrain height (PBR displacement maps). */ + 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 layerGateThreshold = TerrainLayerGateThreshold(heightBlend, w1, w2, params); + 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; + } + + /** @brief Four-UV height sample for coarse ray-march steps (PBR). */ + 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; + float layerGateThreshold = TerrainLayerGateThreshold(heightBlend, w1, w2, params); + float2 uvs[4] = { u0, u1, u2, u3 }; + float h4[4][6]; + [unroll] for (uint qi = 0; qi < 4; qi++) + [unroll] for (uint lj = 0; lj < 6; lj++) + h4[qi][lj] = 0; + + EM_PBR_DISP_FOREACH(EM_PBR_DISP_LAYER_QUAD) + + return FinishTerrainHeightQuadBlend(heightBlend, w1, w2, h4[0], h4[1], h4[2], h4[3], weights); + } + +#undef EM_PBR_DISP_LAYER_SCALAR +#undef EM_PBR_DISP_LAYER_QUAD +#undef EM_PBR_DISP_FOREACH + +# else + +#define EM_LEGACY_LAYER_SCALAR(N, THFLAG, THSAMPLER, COLSAMPLER, WGT) \ + if ((WGT) > 0.01 && (WGT)*params[N].HeightScale >= layerGateThreshold) { \ + [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_LAYER_QUAD(N, THFLAG, THSAMPLER, COLSAMPLER, WGT) \ + if ((WGT) > 0.01 && (WGT)*params[N].HeightScale >= layerGateThreshold) { \ + [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_FOREACH(M) \ + M(0, Permutation::ExtraFeatureFlags::THLand0HasDisplacement, TexLandTHDisp0Sampler, TexColorSampler, w1.x) \ + M(1, Permutation::ExtraFeatureFlags::THLand1HasDisplacement, TexLandTHDisp1Sampler, TexLandColor2Sampler, w1.y) \ + M(2, Permutation::ExtraFeatureFlags::THLand2HasDisplacement, TexLandTHDisp2Sampler, TexLandColor3Sampler, w1.z) \ + M(3, Permutation::ExtraFeatureFlags::THLand3HasDisplacement, TexLandTHDisp3Sampler, TexLandColor4Sampler, w1.w) \ + M(4, Permutation::ExtraFeatureFlags::THLand4HasDisplacement, TexLandTHDisp4Sampler, TexLandColor5Sampler, w2.x) \ + M(5, Permutation::ExtraFeatureFlags::THLand5HasDisplacement, TexLandTHDisp5Sampler, TexLandColor6Sampler, w2.y) + + /** @brief Weighted terrain height (legacy TH / color-alpha displacement). */ + 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 layerGateThreshold = TerrainLayerGateThreshold(heightBlend, w1, w2, params); + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + + EM_LEGACY_FOREACH(EM_LEGACY_LAYER_SCALAR) + + float total = 0.0; + ProcessTerrainHeightWeights(heightBlend, w1, w2, heights, weights, total); + return total; + } + + /** @brief Four-UV height sample for coarse ray-march steps (legacy). */ + 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; + float layerGateThreshold = TerrainLayerGateThreshold(heightBlend, w1, w2, params); + float2 uvs[4] = { u0, u1, u2, u3 }; + float h4[4][6]; + [unroll] for (uint qi = 0; qi < 4; qi++) + [unroll] for (uint lj = 0; lj < 6; lj++) + h4[qi][lj] = 0; + + EM_LEGACY_FOREACH(EM_LEGACY_LAYER_QUAD) + + return FinishTerrainHeightQuadBlend(heightBlend, w1, w2, h4[0], h4[1], h4[2], h4[3], weights); + } + +#undef EM_LEGACY_LAYER_SCALAR +#undef EM_LEGACY_LAYER_QUAD +#undef EM_LEGACY_FOREACH + +# 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) + + /** @brief True when any landscape layer has displacement available. */ + inline bool TerrainHasAnyDisplacement() + { +# if defined(TRUE_PBR) + return (PBRFlags & TERRAIN_DISPLACEMENT_MASK) != 0; +# else + return SharedData::extendedMaterialSettings.EnableTerrainParallax || + (Permutation::ExtraFeatureDescriptor & Permutation::ExtraFeatureFlags::THLandHasDisplacement) != 0; +# endif + } + + /** @brief Max height scale weighted by current land blend weights. */ + inline float TerrainMaxWeightedHeightScale(PS_INPUT input, DisplacementParams params[6]) + { + return TerrainMaxWeightedHeightScaleW(input.LandBlendWeights1, input.LandBlendWeights2.xy, params); + } + + /** + * @brief Tap count for directional terrain parallax soft shadows. + * @details Each tap is a full six-layer height blend; one tap is used. + */ + inline uint TerrainDirectionalShadowTapCount(float quality) + { + return quality > 0.0 ? 1u : 0u; + } + + /** + * @brief Base height for terrain parallax shadows. + * @return false when the surface has no displacement. + */ + 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 (!TerrainHasAnyDisplacement()) + return false; + + float weights[6] = { 0, 0, 0, 0, 0, 0 }; + sh0 = TERRAIN_HEIGHT_AT(coords, mipLevels, quality, weights); + return true; + } + + /** + * @brief Terrain parallax soft shadow along light L (point lights). + * @details Single tap; intensity uses the same 4/tapCount scale as quality tiers. + */ + 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) { + /** Single tap; strength matches the 4/tapCount object-shadow scale. */ + float shadowStrength = ShadowIntensity * 4.0; + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = L.xy * 0.1; + float shi = TERRAIN_HEIGHT_AT(coords + rayDir * rcp(1.0 + noise), mipLevel, quality, heights); + return 1.0 - saturate(max(0, shi - sh0) * shadowStrength); + } + return 1.0; + } + + /** @brief Directional-light terrain parallax soft shadow. */ + float EvaluateTerrainDirectionalParallaxShadowMultiplier(PS_INPUT input, float2 coords, float mipLevels[6], float3 lightDirection, float quality, float noise, DisplacementParams params[6], StochasticOffsets sharedOffset, float sh0) + { + if (TerrainDirectionalShadowTapCount(quality) == 0) + return 1.0; + float shadowStrength = ShadowIntensity * 2.0; + + float heights[6] = { 0, 0, 0, 0, 0, 0 }; + float2 rayDir = lightDirection.xy * 0.1; + float shi = TERRAIN_HEIGHT_AT(coords + rayDir * rcp(1.0 + noise), mipLevels, quality, heights); + return 1.0 - saturate(max(0, shi - sh0) * shadowStrength); + } + +# undef TERRAIN_HEIGHT_AT + +#endif // EXTENDED_MATERIALS_TERRAIN_HLSLI 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..312283d442 100644 --- a/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli +++ b/features/Terrain Variation/Shaders/TerrainVariation/TerrainVariation.hlsli @@ -1,203 +1,221 @@ -// Implements stochastic noise sampling for terrain textures to reduce tiling artifacts and improve visual quality. -// Based on paper "Procedural Stochastic Textures by Tiling and Blending" by Thomas Deliot & Eric Heitz. -// https://eheitzresearch.wordpress.com/722-2/ +/** + * @file TerrainVariation.hlsli + * @brief Stochastic terrain tiling (Deliot & Heitz). + * @see https://eheitzresearch.wordpress.com/722-2/ + */ #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 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 +/** @brief Mix of stochastic vs height-driven tap weights in @ref StochasticBlendTwoSamples. */ +static const float HEIGHT_INFLUENCE = 0.3; +static const float3 LUMINANCE_WEIGHTS = float3(0.2126, 0.7152, 0.0722); +/** @brief Barycentric contrast for the 2-tap path (milder than classic Heitz ~8). */ +static const float STOCHASTIC_WEIGHT_POWER = 2.0; +/** @brief Fade width for c1/c2 rank-swap seams in @ref ComputeStochasticOffsets. */ +static const float TAP_SWAP_FADE_WIDTH = 0.1; +/** @brief Narrow guard that disables the rank-swap fade near c0/c1 ties. */ +static const float DOMINANCE_GUARD_WIDTH = 0.02; +/** @brief w2/w1 ratio below which the 2nd tap collapses onto tap 1. */ +static const float TAP2_COLLAPSE_RATIO_LO = 0.02; +/** @brief w2/w1 ratio where the 2nd-tap collapse fade begins. */ +static const float TAP2_COLLAPSE_RATIO_HI = 0.04; +/** @brief Golden ratio for LOD sample jitter. */ +static const float STOCHASTIC_LOD_PHI = 1.618; +/** @brief LOD two-tap blend weight (callers gate on enableLODTerrainTilingFix). */ +static const float STOCHASTIC_LOD_BLEND = 0.65; + +/** + * @brief Per-pixel stochastic UV offsets and blend weights for near terrain. + * @details Lives across the POM ray march; keep fields minimal. + */ struct StochasticOffsets { float2 offset1; float2 offset2; - float2 offset3; - float3 weights; + float w1Contrast; + float w2Contrast; +}; + +/** @brief Shared ddx/ddy for landscape UVs (set once before the six-way blend). */ +struct TerrainGradients +{ + float2 gradDx; + float2 gradDy; }; -// --------------------- 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); +static TerrainGradients g_terrainStochasticGrad; -// --------------------- COMPUTE FUNCTIONS --------------------- // +/** @brief Triangle corner (cell id + barycentric weight) for sorting. */ +struct StochasticCorner +{ + float2 cell; + float w; +}; -// Hash function for stochastic sampling +/** @brief 2D hash for near-terrain stochastic offsets. */ 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); } +/** @brief 2D hash for LOD terrain stochastic offsets. */ 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) +/** + * @brief Landscape UV gradients for SampleGrad. + * @details Scales by @c exp2(MipBias) to match SampleBias sharpening under upscaling. + */ +inline TerrainGradients ComputeTerrainGradients(float2 uv) { - 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; + TerrainGradients g; + float biasScale = exp2(SharedData::MipBias); + g.gradDx = ddx(uv) * biasScale; + g.gradDy = ddy(uv) * biasScale; + return g; } -// Common barycentric coordinate calculation for stochastic sampling -inline float4x3 ComputeBarycentricVerts(float2 landscapeUV) +/** + * @brief Builds 2-tap stochastic offsets from landscape UV (triangular grid). + * @details Keeps the two highest barycentric corners. Fades w2 near c1/c2 ties + * (@ref TAP_SWAP_FADE_WIDTH) so the discarded corner cannot pop identity; + * @ref DOMINANCE_GUARD_WIDTH disables that fade near c0/c1 ties so the + * primary swap stays symmetric. Collapses the 2nd tap when w2/w1 is tiny + * (@ref TAP2_COLLAPSE_RATIO_LO / @ref TAP2_COLLAPSE_RATIO_HI). + */ +inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) { - float2 scaledUV = landscapeUV * (WORLD_SCALE); - float2 skewUV = mul(SKEW_MATRIX, scaledUV); + float2 skewUV = mul(SKEW_MATRIX, landscapeUV * WORLD_SCALE); float2 vxID = floor(skewUV); - float2 frac_uv = frac(skewUV); + float2 f = frac(skewUV); + float bz = 1.0 - f.x - f.y; + + StochasticCorner c0, c1, c2; + if (bz > 0) { + c0.cell = vxID; + c0.w = bz; + c1.cell = vxID + float2(0, 1); + c1.w = f.y; + c2.cell = vxID + float2(1, 0); + c2.w = f.x; + } else { + c0.cell = vxID + 1.0; + c0.w = -bz; + c1.cell = vxID + float2(1, 0); + c1.w = 1.0 - f.y; + c2.cell = vxID + float2(0, 1); + c2.w = 1.0 - f.x; + } - float barry_z = 1.0 - frac_uv.x - frac_uv.y; - float3 barry = float3(frac_uv, barry_z); + if (c1.w > c0.w) { + StochasticCorner t = c0; + c0 = c1; + c1 = t; + } + if (c2.w > c0.w) { + StochasticCorner t = c0; + c0 = c2; + c2 = t; + } + if (c2.w > c1.w) { + StochasticCorner t = c1; + c1 = c2; + c2 = t; + } - return (barry.z > 0) ? - float4x3(float3(vxID, 0), float3(vxID + float2(0, 1), 0), float3(vxID + float2(1, 0), 0), barry.zyx) : - float4x3(float3(vxID + float2(1, 1), 0), float3(vxID + float2(1, 0), 0), float3(vxID + float2(0, 1), 0), float3(-barry.z, 1.0 - barry.y, 1.0 - barry.x)); + StochasticOffsets o; + o.offset1 = hash2D2D(c0.cell); + o.w1Contrast = pow(saturate(c0.w), STOCHASTIC_WEIGHT_POWER); + float rankFade = smoothstep(0.0, TAP_SWAP_FADE_WIDTH, c1.w - c2.w); + float dominanceGuard = smoothstep(0.0, DOMINANCE_GUARD_WIDTH, c0.w - c1.w); + float w2Contrast = pow(saturate(c1.w), STOCHASTIC_WEIGHT_POWER) * lerp(1.0, rankFade, dominanceGuard); + w2Contrast *= smoothstep(TAP2_COLLAPSE_RATIO_LO, TAP2_COLLAPSE_RATIO_HI, w2Contrast / max(o.w1Contrast, 1e-6)); + o.w2Contrast = w2Contrast; + o.offset2 = w2Contrast > 0.0 ? hash2D2D(c1.cell) : o.offset1; + return o; } -inline StochasticOffsets ComputeStochasticOffsets(float2 landscapeUV) +/** + * @brief Height-aware blend of two stochastic taps. + * @details At w2Contrast == 0 returns exactly s1. + */ +inline float4 StochasticBlendTwoSamples(float4 s1, float4 s2, float w1Contrast, float w2Contrast, float blendFactor1, float blendFactor2) { - float4x3 BW_vx = ComputeBarycentricVerts(landscapeUV); - - 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]; - - return offsets; + float w1 = w1Contrast * (1.0 + HEIGHT_INFLUENCE * blendFactor1); + float w2 = w2Contrast * (1.0 + HEIGHT_INFLUENCE * blendFactor2); + float denom = max(w1 + w2, 1e-8); + return lerp(s2, s1, w1 / denom); } -inline StochasticOffsets ComputeStochasticOffsetsLOD(float2 landscapeUV) +/** + * @brief Two-tap SampleBias LOD terrain sampling. + * @details Call only when enableLODTerrainTilingFix is on; builds cell hashes and jitter internally. + * @param rnd Screen noise in [0,1] for low-discrepancy UV jitter. + */ +inline float4 StochasticSampleLOD(float rnd, Texture2D tex, SamplerState samp, float2 uv) { - // Precomputed scaling: (WORLD_SCALE / 0.010416667) * 8.0 = ~255437 - static const float LOD_SCALE = 255437.0; - - float2 scaledUV = landscapeUV * LOD_SCALE; - float2 cellID = floor(scaledUV); - - StochasticOffsets offsetsLOD; - // Generate both offsets from single hash to reduce calls - float2 hash1 = hashLOD(cellID); - float2 hash2 = hashLOD(cellID + 127.0); - - offsetsLOD.offset1 = hash1 * 0.08; - offsetsLOD.offset2 = hash2 * 0.08; - - // Simplified weights since we only use 2 samples now - offsetsLOD.weights = float3(0.65, 0.35, 0.0); - - return offsetsLOD; + float2 cellID = floor(uv * 255437.0); + float2 offset1 = hashLOD(cellID) * 0.08; + float2 offset2 = hashLOD(cellID + 127.0) * 0.08; + float2 jitter = float2(rnd - 0.5, frac(rnd * STOCHASTIC_LOD_PHI) - 0.5); + float2 j1 = (offset1 + jitter) * 0.01; + float2 j2 = (offset2 + float2(jitter.y, -jitter.x)) * 0.01; + float4 s1 = tex.SampleBias(samp, uv + j1, SharedData::MipBias); + float4 s2 = tex.SampleBias(samp, uv + j2, SharedData::MipBias); + return lerp(s2, s1, STOCHASTIC_LOD_BLEND); } -// --------------------- 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) +/** + * @brief Two-tap SampleGrad stochastic sampling for near terrain albedo/normals. + * @details Uses @ref g_terrainStochasticGrad. Skips the 2nd fetch when w2Contrast == 0. + */ +inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets) { - 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); - - // 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); - - // Simple 2-sample blend weighted toward first sample - return lerp(sample2, sample1, 0.65); -} + TerrainGradients g = g_terrainStochasticGrad; + float4 s1 = tex.SampleGrad(samp, uv + offsets.offset1, g.gradDx, g.gradDy); + float4 result; + + [branch] if (offsets.w2Contrast <= 0.0) + result = s1; + else { + float4 s2 = tex.SampleGrad(samp, uv + offsets.offset2, g.gradDx, g.gradDy); + 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)); + result = StochasticBlendTwoSamples(s1, s2, offsets.w1Contrast, offsets.w2Contrast, h1, h2); + } -// Main stochastic sampling function -inline float4 StochasticEffect(Texture2D tex, SamplerState samp, float2 uv, StochasticOffsets offsets, float2 dx, float2 dy) -{ - // Calculate custom mip level from original UVs. - float mipLevel = tex.CalculateLevelOfDetail(samp, uv); - float adjustedMipLevel = mipLevel + SharedData::MipBias; - - // 3 Sample Blend - float4 sample1 = tex.SampleLevel(samp, uv + offsets.offset1, adjustedMipLevel); - float4 sample2 = tex.SampleLevel(samp, uv + offsets.offset2, adjustedMipLevel); - float4 sample3 = tex.SampleLevel(samp, uv + offsets.offset3, adjustedMipLevel); - - // Full height-based blending for terrain - float contrastFactor = HEIGHT_BLEND_CONTRAST * (1.0 - HEIGHT_INFLUENCE); - float3 blendWeights = pow(saturate(offsets.weights), contrastFactor); - - // Height calculation - use luminance for RGB data, alpha when available - float3 luminanceHeights = float3( - dot(sample1.rgb, LUMINANCE_WEIGHTS), - dot(sample2.rgb, LUMINANCE_WEIGHTS), - dot(sample3.rgb, LUMINANCE_WEIGHTS)); - - float3 alphaValues = float3(sample1.a, sample2.a, sample3.a); - float3 alphaMask = step(0.001, alphaValues); - float3 heights = lerp(luminanceHeights, alphaValues, alphaMask); - - // Combined weight calculation and normalization - float3 weights = NormalizeWeights(blendWeights * (1.0 + HEIGHT_INFLUENCE * heights)); - - // Final blend - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; + return result; } -// 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) +/** + * @brief Two-tap SampleLevel stochastic sampling for parallax/height. + * @details Skips the 2nd fetch when w2Contrast == 0 (same as @ref StochasticEffect). + * Same offsets as albedo so height stays aligned. + */ +inline float4 StochasticEffectParallax(Texture2D tex, SamplerState samp, float2 uv, float mipLevel, StochasticOffsets offsets) { - // Early exit for disabled terrain variation - avoid all other computations - if (!SharedData::terrainVariationSettings.enableTilingFix) { - return tex.SampleLevel(samp, uv, mipLevel); + float4 s1 = tex.SampleLevel(samp, uv + offsets.offset1, mipLevel); + float4 result; + + [branch] if (offsets.w2Contrast <= 0.0) + result = s1; + else { + float4 s2 = tex.SampleLevel(samp, uv + offsets.offset2, mipLevel); + result = StochasticBlendTwoSamples(s1, s2, offsets.w1Contrast, offsets.w2Contrast, s1.a, s2.a); } - // 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); - - // Simple barycentric blend without height influence - float3 weights = NormalizeWeights(saturate(offsets.weights)); - return sample1 * weights.x + sample2 * weights.y + sample3 * weights.z; + return result; } -#pragma warning(pop) -#endif // TERRAIN_VARIATION_HLSLI \ No newline at end of file +#endif // TERRAIN_VARIATION_HLSLI diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index 3bd82e4311..dc026e01e6 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", @@ -1373,8 +1371,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/ru.json b/package/SKSE/Plugins/CommunityShaders/Translations/ru.json index 2fc4d6a2f8..88f0e44188 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/ru.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/ru.json @@ -621,8 +621,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": "Поддержка карт высот рельефа", @@ -1231,8 +1229,6 @@ "feature.terrain_variation.apply_to_lod_terrain": "Применить к LOD рельефа", "feature.terrain_variation.apply_to_lod_terrain_tooltip": "Применяет исправление тайлинга к LOD-объектам рельефа.\nПомогает уменьшить видимый эффект тайлинга на дальнем рельефе.", "feature.terrain_variation.description": "Terrain Variation уменьшает эффект повторяющегося паттерна на текстурах рельефа.\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/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json index 5e9bda1fde..8d5dbca860 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json @@ -618,9 +618,7 @@ "feature.extended_materials.enable_parallax_warping_fix": "启用视差变形修复", "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.enable_shadows_tooltip": "使用视差时启用廉价软阴影。适用于所有方向光和点光源。", "feature.extended_materials.key_feature_1": "视差遮蔽映射,增加深度感", "feature.extended_materials.key_feature_2": "复杂材质混合", "feature.extended_materials.key_feature_3": "地形高度图支持", @@ -1247,8 +1245,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..6a431bb818 --- /dev/null +++ b/package/Shaders/Common/LightingLandscape.hlsli @@ -0,0 +1,95 @@ +/** + * @file LightingLandscape.hlsli + * @brief Landscape layer helpers and six-way blend macros for Lighting.hlsl. + * @details PBR tile bits must match @c PBR::TerrainFlags in PBRMath.hlsli. + * Texture registers cannot be indexed by loop variable; hence one macro expansion per layer. + */ + +#ifndef __LIGHTING_LANDSCAPE_HLSLI__ +#define __LIGHTING_LANDSCAPE_HLSLI__ + +#if defined(LANDSCAPE) + +# if !defined(__PERMUTATION_DEPENDENCY_HLSL__) +# include "Common/Permutation.hlsli" +# endif + +# if defined(TRUE_PBR) +namespace LandscapeLayers +{ + /** @brief True if tile uses full PBR material. */ + inline bool PbrTileUsesFullPBR(uint tileIndex) + { + return (PBRFlags & (1u << tileIndex)) != 0; + } + /** @brief True if tile has glint. */ + inline bool PbrTileHasGlint(uint tileIndex) + { + return (PBRFlags & (1u << (tileIndex + 12u))) != 0; + } +} +# endif + +/** @brief Per-layer landscape diffuse/normal(/RMAOS) blend body used by Lighting.hlsl. */ +# 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, landDistanceTexMipBias); \ + float3 landColorRGB = landColor.rgb; \ + [branch] if (!LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landColorRGB = Color::SrgbToLinear(landColorRGB / Color::PBRLightingScale); \ + } \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + float4 landRMAOS; \ + [branch] if (LandscapeLayers::PbrTileUsesFullPBR(TILE)) \ + { \ + landRMAOS = SampleTerrain(RMAOS_TEX, RMAOS_SAMP, uv, sharedOffset, landDistanceTexMipBias) * float4((PBR_PARAMS3).x, 1, 1, (PBR_PARAMS3).z); \ + [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, landDistanceTexMipBias); \ + float3 landColorRGB = landColor.rgb; \ + float landAlpha = landColor.a; \ + float4 landNormal = SampleTerrain(NORM_TEX, NORM_SAMP, uv, sharedOffset, landDistanceTexMipBias); \ + float3 landNormalRGB = landNormal.rgb; \ + float landNormalAlpha = landNormal.a; \ + blendedRGB += landColorRGB * weight; \ + blendedAlpha += landAlpha * weight; \ + blendedNormalRGB += landNormalRGB * weight; \ + blendedNormalAlpha += landNormalAlpha * weight; \ + LIGHTING_LAND_SNOW_ACCUM(SNOW_COMPONENT) \ + } +# endif + +#endif // LANDSCAPE + +#endif // __LIGHTING_LANDSCAPE_HLSLI__ diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 4902e7abae..87a20ef2eb 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 @@ -185,11 +184,11 @@ namespace SharedData uint3 pad; }; + /** @brief Terrain Variation feature settings. */ struct TerrainVariationSettings { - uint enableTilingFix; - uint enableLODTerrainTilingFix; - float2 pad0; + uint enableLODTerrainTilingFix; ///< 1 = apply variation to LOD terrain. + uint3 pad; }; struct IBLSettings diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 90ec257f1e..36cb618581 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -859,7 +859,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 @@ -954,11 +958,36 @@ 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; +# 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) +# if defined(EMAT) float mipLevels[6]; + 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; + /** Pixel-invariant terrain POM soft-shadow eligibility for the point-light loop. */ + bool hasTerrainParallaxShadow = false; +# endif # else float mipLevel = 0; # endif // LANDSCAPE @@ -989,21 +1018,35 @@ 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); float3 fdy = ddy(input.WorldPosition.xyz); float fragSize = rcp(length(max(abs(fdx), abs(fdy)))); - curvature = pow(length(max(abs(ndx), abs(ndy))) * fragSize, 0.5); - float3 flatWorldNormal = normalize(-cross(ddx(input.WorldPosition.xyz), ddy(input.WorldPosition.xyz))); - normalSmoothness = (1 - dot(vertexNormal, flatWorldNormal)); + float normalDelta = length(max(abs(ndx), abs(ndy))) * fragSize; + float3 flatWorldNormal = normalize(-cross(fdx, fdy)); + normalSmoothness = saturate(1.0 - dot(vertexNormal, flatWorldNormal)); # if defined(LANDSCAPE) - displacementParams[0].HeightScale = saturate(1 - curvature); + curvature = sqrt(normalDelta); + displacementParams[0].HeightScale = saturate(1.0 - curvature); displacementParams[0].FlattenAmount = (normalSmoothness + curvature); # else - displacementParams.HeightScale = saturate(1 - curvature); - displacementParams.FlattenAmount = (normalSmoothness + curvature); + /** + * Objects: do not crush HeightScale (that flattens convex faces you are looking at). + * Only add FlattenAmount past the facing hemisphere (grazing > facing) and only if bent. + * Flat walls stay at bend ≈ 0. Flatten tracks UV stretch (÷ facing). + */ + curvature = normalDelta; + float facing = saturate(dot(vertexNormal, viewDirection)); + float grazing = 1.0 - facing; + float bend = saturate(normalSmoothness + curvature); + float silhouette = saturate(grazing - facing); + displacementParams.FlattenAmount = silhouette * bend * rcp(max(facing, 0.0625)); # endif } # endif @@ -1019,9 +1062,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)) @@ -1052,9 +1095,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 { @@ -1079,7 +1122,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) PBRParallax = true; [branch] if ((PBRFlags & PBR::Flags::InterlayerParallax) != 0) { - displacementParams.HeightScale = PBRParams1.y; + displacementParams.HeightScale *= PBRParams1.y; displacementParams.DisplacementScale = 0.5; displacementParams.DisplacementOffset = -0.25; @@ -1099,9 +1142,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 @@ -1146,36 +1189,14 @@ 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)) { -# endif - mipLevels[0] = ExtendedMaterials::GetMipLevel(uv, TexColorSampler, screenNoise); - mipLevels[1] = ExtendedMaterials::GetMipLevel(uv, TexLandColor2Sampler, screenNoise); - mipLevels[2] = ExtendedMaterials::GetMipLevel(uv, TexLandColor3Sampler, screenNoise); - mipLevels[3] = ExtendedMaterials::GetMipLevel(uv, TexLandColor4Sampler, screenNoise); - mipLevels[4] = ExtendedMaterials::GetMipLevel(uv, TexLandColor5Sampler, screenNoise); - mipLevels[5] = ExtendedMaterials::GetMipLevel(uv, TexLandColor6Sampler, screenNoise); + if (LANDSCAPE_PARALLAX_ENABLED) { + float terrainMaxTexDim = 0.0; + ExtendedMaterials::InitializeTerrainMipLevels(uv, mipLevels, terrainMaxTexDim); + [unroll] for (uint terrainMipIndex = 0; terrainMipIndex < 6; terrainMipIndex++) + { + terrainShadowMipLevels[terrainMipIndex] = min(mipLevels[terrainMipIndex], ExtendedMaterials::TerrainParallaxShadowMaxMipLevel); + } displacementParams[1] = displacementParams[0]; displacementParams[2] = displacementParams[0]; @@ -1192,15 +1213,20 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif float weights[6]; - // Initialize weights array weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0; -# if defined(TERRAIN_VARIATION) - uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, dx, dy, pixelOffset, - weights); -# else - uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset, - weights); -# endif + + const bool doTerrainPom = ExtendedMaterials::TerrainHasAnyDisplacement() && + ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams) > 0.01; + [branch] if (doTerrainPom) + { + uv = ExtendedMaterials::GetParallaxCoords(input, uv, mipLevels, terrainMaxTexDim, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, pixelOffset, weights); + } + else if (SharedData::extendedMaterialSettings.EnableHeightBlending) + { + float unusedHeight; + unusedHeight = ExtendedMaterials::GetTerrainHeight(screenNoise, input, uv, mipLevels, displacementParams, 1.0, input.LandBlendWeights1, input.LandBlendWeights2.xy, sharedOffset, weights); + } + if (SharedData::extendedMaterialSettings.EnableHeightBlending) { input.LandBlendWeights1.x = weights[0]; input.LandBlendWeights1.y = weights[1]; @@ -1209,29 +1235,22 @@ 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 + /** + * After optional height-blend weight rewrite: distance / displacement / weighted + * height gates for the point-light loop (pixel-invariant; not rechecked per light). + */ + hasTerrainParallaxShadow = + viewPosition.z < ExtendedMaterials::ParallaxCheapDistance && + ExtendedMaterials::TerrainHasAnyDisplacement() && + ExtendedMaterials::TerrainMaxWeightedHeightScale(input, displacementParams) > 0.01; + if (doTerrainPom && 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; # endif // EMAT # endif // LANDSCAPE @@ -1260,7 +1279,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float4 glintParameters = 0; -# if defined(SNOW) // Earlier snow definition for Terrain Variation rework. +# if defined(SNOW) # if !defined(TRUE_PBR) float landSnowMask = 0.0; # if defined(LANDSCAPE) @@ -1270,488 +1289,29 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # endif # if defined(LANDSCAPE) - // Layer 1 (LandBlendWeights1.x) - if (input.LandBlendWeights1.x > 0.01) { - float weight = input.LandBlendWeights1.x; - - // Sample diffuse texture for layer 1 -# if defined(TERRAIN_VARIATION) - float4 landColor1; - [branch] if (useTerrainVariation) - { - landColor1 = StochasticEffect(TexColorSampler, SampColorSampler, uv, sharedOffset, dx, dy); - } - else - { - landColor1 = TexColorSampler.SampleBias(SampColorSampler, uv, SharedData::MipBias); - } -# else - float4 landColor1 = TexColorSampler.SampleBias(SampColorSampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB1 = landColor1.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile0PBR) == 0) - { - landColorRGB1 = Color::SrgbToLinear(landColorRGB1 / Color::PBRLightingScale); - } -# endif - float landAlpha1 = landColor1.a; - float landSnowMask1 = GetLandSnowMaskValue(landColor1.w); - - // Sample normal texture for layer 1 -# if defined(TERRAIN_VARIATION) - float4 landNormal1; - [branch] if (useTerrainVariation) - { - landNormal1 = StochasticEffect(TexNormalSampler, SampNormalSampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal1 = TexNormalSampler.SampleBias(SampNormalSampler, uv, SharedData::MipBias); - } -# else - float4 landNormal1 = TexNormalSampler.SampleBias(SampNormalSampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB1 = landNormal1.rgb; - float landNormalAlpha1 = landNormal1.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.x * input.LandBlendWeights1.x * landSnowMask1; -# endif // SNOW - - // Sample RMAOS texture for layer 1 -# if defined(TRUE_PBR) - float4 landRMAOS1; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile0PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS1 = StochasticEffect(TexRMAOSSampler, SampRMAOSSampler, uv, sharedOffset, dx, dy) * float4(PBRParams1.x, 1, 1, PBRParams1.z); - } - else - { - landRMAOS1 = TexRMAOSSampler.SampleBias(SampRMAOSSampler, uv, SharedData::MipBias) * float4(PBRParams1.x, 1, 1, PBRParams1.z); - } -# else - landRMAOS1 = TexRMAOSSampler.SampleBias(SampRMAOSSampler, uv, SharedData::MipBias) * float4(PBRParams1.x, 1, 1, PBRParams1.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile0HasGlint) != 0) { - glintParameters += weight * LandscapeTexture1GlintParameters; - } - } - else - { - landRMAOS1 = input.LandBlendWeights1.x * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS1 * weight; -# endif - blendedRGB += landColorRGB1 * weight; - blendedAlpha += landAlpha1 * weight; - blendedNormalRGB += landNormalRGB1 * weight; - blendedNormalAlpha += landNormalAlpha1 * weight; - } - - // Layer 2 (LandBlendWeights1.y) - if (input.LandBlendWeights1.y > 0.01) { - float weight = input.LandBlendWeights1.y; - - // Sample diffuse texture for layer 2 -# if defined(TERRAIN_VARIATION) - float4 landColor2; - [branch] if (useTerrainVariation) - { - landColor2 = StochasticEffect(TexLandColor2Sampler, SampLandColor2Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor2 = TexLandColor2Sampler.SampleBias(SampLandColor2Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor2 = TexLandColor2Sampler.SampleBias(SampLandColor2Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB2 = landColor2.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile1PBR) == 0) - { - landColorRGB2 = Color::SrgbToLinear(landColorRGB2 / Color::PBRLightingScale); - } -# endif - float landAlpha2 = landColor2.a; - float landSnowMask2 = GetLandSnowMaskValue(landColor2.w); - - // Sample normal texture for layer 2 -# if defined(TERRAIN_VARIATION) - float4 landNormal2; - [branch] if (useTerrainVariation) - { - landNormal2 = StochasticEffect(TexLandNormal2Sampler, SampLandNormal2Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal2 = TexLandNormal2Sampler.SampleBias(SampLandNormal2Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal2 = TexLandNormal2Sampler.SampleBias(SampLandNormal2Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB2 = landNormal2.rgb; - float landNormalAlpha2 = landNormal2.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.y * input.LandBlendWeights1.y * landSnowMask2; -# endif // SNOW - - // Sample RMAOS texture for layer 2 -# if defined(TRUE_PBR) - float4 landRMAOS2; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile1PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS2 = StochasticEffect(TexLandRMAOS2Sampler, SampLandRMAOS2Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture2PBRParams.x, 1, 1, LandscapeTexture2PBRParams.z); - } - else - { - landRMAOS2 = TexLandRMAOS2Sampler.SampleBias(SampLandRMAOS2Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture2PBRParams.x, 1, 1, LandscapeTexture2PBRParams.z); - } -# else - landRMAOS2 = TexLandRMAOS2Sampler.SampleBias(SampLandRMAOS2Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture2PBRParams.x, 1, 1, LandscapeTexture2PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile1HasGlint) != 0) { - glintParameters += weight * LandscapeTexture2GlintParameters; - } - } - else - { - landRMAOS2 = input.LandBlendWeights1.y * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS2 * weight; -# endif - blendedRGB += landColorRGB2 * weight; - blendedAlpha += landAlpha2 * weight; - blendedNormalRGB += landNormalRGB2 * weight; - blendedNormalAlpha += landNormalAlpha2 * weight; - } - - // Layer 3 (LandBlendWeights1.z) - if (input.LandBlendWeights1.z > 0.01) { - float weight = input.LandBlendWeights1.z; - // Sample diffuse texture for layer 3 -# if defined(TERRAIN_VARIATION) - float4 landColor3; - [branch] if (useTerrainVariation) - { - landColor3 = StochasticEffect(TexLandColor3Sampler, SampLandColor3Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor3 = TexLandColor3Sampler.SampleBias(SampLandColor3Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor3 = TexLandColor3Sampler.SampleBias(SampLandColor3Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB3 = landColor3.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile2PBR) == 0) - { - landColorRGB3 = Color::SrgbToLinear(landColorRGB3 / Color::PBRLightingScale); - } -# endif - float landAlpha3 = landColor3.a; - float landSnowMask3 = GetLandSnowMaskValue(landColor3.w); - - // Sample normal texture for layer 3 -# if defined(TERRAIN_VARIATION) - float4 landNormal3; - [branch] if (useTerrainVariation) - { - landNormal3 = StochasticEffect(TexLandNormal3Sampler, SampLandNormal3Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal3 = TexLandNormal3Sampler.SampleBias(SampLandNormal3Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal3 = TexLandNormal3Sampler.SampleBias(SampLandNormal3Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB3 = landNormal3.rgb; - float landNormalAlpha3 = landNormal3.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.z * input.LandBlendWeights1.z * landSnowMask3; -# endif // SNOW - - // Sample RMAOS texture for layer 3 -# if defined(TRUE_PBR) - float4 landRMAOS3; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile2PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS3 = StochasticEffect(TexLandRMAOS3Sampler, SampLandRMAOS3Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture3PBRParams.x, 1, 1, LandscapeTexture3PBRParams.z); - } - else - { - landRMAOS3 = TexLandRMAOS3Sampler.SampleBias(SampLandRMAOS3Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture3PBRParams.x, 1, 1, LandscapeTexture3PBRParams.z); - } -# else - landRMAOS3 = TexLandRMAOS3Sampler.SampleBias(SampLandRMAOS3Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture3PBRParams.x, 1, 1, LandscapeTexture3PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile2HasGlint) != 0) { - glintParameters += weight * LandscapeTexture3GlintParameters; - } - } - else - { - landRMAOS3 = input.LandBlendWeights1.z * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS3 * weight; -# endif - blendedRGB += landColorRGB3 * weight; - blendedAlpha += landAlpha3 * weight; - blendedNormalRGB += landNormalRGB3 * weight; - blendedNormalAlpha += landNormalAlpha3 * weight; - } - // Layer 4 (LandBlendWeights1.w) - if (input.LandBlendWeights1.w > 0.01) { - float weight = input.LandBlendWeights1.w; - - // Sample diffuse texture for layer 4 -# if defined(TERRAIN_VARIATION) - float4 landColor4; - [branch] if (useTerrainVariation) - { - landColor4 = StochasticEffect(TexLandColor4Sampler, SampLandColor4Sampler, uv, sharedOffset, dx, dy); - } - else - { - landColor4 = TexLandColor4Sampler.SampleBias(SampLandColor4Sampler, uv, SharedData::MipBias); - } -# else - float4 landColor4 = TexLandColor4Sampler.SampleBias(SampLandColor4Sampler, uv, SharedData::MipBias); -# endif - float3 landColorRGB4 = landColor4.rgb; -# if defined(TRUE_PBR) - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile3PBR) == 0) - { - landColorRGB4 = Color::SrgbToLinear(landColorRGB4 / Color::PBRLightingScale); - } -# endif - float landAlpha4 = landColor4.a; - float landSnowMask4 = GetLandSnowMaskValue(landColor4.w); - - // Sample normal texture for layer 4 -# if defined(TERRAIN_VARIATION) - float4 landNormal4; - [branch] if (useTerrainVariation) - { - landNormal4 = StochasticEffect(TexLandNormal4Sampler, SampLandNormal4Sampler, uv, sharedOffset, dx, dy); - } - else - { - landNormal4 = TexLandNormal4Sampler.SampleBias(SampLandNormal4Sampler, uv, SharedData::MipBias); - } -# else - float4 landNormal4 = TexLandNormal4Sampler.SampleBias(SampLandNormal4Sampler, uv, SharedData::MipBias); -# endif - float3 landNormalRGB4 = landNormal4.rgb; - float landNormalAlpha4 = landNormal4.a; -# if defined(SNOW) && !defined(TRUE_PBR) - landSnowMask += LandscapeTexture1to4IsSnow.w * input.LandBlendWeights1.w * landSnowMask4; -# endif // SNOW - - // Sample RMAOS texture for layer 4 -# if defined(TRUE_PBR) - float4 landRMAOS4; - [branch] if ((PBRFlags & PBR::TerrainFlags::LandTile3PBR) != 0) - { -# if defined(TERRAIN_VARIATION) - [branch] if (useTerrainVariation) - { - landRMAOS4 = StochasticEffect(TexLandRMAOS4Sampler, SampLandRMAOS4Sampler, uv, sharedOffset, dx, dy) * float4(LandscapeTexture4PBRParams.x, 1, 1, LandscapeTexture4PBRParams.z); - } - else - { - landRMAOS4 = TexLandRMAOS4Sampler.SampleBias(SampLandRMAOS4Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture4PBRParams.x, 1, 1, LandscapeTexture4PBRParams.z); - } -# else - landRMAOS4 = TexLandRMAOS4Sampler.SampleBias(SampLandRMAOS4Sampler, uv, SharedData::MipBias) * float4(LandscapeTexture4PBRParams.x, 1, 1, LandscapeTexture4PBRParams.z); -# endif - if ((PBRFlags & PBR::TerrainFlags::LandTile3HasGlint) != 0) { - glintParameters += weight * LandscapeTexture4GlintParameters; - } - } - else - { - landRMAOS4 = input.LandBlendWeights1.w * float4(1 - glossiness.x, 0, 1, 0); - } - blendedRMAOS += landRMAOS4 * weight; -# endif - blendedRGB += landColorRGB4 * weight; - blendedAlpha += landAlpha4 * weight; - blendedNormalRGB += landNormalRGB4 * weight; - blendedNormalAlpha += landNormalAlpha4 * weight; - } - - // Layer 5 (LandBlendWeights2.x) - if (input.LandBlendWeights2.x > 0.01) { - float weight = input.LandBlendWeights2.x; - // Sample diffuse texture for layer 5 + float landDistanceTexMipBias = 0.0; # 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); - } + g_terrainStochasticGrad = ComputeTerrainGradients(uv); +# define SampleTerrain(TEX, SAMP, UV, OFFSET, EXTRA_BIAS) StochasticEffect(TEX, SAMP, UV, OFFSET) # else - float4 landColor5 = TexLandColor5Sampler.SampleBias(SampLandColor5Sampler, uv, SharedData::MipBias); +# define SampleTerrain(TEX, SAMP, UV, OFFSET, EXTRA_BIAS) TEX.SampleBias(SAMP, UV, SharedData::MipBias + EXTRA_BIAS) # 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); - } + 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 landNormal5 = TexLandNormal5Sampler.SampleBias(SampLandNormal5Sampler, uv, SharedData::MipBias); + 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 - 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; - } +# undef SampleTerrain float4 rawBaseColor = float4(blendedRGB, blendedAlpha); baseColor = float4(Color::Diffuse(blendedRGB), blendedAlpha); @@ -1776,15 +1336,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) # if defined(LODOBJECTS) || defined(LODOBJECTSHD) baseColor.xyz = pow(abs(baseColor.xyz), SharedData::lodBlendingSettings.LODObjectGamma) * SharedData::lodBlendingSettings.LODObjectBrightness; # elif defined(LODLANDSCAPE) -// First apply terrain variation if enabled # if defined(TERRAIN_VARIATION) - if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) { - float2 dx = ddx(uv); - float2 dy = ddy(uv); - StochasticOffsets lodOffset = ComputeStochasticOffsetsLOD(uv); - float4 lodStochasticColor = StochasticSampleLOD(screenNoise, TexColorSampler, SampColorSampler, uv, lodOffset, dx, dy); - - // Apply the stochastic result directly + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + { + float4 lodStochasticColor = StochasticSampleLOD(screenNoise, TexColorSampler, SampColorSampler, uv); baseColor.xyz = Color::Diffuse(lodStochasticColor.rgb); } # endif @@ -1929,16 +1484,11 @@ 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; + [branch] if (SharedData::terrainVariationSettings.enableLODTerrainTilingFix) + lodLandColor = StochasticSampleLOD(screenNoise, TexLandLodBlend1Sampler, SampLandLodBlend1Sampler, blendColorUV); + else + lodLandColor = TexLandLodBlend1Sampler.SampleBias(SampLandLodBlend1Sampler, blendColorUV, SharedData::MipBias); # else lodLandColor = TexLandLodBlend1Sampler.Sample(SampLandLodBlend1Sampler, input.TexCoord0.zw); # endif @@ -2610,33 +2160,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)) @@ -2829,16 +2371,8 @@ 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 + [branch] if (hasTerrainParallaxShadow) + 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); @@ -3415,6 +2949,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 ef1938bfb9..68c8490def 100644 --- a/src/Features/ExtendedMaterials.h +++ b/src/Features/ExtendedMaterials.h @@ -32,10 +32,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 5c71eca70b..a1ca033a95 100644 --- a/src/Features/TerrainVariation.h +++ b/src/Features/TerrainVariation.h @@ -13,10 +13,13 @@ struct TerrainVariation : Feature virtual inline std::string GetShortName() override { return "TerrainVariation"; } virtual inline std::string GetFeatureModLink() override { return MakeNexusModURL(MOD_ID); } virtual inline std::string_view GetShaderDefineName() override { return "TERRAIN_VARIATION"; } - /** @brief Returns true only for Lighting shader type. */ + /** + * @brief Returns true for Lighting shaders when the feature is loaded. + * @details LOD tiling remains gated by @c enableLODTerrainTilingFix. + */ virtual inline bool HasShaderDefine(RE::BSShader::Type shaderType) override { - return (shaderType == RE::BSShader::Type::Lighting); + return loaded && shaderType == RE::BSShader::Type::Lighting; } virtual bool IsCore() const override { return false; }; virtual std::string_view GetCategory() const override { return FeatureCategories::kLandscapeAndTextures; } @@ -31,12 +34,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; /** @brief Draws the ImGui settings panel for Terrain Variation configuration. */ virtual void DrawSettings() override; @@ -48,6 +54,4 @@ struct TerrainVariation : Feature /** @brief Initializes the feature and applies shader settings after plugin load. */ virtual void PostPostLoad() override; - /** @brief Marks the vertex descriptor as dirty to trigger a shader settings update. */ - void UpdateShaderSettings(); }; \ No newline at end of file