forked from BilalY/Rasagar
422 lines
18 KiB
C#
422 lines
18 KiB
C#
|
using Unity.Mathematics;
|
||
|
using static Unity.Mathematics.math;
|
||
|
|
||
|
namespace UnityEngine.Rendering.HighDefinition
|
||
|
{
|
||
|
partial class WaterSystem
|
||
|
{
|
||
|
static internal void GetFFTKernels(ComputeShader fourierTransformCS, WaterSimulationResolution resolution, out int rowKernel, out int columnKernel)
|
||
|
{
|
||
|
switch (resolution)
|
||
|
{
|
||
|
case WaterSimulationResolution.High256:
|
||
|
{
|
||
|
rowKernel = fourierTransformCS.FindKernel("RowPassTi_256");
|
||
|
columnKernel = fourierTransformCS.FindKernel("ColPassTi_256");
|
||
|
}
|
||
|
break;
|
||
|
case WaterSimulationResolution.Medium128:
|
||
|
{
|
||
|
rowKernel = fourierTransformCS.FindKernel("RowPassTi_128");
|
||
|
columnKernel = fourierTransformCS.FindKernel("ColPassTi_128");
|
||
|
}
|
||
|
break;
|
||
|
case WaterSimulationResolution.Low64:
|
||
|
{
|
||
|
rowKernel = fourierTransformCS.FindKernel("RowPassTi_64");
|
||
|
columnKernel = fourierTransformCS.FindKernel("ColPassTi_64");
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
{
|
||
|
rowKernel = fourierTransformCS.FindKernel("RowPassTi_64");
|
||
|
columnKernel = fourierTransformCS.FindKernel("ColPassTi_64");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static internal int EvaluateBandCount(WaterSurfaceType surfaceType, bool ripplesOn)
|
||
|
{
|
||
|
switch (surfaceType)
|
||
|
{
|
||
|
case WaterSurfaceType.OceanSeaLake:
|
||
|
return ripplesOn ? 3 : 2;
|
||
|
case WaterSurfaceType.River:
|
||
|
return ripplesOn ? 2 : 1;
|
||
|
case WaterSurfaceType.Pool:
|
||
|
return 1;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static internal int EvaluateCPUBandCount(WaterSurfaceType surfaceType, bool ripplesOn, bool evaluateRipplesCPU)
|
||
|
{
|
||
|
return EvaluateBandCount(surfaceType, ripplesOn && evaluateRipplesCPU);
|
||
|
}
|
||
|
|
||
|
static float EvaluateCausticsMaxLOD(WaterSurface.WaterCausticsResolution resolution)
|
||
|
{
|
||
|
switch (resolution)
|
||
|
{
|
||
|
case WaterSurface.WaterCausticsResolution.Caustics256:
|
||
|
return 2.0f;
|
||
|
case WaterSurface.WaterCausticsResolution.Caustics512:
|
||
|
return 3.0f;
|
||
|
case WaterSurface.WaterCausticsResolution.Caustics1024:
|
||
|
return 4.0f;
|
||
|
}
|
||
|
return 2.0f;
|
||
|
}
|
||
|
|
||
|
static internal int SanitizeCausticsBand(int band, int bandCount)
|
||
|
{
|
||
|
return Mathf.Min(band, bandCount - 1);
|
||
|
}
|
||
|
|
||
|
static internal float EvaluateFrequencyOffset(WaterSimulationResolution resolution)
|
||
|
{
|
||
|
switch (resolution)
|
||
|
{
|
||
|
case WaterSimulationResolution.High256:
|
||
|
return 0.5f;
|
||
|
case WaterSimulationResolution.Medium128:
|
||
|
return 0.25f;
|
||
|
case WaterSimulationResolution.Low64:
|
||
|
return 0.125f;
|
||
|
default:
|
||
|
return 0.5f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static internal int EvaluateWaterNoiseSampleOffset(WaterSimulationResolution resolution)
|
||
|
{
|
||
|
switch (resolution)
|
||
|
{
|
||
|
case WaterSimulationResolution.High256:
|
||
|
return 0;
|
||
|
case WaterSimulationResolution.Medium128:
|
||
|
return 64;
|
||
|
case WaterSimulationResolution.Low64:
|
||
|
return 96;
|
||
|
default:
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static internal void BuildGridMeshes(ref Mesh grid, ref Mesh ring, ref Mesh ringLow)
|
||
|
{
|
||
|
Vector3[] CreateNormals(int size)
|
||
|
{
|
||
|
Vector3[] normals = new Vector3[size];
|
||
|
for (int i = 0; i < size; ++i)
|
||
|
normals[i] = Vector3.up;
|
||
|
return normals;
|
||
|
}
|
||
|
|
||
|
int i, y, ti, vi;
|
||
|
|
||
|
// Build central grid
|
||
|
int meshResolution = WaterConsts.k_WaterTessellatedMeshResolution;
|
||
|
Vector3[] vertices = new Vector3[(meshResolution + 1) * (meshResolution + 1)];
|
||
|
for (i = 0, y = 0; y <= meshResolution; y++)
|
||
|
{
|
||
|
for (int x = 0; x <= meshResolution; x++, i++)
|
||
|
{
|
||
|
vertices[i] = new Vector3(x / (float)meshResolution - 0.5f, 0.0f, y / (float)meshResolution - 0.5f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int[] triangles = new int[meshResolution * meshResolution * 6];
|
||
|
for (ti = 0, vi = 0, y = 0; y < meshResolution; y++, vi++)
|
||
|
{
|
||
|
for (int x = 0; x < meshResolution; x++, ti += 6, vi++)
|
||
|
{
|
||
|
triangles[ti] = vi;
|
||
|
triangles[ti + 3] = triangles[ti + 2] = vi + 1;
|
||
|
triangles[ti + 4] = triangles[ti + 1] = vi + meshResolution + 1;
|
||
|
triangles[ti + 5] = vi + meshResolution + 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
grid = new Mesh()
|
||
|
{
|
||
|
vertices = vertices,
|
||
|
normals = CreateNormals(vertices.Length),
|
||
|
triangles = triangles,
|
||
|
};
|
||
|
|
||
|
// Build ring mesh with correct junctions
|
||
|
int resX = meshResolution - meshResolution / 4, resY = meshResolution / 4 - 1;
|
||
|
int subdivStart = meshResolution / 4, subdivCount = meshResolution;
|
||
|
|
||
|
int quadCount = 2 * ((meshResolution - meshResolution / 4) * (meshResolution / 4 - 1) + meshResolution / 4);
|
||
|
int triCount = quadCount + 3 * meshResolution / 2;
|
||
|
|
||
|
float offsetX = meshResolution / 4, scale = 2.0f / meshResolution;
|
||
|
vertices = new Vector3[(resY + 1) * (resX + 1) + (subdivStart + subdivCount + 1)];
|
||
|
for (i = 0, y = 0; y <= resY; y++)
|
||
|
{
|
||
|
for (int x = 0; x <= resX; x++, i++)
|
||
|
{
|
||
|
vertices[i] = new Vector3((x - offsetX) * scale - 0.5f, 0.0f, (y - resY - 1) * scale - 0.5f);
|
||
|
}
|
||
|
}
|
||
|
for (int x = 0; x <= subdivStart; x++, i++)
|
||
|
vertices[i] = new Vector3((x - offsetX) * scale - 0.5f, 0.0f, -0.5f);
|
||
|
for (int x = 1; x <= subdivCount; x++, i++)
|
||
|
vertices[i] = new Vector3(x * scale * 0.5f - 0.5f, 0.0f, -0.5f);
|
||
|
|
||
|
triangles = new int[triCount * 3];
|
||
|
for (ti = 0, vi = 0, y = 0; y < resY; y++, vi++)
|
||
|
{
|
||
|
for (int x = 0; x < resX; x++, ti += 6, vi++)
|
||
|
{
|
||
|
triangles[ti] = vi;
|
||
|
triangles[ti + 1] = vi + resX + 1;
|
||
|
triangles[ti + 2] = vi + 1;
|
||
|
|
||
|
triangles[ti + 3] = vi + 1;
|
||
|
triangles[ti + 4] = vi + resX + 1;
|
||
|
triangles[ti + 5] = vi + resX + 2;
|
||
|
}
|
||
|
}
|
||
|
for (int x = 0; x < subdivStart; x++, ti += 6, vi++)
|
||
|
{
|
||
|
triangles[ti] = vi;
|
||
|
triangles[ti + 1] = vi + resX + 1;
|
||
|
triangles[ti + 2] = vi + 1;
|
||
|
|
||
|
triangles[ti + 3] = vi + 1;
|
||
|
triangles[ti + 4] = vi + resX + 1;
|
||
|
triangles[ti + 5] = vi + resX + 2;
|
||
|
}
|
||
|
for (int x = 0; x < subdivCount / 2; x++, ti += 9, vi++)
|
||
|
{
|
||
|
triangles[ti] = vi;
|
||
|
triangles[ti + 1] = vi + resX + x + 1;
|
||
|
triangles[ti + 2] = vi + resX + x + 2;
|
||
|
|
||
|
triangles[ti + 3] = vi;
|
||
|
triangles[ti + 4] = vi + resX + x + 2;
|
||
|
triangles[ti + 5] = vi + 1;
|
||
|
|
||
|
triangles[ti + 6] = vi + 1;
|
||
|
triangles[ti + 7] = vi + resX + x + 2;
|
||
|
triangles[ti + 8] = vi + resX + x + 3;
|
||
|
}
|
||
|
|
||
|
ring = new Mesh()
|
||
|
{
|
||
|
vertices = vertices,
|
||
|
normals = CreateNormals(vertices.Length),
|
||
|
triangles = triangles,
|
||
|
};
|
||
|
|
||
|
// Build flat outer ring mesh, no need to handle junctions
|
||
|
// Although we put the mesh at 0.999 instead of 1 to avoid missing pixels at junction when rasterizing
|
||
|
float close = 0.999f, far = 1000.0f;
|
||
|
ringLow = new Mesh();
|
||
|
ringLow.vertices = new Vector3[] {
|
||
|
new Vector3(-1f, 0f, 1f) * close,
|
||
|
new Vector3( 1f, 0f, 1f) * close,
|
||
|
new Vector3(-1f, 0f, -1f) * close,
|
||
|
new Vector3( 1f, 0f, -1f) * close,
|
||
|
|
||
|
new Vector3(-1f, 0f, 1f) * far,
|
||
|
new Vector3( 1f, 0f, 1f) * far,
|
||
|
new Vector3(-1f, 0f, -1f) * far,
|
||
|
new Vector3( 1f, 0f, -1f) * far,
|
||
|
};
|
||
|
ringLow.normals = CreateNormals(ringLow.vertexCount);
|
||
|
ringLow.triangles = new int[] {
|
||
|
0, 4, 1, 1, 4, 5,
|
||
|
1, 5, 3, 3, 5, 7,
|
||
|
2, 3, 7, 2, 7, 6,
|
||
|
0, 2, 6, 0, 6, 4,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Converts an angle to a 2d direction
|
||
|
static internal float2 OrientationToDirection(float orientation)
|
||
|
{
|
||
|
float orientationRad = orientation * Mathf.Deg2Rad;
|
||
|
float directionX = Mathf.Cos(orientationRad);
|
||
|
float directionY = Mathf.Sin(orientationRad);
|
||
|
return new float2(directionX, directionY);
|
||
|
}
|
||
|
|
||
|
|
||
|
static internal float EvaluateSwellSecondPatchSize(float maxPatchSize)
|
||
|
{
|
||
|
float relativeSwellPatchSize = (maxPatchSize - WaterConsts.k_SwellMinPatchSize) / (WaterConsts.k_SwellMaxPatchSize - WaterConsts.k_SwellMinPatchSize);
|
||
|
return Mathf.Lerp(WaterConsts.k_SwellMinRatio, WaterConsts.k_SwellMaxRatio, relativeSwellPatchSize);
|
||
|
}
|
||
|
|
||
|
static internal float SampleMaxAmplitudeTable(int2 pixelCoord)
|
||
|
{
|
||
|
int clampedX = Mathf.Clamp(pixelCoord.x, 0, WaterConsts.k_TableResolution - 1);
|
||
|
int clampedY = Mathf.Clamp(pixelCoord.y, 0, WaterConsts.k_TableResolution - 1);
|
||
|
int tapIndex = clampedX + clampedY * WaterConsts.k_TableResolution;
|
||
|
return WaterConsts.k_MaximumAmplitudeTable[tapIndex];
|
||
|
}
|
||
|
|
||
|
static internal float NormalizeAngle(float degrees)
|
||
|
{
|
||
|
float angle = degrees % 360.0f;
|
||
|
return angle < 0.0 ? angle + 360.0f : angle;
|
||
|
}
|
||
|
|
||
|
static internal float EvaluateMaxAmplitude(float patchSize, float windSpeed)
|
||
|
{
|
||
|
// Convert the position from uv to floating pixel coordinates (for the bilinear interpolation)
|
||
|
Vector2 uv = new Vector2(windSpeed / WaterConsts.k_SwellMaximumWindSpeed, Mathf.Clamp((patchSize - 25.0f) / 4975.0f, 0.0f, 1.0f));
|
||
|
PrepareCoordinates(uv, WaterConsts.k_TableResolution - 1, out int2 pixelCoord, out float2 fract);
|
||
|
|
||
|
// Evaluate the UV for this sample
|
||
|
float p0 = SampleMaxAmplitudeTable(pixelCoord);
|
||
|
float p1 = SampleMaxAmplitudeTable(pixelCoord + new int2(1, 0));
|
||
|
float p2 = SampleMaxAmplitudeTable(pixelCoord + new int2(0, 1));
|
||
|
float p3 = SampleMaxAmplitudeTable(pixelCoord + new int2(1, 1));
|
||
|
|
||
|
// Do the bilinear interpolation
|
||
|
float i0 = lerp(p0, p1, fract.x);
|
||
|
float i1 = lerp(p2, p3, fract.x);
|
||
|
return lerp(i0, i1, fract.y);
|
||
|
}
|
||
|
|
||
|
// Function that returns a mip offset (for caustics) based on the simulation resolution
|
||
|
static internal int EvaluateNormalMipOffset(WaterSimulationResolution resolution)
|
||
|
{
|
||
|
switch (resolution)
|
||
|
{
|
||
|
case WaterSimulationResolution.High256:
|
||
|
return 2;
|
||
|
case WaterSimulationResolution.Medium128:
|
||
|
return 1;
|
||
|
case WaterSimulationResolution.Low64:
|
||
|
return 0;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void SetupWaterShaderKeyword(CommandBuffer cmd, bool decalWorkflow, int bandCount, bool localCurrent)
|
||
|
{
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_DECAL_PARTIAL", !decalWorkflow);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_DECAL_COMPLETE", decalWorkflow);
|
||
|
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_ONE_BAND", bandCount == 1);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_TWO_BANDS", bandCount == 2);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_THREE_BANDS", bandCount == 3);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_LOCAL_CURRENT", localCurrent);
|
||
|
}
|
||
|
|
||
|
static void ResetWaterShaderKeyword(CommandBuffer cmd)
|
||
|
{
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_ONE_BAND", false);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_TWO_BANDS", false);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_THREE_BANDS", false);
|
||
|
CoreUtils.SetKeyword(cmd, "WATER_LOCAL_CURRENT", false);
|
||
|
}
|
||
|
|
||
|
static bool FindPassIndex(Material material, string passName, out int passIndex)
|
||
|
{
|
||
|
passIndex = material.FindPass(passName);
|
||
|
#if UNITY_EDITOR
|
||
|
if (!UnityEditor.ShaderUtil.IsPassCompiled(material, passIndex))
|
||
|
{
|
||
|
UnityEditor.ShaderUtil.CompilePass(material, passIndex);
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void DrawInstancedQuads(CommandBuffer cmd, WaterRenderingData parameters, ref WaterSurfaceGBufferData surfaceData, int passIndex, int lowResPassIndex)
|
||
|
{
|
||
|
var cb = parameters.sharedPerCameraDataArray[surfaceData.surfaceIndex];
|
||
|
|
||
|
bool drawCentralPatch = true;
|
||
|
bool drawInfinitePatch = surfaceData.drawInfiniteMesh;
|
||
|
if (!surfaceData.infinite)
|
||
|
{
|
||
|
drawCentralPatch = all(abs(cb._PatchOffset) < cb._RegionExtent + cb._GridSize * 0.5f);
|
||
|
|
||
|
if (surfaceData.drawInfiniteMesh)
|
||
|
drawInfinitePatch = all(abs(cb._PatchOffset) < abs(cb._RegionExtent - cb._GridSize * 0.5f));
|
||
|
}
|
||
|
|
||
|
// Draw everything beyond distance fade with a single flat mesh
|
||
|
if (drawInfinitePatch)
|
||
|
cmd.DrawMesh(parameters.ringMeshLow, Matrix4x4.identity, surfaceData.waterMaterial, 0, lowResPassIndex, surfaceData.mpb);
|
||
|
|
||
|
// Draw high res grid under the camera
|
||
|
if (drawCentralPatch)
|
||
|
cmd.DrawMesh(parameters.tessellableMesh, Matrix4x4.identity, surfaceData.waterMaterial, 0, passIndex, surfaceData.mpb);
|
||
|
|
||
|
// Draw the remaining patches
|
||
|
if (cb._MaxLOD > 0)
|
||
|
{
|
||
|
int patchEvaluation = surfaceData.infinite ? parameters.patchEvaluationInfinite : parameters.patchEvaluation;
|
||
|
|
||
|
// Make sure both constant buffers are properly injected
|
||
|
BindPerSurfaceConstantBuffer(cmd, parameters.waterSimulation, parameters.perSurfaceCB[surfaceData.surfaceIndex]);
|
||
|
|
||
|
// Prepare the indirect parameters
|
||
|
cmd.SetComputeConstantBufferParam(parameters.waterSimulation, HDShaderIDs._ShaderVariablesWaterPerCamera, parameters.perCameraCB, 0, parameters.perCameraCB.stride);
|
||
|
cmd.SetComputeBufferParam(parameters.waterSimulation, patchEvaluation, HDShaderIDs._WaterPatchDataRW, parameters.patchDataBuffer);
|
||
|
cmd.SetComputeBufferParam(parameters.waterSimulation, patchEvaluation, HDShaderIDs._WaterInstanceDataRW, parameters.indirectBuffer);
|
||
|
cmd.DispatchCompute(parameters.waterSimulation, patchEvaluation, 1, 1, 1);
|
||
|
|
||
|
// Draw all the patches
|
||
|
cmd.DrawMeshInstancedIndirect(parameters.ringMesh, 0, surfaceData.waterMaterial, passIndex, parameters.indirectBuffer, 0, surfaceData.mpb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void DrawMeshRenderers(CommandBuffer cmd, ref WaterSurfaceGBufferData surfaceData, int passIndex)
|
||
|
{
|
||
|
int numMeshRenderers = surfaceData.meshRenderers.Count;
|
||
|
for (int meshRenderer = 0; meshRenderer < numMeshRenderers; ++meshRenderer)
|
||
|
{
|
||
|
MeshRenderer currentRenderer = surfaceData.meshRenderers[meshRenderer];
|
||
|
if (currentRenderer != null && currentRenderer.TryGetComponent(out MeshFilter filter))
|
||
|
{
|
||
|
Mesh mesh = filter.sharedMesh;
|
||
|
int numSubMeshes = mesh.subMeshCount;
|
||
|
for (int subMeshIdx = 0; subMeshIdx < numSubMeshes; ++subMeshIdx)
|
||
|
cmd.DrawMesh(mesh, currentRenderer.transform.localToWorldMatrix, surfaceData.waterMaterial, subMeshIdx, passIndex, surfaceData.mpb);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void DrawWaterSurface(CommandBuffer cmd, string[] passNames, WaterRenderingData parameters, ref WaterSurfaceGBufferData surfaceData)
|
||
|
{
|
||
|
int lowResPassIndex = 0;
|
||
|
bool missingPass = !FindPassIndex(surfaceData.waterMaterial, passNames[0], out var passIndex);
|
||
|
if (surfaceData.instancedQuads)
|
||
|
missingPass |= !FindPassIndex(surfaceData.waterMaterial, passNames[1], out lowResPassIndex);
|
||
|
if (missingPass)
|
||
|
return;
|
||
|
|
||
|
surfaceData.mpb.SetConstantBuffer(HDShaderIDs._ShaderVariablesWaterPerCamera, parameters.perCameraCB, 0, parameters.perCameraCB.stride);
|
||
|
|
||
|
if (surfaceData.instancedQuads)
|
||
|
{
|
||
|
DrawInstancedQuads(cmd, parameters, ref surfaceData, passIndex, lowResPassIndex);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Based on if this is a custom mesh or not trigger the right geometry/geometries and shader pass
|
||
|
if (!surfaceData.customMesh)
|
||
|
{
|
||
|
cmd.DrawMesh(parameters.tessellableMesh, Matrix4x4.identity, surfaceData.waterMaterial, 0, passIndex, surfaceData.mpb);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DrawMeshRenderers(cmd, ref surfaceData, passIndex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|