Files
Fishing2/Packages/com.waveharmonic.crest/Runtime/Shaders/Volume/UnderwaterShared.hlsl
2025-05-10 12:49:47 +08:00

486 lines
17 KiB
HLSL

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#ifndef CREST_UNDERWATER_EFFECT_SHARED_INCLUDED
#define CREST_UNDERWATER_EFFECT_SHARED_INCLUDED
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Settings.Crest.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Macros.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Constants.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/InputsDriven.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Globals.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Helpers.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Depth.hlsl"
#if defined(CREST_WATER_VOLUME) || defined(CREST_WATER_VOLUME_FULLSCREEN)
#include "Packages/com.waveharmonic.crest.portals/Runtime/Shaders/Library/Portals.hlsl"
#endif
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Cascade.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Texture.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Lighting.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/VolumeLighting.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Caustics.hlsl"
// These are set via call to CopyPropertiesFromMaterial() and must have the same
// names as the surface material parameters.
CBUFFER_START(CrestPerMaterial)
//
// Surface Shared
//
half4 _Crest_Absorption;
half4 _Crest_Scattering;
half _Crest_Anisotropy;
bool _Crest_CausticsEnabled;
float _Crest_CausticsTextureScale;
float _Crest_CausticsScrollSpeed;
float _Crest_CausticsTextureAverage;
float _Crest_CausticsStrength;
float _Crest_CausticsFocalDepth;
float _Crest_CausticsDepthOfField;
float _Crest_CausticsDistortionStrength;
float _Crest_CausticsDistortionScale;
half _Crest_CausticsMotionBlur;
float4 _Crest_CausticsTexture_TexelSize;
float4 _Crest_CausticsDistortionTexture_TexelSize;
half _Crest_DirectTerm;
half _Crest_AmbientTerm;
half _Crest_ShadowsAffectsAmbientFactor;
//
// Volume Only
//
float2 _Crest_HorizonNormal;
// Out-scattering. Driven by the Water Renderer and Underwater Environmental Lighting.
float _Crest_VolumeExtinctionLength;
float _Crest_UnderwaterEnvironmentalLightingWeight;
// Also applied to transparent objects.
half _Crest_ExtinctionMultiplier;
half _Crest_SunBoost;
float _Crest_OutScatteringFactor;
float _Crest_OutScatteringExtinctionFactor;
half3 _Crest_AmbientLighting;
int _Crest_DataSliceOffset;
half _Crest_DitheringIntensity;
CBUFFER_END
TEXTURE2D_X(_Crest_WaterMaskTexture);
TEXTURE2D_X(_Crest_WaterMaskDepthTexture);
TEXTURE2D_X(_Crest_CameraColorTexture);
TEXTURE2D(_Crest_CausticsTexture);
SAMPLER(sampler_Crest_CausticsTexture);
TEXTURE2D(_Crest_CausticsDistortionTexture);
SAMPLER(sampler_Crest_CausticsDistortionTexture);
// NOTE: Cannot put this in namespace due to compiler bug. Fixed when using DXC.
static const m_Crest::TiledTexture _Crest_CausticsTiledTexture =
m_Crest::TiledTexture::Make(_Crest_CausticsTexture, sampler_Crest_CausticsTexture, _Crest_CausticsTexture_TexelSize, _Crest_CausticsTextureScale, _Crest_CausticsScrollSpeed);
static const m_Crest::TiledTexture _Crest_CausticsDistortionTiledTexture =
m_Crest::TiledTexture::Make(_Crest_CausticsDistortionTexture, sampler_Crest_CausticsDistortionTexture, _Crest_CausticsDistortionTexture_TexelSize, _Crest_CausticsDistortionScale, 1.0);
m_CrestNameSpace
float LinearToDeviceDepth(float linearDepth, float4 zBufferParam)
{
//linear = 1.0 / (zBufferParam.z * device + zBufferParam.w);
float device = (1.0 / linearDepth - zBufferParam.w) / zBufferParam.z;
return device;
}
#if d_Dithering
// Adapted from:
// https://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf
float3 ScreenSpaceDither(const float2 i_ScreenPosition)
{
// Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR.
float3 dither = dot(float2(171.0, 231.0), i_ScreenPosition.xy);
dither.rgb = frac(dither.rgb / float3(103.0, 71.0, 97.0)) - float3(0.5, 0.5, 0.5);
return (dither.rgb / 255.0);
}
#endif
float4 DebugRenderWaterMask(const bool isWaterSurface, const bool isUnderwater, const float mask, const float3 sceneColour)
{
// Red: surface front face when above water
// Green: surface back face when below water
// Cyan: background when above water
// Magenta: background when below water
if (isWaterSurface)
{
return float4(sceneColour * float3(mask >= CREST_MASK_ABOVE_SURFACE, mask <= CREST_MASK_BELOW_SURFACE, 0.0), 1.0);
}
else
{
return float4(sceneColour * float3(isUnderwater * 0.5, (1.0 - isUnderwater) * 0.5, 1.0), 1.0);
}
}
float4 DebugRenderStencil(float3 sceneColour)
{
float3 stencil = 1.0;
#if CREST_WATER_VOLUME_FRONT_FACE
stencil = float3(1.0, 0.0, 0.0);
#elif CREST_WATER_VOLUME_BACK_FACE
stencil = float3(0.0, 1.0, 0.0);
#elif CREST_WATER_VOLUME_FULLSCREEN
stencil = float3(0.0, 0.0, 1.0);
#endif
return float4(sceneColour * stencil, 1.0);
}
float MeniscusSampleWaterMask(const float mask, const int2 positionSS, const float2 offset, const float magnitude, const float scale)
{
float2 uv = positionSS + offset * magnitude
#if defined(CREST_WATER_VOLUME) || defined(CREST_WATER_VOLUME_FULLSCREEN)
* scale
#endif
;
float newMask = LOAD_TEXTURE2D_X(_Crest_WaterMaskTexture, uv).r;
#if CREST_UNDERWATER_BEFORE_TRANSPARENT
// Normalize mask.
newMask = clamp(newMask, -1.0, 1.0);
#endif
#if CREST_WATER_VOLUME
// No mask means no underwater effect so ignore the value.
return (newMask == CREST_MASK_NONE ? mask : newMask);
#endif
return newMask;
}
half ComputeMeniscusWeight(const int2 positionSS, float mask, const float2 horizonNormal, const float meniscusDepth)
{
float weight = 1.0;
#if d_Meniscus
#if !_FULL_SCREEN_EFFECT
#if CREST_UNDERWATER_BEFORE_TRANSPARENT
// Normalize mask.
mask = clamp(mask, -1.0, 1.0);
#endif
// Render meniscus by checking the mask along the horizon normal which is flipped using the surface normal from
// mask. Adding the mask value will flip the UV when mask is below surface.
float2 offset = (float2)-mask * horizonNormal;
float multiplier = 0.9;
#if defined(CREST_WATER_VOLUME) || defined(CREST_WATER_VOLUME_FULLSCREEN)
// The meniscus at the boundary can be at a distance. We need to scale the offset as 1 pixel at a distance is much
// larger than 1 pixel up close.
const float scale = 1.0 - saturate(meniscusDepth / MENISCUS_MAXIMUM_DISTANCE);
// Exit early.
if (scale == 0.0)
{
return 1.0;
}
#else
// Dummy value.
const float scale = 0.0;
#endif
// Sample three pixels along the normal. If the sample is different than the current mask, apply meniscus.
// Offset must be added to positionSS as floats.
weight *= (MeniscusSampleWaterMask(mask, positionSS, offset, 1.0, scale) != mask) ? multiplier : 1.0;
weight *= (MeniscusSampleWaterMask(mask, positionSS, offset, 2.0, scale) != mask) ? multiplier : 1.0;
weight *= (MeniscusSampleWaterMask(mask, positionSS, offset, 3.0, scale) != mask) ? multiplier : 1.0;
#endif // _FULL_SCREEN_EFFECT
#endif // d_Meniscus
return weight;
}
void GetWaterSurfaceAndUnderwaterData
(
const float4 positionCS,
const int2 positionSS,
const float rawMaskDepth,
const float mask,
inout float rawDepth,
inout float rawMeniscusDepth,
inout bool isWaterSurface,
inout bool isUnderwater,
inout bool hasCaustics,
inout bool hasMeniscus,
inout float sceneZ
)
{
const float rawSceneDepth = rawDepth;
hasCaustics = rawDepth != 0.0;
isWaterSurface = false;
isUnderwater = mask <= CREST_MASK_BELOW_SURFACE;
hasMeniscus = true;
rawMeniscusDepth = positionCS.z;
#if defined(CREST_WATER_VOLUME_HAS_BACKFACE) || defined(CREST_WATER_VOLUME_BACK_FACE) || defined(CREST_WATER_VOLUME_NEGATIVE)
const float rawGeometryDepth =
#if CREST_WATER_VOLUME_HAS_BACKFACE
// 3D has a back face texture for the depth.
LOAD_DEPTH_TEXTURE_X(_Crest_WaterVolumeBackFaceTexture, positionSS);
#elif CREST_WATER_VOLUME_NEGATIVE
LOAD_DEPTH_TEXTURE_X(_Crest_WaterVolumeFrontFaceTexture, positionSS);
#else
// Volume is rendered using the back face so that is the depth.
positionCS.z;
#endif // CREST_WATER_VOLUME_HAS_BACKFACE
;
if ((rawMaskDepth > 0.0 && rawMaskDepth <= rawSceneDepth) || (rawGeometryDepth > 0.0 && rawGeometryDepth <= rawSceneDepth))
{
hasMeniscus = false;
}
// Use backface depth if closest.
if (rawDepth < rawGeometryDepth)
{
// Cancels out caustics.
hasCaustics = false;
rawDepth = rawGeometryDepth;
}
#if CREST_WATER_VOLUME_NEGATIVE
rawMeniscusDepth = rawGeometryDepth;
if (rawGeometryDepth == 0.0)
{
const float rawBackFaceDepth = LOAD_DEPTH_TEXTURE_X(_Crest_WaterVolumeBackFaceTexture, positionSS);
// We are in the negative volume. Already handled by front face.
if (rawBackFaceDepth > 0.0)
{
if (rawBackFaceDepth <= rawSceneDepth)
{
hasMeniscus = false;
}
isUnderwater = false;
}
}
#endif // CREST_WATER_VOLUME_NEGATIVE
#endif // CREST_WATER_VOLUME
#if CREST_WATER_VOLUME_FRONT_FACE
// If negative volume, we keep the mask intact, and just mark it.
if (mask >= CREST_MASK_ABOVE_SURFACE_KEPT)
{
isUnderwater = true;
}
else if (mask <= CREST_MASK_BELOW_SURFACE_KEPT)
{
isUnderwater = false;
}
#endif
// Merge water depth with scene depth.
if (rawDepth < rawMaskDepth)
{
#if CREST_UNDERWATER_BEFORE_TRANSPARENT
// Apply fog to culled tiles otherwise there will be no fog as water shader can only fog enabled tiles. And
// only apply fog to culled tiles otherwise it will be fogged twice (second by water shader).
isUnderwater = mask <= CREST_MASK_BELOW_SURFACE_CULLED;
#endif
isWaterSurface = true;
hasCaustics = false;
rawDepth = rawMaskDepth;
}
sceneZ = Utility::CrestLinearEyeDepth(rawDepth);
}
void ApplyWaterVolumeToUnderwaterFogAndMeniscus(float4 positionCS, const float meniscusRawDepth, inout float fogDistance, inout float meniscusDepth)
{
#if CREST_WATER_VOLUME_FRONT_FACE
float depth = Utility::CrestLinearEyeDepth(positionCS.z);
// Meniscus is rendered at the boundary so use the geometry z.
meniscusDepth = depth;
fogDistance -= depth;
#else
float depth = Utility::CrestLinearEyeDepth(meniscusRawDepth);
// Meniscus is rendered at the boundary so use the geometry z.
meniscusDepth = depth;
#endif
}
half3 ApplyUnderwaterEffect(
half3 sceneColour,
const float rawDepth,
const float sceneZ,
const float fogDistance,
const half3 view,
const uint2 i_positionSS,
const float3 i_positionWS,
const bool hasCaustics
) {
const bool isUnderwater = true;
float3 lightDirection; float3 lightColor;
PrimaryLight(i_positionWS, lightColor, lightDirection);
// Uniform effect calculated from camera position.
half3 volumeLight = 0.0;
half3 volumeOpacity = 1.0;
{
half3 absorption = _Crest_Absorption.xyz;
half3 scattering = _Crest_Scattering.xyz;
// We sample shadows at the camera position. Pass a user defined slice offset for smoothing out detail.
// Offset slice so that we dont get high freq detail. But never use last lod as this has crossfading.
int sliceIndex = clamp(_Crest_DataSliceOffset, 0, g_Crest_LodCount - 2);
if (g_Crest_SampleAbsorptionSimulation) absorption = Cascade::MakeAbsorption(sliceIndex).Sample(_WorldSpaceCameraPos.xz).xyz;
if (g_Crest_SampleScatteringSimulation) scattering = Cascade::MakeScattering(sliceIndex).Sample(_WorldSpaceCameraPos.xz).xyz;
const float waterLevel = g_Crest_WaterCenter.y + Cascade::MakeAnimatedWaves(sliceIndex).Sample(_WorldSpaceCameraPos.xz).w;
half shadow = 1.0;
{
// #if CREST_SHADOWS_ON
// Camera should be at center of LOD system so no need for blending (alpha, weights, etc). This might not be
// the case if there is large horizontal displacement, but the _Crest_DataSliceOffset should help by setting a
// large enough slice as minimum.
half2 shadowSoftHard = Cascade::MakeShadow(sliceIndex).SampleShadow(_WorldSpaceCameraPos.xz);
// Soft in red, hard in green. But hard not computed in HDRP.
shadow = 1.0 - shadowSoftHard.x;
// #endif
}
half3 ambientLighting = _Crest_AmbientLighting;
#if CREST_HDRP
ApplyIndirectLightingMultiplier(ambientLighting);
#endif
// Out-Scattering Term.
{
float3 positionWS = i_positionWS;
#if !CREST_REFLECTION
// Project point onto sphere at the extinction length.
float3 toSphere = -view * _Crest_VolumeExtinctionLength * _Crest_OutScatteringExtinctionFactor;
float3 toScene = i_positionWS - _WorldSpaceCameraPos.xyz;
positionWS = _WorldSpaceCameraPos.xyz + toSphere;
// Get closest position.
positionWS = dot(toScene, toScene) < dot(toSphere, toSphere) ? i_positionWS : positionWS;
#endif
// Account for average extinction of light as it travels down through volume. Assume flat water as anything
// else would be expensive.
half3 extinction = (absorption + scattering) * _Crest_ExtinctionMultiplier;
float waterDepth = max(0.0, (waterLevel - positionWS.y));
#if CREST_REFLECTION
waterDepth *= 2.0;
if (rawDepth == 0.0) waterDepth = _Crest_VolumeExtinctionLength;
#else
// Full strength seems too extreme. Third strength seems reasonable.
waterDepth *= _Crest_OutScatteringFactor;
#endif
float3 outScatteringTerm = exp(-extinction * waterDepth);
// Transition between the Underwater Environmental Lighting (if present) and this. This will give us the
// benefit of both approaches.
outScatteringTerm = lerp(outScatteringTerm, 1.0, _Crest_UnderwaterEnvironmentalLightingWeight);
// Darken scene and light.
sceneColour *= outScatteringTerm;
#if !CREST_REFLECTION
lightColor *= outScatteringTerm;
ambientLighting *= outScatteringTerm;
#endif
}
VolumeLighting
(
absorption * _Crest_ExtinctionMultiplier,
scattering * _Crest_ExtinctionMultiplier,
_Crest_Anisotropy,
shadow,
view,
ambientLighting,
lightDirection,
lightColor,
half3(0.0, 0.0, 0.0),
_Crest_AmbientTerm,
_Crest_DirectTerm,
fogDistance,
_Crest_SunBoost,
_Crest_ShadowsAffectsAmbientFactor,
volumeLight,
volumeOpacity
);
}
#ifndef k_DisableCaustics
if (_Crest_CausticsEnabled && hasCaustics)
{
float3 position = i_positionWS;
#if CREST_BIRP
position = float3(i_positionSS, 0);
#endif
half lightOcclusion = PrimaryLightShadows(position);
half blur = 0.0;
const uint slice0 = PositionToSliceIndex(i_positionWS.xz, 0, g_Crest_WaterScale);
#ifdef CREST_FLOW_ON
half2 flowData = Cascade::MakeFlow(slice0).SampleFlow(i_positionWS.xz);
const Flow flow = Flow::Make(flowData, g_Crest_Time);
blur = _Crest_CausticsMotionBlur;
#endif
const float4 displacement = Cascade::MakeAnimatedWaves(slice0).Sample(i_positionWS.xz);
const float surfaceHeight = displacement.y + g_Crest_WaterCenter.y + displacement.w;
sceneColour *= Caustics
(
#ifdef CREST_FLOW_ON
flow,
#endif
i_positionWS,
surfaceHeight,
lightColor,
lightDirection,
lightOcclusion,
sceneZ,
_Crest_CausticsTiledTexture,
_Crest_CausticsTextureAverage,
_Crest_CausticsStrength,
_Crest_CausticsFocalDepth,
_Crest_CausticsDepthOfField,
_Crest_CausticsDistortionTiledTexture,
_Crest_CausticsDistortionStrength,
blur,
isUnderwater
);
}
#endif
#if CREST_HDRP
volumeLight *= GetCurrentExposureMultiplier();
#endif
#ifndef k_DisableDithering
#if d_Dithering
// Increasing intensity can be required for HDRP.
volumeLight += ScreenSpaceDither(i_positionSS) * _Crest_DitheringIntensity;
#endif
#endif
return lerp(sceneColour, volumeLight, volumeOpacity);
}
m_CrestNameSpaceEnd
#endif // CREST_UNDERWATER_EFFECT_SHARED_INCLUDED