Skip to content

Instantly share code, notes, and snippets.

@PamisuMyon
Last active July 10, 2024 10:21
Show Gist options
  • Save PamisuMyon/ce962f558b7ecb63be73bea3b406cafc to your computer and use it in GitHub Desktop.
Save PamisuMyon/ce962f558b7ecb63be73bea3b406cafc to your computer and use it in GitHub Desktop.
Unity URP DrawMeshInstancedIndirect Frustum Culling Demo

Unity URP DrawMeshInstancedIndirect Frustum Culling Demo

#pragma kernel CSMain
float4 _FrustumPlanes[6]; // 视锥体的六个面
float3 _BoundMin; // 物体包围盒最小点
float3 _BoundMax; // 物体包围盒最大点
StructuredBuffer<float4x4> _AllMatricesBuffer; // 所有物体的复合变换矩阵
AppendStructuredBuffer<uint> _VisibleIDsBuffer; // 可见物体实例ID
bool IsOutsideThePlane(float4 plane, float3 position)
{
return dot(plane.xyz, position) + plane.w > 0;
}
[numthreads(640, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
float4x4 m = _AllMatricesBuffer[id.x];
float4 boundPoints[8];
boundPoints[0] = mul(m, float4(_BoundMin, 1));
boundPoints[1] = mul(m, float4(_BoundMax, 1));
boundPoints[2] = mul(m, float4(_BoundMax.x, _BoundMax.y, _BoundMin.z, 1));
boundPoints[3] = mul(m, float4(_BoundMax.x, _BoundMin.y, _BoundMax.z, 1));
boundPoints[4] = mul(m, float4(_BoundMax.x, _BoundMin.y, _BoundMin.z, 1));
boundPoints[5] = mul(m, float4(_BoundMin.x, _BoundMax.y, _BoundMax.z, 1));
boundPoints[6] = mul(m, float4(_BoundMin.x, _BoundMax.y, _BoundMin.z, 1));
boundPoints[7] = mul(m, float4(_BoundMin.x, _BoundMin.y, _BoundMax.z, 1));
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 8; j++)
{
float3 p = boundPoints[j].xyz;
if (!IsOutsideThePlane(_FrustumPlanes[i], p))
break;
if (j == 7)
return;
}
}
_VisibleIDsBuffer.Append(id.x);
}
using UnityEngine;
using Random = UnityEngine.Random;
namespace Pamisu
{
public class FrustumCullingRenderer : MonoBehaviour
{
public int instanceCount = 100000;
public Mesh instanceMesh;
public Material instanceMaterial;
public int subMeshIndex = 0;
public Vector3 objectBoundMin;
public Vector3 objectBoundMax;
public ComputeShader cullingComputeShader;
int cachedInstanceCount = -1;
int cachedSubMeshIndex = -1;
int kernel = 0;
ComputeBuffer allMatricesBuffer;
ComputeBuffer visibleIDsBuffer;
ComputeBuffer argsBuffer;
uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
Plane[] cameraFrustumPlanes = new Plane[6];
Vector4[] frustumPlanes = new Vector4[6];
void Start()
{
kernel = cullingComputeShader.FindKernel("CSMain");
}
void Update()
{
// 更新Buffer
UpdateBuffers();
// 方向键改变绘制数量
if (Input.GetAxisRaw("Horizontal") != 0.0f)
instanceCount = (int)Mathf.Clamp(instanceCount + Input.GetAxis("Horizontal") * 40000, 1.0f, 5000000.0f);
// 视锥剔除
GeometryUtility.CalculateFrustumPlanes(Camera.main, cameraFrustumPlanes);
for (int i = 0; i < cameraFrustumPlanes.Length; i++)
{
var normal = -cameraFrustumPlanes[i].normal;
frustumPlanes[i] = new Vector4(normal.x, normal.y, normal.z, -cameraFrustumPlanes[i].distance);
}
visibleIDsBuffer.SetCounterValue(0);
cullingComputeShader.SetVectorArray("_FrustumPlanes", frustumPlanes);
cullingComputeShader.Dispatch(kernel, Mathf.CeilToInt(instanceCount / 640f), 1, 1);
ComputeBuffer.CopyCount(visibleIDsBuffer, argsBuffer, sizeof(uint));
// 渲染
Bounds renderBounds = new Bounds(Vector3.zero, new Vector3(200.0f, 200.0f, 200.0f));
Graphics.DrawMeshInstancedIndirect(instanceMesh, subMeshIndex, instanceMaterial, renderBounds, argsBuffer);
}
void OnGUI()
{
GUI.Label(new Rect(265, 25, 200, 30), "Instance Count: " + instanceCount.ToString());
instanceCount = (int)GUI.HorizontalSlider(new Rect(25, 20, 200, 30), instanceCount, 1.0f, 5000000.0f);
}
void UpdateBuffers()
{
// 不需要更新时返回
if ((cachedInstanceCount == instanceCount || cachedSubMeshIndex != subMeshIndex)
&& argsBuffer != null)
{
return;
}
// 规范subMeshIndex
if (instanceMesh != null)
subMeshIndex = Mathf.Clamp(subMeshIndex, 0, instanceMesh.subMeshCount - 1);
// 复合变换矩阵
allMatricesBuffer?.Release();
allMatricesBuffer = new ComputeBuffer(instanceCount, sizeof(float) * 16); // float4x4
Matrix4x4[] trs = new Matrix4x4[instanceCount];
for (int i = 0; i < instanceCount; i++)
{
// 随机位置
float angle = Random.Range(0.0f, Mathf.PI * 2.0f);
float distance = Random.Range(8.0f, 90.0f);
float height = Random.Range(-5.0f, 5.0f);
float size = Random.Range(0.05f, 1f);
var position = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size);
trs[i] = Matrix4x4.TRS(position, Random.rotationUniform, new Vector3(size, size, size));
}
allMatricesBuffer.SetData(trs);
instanceMaterial.SetBuffer("_AllMatricesBuffer", allMatricesBuffer);
// 可见实例 Buffer
visibleIDsBuffer?.Release();
visibleIDsBuffer = new ComputeBuffer(instanceCount, sizeof(uint), ComputeBufferType.Append);
instanceMaterial.SetBuffer("_VisibleIDsBuffer", visibleIDsBuffer);
// Indirect args
argsBuffer?.Release();
argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
if (instanceMesh != null)
{
args[0] = instanceMesh.GetIndexCount(subMeshIndex);
args[1] = (uint)instanceCount;
args[2] = instanceMesh.GetIndexStart(subMeshIndex);
args[3] = instanceMesh.GetBaseVertex(subMeshIndex);
}
else
{
args[0] = args[1] = args[2] = args[3] = 0;
}
argsBuffer.SetData(args);
// ComputeShader
cullingComputeShader.SetVector("_BoundMin", objectBoundMin);
cullingComputeShader.SetVector("_BoundMax", objectBoundMax);
cullingComputeShader.SetBuffer(kernel, "_AllMatricesBuffer", allMatricesBuffer);
cullingComputeShader.SetBuffer(kernel, "_VisibleIDsBuffer", visibleIDsBuffer);
cachedInstanceCount = instanceCount;
cachedSubMeshIndex = subMeshIndex;
}
void OnDisable()
{
allMatricesBuffer?.Release();
allMatricesBuffer = null;
visibleIDsBuffer?.Release();
visibleIDsBuffer = null;
argsBuffer?.Release();
argsBuffer = null;
}
}
}
Shader "Custom/URP/Instanced Culling"
{
Properties
{
[MainTexture] _BaseMap("Albedo", 2D) = "white" {}
[MainColor] _BaseColor("Color", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8, 256)) = 16
_SpecularColor("Specular Color", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _Gloss;
half4 _SpecularColor;
StructuredBuffer<float4x4> _AllMatricesBuffer;
StructuredBuffer<uint> _VisibleIDsBuffer;
CBUFFER_END
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
ENDHLSL
Pass
{
Tags
{
"LightMode" = "UniversalForward"
}
HLSLPROGRAM
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile_fog
#pragma vertex Vertex
#pragma fragment Fragment
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float4 normalWSAndFogFactor : TEXCOORD1;
float3 positionWS : TEXCOORD2;
};
Varyings Vertex(Attributes IN, uint instanceID : SV_InstanceID)
{
Varyings OUT;
#if SHADER_TARGET >= 45
float4x4 data = _AllMatricesBuffer[_VisibleIDsBuffer[instanceID]];
#else
float4x4 data = 0;
#endif
// float3 positionWS = mul(mul(unity_ObjectToWorld, data), IN.positionOS).xyz;
float3 positionWS = mul(data, IN.positionOS).xyz;
OUT.positionWS = positionWS;
OUT.positionCS = mul(unity_MatrixVP, float4(positionWS, 1.0));
OUT.uv = TRANSFORM_TEX(IN.texcoord, _BaseMap);
// float3 normalWS = TransformObjectToWorldNormal(normalize(mul(data, IN.normalOS)));
// Note: Uniform scaling only
float3 normalWS = normalize(mul(data, float4(IN.normalOS, 0))).xyz;
float fogFactor = ComputeFogFactor(OUT.positionCS.z);
OUT.normalWSAndFogFactor = float4(normalWS, fogFactor);
return OUT;
}
half4 Fragment(Varyings IN) : SV_Target
{
half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv) * _BaseColor;
Light light = GetMainLight(TransformWorldToShadowCoord(IN.positionWS));
half3 lighting = light.color * light.distanceAttenuation * light.shadowAttenuation;
float3 normalWS = IN.normalWSAndFogFactor.xyz;
half3 diffuse = saturate(dot(normalWS, light.direction)) * lighting;
float3 v = normalize(_WorldSpaceCameraPos - IN.positionWS);
float3 h = normalize(v + light.direction);
half3 specular = pow(saturate(dot(normalWS, h)), _Gloss) * _SpecularColor.rgb * lighting;
half3 ambient = SampleSH(normalWS);
half4 color = half4(albedo.rgb * diffuse + specular + ambient, 1.0);
float fogFactor = IN.normalWSAndFogFactor.w;
color.rgb = MixFog(color.rgb, fogFactor);
return color;
}
ENDHLSL
}
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vertex
#pragma fragment Fragment
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct Varyings
{
float2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
};
float3 _LightDirection;
Varyings Vertex(Attributes IN, uint instanceID : SV_InstanceID)
{
Varyings OUT;
#if SHADER_TARGET >= 45
float4x4 data = _AllMatricesBuffer[_VisibleIDsBuffer[instanceID]];
#else
float4x4 data = 0;
#endif
float3 positionWS = mul(data, IN.positionOS).xyz;
// float3 normalWS = TransformObjectToWorldNormal(normalize(mul(data, IN.normalOS)));
float3 normalWS = normalize(mul(data, float4(IN.normalOS, 0))).xyz;
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
#if UNITY_REVERSED_Z
positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
OUT.positionCS = positionCS;
OUT.uv = TRANSFORM_TEX(IN.texcoord, _BaseMap);
return OUT;
}
half4 Fragment(Varyings IN) : SV_TARGET
{
return 0;
}
ENDHLSL
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment