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

240 lines
12 KiB
C#

using System.Diagnostics;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace FluffyGroomingTool {
public class CullAndSortController {
private ComputeShader bitonicSortComputeShader;
private ComputeShader cullAndDistanceComputeShader;
private GraphicsBuffer indexBuffer;
private readonly int distanceAndVisibility;
private readonly int assembleIndicesKernel;
private readonly int frustumCullingAndLodOnlyKernel;
private readonly int assembleVisibleTrianglesIndices;
private int currentLodIndex = -1;
private readonly ComputeBuffer triangles;
private readonly ComputeBuffer visibleTriangles;
private readonly BitonicMergeSort bitonicSorter;
private readonly ComputeBuffer sortedKeys;
private GraphicsBuffer.IndirectDrawIndexedArgs[] renderMeshData;
public GraphicsBuffer RenderMeshArguments { get; }
private FurRendererSettings settings;
public CullAndSortController(int meshBufferStride, GraphicsBuffer indexBuffer, GraphicsBuffer furMeshBuffer, int trianglesCount,
FurRendererSettings settings) {
this.indexBuffer = indexBuffer;
RenderMeshArguments = createRenderMeshArguments();
this.settings = settings;
cullAndDistanceComputeShader = Object.Instantiate(Resources.Load<ComputeShader>("CullingComputeShader"));
bitonicSortComputeShader = Object.Instantiate(Resources.Load<ComputeShader>("BitonicSort"));
triangles = new ComputeBuffer(trianglesCount, sizeof(int) * 3 + sizeof(float) + sizeof(int));
visibleTriangles = new ComputeBuffer(trianglesCount, sizeof(int) * 3 + sizeof(float) + sizeof(int), ComputeBufferType.Append);
distanceAndVisibility = cullAndDistanceComputeShader.FindKernel("DistanceAndVisibility");
var initializeKernel = cullAndDistanceComputeShader.FindKernel("Initialize");
assembleIndicesKernel = cullAndDistanceComputeShader.FindKernel("AssembleIndices");
assembleVisibleTrianglesIndices = cullAndDistanceComputeShader.FindKernel("AssembleVisibleTrianglesIndices");
frustumCullingAndLodOnlyKernel = cullAndDistanceComputeShader.FindKernel("FrustomCullingAndLodOnly");
cullAndDistanceComputeShader.SetBuffer(initializeKernel, ShaderID.MESH_INDEX_BUFFER, indexBuffer);
cullAndDistanceComputeShader.SetBuffer(initializeKernel, ShaderID.TRIANGLES, triangles);
cullAndDistanceComputeShader.SetBuffer(assembleIndicesKernel, ShaderID.MESH_INDEX_BUFFER, indexBuffer);
cullAndDistanceComputeShader.SetBuffer(assembleIndicesKernel, ShaderID.TRIANGLES, triangles);
cullAndDistanceComputeShader.SetBuffer(assembleIndicesKernel, ShaderID.VISIBLE_TRIANGLES, visibleTriangles);
cullAndDistanceComputeShader.SetBuffer(assembleIndicesKernel, ShaderID.FUR_MESH_BUFFER, furMeshBuffer);
cullAndDistanceComputeShader.SetBuffer(assembleVisibleTrianglesIndices, ShaderID.MESH_INDEX_BUFFER, indexBuffer);
cullAndDistanceComputeShader.SetBuffer(assembleVisibleTrianglesIndices, ShaderID.VISIBLE_TRIANGLES_READ, visibleTriangles);
cullAndDistanceComputeShader.SetBuffer(distanceAndVisibility, ShaderID.FUR_MESH_BUFFER, furMeshBuffer);
cullAndDistanceComputeShader.SetBuffer(distanceAndVisibility, ShaderID.TRIANGLES, triangles);
cullAndDistanceComputeShader.SetBuffer(distanceAndVisibility, ShaderID.VISIBLE_TRIANGLES, visibleTriangles);
cullAndDistanceComputeShader.SetBuffer(frustumCullingAndLodOnlyKernel, ShaderID.TRIANGLES, triangles);
cullAndDistanceComputeShader.SetBuffer(frustumCullingAndLodOnlyKernel, ShaderID.FUR_MESH_BUFFER, furMeshBuffer);
cullAndDistanceComputeShader.SetBuffer(frustumCullingAndLodOnlyKernel, ShaderID.MESH_INDEX_BUFFER, indexBuffer);
cullAndDistanceComputeShader.SetInt(ShaderID.VERTEX_BUFFER_STRIDE, meshBufferStride);
cullAndDistanceComputeShader.SetFloat(ShaderID.IS_FLIPPED_Y, SystemInfo.graphicsUVStartsAtTop ? 1 : -1);
cullAndDistanceComputeShader.Dispatch(initializeKernel, trianglesCount.toCsGroups(), 1, 1);
var powerOf2SortIndices = nextPowerOf2(trianglesCount);
sortedKeys = new ComputeBuffer(powerOf2SortIndices, sizeof(uint));
bitonicSorter = new BitonicMergeSort(bitonicSortComputeShader);
bitonicSorter.inizialize(sortedKeys);
}
private GraphicsBuffer createRenderMeshArguments() {
var args = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawIndexedArgs.size);
renderMeshData = new GraphicsBuffer.IndirectDrawIndexedArgs[1];
renderMeshData[0].indexCountPerInstance = (uint) indexBuffer.count;
renderMeshData[0].instanceCount = 1;
args.SetData(renderMeshData);
return args;
}
public int getCurrentLodIndex() {
return Mathf.Max(currentLodIndex, 0);
}
public static int nextPowerOf2(int n) {
int count = 0;
if (n > 0 && (n & (n - 1)) == 0)
return n;
while (n != 0) {
n >>= 1;
count += 1;
}
return 1 << count;
}
public void update(Camera mainCamera, FurContainer furContainer, Vector3 objectPosition) {
IsCulled = false;
if (!settings.IsLodEnabled && currentLodIndex != 0) {
setLodIndex(0, furContainer);
}
if (mainCamera == null) {
Debug.Log("Fur LOD will not work unless you assign the lodCamera.");
return;
}
setupLod(mainCamera, furContainer, objectPosition);
Stopwatch sw = Stopwatch.StartNew();
sendCommonValuesToShader(mainCamera);
if (isFrustumAndLodOnly()) {
dispatchFrustumAndLodOnlyKernel();
}
else if (settings.isAlphaSortingEnabled) {
dispatchSortAndCullKernels();
}
sw.Stop();
}
private bool isFrustumAndLodOnly() {
return settings.IsFrustumCullingEnabled && settings.IsLodEnabled && !settings.isOcclusionCullingEnabled &&
!settings.isAlphaSortingEnabled;
}
private bool isLodOnly() {
return !settings.IsFrustumCullingEnabled && settings.IsLodEnabled && !settings.isOcclusionCullingEnabled &&
!settings.isAlphaSortingEnabled;
}
private void sendCommonValuesToShader(Camera mainCamera) {
cullAndDistanceComputeShader.SetMatrix(ShaderID.UNITY_MATRIX_MVP, getMVPMatrix(mainCamera));
cullAndDistanceComputeShader.SetVector(ShaderID.WORLD_SPACE_CAMERA_POSITION, mainCamera.transform.position);
cullAndDistanceComputeShader.SetInt(ShaderID.IS_FRUSTUM_CULLING_ENABLED, settings.isFrustumCullingEnabled ? 1 : 0);
cullAndDistanceComputeShader.SetFloat(ShaderID.CAMERA_FAR_CLIP_PLANE, mainCamera.farClipPlane);
}
private void dispatchSortAndCullKernels() {
visibleTriangles.SetCounterValue(0);
cullAndDistanceComputeShader.SetInt(ShaderID.TRIANGLES_COUNT, triangles.count);
cullAndDistanceComputeShader.Dispatch(distanceAndVisibility, triangles.count.toCsGroups(), 1, 1);
var numberOfVisibleTriangles = updateArgumentsBuffer();
cullAndDistanceComputeShader.SetInt(ShaderID.NUM_VISIBLE_TRIANGLES_COUNT, numberOfVisibleTriangles);
if (settings.isAlphaSortingEnabled && numberOfVisibleTriangles > 0) {
bitonicSorter.sort(sortedKeys, triangles);
cullAndDistanceComputeShader.SetBuffer(assembleIndicesKernel, ShaderID.KEYS, sortedKeys);
cullAndDistanceComputeShader.Dispatch(assembleIndicesKernel, numberOfVisibleTriangles.toCsGroups(), 1, 1);
}
else if (!settings.isAlphaSortingEnabled && numberOfVisibleTriangles > 0) {
cullAndDistanceComputeShader.Dispatch(assembleVisibleTrianglesIndices, numberOfVisibleTriangles.toCsGroups(), 1, 1);
}
}
public void dispatchFrustumAndLodOnlyKernel() {
renderMeshData[0].indexCountPerInstance = (uint) (triangles.count * 3);
RenderMeshArguments.SetData(renderMeshData);
cullAndDistanceComputeShader.SetInt(ShaderID.TRIANGLES_COUNT, triangles.count);
cullAndDistanceComputeShader.Dispatch(frustumCullingAndLodOnlyKernel, triangles.count.toCsGroups(), 1, 1);
}
private int updateArgumentsBuffer() {
GraphicsBuffer.CopyCount(visibleTriangles, RenderMeshArguments, 0);
RenderMeshArguments.GetData(renderMeshData);
var visibleTrianglesCount = renderMeshData[0].indexCountPerInstance;
renderMeshData[0].indexCountPerInstance = visibleTrianglesCount * 3;
RenderMeshArguments.SetData(renderMeshData);
return (int) visibleTrianglesCount;
}
public bool IsCulled { get; set; }
private void setupLod(Camera mainCamera, FurContainer furContainer, Vector3 objectPosition) {
if (settings.IsLodEnabled) {
var cameraPosition = mainCamera.transform.position;
var objectDistanceToCamera = Vector3.Distance(cameraPosition, objectPosition);
var lod1StartDistance = furContainer.furLods[1].startDistance;
var lod2StartDistance = furContainer.furLods[2].startDistance;
if (objectDistanceToCamera > furContainer.culledDistance) {
IsCulled = true;
}
else if (objectDistanceToCamera > lod2StartDistance && currentLodIndex != 2) {
setLodIndex(2, furContainer);
}
else if (objectDistanceToCamera < lod2StartDistance && objectDistanceToCamera > lod1StartDistance && currentLodIndex != 1) {
setLodIndex(1, furContainer);
}
else if (objectDistanceToCamera < lod1StartDistance && currentLodIndex != 0) {
setLodIndex(0, furContainer);
}
}
else {
cullAndDistanceComputeShader.SetInt(ShaderID.LOD_SKIP_STRANDS_COUNT, 0);
}
}
private Matrix4x4 getMVPMatrix(Camera mainCamera) {
var v = mainCamera.worldToCameraMatrix;
var p = mainCamera.projectionMatrix;
return p * v;
}
public void dispose() {
indexBuffer?.Release();
triangles?.Dispose();
visibleTriangles?.Dispose();
RenderMeshArguments?.Dispose();
sortedKeys?.Dispose();
}
public void setLodIndex(int index, FurContainer furContainer) {
if (currentLodIndex != index) {
currentLodIndex = index;
var currentLod = furContainer.furLods[index];
cullAndDistanceComputeShader.SetInt(ShaderID.LOD_SKIP_STRANDS_COUNT, currentLod.skipStrandsCount);
if (isLodOnly()) {
dispatchFrustumAndLodOnlyKernel();
}
}
}
public string getCurrentLodName() {
if (IsCulled) {
return "Culled";
}
return currentLodIndex switch {
0 => "LOD 1",
1 => "LOD 2",
_ => "LOD 3"
};
}
}
}