GeometryShader执行顺序在顶点着色器之后,片元着色器以前。GeometryShader以一个/多个顶点组成的图元为输入,开发人员可以修改/添加顶点,修改为完全不同的网格,得到更多好看的效果。
缺点:并行困难,对移动端不友好,需要ShaderModel4.0以上
定义一个几何着色器,首先需要在声明模块添加几何着色器的声明;添加顶点着色器向几何着色器输出的结构体;修改ShaderModel版本为4.0以上

#pragma vertex vert
#pragma geometry geo
#pragma fragment frag#include "UnityCG.cginc"
#pragma target 5.0struct appdata
{float4 vertex : POSITION;float2 uv : TEXCOORD0;
};struct v2g
{float4 vertex : POSITION;float2 uv : TEXCOORD0;
};struct g2f
{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;
};

然后编写geometryShader主体:

[maxvertexcount(3)] // 最多调用3个顶点
// 输入:point / line / lineadj / triangle / triangleadj
// 输出:LineStream / PointStream / TriangleStream
void geo(triangle v2g input[3], inout PointStream<g2f> outStream)
{g2f o;o.vertex = input[1].vertex;o.uv = input[1].uv;outStream.Append(o);
}
  • [maxvertexcount(value)] 代表告诉Shader该几何着色器最多单次调用多少个顶点
  • triangle v2g input[3] 参数代表以一个三角形图元为单位进行输入,包含3个顶点;
  • inout PointStream outStream 参数代表以一个点(PointStream)为单位进行输出;
  • outStream.Append(o) 代表将o点添加到outStream中;

可以看到几何着色器是以流为单位进行输入输出的,输入和输出关键字的区别会让流的解析发生改变,例如输出选择了PointStream,该类型的流会认为给定的数据中包含一个顶点,然后进行解析,将这个顶点输出;而选择了TriangStream则会认为给定的数据包含三个顶点,进行解析时会将三个顶点合成为一个三角形输出
上述代码:输入一个三角形,输出该三角形中的2号顶点(数组中顶点编号0,1,2代表三角形顶点编号1,2,3)。可以得到一个点阵Shader:

[maxvertexcount(3)]
void geo(triangle v2g input[3], inout LineStream<g2f> outStream)
{g2f o;for(int i=0; i<2; i++){o.vertex = input[i].vertex;o.uv = input[i].uv;outStream.Append(o);}
}

上述代码:输入一个三角形,输出该三角形的0顶点、1顶点连接成的线段。(网格效果)

几何着色器还可以根据已有顶点生成新的顶点并构建图形,达到一些其他效果。此处尝试给每个三角形生成中心点:

[maxvertexcount(9)]
void geo(triangle v2g input[3], inout TriangleStream<g2f> outStream)
{g2f o;// 获取中心顶点float3 centerPos = (input[0].vertex + input[1].vertex + input[2].vertex) / 3;float2 centerUV = (input[0].uv + input[1].uv + input[2].uv) / 3;for(uint i=0; i<3; i++){o.vertex = UnityObjectToClipPos(input[i].vertex);o.uv = input[i].uv;outStream.Append(o);uint j = (i + 1) % 3;o.vertex = UnityObjectToClipPos(input[j].vertex);o.uv = input[j].uv;outStream.Append(o);o.vertex = UnityObjectToClipPos(float4(centerPos, 1.0));o.uv = centerUV;outStream.Append(o);// 重置剥outStream.RestartStrip();}
}

此时要适当提高控制的顶点数(9个,因为会输出3个三角面)。算法计算得出中心点在模型空间中的位置、uv等参数,将三个顶点(0号、1号、中心点)合成一个三角面添加进入outStream,直到将中心点分割得到的三个三角面均添加进入outStream,最后输出:

需要注意的是,如果先进行了顶点着色,即进入几何着色步骤时顶点已经转换到齐次裁剪空间下,会导致中心顶点计算错误,法线不匹配等问题。解决的方法就是将空间变换算法移动到中心顶点计算之后进行,如上述算法就是将UnityObjectToClipPos写到中心顶点计算完成之后(VertexShader只进行了数据转移操作)。
之后让中心点根据自身法线进行外扩,获取法线方向,然后对中心点坐标进行移动:

float3 edgeA = input[1].vertex - input[0].vertex;
float3 edgeB = input[2].vertex - input[0].vertex;
float3 normal = normalize(cross(edgeA, edgeB));
// 中心点向外挤出
centerPos += normal.xyz * _Length;

可以获得刺球效果:

生成大面积草地

几何着色器可用于生成网格,如果在其中增加一些随机数,就能让网格获得不同的旋转角度、弯曲程度、高度、摆动速度…足够生成一片随机的草地,而且因为没有使用预设的网格/实例,性能也变得可观。
参考:https://zhuanlan.zhihu.com/p/29632347
首先利用CPU生成大量随机位置,然后GPU在其上生成网格,渲染为草。
草的网格:

偶数顶点在左侧、奇数顶点在右侧,方便遍历和计算uv。
Shader需要根据给定顶点产生多个上述网格:

[maxvertexcount(30)]
void geo(point v2g input[1], inout TriangleStream<g2f> outStream)
{const uint vertexCount = 12; // 顶点数量g2f o[vertexCount];float currentVertexHeight = 0; // 当前顶点距离生成位置的高度float currentV = 0; // 当前顶点在uv中的v坐标float offsetV = 1.0 / (vertexCount / 2.0 - 1.0); // uv中v轴向相邻顶点之间的v值差距float4 root = input[0].vertex; // 定义草起始点for(uint i = 0; i < vertexCount; i++){o[i].vertex = float4(0.0, 0.0, 0.0, 1.0);o[i].normal = float3(0.0, 0.0, 1.0);o[i].uv = float2(0.0, 0.0);if(fmod(i, 2) == 0) // 处理偶数号顶点{o[i].vertex = float4(root.x - _Width, root.y + currentVertexHeight, root.z, 1);o[i].uv = float2(0, currentV);}else // 处理奇数号顶点{o[i].vertex = float4(root.x + _Width, root.y + currentVertexHeight, root.z, 1);o[i].uv = float2(1, currentV);// 抬升uv和坐标currentV += offsetV;currentVertexHeight += currentV * _Height;}o[i].vertex = UnityObjectToClipPos(o[i].vertex);}for(uint j = 0; j < vertexCount-2; j++){outStream.Append(o[j]);outStream.Append(o[j+2]);outStream.Append(o[j+1]);}
}

技术要点:

  • 以point为输入,即获取网格的所有顶点,在其上进行操作
  • 根据上个顶点的uv、位置,增量推导出下一个顶点的uv、位置

此时可以根据给定的网格,每个顶点生成一个草网格

然后需要对生成的草进行随机处理,如宽高、角度、摆动等

// 对草的宽高进行随机增减(基于平面坐标xz)
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
_Width += random/50.0;
_Height += random/5.0;

进行渲染:

SubShader
{Tags { "RenderType"="Transparent" "Queue"="AlphaTest" "IgnoreProjector" = "True"}Pass{Cull offTags {"LightMode"="ForwardBase"}AlphaToMask On...#include "UnityLightingCommon.cginc"fixed4 frag (g2f i) : SV_Target{half lDirWS = _WorldSpaceLightPos0.xyz;half hDirWS = normalize(lDirWS + (_WorldSpaceCameraPos.xyz - i.posWS));fixed3 diffuse = saturate(dot(i.normal, lDirWS)) * _LightColor0;fixed3 specular = pow(saturate(dot(i.normal, hDirWS)), _PhongPow) * _LightColor0;fixed4 var_MainTex = tex2D(_MainTex, i.uv);fixed3 finalRGB = (diffuse + specular) * var_MainTex.rgb;fixed alpha = var_MainTex.a;return fixed4(finalRGB, alpha);}
}


之后就需要生成草地网格,有两种生成方式:**按照给定高度图生成地形与草地、直接按照给定的模型生成覆于其上的草地。**此处使用第二种方式。
首先根据灰度图生成一个错落有致的网格:

并且根据给定贴图生成顶点色,rgb通道规定草地的颜色,a通道规定草的高度:


具体代码:

public Texture2D heightMap;
public Texture2D ColorMap;
public float terrainHeight;
public int terrainSize = 250;
public Material terrainMat;private void GenerateTerrain()
{List<Vector3> verts = new List<Vector3>();List<int> tris = new List<int>();for(int i=0;i<terrainSize;i++){for(int j=0;j<terrainSize;j++){// 添加顶点verts.Add(new Vector3(i, heightMap.GetPixel(i, j).grayscale * terrainHeight, j));colors.Add(ColorMap.GetPixel(i,j));if(i == 0 || j == 0) continue;// 添加三角形tris.Add(terrainSize * i + j);tris.Add(terrainSize * i + j - 1);tris.Add(terrainSize * (i - 1) + j - 1);tris.Add(terrainSize * (i - 1) + j - 1);tris.Add(terrainSize * (i - 1) + j);tris.Add(terrainSize * i + j);}}// 地形游戏对象GameObject plane = new GameObject("TerrainPlane");plane.AddComponent<MeshFilter>();MeshRenderer renderer = plane.AddComponent<MeshRenderer>(); // 渲染器renderer.material = terrainMat;// 地形网格Mesh terrainMesh = new Mesh();terrainMesh.vertices = verts.ToArray();terrainMesh.triangles = tris.ToArray();terrainMesh.RecalculateNormals();terrainMesh.colors = colors.ToArray();plane.GetComponent<MeshFilter>().mesh = terrainMesh;verts.Clear();
}

生成地形(Terrain)网格后(或从外部导入网格也可以,保证网格的顶点色符合需求即可),需要给Terrain网格添加脚本在其上生成草坪(Grass)网格,也就是在Terrain网格上生成随机顶点构成新的Grass网格,经由Grass网格上的几何着色器生成草地。
对于Terrain网格上的每个三角形,都可以生成0个或多个顶点,以便适配网格密度小/大的不同情况。设三角形的三个顶点为ABC,首先记录ABC三点的坐标,将B点和C点的坐标位置改为从A点出发到B/C的偏移量(这样方便获取AB、AC的方向以便生成随机点)

然后以A为起点,加上[0,1]随机数与AB、AC偏移量的乘积就能得到三角形附近的随机点(大致)。

具体代码:

public Material grassMat;
public Texture2D ColorMap;private List<Vector3> verts = new List<Vector3>(); // 顶点集
private List<Color> colors = new List<Color>(); // 顶点颜色集
private List<Vector3> norm = new List<Vector3>(); // 顶点法线集
private Vector3[] vertices;
private Vector3[] normals;
private Color[] col;
private int[] tris;
private Random random;
void Start()
{random = new Random();Mesh mesh = GetComponent<MeshFilter>().sharedMesh;vertices = mesh.vertices;normals = mesh.normals;tris = mesh.triangles;col = mesh.colors;GenerateField(1); // 1为期望在生成草的草单元内生成1个草
}private void GenerateField(int patchGrassCount)
{List<int> indices = new List<int>();for(int i=0; i<65000; i++){indices.Add(i);}// 生成草单元// 此处因为网格比较密集,所以让每四个三角形生成一个草单元,也就是每隔12个顶点for(int x = 0; x < tris.Length; x+=12) {GenerateGrass(x, patchGrassCount);}GameObject grass;MeshFilter filter;MeshRenderer renderer;Mesh mesh;// 顶点数过多时需要拆分部分顶点到新建的草网格while(verts.Count > 65000){mesh = new Mesh();mesh.vertices = verts.GetRange(0, 65000).ToArray(); // 设定顶点mesh.SetIndices(indices.GetRange(0, 65000).ToArray(), MeshTopology.Points, 0); // 设定点间关系mesh.colors = colors.GetRange(0, 65000).ToArray();mesh.normals = norm.GetRange(0, 65000).ToArray();// 生成网格grass = new GameObject("Grass");filter = grass.AddComponent<MeshFilter>();renderer = grass.AddComponent<MeshRenderer>();renderer.material = grassMat;filter.mesh = mesh;verts.RemoveRange(0, 65000);}// 顶点数小于65000则正常生成mesh = new Mesh();mesh.vertices = verts.ToArray();mesh.SetIndices(indices.GetRange(0, verts.Count).ToArray(), MeshTopology.Points, 0);mesh.colors = colors.GetRange(0, verts.Count).ToArray();mesh.normals = norm.GetRange(0, verts.Count).ToArray();grass = new GameObject("Grass");filter = grass.AddComponent<MeshFilter>();renderer = grass.AddComponent<MeshRenderer>();renderer.sharedMaterial = grassMat;filter.mesh = mesh;
}

草单元生成函数:

// index 草单元起始点编号
// count 草单元内草数量
private void GenerateGrass(int index, int count)
{Vector3 pointA = vertices[tris[index]];Vector3 pointB = vertices[tris[index+1]] - pointA;Vector3 pointC = vertices[tris[index+2]] - pointA;Color colA = col[tris[index]];Color colB = col[tris[index+1]] - colA;Color colC = col[tris[index+2]] - colA;Vector3 normalA = normals[tris[index]];Vector3 normalB = normals[tris[index+1]] - normalA;Vector3 normalC = normals[tris[index+2]] - normalA;for(var i=0; i<count; i++){// nextDouble()用于生成介于0~1之间的double值,需要使用头文件using Random = System.Random;float randomAB = (float)random.NextDouble();float randomAC = (float)random.NextDouble();Vector3 randomNormalAB = randomAB * normalB;Vector3 randomNormalAC = randomAC * normalC;Vector3 normal = normalA + randomNormalAB + randomNormalAC;Vector3 randomVertAB = randomAB * pointB;Vector3 randomVertAC = randomAC * pointC;Color randomColAB = randomAB * colB;Color randomColAC = randomAC * colC;Color col = colA + randomColAB + randomColAC;// 生成单元内随机坐标Vector3 pos = pointA + randomVertAB + randomVertAC;if (col.a <= 0.2f) continue; // a值太小则舍弃,节省顶点数verts.Add(pos);colors.Add(col);norm.Add(normal);}
}

在给定网格上生成了很多顶点作为草坪网格:

为草坪网格添加草地着色器即可,之后进行草地优化。
草地会随着风进行规律性摆动,所以在草地的动画方面,需要营造一种个体上随机,整体上一致的动画效果。首先需要根据顶点在平面中的位置生成具有规律的系数:

// 系数(基于平面坐标xz)
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
// 对草的宽高进行增减
_Width = (_Width + random / 50.0) * input[0].color.a;
_Height = (_Height + random / 5.0) * input[0].color.a;

三角函数能够很好的限制系数范围并带来规律性。将其用于草地摆动就可以得到整体上一致的摆动效果。

float2 wind = float2(sin(_Time.x * UNITY_PI * 5), cos(_Time.x * UNITY_PI * 5));
wind.x += sin(_Time.y + root.x / _WindRoute); // 风周期
wind.y += cos(_Time.y + root.z / _WindRoute);
wind *= random;

此外除了规律性的摆动,还需要个体上的摇曳效果,摇曳效果往往是快速的小角度的运动:

// 草摆动
float lerpEffect = (sin(_Time.y + random));
float left = wind * (1.0 - _OscInt);
float right = wind * (1.0 + _OscInt);
wind += lerp(left, right, lerpEffect) * _Height * 0.3;float randomAngle = lerp(-UNITY_PI, UNITY_PI, random); // 随机角度
float2 randomWindDir = float2(sin(randomAngle), cos(randomAngle)); // 随机风向
wind += randomWindDir * random;

将顶点动画效果加入草,另外,风对草中比较高的部位影响更大,所以需要使用windEffect变量判断当前顶点的高度:

float windEffect = 0.0;o[i].vertex.xz += wind.xy * windEffect;
o[i].vertex.y -= wind * windEffect * 0.2;if(fmod(i, 2) == 1)    windEffect += offsetV;

完毕之后对处理完成的顶点进行输出:

o[i].vertex = UnityObjectToClipPos(o[i].vertex);
o[i].normal = UnityObjectToWorldNormal(o[i].normal);
o[i].posWS = mul(unity_ObjectToWorld, o[i].vertex);
// 顶点色分层处理 用于草颜色
o[i].color = ((i > 7) ? input[0].color : input[0].color - 0.15) - random / 10; ...for(uint j = 0; j < vertexCount-2; j++)
{outStream.Append(o[j]);outStream.Append(o[j+2]);outStream.Append(o[j+1]);
}

此处草渲染只传递一张透明度贴图控制草形状,草颜色由网格顶点色产生:
这种方法渲染的草只会占用很少的DrawCall。

同时为了能给草地提供更多不同明度的颜色,这里将顶点的法线也传递给草地,产生不同的受光效果。


草地交互

踩踏草地时,部分草地会被压扁,附近的草地会发生偏移。所以在Grass游戏对象上挂载脚本控制草地受到碰撞时的反应。
这里为草地添加了Box碰撞体并勾选isTrigger,需要引发交互的物体添加Sphere碰撞体(是不是触发器没有关系)并添加刚体。
当交互物体进入草地碰撞体后,传递自身坐标到草地原点的相对位置进入材质。

[ExecuteAlways]
public class GrassReaction : MonoBehaviour
{private Vector3 pos;private Material material;private int ModelPosPropID;private void Start(){material = GetComponent<MeshRenderer>().sharedMaterial;ModelPosPropID = Shader.PropertyToID("_ModelPos");}private void OnTriggerStay(Collider other) {if(other.tag == "Player"){pos = other.transform.position;pos -= transform.position;material.SetVector(ModelPosPropID, new Vector4(pos.x, pos.y, pos.z, 0.0f));}
}

在着色器中,根据得到的坐标,在每个草进行渲染时读取该坐标并计算距离,小于某个范围则受到影响,距离越近草的宽高越小(这里通过控制顶点色a通道值控制草的宽高)。在此范围内距离越远的草受到偏转的影响越大

float4 _ModelPos;
float _ModelWidth;// 距离
float3 distance = root.xyz - _ModelPos.xyz;// 距离衰减
float drag = sqrt((distance.x * distance.x) + (distance.y * distance.y) + (distance.z * distance.z)) / _ModelWidth;
drag = drag < -1 ? -1 : drag;
drag = drag > 1 ? 1 : drag;
input[0].color.a = min(input[0].color.a,saturate(drag));// 对草的宽高进行增减
_Width = (_Width + random / 50.0) * input[0].color.a;
_Height = (_Height + random / 5.0) * input[0].color.a;// 风力计算完成后
o[i].vertex.xz += (1.0 - drag) * distance * windEffect;


草地还有很多可拓展性,如:
将草地交互点进行记录,让草地延迟恢复,这项功能类似于雪地的延迟恢复;
在生成草地网格阶段写一个画笔工具,实时在网格上绘制顶点和顶点色,控制草的生成位置、宽高和颜色;
根据模型的不同、游戏特效产生的风压对交互效果产生更进一步的控制;
引燃效果等。

UnityShader[4]几何着色器与可交互草地相关推荐

  1. UnityShader_使用几何着色器实现草坪效果(简易)

    本文参考自该文章 实现思路: 1.几何着色器实现草 (三角形),原理是将几何着色器的三角片元输入重新构造成草的样式 2.让草可以在各种地形朝上生长(切线空间) 3.曲面细分可以动态改变模型定点数,进而 ...

  2. 几何着色器与细分(镶嵌)着色器

    几何着色器与细分(镶嵌)着色器 GL细分着色器:https://www.cnblogs.com/zenny-chen/p/4280100.html GL几何着色器:https://blog.csdn. ...

  3. DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果

    DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果 原文:DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果 前言 上一 ...

  4. OpenGL Tessellation and Geometry Shaders镶嵌和几何着色器的实例

    OpenGL 镶嵌和几何着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <sb7.h> class tessllatedgst ...

  5. OpenGL 几何着色器细分的实例

    OpenGL 几何着色器细分 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <vmath.h> #include <cmath ...

  6. OpenGL 几何着色器剔除的实例

    OpenGL 几何着色器剔除 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <vmath.h> #include <objec ...

  7. OpenGL几何着色器

    OpenGL几何着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <stdio.h> #include "GL/glu ...

  8. OpenGL 几何着色器Geometry Shader

    OpenGL几何着色器Geometry Shader 几何着色器Geometry Shader简介 使用几何着色器 造几个房子 爆破物体 法向量可视化 几何着色器Geometry Shader简介 在 ...

  9. opengl 纹理贴到对应的位置_一步步学OpenGL(27) -《公告牌技术与几何着色器》

    教程 27 公告牌技术与几何着色器 原文: http://ogldev.atspace.co.uk/www/tutorial27/tutorial27.html CSDN完整版专栏: https:// ...

最新文章

  1. (节点分类)四大图数据集AIFB,MUTAG,BGS,AM数据集获取
  2. 如何为网站增加索引,促进网站SEO优化?
  3. r 字符串转化为数值_Lua 字符串处理
  4. Spring MVC--使用默认的servlet来响应静态文件
  5. #中delay函数_ECBM系列教程3:闪烁灯——delay函数的用法。
  6. html5离线保存需要联网吗,html5 离线存储
  7. 关于程序设计语言(贴吧里很有意思的一个帖子)
  8. python继承问题_深入浅析python继承问题
  9. JSON 序列化与反序列化:使用TypeReference 构建类型安全的异构容器
  10. 字段定义_逐浪CMS对用户注册字段正则的自由定义(注册字段必填)
  11. 计算机教案的自我分析怎么写,教案的自我分析怎么写.docx
  12. 比较牛逼的答题卡扫描算法
  13. 区块链开发入门书籍和文档
  14. nacos 服务日志_如何屏蔽Nacos日志输出?
  15. djangosave保存数据太慢_PaddlePaddle从入门到炼丹八——模型的保存与使用
  16. SCSI代码分析(5)SCSI驱动编程模式
  17. html中怎么让图片做背景透明背景图片,透明背景图片怎么做?
  18. 手把手教你理解SURF算法的全部过程
  19. java pos58打印_POS58票据热敏打印机,怎么用ESC/POS命令控制打印
  20. 智课雅思短语---三、unshakable duty

热门文章

  1. 电子元件学习——晶闸管(可控硅)
  2. 强大的刻盘软件 Ashampoo Burning Studio 9.20 中文版 + 注册码下载
  3. LoadRunner破解
  4. FineReport数据分析教程- 图表刷新接口
  5. USS-INT指令详解
  6. PLC笔记(二)西门子STEP 7编程软件安装
  7. shopyy独立站开发功能分析
  8. jaas权限认证的剖析
  9. KVM虚拟化管理工具之virtsh(三)
  10. 最新C# MD5 32位加密和16位加密