(Update) Clean Projet
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
||||
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
||||
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
||||
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Rendering;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace StylizedWater3
|
||||
{
|
||||
public static partial class HeightQuerySystem
|
||||
{
|
||||
public class Query
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum allowed number of sample positions allowed within a single height query
|
||||
/// A value too high may result in decreased performance, as the data payload needed to be retrieved from the GPU becomes too large.
|
||||
/// </summary>
|
||||
public const int MAX_SIZE = 128;
|
||||
|
||||
//Input
|
||||
private NativeArray<float3> samplePositions;
|
||||
public readonly GraphicsBuffer inputPositionBuffer;
|
||||
|
||||
//Output
|
||||
public NativeArray<float> outputOffsets;
|
||||
public readonly GraphicsBuffer outputOffsetsBuffer;
|
||||
|
||||
private int currentBufferIndex = 0;
|
||||
//Need to keep a buffer alive so it can be used the next frame
|
||||
private readonly NativeArray<float>[] readbackBuffers = new NativeArray<float>[2];
|
||||
|
||||
//Every request is issued with an ID
|
||||
public readonly Dictionary<int, AsyncRequest> requests = new Dictionary<int, AsyncRequest>();
|
||||
|
||||
//Index of the last request.
|
||||
public int sampleCount;
|
||||
private bool hasPendingRequest;
|
||||
|
||||
public List<int> availableIndices;
|
||||
|
||||
public Query()
|
||||
{
|
||||
RecreateIndexPool();
|
||||
|
||||
//CPU input
|
||||
samplePositions = new NativeArray<float3>(MAX_SIZE, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
//GPU input
|
||||
inputPositionBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, MAX_SIZE, 3 * sizeof(float));
|
||||
inputPositionBuffer.name = "Water Height Query: Sample Positions";
|
||||
|
||||
//GPU output
|
||||
outputOffsetsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, MAX_SIZE, sizeof(float));
|
||||
inputPositionBuffer.name = "Water Height Query: Sampled Heights";
|
||||
}
|
||||
|
||||
public int GetNextAvailableIndex()
|
||||
{
|
||||
var index = availableIndices[0];
|
||||
|
||||
//Remove as it is now no longer available
|
||||
availableIndices.Remove(index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public void ReleaseIndex(int index)
|
||||
{
|
||||
availableIndices.Add(index);
|
||||
|
||||
//Sorting optimizes the array occupancy
|
||||
availableIndices.Sort();
|
||||
}
|
||||
|
||||
private void RecreateIndexPool()
|
||||
{
|
||||
//Populate pool of available indices
|
||||
availableIndices = new List<int>();
|
||||
for (int i = 0; i < MAX_SIZE; i++)
|
||||
{
|
||||
availableIndices.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
//Combine all the positions from the requests into one list
|
||||
private void PopulateSampleList()
|
||||
{
|
||||
//Copy all the sample positions in the queue to the summed array
|
||||
foreach (KeyValuePair<int, AsyncRequest> request in requests)
|
||||
{
|
||||
for (int i = 0; i < request.Value.indices.Count; i++)
|
||||
{
|
||||
int index = request.Value.indices[i];
|
||||
|
||||
//NOTE: Setting items of a NativeArray is slow
|
||||
samplePositions[index] = request.Value.sampler.positions[i];
|
||||
}
|
||||
}
|
||||
|
||||
//Note: unused indices are not initialized.
|
||||
//Compute shader is configured not to sample them. Doing so otherwise causes VRAM corruption on Metal.
|
||||
}
|
||||
|
||||
//Dispatch the compute shader. The "offsets" array will be populated based on the current GPU buffer contents.
|
||||
public void Dispatch(ComputeCommandBuffer cmd, ComputeShader cs, int kernel)
|
||||
{
|
||||
Profiler.BeginSample($"{PROFILER_PREFIX} Setup and dispatch");
|
||||
|
||||
PopulateSampleList();
|
||||
|
||||
cmd.SetBufferData(inputPositionBuffer, samplePositions);
|
||||
cmd.SetComputeIntParam(cs, "sampleCount", sampleCount);
|
||||
|
||||
cmd.SetComputeBufferParam(cs, kernel, "positions", inputPositionBuffer);
|
||||
|
||||
//Output
|
||||
cmd.SetComputeBufferParam(cs, kernel, "offsets", outputOffsetsBuffer);
|
||||
|
||||
cmd.DispatchCompute(cs, kernel, RenderPass.THREAD_GROUPS, 1, 1);
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
void ValidateNativeBuffer(ref NativeArray<float> buffer)
|
||||
{
|
||||
if (!buffer.IsCreated || buffer.Length != MAX_SIZE)
|
||||
{
|
||||
if (buffer.IsCreated) buffer.Dispose();
|
||||
|
||||
buffer = new NativeArray<float>(MAX_SIZE, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
}
|
||||
}
|
||||
|
||||
public void Readback(UnsafeCommandBuffer cmd)
|
||||
{
|
||||
//Array was disposed of after readback request. Forced to recreate it
|
||||
//https://forum.unity.com/threads/asyncgpureadback-requestintonativearray-causes-invalidoperationexception-on-nativearray.1011955
|
||||
//AllocateReadbackBuffer();
|
||||
|
||||
//Query may have been disposed, but an async readback was still pending...
|
||||
if (hasPendingRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
hasPendingRequest = true;
|
||||
|
||||
//After the readback request is complete Unity will dispose of this array automatically. Possibly when 'GetData' is called.
|
||||
//Hence a swap-buffer method is employed
|
||||
ValidateNativeBuffer(ref readbackBuffers[0]);
|
||||
ValidateNativeBuffer(ref readbackBuffers[1]);
|
||||
|
||||
NativeArray<float> nextBuffer = readbackBuffers[NextBufferIndex()];
|
||||
|
||||
#if UNITY_EDITOR
|
||||
//Unity dev: Remove when bug is fixed
|
||||
AtomicSafetyHandle ash = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(nextBuffer);
|
||||
AtomicSafetyHandle.CheckReadAndThrow(ash);
|
||||
AtomicSafetyHandle.CheckDeallocateAndThrow(ash);
|
||||
#endif
|
||||
|
||||
cmd.RequestAsyncReadbackIntoNativeArray(ref nextBuffer, outputOffsetsBuffer, MAX_SIZE * outputOffsetsBuffer.stride, 0, OnCompleteReadback);
|
||||
}
|
||||
|
||||
private void SwapCurrentBuffer()
|
||||
{
|
||||
currentBufferIndex = (currentBufferIndex + 1) % 2;
|
||||
}
|
||||
|
||||
int NextBufferIndex()
|
||||
{
|
||||
//return 0;
|
||||
return (currentBufferIndex + 1) % 2;
|
||||
}
|
||||
|
||||
private void OnCompleteReadback(AsyncGPUReadbackRequest asyncGPUReadbackRequest)
|
||||
{
|
||||
if (asyncGPUReadbackRequest.hasError)
|
||||
{
|
||||
throw new Exception("Error reading GPU water height with AsyncGPUReadbackRequest.");
|
||||
}
|
||||
|
||||
Profiler.BeginSample($"{PROFILER_PREFIX} Readback data");
|
||||
|
||||
outputOffsets = asyncGPUReadbackRequest.GetData<float>();
|
||||
|
||||
foreach (KeyValuePair<int, AsyncRequest> m_request in requests)
|
||||
{
|
||||
AsyncRequest request = m_request.Value;
|
||||
|
||||
int queryLength = request.indices.Count;
|
||||
|
||||
for (int i = 0; i < queryLength; i++)
|
||||
{
|
||||
//List of indices this request occupies in the query
|
||||
int index = request.indices[i];
|
||||
|
||||
var waterHeight = outputOffsets[index];
|
||||
|
||||
//Height value equals a void do not assign it
|
||||
if (request.invalidateMisses && EqualsVoid(waterHeight))
|
||||
{
|
||||
//Debug.Log($"Height request for {request.label} at index {index} was invalidated (value={waterHeight}).");
|
||||
continue;
|
||||
}
|
||||
|
||||
request.sampler.heightValues[i] = waterHeight;
|
||||
}
|
||||
|
||||
//Issue a callback event for the external scripts that issued the request
|
||||
request.InvokeCallback();
|
||||
}
|
||||
outputOffsets.Dispose();
|
||||
|
||||
SwapCurrentBuffer();
|
||||
|
||||
hasPendingRequest = false;
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (KeyValuePair<int, AsyncRequest> request in requests)
|
||||
{
|
||||
request.Value.sampler.Dispose();
|
||||
}
|
||||
requests.Clear();
|
||||
|
||||
RecreateIndexPool();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
|
||||
//Remove itself from the list of queries
|
||||
queries.Remove(this);
|
||||
QueryCount--;
|
||||
|
||||
//Wait before we freeing the resources
|
||||
if (hasPendingRequest) AsyncGPUReadback.WaitAllRequests();
|
||||
|
||||
samplePositions.Dispose();
|
||||
inputPositionBuffer.Dispose();
|
||||
|
||||
//Dispose any allocated arrays
|
||||
outputOffsetsBuffer.Dispose();
|
||||
|
||||
if (readbackBuffers[0].IsCreated)
|
||||
readbackBuffers[0].Dispose();
|
||||
|
||||
if (readbackBuffers[1].IsCreated)
|
||||
readbackBuffers[1].Dispose();
|
||||
|
||||
//If the query is being disposed, whilst no readback request was pending then this array would not be allocated
|
||||
if(outputOffsets.IsCreated) outputOffsets.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c49a6fa4f6da4e81b30d4703ef16ca15
|
||||
timeCreated: 1718013507
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 287769
|
||||
packageName: Stylized Water 3
|
||||
packageVersion: 3.2.6
|
||||
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Query.cs
|
||||
uploadId: 895866
|
||||
@@ -0,0 +1,142 @@
|
||||
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
||||
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
||||
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
||||
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.RenderGraphModule;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace StylizedWater3
|
||||
{
|
||||
public static partial class HeightQuerySystem
|
||||
{
|
||||
public class RenderPass : ScriptableRenderPass
|
||||
{
|
||||
public const int THREAD_GROUPS = 64; //Value must be mirrored in compute shader
|
||||
|
||||
private const string PROFILER_PREFIX = "[GPU] Water Height Query:";
|
||||
|
||||
private static readonly ProfilingSampler computeProfilerSampler = new ProfilingSampler( $"{PROFILER_PREFIX} Dispatch");
|
||||
private static readonly ProfilingSampler readbackAsyncProfilerSampler = new ProfilingSampler( $"{PROFILER_PREFIX} Readback Async");
|
||||
|
||||
private ComputeShader cs;
|
||||
private int kernel = -1;
|
||||
|
||||
public void Setup(StylizedWaterRenderFeature renderFeature, ComputeShader heightReadbackCs)
|
||||
{
|
||||
if (!heightReadbackCs)
|
||||
{
|
||||
Debug.LogError("[Stylized Water 3] Height query render pass initialized with an empty compute shader reference. Was it deleted from the project? Or not referenced on the render feature?" +
|
||||
" This may happen when deleting the Library folder, creating a race condition where the Compute shader isn't yet imported. You should not add render features in play mode!.", renderFeature);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cs = heightReadbackCs;
|
||||
this.kernel = cs.FindKernel("SampleOffsets");
|
||||
}
|
||||
|
||||
private class PassData
|
||||
{
|
||||
// Compute shader.
|
||||
public ComputeShader cs;
|
||||
public int kernel;
|
||||
|
||||
// Buffer handles for the compute buffers.
|
||||
public GraphicsBuffer[] input;
|
||||
public GraphicsBuffer[] output;
|
||||
}
|
||||
|
||||
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameContext)
|
||||
{
|
||||
if (kernel < 0) return;
|
||||
|
||||
HeightPrePass.FrameData heightPrePassData = frameContext.Get<HeightPrePass.FrameData>();
|
||||
|
||||
bool cull = HeightQuerySystem.QueryCount == 0;
|
||||
|
||||
using(var builder = renderGraph.AddComputePass($"Water Height Query Sampling", out PassData passData))
|
||||
{
|
||||
passData.cs = this.cs;
|
||||
passData.kernel = this.kernel;
|
||||
|
||||
passData.input = new GraphicsBuffer[HeightQuerySystem.QueryCount];
|
||||
passData.output = new GraphicsBuffer[HeightQuerySystem.QueryCount];
|
||||
|
||||
for (int i = 0; i < HeightQuerySystem.QueryCount; i++)
|
||||
{
|
||||
passData.input[i] = HeightQuerySystem.queries[i].inputPositionBuffer;
|
||||
BufferHandle inputBufferHandle = renderGraph.ImportBuffer(passData.input[i]);
|
||||
builder.UseBuffer(inputBufferHandle);
|
||||
|
||||
passData.output[i] = HeightQuerySystem.queries[i].outputOffsetsBuffer;
|
||||
BufferHandle outputBufferHandle = renderGraph.ImportBuffer(passData.output[i]);
|
||||
builder.UseBuffer(outputBufferHandle);
|
||||
}
|
||||
|
||||
//Input
|
||||
builder.UseTexture(heightPrePassData._WaterHeightBuffer);
|
||||
|
||||
builder.AllowPassCulling(cull);
|
||||
builder.SetRenderFunc((PassData data, ComputeGraphContext cgContext) => ExecuteSampling(data, cgContext));
|
||||
}
|
||||
|
||||
using(var builder = renderGraph.AddUnsafePass("Water Height Query: Async Readback", out PassData passData))
|
||||
{
|
||||
//WebGPU and Nintendo Switch would not support this
|
||||
builder.EnableAsyncCompute(SystemInfo.supportsAsyncCompute);
|
||||
|
||||
builder.AllowPassCulling(cull);
|
||||
builder.SetRenderFunc((PassData data, UnsafeGraphContext cgContext) => ExecuteReadback(data, cgContext));
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSampling(PassData data, ComputeGraphContext context)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (HeightQuerySystem.DISABLE_IN_EDIT_MODE && Application.isPlaying == false) return;
|
||||
#endif
|
||||
|
||||
var cmd = context.cmd;
|
||||
using (new ProfilingScope(cmd, computeProfilerSampler))
|
||||
{
|
||||
foreach (var q in HeightQuerySystem.queries)
|
||||
{
|
||||
q.Dispatch(cmd, data.cs, data.kernel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Pass using "cmd.RequestAsyncReadbackIntoNativeArray"
|
||||
private void ExecuteReadback(PassData data, UnsafeGraphContext context)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (HeightQuerySystem.DISABLE_IN_EDIT_MODE && Application.isPlaying == false) return;
|
||||
#endif
|
||||
|
||||
var cmd = context.cmd;
|
||||
|
||||
using (new ProfilingScope(cmd, readbackAsyncProfilerSampler))
|
||||
{
|
||||
foreach (var q in HeightQuerySystem.queries)
|
||||
{
|
||||
q.Readback(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !UNITY_6000_4_OR_NEWER
|
||||
#pragma warning disable CS0672
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { }
|
||||
#pragma warning restore CS0672
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e53401f9bd904222bee47b59d0342925
|
||||
timeCreated: 1718013618
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 287769
|
||||
packageName: Stylized Water 3
|
||||
packageVersion: 3.2.6
|
||||
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Rendering.cs
|
||||
uploadId: 895866
|
||||
@@ -0,0 +1,80 @@
|
||||
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
||||
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
||||
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
||||
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StylizedWater3
|
||||
{
|
||||
public partial class HeightQuerySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Issues the data from a <see cref="HeightQuerySystem.Sampler"/> to an asynchronous GPU readback request.
|
||||
/// </summary>
|
||||
public class AsyncRequest
|
||||
{
|
||||
public delegate void OnRequestCompleted();
|
||||
/// <summary>
|
||||
/// Callback fired whenever a height query was successfully returned from the GPU.
|
||||
/// </summary>
|
||||
public event OnRequestCompleted onCompleted;
|
||||
|
||||
//GUID
|
||||
public readonly int hashCode;
|
||||
//Identifier, mainly for debugging
|
||||
public string label;
|
||||
|
||||
public Sampler sampler;
|
||||
|
||||
/// <summary>
|
||||
/// If the sampling position falls outside the camera frustum, or is not above a water surface, it will hit a void. A default value of -1000 is then used.
|
||||
/// Use this option to specify if the (invalid) value should be kept or not. If not, the value will represent that of the last successful hit.
|
||||
/// </summary>
|
||||
public bool invalidateMisses = true;
|
||||
|
||||
//The indices this request occupies in the array
|
||||
|
||||
//TODO: Allow a request to span over multiple queries. This will work around the limit of 128 sample points.
|
||||
//A request would then reference multiple sets of indices, one per query
|
||||
public readonly List<int> indices = new List<int>();
|
||||
|
||||
public int SampleCount => indices.Count;
|
||||
|
||||
public AsyncRequest(int hashCode, Sampler sampler, string label = "")
|
||||
{
|
||||
this.hashCode = hashCode;
|
||||
this.sampler = sampler;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public void Issue()
|
||||
{
|
||||
if (IsSupported() == false)
|
||||
{
|
||||
throw new System.ComponentModel.WarningException(HeightQuerySystem.UNSUPPORTED_MESSAGE);
|
||||
}
|
||||
|
||||
AddRequest(this);
|
||||
}
|
||||
|
||||
public void Withdraw()
|
||||
{
|
||||
WithdrawRequest(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Withdraw();
|
||||
|
||||
sampler.Dispose();
|
||||
}
|
||||
|
||||
internal void InvokeCallback()
|
||||
{
|
||||
onCompleted?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fef246c1fe84870b35f921448c136e3
|
||||
timeCreated: 1718111030
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 287769
|
||||
packageName: Stylized Water 3
|
||||
packageVersion: 3.2.6
|
||||
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Request.cs
|
||||
uploadId: 895866
|
||||
@@ -0,0 +1,104 @@
|
||||
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
||||
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
||||
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
||||
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
||||
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace StylizedWater3
|
||||
{
|
||||
public static partial class HeightQuerySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds a list of sampling positions, and the returned water height values relative to them
|
||||
/// </summary>
|
||||
public class Sampler
|
||||
{
|
||||
/// <summary>
|
||||
/// Input sample positions in world-space
|
||||
/// </summary>
|
||||
public NativeArray<float3> positions;
|
||||
/// <summary>
|
||||
/// Output height values at each sampling <see cref="positions"/>
|
||||
/// </summary>
|
||||
public NativeArray<float> heightValues;
|
||||
|
||||
private int currentSampleCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the sampler has been initialized
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsCreated()
|
||||
{
|
||||
return currentSampleCount > 0;
|
||||
}
|
||||
|
||||
public int SampleCount => currentSampleCount;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the sampler with a number of sampling points. This allocates the memory required.
|
||||
/// </summary>
|
||||
/// <param name="sampleCount">Number of positions to sample at. For best performance, this number should be conservative!</param>
|
||||
/// <param name="cpu">Specify if this sampler is used with the CPU-height query method. If so, no limit is imposed on the sample count</param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void SetSampleCount(int sampleCount, bool cpu = false)
|
||||
{
|
||||
if (cpu == false && sampleCount > Query.MAX_SIZE)
|
||||
{
|
||||
Dispose();
|
||||
|
||||
throw new Exception($"The number of sample positions ({sampleCount}) exceeds the maximum capacity ({Query.MAX_SIZE}) of a single sampler." +
|
||||
$" Decrease the number of input positions, or issue multiple smaller requests");
|
||||
}
|
||||
|
||||
//Changed
|
||||
if (currentSampleCount != sampleCount)
|
||||
{
|
||||
#if SWS_DEV
|
||||
if(currentSampleCount > 0) Debug.Log($"Sampler count changed from {currentSampleCount} to {sampleCount}");
|
||||
#endif
|
||||
|
||||
if (positions.IsCreated) positions.Dispose();
|
||||
|
||||
//Input data
|
||||
positions = new NativeArray<float3>(sampleCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
if (heightValues.IsCreated) heightValues.Dispose();
|
||||
|
||||
//Output data
|
||||
heightValues = new NativeArray<float>(sampleCount, Allocator.Persistent);
|
||||
}
|
||||
currentSampleCount = sampleCount;
|
||||
}
|
||||
|
||||
[Obsolete("Use SetSampleCount() instead. Method was renamed for clarity")]
|
||||
public void Initialize(int sampleCount, bool cpu = false)
|
||||
{
|
||||
SetSampleCount(sampleCount, cpu);
|
||||
}
|
||||
|
||||
public void SetSamplePosition(int index, float3 value)
|
||||
{
|
||||
if (index > currentSampleCount)
|
||||
{
|
||||
throw new Exception($"Index out of range. This sampler was initialized with {currentSampleCount} number of samples. Dispose() and SetSampleCount() the sampler to increase the number of samples!");
|
||||
}
|
||||
|
||||
positions[index] = value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (positions.IsCreated) positions.Dispose();
|
||||
if (heightValues.IsCreated) heightValues.Dispose();
|
||||
|
||||
currentSampleCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5b78379f8de42328f0f5b50b094f050
|
||||
timeCreated: 1728760960
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 287769
|
||||
packageName: Stylized Water 3
|
||||
packageVersion: 3.2.6
|
||||
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.Sampler.cs
|
||||
uploadId: 895866
|
||||
@@ -0,0 +1,275 @@
|
||||
// Stylized Water 3 by Staggart Creations (http://staggart.xyz)
|
||||
// COPYRIGHT PROTECTED UNDER THE UNITY ASSET STORE EULA (https://unity.com/legal/as-terms)
|
||||
// • Copying or referencing source code for the production of new asset store, or public, content is strictly prohibited!
|
||||
// • Uploading this file to a public repository will subject it to an automated DMCA takedown request.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace StylizedWater3
|
||||
{
|
||||
public static partial class HeightQuerySystem
|
||||
{
|
||||
private const string PROFILER_PREFIX = "[GPU] Water Height Query:";
|
||||
|
||||
public static bool DISABLE_IN_EDIT_MODE
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
get { return UnityEditor.EditorPrefs.GetBool("SW3_HeightQuerySystem_EditMode", false); }
|
||||
set { UnityEditor.EditorPrefs.SetBool("SW3_HeightQuerySystem_EditMode", value); }
|
||||
#else
|
||||
get { return false; }
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reports if the current device/platform supports Compute Shaders
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsSupported()
|
||||
{
|
||||
#if UNITY_WEBGL && !UNITY_6000_1_OR_NEWER
|
||||
return false;
|
||||
#else
|
||||
return SystemInfo.supportsComputeShaders;
|
||||
#endif
|
||||
}
|
||||
internal const string UNSUPPORTED_MESSAGE = "[Stylized Water 3] Compute shaders are reportedly not supported on this platform. The GPU Height readback technique relies on this, so is not supported either. " +
|
||||
"If you are using any \"Align To Water\" components, or custom buoyancy physics using the API, switch all of them to the \"CPU\" method.";
|
||||
|
||||
/// <summary>
|
||||
/// Verifies if the returned height value is valid. If not, the sampling position fell outside the camera frustum or was not above any water surface
|
||||
/// If false, do not incorporate this value in any processing!
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static bool EqualsVoid(float value)
|
||||
{
|
||||
return value <= HeightPrePass.VOID_THRESHOLD;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given 4 height values (each representing the points of a +sign) a normal vector can be derived
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="down"></param>
|
||||
/// <param name="up"></param>
|
||||
/// <param name="strength"></param>
|
||||
/// <returns></returns>
|
||||
public static Vector3 DeriveNormal(float left, float right, float down, float up, float strength = 1f)
|
||||
{
|
||||
float xDelta = (left - right) * strength;
|
||||
float zDelta = (down - up) * strength;
|
||||
|
||||
return Vector3.Normalize(new Vector3(xDelta, 1.0f, zDelta));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A generic front-end to determine the method used to sampling the water height
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class Interface
|
||||
{
|
||||
public enum Method
|
||||
{
|
||||
[InspectorName("GPU (Async height readback)")]
|
||||
GPU,
|
||||
[InspectorName("CPU (Wave pattern replication)")]
|
||||
CPU
|
||||
}
|
||||
[Tooltip("Two completely different methods can be used to reproduce the wave height." +
|
||||
"\n\n" +
|
||||
"[GPU] Requires the \"Height Pre-pass\" feature to be enabled on the render feature. This queues up height samples and processes them in a compute shader, the result will be read back from the GPU asynchronously. " +
|
||||
"\n\n" +
|
||||
"Height values will represent the water surface height as it literally appears in the world, including any and all displacement effects. Slowest method, but ultimately more flexible." +
|
||||
"\n\n" +
|
||||
"[CPU] Given a water level, and water material, the same wave pattern can be 1:1 replicated through script. Does not include displacement effects and supports flat water geometry only. Fastest method")]
|
||||
public Method method = Method.GPU;
|
||||
|
||||
[Tooltip("This reference is required to grab the wave distance and height values")]
|
||||
public WaterObject waterObject;
|
||||
[Tooltip("Try to find the Water Object below or above the Transform's position. This is slower than assigning a specific Water Object directly!")]
|
||||
public bool autoFind = true;
|
||||
public WaveProfile waveProfile;
|
||||
|
||||
public enum WaterLevelSource
|
||||
{
|
||||
FixedValue,
|
||||
[InspectorName("Water Object Y-position")]
|
||||
WaterObject,
|
||||
Transform,
|
||||
Ocean
|
||||
}
|
||||
[Tooltip("Configure what should be used to set the base water level. Relative wave height is added to this value")]
|
||||
public WaterLevelSource waterLevelSource = WaterLevelSource.WaterObject;
|
||||
[Tooltip("This transform's Y-position is used as the base water level, this value is important and required for correct rendering. As such, underwater rendering does not work with rivers or other non-flat water")]
|
||||
public Transform waterLevelTransform;
|
||||
public float waterLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Based on the current configuration, retrieve the water level height value
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public float GetWaterLevel()
|
||||
{
|
||||
if (waterLevelSource == WaterLevelSource.WaterObject && waterObject) return waterObject.transform.position.y;
|
||||
if (waterLevelSource == WaterLevelSource.Transform && waterLevelTransform) return waterLevelTransform.position.y;
|
||||
if (waterLevelSource == WaterLevelSource.Ocean && OceanFollowBehaviour.Instance)
|
||||
{
|
||||
//Store it, so that it is always valid even when the singleton hasn't loaded yet
|
||||
waterLevel = OceanFollowBehaviour.Instance.transform.position.y;
|
||||
return waterLevel;
|
||||
}
|
||||
|
||||
return waterLevel;
|
||||
}
|
||||
|
||||
public bool IsRiverMaterial()
|
||||
{
|
||||
return waterObject.material.IsKeywordEnabled(ShaderParams.Keywords.River);
|
||||
}
|
||||
|
||||
public WaterObject GetWaterObject(Vector3 worldPosition)
|
||||
{
|
||||
if (autoFind) waterObject = WaterObject.Find(worldPosition, false);
|
||||
|
||||
return waterObject;
|
||||
}
|
||||
|
||||
public bool HasMissingReferences()
|
||||
{
|
||||
return (waterObject && waterObject.material && waveProfile) == false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of queries being submitted the next frame
|
||||
/// </summary>
|
||||
public static readonly List<Query> queries = new List<Query>();
|
||||
|
||||
public static int QueryCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If there are any height queries present, the displacement pre-pass must execute to ensure data is being returned
|
||||
/// </summary>
|
||||
public static bool RequiresHeightPrepass => QueryCount > 0;
|
||||
|
||||
private static void AddRequest(AsyncRequest request)
|
||||
{
|
||||
//Find the next available query with enough space for the positions
|
||||
//-If not, create a new query
|
||||
|
||||
Profiler.BeginSample($"{PROFILER_PREFIX} Add Request");
|
||||
|
||||
foreach (Query q in queries)
|
||||
{
|
||||
if (q.requests.ContainsKey(request.hashCode))
|
||||
{
|
||||
throw new Exception($"A request with the ID {request.hashCode} has already been issued. Use the \"onReadbackCompleted\" callback to receive the data." +
|
||||
$"Use the DisposeRequest function only when you are sure you no longer need the data.");
|
||||
}
|
||||
}
|
||||
|
||||
int queryIndex = queries.Count;
|
||||
int sampleCount = request.sampler.SampleCount;
|
||||
|
||||
if (sampleCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void CreateNewQuery()
|
||||
{
|
||||
queries.Add(new Query());
|
||||
QueryCount++;
|
||||
queryIndex++;
|
||||
}
|
||||
|
||||
//Initial query needs to be created
|
||||
if (queryIndex == 0)
|
||||
{
|
||||
CreateNewQuery();
|
||||
}
|
||||
|
||||
int occupiedIndices = HeightQuerySystem.Query.MAX_SIZE - queries[queryIndex - 1].availableIndices.Count;
|
||||
//Not enough space in the latest query for this many samples
|
||||
if (occupiedIndices + sampleCount >= HeightQuerySystem.Query.MAX_SIZE)
|
||||
{
|
||||
CreateNewQuery();
|
||||
|
||||
//Debug.Log($"Query #{queryIndex-1} contains {occupiedIndices}, requires {occupiedIndices + sampleCount}. Created a new query (#{queryIndex}).");
|
||||
}
|
||||
|
||||
var query = queries[queryIndex-1];
|
||||
|
||||
//Assign the indices available in the query to the request
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
request.indices.Add(query.GetNextAvailableIndex());
|
||||
}
|
||||
|
||||
query.sampleCount += sampleCount;
|
||||
query.requests.Add(request.hashCode, request);
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
private static void WithdrawRequest(AsyncRequest request)
|
||||
{
|
||||
Profiler.BeginSample($"{PROFILER_PREFIX} Withdraw Request");
|
||||
|
||||
for (int i = 0; i < queries.Count; i++)
|
||||
{
|
||||
var query = queries[i];
|
||||
|
||||
if (query.requests.TryGetValue(request.hashCode, out _))
|
||||
{
|
||||
//Remove request from query
|
||||
query.requests.Remove(request.hashCode);
|
||||
|
||||
int indexCount = request.indices.Count;
|
||||
|
||||
//Return the occupied indices to the pool
|
||||
for (int j = 0; j < indexCount; j++)
|
||||
{
|
||||
query.ReleaseIndex(request.indices[j]);
|
||||
}
|
||||
|
||||
//Update the current sample count
|
||||
query.sampleCount -= indexCount;
|
||||
|
||||
//Clear the list of occupied indices, these will be repopulate should the request be issued again
|
||||
request.indices.Clear();
|
||||
|
||||
//If the query is now completely empty, yeet it
|
||||
if (query.requests.Count == 0)
|
||||
{
|
||||
//Debug.Log($"Query #{i} is now empty and was disposed");
|
||||
|
||||
query.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all queries from the system
|
||||
/// </summary>
|
||||
//Need to force a clean start when entering/exiting play mode. Otherwise certain arrays will get de-allocated.
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
||||
public static void Clear()
|
||||
{
|
||||
for (int i = 0; i < queries.Count; i++)
|
||||
{
|
||||
queries[i].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a62da4d17a748dc840316692da46106
|
||||
timeCreated: 1717752156
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 287769
|
||||
packageName: Stylized Water 3
|
||||
packageVersion: 3.2.6
|
||||
assetPath: Assets/Stylized Water 3/Runtime/HeightQueries/HeightQuerySystem.cs
|
||||
uploadId: 895866
|
||||
Reference in New Issue
Block a user