最近十一因为怕疫情问题,哪里也不去,就在家里窝着搞塞尔达(荒野之息和织梦岛)。
荒野之息这游戏有个特点就是野外漫山遍野的青草,这对于图形性能是个严格的考验,当然这漫山的草可不是我们通常用地形刷刷上去的那种,因为这个密密麻麻的数量级,不论是静态批处理还是动态批处理亦或者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 100Pass{Cull OffCGPROGRAM#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 100Pass{Cull OffCGPROGRAM#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贴图效果,稍微生动一些。