Created
May 5, 2024 15:44
-
-
Save NicolasCaous/5b7621b5f65a6e71b63b4bf6c07ea870 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SHADERGRAPHHAX | |
#define SHADERGRAPHHAX | |
StructuredBuffer<float3> _Positions; | |
float3 _Scale; | |
float3 _PositionOffset; | |
sampler2D _Terrain; | |
void DoHax_float(float vertex_id, out float3 position) | |
{ | |
position = _Positions[round(vertex_id)]; | |
position.y = tex2Dlod(_Terrain, float4(position.xz, 0, 0)).x; | |
position *= _Scale; | |
position += _PositionOffset; | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "UnityIndirect.cginc" | |
#pragma kernel ComputeSizes | |
#pragma kernel ComputeVertices | |
#pragma kernel ComputeTriangles | |
#pragma kernel FirstStep | |
#pragma kernel SecondStep | |
#pragma kernel ThirdStep | |
#pragma kernel ForthStep | |
struct Uniforms | |
{ | |
uint subdivisions; | |
uint currentSubdivision; | |
uint trianglesCount; | |
uint vertexCount; | |
float3 cameraPosition; | |
float threshold; | |
}; | |
struct LocalUniforms | |
{ | |
uint selectedTrianglesCount; | |
float3 position; | |
float3 scale; | |
}; | |
RWStructuredBuffer<Uniforms> _Uniforms; | |
RWStructuredBuffer<LocalUniforms> _LocalUniforms; | |
RWStructuredBuffer<float3> _Positions; | |
RWStructuredBuffer<int3> _Triangles; | |
RWStructuredBuffer<int3> _SelectedTriangles; | |
RWStructuredBuffer<IndirectDrawIndexedArgs> _IndirectDrawIndexedArgs; | |
StructuredBuffer<Uniforms> _UniformsReadOnly; | |
StructuredBuffer<LocalUniforms> _LocalUniformsReadOnly; | |
StructuredBuffer<float3> _PositionsReadOnly; | |
StructuredBuffer<int3> _TrianglesReadOnly; | |
RWStructuredBuffer<uint> _SelectionBufferA; | |
RWStructuredBuffer<uint> _SelectionBufferB; | |
RWStructuredBuffer<int3> _SelectionTriangles; | |
StructuredBuffer<uint> _SelectionBufferReadOnly; | |
[numthreads(1,1,1)] | |
void ComputeSizes (uint3 id : SV_DispatchThreadID) | |
{ | |
const uint subdivisions = _Uniforms[0].subdivisions; | |
uint side = (1 << (subdivisions + 1)) - (1 << subdivisions) + 1; | |
_Uniforms[0].vertexCount = side * side; | |
_Uniforms[0].trianglesCount = 2; | |
for (uint i = 1; i <= subdivisions; ++i) | |
{ | |
_Uniforms[0].trianglesCount += 1 << (i * 2 + 1); | |
} | |
} | |
[numthreads(1,1,1)] | |
void ComputeVertices (uint3 id : SV_DispatchThreadID) | |
{ | |
const uint subdivisions = _UniformsReadOnly[0].subdivisions; | |
const uint side = (1 << subdivisions) + 1; | |
double gap = 1.0L / (side - 1); | |
for (uint i = 0; i < side; ++i) | |
{ | |
for (uint j = 0; j < side; ++j) | |
{ | |
_Positions[i * side + j].x = (float)(gap * j); | |
_Positions[i * side + j].y = 0; | |
_Positions[i * side + j].z = (float)(gap * i); | |
} | |
} | |
} | |
[numthreads(1,1,1)] | |
void ComputeTriangles (uint3 id : SV_DispatchThreadID) | |
{ | |
const uint current_subdivision = _UniformsReadOnly[0].currentSubdivision; | |
const uint subdivisions = _UniformsReadOnly[0].subdivisions; | |
const uint side = (1 << subdivisions) + 1; | |
uint offset = 0; | |
for(uint expo = 0; expo < current_subdivision; ++expo) | |
{ | |
offset += 1 << (expo * 2 + 1); | |
} | |
const uint index_gap = (side - 1) / (1 << current_subdivision); | |
const uint sections = (side - 1) / index_gap; | |
for (uint i = 0; i < sections; ++i) | |
{ | |
for (uint j = 0; j < sections; ++j) | |
{ | |
const uint bottom_triangle_index = offset + (j * sections + i) * 2; | |
const uint top_triangle_index = bottom_triangle_index + 1; | |
{ | |
uint x = j * index_gap; | |
uint y = i * index_gap; | |
_Triangles[bottom_triangle_index].x = y * side + x; | |
x += index_gap; | |
y += index_gap; | |
_Triangles[bottom_triangle_index].y = y * side + x; | |
y -= index_gap; | |
_Triangles[bottom_triangle_index].z = y * side + x; | |
} | |
{ | |
uint x = j * index_gap; | |
uint y = i * index_gap; | |
_Triangles[top_triangle_index].x = y * side + x; | |
y += index_gap; | |
_Triangles[top_triangle_index].y = y * side + x; | |
x += index_gap; | |
_Triangles[top_triangle_index].z = y * side + x; | |
} | |
} | |
} | |
} | |
uint ReadBit(StructuredBuffer<uint> buffer, uint id) | |
{ | |
return buffer[id]; | |
} | |
void WriteBit(RWStructuredBuffer<uint> buffer, uint id, uint value) | |
{ | |
buffer[id] = value; | |
} | |
// first step -> detect every triangle that is bigger than treshhold (+ apply GPU culling) | |
[numthreads(1024, 1, 1)] | |
void FirstStep (uint3 id : SV_DispatchThreadID) | |
{ | |
if (id.x >= _UniformsReadOnly[0].trianglesCount) return; | |
float3 firstVertex = _PositionsReadOnly[_TrianglesReadOnly[id.x].x] * _LocalUniformsReadOnly[0].scale + _LocalUniformsReadOnly[0].position - _UniformsReadOnly[0].cameraPosition; | |
float3 secondVertex = _PositionsReadOnly[_TrianglesReadOnly[id.x].y] * _LocalUniformsReadOnly[0].scale + _LocalUniformsReadOnly[0].position - _UniformsReadOnly[0].cameraPosition; | |
float3 thirdVertex = _PositionsReadOnly[_TrianglesReadOnly[id.x].z] * _LocalUniformsReadOnly[0].scale + _LocalUniformsReadOnly[0].position - _UniformsReadOnly[0].cameraPosition; | |
float firstSquaredDistance = dot(firstVertex, firstVertex); | |
float secondSquaredDistance = dot(secondVertex, secondVertex); | |
float thirdSquaredDistance = dot(thirdVertex, thirdVertex); | |
if (firstSquaredDistance < _UniformsReadOnly[0].threshold | |
|| secondSquaredDistance < _UniformsReadOnly[0].threshold | |
|| thirdSquaredDistance < _UniformsReadOnly[0].threshold) | |
{ | |
WriteBit(_SelectionBufferA, id.x, 1); | |
WriteBit(_SelectionBufferB, id.x, 1); | |
} | |
else | |
{ | |
WriteBit(_SelectionBufferA, id.x, 0); | |
WriteBit(_SelectionBufferB, id.x, 0); | |
} | |
} | |
// second step -> for every child triangle from the triangle that is going to be rendered, don't render if children are going to be rendered | |
[numthreads(1024, 1, 1)] | |
void SecondStep (uint3 id : SV_DispatchThreadID) | |
{ | |
if (id.x >= _UniformsReadOnly[0].trianglesCount) return; | |
// skip triangle id.x if it is not selected | |
if (ReadBit(_SelectionBufferReadOnly, id.x) == 0) return; | |
uint offset = 0; | |
uint lastOffset; | |
uint i = 0; | |
do | |
{ | |
lastOffset = 1 << (i * 2 + 1); | |
offset += lastOffset; | |
i += 1; | |
} while(offset <= id.x); | |
// Last subdivision has no children | |
if (offset >= _UniformsReadOnly[0].trianglesCount) return; | |
i -= 1; | |
offset -= lastOffset; | |
uint subdivisionOffset = id.x - offset; | |
bool isTopTriangle = (subdivisionOffset % 2) == 1; | |
uint side = (lastOffset / 2) >> i; | |
uint parentRow = (subdivisionOffset / 2) / side; | |
uint parentColumn = (subdivisionOffset / 2) % side; | |
offset += lastOffset; | |
side *= 2; | |
uint bottomChildrenRow = parentRow * 2; | |
uint bottomChildrenColumn = parentColumn * 2; | |
uint childrenRendering = 0; | |
if (isTopTriangle) | |
{ | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + bottomChildrenRow * 2 * side + bottomChildrenColumn * 2 + 1); | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + (bottomChildrenRow * 2 + 1) * side + bottomChildrenColumn * 2); | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + (bottomChildrenRow * 2 + 1) * side + bottomChildrenColumn * 2 + 1); | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + (bottomChildrenRow * 2 + 1) * side + bottomChildrenColumn * 2 + 3); | |
} | |
else | |
{ | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + bottomChildrenRow * 2 * side + bottomChildrenColumn * 2); | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + bottomChildrenRow * 2 * side + bottomChildrenColumn * 2 + 2); | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + bottomChildrenRow * 2 * side + bottomChildrenColumn * 2 + 3); | |
childrenRendering |= ReadBit(_SelectionBufferReadOnly, offset + (bottomChildrenRow * 2 + 1) * side + bottomChildrenColumn * 2 + 2); | |
} | |
if (childrenRendering != 0) | |
{ | |
WriteBit(_SelectionBufferB, id.x, 0); | |
} | |
} | |
// third step -> for every triangle, copy to buffer to be rendered using atomic add | |
[numthreads(1024, 1, 1)] | |
void ThirdStep (uint3 id : SV_DispatchThreadID) | |
{ | |
if (id.x >= _UniformsReadOnly[0].trianglesCount) return; | |
// skip triangle id.x if it is not selected | |
if (ReadBit(_SelectionBufferReadOnly, id.x) == 0) return; | |
uint triangleId; | |
InterlockedAdd(_LocalUniforms[0].selectedTrianglesCount, 1, triangleId); | |
_SelectionTriangles[triangleId] = _TrianglesReadOnly[id.x]; | |
} | |
[numthreads(1,1,1)] | |
void ForthStep (uint3 id : SV_DispatchThreadID) | |
{ | |
_IndirectDrawIndexedArgs[0].indexCountPerInstance = _LocalUniformsReadOnly[0].selectedTrianglesCount * 3; | |
_IndirectDrawIndexedArgs[0].instanceCount = 1; | |
_IndirectDrawIndexedArgs[0].startIndex = 0; | |
_IndirectDrawIndexedArgs[0].baseVertexIndex = 0; | |
_IndirectDrawIndexedArgs[0].startInstance = 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.InteropServices; | |
using UnityEngine; | |
using UnityEngine.Rendering; | |
public class TerrainRenderer : MonoBehaviour | |
{ | |
[StructLayout(LayoutKind.Sequential)] | |
public struct Uniforms | |
{ | |
public const int size = sizeof(uint) * 4 + sizeof(float) * 4; | |
public uint subdivisions; | |
public uint currentSubdivision; | |
public uint trianglesCount; | |
public uint vertexCount; | |
public Vector3 cameraPosition; | |
public float threshold; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
struct LocalUniforms | |
{ | |
public const int size = sizeof(uint) + sizeof(float) * 3 * 2; | |
public uint selectedTrianglesCount; | |
public Vector3 position; | |
public Vector3 scale; | |
}; | |
public Material material; | |
public ComputeShader computeShader; | |
public Texture terrain; | |
public Transform cameraTransform; | |
[Range(0, 12)] | |
public int subdivisions = 4; | |
[Range(0f, 1000000f)] | |
public float threshold = 10000f; | |
GraphicsBuffer UniformsBuffer; | |
GraphicsBuffer LocalUniformsBuffer; | |
GraphicsBuffer TrianglesBuffer; | |
GraphicsBuffer PositionsBuffer; | |
GraphicsBuffer SelectionBufferA; | |
GraphicsBuffer SelectionBufferB; | |
GraphicsBuffer SelectionTrianglesBuffer; | |
GraphicsBuffer IndirectDrawIndexedArgsBuffer; | |
Uniforms[] uniforms; | |
LocalUniforms[] localUniforms; | |
CommandBuffer cmd; | |
RenderParams rp; | |
static readonly int PositionsID = Shader.PropertyToID("_Positions"); | |
static readonly int ScaleID = Shader.PropertyToID("_Scale"); | |
static readonly int PositionOffsetID = Shader.PropertyToID("_PositionOffset"); | |
static readonly int TerrainID = Shader.PropertyToID("_Terrain"); | |
void InitializeTerrain() | |
{ | |
UniformsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Uniforms.size); | |
uniforms = new Uniforms[1]; | |
uniforms[0].subdivisions = (uint) subdivisions; | |
uniforms[0].currentSubdivision = 0; | |
uniforms[0].trianglesCount = 0; | |
uniforms[0].vertexCount = 0; | |
uniforms[0].cameraPosition = Vector3.zero; | |
uniforms[0].threshold = threshold; | |
UniformsBuffer.SetData(uniforms); | |
LocalUniformsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, LocalUniforms.size); | |
localUniforms = new LocalUniforms[1]; | |
localUniforms[0].selectedTrianglesCount = 0; | |
LocalUniformsBuffer.SetData(localUniforms); | |
int kernelId = computeShader.FindKernel("ComputeSizes"); | |
computeShader.SetBuffer(kernelId, "_Uniforms", UniformsBuffer); | |
computeShader.Dispatch(kernelId, 1, 1, 1); | |
UniformsBuffer.GetData(uniforms); | |
TrianglesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int) uniforms[0].trianglesCount * 3, sizeof(int)); | |
PositionsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int) uniforms[0].vertexCount, 3 * sizeof(float)); | |
SelectionBufferA = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int) uniforms[0].trianglesCount, sizeof(uint)); | |
SelectionBufferB = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int) uniforms[0].trianglesCount, sizeof(uint)); | |
SelectionTrianglesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (1 << (subdivisions * 2 + 1)) * 3, sizeof(int)); | |
kernelId = computeShader.FindKernel("ComputeVertices"); | |
computeShader.SetBuffer(kernelId, "_UniformsReadOnly", UniformsBuffer); | |
computeShader.SetBuffer(kernelId, "_Triangles", TrianglesBuffer); | |
computeShader.SetBuffer(kernelId, "_Positions", PositionsBuffer); | |
computeShader.Dispatch(kernelId, 1, 1, 1); | |
kernelId = computeShader.FindKernel("ComputeTriangles"); | |
computeShader.SetBuffer(kernelId, "_UniformsReadOnly", UniformsBuffer); | |
computeShader.SetBuffer(kernelId, "_Triangles", TrianglesBuffer); | |
computeShader.SetBuffer(kernelId, "_Positions", PositionsBuffer); | |
for (int i = 0; i <= subdivisions; i++) | |
{ | |
uniforms[0].currentSubdivision = (uint) i; | |
UniformsBuffer.SetData(uniforms); | |
computeShader.Dispatch(kernelId, 1, 1, 1); | |
} | |
rp = new RenderParams(material); | |
rp.worldBounds = new Bounds(transform.position + transform.localScale * 0.5f, transform.localScale); | |
rp.matProps = new MaterialPropertyBlock(); | |
rp.matProps.SetBuffer(PositionsID, PositionsBuffer); | |
rp.matProps.SetVector(ScaleID, transform.localScale); | |
rp.matProps.SetVector(PositionOffsetID, transform.position); | |
rp.matProps.SetTexture(TerrainID, terrain); | |
IndirectDrawIndexedArgsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawIndexedArgs.size); | |
cmd = new CommandBuffer(); | |
kernelId = computeShader.FindKernel("FirstStep"); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_LocalUniformsReadOnly", LocalUniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_PositionsReadOnly", PositionsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_TrianglesReadOnly", TrianglesBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionBufferA", SelectionBufferA); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionBufferB", SelectionBufferB); | |
cmd.DispatchCompute(computeShader, kernelId, (int) uniforms[0].trianglesCount / 1024 + 1, 1, 1); | |
kernelId = computeShader.FindKernel("SecondStep"); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionBufferReadOnly", SelectionBufferA); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionBufferB", SelectionBufferB); | |
cmd.DispatchCompute(computeShader, kernelId, (int) uniforms[0].trianglesCount / 1024 + 1, 1, 1); | |
kernelId = computeShader.FindKernel("ThirdStep"); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_LocalUniforms", LocalUniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_TrianglesReadOnly", TrianglesBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionBufferReadOnly", SelectionBufferB); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionTriangles", SelectionTrianglesBuffer); | |
cmd.DispatchCompute(computeShader, kernelId, (int) uniforms[0].trianglesCount / 1024 + 1, 1, 1); | |
kernelId = computeShader.FindKernel("ForthStep"); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_LocalUniformsReadOnly", LocalUniformsBuffer); | |
cmd.SetComputeBufferParam(computeShader, kernelId, "_IndirectDrawIndexedArgs", IndirectDrawIndexedArgsBuffer); | |
cmd.DispatchCompute(computeShader, kernelId, 1, 1, 1); | |
} | |
void UpdateUniforms() | |
{ | |
uniforms[0].cameraPosition = cameraTransform.position; | |
uniforms[0].threshold = threshold; | |
UniformsBuffer.SetData(uniforms); | |
localUniforms[0].selectedTrianglesCount = 0; | |
localUniforms[0].position = transform.position; | |
localUniforms[0].scale = transform.localScale; | |
LocalUniformsBuffer.SetData(localUniforms); | |
rp.matProps.SetVector(ScaleID, transform.localScale); | |
rp.matProps.SetVector(PositionOffsetID, transform.position); | |
} | |
void UpdateTerrain() | |
{ | |
Graphics.ExecuteCommandBuffer(cmd); | |
Graphics.RenderPrimitivesIndexedIndirect(rp, MeshTopology.Triangles, SelectionTrianglesBuffer, IndirectDrawIndexedArgsBuffer, 1, 0); | |
} | |
void Start() | |
{ | |
InitializeTerrain(); | |
} | |
void OnDestroy() | |
{ | |
UniformsBuffer?.Dispose(); | |
UniformsBuffer = null; | |
LocalUniformsBuffer?.Dispose(); | |
LocalUniformsBuffer = null; | |
TrianglesBuffer?.Dispose(); | |
TrianglesBuffer = null; | |
PositionsBuffer?.Dispose(); | |
PositionsBuffer = null; | |
SelectionBufferA?.Dispose(); | |
SelectionBufferA = null; | |
SelectionBufferB?.Dispose(); | |
SelectionBufferB = null; | |
SelectionTrianglesBuffer?.Dispose(); | |
SelectionTrianglesBuffer = null; | |
IndirectDrawIndexedArgsBuffer?.Dispose(); | |
IndirectDrawIndexedArgsBuffer = null; | |
} | |
void Update() | |
{ | |
UpdateUniforms(); | |
UpdateTerrain(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment