Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions features/Water Effects/Shaders/WaterEffects/WaterParallax.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,126 @@ namespace WaterEffects

return parallaxOffsetTS.xy * parallaxAmount;
}

#if defined(FLOWMAP)
float GetFlowmapHeight(PS_INPUT input, float2 uvShift, float multiplier, float offset, float mipLevel)
{
FlowmapData flowData = GetFlowmapDataUV(input, uvShift);
float2 baseUV = offset + (flowData.flowVector - float2(multiplier * ((0.001 * ReflectionColor.w) * flowData.color.w), 0));
return FlowMapNormalsTex.SampleLevel(FlowMapNormalsSampler, baseUV, mipLevel).w;
}

float GetFlowmapBlendedHeight(PS_INPUT input, float2 normalMul, float2 uvShift, float mipLevel)
{
float height0 = GetFlowmapHeight(input, uvShift, 9.92, 0, mipLevel);
float height1 = GetFlowmapHeight(input, float2(0, uvShift.y), 10.64, 0.27, mipLevel);
float height2 = GetFlowmapHeight(input, 0.0.xx, 8, 0, mipLevel);
float height3 = GetFlowmapHeight(input, float2(uvShift.x, 0), 8.48, 0.62, mipLevel);

float blendedHeight =
normalMul.y * (normalMul.x * height2 + (1 - normalMul.x) * height3) +
(1 - normalMul.y) * (normalMul.x * height1 + (1 - normalMul.x) * height0);

return blendedHeight;
}

float GetFlowmapParallaxAmount(PS_INPUT input, float2 flowmapDims, float3 viewDirection)
{
float viewDotUp = -viewDirection.z;

if (viewDotUp < 0.05)
return 0.0;

float2 parallaxDir = viewDirection.xy / -viewDirection.z;
parallaxDir.y = -parallaxDir.y;

float parallaxScale = 0.008 * saturate(viewDotUp * 2.0);
parallaxDir *= parallaxScale;

float2 uvShiftPx = 1 / (128 * flowmapDims);

int numSteps = (int)lerp(32.0, 8.0, viewDotUp);
float stepSize = rcp((float)numSteps);

float currBound = 0.0;
float currHeight = 1.0;
float prevHeight = 1.0;

[loop] for (int i = 0; i < numSteps && currHeight > currBound; i++)
{
prevHeight = currHeight;
currBound += stepSize;

PS_INPUT offsetInput = input;
offsetInput.TexCoord3.xy = input.TexCoord3.xy + currBound * parallaxDir;

float2 cellBlend = 0.5 + -(-0.5 + abs(frac(offsetInput.TexCoord2.zw * (64 * flowmapDims)) * 2 - 1));
currHeight = 1.0 - GetFlowmapBlendedHeight(offsetInput, cellBlend, uvShiftPx, 0);
}

float prevBound = currBound - stepSize;
float delta2 = prevBound - prevHeight;
float delta1 = currBound - currHeight;
float denominator = delta2 - delta1;

return denominator != 0.0 ? (currBound * delta2 - prevBound * delta1) / denominator : currBound;
}

float GetFlowmapParallaxHeight(PS_INPUT input, float2 currentOffset, float3 normalScalesRcp, float mipLevel)
{
float height = Normals01Tex.SampleLevel(Normals01Sampler, input.TexCoord1.xy + currentOffset * normalScalesRcp.x, mipLevel).w;
height *= NormalsAmplitude.x;
return 1.0 - height;
}

float2 GetFlowmapParallaxUVOffset(PS_INPUT input, float3 viewDirection, float3 normalScalesRcp)
{
float2 parallaxOffsetTS = viewDirection.xy / -viewDirection.z;
parallaxOffsetTS *= 80.0;

float2 textureDims;
Normals01Tex.GetDimensions(textureDims.x, textureDims.y);
#if defined(VR)
textureDims /= 16.0;
#else
textureDims /= 8.0;
#endif
float2 texCoordsPerSize = input.TexCoord1.xy * textureDims;
float2 dxSize = ddx(texCoordsPerSize);
float2 dySize = ddy(texCoordsPerSize);
float2 dTexCoords = dxSize * dxSize + dySize * dySize;
float minTexCoordDelta = max(dTexCoords.x, dTexCoords.y);
float mipLevel = max(0.5 * log2(minTexCoordDelta), 0);
#if defined(VR)
mipLevel += 4;
#else
mipLevel += 3;
#endif

float stepSize = rcp(16.0);
float currBound = 0.0;
float currHeight = 1.0;
float prevHeight = 1.0;

[loop] while (currHeight > currBound)
{
prevHeight = currHeight;
currBound += stepSize;
currHeight = GetFlowmapParallaxHeight(input, currBound * parallaxOffsetTS.xy, normalScalesRcp, mipLevel);
}

float prevBound = currBound - stepSize;
float delta2 = prevBound - prevHeight;
float delta1 = currBound - currHeight;
float denominator = delta2 - delta1;
float parallaxAmount = (currBound * delta2 - prevBound * delta1) / denominator;

return parallaxOffsetTS.xy * parallaxAmount;
}
Comment thread
davo0411 marked this conversation as resolved.

float2 GetFlowmapParallaxOffset(PS_INPUT input, float2 flowmapDimensions, float3 viewDirection, float3 normalScalesRcp)
{
return GetFlowmapParallaxUVOffset(input, viewDirection, normalScalesRcp);
}
#endif
}
152 changes: 123 additions & 29 deletions package/Shaders/Water.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ struct VS_OUTPUT
float4 TexCoord3 : TEXCOORD3;
# endif
# if defined(FLOWMAP)
nointerpolation float TexCoord4 : TEXCOORD4;
nointerpolation float2 TexCoord4 : TEXCOORD4;
# endif
# if NUM_SPECULAR_LIGHTS == 0
float4 MPosition : TEXCOORD5;
Expand Down Expand Up @@ -453,7 +453,7 @@ struct FlowmapData
FlowmapData GetFlowmapDataTextureSpace(PS_INPUT input, float2 uvShift)
{
FlowmapData data;
data.color = FlowMapTex.Sample(FlowMapSampler, input.TexCoord2.zw + uvShift);
data.color = FlowMapTex.SampleLevel(FlowMapSampler, input.TexCoord2.zw + uvShift, 0);
data.flowVector = (64 * input.TexCoord3.xy) * sqrt(1.01 - data.color.z);
// NOTE: flowVector is NOT transformed yet - this is the raw vector before rotation matrix
return data;
Expand Down Expand Up @@ -486,23 +486,90 @@ FlowmapData GetFlowmapDataUV(PS_INPUT input, float2 uvShift)
data.flowVector = mul(transpose(flowRotationMatrix), data.flowVector);
return data;
}
// ----------------------------------------------------------------
// Flowmap Parallax Functions
// ----------------------------------------------------------------

