在Unity中用ComputeShader实现草地剔除(基于URP)

步骤:一,获取所有草的数据

二,将数据传到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
		}
    }
}

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值