zoukankan      html  css  js  c++  java
  • Unity GPU Instance 无尽草地渲染

    问题

    项目中需要渲染一大片草地,最初始的实现是使用Unity自带的地形插件,直接在地形中绘制大量的草。这种方法会导致场景中的模型顶点数爆炸,一棵草的顶点数虽然不多,但是大量的草叠加到场景中时,场景中的模型顶点数过度,会造成卡顿。

    方案

    shader geometry

    有一个解决方案是,通过shader来绘制草,在GPU中绘制草的顶点,模拟风等动画。但是对于某些GPU,并不支持shader的顶点绘制。

    GPU Instance

    另外的一个方案是使用Unity 提供的 GPU Instance 方式,使用 Graphics.DrawMeshInstanced 接口传入模型,材质,位置等信息,然后由GPU批量渲染。对于手机游戏,有一定的限制,例如,单次的渲染的数量不能超过1024。具体可以参考https://docs.unity3d.com/2019.1/Documentation/Manual/GPUInstancing.html

    shader code

    shader中需要在 pass 中声明 #pragma multi_compile_instancing,在输入输出的结构体中声明宏 UNITY_VERTEX_INPUT_INSTANCE_ID

    需要在shader 中模拟风吹动的效果,调用GetWinWave计算风影响的顶点位移, 然后根据顶点的高度计算位移的大小。frag函数中根据高度,处理输出的颜色。

    Shader "Grass/Grass"
    {
        Properties
        {
            _MainTex ("Texture", 2D) = "white" {}
            _WindTex ("风贴图", 2D) = "white" {}
            [HDR]_Color ("颜色", Color) = (0,1,0,1)
            _Height("高度",Float)=1
            _WindSpeed("风速",Float)=2
            _WindSize("风尺寸",Float)=10
    
            _LowColor("草根部颜色",Color)= (1,1,1,1)
            _TopColor("草顶部颜色",Color) = (1,1,1,1)
            _MaxHight("草的最大高度",Float) = 3
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
            Cull off
    
            Pass
            {
                Tags { "LightMode"="ForwardBase" }
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                // make fog work
                #pragma multi_compile_fog
                #pragma multi_compile_instancing
    
                #include "UnityCG.cginc"
                #include "Lighting.cginc"
    
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    float3 uv : TEXCOORD0;
                    UNITY_FOG_COORDS(1)
    
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
                sampler2D _WindTex;
    
                float _Height;
                float _WindSpeed;
                float _WindSize;
                float4 _Color;
    
                float4 _LowColor;
                float4 _TopColor;
                float _MaxHight;
    
                float GetWindWave(float2 position,float height){
                    //以物体坐标点采样风的强度,
                    //风按照时间*风速移动,以高度不同获得略微有差异的数据
                    //移动值以高度不同进行减免,越低移动的越少.
                    //根据y值获得不同的
                    float4 p=tex2Dlod(_WindTex,float4(position/_WindSize+float2(_Time.x*_WindSpeed+height*.01,0),0.0,0.0)); 
                    return height * saturate(p.r-.2);
                }
    
                v2f vert (appdata v , uint instanceID : SV_InstanceID)
                {
                    v2f o;
                    
                    //GPU Instance 宏
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_TRANSFER_INSTANCE_ID(v, o);
    
                    //设置风的影响
                    float4 worldPos = mul(unity_ObjectToWorld,v.vertex);
                    float win = GetWindWave(worldPos.xz,v.vertex.y);
                    v.vertex.x += win;
                    v.vertex.y +=_Height+ win * 0.2;
                    
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv.xy = TRANSFORM_TEX(v.uv.xy, _MainTex);
                    o.uv.z = saturate( v.vertex.y / _MaxHight);
    
                    UNITY_TRANSFER_FOG(o,o.vertex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
                     UNITY_SETUP_INSTANCE_ID(i);
            
                    fixed4 col = tex2D(_MainTex, i.uv.xy)*_Color;
                    clip(col.a -0.6); //透明度剔除
    
                    fixed hightColFac = i.uv.z;
                    fixed3 higthCol = lerp(_LowColor,_TopColor,hightColFac);
                    col = fixed4(col.rgb*higthCol , col.a); 
    
                    // apply fog
                    UNITY_APPLY_FOG(i.fogCoord, col);
                    return col;
                }
                ENDCG
            }
        }
    }
    

    c# Code

    在C#代码中,需要在每个update 中调用 Graphics.DrawMeshInstanced(grassMesh, 0, grassMaterial, grassMaterix4X4,grassMaterix4X4.Length); ,每帧渲染一次草地。

    在调用这个接口前需要准备好相关的数据,模型,材质,和矩阵数组。矩阵中包括每棵草的位置旋转缩放信息,参考SetupGrassBuffers函数。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class DrawGrass : MonoBehaviour
    {
        public int grassCount = 100;
        public int flowerCount = 100;
    
    
        private Mesh grassMesh;
        private Material grassMaterial;
    
        private Mesh flowerMesh;
        private Material flowerMaterial;
    
        public Transform grassContainer;
        public Transform flowerContainer;
    
        private GameObject grassGO=null;
        private GameObject flowerGo = null;
    
        public float xRange= 100f;
        public float zRange= 100f;
    
        public float minHightScale = 0.8f;
        public float maxHightScale = 1.5f;
    
        public float drawGrassHeight = 20f;
    
        public float limitHeight = 29f;
    
        public Bounds grassBounds;
    
        Matrix4x4[] grassMaterix4X4;
        Matrix4x4[] flowerMaterix4X4;
        Vector4[] positions;
    
        Vector3 selfPosition;
        private float maxHeight=0f;
    
        //private int curGrassCount=0, curFlowerCount=0;
    
        void Start(){
              Draw();
        }
        public void Draw(){
            
            if(grassGO == null){
                int childCount = grassContainer.childCount;
                int randomIndex = Random.Range(0,childCount);
                grassGO = grassContainer.GetChild(randomIndex).gameObject;
            }
            if(flowerGo == null){
                int childCount = flowerContainer.childCount;
                int randomIndex =Random.Range(0,childCount);
                flowerGo = flowerContainer.GetChild(randomIndex).gameObject;
            }
    
             grassMesh = grassGO.GetComponent<MeshFilter>().mesh;  
             grassMaterial = grassGO.GetComponent<MeshRenderer>().sharedMaterial;
             if(grassMesh == null || grassMaterial == null){
                 Debug.LogError("mesh or material is null");
                 return;
             }
    
            flowerMesh = flowerGo.GetComponent<MeshFilter>().mesh;  
            flowerMaterial = flowerGo.GetComponent<MeshRenderer>().sharedMaterial;
           
    
            selfPosition = transform.position;
            maxHeight =0;
            SetupGrassBuffers();
            SetupFlowerBuffers();
            maxHeight+=1.5f;
            grassBounds = new Bounds(new Vector3(selfPosition.x,maxHeight/2,selfPosition.z),new Vector3(xRange*2,maxHeight/2,zRange*2));
        }
        void Update()
        {
            Graphics.DrawMeshInstanced(grassMesh, 0, grassMaterial, grassMaterix4X4,grassMaterix4X4.Length);
    
            if(flowerCount>0){
               Graphics.DrawMeshInstanced(flowerMesh, 0, flowerMaterial, flowerMaterix4X4,flowerMaterix4X4.Length);
            }
    
        }
        // void OnDrawGizmos(){
        //     Gizmos.DrawCube(grassBounds.center,grassBounds.size);
        // }
    
        void SetupGrassBuffers()
        {
            if (grassCount < 1) grassCount = 1;
            List<Matrix4x4> matrixList = new List<Matrix4x4>();
           
            for (int i = 0; i < grassCount; i++)
            {
    
                float x = Random.Range(-xRange,xRange) + selfPosition.x;
                float z = Random.Range(-zRange,zRange) + selfPosition.z;
                float y = drawGrassHeight;//selfPosition.y;
    
                Vector3 randomPos=new Vector4(x, y, z, 1f);
                if(GetGround(ref randomPos)){
                    float rotateY = Random.Range(0,360);
                    float heightScale = Random.Range(minHightScale,maxHightScale);
                    if(randomPos.y > maxHeight){
                        maxHeight = randomPos.y;
                    }
                    matrixList.Add( Matrix4x4.TRS(randomPos, Quaternion.Euler(0F, rotateY, 0F), new Vector3(1,heightScale,1)));
                }
            }
            grassMaterix4X4 = matrixList.ToArray();
        }
    
        void SetupFlowerBuffers()
        {
            if (flowerCount < 1) {
               return;
            }
    
            List<Matrix4x4> matrixList = new List<Matrix4x4>();
    
            for (int i = 0; i < flowerCount; i++)
            {
                float x = Random.Range(-xRange,xRange) + selfPosition.x;
                float z = Random.Range(-zRange,zRange) + selfPosition.z;
                float y = drawGrassHeight;//selfPosition.y;
    
                Vector3 randomPos=new Vector4(x, y, z, 1f);
                if(GetGround(ref randomPos)){
                    float rotateY = Random.Range(0,360);
                    if(randomPos.y > maxHeight){
                        maxHeight = randomPos.y;
                    }
                   matrixList.Add( Matrix4x4.TRS(randomPos, Quaternion.Euler(0F, rotateY, 0F), Vector3.one) );
                }
            }
            flowerMaterix4X4 = matrixList.ToArray();
        }
    
    	RaycastHit[] hitArr = new RaycastHit[3];
        bool GetGround(ref Vector3 p)
    	{
    		Ray ray = new Ray(p, Vector3.down);
            int hitCount = Physics.RaycastNonAlloc(ray, hitArr, drawGrassHeight);
    		if (hitCount>0)
    		{
                hitCount = Mathf.Min(hitCount,hitArr.Length);
                float maxHight = float.MinValue;
                int index=-1;
                for(int i=0;i<hitCount;++i){
                    RaycastHit hit = hitArr[i];
    
                    if(hit.point.y > maxHight){
                        maxHight = hit.point.y;
                        index = i;
                    }
                }
                if(index >=0){
                    RaycastHit closeHit = hitArr[index];
                    
                    if (closeHit.collider.CompareTag("Terrain") || closeHit.collider.CompareTag("SkyGround"))
                    {
                        //如果命中地面,则使用命中后的位置.
                        p = closeHit.point;
                        if(p.y >= limitHeight){
                            return false;
                        }
                        return true;
                    }
                }
            
    		}
    		return false;
    	}
    }
    
    

    效果图


    About

    Author:superzhan
    Blog: http://www.superzhan.cn
    Github: https://github.com/superzhan

  • 相关阅读:
    刚下飞机——Alpha冲刺 总结随笔
    刚下飞机——Alpha冲刺Day10
    刚下飞机——Alpha冲刺Day9
    刚下飞机——Alpha冲刺Day8
    刚下飞机——Alpha冲刺Day7
    快乐就队——Beta冲刺(1/7)
    快乐就队——凡事预则立
    快乐就队——Alpha冲刺问题总结&事后诸葛亮
    快乐就队——换组记录
    快乐就队——Alpha冲刺总结
  • 原文地址:https://www.cnblogs.com/superzhan/p/11791749.html
Copyright © 2011-2022 走看看