/**
* Generates flowmap-based normal perturbation for water surface
* Samples height from flowmap texture using the same 4-sample blend as flowmap normals
* This ensures height transitions match the normal transitions exactly
*
* @param input Pixel shader input containing texture coordinates and world position
* @param uvShift UV offset for flowmap sampling (used for animation phases)
* @param multiplier Intensity multiplier for the flow effect
* @param offset Base UV offset for the normal texture sampling
* @return float3 Normal perturbation (XY=normal offset, Z=flow strength mask)
*
* @details This function uses flowmap data to:
* - Calculate flow-displaced UV coordinates for normal texture sampling
* - Apply flow-based animation to water normal textures
* - Return both the normal perturbation and flow strength information
*
* @note The returned Z component contains the original flowmap strength value
* which can be used for blending between flow and non-flow normals
* @param input PS_INPUT for flowmap coordinate access
* @param normalMul The blend weights from the flowmap system (same as used for normals)
* @param uvShift The UV shift value (1 / (128 * flowmapDimensions))
* @param mipLevel Mip level for texture sampling
*/
float GetFlowmapHeightBlended(PS_INPUT input, float2 normalMul, float2 uvShift, float mipLevel)
{
// Sample height using the EXACT same UV computation as GetFlowmapNormal
// This ensures the height blending matches the normal blending perfectly

// Sample 0: uvShift, multiplier=9.92, offset=0
FlowmapData flowData0 = GetFlowmapDataUV(input, uvShift);
float2 uv0 = 0 + (flowData0.flowVector - float2(9.92 * ((0.001 * ReflectionColor.w) * flowData0.color.w), 0));
float height0 = FlowMapNormalsTex.SampleLevel(FlowMapNormalsSampler, uv0, mipLevel).w;

// Sample 1: float2(0, uvShift.y), multiplier=10.64, offset=0.27
FlowmapData flowData1 = GetFlowmapDataUV(input, float2(0, uvShift.y));
float2 uv1 = 0.27 + (flowData1.flowVector - float2(10.64 * ((0.001 * ReflectionColor.w) * flowData1.color.w), 0));
float height1 = FlowMapNormalsTex.SampleLevel(FlowMapNormalsSampler, uv1, mipLevel).w;

// Sample 2: 0.0.xx, multiplier=8, offset=0
FlowmapData flowData2 = GetFlowmapDataUV(input, 0.0.xx);
float2 uv2 = 0 + (flowData2.flowVector - float2(8 * ((0.001 * ReflectionColor.w) * flowData2.color.w), 0));
float height2 = FlowMapNormalsTex.SampleLevel(FlowMapNormalsSampler, uv2, mipLevel).w;

// Sample 3: float2(uvShift.x, 0), multiplier=8.48, offset=0.62
FlowmapData flowData3 = GetFlowmapDataUV(input, float2(uvShift.x, 0));
float2 uv3 = 0.62 + (flowData3.flowVector - float2(8.48 * ((0.001 * ReflectionColor.w) * flowData3.color.w), 0));
float height3 = FlowMapNormalsTex.SampleLevel(FlowMapNormalsSampler, uv3, mipLevel).w;

// Use the EXACT same blending formula as flowmap normals
float blendedHeight =
normalMul.y * (normalMul.x * height2 + (1 - normalMul.x) * height3) +
(1 - normalMul.y) * (normalMul.x * height1 + (1 - normalMul.x) * height0);

return blendedHeight;
}

// Keep this for compatibility - just forwards to the proper function
float GetFlowmapHeightBarycentric(PS_INPUT input, float2 flowmapDimensions, float2 baseUV, float mipLevel)
{
// This is now unused - we use GetFlowmapHeightBlended directly
return FlowMapNormalsTex.SampleLevel(FlowMapNormalsSampler, baseUV, mipLevel).w;
}

/**
* Computes mip level for flowmap texture sampling
*/
float GetFlowmapMipLevel(float2 flowmapUV)
{
float2 textureDims;
FlowMapNormalsTex.GetDimensions(textureDims.x, textureDims.y);

#if defined(VR)
textureDims /= 16.0;
#else
textureDims /= 8.0;
#endif

float2 texCoordsPerSize = flowmapUV * textureDims;
float2 dxSize = ddx(texCoordsPerSize);
float2 dySize = ddy(texCoordsPerSize);
float2 dTexCoords = dxSize * dxSize + dySize * dySize;
float minTexCoordDelta = max(dTexCoords.x, dTexCoords.y);
return max(0.5 * log2(minTexCoordDelta), 0);
}

/**
* Samples height from flowmap texture (riverflow.dds alpha channel)
* Uses the same UV calculation as GetFlowmapNormal for consistency
*/


