Skip to content

Instantly share code, notes, and snippets.

@HAliss
Created February 18, 2022 15:00
Show Gist options
  • Save HAliss/e5f799f8d442d460bf97c78a178d9b1a to your computer and use it in GitHub Desktop.
Save HAliss/e5f799f8d442d460bf97c78a178d9b1a to your computer and use it in GitHub Desktop.
EGC Workshop
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel GrassGeneration
struct InputData {
float3 position;
float3 normal;
float3 tangent;
};
struct IndirectArgs {
uint numVerticesPerInstance;
uint numInstances;
uint startVertexIndex;
uint startInstanceIndex;
};
struct DrawVertex {
float3 position;
float3 normal;
float2 uv;
};
struct DrawTriangle {
DrawVertex vertices[3];
};
StructuredBuffer<InputData> _InputDataBuffer;
AppendStructuredBuffer<DrawTriangle> _DrawTrianglesBuffer;
RWStructuredBuffer<IndirectArgs> _IndirectArgsBuffer;
int _VertexCount;
float4x4 _LocalToWorld;
float4 _WidthHeightRange;
DrawVertex GetVertex(float3 position, float3 normal, float2 uv) {
DrawVertex output = (DrawVertex) 0;
output.position = mul(_LocalToWorld, float4(position, 1.0)).xyz;
output.normal = mul(_LocalToWorld, float4(normal, 0.0)).xyz;
output.uv = uv;
return output;
}
float random (float2 st) {
return frac(sin(dot(st.xy,float2(12.9898,78.233)))*43758.5453123);
}
[numthreads(64,1,1)]
void GrassGeneration (uint3 id : SV_DispatchThreadID)
{
if ((int) id.x >= _VertexCount) {
return;
}
InputData input = _InputDataBuffer[id.x];
DrawVertex drawVertices[6];
float randValue = random(input.position.xz);
float width = lerp(_WidthHeightRange.x, _WidthHeightRange.y, randValue);
float height = lerp(_WidthHeightRange.z, _WidthHeightRange.w, randValue);
drawVertices[0] = GetVertex(input.position - width * input.tangent, input.normal, float2(0, 0));
drawVertices[1] = GetVertex(input.position + width * input.tangent, input.normal, float2(1, 0));
drawVertices[2] = GetVertex(input.position - width * input.tangent + height * input.normal, input.normal, float2(0, 1));
drawVertices[3] = GetVertex(input.position + width * input.tangent, input.normal, float2(1, 0));
drawVertices[4] = GetVertex(input.position + width * input.tangent + height * input.normal, input.normal, float2(1, 1));
drawVertices[5] = GetVertex(input.position - width * input.tangent + height * input.normal, input.normal, float2(0, 1));
DrawTriangle tri = (DrawTriangle) 0;
tri.vertices[0] = drawVertices[0];
tri.vertices[1] = drawVertices[1];
tri.vertices[2] = drawVertices[2];
_DrawTrianglesBuffer.Append(tri);
tri.vertices[0] = drawVertices[3];
tri.vertices[1] = drawVertices[4];
tri.vertices[2] = drawVertices[5];
_DrawTrianglesBuffer.Append(tri);
InterlockedAdd(_IndirectArgsBuffer[0].numVerticesPerInstance, 6);
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class GrassGenerator : MonoBehaviour
{
public struct InputData
{
public Vector3 position;
public Vector3 normal;
public Vector3 tangent;
public InputData(Vector3 position, Vector3 normal, Vector3 tangent)
{
this.position = position;
this.normal = normal;
this.tangent = tangent;
}
}
public ComputeShader grassGenerationCS;
public Material renderingMaterial;
public Vector2 width = new Vector2(0.25f, 0.5f);
public Vector2 height = new Vector2(0.25f, 0.5f);
private List<InputData> inputDataList = new List<InputData>();
private int[] indirectArgs = new int[] { 0, 1, 0, 0 };
private ComputeBuffer inputDataBuffer;
private ComputeBuffer drawTrianglesBuffer;
private ComputeBuffer indirectArgsBuffer;
private const int INPUTDATA_STRIDE = sizeof(float) * (3 + 3 + 3);
private const int DRAWTRIANGLES_STRIDE = sizeof(float) * 3 * (3 + 3 + 2);
private const int INDIRECTARGS_STRIDE = sizeof(int) * 4;
private const int MAX_TRIANGLES = 2;
private Mesh mesh;
private int vertexCount;
private int kernelID;
private int threadGroupSize;
private Bounds bounds;
private void OnEnable()
{
mesh = GetComponent<MeshFilter>().sharedMesh;
vertexCount = mesh.vertexCount;
SetupBuffers();
SetupData();
}
private void OnDisable()
{
ReleaseBuffers();
}
private void SetupBuffers()
{
inputDataBuffer = new ComputeBuffer(vertexCount, INPUTDATA_STRIDE, ComputeBufferType.Structured, ComputeBufferMode.Immutable);
drawTrianglesBuffer = new ComputeBuffer(vertexCount * MAX_TRIANGLES, DRAWTRIANGLES_STRIDE, ComputeBufferType.Append);
indirectArgsBuffer = new ComputeBuffer(1, INDIRECTARGS_STRIDE, ComputeBufferType.IndirectArguments);
}
private void ReleaseBuffers()
{
ReleaseBuffer(inputDataBuffer);
ReleaseBuffer(drawTrianglesBuffer);
ReleaseBuffer(indirectArgsBuffer);
}
private void SetupData()
{
if (mesh == null)
{
return;
}
inputDataList.Clear();
for (int i = 0; i < vertexCount; i++)
{
inputDataList.Add(new InputData(mesh.vertices[i], mesh.normals[i], mesh.tangents[i]));
}
bounds = GetComponent<MeshRenderer>().bounds;
inputDataBuffer.SetData(inputDataList);
indirectArgsBuffer.SetData(indirectArgs);
kernelID = grassGenerationCS.FindKernel("GrassGeneration");
grassGenerationCS.GetKernelThreadGroupSizes(kernelID, out uint threadGroupSizeX, out _, out _);
threadGroupSize = Mathf.CeilToInt((float)vertexCount / threadGroupSizeX);
grassGenerationCS.SetBuffer(kernelID, "_InputDataBuffer", inputDataBuffer);
grassGenerationCS.SetBuffer(kernelID, "_DrawTrianglesBuffer", drawTrianglesBuffer);
grassGenerationCS.SetBuffer(kernelID, "_IndirectArgsBuffer", indirectArgsBuffer);
grassGenerationCS.SetInt("_VertexCount", vertexCount);
renderingMaterial.SetBuffer("_DrawTrianglesBuffer", drawTrianglesBuffer);
}
private void GenerateGeometry()
{
if (mesh == null || drawTrianglesBuffer == null || indirectArgsBuffer == null || inputDataBuffer == null || grassGenerationCS == null || renderingMaterial == null)
{
return;
}
drawTrianglesBuffer.SetCounterValue(0);
grassGenerationCS.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
grassGenerationCS.SetVector("_WidthHeightRange", new Vector4(width.x, width.y, height.x, height.y));
grassGenerationCS.Dispatch(kernelID, threadGroupSize, 1, 1);
}
private void Update()
{
GenerateGeometry();
Graphics.DrawProceduralIndirect(
renderingMaterial,
bounds,
MeshTopology.Triangles,
indirectArgsBuffer,
0,
null,
null,
UnityEngine.Rendering.ShadowCastingMode.On,
true,
gameObject.layer
);
}
private void ReleaseBuffer(ComputeBuffer buffer)
{
if (buffer != null)
{
buffer.Release();
buffer = null;
}
}
}
Shader "Unlit/ProceduralGrassUnlit"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Cull Off
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct DrawVertex {
float3 position;
float3 normal;
float2 uv;
};
struct DrawTriangle {
DrawVertex vertices[3];
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
StructuredBuffer<DrawTriangle> _DrawTrianglesBuffer;
v2f vert (uint vertexID : SV_VertexID)
{
v2f o;
DrawTriangle tri = _DrawTrianglesBuffer[vertexID / 3];
DrawVertex v = tri.vertices[vertexID % 3];
o.vertex = UnityObjectToClipPos(v.position);
o.uv = v.uv;
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = fixed4(i.uv, 0, 1);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment