Skip to content

Instantly share code, notes, and snippets.

@NicolasCaous
Created May 6, 2024 01:37
Show Gist options
  • Save NicolasCaous/ff2e429bf7ee8012c32607f4e8a8b126 to your computer and use it in GitHub Desktop.
Save NicolasCaous/ff2e429bf7ee8012c32607f4e8a8b126 to your computer and use it in GitHub Desktop.
#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
#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;
float distanceFactor;
float bias;
};
struct LocalUniforms
{
uint selectedTrianglesCount;
float3 position;
float3 scale;
};
RWStructuredBuffer<Uniforms> _Uniforms;
RWStructuredBuffer<LocalUniforms> _LocalUniforms;
RWStructuredBuffer<float3> _Positions;
RWStructuredBuffer<int3> _Triangles;
RWStructuredBuffer<int> _ParentTriangles;
RWStructuredBuffer<int3> _SelectedTriangles;
RWStructuredBuffer<IndirectDrawIndexedArgs> _IndirectDrawIndexedArgs;
StructuredBuffer<Uniforms> _UniformsReadOnly;
StructuredBuffer<LocalUniforms> _LocalUniformsReadOnly;
StructuredBuffer<float3> _PositionsReadOnly;
StructuredBuffer<int3> _TrianglesReadOnly;
StructuredBuffer<int> _ParentTrianglesReadOnly;
RWStructuredBuffer<uint> _SelectionBuffer;
RWStructuredBuffer<int3> _SelectionTriangles;
StructuredBuffer<uint> _SelectionBufferReadOnly;
sampler2D _Terrain;
[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;
}
if (current_subdivision < subdivisions)
{
const uint childOffset = offset + (1 << (current_subdivision * 2 + 1));
const uint childFirstRowOffset = childOffset + ((j * 2) * (sections * 2) + (i * 2)) * 2;
const uint childSecondRowOffset = childOffset + ((j * 2 + 1) * (sections * 2) + (i * 2)) * 2;
_ParentTriangles[childFirstRowOffset] = bottom_triangle_index;
_ParentTriangles[childFirstRowOffset + 1] = top_triangle_index;
_ParentTriangles[childFirstRowOffset + 2] = bottom_triangle_index;
_ParentTriangles[childFirstRowOffset + 3] = top_triangle_index;
_ParentTriangles[childSecondRowOffset] = bottom_triangle_index;
_ParentTriangles[childSecondRowOffset + 1] = top_triangle_index;
_ParentTriangles[childSecondRowOffset + 2] = bottom_triangle_index;
_ParentTriangles[childSecondRowOffset + 3] = top_triangle_index;
}
if (current_subdivision == 0)
{
_ParentTriangles[0] = -1;
_ParentTriangles[1] = -1;
}
}
}
}
uint ReadBit(StructuredBuffer<uint> buffer, uint id)
{
return buffer[id];
}
uint ReadBit(RWStructuredBuffer<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 p1 = _PositionsReadOnly[_TrianglesReadOnly[id.x].x];
float3 p2 = _PositionsReadOnly[_TrianglesReadOnly[id.x].y];
float3 p3 = _PositionsReadOnly[_TrianglesReadOnly[id.x].z];
p1 += float3(0.0, tex2Dlod(_Terrain, float4(p1.xz, 0.0, 0.0)).x, 0.0);
p2 += float3(0.0, tex2Dlod(_Terrain, float4(p2.xz, 0.0, 0.0)).x, 0.0);
p3 += float3(0.0, tex2Dlod(_Terrain, float4(p3.xz, 0.0, 0.0)).x, 0.0);
const float3 firstVertex = p1 * _LocalUniformsReadOnly[0].scale + _LocalUniformsReadOnly[0].position - _UniformsReadOnly[0].cameraPosition;
const float3 secondVertex = p2 * _LocalUniformsReadOnly[0].scale + _LocalUniformsReadOnly[0].position - _UniformsReadOnly[0].cameraPosition;
const float3 thirdVertex = p3 * _LocalUniformsReadOnly[0].scale + _LocalUniformsReadOnly[0].position - _UniformsReadOnly[0].cameraPosition;
const float firstSquared = dot(firstVertex, firstVertex);
const float secondSquared = dot(secondVertex, secondVertex);
const float thirdSquared = dot(thirdVertex, thirdVertex);
const float3 projectedSecond = secondVertex * sqrt(firstSquared / secondSquared);
const float3 projectedThird = thirdVertex * sqrt(firstSquared / thirdSquared);
const float3 doubleAreaOfTriangleVector = cross(projectedSecond - firstVertex, projectedThird - firstVertex);
const float doubleAreaOfTriangleSquared = dot(doubleAreaOfTriangleVector, doubleAreaOfTriangleVector);
const float3 normal = cross(secondVertex - firstVertex, thirdVertex - firstVertex);
if (doubleAreaOfTriangleSquared * _UniformsReadOnly[0].bias - firstSquared / _UniformsReadOnly[0].distanceFactor > _UniformsReadOnly[0].threshold
&& dot(normal, firstVertex) < 0)
{
WriteBit(_SelectionBuffer, id.x, 1);
}
else
{
WriteBit(_SelectionBuffer, id.x, 0);
}
}
// second step -> for every child triangle from the triangle that is going to be rendered, don't render parents
[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(_SelectionBuffer, id.x) == 0) return;
uint parentId = _ParentTrianglesReadOnly[id.x];
while (parentId != -1)
{
WriteBit(_SelectionBuffer, parentId, 0);
parentId = _ParentTrianglesReadOnly[parentId];
}
}
// 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;
}
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) * 6;
public uint subdivisions;
public uint currentSubdivision;
public uint trianglesCount;
public uint vertexCount;
public Vector3 cameraPosition;
public float threshold;
public float distanceFactor;
public float bias;
}
[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(-500f, 100f)]
public float threshold = -20f;
[Range(0f, 10000f)]
public float distanceFactor = 1000f;
[Range(0f, 10000f)]
public float bias = 4000f;
GraphicsBuffer UniformsBuffer; // Global
GraphicsBuffer LocalUniformsBuffer; // Local
GraphicsBuffer TrianglesBuffer; // Global
GraphicsBuffer ParentTrianglesBuffer; // Global
GraphicsBuffer PositionsBuffer; // Global
GraphicsBuffer SelectionBuffer; // Local
GraphicsBuffer SelectionTrianglesBuffer; // Local
GraphicsBuffer IndirectDrawIndexedArgsBuffer; // Local
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;
uniforms[0].distanceFactor = distanceFactor;
uniforms[0].bias = bias;
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));
ParentTrianglesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int) uniforms[0].trianglesCount, sizeof(int));
PositionsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int) uniforms[0].vertexCount, 3 * sizeof(float));
SelectionBuffer = 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, "_ParentTriangles", ParentTrianglesBuffer);
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, "_SelectionBuffer", SelectionBuffer);
cmd.SetComputeTextureParam(computeShader, kernelId, "_Terrain", terrain);
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, "_ParentTrianglesReadOnly", ParentTrianglesBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_SelectionBuffer", SelectionBuffer);
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", SelectionBuffer);
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;
uniforms[0].distanceFactor = distanceFactor;
uniforms[0].bias = bias;
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;
ParentTrianglesBuffer?.Dispose();
ParentTrianglesBuffer = null;
PositionsBuffer?.Dispose();
PositionsBuffer = null;
SelectionBuffer?.Dispose();
SelectionBuffer = 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