Shaders: vertex and fragment programs - Unity Shader Reference 番外2

Shaders: vertex and fragment programs

着色器:顶点与片断程序

本文档主要是对Unity官方手册的个人理解与总结(其实以翻译记录为主:>)
仅作为个人学习使用,不得作为商业用途,欢迎转载,并请注明出处。
文章中涉及到的操作都是基于Unity2018.2版本
参考链接:https://docs.unity3d.com/Manual/ShaderTut2.html

This tutorial will teach you the basics of how to write vertex and fragment programs in Unity shaders. For a basic introduction to ShaderLab see the Getting Started tutorial. If you want to write shaders that interact with lighting, read about Surface Shaders instead.
本教程将向您介绍如何在Unity着色器中编写顶点和片段程序的基础知识。对于ShaderLab的基本介绍,请参阅入门教程。如果你想要写与灯光交互的着色器,那就去阅读表面着色器吧。

Lets start with a small recap of the general structure of a shader:

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

Here at the end we introduce a new command: FallBack “VertexLit”. The Fallback command can be used at the end of the shader; it tells which shader should be used if no SubShaders from the current shader can run on user’s graphics hardware. The effect is the same as including all SubShaders from the fallback shader at the end. For example, if you were to write a fancy normal-mapped shader, then instead of writing a very basic non-normal-mapped subshader for old graphics cards you can just fallback to built-in VertexLit shader.
在这里,我们引入了一个新的命令:回退(FallBack) “VertexLit”。回退命令可以在着色器的末端使用;它告诉我们,如果当前着色器中没有子材质可以在用户的图形硬件上运行,那么应该使用哪个着色器。其效果在所有子着色器是一样的被包含在最后的回退着色器中。例如,如果您要编写一个漂亮的法线映射着色器,而不是为旧的图形卡编写一个非常基本的无法线映射的子着色器,您可以直接回退到内置的VertexLit着色器。

The basic building blocks of the shader are introduced in the first shader tutorial while the full documentation of Properties, SubShaders and Passes are also available.
着色器的基本构建块是在第一个着色器教程中引入的,而属性、SubShaders和Passes的完整文档也是可用的。

A quick way of building SubShaders is to use passes defined in other shaders. The command UsePass does just that, so you can reuse shader code in a neat fashion. As an example the following command uses the pass with the name “FORWARD” from the built-in Specular shader: UsePass “Specular/FORWARD”.
构建子着色器的一种快速方法是使用在其他着色器中定义的passes。UsePass的命令就是这样做的,所以您可以以一种整洁的方式重用着色器代码。作为一个例子,下面的命令使用来自内置的高光着色器的“FORWARD”这个名称:UsePass “Specular/FORWARD”.

In order for UsePass to work, a name must be given to the pass one wishes to use. The Name command inside the pass gives it a name: Name “MyPassName”.
为了让UsePass工作,必须给希望被使用的pass提供一个名称。pass内部的名称命令给它起了一个名字:Name “MyPassName”。

Vertex and fragment programs

We described a pass that used just a single texture combine instruction in the first tutorial. Now it is time to demonstrate how we can use vertex and fragment programs in our pass.
我们描述了在第一个教程中只使用一个纹理组合指令的pass。现在是演示如何在我们的pass中使用顶点和片段程序的时候了。

When you use vertex and fragment programs (the so called “programmable pipeline”), most of the hardcoded functionality (“fixed function pipeline”) in the graphics hardware is switched off. For example, using a vertex program turns off standard 3D transformations, lighting and texture coordinate generation completely. Similarly, using a fragment program replaces any texture combine modes that would be defined in SetTexture commands; thus SetTexture commands are not needed.
当您使用顶点和片段程序(所谓的“可编程管线”)时,图形硬件中的大多数硬编码功能(“固定管线”)都被关闭了。例如,使用顶点程序会完全关闭标准3D转换、光照和纹理坐标的生成。类似地,使用片段程序替换在SetTexture命令中定义的任何纹理组合模式;因此SetTexture不再需要。