/**
* Generates flowmap-based normal (no parallax - flowmap normals are not parallax-shifted)
* Uses mip clamping to preserve detail at distance and prevent over-blurring
*/
float3 GetFlowmapNormal(PS_INPUT input, float2 uvShift, float multiplier, float offset)
{
Expand Down Expand Up @@ -588,14 +655,34 @@ WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float norma
# endif

# if defined(FLOWMAP)
float2 normalMul =
0.5 + -(-0.5 + abs(frac(input.TexCoord2.zw * (64 * input.TexCoord4)) * 2 - 1));
float uvShift = 1 / (128 * input.TexCoord4);
# if defined(UNIFIED_WATER)
float2 flowmapDimensions = input.TexCoord4.xy;
# else
float2 flowmapDimensions = input.TexCoord4.xx;
# endif
float2 uvShift = 1 / (128 * flowmapDimensions);

// Compute flowmap parallax and create parallaxed input for normal sampling
PS_INPUT flowmapInput = input;
float2 flowmapParallaxOffset = float2(0, 0);
# if defined(WATER_PARALLAX) && !defined(LOD)
float parallaxAmount = WaterEffects::GetFlowmapParallaxAmount(input, flowmapDimensions, viewDirection);
float2 parallaxDir = viewDirection.xy / -viewDirection.z;
parallaxDir.y = -parallaxDir.y;
float viewDotUp = -viewDirection.z;
parallaxDir *= 0.008 * saturate(viewDotUp * 2.0);
flowmapInput.TexCoord3.xy = input.TexCoord3.xy + parallaxAmount * parallaxDir;
flowmapParallaxOffset = WaterEffects::GetFlowmapParallaxOffset(input, flowmapDimensions, viewDirection, normalScalesRcp);
# endif

float3 flowmapNormal0 = GetFlowmapNormal(input, uvShift.xx, 9.92, 0);
float3 flowmapNormal1 = GetFlowmapNormal(input, float2(0, uvShift), 10.64, 0.27);
float3 flowmapNormal2 = GetFlowmapNormal(input, 0.0.xx, 8, 0);
float3 flowmapNormal3 = GetFlowmapNormal(input, float2(uvShift, 0), 8.48, 0.62);
// Calculate cell blend weights using parallaxed input
float2 normalMul = 0.5 + -(-0.5 + abs(frac(flowmapInput.TexCoord2.zw * (64 * flowmapDimensions)) * 2 - 1));

// Sample flowmap normals with parallax applied
float3 flowmapNormal0 = GetFlowmapNormal(flowmapInput, uvShift, 9.92, 0);
float3 flowmapNormal1 = GetFlowmapNormal(flowmapInput, float2(0, uvShift.y), 10.64, 0.27);
float3 flowmapNormal2 = GetFlowmapNormal(flowmapInput, 0.0.xx, 8, 0);
float3 flowmapNormal3 = GetFlowmapNormal(flowmapInput, float2(uvShift.x, 0), 8.48, 0.62);

float2 flowmapNormalWeighted =
normalMul.y * (normalMul.x * flowmapNormal2.xy + (1 - normalMul.x) * flowmapNormal3.xy) +
Expand All @@ -608,14 +695,21 @@ WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float norma
0);
flowmapNormal.z =
sqrt(1 - flowmapNormal.x * flowmapNormal.x - flowmapNormal.y * flowmapNormal.y);
# endif

float2 baseNormalUv = input.TexCoord1.xy;
# if defined(WATER_PARALLAX)
float3 normals1 = Normals01Tex.SampleBias(Normals01Sampler, input.TexCoord1.xy + parallaxOffset.xy * normalScalesRcp.x, SharedData::MipBias).xyz * 2.0 + float3(-1, -1, -2);
# else
float3 normals1 = Normals01Tex.SampleBias(Normals01Sampler, input.TexCoord1.xy, SharedData::MipBias).xyz * 2.0 + float3(-1, -1, -2);
// Use flowmap-derived parallax offset for base normals
baseNormalUv += flowmapParallaxOffset.xy * normalScalesRcp.x;
# endif

float3 normals1 = Normals01Tex.SampleBias(Normals01Sampler, baseNormalUv, SharedData::MipBias).xyz * 2.0 + float3(-1, -1, -2);
# endif // End of FLOWMAP block

# if !defined(FLOWMAP)
# if defined(WATER_PARALLAX)
float3 normals1 = Normals01Tex.SampleBias(Normals01Sampler, input.TexCoord1.xy + parallaxOffset.xy * normalScalesRcp.x, SharedData::MipBias).xyz * 2.0 + float3(-1, -1, -2);
# else
float3 normals1 = Normals01Tex.SampleBias(Normals01Sampler, input.TexCoord1.xy, SharedData::MipBias).xyz * 2.0 + float3(-1, -1, -2);
# endif
# endif // End of !FLOWMAP block
# if defined(FLOWMAP) && !defined(BLEND_NORMALS)
# ifdef DISABLE_FLOWMAP_NORMALS
// FLOWMAP NORMALS DISABLED: Using only base normals (flow system still active for ripples/splashes)
Expand Down