GeometryShader执行顺序在顶点着色器之后,片元着色器以前。GeometryShader以一个/多个顶点组成的图元为输入,开发人员可以修改/添加顶点,修改为完全不同的网格,得到更多好看的效果。
缺点:并行困难,对移动端不友好,需要ShaderModel4.0以上。
定义一个几何着色器,首先需要在声明模块添加几何着色器的声明;添加顶点着色器向几何着色器输出的结构体;修改ShaderModel版本为4.0以上
#pragma vertex vert
#pragma geometry geo
#pragma fragment frag
#include "UnityCG.cginc"
#pragma target 5.0
struct 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]