步骤:一,获取所有草的数据
二,将数据传到ComputerShader里做剔除
三,将剔除后的数据传到Shader渲染
第一步,获取所有草的数据
草地数据包括模型,旋转,位置,缩放。
模型可以动态创建,也可以直接用静态模型,声明一个uint数组储存模型信息。
public Mesh grassMesh;
uint[] args = new uint[5] {0, 0, 0, 0, 0};
......
args[0] = grassMesh.GetIndexCount(0);
args[2] = grassMesh.GetIndexStart(0);
args[3] = grassMesh.GetBaseVertex(0);
声明一个ComputeBuffer储存草的transform信息。
ComputeBuffer grassMatrixBuffer = new ComputeBuffer(m_grassCount, sizeof(float) * 16);
Matrix4x4[] grassMatrixs = new Matrix4x4[m_grassCount];
for (int i = 0; i < m_grassCount; i++)
{
Vector3 pos = Vector3.zero;
pos.x = Random.Range(bounds.min.x * 0.1f, bounds.max.x * 0.1f);
pos.z = Random.Range(bounds.min.z * 0.1f, bounds.max.z * 0.1f);
pos.y = terrain.SampleHeight(new Vector3(pos.x, 0, pos.z));
grassMatrixs[i] = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
}
grassMatrixBuffer.SetData(grassMatrixs);
第二步,将数据传到ComputerShader里做剔除
需要传到ComputeShader里的数据:草的数量,所有草的矩阵信息,包围盒尺寸,相机的VP矩阵,还有用于计算遮挡剔除的深度图信息。
首先声明两个ComputeBuffer,用来储存Mesh和剔除后的数据
ComputeBuffer argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
ComputeBuffer cullResultBuffer = new ComputeBuffer(m_grassCount, sizeof(float) * 16, ComputeBufferType.Append);
......
argsBuffer.SetData(args);
包围盒尺寸可以直接在ComputeShader里设置。
获取深度图信息
private RenderTexture depthTexture;
depthTexture = new RenderTexture(1024, 1024, 0, RenderTextureFormat.RHalf);
depthTexture.autoGenerateMips = false;
depthTexture.useMipMap = true;
depthTexture.filterMode = FilterMode.Point;
depthTexture.Create();
depthMat = new Material(depthShader);
......
int w = depthTexture.width;
int h = depthTexture.height;
int level = 0;
RenderTexture lastRt = null;
if (ID_DepthTexture == 0)
{
ID_DepthTexture = Shader.PropertyToID("_DepthTexture");
ID_InvSize = Shader.PropertyToID("_InvSize");
}
RenderTexture tempRT;
while (h > 8)
{
depthMat.SetVector(ID_InvSize, new Vector4(1.0f / w, 1.0f / h, 0, 0));
tempRT = RenderTexture.GetTemporary(w, h, 0, depthTexture.format);
tempRT.filterMode = FilterMode.Point;
if (lastRt == null)
{
Graphics.Blit(Shader.GetGlobalTexture("_CameraDepthTexture"), tempRT);
}
else
{
depthMat.SetTexture(ID_DepthTexture, lastRt);
Graphics.Blit(null, tempRT, depthMat);
RenderTexture.ReleaseTemporary(lastRt);
}
Graphics.CopyTexture(tempRT, 0, 0, depthTexture, 0, level);
lastRt = tempRT;
w /= 2;
h /= 2;
level++;
}
RenderTexture.ReleaseTemporary(lastRt);
相机的VP矩阵
Matrix4x4 vp = GL.GetGPUProjectionMatrix(mainCamera.projectionMatrix, false) * mainCamera.worldToCameraMatrix);
信息获取完后传到ComputeShader里并执行
public ComputeShader compute;
private int kernel;
kernel = compute.FindKernel("GrassCulling");
......
compute.SetInt("depthTextureSize", 1024);
compute.SetTexture(kernel, depthTextureId, depthTexture);
compute.SetInt("grassCount", m_grassCount);
compute.SetBuffer(kernel, "grassMatrixBuffer", grassMatrixBuffer);
compute.SetMatrix(vpMatrixId,vp);
cullResultBuffer.SetCounterValue(0);
compute.SetBuffer(kernel, cullResultBufferId, cullResultBuffer);
//执行
compute.Dispatch(kernel, 1 + m_grassCount / 640, 1, 1);
//传值
grassMaterial.SetBuffer(positionBufferId, cullResultBuffer);
//绘制
ComputeBuffer.CopyCount(cullResultBuffer, argsBuffer, sizeof(uint));
Graphics.DrawMeshInstancedIndirect(grassMesh, 0, grassMaterial,bounds, argsBuffer);
释放内存
grassMatrixBuffer?.Release();
grassMatrixBuffer = null;
cullResultBuffer?.Release();
cullResultBuffer = null;
argsBuffer?.Release();
argsBuffer = null;
depthTexture.Release();
Destroy(depthTexture);
最后贴上ComputeShader和DepthShader
#pragma kernel GrassCulling
uint grassCount;
uint depthTextureSize;
StructuredBuffer<float4x4> grassMatrixBuffer;
bool isOpenGL;
float4x4 vpMatrix;
AppendStructuredBuffer<float4x4> cullResultBuffer;
Texture2D hizTexture;
static float3 boundMin = float3(-0.2f, 0.0f, -0.3f);
static float3 boundMax = float3(0.2f, 0.5f, 0.3f);
bool IsInClipSpace(float4 clipSpacePosition)
{
if (isOpenGL)
return clipSpacePosition.x > -clipSpacePosition.w && clipSpacePosition.x < clipSpacePosition.w&&
clipSpacePosition.y > -clipSpacePosition.w && clipSpacePosition.y < clipSpacePosition.w&&
clipSpacePosition.z > -clipSpacePosition.w && clipSpacePosition.z < clipSpacePosition.w;
else
return clipSpacePosition.x > -clipSpacePosition.w && clipSpacePosition.x < clipSpacePosition.w&&
clipSpacePosition.y > -clipSpacePosition.w && clipSpacePosition.y < clipSpacePosition.w&&
clipSpacePosition.z > 0 && clipSpacePosition.z < clipSpacePosition.w;
}
[numthreads(640, 1, 1)]
void GrassCulling(uint3 id : SV_DispatchThreadID)
{
if (id.x >= grassCount) return;
float4x4 grassMatrix = grassMatrixBuffer[id.x];
float4x4 mvpMatrix = mul(vpMatrix, grassMatrix);
float4 boundVerts[8];
boundVerts[0] = float4(boundMin, 1);
boundVerts[1] = float4(boundMax, 1);
boundVerts[2] = float4(boundMax.x, boundMax.y, boundMin.z, 1);
boundVerts[3] = float4(boundMax.x, boundMin.y, boundMax.z, 1);
boundVerts[4] = float4(boundMax.x, boundMin.y, boundMin.z, 1);
boundVerts[5] = float4(boundMin.x, boundMax.y, boundMax.z, 1);
boundVerts[6] = float4(boundMin.x, boundMax.y, boundMin.z, 1);
boundVerts[7] = float4(boundMin.x, boundMin.y, boundMax.z, 1);
float minX = 1, minY = 1, minZ = 1, maxX = -1, maxY = -1, maxZ = -1;
//-------------------------------------------------------视椎剔除-------------------------------------------------------
bool isInClipSpace = false;
for (int i = 0; i < 8; i++)
{
float4 clipSpace = mul(mvpMatrix, boundVerts[i]);
if (!isInClipSpace && IsInClipSpace(clipSpace))
isInClipSpace = true;
float3 ndc = clipSpace.xyz / clipSpace.w;
if (minX > ndc.x) minX = ndc.x;
if (minY > ndc.y) minY = ndc.y;
if (minZ > ndc.z) minZ = ndc.z;
if (maxX < ndc.x) maxX = ndc.x;
if (maxY < ndc.y) maxY = ndc.y;
if (maxZ < ndc.z) maxZ = ndc.z;
}
if (!isInClipSpace)
return;
//-------------------------------------------------------Hiz遮挡剔除-------------------------------------------------------
float2 uvLeftBottom = float2(minX, minY) * 0.5f + 0.5f;
float2 uvRightTop = float2(maxX, maxY) * 0.5f + 0.5f;
float depth = maxZ;
if (isOpenGL) {
depth = minZ;
depth = depth * 0.5f + 0.5f;
}
uint mipmapLevel = (uint)clamp(depthTextureSize * 2 / log2(max(maxX - minX, maxY - minY)), 0, log2(depthTextureSize) - 4);
uint size = depthTextureSize / (1 << mipmapLevel);
uint2 pixelLeftBottom = uint2(clamp(uvLeftBottom.x * size, 0, size - 1), clamp(uvLeftBottom.y * size, 0, size - 1));
uint2 pixelRightTop = uint2(clamp(uvRightTop.x * size, 0, size - 1), clamp(uvRightTop.y * size, 0, size - 1));
float depthInTexture = hizTexture.mips[mipmapLevel][pixelLeftBottom].r;
if (isOpenGL) {
if (pixelLeftBottom.x < pixelRightTop.x && pixelLeftBottom.y < pixelRightTop.y) {
depthInTexture = max(max(depthInTexture, hizTexture.mips[mipmapLevel][pixelRightTop].r),
max(hizTexture.mips[mipmapLevel][int2(pixelLeftBottom.x, pixelRightTop.y)].r, hizTexture.mips[mipmapLevel][int2(pixelRightTop.x, pixelLeftBottom.y)].r));
}
else if (pixelLeftBottom.x < pixelRightTop.x)
depthInTexture = max(depthInTexture, hizTexture.mips[mipmapLevel][int2(pixelRightTop.x, pixelLeftBottom.y)].r);
else if (pixelLeftBottom.y < pixelRightTop.y)
depthInTexture = max(depthInTexture, hizTexture.mips[mipmapLevel][int2(pixelLeftBottom.x, pixelRightTop.y)].r);
if (depthInTexture < depth)
return;
}
else {
if (pixelLeftBottom.x < pixelRightTop.x && pixelLeftBottom.y < pixelRightTop.y) {
depthInTexture = min(min(depthInTexture, hizTexture.mips[mipmapLevel][pixelRightTop].r),
min(hizTexture.mips[mipmapLevel][int2(pixelLeftBottom.x, pixelRightTop.y)].r, hizTexture.mips[mipmapLevel][int2(pixelRightTop.x, pixelLeftBottom.y)].r));
}
else if (pixelLeftBottom.x < pixelRightTop.x)
depthInTexture = min(depthInTexture, hizTexture.mips[mipmapLevel][int2(pixelRightTop.x, pixelLeftBottom.y)].r);
else if (pixelLeftBottom.y < pixelRightTop.y)
depthInTexture = min(depthInTexture, hizTexture.mips[mipmapLevel][int2(pixelLeftBottom.x, pixelRightTop.y)].r);
if (depthInTexture > depth)
return;
}
cullResultBuffer.Append(grassMatrix);
}
Shader "Hidden/DepthTexture" {
Properties {
[HideInInspector] _DepthTexture("Depth Texture", 2D) = "black" {}
[HideInInspector] _InvSize("Inverse Mipmap Size", Vector) = (0, 0, 0, 0)
}
SubShader {
Pass {
Cull Off ZWrite Off ZTest Always
CGPROGRAM
#pragma target 3.0
#pragma vertex HZBVert
#pragma fragment HZBBuildFrag
sampler2D _DepthTexture;
float4 _InvSize;
struct HZBAttributes
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct HZBVaryings
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
inline float HZBReduce(sampler2D mainTex, float2 inUV, float2 invSize)
{
float4 depth;
float2 uv0 = inUV + float2(-0.25f, -0.25f) * invSize;
float2 uv1 = inUV + float2(0.25f, -0.25f) * invSize;
float2 uv2 = inUV + float2(-0.25f, 0.25f) * invSize;
float2 uv3 = inUV + float2(0.25f, 0.25f) * invSize;
depth.x = tex2D(mainTex, uv0);
depth.y = tex2D(mainTex, uv1);
depth.z = tex2D(mainTex, uv2);
depth.w = tex2D(mainTex, uv3);
#if defined(UNITY_REVERSED_Z)
return min(min(depth.x, depth.y), min(depth.z, depth.w));
#else
return max(max(depth.x, depth.y), max(depth.z, depth.w));
#endif
}
HZBVaryings HZBVert(HZBAttributes v)
{
HZBVaryings o;
o.vertex = UnityObjectToClipPos(v.vertex.xyz);
o.uv = v.uv;
return o;
}
float4 HZBBuildFrag(HZBVaryings input) : Color
{
float2 invSize = _InvSize.xy;
float2 inUV = input.uv;
float depth = HZBReduce(_DepthTexture, inUV, invSize);
return float4(depth, 0, 0, 1.0f);
}
ENDCG
}
}
}