UnityShader[4]几何着色器与可交互草地

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:
UnityShader[4]几何着色器与可交互草地_第1张图片

[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顶点连接成的线段。(网格效果)

UnityShader[4]几何着色器与可交互草地_第2张图片

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

[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,最后输出:
UnityShader[4]几何着色器与可交互草地_第3张图片

需要注意的是,如果先进行了顶点着色,即进入几何着色步骤时顶点已经转换到齐次裁剪空间下,会导致中心顶点计算错误,法线不匹配等问题。解决的方法就是将空间变换算法移动到中心顶点计算之后进行,如上述算法就是将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;

可以获得刺球效果:
UnityShader[4]几何着色器与可交互草地_第4张图片

生成大面积草地

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

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

[maxvertexcount(30)]
void geo(point v2g input[1], inout TriangleStream<g2f> outStream)
{
   
    const uint vertexCount = 12; // 顶点数量
    g2f o[vertexCount]

你可能感兴趣的:(着色器,unity,c#)