Skip to content

Instantly share code, notes, and snippets.

@NicolasCaous
Created May 4, 2024 19:24
Show Gist options
  • Save NicolasCaous/fd202c767acc6f7f160be95cce40f291 to your computer and use it in GitHub Desktop.
Save NicolasCaous/fd202c767acc6f7f160be95cce40f291 to your computer and use it in GitHub Desktop.
#include "UnityIndirect.cginc"
#pragma kernel ComputeSizes
#pragma kernel ComputeVertices
#pragma kernel ComputeTriangles
#pragma kernel FirstStep
#pragma kernel SecondStep
#pragma kernel ThirdStep
struct Uniforms
{
uint subdivisions;
uint currentSubdivision;
uint trianglesCount;
uint vertexCount;
float3 cameraPosition;
};
RWStructuredBuffer<Uniforms> _Uniforms;
RWStructuredBuffer<float3> _Positions;
RWStructuredBuffer<int3> _Triangles;
StructuredBuffer<Uniforms> _UniformsReadOnly;
StructuredBuffer<float3> _PrecomputedPositions;
StructuredBuffer<int3> _PrecomputedTriangles;
RWStructuredBuffer<IndirectDrawArgs> _IndirectDrawCallArgs;
[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 = (float)(gap * i);
_Positions[i * side + j].z = 0;
}
}
}
[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;
}
}
}
}
// first step -> detect every triangle that is bigger than treshhold (+ apply GPU culling)
[numthreads(64,1,1)]
void FirstStep (uint3 id : SV_DispatchThreadID)
{
if (id.x == 128)
{
_IndirectDrawCallArgs[0].vertexCountPerInstance = _UniformsReadOnly[0].trianglesCount * 3;
_IndirectDrawCallArgs[0].instanceCount = 1;
_IndirectDrawCallArgs[0].startVertex = 0;
_IndirectDrawCallArgs[0].startInstance = 0;
}
}
// second step -> for every triangle that is going to be rendered, remove all parents that are going to be rendered
[numthreads(64,1,1)]
void SecondStep (uint3 id : SV_DispatchThreadID)
{
if (id.x == 128)
{
_IndirectDrawCallArgs[0].vertexCountPerInstance = _UniformsReadOnly[0].trianglesCount * 3;
_IndirectDrawCallArgs[0].instanceCount = 1;
_IndirectDrawCallArgs[0].startVertex = 0;
_IndirectDrawCallArgs[0].startInstance = 0;
}
}
// third step -> for every triangle, copy to buffer to be rendered using atomic add
[numthreads(64,1,1)]
void ThirdStep (uint3 id : SV_DispatchThreadID)
{
if (id.x == 128)
{
_IndirectDrawCallArgs[0].vertexCountPerInstance = _UniformsReadOnly[0].trianglesCount * 3;
_IndirectDrawCallArgs[0].instanceCount = 1;
_IndirectDrawCallArgs[0].startVertex = 0;
_IndirectDrawCallArgs[0].startInstance = 0;
}
}
#ifndef SHADERGRAPHHAX
#define SHADERGRAPHHAX
StructuredBuffer<int> _Triangles;
StructuredBuffer<float3> _Positions;
float3 _Scale;
float3 _PositionOffset;
sampler2D _Terrain;
void DoHax_float(float vertex_id, out float3 position)
{
position = _Positions[_Triangles[round(vertex_id)]];
position.z = position.y;
position.y = tex2Dlod(_Terrain, float4(position.xz, 0, 0)).x;
position *= _Scale;
position += _PositionOffset;
}
#endif
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) * 3;
public uint subdivisions;
public uint currentSubdivision;
public uint trianglesCount;
public uint vertexCount;
public Vector3 cameraPosition;
}
public Material material;
public ComputeShader computeShader;
public Texture terrain;
public Transform cameraTransform;
[Range(0, 12)]
public int subdivisions = 4;
GraphicsBuffer TrianglesBuffer;
GraphicsBuffer PositionsBuffer;
GraphicsBuffer UniformsBuffer;
GraphicsBuffer IndirectDrawCallArgsBuffer;
Uniforms[] uniforms;
CommandBuffer cmd;
RenderParams rp;
static readonly int PositionsID = Shader.PropertyToID("_Positions");
static readonly int TrianglesID = Shader.PropertyToID("_Triangles");
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;
UniformsBuffer.SetData(uniforms);
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));
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(TrianglesID, TrianglesBuffer);
rp.matProps.SetBuffer(PositionsID, PositionsBuffer);
rp.matProps.SetVector(ScaleID, transform.localScale);
rp.matProps.SetVector(PositionOffsetID, transform.position);
rp.matProps.SetTexture(TerrainID, terrain);
IndirectDrawCallArgsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawArgs.size);
cmd = new CommandBuffer();
kernelId = computeShader.FindKernel("FirstStep");
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_PrecomputedPositions", PositionsBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_PrecomputedTriangles", TrianglesBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_IndirectDrawCallArgs", IndirectDrawCallArgsBuffer);
cmd.DispatchCompute(computeShader, kernelId, 1, 1, 1);
kernelId = computeShader.FindKernel("SecondStep");
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_PrecomputedPositions", PositionsBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_PrecomputedTriangles", TrianglesBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_IndirectDrawCallArgs", IndirectDrawCallArgsBuffer);
cmd.DispatchCompute(computeShader, kernelId, 1, 1, 1);
kernelId = computeShader.FindKernel("ThirdStep");
cmd.SetComputeBufferParam(computeShader, kernelId, "_UniformsReadOnly", UniformsBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_PrecomputedPositions", PositionsBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_PrecomputedTriangles", TrianglesBuffer);
cmd.SetComputeBufferParam(computeShader, kernelId, "_IndirectDrawCallArgs", IndirectDrawCallArgsBuffer);
cmd.DispatchCompute(computeShader, kernelId, 1, 1, 1);
}
void UpdateUniforms()
{
uniforms[0].cameraPosition = cameraTransform.position;
UniformsBuffer.SetData(uniforms);
rp.matProps.SetVector(ScaleID, transform.localScale);
rp.matProps.SetVector(PositionOffsetID, transform.position);
}
void UpdateTerrain()
{
Graphics.ExecuteCommandBuffer(cmd);
Graphics.RenderPrimitivesIndirect(rp, MeshTopology.Triangles, IndirectDrawCallArgsBuffer, 1, 0);
}
void Start()
{
InitializeTerrain();
}
void OnDestroy()
{
TrianglesBuffer?.Dispose();
TrianglesBuffer = null;
PositionsBuffer?.Dispose();
PositionsBuffer = null;
UniformsBuffer?.Dispose();
UniformsBuffer = null;
IndirectDrawCallArgsBuffer?.Dispose();
IndirectDrawCallArgsBuffer = null;
}
void Update()
{
UpdateUniforms();
UpdateTerrain();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment