Skip to content

Instantly share code, notes, and snippets.

@NicolasCaous
Created May 3, 2024 16:46
Show Gist options
  • Save NicolasCaous/d6454b59a6b20b34639f62b8f394e295 to your computer and use it in GitHub Desktop.
Save NicolasCaous/d6454b59a6b20b34639f62b8f394e295 to your computer and use it in GitHub Desktop.
Triangle subdivision algorithm v2
#pragma kernel ComputeSizes
#pragma kernel ComputeVerticesAndIndices
struct Uniforms
{
uint subdivisions;
uint vertex_count;
uint index_count;
};
RWStructuredBuffer<Uniforms> uniforms;
//Texture2D<half> terrain;
RWStructuredBuffer<float2> vertices;
RWStructuredBuffer<uint3> indices;
[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].vertex_count = side * side;
uniforms[0].index_count = 2;
for (uint i = 1; i <= subdivisions; ++i)
{
uniforms[0].index_count += 1 << (i * 2 + 1);
}
}
[numthreads(1,1,1)]
void ComputeVerticesAndIndices (uint3 id : SV_DispatchThreadID)
{
const uint subdivisions = uniforms[0].subdivisions;
const uint side = (1 << (subdivisions + 1)) - (1 << subdivisions) + 1;
double gap = 1.0L / (side - 1);
for (uint i = 0; i < side; ++i)
{
for (uint j = 0; j < side; ++j)
{
vertices[i * side + j].x = (float)(gap * j);
vertices[i * side + j].y = (float)(gap * i);
}
}
uint subdivision_level = 0;
uint offset = 0;
for (uint index_gap = side - 1; index_gap > 0; index_gap /= 2)
{
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;
indices[bottom_triangle_index].x = y * side + x;
x += index_gap;
y += index_gap;
indices[bottom_triangle_index].y = y * side + x;
y -= index_gap;
indices[bottom_triangle_index].z = y * side + x;
}
{
uint x = j * index_gap;
uint y = i * index_gap;
indices[top_triangle_index].x = y * side + x;
y += index_gap;
indices[top_triangle_index].y = y * side + x;
x += index_gap;
indices[top_triangle_index].z = y * side + x;
}
}
}
offset += 1 << (subdivision_level * 2 + 1);
subdivision_level += 1;
}
}
// first step -> detect every triangle that is bigger than treshhold (+ apply GPU culling)
// second step -> for every triangle that is going to be rendered, remove all parents that are going to be rendered
// third step -> for every triangle, copy to buffer to be rendered using atomic add
using System;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;
public class TerrainRenderer : MonoBehaviour
{
[StructLayout(LayoutKind.Sequential)]
struct Vertex
{
public const int size = sizeof(float) * 2;
public float x;
public float y;
}
[StructLayout(LayoutKind.Sequential)]
struct Index
{
public const int size = sizeof(uint) * 3;
public uint first;
public uint second;
public uint third;
}
[StructLayout(LayoutKind.Sequential)]
public struct Uniforms
{
public const int size = sizeof(uint) * 3;
public uint subdivisions;
public uint vertexCount;
public uint indexCount;
}
public ComputeShader computeShader;
[Range(0, 13)]
public int subdivisions = 4;
private Uniforms[] _uniforms;
private ComputeBuffer _uniformsBuffer;
private ComputeBuffer _vertexBuffer;
private ComputeBuffer _indexBuffer;
private ComputeBuffer _drawCallArgsBuffer;
public Material material;
public Mesh mesh;
GraphicsBuffer meshTriangles;
GraphicsBuffer meshPositions;
GraphicsBuffer commandBuf;
GraphicsBuffer.IndirectDrawArgs[] commandData;
void Start()
{
_uniforms = new Uniforms[1];
_uniforms[0].subdivisions = (uint)subdivisions;
_uniforms[0].vertexCount = 0;
_uniforms[0].indexCount = 0;
_uniformsBuffer = new ComputeBuffer(1, Uniforms.size);
_uniformsBuffer.SetData(_uniforms);
//_drawCallArgsBuffer = new ComputeBuffer(1, Index.size, ComputeBufferType.IndirectArguments);
int kernelId = computeShader.FindKernel("ComputeSizes");
computeShader.SetBuffer(kernelId, "uniforms", _uniformsBuffer);
computeShader.Dispatch(kernelId, 1, 1, 1);
_uniformsBuffer.GetData(_uniforms);
_vertexBuffer = new ComputeBuffer((int)_uniforms[0].vertexCount, Vertex.size);
_indexBuffer = new ComputeBuffer((int)_uniforms[0].indexCount, Index.size);
kernelId = computeShader.FindKernel("ComputeVerticesAndIndices");
computeShader.SetBuffer(kernelId, "uniforms", _uniformsBuffer);
computeShader.SetBuffer(kernelId, "vertices", _vertexBuffer);
computeShader.SetBuffer(kernelId, "indices", _indexBuffer);
computeShader.Dispatch(kernelId, 1, 1, 1);
Debug.Log(_uniforms[0].subdivisions);
Debug.Log(_uniforms[0].vertexCount);
Debug.Log(_uniforms[0].indexCount);
Vertex[] v = new Vertex[_uniforms[0].vertexCount];
Index[] i = new Index[_uniforms[0].indexCount];
_vertexBuffer.GetData(v);
_indexBuffer.GetData(i);
mesh.SetIndices(new int[0] {}, MeshTopology.Triangles, 0);
mesh.SetVertices(v.Select(vertex => new Vector3(vertex.x, vertex.y, 0f)).ToArray());
mesh.SetIndices(i.Select(index => new int[] { (int) index.first, (int) index.second, (int) index.third }).SelectMany(x => x).ToArray(), MeshTopology.Triangles, 0);
mesh.RecalculateBounds();
mesh.RecalculateNormals();
mesh.RecalculateTangents();
meshTriangles = new GraphicsBuffer(GraphicsBuffer.Target.Structured, mesh.triangles.Length, sizeof(int));
meshTriangles.SetData(mesh.triangles);
meshPositions = new GraphicsBuffer(GraphicsBuffer.Target.Structured, mesh.vertices.Length, 3 * sizeof(float));
meshPositions.SetData(mesh.vertices);
commandBuf = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, GraphicsBuffer.IndirectDrawArgs.size);
commandData = new GraphicsBuffer.IndirectDrawArgs[1];
}
private void Update()
{
RenderParams rp = new RenderParams(material);
rp.worldBounds = new Bounds(Vector3.zero, 10000*Vector3.one); // use tighter bounds
rp.matProps = new MaterialPropertyBlock();
rp.matProps.SetBuffer("_Triangles", meshTriangles);
rp.matProps.SetBuffer("_Positions", meshPositions);
rp.matProps.SetInt("_BaseVertexIndex", (int)mesh.GetBaseVertex(0));
rp.matProps.SetMatrix("_ObjectToWorld", Matrix4x4.Translate(new Vector3(-4.5f, 0, 0)));
commandData[0].vertexCountPerInstance = mesh.GetIndexCount(0);
commandData[0].instanceCount = 1;
commandBuf.SetData(commandData);
Graphics.RenderPrimitivesIndirect(rp, MeshTopology.Triangles, commandBuf, 1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment