U3DC.COM | 优三帝研究院

Menu

可编程管线Shader教程:顶点和片段着色器程序

这个教程将带你学习如何自定义一个Unity顶点和片段着色器,在此之前,你可以先参阅ShaderLab的基础篇:《Getting Started tutorial》,如果你想编写一个能与光线交互的shader,那么你可以参阅这篇文章:《Surface Shaders》

开始前我们先概述一下shader的框架:

Shader "MyShaderName" {
    Properties {
        // ... properties here ...
    }
    SubShader {
        // ... subshader for graphics hardware A ...
        Pass {
            // ... pass commands ...
        }
        // ... more passes if needed ...
    }
    SubShader {
        // ... subshader for graphics hardware B ...
    }
    // ... Optional fallback ...
    FallBack "VertexLit"
}



 在这个框架的结尾,我们看到了这个命令:FallBack “VertexLit”
Fallback命令在shader的最后被调用,它表示:如果当前的subshaders无法运行在用户的显卡上,它将指定一个shader执行。比如说:你编写一个normal-mapped shader,然而你的旧显卡并不支持,所以你只好使用命令fallback,让它回滚到内置的VertexLit shader。
shader的一些基础内建块可以参阅first shader tutorial,这里将有完整的介绍PropertiesSubShaders,和 Passes。
创建 SubShaders 的快速方法是使用在其他着色器中定义的passes。使用UsePass命令就可以实现shader代码的复用,例如你可以使用这样的命令来调用内建的Specular shader中名为”BASE”的pass:UsePass”Specular/BASE”.
当然,要让UsePass命令起作用,关键的一点是要给被调用的pass取名,在pass中你需要这样定义:Name”MyPassName”.

顶点和片段程序

在上一个教程中,我们说的pass调用只是单张纹理贴图的混合指令,这回我们要来说说如何在pass中使用顶点和片段程序。
顶点&片段程序也称作:可编程管线程序。当然,大部分函数在显卡硬件中是被写死的,叫做固定管线。比如使用顶点程序完全关闭标准的3d变形、关照和纹理坐标。同样,使用片段程序替换SetTexture命令中定义的纹理合并模式,因此不需要SetTexture命令。
编写顶点/片段程序需要大量关于3D变换,光照和坐标控件的知识,因为你不得不重写内置的函数,比如你将自定义OpenGL的固定功能,另一方面,比起内置的那些功能,你可以扩展的更多。

在ShaderLab开发中使用Cg/HLSL

shader通常使用Cg/HLSL程序语言编写,Cg和HLSL实际上是同一种语言(Cg语言是Microsoft和NVIDIA相互协作在标准硬件光照语言的语法和语义上达成了一致),所以我们可以交换使用这两种语言。

着色器代码写的着色器文本中嵌入"Cg 片段"。Cg 片段由Unity编辑器编译成低级着色器程序集,最终着色器将是包含在你的那些只包含此低级程序集或字节码的游戏数据文件中,当然这是特定于平台。当您在项目视图中选择shader时,检查面板有一个按钮用于显示编译着色器代码,可能会帮助帮助调试。Unity将自动编译所有相关的平台 (Direct3D 9、 OpenGL,Direct3D 11、 OpenGL ES 等等) 的 Cg 片段代码。请注意,由于 Cg 代码由编辑器编译,所以你不能在运行时创建 Cg 着色器。
总的来说,Cg片段放在Pass块中,就像这样:
Pass {
    // ... the usual pass state setup ...

    CGPROGRAM
    // compilation directives for this snippet, e.g.:
    #pragma vertex vert
    #pragma fragment frag

    // the Cg code itself

    ENDCG
    // ... the rest of pass setup ...
}
下面的示例演示完整的着色器,Cg 程序呈现对象法线作为颜色:
Shader "Tutorial/Display Normals" {
    SubShader {
        Pass {

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR0;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.color = v.normal * 0.5 + 0.5;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4 (i.color, 1);
            }
            ENDCG

        }
    }
}

测试结果:
69A1D4FC-7060-4DDB-A30B-B96F52262D86
在我们这个"Display Normals"的shader中并没有任何属性(Properties),除了Cg代码外,只包含了一个SubShader和一个Pass,让我们逐一分析下Cg代码:
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// ...
ENDCG


