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.
 
 
 
 
 

543 lines
23 KiB

using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using static Unity.Mathematics.math;
namespace UnityEngine.Rendering.HighDefinition
{
/// <summary>
/// Structure that holds the water surface data used for height requests.
/// </summary>
public struct WaterSimSearchData
{
#region Simulation
internal float simulationTime;
internal int simulationRes;
internal bool cpuSimulation;
[ReadOnly] internal NativeArray<float4> displacementDataCPU;
[ReadOnly] internal NativeArray<half4> displacementDataGPU;
internal WaterSpectrumParameters spectrum;
internal WaterRenderingParameters rendering;
internal int activeBandCount;
#endregion
#region Water Mask
internal bool activeMask;
[ReadOnly] internal NativeArray<uint> maskBuffer;
internal TextureWrapMode maskWrapModeU;
internal TextureWrapMode maskWrapModeV;
internal int2 maskResolution;
internal float2 maskRemap;
internal float2 maskScale;
internal float2 maskOffset;
#endregion
#region Deformation
internal bool activeDeformation;
[ReadOnly] internal NativeArray<half> deformationBuffer;
internal int2 deformationResolution;
internal float2 deformationRegionScale;
internal float2 deformationRegionOffset;
internal float2 waterForwardXZ;
#endregion
#region Current Map
// Group 0
internal bool activeGroup0CurrentMap;
[ReadOnly] internal NativeArray<uint> group0CurrentMap;
internal TextureWrapMode group0CurrentMapWrapModeU;
internal TextureWrapMode group0CurrentMapWrapModeV;
internal int2 group0CurrentMapResolution;
internal float2 group0CurrentRegionScale;
internal float2 group0CurrentRegionOffset;
internal float group0CurrentMapInfluence;
// Group 1
internal bool activeGroup1CurrentMap;
[ReadOnly] internal NativeArray<uint> group1CurrentMap;
internal TextureWrapMode group1CurrentMapWrapModeU;
internal TextureWrapMode group1CurrentMapWrapModeV;
internal int2 group1CurrentMapResolution;
internal float2 group1CurrentRegionScale;
internal float2 group1CurrentRegionOffset;
internal float group1CurrentMapInfluence;
// Common
[ReadOnly] internal NativeArray<float4> sectorData;
#endregion
}
// NOTE: make sure that any new feature on the CPU requests are matched on the VFX sammple node
/// <summary>
/// Structure that holds the input parameters of the search.
/// </summary>
public struct WaterSearchParameters
{
/// <summary>
/// Target position in world space that the search needs to evaluate the height at.
/// </summary>
public float3 targetPositionWS;
/// <summary>
/// World Space Position that the search starts from. Can be used as a hint for the search algorithm.
/// </summary>
public float3 startPositionWS;
/// <summary>
/// Target error value at which the algorithm should stop.
/// </summary>
public float error;
/// <summary>
/// Number of iterations of the search algorithm.
/// </summary>
public int maxIterations;
/// <summary>
/// Specifies if the search should include deformation.
/// </summary>
public bool includeDeformation;
/// <summary>
/// Specifies if the search should ignore the simulation.
/// </summary>
public bool excludeSimulation;
/// <summary>
/// Specifies if the search should compute the normal of the water surface at the projected position.
/// </summary>
public bool outputNormal;
}
/// <summary>
/// Structure that holds the output parameters of the search.
/// </summary>
public struct WaterSearchResult
{
/// <summary>
/// Returns the world space position projected on the water surface along the up vector of the water surface.
/// </summary>
public float3 projectedPositionWS;
/// <summary>
/// If requested in the search parameters, returns the world space normal of the water surface at the projected position.
/// </summary>
public float3 normalWS;
/// <summary>
/// Location of the 3D world space point that has been displaced to the target positions
/// </summary>
public float3 candidateLocationWS;
/// <summary>
/// Vector that gives the local current orientation (if any).
/// </summary>
public float3 currentDirectionWS;
/// <summary>
/// Number of iterations of the search algorithm to find the height value.
/// </summary>
public int numIterations;
/// <summary>
/// Horizontal error value of the search algorithm.
/// </summary>
public float error;
}
public partial class HDRenderPipeline
{
static void EvaluateWaterDisplacement(WaterSimSearchData wsd, float3 positionAWS, bool includeSimulation, out float3 totalDisplacement, out float2 dir)
{
// Will hold the total displacement
totalDisplacement = 0.0f;
dir = OrientationToDirection(wsd.spectrum.patchOrientation.x);
if (includeSimulation)
{
// Compute the simulation coordinates
float2 uv = float2(positionAWS.x, positionAWS.z);
float3 waterMask = EvaluateWaterMask(wsd, uv);
// The behavior is different if we have a current map or we don't
if (wsd.activeGroup0CurrentMap || wsd.activeGroup1CurrentMap)
{
// Read the current data
CurrentData gr0CurrentData, gr1CurrentData;
EvaluateGroup0CurrentData(wsd, uv, out gr0CurrentData);
EvaluateGroup1CurrentData(wsd, uv, out gr1CurrentData);
// Rotate locally the current direction
float cosAngle = cos(gr0CurrentData.angle);
float sinAngle = sin(gr0CurrentData.angle);
dir = float2(dir.x * cosAngle - dir.y * sinAngle, dir.y * cosAngle + dir.x * sinAngle);
// Compute the simulation coordinates
float4 gr0uv, gr1uv;
SwizzleSamplingCoordinates(uv, gr0CurrentData.quadrant, wsd.sectorData, out gr0uv);
SwizzleSamplingCoordinates(uv, gr1CurrentData.quadrant, wsd.sectorData, out gr1uv);
// Compute the 2 simulation coordinates
WaterSimCoord gr0SimCoord;
WaterSimCoord gr1SimCoord;
WaterSimCoord finalSimCoord;
// Sample the simulation (first time)
ComputeWaterUVs(wsd, gr0uv.xy, out gr0SimCoord);
ComputeWaterUVs(wsd, gr1uv.xy, out gr1SimCoord);
AggregateWaterSimCoords(wsd, gr0SimCoord, gr1SimCoord, gr0CurrentData, gr1CurrentData, true, out finalSimCoord);
float3 totalDisplacement0 = EvaluateWaterSimulation(wsd, finalSimCoord, waterMask);
// Sample the simulation (second time)
ComputeWaterUVs(wsd, gr0uv.zw, out gr0SimCoord);
ComputeWaterUVs(wsd, gr1uv.zw, out gr1SimCoord);
AggregateWaterSimCoords(wsd, gr0SimCoord, gr1SimCoord, gr0CurrentData, gr1CurrentData, false, out finalSimCoord);
float3 totalDisplacement1 = EvaluateWaterSimulation(wsd, finalSimCoord, waterMask);
// Combine both contributions
totalDisplacement = totalDisplacement0 + totalDisplacement1;
}
else
{
ComputeWaterUVs(wsd, uv, out var coords);
totalDisplacement = EvaluateWaterSimulation(wsd, coords, waterMask);
}
// We only apply the choppiness tot he first two bands, doesn't behave very good past those
totalDisplacement.yz *= WaterConsts.k_WaterMaxChoppinessValue;
// The vertical displacement is stored in the X channel and the XZ displacement in the YZ channel
totalDisplacement = ShuffleDisplacement(totalDisplacement);
}
}
static float3 LoadDisplacement(WaterSimSearchData wsd, int2 coord, int bandIdx)
{
return wsd.cpuSimulation ? LoadTexture2DArray(wsd.displacementDataCPU, coord, bandIdx, wsd.simulationRes).xyz :
LoadTexture2DArray(wsd.displacementDataGPU, coord, bandIdx, wsd.simulationRes).xyz;
}
static float2 EvaluateWaterNormal(WaterSimSearchData wsd, WaterSimCoord waterCoord, float3 waterMask)
{
float2 surfaceGradient = 0;
for (int bandIdx = 0; bandIdx < wsd.activeBandCount; bandIdx++)
{
PatchSimData data = bandIdx == 0 ? waterCoord.data0 : (bandIdx == 1 ? waterCoord.data1 : waterCoord.data2);
int2 coord = (int2)(data.uv * wsd.simulationRes);
// Get the displacement we need for the evaluate (and re-order them)
// Note: we could do some sort of bilinear here to filter the result
float3 displacementCenter = ShuffleDisplacement(LoadDisplacement(wsd, coord, bandIdx));
float3 displacementRight = ShuffleDisplacement(LoadDisplacement(wsd, coord + int2(1, 0), bandIdx));
float3 displacementUp = ShuffleDisplacement(LoadDisplacement(wsd, coord + int2(0, 1), bandIdx));
// Evaluate the displacement normalization factor and pixel size
float pixelSize = wsd.spectrum.patchSizes[bandIdx] / (float)wsd.simulationRes;
// We evaluate the displacement without the choppiness as it doesn't behave properly for distance surfaces
EvaluateDisplacedPoints(displacementCenter, displacementRight, displacementUp, wsd.rendering.patchAmplitudeMultiplier[bandIdx], pixelSize,
out var p0, out var p1, out var p2);
// Compute the surface gradients of this band
float2 additionalData = EvaluateSurfaceGradients(p0, p1, p2);
additionalData.xy *= data.blend * waterMask[bandIdx];
// Swizzle the displacement
additionalData.xy = float2(dot(additionalData.xy, data.swizzle.xy), dot(additionalData.xy, data.swizzle.zw));
// Evaluate the surface gradient
surfaceGradient += additionalData.xy;
}
return surfaceGradient;
}
static float3 EvaluateNormal(WaterSimSearchData wsd, WaterSearchParameters wsp, float3 positionAWS)
{
float2 surfaceGradient = 0;
float2 uv = float2(positionAWS.x, positionAWS.z);
float3 waterMask = EvaluateWaterMask(wsd, uv);
if (!wsp.excludeSimulation)
{
if (wsd.activeGroup0CurrentMap || wsd.activeGroup1CurrentMap)
{
// Read the current data
CurrentData gr0CurrentData, gr1CurrentData;
EvaluateGroup0CurrentData(wsd, uv, out gr0CurrentData);
EvaluateGroup1CurrentData(wsd, uv, out gr1CurrentData);
// Compute the simulation coordinates
float4 gr0uv, gr1uv;
SwizzleSamplingCoordinates(uv, gr0CurrentData.quadrant, wsd.sectorData, out gr0uv);
SwizzleSamplingCoordinates(uv, gr1CurrentData.quadrant, wsd.sectorData, out gr1uv);
// Compute the 2 simulation coordinates
WaterSimCoord gr0SimCoord;
WaterSimCoord gr1SimCoord;
WaterSimCoord finalSimCoord;
// Sample the simulation (first time)
ComputeWaterUVs(wsd, gr0uv.xy, out gr0SimCoord);
ComputeWaterUVs(wsd, gr1uv.xy, out gr1SimCoord);
AggregateWaterSimCoords(wsd, gr0SimCoord, gr1SimCoord, gr0CurrentData, gr1CurrentData, true, out finalSimCoord);
float2 surfaceGradient0 = EvaluateWaterNormal(wsd, finalSimCoord, waterMask);
// Sample the simulation (second time)
ComputeWaterUVs(wsd, gr0uv.zw, out gr0SimCoord);
ComputeWaterUVs(wsd, gr1uv.zw, out gr1SimCoord);
AggregateWaterSimCoords(wsd, gr0SimCoord, gr1SimCoord, gr0CurrentData, gr1CurrentData, false, out finalSimCoord);
float2 surfaceGradient1 = EvaluateWaterNormal(wsd, finalSimCoord, waterMask);
// Combine both contributions
surfaceGradient = surfaceGradient0 + surfaceGradient1;
}
else
{
ComputeWaterUVs(wsd, uv, out var waterCoord);
surfaceGradient = EvaluateWaterNormal(wsd, waterCoord, waterMask);
}
}
if (wsp.includeDeformation)
surfaceGradient += EvaluateDeformerNormal(wsd, wsp.targetPositionWS);
return SurfaceGradientResolveNormal(float3(0, 1, 0), float3(surfaceGradient.x, 0, surfaceGradient.y));
}
internal struct WaterSimulationTapData
{
public float3 currentDisplacement;
public float2 direction;
public float3 displacedPoint;
public float2 offset;
public float distance;
public float height;
}
static WaterSimulationTapData EvaluateDisplacementData(WaterSimSearchData wsd, float3 currentLocation, float3 referencePosition, bool includeSimulation)
{
WaterSimulationTapData data;
// Evaluate the displacement at the current point
EvaluateWaterDisplacement(wsd, currentLocation, includeSimulation, out data.currentDisplacement, out data.direction);
// Evaluate the complete position
data.displacedPoint = currentLocation + data.currentDisplacement;
// Evaluate the distance to the reference point
data.offset = referencePosition.xz - data.displacedPoint.xz;
// Length of the offset vector
data.distance = Mathf.Sqrt(data.offset.x * data.offset.x + data.offset.y * data.offset.y);
// Simulation height of the position of the offset vector
data.height = data.currentDisplacement.y;
return data;
}
internal static bool ProjectPointOnWaterSurface(WaterSimSearchData wsd,
WaterSearchParameters wsp,
ref WaterSearchResult sr)
{
// Convert the target position to the water object space
float3 targetPositionOS = mul(wsd.rendering.worldToWaterMatrix, float4(wsp.targetPositionWS, 1.0f)).xyz;
// Convert the target position to the water object space
float3 startPositionOS = mul(wsd.rendering.worldToWaterMatrix, float4(wsp.startPositionWS, 1.0f)).xyz;
// Initialize the search data
WaterSimulationTapData tapData = EvaluateDisplacementData(wsd, startPositionOS, targetPositionOS, !wsp.excludeSimulation);
if (float.IsNaN(tapData.distance))
return false;
float2 stepSize = tapData.offset;
sr.error = tapData.distance;
sr.candidateLocationWS = startPositionOS;
sr.currentDirectionWS = float3(tapData.direction.x, 0, tapData.direction.y);
sr.numIterations = 0;
// Go through the steps until we found a position that satisfies our constraints
while (sr.numIterations < wsp.maxIterations)
{
// Is the point close enough to target position?
if (sr.error < wsp.error)
break;
// Reset the search progress flag
bool progress = false;
float3 candidateLocation = sr.candidateLocationWS + new float3(stepSize.x, 0, stepSize.y);
tapData = EvaluateDisplacementData(wsd, candidateLocation, targetPositionOS, !wsp.excludeSimulation);
if (float.IsNaN(tapData.distance))
return false;
if (tapData.distance < sr.error)
{
sr.candidateLocationWS = candidateLocation;
stepSize = tapData.offset;
sr.error = tapData.distance;
sr.currentDirectionWS = float3(tapData.direction.x, 0, tapData.direction.y);
progress = true;
}
// If we didn't make any progress in this step, this means out steps are probably too big make them smaller
if (!progress)
stepSize *= 0.5f;
sr.numIterations++;
}
// Apply the deformation if required
if (wsp.includeDeformation && wsd.activeDeformation)
{
float deformation = EvaluateDeformers(wsd, wsp.targetPositionWS);
if (float.IsNaN(deformation))
return false;
tapData.displacedPoint.y += deformation;
tapData.currentDisplacement.y += deformation;
tapData.height = tapData.currentDisplacement.y;
}
// Convert the positions from OS to world space
sr.projectedPositionWS = mul(wsd.rendering.waterToWorldMatrix, float4(targetPositionOS.x, tapData.height, targetPositionOS.z, 1.0f)).xyz;
sr.candidateLocationWS = mul(wsd.rendering.waterToWorldMatrix, float4(sr.candidateLocationWS, 1.0f)).xyz;
if (wsp.outputNormal)
{
float3 normalOS = EvaluateNormal(wsd, wsp, sr.candidateLocationWS);
sr.normalWS = mul((float3x3)wsd.rendering.waterToWorldMatrix, normalOS);
}
return true;
}
}
/// <summary>
/// C# Job that evaluate the height for a set of WaterSearchParameters and returns a set of WaterSearchResult (and stored them into native buffers).
/// </summary>
[BurstCompile]
public struct WaterSimulationSearchJob : IJobParallelFor
{
/// <summary>
/// Input simulation search data produced by the water surface.
/// </summary>
public WaterSimSearchData simSearchData;
/// <summary>
/// Native array that holds the set of position that the job will need to evaluate the height for.
/// </summary>
[ReadOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float3> targetPositionWSBuffer;
/// <summary>
/// Native array that holds the set of "hint" position that the algorithm starts from.
/// </summary>
[ReadOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float3> startPositionWSBuffer;
/// <summary>
/// Target error value at which the algorithm should stop.
/// </summary>
public float error;
/// <summary>
/// Number of iterations of the search algorithm.
/// </summary>
public int maxIterations;
/// <summary>
/// Specifies if the search job should include the deformations
/// </summary>
public bool includeDeformation;
/// <summary>
/// Specifies if the search should ignore the simulation.
/// </summary>
public bool excludeSimulation;
/// <summary>
/// Specifies if the search should compute the normal of the water surface at the projected position.
/// </summary>
public bool outputNormal;
/// <summary>
/// Output native array that holds the set of world space position projected on the water surface along the up vector of the water surface.
/// </summary>
[WriteOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float3> projectedPositionWSBuffer;
/// <summary>
/// Output native array that holds the set of normals at projected positions.
/// </summary>
[WriteOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float3> normalWSBuffer;
/// <summary>
/// Output native array that holds the set of horizontal error for each target position.
/// </summary>
[WriteOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float> errorBuffer;
/// <summary>
/// Output native array that holds the set of positions that were used to generate the height value.
/// </summary>
[WriteOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float3> candidateLocationWSBuffer;
/// <summary>
/// Output native array that holds the set of direction for each target position;
/// </summary>
[WriteOnly]
[NativeDisableParallelForRestriction]
public NativeArray<float3> directionBuffer;
/// <summary>
/// Output native array that holds the set of steps that were executed to find the height.
/// </summary>
[WriteOnly]
[NativeDisableParallelForRestriction]
public NativeArray<int> stepCountBuffer;
/// <summary>
/// Function that evaluates the height for a given element in the input buffer.
/// </summary>
/// <param name="index">The index of the element that the function will process.</param>
public void Execute(int index)
{
// Fill the search parameters
WaterSearchParameters wsp = new WaterSearchParameters();
wsp.targetPositionWS = targetPositionWSBuffer[index];
wsp.startPositionWS = startPositionWSBuffer[index];
wsp.error = error;
wsp.maxIterations = maxIterations;
wsp.includeDeformation = includeDeformation;
wsp.excludeSimulation = excludeSimulation;
wsp.outputNormal = outputNormal;
// Do the search
var wsr = new WaterSearchResult();
HDRenderPipeline.ProjectPointOnWaterSurface(simSearchData, wsp, ref wsr);
// Output the result to the output buffers
errorBuffer[index] = wsr.error;
candidateLocationWSBuffer[index] = wsr.candidateLocationWS;
projectedPositionWSBuffer[index] = wsr.projectedPositionWS;
directionBuffer[index] = wsr.currentDirectionWS;
stepCountBuffer[index] = wsr.numIterations;
if (outputNormal)
normalWSBuffer[index] = wsr.normalWS;
}
}
}