C for Graphic:Geometry Grass

      最近十一因为怕疫情问题,哪里也不去,就在家里窝着搞塞尔达(荒野之息和织梦岛)。

   

      荒野之息这游戏有个特点就是野外漫山遍野的青草,这对于图形性能是个严格的考验,当然这漫山的草可不是我们通常用地形刷刷上去的那种,因为这个密密麻麻的数量级,不论是静态批处理还是动态批处理亦或者GPUInstancing全都不顶用,因为这三种操作必须经过渲染流程的应用阶段,而这个mesh数量级毫无疑问可以爆掉CPU和总线BUS了。

      不过我们可以通过geometry shader生成mesh,绕过应用阶段。

      我们以前就学过geometry shader,而且用geometry shader做过效果的,大家还记得吧(不记得或者没看过建议返回去看,有个概念)。我们通过geometry shader可以将网格的每个顶点都扩展成另外的几何体(之前我们扩展的立方体),那么我们我们将顶点扩展成为一个草体也是一样的原理。

      核心:将mesh的一个vertex扩展成一个grass mesh

      我们先创建一个mesh:

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

[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class RectangleMesh : MonoBehaviour
{
    public bool updated = false;
    public int CellCount = 50;
    public float CellLength = 10f;

    void Start()
    {
        updated = true;
    }

    void Update()
    {
        if (updated)
        {
            CreateMesh();
            updated = false;
        }
    }

    private void CreateMesh()
    {
        Mesh mesh = new Mesh();
        int cellCountAddOne = CellCount + 1;

        Vector3[] vertices = new Vector3[cellCountAddOne * cellCountAddOne];
        Vector3[] normals = new Vector3[cellCountAddOne * cellCountAddOne];
        int[] triangles = new int[CellCount * CellCount * 2 * 3];
        Vector2[] uvs = new Vector2[cellCountAddOne * cellCountAddOne];

        int triangleindex = 0;
        for (int x = 0; x < cellCountAddOne; x++)
        {
            for (int y = 0; y < cellCountAddOne; y++)
            {
                int index = x + cellCountAddOne * y;
                vertices[index] = new Vector3(x * CellLength, 0, y * CellLength);
                normals[index] = new Vector3(0, 1, 0);
                uvs[index] = new Vector2((float) x / (float) CellCount, (float) y / (float) CellCount);

                if (x < CellCount && y < CellCount)
                {
                    triangles[triangleindex] = x + y * cellCountAddOne;
                    triangles[triangleindex + 1] = x + (y + 1) * cellCountAddOne;
                    triangles[triangleindex + 2] = x + y * cellCountAddOne + 1;
                    triangles[triangleindex + 3] = x + y * cellCountAddOne + 1;
                    triangles[triangleindex + 4] = x + (y + 1) * cellCountAddOne;
                    triangles[triangleindex + 5] = x + (y + 1) * cellCountAddOne + 1;
                    triangleindex += 6;
                }
            }
        }

        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.triangles = triangles;
        mesh.uv = uvs;
        GetComponent<MeshFilter>().sharedMesh = mesh;
    }
}

         创建一个正方形的mesh(创建mesh的方法以前聊过,不理解的返回以前学习怎么创建拓扑网格),帖上拓扑结构:

      

        效果如图:

      

       接下来使用geometry shader将正方形网格的每一个顶点扩展出一个草型网格,草型网格拓扑结构图:

      

       所以我们根据草型网格在geom函数中生成一片草地:

Shader "GeomGrass/GeometryGrassShader"
{
    Properties
    {
        _MainColor ("Color", Color) = (1,1,1,1)
        _GrassWidth("Grass Bottom Width",Range(0,5)) = 1
        _GrassHeight("Grass Single Height",Range(0,10)) = 3
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Cull Off
            CGPROGRAM
            #pragma target 4.0
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            #include "UnityCG.cginc"

            struct app2vert
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct vert2geom
            {
                float4 vertex : POSITION;
                float3 normal : TEXCOORD0;
                float3 tangent : TEXCOORD1;
            };

            struct geom2frag
            {
                float4 vertex : SV_POSITION;
            };

            float4 _MainColor;
            float _GrassWidth;
            float _GrassHeight;

            vert2geom vert (app2vert v)
            {
                vert2geom o;
                o.vertex = v.vertex;
                o.normal = v.normal;
                o.tangent = v.tangent.xyz;
                return o;
            }

            geom2frag topoVert(float4 vertex)
            {
                geom2frag g2f;
                g2f.vertex = vertex;
                return g2f;     
            }

            void topoTri(float4 v0,float4 v1,float4 v2,inout TriangleStream<geom2frag> tss)
            {
                tss.Append(topoVert(v0));
                tss.Append(topoVert(v1));
                tss.Append(topoVert(v2));
            }
            //拓扑构建grass的网格
            void topoGrass(float4 vertex,float3 norm,float3 tan,inout TriangleStream<geom2frag> tss)
            {
                float4 vertex0 = UnityObjectToClipPos(vertex-float4(tan*_GrassWidth*0.5,0));
                float4 vertex1 = UnityObjectToClipPos(vertex+float4(tan*_GrassWidth*0.5,0));
                float4 vertex2 = UnityObjectToClipPos(vertex-float4(tan*_GrassWidth*0.4,0)+float4(norm*_GrassHeight,0));
                float4 vertex3 = UnityObjectToClipPos(vertex+float4(tan*_GrassWidth*0.4,0)+float4(norm*_GrassHeight,0));
                float4 vertex4 = UnityObjectToClipPos(vertex-float4(tan*_GrassWidth*0.25,0)+float4(norm*_GrassHeight*2,0));
                float4 vertex5 = UnityObjectToClipPos(vertex+float4(tan*_GrassWidth*0.25,0)+float4(norm*_GrassHeight*2,0));
                float4 vertex6 = UnityObjectToClipPos(vertex+float4(norm*_GrassHeight*3,0));

                topoTri(vertex0,vertex1,vertex3,tss);
                topoTri(vertex0,vertex3,vertex2,tss);
                topoTri(vertex2,vertex3,vertex5,tss);
                topoTri(vertex2,vertex5,vertex4,tss);
                topoTri(vertex4,vertex5,vertex6,tss);

                tss.RestartStrip();
            }

            [maxvertexcount(36)]
            void geom(triangle vert2geom vg[3],inout TriangleStream<geom2frag> tss)
            {
                for(int i=0;i<3;i++)
                {
                    float4 localvertex = vg[i].vertex;
                    float3 localnorm = normalize(vg[i].normal);
                    float3 localtan = normalize(vg[i].tangent);
                    topoGrass(localvertex,localnorm,localtan,tss);
                }
            }

            fixed4 frag (geom2frag i) : SV_Target
            {
                fixed4 col = _MainColor;
                return col;
            }
            ENDCG
        }
    }
}

        效果如下:

          干巴巴的效果,我们继续增加一点点“生气”,比如纹理、运动、随机等。

          首先用ps制作一个草的纹理图:

        

         然后给草型网格映射uv:

       

        同时现实中我们观察草地,草的生长大部分是随机的,所以我们可以绕normal轴随机旋转一下草的角度即可。

        同时草地也有自己的随风运动、使用顶点运动即可。

Shader "GeomGrass/LifeGeomGrassShader"
{
    Properties
    {
        _GrassTex("Grass Gradient Texture",2D) = "white" {}
        _GrassWidth("Grass Bottom Width",Range(0,5)) = 1
        _GrassHeight("Grass Single Height",Range(0,10)) = 3
        _GrassSliceHWid0("Grass Buttom0 Half Width Ratio",Range(0,0.5)) = 0.5
        _GrassSliceHWid1("Grass Buttom1 Half Width Ratio",Range(0,0.5)) = 0.4
        _GrassSliceHWid2("Grass Buttom2 Half Width Ratio",Range(0,0.5)) = 0.25
        _GrassWaveSpeed("Grass Wave Speed",Range(0,10)) = 1
        _GrassWavePower("Grass Wave Power",Range(1,5)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Cull Off
            CGPROGRAM
            #pragma target 4.0
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            #define PI 3.1415926

            #include "UnityCG.cginc"

            struct app2vert
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct vert2geom
            {
                float4 vertex : POSITION;
                float3 normal : TEXCOORD0;
                float3 tangent : TEXCOORD1;
            };

            struct geom2frag
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _GrassTex;

            float _GrassWidth;
            float _GrassHeight;

            float _GrassSliceHWid0;
            float _GrassSliceHWid1;
            float _GrassSliceHWid2;

            float _GrassWaveSpeed;
            float _GrassWavePower;

            //wave顶点动画
            //根据vertical权重(越根部权重越小,不会摆动,越顶部权重越大,摆动越大)
            float4 waveVertex(float4 vertex,float vertical)
            {
                float4 wavepos = float4(abs(sin(_Time.y*_GrassWaveSpeed))*_GrassWavePower*vertical,0,abs(cos(_Time.y*_GrassWaveSpeed))*_GrassWavePower*vertical,0);
                vertex+=wavepos;
                return vertex;
            }

            //随机数
            //随便写的,怎么改都无所谓
            float getRandom(float4 vertex)
            {
                float a = 125;
                float b = 557;
                float c = 9831;
                float d = 5412;
                float val = a*vertex.x+b*vertex.y+c*vertex.z+d*vertex.w;
                return val%PI;
            }

            //以O原点为基准绕轴旋转
            //先根据cvertex移动到O原点“附近”
            //再绕local法向量旋转
            //再平移到cvertex“附近”
            float4 rotateAroundAxis(float4 cvertex,float4 vertex,float3 axis,float rad)
            {
                float n1 = axis.x;
                float n2 = axis.y;
                float n3 = axis.z;
                float cosAngle = cos(rad);
                float sinAngle = sin(rad);
                float4x4 mataxis = float4x4(n1*n1 * (1 - cosAngle) + cosAngle, n1*n2*(1 - cosAngle) - n3*sinAngle, n1*n3*(1 - cosAngle) + n2*sinAngle, 0,
                    n1*n2*(1 - cosAngle) + n3*sinAngle, n2*n2 * (1 - cosAngle) + cosAngle, n2*n3*(1 - cosAngle) - n1*sinAngle, 0,
                    n1*n3*(1 - cosAngle) - n2*sinAngle, n2*n3*(1 - cosAngle) + n1*sinAngle, n3*n3 * (1 - cosAngle) + cosAngle, 0,
                    0, 0, 0, 1);
                float4x4 mattoO = float4x4(1,0,0,-cvertex.x,
                                            0,1,0,-cvertex.y,
                                            0,0,1,-cvertex.z,
                                            0,0,0,1);
                float4x4 mattoC = float4x4(1,0,0,cvertex.x,
                                            0,1,0,cvertex.y,
                                            0,0,1,cvertex.z,
                                            0,0,0,1);
                vertex = mul(mattoC,mul(mataxis,mul(mattoO,vertex)));
                return vertex;
            }

            vert2geom vert (app2vert v)
            {
                vert2geom o;
                o.vertex = v.vertex;
                o.normal = v.normal;
                o.tangent = v.tangent.xyz;
                return o;
            }

            geom2frag topoVert(float4 vertex,float2 uv)
            {
                geom2frag g2f;
                g2f.vertex = vertex;
                g2f.uv = uv;
                return g2f;     
            }

            void topoTri(float4 v0,float2 uv0,float4 v1,float2 uv1,float4 v2,float2 uv2,inout TriangleStream<geom2frag> tss)
            {
                tss.Append(topoVert(v0,uv0));
                tss.Append(topoVert(v1,uv1));
                tss.Append(topoVert(v2,uv2));
            }
            //拓扑构建grass的网格
            void topoGrass(float4 vertex,float3 norm,float3 tan,inout TriangleStream<geom2frag> tss)
            {
                float3 localaxis = norm;
                float4 localcenter = vertex;

                float4 localvertex0 = vertex-float4(tan*_GrassWidth*_GrassSliceHWid0,0);
                float4 localvertex1 = vertex+float4(tan*_GrassWidth*_GrassSliceHWid0,0);
                float4 localvertex2 = vertex-float4(tan*_GrassWidth*_GrassSliceHWid1,0)+float4(norm*_GrassHeight,0);
                float4 localvertex3 = vertex+float4(tan*_GrassWidth*_GrassSliceHWid1,0)+float4(norm*_GrassHeight,0);
                float4 localvertex4 = vertex-float4(tan*_GrassWidth*_GrassSliceHWid2,0)+float4(norm*_GrassHeight*2,0);
                float4 localvertex5 = vertex+float4(tan*_GrassWidth*_GrassSliceHWid2,0)+float4(norm*_GrassHeight*2,0);
                float4 localvertex6 = vertex+float4(norm*_GrassHeight*3,0);

                float4 vertex0 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex0,localaxis,getRandom(localvertex0))),0);
                float4 vertex1 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex1,localaxis,getRandom(localvertex0))),0);
                float4 vertex2 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex2,localaxis,getRandom(localvertex0))),0.33);
                float4 vertex3 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex3,localaxis,getRandom(localvertex0))),0.33);
                float4 vertex4 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex4,localaxis,getRandom(localvertex0))),0.67);
                float4 vertex5 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex5,localaxis,getRandom(localvertex0))),0.67);
                float4 vertex6 = waveVertex(UnityObjectToClipPos(rotateAroundAxis(localcenter,localvertex6,localaxis,getRandom(localvertex0))),1);

                float uvlen = 3*_GrassHeight;
                float uvhflen = 1.5*_GrassHeight;

                float2 uv0 = float2((uvhflen-_GrassSliceHWid0*_GrassWidth)/uvlen,0);
                float2 uv1 = float2((uvhflen+_GrassSliceHWid0*_GrassWidth)/uvlen,0);
                float2 uv2 = float2((uvhflen-_GrassSliceHWid1*_GrassWidth)/uvlen,0.33);
                float2 uv3 = float2((uvhflen+_GrassSliceHWid1*_GrassWidth)/uvlen,0.33);
                float2 uv4 = float2((uvhflen-_GrassSliceHWid2*_GrassWidth)/uvlen,0.67);
                float2 uv5 = float2((uvhflen+_GrassSliceHWid2*_GrassWidth)/uvlen,0.67);
                float2 uv6 = float2(0.5,1);

                topoTri(vertex0,uv0,vertex1,uv1,vertex3,uv3,tss);
                topoTri(vertex0,uv0,vertex3,uv3,vertex2,uv2,tss);
                topoTri(vertex2,uv2,vertex3,uv3,vertex5,uv5,tss);
                topoTri(vertex2,uv2,vertex5,uv5,vertex4,uv4,tss);
                topoTri(vertex4,uv4,vertex5,uv5,vertex6,uv6,tss);

                tss.RestartStrip();
            }

            [maxvertexcount(36)]
            void geom(triangle vert2geom vg[3],inout TriangleStream<geom2frag> tss)
            {
                for(int i=0;i<3;i++)
                {
                    float4 localvertex = vg[i].vertex;
                    float3 localnorm = normalize(vg[i].normal);
                    float3 localtan = normalize(vg[i].tangent);
                    topoGrass(localvertex,localnorm,localtan,tss);
                }
            }

            fixed4 frag (geom2frag i) : SV_Target
            {
                fixed4 col = tex2D(_GrassTex,i.uv);
                return col;
            }
            ENDCG
        }
    }
}

          效果如下:

          这样就添加了随机生成、摆动、uv贴图效果,稍微生动一些。

         ps:上面的shader建议不要直接使用,因为效率不高,只是写博客说明原理的示例。如果小伙伴有草地渲染的需求,建议修改,修改方向如下:

         1.使用noise纹理代替getRandom产生随机radian

         2.使用c#传入uniform vector[] uvs代替uv0-uv6的计算,节省GPU的tflpos,因为uvs是固定的

         3.使用c#传入uniform vector[] worldvertexoffset,然后根据vert中worldvertex和worldvertexoffset计算(简单的加运算)得到worldvertex0-worldvertex6,这样就避免了TopoGrass时候大量的矩阵浮点运算

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/108588720