Unity Shader 之GPU Instancing的简单使用

GPU Instancing,即GPU实例化,实现方式是将使用同一个材质及网格的物体(需要大批量渲染)的数据一次性打包发给GPU(最常见的运用无非是草的渲染了…),以达到减少大量DrawCall的目的。其它的我就不啰嗦了,一些细节有必要我会在后面再说。
这次我也只是实现(抄了)了一个比较简单的草地Demo,参考项目是Unity利用GPUinstancing实现大面积草地。下面看一下我自己实现的效果图:
在这里插入图片描述
送你一片青青草原!
动图来一发,看着很卡还很糊还很难看…但是实际上一点都不卡(电脑上)
在这里插入图片描述
以下是代码

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class MeshGrass : MonoBehaviour
{
    public Material grassmat;
    public Mesh quad;
    public Texture2D heightMap;
    public float terrainHeight = 20;
    public int terrainSize = 80;
    public Material terrainMat;
    public int grasscount = 300;
    [Range(0.0f, 1f)]
    private float grassdensity;
    private List<Matrix4x4[]> matrixlist = new List<Matrix4x4[]>();
    private Camera mainCamera;
    private CommandBuffer myBuffer;

    void Start()
    {
        CreateTerrian();
        MeshComputing();
    }

    private void Update()
    {
        RenderGrass();
    }

    void CreateTerrian()
    {
        List<Vector3> vertices = new List<Vector3>();
        List<int> triangles = new List<int>();
        for (int i = 0; i < terrainSize; i++)
        {
            for (int j = 0; j < terrainSize; j++)
            {
                vertices.Add(new Vector3(i, heightMap.GetPixel(i, j).grayscale * terrainHeight, j));
                if (i == 0 || j == 0) continue;
                triangles.Add(terrainSize * i + j);
                triangles.Add(terrainSize * i + j - 1);
                triangles.Add(terrainSize * (i - 1) + j - 1);
                triangles.Add(terrainSize * (i - 1) + j - 1);
                triangles.Add(terrainSize * (i - 1) + j);
                triangles.Add(terrainSize * i + j);
            }
        }
        Vector2[] uvs = new Vector2[vertices.Count];
        for (var i = 0; i < uvs.Length; i++)
        {
            uvs[i] = new Vector2(vertices[i].x, vertices[i].z);
        }
        GameObject terrain = this.gameObject;
        terrain.AddComponent<MeshFilter>();
        MeshRenderer renderer = terrain.AddComponent<MeshRenderer>();
        renderer.sharedMaterial = terrainMat;
        renderer.shadowCastingMode = ShadowCastingMode.On;
        renderer.receiveShadows = true;
        Mesh groundMesh = new Mesh();
        groundMesh.vertices = vertices.ToArray();
        groundMesh.uv = uvs;
        groundMesh.triangles = triangles.ToArray();
        groundMesh.RecalculateNormals();//生成法线
        terrain.GetComponent<MeshFilter>().mesh = groundMesh;
        terrain.AddComponent<MeshCollider>();
        vertices.Clear();
    }

    void MeshComputing()
    {
        grassdensity = (float)terrainSize / grasscount;
        Matrix4x4[] matrices = new Matrix4x4[1023];
        Quaternion rotation = Quaternion.Euler(0, 0, 0);
        Vector3 scale = Vector3.one;
        int mm = 0;
        for (int i = 0; i < grasscount; i++)
        {
            for (int j = 0; j < grasscount; j++)
            {
                float ran = Random.Range(-0.19f, 0.2f);
                float ii = i * grassdensity;
                float jj = j * grassdensity;
                float x = ii + ran;
                float y = jj + ran;
                float h = heightMap.GetPixel(Mathf.FloorToInt(ii), Mathf.FloorToInt(jj)).grayscale * terrainHeight + 0.5f;
                Vector3 position = new Vector3(x, h, y)+transform.position;
                matrices[mm] = Matrix4x4.TRS(position, rotation, scale);

                mm++;
                if (mm % 1022 == 0)
                {
                    matrixlist.Add(new Matrix4x4[1023]);
                    matrixlist[matrixlist.Count - 1] = matrices;
                    matrices = new Matrix4x4[1023];
                    mm = 0;
                }
            }
        }
        matrixlist.Add(matrices);
    }

    void RenderGrass()
    {
        MaterialPropertyBlock block = new MaterialPropertyBlock();
        foreach (Matrix4x4[] mat in matrixlist)
        {
            Graphics.DrawMeshInstanced(quad, 0, grassmat, mat, 1023, block, ShadowCastingMode.Off, false);
        }
    }
}

这个代码也很好理解,花点时间看一看就基本能看懂了

void CreateTerrian()  根据噪音图生成地形网格
void MeshComputing()  生成草的Model矩阵并保存
void RenderGrass()    渲染草

这里需要重点说明一下Graphics.DrawMeshInstanced(),该函数需要每帧调用。我们这里只用到了前8个参数,分别是mesh网格,submeshIndex要绘制网格的哪个子集(这仅适用于由多种材料组成的网格。),material材质,matrices模型矩阵数组,count数量(最大数量是1023,这就是为什么草的Model矩阵分1023存做一批),properties要应用的其他材料属性(这个可以跳过),castShadows是否投射阴影(我开了好像没有用,不知道为什么)。