全部Cg 代码将在 CGPROGRAM 和 ENDCG 关键字之间编写。#pragma 将声明开始编译指令的语句。

其中#pragma vertex name 将告诉程序这里包含了一个顶点着色器

#pragma fragment name 将告诉程序这里包含了一个片段着色器

以下编译指令是只是纯 Cg 代码。它告诉程序包含了一个内置的 Cg 文件(文件里包含了我们需要的函数):

#include "UnityCG.cginc"

UnityCG.cginc 文件包含常用的声明和函数,这样我们的着色器可以保持一个比较SMALL的状态 (参见Shader include files页面的详细信息)。我们将从该文件使用 appdata_base 结构。当然,我们也可以直接在当前shader定义,这样就不用引入这个文件。

接下来,我们定义"vertex to fragment"结构 (这里命名为 v2f):v2f表示:顶点到片断程序的信息传递。这里我们传递的是位置和颜色的参数。顶点着色器将计算color并且输出给片段着色器。

我们继续通过定义顶点程序-vert函数。我们计算位置信息并且输出,输入法线作为color:o.color = v.normal * 0.5 +0.5;(这里为什么是*0.5+0.5呢?因为法线的值为-1到1,而color的值为0到1,为了让他们的值能规范匹配,否则会报错)。

接下来,我们定义一个片段程序-用于输出计算出来的颜色和1作为alpha component。

fixed4 frag (v2f i) : SV_Target
{
    return fixed4 (i.color, 1);
}

就这样,我们的shader就完成了,虽然很简单,但是对于可视化网格法线是非常有用的。

当然,这个shader并无法响应灯光,这也是有趣的地方。与灯光相关,可以参考Surface Shader。


在Cg代码中使用属性

当你在shader中定义属性,你可以给它们命名类似:_Color或者_MainTex。要使用它们,你就必须要定义一个变量和对应的名称及类型。Unity会自动设定具有名称与属性匹配的Cg变量。

下面是一个关于颜色调节纹理的完整shader,当然你同样可以轻易的在纹理合成器中调用,但这里主要是为了SHOW一下:如何在Cg中使用属性。

Shader "Tutorial/Textured Colored" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,0.5)
        _MainTex ("Texture", 2D) = "white" { }
    }
    SubShader {
        Pass {

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        fixed4 _Color;
        sampler2D _MainTex;

        struct v2f {
            float4 pos : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        float4 _MainTex_ST;

        v2f vert (appdata_base v)
        {
            v2f o;
            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
            o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 texcol = tex2D (_MainTex, i.uv);
            return texcol * _Color;
        }
        ENDCG

        }
    }
}

这个shader的结构跟之前的例子里的是一样样的。这里我们定义了两个属性,分别命名为:_Color和_MainTex.在Cg代码段中我们定义了相对应的变量:
fixed4 _Color;
sampler2D _MainTex;
可以参阅《Accessing Shader Properties in Cg》获取更多信息。
顶点着色器和片段着色器并不是想象中那么的难懂和神秘。顶点着色程序通过UnityCG.cginc的TRANSFORM_TEX宏来确定纹理的缩放和偏移是否正确,而片段着色程序只是采样纹理并乘以颜色属性。

总结(终于要结束了)

在这篇译文中,我们SHOW了如何自定义着色器,简单几步就写出了一个shader程序,例子虽然简单,但万变不离其宗,你依然能基于此写出更加复杂绚丽的shader程序,本篇可以充分帮助你利用unity来优化渲染。

翻译自官方教程:《Shaders: Vertex and Fragment Programs》

译者:优三帝同学 2016.02.29 原帖地址:https://www.u3dc.com/archives/2152

Unity学习QQ交流群(新):139457522 (加群备注:Unity)

打赏
— 于 共写了4172个字
— 文内使用到的标签:

《“可编程管线Shader教程:顶点和片段着色器程序”》 有 1 条评论

  1. 辛叙白说道:

    – – 帖子里是一堆超链? – – 貌似都点不了

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据