You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

351 lines
13 KiB

#pragma kernel InitializePhillipsSpectrum
#pragma kernel EvaluateDispersion
#pragma kernel EvaluateNormals EVALUATE_ADDITIONAL_DATA=EvaluateNormals
#pragma kernel EvaluateNormalsJacobian EVALUATE_ADDITIONAL_DATA=EvaluateNormalsJacobian EVALUATE_JACOBIAN
#pragma kernel PrepareCausticsGeometry
#pragma kernel EvaluateInstanceData EVALUATE_INSTANCE_DATA=EvaluateInstanceData
#pragma kernel EvaluateInstanceDataInfinite EVALUATE_INSTANCE_DATA=EvaluateInstanceDataInfinite INFINITE_WATER
#pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch
// #pragma enable_d3d11_debug_symbols
// Required to be defined for some includes
#define WATER_SIMULATION
// These values are chosen so that an iFFT patch of 1000km^2 will
// yield a Phillips spectrum distribution in the [-1, 1] range
#define EARTH_GRAVITY 9.81
#define SQRT2 1.41421356237
#define ONE_OVER_SQRT2 0.70710678118
#define PHILLIPS_AMPLITUDE_SCALAR 0.2
// SRP generic includes
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Water/WaterSystemDef.cs.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Water/Shaders/WaterUtilities.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/FrustumCulling.hlsl"
uint4 WaterHashFunctionUInt4(uint3 coord)
{
uint4 x = coord.xyzz;
x = ((x >> 16u) ^ x.yzxy) * 0x45d9f3bu;
x = ((x >> 16u) ^ x.yzxz) * 0x45d9f3bu;
x = ((x >> 16u) ^ x.yzxx) * 0x45d9f3bu;
return x;
}
float4 WaterHashFunctionFloat4(uint3 p)
{
return WaterHashFunctionUInt4(p) / (float)0xffffffffU;
}
//http://www.dspguide.com/ch2/6.htm
float GaussianDis(float u, float v)
{
return sqrt(-2.0 * log(max(u, 1e-6f))) * cos(PI * v);
}
float Phillips(float2 k, float2 w, float V, float directionDampener, float invPatchSize)
{
float kk = k.x * k.x + k.y * k.y;
float result = 0.0;
if (kk != 0.0)
{
float L = (V * V) / EARTH_GRAVITY;
// To avoid _any_ directional bias when there is no wind we lerp towards 0.5f
float wk = lerp(dot(normalize(k), w), 0.5, directionDampener);
float phillips = (exp(-1.0f / (kk * L * L)) / (kk * kk)) * (wk * wk);
result = phillips * (wk < 0.0f ? directionDampener : 1.0);
}
return PHILLIPS_AMPLITUDE_SCALAR * result * invPatchSize * invPatchSize;
}
float2 ComplexExp(float arg)
{
return float2(cos(arg), sin(arg));
}
float2 ComplexMult(float2 a, float2 b)
{
return float2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}
float3 ShuffleDisplacement(float3 displacement)
{
return float3(-displacement.y, displacement.x, -displacement.z);
}
// InitializePhillipsSpectrum UAVS
RWTexture2DArray<float2> _H0BufferRW;
// The noise function that we use generates a visible assymetry in the spectrum, this solves the issue
#define NOISE_FUNCTION_OFFSET 64
[numthreads(8, 8, 1)]
void InitializePhillipsSpectrum(uint3 thread : SV_DispatchThreadID)
{
// See EvaluateWaterNoiseSampleOffset
int sampleOffset = 0;
if (_BandResolution == 128)
sampleOffset = 64;
if (_BandResolution == 64)
sampleOffset = 96;
// This water offset ensures that when we switch the resolution the spectrum stays about the same
uint3 sampleCoordinate = uint3(thread.xy + sampleOffset, thread.z);
// We need 4 random numbers from the sample coords
float4 rn = WaterHashFunctionFloat4(sampleCoordinate + NOISE_FUNCTION_OFFSET);
// First part of the phillips spectrum term
float2 E = ONE_OVER_SQRT2 * float2(GaussianDis(rn.x, rn.y), GaussianDis(rn.z, rn.w));
// Second part of the phillips spectrum term
float invPatchSize = GetBandPatchData(thread.z).x;
float2 k = TWO_PI * (thread.xy - _BandResolution * 0.5) * invPatchSize;
float2 windDirection = -OrientationToDirection(_PatchOrientation[thread.z]);
float P = Phillips(k, windDirection, _PatchWindSpeed[thread.z], _PatchDirectionDampener[thread.z], invPatchSize);
// Combine and output
_H0BufferRW[int3(thread.xyz)] = E * sqrt(P);
}
// EvaluateDispersion UAVS
Texture2DArray<float2> _H0Buffer;
RWTexture2DArray<float4> _HtRealBufferRW;
RWTexture2DArray<float4> _HtImaginaryBufferRW;
[numthreads(8, 8, 1)]
void EvaluateDispersion(uint3 currentThread : SV_DispatchThreadID)
{
float invPatchSize = GetBandPatchData(currentThread.z).x;
float2 k = TWO_PI * (currentThread.xy - _BandResolution * 0.5) * invPatchSize;
float kl = length(k);
float w = sqrt(EARTH_GRAVITY * kl);
float2 kx = float2(k.x / kl, 0.0);
float2 ky = float2(k.y / kl, 0.0);
float2 h0 = LOAD_TEXTURE2D_ARRAY(_H0Buffer, currentThread.xy, currentThread.z);
float2 ht = ComplexMult(h0, ComplexExp(w * _SimulationTime));
float2 dx = ComplexMult(ComplexMult(float2(0, -1), kx), ht);
float2 dy = ComplexMult(ComplexMult(float2(0, -1), ky), ht);
if (dx.x != dx.x) dx.x = 0.f;
if (dx.y != dx.y) dx.y = 0.f;
if (dy.x != dy.x) dy.x = 0.f;
if (dy.y != dy.y) dy.y = 0.f;
// TODO: This is a work around to handle singularity at origin.
// The above nan check should have picked it up but it doesn't
// work on metal. Must investigate.
uint halfBandResolution = _BandResolution / 2;
if((currentThread.x == halfBandResolution) && (currentThread.y == halfBandResolution))
{
dx = float2(0, 0);
dy = float2(0, 0);
}
_HtRealBufferRW[int3(currentThread.xy, currentThread.z)] = float4(ht.x, dx.x, dy.x, 0);
_HtImaginaryBufferRW[int3(currentThread.xy, currentThread.z)] = float4(ht.y, dx.y, dy.y, 0);
}
// EvaluateNormals UAVS
Texture2DArray<float4> _PreviousWaterAdditionalDataBuffer;
RWTexture2DArray<float4> _WaterAdditionalDataBufferRW;
void EvaluateDisplacedPoints(float3 displacementC, float3 displacementR, float3 displacementU,
float normalization, float pixelSize,
out float3 p0, out float3 p1, out float3 p2)
{
p0 = displacementC * normalization;
p1 = displacementR * normalization + float3(pixelSize, 0, 0);
p2 = displacementU * normalization + float3(0, 0, pixelSize);
}
float EvaluateJacobian(float3 p0, float3 p1, float3 p2, float pixelSize)
{
// Compute the jacobian of this texel
float Jxx = 1.f + (p1.x - p0.x) / pixelSize;
float Jyy = 1.f + (p2.z - p0.z) / pixelSize;
float Jyx = (p1.z - p0.z) / pixelSize;
float Jxy = (p2.x - p0.x) / pixelSize;
return (Jxx * Jyy - Jxy * Jyx);
}
[numthreads(8, 8, 1)]
void EVALUATE_ADDITIONAL_DATA(uint3 currentThread : SV_DispatchThreadID)
{
// Extract the information about the pixel to process
uint2 coord = currentThread.xy;
uint bandIdx = currentThread.z;
// Get the displacement we need for the evaluate (and re-order them)
float3 displacementCenter = ShuffleDisplacement(LOAD_TEXTURE2D_ARRAY(_WaterDisplacementBuffer, coord, bandIdx).xyz);
float3 displacementRight = ShuffleDisplacement(LOAD_TEXTURE2D_ARRAY(_WaterDisplacementBuffer, uint2(coord + int2(1, 0)) & (_BandResolution - 1), bandIdx).xyz);
float3 displacementUp = ShuffleDisplacement(LOAD_TEXTURE2D_ARRAY(_WaterDisplacementBuffer, uint2(coord + int2(0, 1)) & (_BandResolution - 1), bandIdx).xyz);
// Evaluate the displacement normalization factor and pixel size
float invPatchSize = GetBandPatchData(bandIdx).x;
float pixelSize = rcp(_BandResolution * invPatchSize);
// We evaluate the displacement without the choppiness as it doesn't behave properly for distance surfaces
float3 p0, p1, p2;
EvaluateDisplacedPoints(displacementCenter, displacementRight, displacementUp, GetPatchAmplitudeMultiplier(bandIdx), pixelSize, p0, p1, p2);
// Compute the surface gradients of this band
float2 surfaceGradient = EvaluateSurfaceGradients(p0, p1, p2);
// Evaluate the jacobian if required
float jacobian = 0.0;
#if defined(EVALUATE_JACOBIAN)
// Compute the jacobian of this texel
jacobian = EvaluateJacobian(p0, p1, p2, pixelSize);
#endif
// Output the normal and foam
_WaterAdditionalDataBufferRW[int3(coord, bandIdx)] = float4(surfaceGradient, jacobian, jacobian);
}
// Output indices for the caustics buffer
RWByteAddressBuffer _CauticsGeometryRW;
int _CausticGeometryResolution;
[numthreads(8, 8, 1)]
void PrepareCausticsGeometry(uint3 currentThread : SV_DispatchThreadID)
{
// This thread is in charge of outputing the indices of the quad which coordinates is currentThread.xy
uint2 coord = currentThread.xy;
// For a given quad resolution N x P, we have (N + 1) x (P + 1) vertices
// Vertices are ordered this way (N being the resolution of the grid)
// 0 1 2 ... N
// N+1 N+2 N+3 ... 2N+1
// ... ... ... ... ...
// (P-1)*N+P-1 (P -1)N+P (P -1)N+P+1 ... P*N+P
int quadIndex = currentThread.y * _CausticGeometryResolution + currentThread.x;
// Indices are ordered this way to be up facing
// A --- C
// | | |
// | | |
// B ----D
// A -> B -> C and C -> B -> D
uint A = quadIndex + currentThread.y;
uint B = A + _CausticGeometryResolution + 1;
uint C = A + 1;
uint D = B + 1;
// Output the indices of triangle 0 of the quad
_CauticsGeometryRW.Store((6 * quadIndex) * 4, A);
_CauticsGeometryRW.Store((6 * quadIndex + 1) * 4, B);
_CauticsGeometryRW.Store((6 * quadIndex + 2) * 4, C);
// Output the indices of triangle 1 of the quad
_CauticsGeometryRW.Store((6 * quadIndex + 3) * 4, C);
_CauticsGeometryRW.Store((6 * quadIndex + 4) * 4, B);
_CauticsGeometryRW.Store((6 * quadIndex + 5) * 4, D);
}
// Function that evaluates the bounds of a given patch based on it's index
void ComputePatchBounds(uint patch,
inout float2 center,
inout float2 size,
inout float2 rotation)
{
uint id = patch % 4;
float scale = 1 << (patch >> 2);
center = float2(-0.25f, -0.75f);
size = float2(1.5f, 0.5f) * 0.5f;
rotation = float2(scale, 0);
if (id == 1) rotation = float2(0, -scale);
if (id == 2) rotation = float2(-scale, 0);
if (id == 3) rotation = float2(0, scale);
center = center.x * rotation.xy + center.y * float2(-rotation.y, rotation.x);
size = size.x * abs(rotation.xy) + size.y * abs(rotation.yx);
center = center * _GridSize + _PatchOffset;
size = size * _GridSize + _MaxWaveDisplacement;
}
bool PatchIsInRegion(float2 center, float2 size)
{
return all(abs(center) < _RegionExtent + size);
}
#define MAX_PATCH_COUNT (7*7)
// Group share flag that allows us to keep track of the valid instances
groupshared uint gs_instanceValidity[MAX_PATCH_COUNT];
groupshared float2 gs_instanceRotation[MAX_PATCH_COUNT];
// Structure that holds the per data patch info
StructuredBuffer<FrustumGPU> _FrustumGPUBuffer;
RWStructuredBuffer<float2> _WaterPatchDataRW;
RWStructuredBuffer<int> _WaterInstanceDataRW;
[numthreads(MAX_PATCH_COUNT, 1, 1)]
void EVALUATE_INSTANCE_DATA(uint currentPatch : SV_DispatchThreadID)
{
// Compute the patch center and size while accounting for it's maximal deformation
float2 center, size, rotation;
ComputePatchBounds(currentPatch, center, size, rotation);
// Only include the instance if it is valid
bool patchIsVisible = (currentPatch >> 2) < _MaxLOD;
#if !defined(INFINITE_WATER)
// In case this is an non-infinite surface, we cull patches outside of the allowed region
patchIsVisible = patchIsVisible && PatchIsInRegion(center, size);
#endif
// Frustum cull the patch
if (patchIsVisible)
{
OrientedBBox obb;
obb.center = mul(ApplyCameraTranslationToMatrix(_WaterSurfaceTransform), float4(center.x, 0.0f, center.y, 1.0f)).xyz;
obb.right = _WaterSurfaceTransform_Inverse[0].xyz;
obb.up = _WaterSurfaceTransform_Inverse[1].xyz;
obb.extentX = size.x;
obb.extentY = _MaxWaveHeight + _MaxWaterDeformation;
obb.extentZ = size.y;
patchIsVisible = FrustumOBBIntersection(obb, _FrustumGPUBuffer[0]);
}
// Propagate the patch data
gs_instanceValidity[currentPatch] = patchIsVisible;
gs_instanceRotation[currentPatch] = rotation;
// Wait untill all workers have processed their patch data
GroupMemoryBarrierWithGroupSync();
// The first thread is in charge of outputing the instance data, their count
if (currentPatch == 0)
{
// For each patch that needs to be processed
uint totalInstanceCount = 0;
for (uint patchIdx = 0; patchIdx < _MaxLOD * 4; ++patchIdx)
{
if (gs_instanceValidity[patchIdx])
{
// Output the patch data to the next slot
_WaterPatchDataRW[totalInstanceCount] = gs_instanceRotation[patchIdx];
// Increment the count
totalInstanceCount++;
}
}
// Output the actual instance count
// NOTE: Here we have to multiply by the XR view count as it redispatches the geometry
// for each view
_WaterInstanceDataRW[1] = totalInstanceCount * _XRViewCount;
}
}