Writing vertex/fragment programs requires a thorough knowledge of 3D transformations, lighting and coordinate spaces - because you have to rewrite the fixed functionality that is built into APIs like OpenGL yourself. On the other hand, you can do much more than what’s built in!
编写顶点/片断程序需要对3D转换、光照和坐标空间有深入的了解——因为你必须自己重写像内置在APIs中的固定管线,比如OpenGL中的。另一方面,你可以做的比内置的要多得多!

Using Cg/HLSL in ShaderLab

Shaders in ShaderLab are usually written in Cg/HLSL programming language. Cg and DX9-style HLSL are for all practical purposes one and the same language, so we’ll be using Cg and HLSL interchangeably (see this page for details).
ShaderLab的着色器通常是用Cg/HLSL编程语言编写的。Cg和DX9-style的HLSL是所有实际用途的一种和相同的语言,所以我们将会交替使用Cg和HLSL(请参阅这一页了解详细信息)。

Shader code is written by embedding “Cg/HLSL snippets” in the shader text. Snippets are compiled into low-level shader assembly by the Unity editor, and the final shader that is included in your game’s data files only contains this low-level assembly or bytecode, that is platform specific. When you select a shader in the Project View
, the Inspector has a button to show compiled shader code, which might help as a debugging aid. Unity automatically compiles Cg snippets for all relevant platforms (Direct3D 9, OpenGL, Direct3D 11, OpenGL ES and so on). Note that because Cg/HLSL code is compiled by the editor, you can’t create shaders from scripts
at runtime.
Shader代码是通过在着色器文本中嵌入“Cg/HLSL片段”来编写的。片段被Unity编辑器编译成低级着色汇编,而在您的游戏数据文件中包含的最终着色器只包含这个低级汇编或字节码,这是特定于平台的。当你在项目视图中选择一个着色器时,检查器有一个按钮来显示编译后的着色代码,这可能有助于调试帮助。Unity自动为所有相关平台(Direct3D 9、OpenGL、Direct3D 11、OpenGL ES等)编译Cg片段。注意,因为Cg/HLSL代码已被编译,你不能在运行时创建shader。

In general, snippets are placed inside Pass blocks. They look like this:
一般来说,代码片段被放置在Pass块中。它们是这样的:

Pass {
    // ... the usual pass state setup ...

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

    // the Cg/HLSL code itself

    ENDCG
    // ... the rest of pass setup ...
}

The following example demonstrates a complete shader that renders object normals as colors:
面的例子演示了一个完整的着色器,它将对象法线作为颜色渲染:

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 = UnityObjectToClipPos(v.vertex);
                o.color = v.normal * 0.5 + 0.5;
                return o;
            }

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

        }
    }
}

When applied on an object it will result in an image like this:
当应用到一个物体上时,它会产生这样的图像:
在这里插入图片描述
Our “Display Normals” shader does not have any properties, contains a single SubShader with a single Pass that is empty except for the Cg/HLSL code. Let’s dissect the code part by part:
我们的“显示法线”着色器没有任何属性,只包含一个带有一个通道的子着色器,通道中只有Cg/HLSL代码。让我们逐个分析代码:

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// ...
ENDCG

The whole snippet is written between CGPROGRAM and ENDCG keywords. At the start compilation directives are given as #pragma statements:
整个代码片段是在CGPROGRAM和ENDCG关键字之间编写的。在开始编译指令给出了#pragma语句:

  • #pragma vertex name tells that the code contains a vertex program in the given function (vert here). 代码在给定的函数中包含一个顶点程序。
  • #pragma fragment name tells that the code contains a fragment program in the given function (frag here). 代码在给定的函数中包含一个片断程序。

Following the compilation directives is just plain Cg/HLSL code. We start by including a built-in include file:
编译指令接下来的只是普通的Cg/HLSL代码。我们开始包括一个内置包括文件:

#include "UnityCG.cginc"

The UnityCG.cginc file contains commonly used declarations and functions so that the shaders can be kept smaller (see shader include files page for details). Here we’ll use appdata_base structure from that file. We could just define them directly in the shader and not include the file of course.
UnityCG.cginc文件包含常用的声明和函数,使着色器可以保持更小(详情见着色器包括文件页)。在这里,我们将使用来自该文件的appdata_base结构。我们可以直接在着色器中定义它们,当然这样就可以不包括文件。

Next we define a “vertex to fragment” structure (here named v2f) - what information is passed from the vertex to the fragment program. We pass the position and color parameters. The color will be computed in the vertex program and just output in the fragment program.
接下来,我们定义一个“顶点到片段”结构体(这里称为v2f)——从顶点到片段程序传递了什么信息。我们传递位置和颜色参数。颜色将在顶点程序中计算,并在片段程序中输出。

We proceed by defining the vertex program - vert function. Here we compute the position and output input normal as a color: o.color = v.normal * 0.5 + 0.5;
我们继续定义顶点程序-vert函数。这里我们计算位置和输出输入法线作为颜色: o.color = v.normal * 0.5 + 0.5;

Normal components are in –1…1 range, while colors are in 0…1 range, so we scale and bias the normal in the code above. Next we define a fragment program - frag function that just outputs the calculated color and 1 as the alpha component:
法线分量在-1 …1范围,而颜色在0…1范围,所以我们在上面的代码中缩放和偏置法线。接下来我们定义一个片段程序- frag函数,它只输出计算好的颜色和作为alpha分量的1:

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

That’s it, our shader is finished! Even this simple shader is very useful to visualize mesh normals.
就这样,我们的着色器完成了!即使是这个简单的着色器也非常有助于可视化网格法线。

Of course, this shader does not respond to lights at all, and that’s where things get a bit more interesting; read about Surface Shaders for details.
当然,这个着色器对光照完全没有反应,这就是事情变得更有趣的地方;阅读关于表面着色器的细节。

Using shader properties in Cg/HLSL code

When you define properties in the shader, you give them a name like _Color or _MainTex. To use them in Cg/HLSL you just have to define a variable of a matching name and type. See properties in shader programs page for details.
当您在着色器中定义属性时,您会给它们一个名称,比如_Color或_MainTex。要在Cg/HLSL中使用它们,只需定义一个匹配名称和类型的变量。有关详细信息,请参阅着色程序页面中的属性。

Here is a complete shader that displays a texture modulated by a color. Of course, you could easily do the same in a texture combiner call, but the point here is just to show how to use properties in Cg:
这是一个完整的着色器,显示由颜色调整的纹理。当然,你可以在一个纹理组合器调用中做同样的事情,但是这里的重点是展示如何在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 = UnityObjectToClipPos(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

        }
    }
}

The structure of this shader is the same as in the previous example. Here we define two properties, namely _Color and _MainTex. Inside Cg/HLSL code we define corresponding variables:
这个着色器的结构体与前面的示例相同。这里我们定义了两个属性,即_Color和_MainTex。在Cg/HLSL代码中,我们定义了相应的变量:

fixed4 _Color;
sampler2D _MainTex;

See Accessing Shader Properties in Cg/HLSL for more information.
有关更多信息,请参阅Cg/HLSL中访问着色器属性。

The vertex and fragment programs here don’t do anything fancy; vertex program uses the TRANSFORM_TEX macro from UnityCG.cginc to make sure texture scale and offset is applied correctly, and fragment program just samples the texture and multiplies by the color property.
这里的顶点和片段程序没有任何花哨的功能;顶点程序使用UnityCG.cginc中的TRANSFORM_TEX宏来确保纹理缩放和偏移的应用是正确的,而片段程序只是采样纹理并乘以颜色属性。

Summary

We have shown how custom shader programs can be written in a few easy steps. While the examples shown here are very simple, there’s nothing preventing you to write arbitrarily complex shader programs! This can help you to take the full advantage of Unity and achieve optimal rendering results.
我们已经展示了如何在几个简单的步骤中编写自定义着色器程序。虽然这里展示的例子非常简单,但没有什么可以阻止你编写任意复杂的着色程序!这可以帮助您充分利用Unity,实现最佳的呈现结果。

The complete ShaderLab reference manual is here, and more examples in vertex and fragment shader examples page. We also have a forum for shaders at forum.unity3d.com so go there to get help with your shaders! Happy programming, and enjoy the power of Unity and ShaderLab.
完整的ShaderLab参考手册在这里,更多的例子在顶点和片断着色器例子页。我们也有一个论坛的着色器在forum.unity3d.com, 所以去那里得到帮助你的着色器!快乐编程,享受Unity和ShaderLab的力量。