美文网首页
Unity Shader:几何着色器

Unity Shader:几何着色器

作者: Dragon_boy | 来源:发表于2020-09-05 20:40 被阅读0次

在我翻译过的OpenGL和实时渲染相关文章中,简要介绍过几何着色器,它的执行顺序位于细分曲面着色器、光栅化与片元着色器之间,有时不会使用细分曲面着色器,且常不表示固定阶段,所以简要来说,顶点着色器的输出到几何着色器,接着进行某些增减基本体的操作,然后进入片元着色器进行光照计算操作。

几何着色器本质上最常用的是增加基本体,可以用于生成粒子,毛发等。

基本的几何着色器

Unity中,和顶点与片元着色器一样,使用一个预编译指令来标明函数,且额外定义一个结构体用于变量的输入输出,供着色器之间通信。这里给出一个简单的几何着色器例子:

Shader "Unlit/SimpleGeometryShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2g
            {
                float vertex: SV_POSITION;
                float uv : TEXCOORD0;
            };

            struct g2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2g vert (appdata v)
            {
                v2g o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            [maxvertexcount(3)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
            {
                g2f o;
                for(int i = 0;i < 3; i++)
                {
                    o.vertex = UnityObjectToClipPos(IN[i].vertex);
                    UNITY_TRANSFER_FOG(o, o.vertex);
                    o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                    triStream.Append(o);
                }
                triStream.RestartStrip();
            }

            fixed4 frag (g2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

注意三个结构体:

  • appdata,将物体的属性传入顶点着色器。
  • v2g,将数据从顶点着色器传入几何着色器。
  • g2f,将数据从几何着色器传入片元着色器。

对于将顶点转换到裁剪空间,以及变换UV的操作,我们在几何着色器中执行了,实际上也可以在顶点着色器中执行,只是这么做的话,更灵活。

几何着色器讲解

对于几何着色器函数,这里复制一下:

            [maxvertexcount(3)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
            {
                g2f o;
                for(int i = 0;i < 3; i++)
                {
                    o.vertex = UnityObjectToClipPos(IN[i].vertex);
                    UNITY_TRANSFER_FOG(o, o.vertex);
                    o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                    triStream.Append(o);
                }
                triStream.RestartStrip();
            }

maxvertexcount代表几何着色器会增加的顶点的最大数量。由于我们在物体上实际添加一个三角形,所以这里设置为3.

接下来讲解几何着色器函数的参数:

  • triangle v2g IN[3]:这是有3个v2g元素的数组,每个元素与我们所编辑的三角形的一个顶点绑定。triangle标签表明几何着色器会将一个三角形作为输入。也可以用line(数组大小为2)或point(数组大小为1)。
  • inout TriangleStream<g2f> triStream:我们可以看到,该函数的返回值为空,所以实际上我们没有返回一个物体。几何着色器实际上将每个三角形添加到一个TriangleStream列表中,类型为g2f。如果想要输出线或点的话,可以用inout LineStream<g2f> lineStreaminout PointStream<g2f> pointStream

接着讲解函数体。首先我们定义一个g2f的结构体对象,我们会对其进行操作然后加入列表中。

然后,我们进行一个简单的循环,将每个输入的顶点添加到流中,创建三角形。因为数据要传入片元着色器,因此我们将顶点坐标转换到裁剪空间,并且按系数偏移UV。

最后,我们使用triStream.Append(o)将一个修改过的g2f结构体添加到三角形流中。在结束循环后,使用RestartStrip函数,这可以让流明白一个独立的三角形要在之后添加。这里并没有额外生成顶点等,一切保持原状。

挤出金字塔

现在我们扩展这个简单的几何着色器,我们从每个三角形上挤出一个金字塔的形状,即在三角形中心添加一个顶点,然后沿法线方向挤出面。


Shader "Unlit/SimpleGeometryShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ExtrusionFactor("Extrusion factor", float)=0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2g
            {
                float4 vertex: SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct g2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 color : COLOR;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _ExtrusionFactor;

            v2g vert (appdata v)
            {
                v2g o;
                o.vertex = v.vertex;
                o.uv = v.uv;
                o.normal = v.normal;
                return o;
            }

            [maxvertexcount(12)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
            {
                g2f o;
                // 重心
                float4 barycenter = (IN[0].vertex + IN[1].vertex + IN[2].vertex)/3;
                float3 normal = (IN[0].normal + IN[1].normal + IN[2].normal)/3;
                 // 构成金字塔的三个三角形
                for(int i = 0;i < 3; i++)
                {
                    // i=0:1;i=1:2;i=2:1;
                    // 即计算相邻顶点索引
                    int next = (i+1)%3;

                    o.vertex = UnityObjectToClipPos(IN[i].vertex);
                    UNITY_TRANSFER_FOG(o, o.vertex);
                    o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                    o.color = fixed4(0.0,0.0,0.0,1.0);
                    triStream.Append(o);

                    // 金字塔三角形顶尖顶点
                    o.vertex = UnityObjectToClipPos(barycenter + float4(normal, 0.0)*_ExtrusionFactor);
                    UNITY_TRANSFER_FOG(o, o.vertex);
                    o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                    o.color = fixed4(1.0,1.0,1.0,1.0);
                    triStream.Append(o);

                    o.vertex = UnityObjectToClipPos(IN[next].vertex);
                    UNITY_TRANSFER_FOG(o, o.vertex);
                    o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                    o.color = fixed4(0.0,0.0,0.0,1.0);
                    triStream.Append(o);
                }
                triStream.RestartStrip();

                // 组装最基本的三角形
                for(int i = 0;i < 3;i++)
                {
                    o.vertex = UnityObjectToClipPos(IN[i].vertex);
                    UNITY_TRANSFER_FOG(o, o.vertex);
                    o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                    o.color = fixed4(0.0,0.0,0.0,1.0);
                    triStream.Append(o);
                }
                triStream.RestartStrip();
            }

            fixed4 frag (g2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

在最开始,我们添加了_ExtrusionFactor属性,用于控制挤出的高度。接着开启Cull Off,关闭面消隐。

在几何着色器函数中,我们首先设置最大输出顶点数量,相当于生成4个三角形,即在每个三角形的基础上生成3个三角形来组成锥体。

对于挤出操作,我们需要每个三角形的中心作为法线,因此,我们通过对三角形各个顶点的坐标做平均计算来得到重心,然后同样通过平均计算来得到三角形的法线。

然后我们生成锥体,算法过程为:

对于每个三角形的点
    得到下一个点的索引
    在当前点的位置处增加一个顶点
    在三角形重心处添加一个顶点,然后沿沿面的法线挤出,乘等于`_ExtrusionFactor`
    在下一个点处添加一个顶点

这就是第一个循环所做的事情。第二个循环中,我们将最开始的三角形组装起来,然后就可以得到想要的三角锥了。

相关文章

网友评论

      本文标题:Unity Shader:几何着色器

      本文链接:https://www.haomeiwen.com/subject/pkvfektx.html