Unity URP DrawMeshInstancedIndirect Frustum Culling Demo
Last active
July 10, 2024 10:21
-
-
Save PamisuMyon/ce962f558b7ecb63be73bea3b406cafc to your computer and use it in GitHub Desktop.
Unity URP DrawMeshInstancedIndirect Frustum Culling Demo
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
#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); | |
} |
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 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; | |
} | |
} | |
} |
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
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