在这里插入图片描述
需要注意的是GetPixel()函数需要将贴图的属性设置为可读,不然会报错
在这里插入图片描述在这里插入图片描述
随便找个空物体把脚本一挂,参数一设,启动就一切OK了
以下是Shader的代码

Shader "MyShader/GPUInstancing"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _WindTex ("Wind Texture",2D)="while"{}
        _Color("Texture Color",Color)=(1,1,1,1)
        _VerticalBillboarding("Vertical Restraints",Range(0,1))=1
        _WindVector("Wind Vector",Vector)=(1,0,0,0)
        _TimeScale("Time Scale",float)=1
        _CutOff("Cut Off",Range(0,0.8))=0.2
    }
    SubShader
    {
        Tags {"IgnoreProjector" = "True" "RenderType" = "TransparentCutout" "DisableBatching" = "True"}
        LOD 100
        Cull Off
        Pass
        {
            Tags{ "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            //第一步,必须要使用这个编译指示符宣告使用GPU多例化技术
            #pragma multi_compile_instancing

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                //第二步,如果要访问片元着色器中的多例化属性变量,需要使用此宏
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _WindTex;

            fixed4 _Color;
            float _VerticalBillboarding;
            float4 _MapMessage;
            float4 _WindVector;
            float _TimeScale;
            float _CutOff;

			//第三步,宣告多例化属性变量语句
			//UNITY_INSTANCING_CBUFFER_START(Props)
   			//UNITY_DEFINE_INSTANCED_PROP(float4,_Color)
            //UNITY_INSTANCING_CBUFFER_END(Props)
            //我这里用不上,就是给每个着色器一个不同的属性(例如颜色),
            //可以代码赋值也可以Inspectors窗口赋值	
            
            v2f vert (appdata v)
            {
                v2f o;
                //第四步,如果要访问片元着色器中的多例化属性变量,需要使用此宏
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v,o);

                float3 center=float3(0,0,0);
                float4 worldPos=mul(unity_ObjectToWorld,float4(center,1));
                float3 viewDir=mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1));
                float3 normalDir=viewDir-center;
                normalDir.y=normalDir.y*_VerticalBillboarding;
                normalDir=normalize(normalDir);
                float3 upDir=abs(normalDir.y)>0.999?float3(0,0,1):float3(0,1,0);
                float3 rightDir=normalize(cross(normalDir,upDir));
                float3 centerOffs=v.vertex.xyz-center;
                float3 localPos=center+rightDir*centerOffs.x+upDir*centerOffs.y+normalDir*centerOffs.z;

                float2 map=_MapMessage.zw-_MapMessage.xy;

                float wind=1-tex2Dlod(_WindTex,float4(worldPos.x/map.x+_Time.x,worldPos.z/map.y,0,0)).b;
                float time=_Time.y*_TimeScale;
                float4 localWindVector=normalize(mul(unity_WorldToObject,_WindVector));
                localPos+=sin(time+wind*10)*cos(time*2/3+1+wind*10)*localWindVector.xyz*
                    clamp(v.uv.y-0.5,0,1);

                o.vertex = UnityObjectToClipPos(float4(localPos,1));
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
            	//第五步,如果要访问片元着色器中的多例化属性变量,需要使用此宏
                UNITY_SETUP_INSTANCE_ID(i);
                fixed4 col = tex2D(_MainTex, i.uv);
                clip(col.a-_CutOff);
                fixed4 finalColor=col*_Color;
                return finalColor;
            }
            ENDCG
        }
    }
}

着色器的重点就在vert部分以及如何使用宏实现GPUInstancing

广告牌部分

		 float3 center=float3(0,0,0);
		 float4 worldPos=mul(unity_ObjectToWorld,float4(center,1));
		 float3 viewDir=mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1));
		 float3 normalDir=viewDir-center;
		 normalDir.y=normalDir.y*_VerticalBillboarding;
		 normalDir=normalize(normalDir);
		 float3 upDir=abs(normalDir.y)>0.999?float3(0,0,1):float3(0,1,0);
		 float3 rightDir=normalize(cross(normalDir,upDir));
		 float3 centerOffs=v.vertex.xyz-center;
		 float3 localPos=center+rightDir*centerOffs.x+upDir*centerOffs.y+normalDir*centerOffs.z;

实现风的扰动效果

	    float wind=1-tex2Dlod(_WindTex,float4(worldPos.x/map.x+_Time.x,worldPos.z/map.y,0,0)).b;
        float time=_Time.y*_TimeScale;
        float4 localWindVector=normalize(mul(unity_WorldToObject,_WindVector));
        localPos+=sin(time+wind*10)*cos(time*2/3+1+wind*10)*localWindVector.xyz*
             clamp(v.uv.y-0.5,0,1);

至于实例化部分我也不是很明白,我也只是Copy一下书中的解释贴在代码里,那些宏我也没办法展开讲,如果想要深入了解推荐购买《Unity内建着色器源码剖析》,那里面第五章就有对Unity实例化的宏有详细的解析,只是仅仅想用这个技术只需要Copy就行了。
在这里插入图片描述
如果当材质使用就把Enable GPU Instancing 勾上,Unity会自动使用实例化,如上面用代码就不用在意。
风的图片资源可以在我参考的博主的GitHub链接上找。
这一篇压了大概两周,996的生活开始展现它枯燥的本质了,每天下班就想干些愉悦的事儿,一直拖拖拖…

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值