Created
May 6, 2024 01:37
-
-
Save NicolasCaous/ff2e429bf7ee8012c32607f4e8a8b126 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; | |
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; | |
} |
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) * 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