Vertex and fragment shader examples 顶点与片断着色器示例

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

This page contains vertex and fragment program examples. For a basic introduction to shaders, see the shader tutorials: Part 1 and Part 2. For an easy way of writing regular material shaders, see Surface Shaders。
此页面包含顶点和片段程序示例。有关着色器的基本介绍,请参阅着色器教程番外篇1,2。要编写常规材质着色器的简单方法,请参阅Surface Shaders。
You can download the examples shown below as a zipped Unity project.

Setting up the scene
If you are not familiar with Unity’s Scene View, Hierarchy View, Project View and Inspector, now would be a good time to read the first few sections from the manual, starting with Unity Basics.
The first step is to create some objects which you will use to test your shaders. Select Game Object > 3D Object > Capsule in the main menu. Then position the camera so it shows the capsule. Double-click the Capsule in the Hierarchy to focus the scene view on it, then select the Main Camera object and click Game object > Align with View from the main menu.
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7
Create a new Material by selecting Create > Material from the menu in the Project View. A new material called New Material will appear in the Project View.
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Creating a shader
Now create a new Shader asset in a similar way. Select Create > Shader > Unlit Shader from the menu in the Project View. This creates a basic shader that just displays a texture without any lighting.
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7
Other entries in the Create > Shader menu create barebone shaders or other types, for example a basic surface shader.

Linking the mesh, material and shader

Make the material use the shader via the material’s inspector, or just drag the shader asset over the material asset in the Project View. The material inspector will display a white sphere when it uses this shader.
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7
Now drag the material onto your mesh object in either the Scene or the Hierarchy views. Alternatively, select the object, and in the inspector make it use the material in the Mesh Renderer component’s Materials slot.
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7
With these things set up, you can now begin looking at the shader code, and you will see the results of your changes to the shader on the capsule in the Scene View.

Main parts of the shader

To begin examining the code of the shader, double-click the shader asset in the Project View. The shader code will open in your script editor (MonoDevelop or Visual Studio).

The shader starts off with this code:

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

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"

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

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

This initial shader does not look very simple! But don’t worry, we will go over each part step-by-step.

Let’s see the main parts of our simple shader.

Shader

The Shader command contains a string with the name of the shader. You can use forward slash characters “/” to place your shader in sub-menus when selecting your shader in the Material inspector.
Shader命令包含一个带有着色器名称的字符串。在材质检查器中选择着色器时,可以使用斜杠字符“/”将着色器放在子菜单中。

Properties

The Properties block contains shader variables (textures, colors etc.) that will be saved as part of the Material, and displayed in the material inspector. In our unlit shader template, there is a single texture property declared.
属性块包含着色器变量(纹理、颜色等),这些变量将作为材质的一部分保存,并显示在材质检查器中。在我们的非光照着色模板中,有一个纹理属性被声明。

SubShader

A Shader can contain one or more SubShaders, which are primarily used to implement shaders for different GPU capabilities. In this tutorial we’re not much concerned with that, so all our shaders will contain just one SubShader.
一个着色器可以包含一个或多个子着色器,这些子着色器主要用于实现不同GPU性能的着色器。在本教程中,我们不太关心这个,所以所有的着色器将只包含一个SubShader。

Pass

Each SubShader is composed of a number of passes, and each Pass represents an execution of the vertex and fragment code for the same object rendered with the material of the shader. Many simple shaders use just one pass, but shaders that interact with lighting might need more (see Lighting Pipeline for details). Commands inside Pass typically setup fixed function state, for example blending modes.
每个子着色器由一些通道组成,每个通道表示对同一个对象的顶点和片段代码的执行,该对象使用着色器的材质参数渲染。许多简单的着色器只有一个通道,但是与光照交互的着色器可能需要更多(详见光照管线)。通道内部命令通常设置固定管线状态,例如混合模式。

__CGPROGRAM__ .. ENDCG

These keywords surround portions of HLSL code within the vertex and fragment shaders. Typically this is where most of the interesting code is. See vertex and fragment shaders for details.
这些关键字围绕着顶点和片段着色器中的部分HLSL代码。通常,这是最有意思的代码所在之处。有关详细信息,请参阅顶点和片断着色器。

Simple unlit shader

The unlit shader template does a few more things than would be absolutely needed to display an object with a texture. For example, it supports Fog, and texture tiling/offset fields in the material. Let’s simplify the shader to bare minimum, and add more comments:
非光照着色器模板所做的事情要比显示具有纹理的对象所做的事情多得多。例如,它支持材质中的雾和纹理贴图/偏移字段。让我们简化着色器到最低限度,并添加更多的评论:

Shader "Unlit/SimpleUnlitTexturedShader"
{
    Properties
    {
        // we have removed support for texture tiling/offset,
        // so make them not be displayed in material inspector
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            // use "vert" function as the vertex shader
            #pragma vertex vert
            // use "frag" function as the pixel (fragment) shader
            #pragma fragment frag

            // vertex shader inputs
            struct appdata
            {
                float4 vertex : POSITION; // vertex position
                float2 uv : TEXCOORD0; // texture coordinate
            };

            // vertex shader outputs ("vertex to fragment")
            struct v2f
            {
                float2 uv : TEXCOORD0; // texture coordinate
                float4 vertex : SV_POSITION; // clip space position
            };

            // vertex shader
            v2f vert (appdata v)
            {
                v2f o;
                // transform position to clip space
                // (multiply with model*view*projection matrix)
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                // just pass the texture coordinate
                o.uv = v.uv;
                return o;
            }
            
            // texture we will sample
            sampler2D _MainTex;

            // pixel shader; returns low precision ("fixed4" type)
            // color ("SV_Target" semantic)
            fixed4 frag (v2f i) : SV_Target
            {
                // sample texture and return it
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

The Vertex Shader is a program that runs on each vertex of the 3D model. Quite often it does not do anything particularly interesting. Here we just transform vertex position from object space into so called “clip space”, which is what’s used by the GPU to rasterize the object on screen. We also pass the input texture coordinate unmodified - we’ll need it to sample the texture in the fragment shader.
顶点着色器是在3D模型的每个顶点上运行的程序。通常它不会做任何特别有趣的事情。这里我们只是将顶点位置从对象空间转换为所谓的“剪辑空间”,这是GPU用来在屏幕上光栅化对象的。我们还传递了未修改的输入纹理坐标——我们需要它在片段着色器中采样纹理。

The Fragment Shader is a program that runs on each and every pixel that object occupies on-screen, and is usually used to calculate and output the color of each pixel. Usually there are millions of pixels on the screen, and the fragment shaders are executed for all of them! Optimizing fragment shaders is quite an important part of overall game performance work.
片断着色器是一个程序,它运行在物体在屏幕上占据的每个像素上,通常用于计算和输出每个像素的颜色。通常在屏幕上有数百万像素,而片段着色器是为它们执行的!优化片断着色器是整个游戏性能工作的重要部分。

Some variable or function definitions are followed by a Semantic Signifier - for example : POSITION or : SV_Target. These semantics signifiers communicate the “meaning” of these variables to the GPU. See the shader semantics page for details.
一些变量或函数定义后面跟着一个语义符号——例如:POSITION或:SV_Target。这些语义符号将这些变量的“意义”传递给GPU。有关详细信息,请参阅着色器语义页面。

When used on a nice model with a nice texture, our simple shader looks pretty good!
当使用在一个漂亮的模型与一个漂亮的纹理,我们的简单着色器看起来相当不错!
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Even simpler single color shader
Let’s simplify the shader even more – we’ll make a shader that draws the whole object in a single color. This is not terribly useful, but hey we’re learning here.
让我们进一步简化着色器——我们将创建一个着色器,以单一颜色绘制整个对象。这不是很有用,但是我们在这里学习。

Shader "Unlit/SingleColor"
{
    Properties
    {
        // Color property for material inspector, default to white
        _Color ("Main Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            // vertex shader
            // this time instead of using "appdata" struct, just spell inputs manually,
            // and instead of returning v2f struct, also just return a single output
            // float4 clip position
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return mul(UNITY_MATRIX_MVP, vertex);
            }
            
            // color from the material
            fixed4 _Color;

            // pixel shader, no inputs needed
            fixed4 frag () : SV_Target
            {
                return _Color; // just return it
            }
            ENDCG
        }
    }
}

This time instead of using structs for input (appdata) and output (v2f), the shader functions just spell out inputs manually. Both ways work, and which you choose to use depends on your coding style and preference.
这次,着色器函数不再使用structs作为输入(appdata)和输出(v2f),而是手动拼写输入。这两种方法都可以工作,您选择使用哪种方法取决于您的编码风格和偏好。
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Using mesh normals for fun and profit

Let’s proceed with a shader that displays mesh normals in world space. Without further ado:
让我们继续一个着色器显示网格法线在世界空间。闲话少说:

Shader "Unlit/WorldSpaceNormals"
{
    // no Properties block this time!
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // include file that contains UnityObjectToWorldNormal helper function
            #include "UnityCG.cginc"

            struct v2f {
                // we'll output world space normal as one of regular ("texcoord") interpolators
                half3 worldNormal : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            // vertex shader: takes object space normal as input too
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // UnityCG.cginc file contains function to transform
                // normal from object to world space, use that
                o.worldNormal = UnityObjectToWorldNormal(normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 c = 0;
                // normal is a 3D vector with xyz components; in -1..1
                // range. To display it as color, bring the range into 0..1
                // and put into red, green, blue components
                c.rgb = i.worldNormal*0.5+0.5;
                return c;
            }
            ENDCG
        }
    }
}

Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Besides resulting in pretty colors, normals are used for all sorts of graphics effects – lighting, reflections, silhouettes and so on.
除了产生漂亮的颜色,法线还用于各种图形效果——灯光、反射、轮廓等等。

In the shader above, we started using one of Unity’s built-in shader include files. Here, UnityCG.cginc was used which contains a handy function UnityObjectToWorldNormal. We have also used the utility function UnityObjectToClipPos, which transforms the vertex from object space to the screen. This just makes the code easier to read and is more efficient under certain circumstances.
在上面的着色器中,我们开始使用Unity的一个内置着色器包括文件。在这里,UnityCG.cginc 包含了一个方便的函数UnityObjectToWorldNormal。我们还使用了实用函数UnityObjectToClipPos,它将顶点从对象空间转换为屏幕。这只会使代码更容易阅读,并且在某些情况下更高效。

We’ve seen that data can be passed from the vertex into fragment shader in so-called “interpolators” (or sometimes called “varyings”). In HLSL shading language they are typically labeled with TEXCOORDn semantic, and each of them can be up to a 4-component vector (see semantics page for details).
我们已经看到数据可以通过所谓的“插值器”(或有时称为“varyings”)从顶点传递到片段着色器。在HLSL着色语言中,它们通常被标记为TEXCOORDn语义,每个语义都可以达到4个组件向量(详细信息请参阅语义页面)。

Also we’ve learned a simple technique in how to visualize normalized vectors (in –1.0 to +1.0 range) as colors: just multiply them by half and add half. See more vertex data visualization examples in vertex program inputs page.
此外,我们还学习了一种简单的技术,如何将规范化向量(在-1.0到+1.0范围内)可视化为颜色:将它们乘以一半,然后加一半。参见顶点程序输入页面中的更多顶点数据可视化示例。

Environment reflection using world-space normals 用世界空间法线做环境反射
When a Skybox is used in the scene as a reflection source (see Lighting Window), then essentially a “default” Reflection Probe is created, containing the skybox data. A reflection probe is internally a Cubemap texture; we will extend the world-space normals shader above to look into it.
当场景中使用Skybox作为反射源时(请参阅“Lighting Window”),将创建一个包含Skybox数据的“默认”反射探头。反射探头内部为立方体结构;我们将扩展上面的世界空间法线材质来观察它。

The code is starting to get a bit involved by now. Of course, if you want shaders that automatically work with lights, shadows, reflections and the rest of the lighting system, it’s way easier to use surface shaders. This example is intended to show you how to use parts of the lighting system in a “manual” way.
代码现在开始有点复杂了。当然,如果你想要能自动与灯光,阴影,反射和其他光照系统一起工作的着色器,使用表面着色器会更容易。本示例旨在向您展示如何以“手动”的方式使用照明系统的各个部分。

Shader "Unlit/SkyReflection"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                half3 worldRefl : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // compute world space position of the vertex
                float3 worldPos = mul(_Object2World, vertex).xyz;
                // compute world space view direction
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // world space normal
                float3 worldNormal = UnityObjectToWorldNormal(normal);
                // world space reflection vector
                o.worldRefl = reflect(-worldViewDir, worldNormal);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the default reflection cubemap, using the reflection vector
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
                // decode cubemap data into actual color
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                // output it!
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}

Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

The example above uses several things from the built-in shader include files:

  • unity_SpecCube0, unity_SpecCube0_HDR, Object2World, UNITY_MATRIX_MVP from the built-in shader variables. unity_SpecCube0 contains data for the active reflection probe. unity_SpecCube0包含**的反射探头的数据。
  • UNITY_SAMPLE_TEXCUBE is a built-in macro to sample a cubemap. Most regular cubemaps are declared and used using standard HLSL syntax (samplerCUBE and texCUBE), however the reflection probe cubemaps in Unity are declared in a special way to save on sampler slots. If you don’t know what that is, don’t worry, just know that in order to use unity_SpecCube0 cubemap you have to use UNITY_SAMPLE_TEXCUBE macro. UNITY_SAMPLE_TEXCUBE是一个内置宏,用于对cubemap进行采样。大多数常规的cubemaps是使用标准的HLSL语法(samplerCUBE和texCUBE)声明和使用的,但是Unity中的反射探头cubemaps是用一种特殊的方式声明的,以节省采样器槽。如果你不知道那是什么,不要担心,只要知道为了使用unity_SpecCube0 cubemap,你必须使用UNITY_SAMPLE_TEXCUBE宏。
  • UnityWorldSpaceViewDir function from UnityCG.cginc, and DecodeHDR function from the same file. The latter is used to get actual color from the reflection probe data – since Unity stores reflection probe cubemap in specially encoded way. UnityWorldSpaceViewDir函数来自UnityCG.cginc和DecodeHDR函数来自同一个文件。后者用于从反射探针数据获得实际颜色——因为Unity以特殊编码的方式存储反射探针cubemap。
  • reflect is just a built-in HLSL function to compute vector reflection around a given normal. reflect是一个内置的HLSL函数,用于计算给定法线周围的向量反射。

Environment reflection with a normal map 用法线图做环境反射

Often Normal Maps are used to create additional detail on objects, without creating additional geometry. Let’s see how to make a shader that reflects the environment, with a normal map texture.
法线贴图通常用于在对象上创建额外的细节,而不创建额外的几何图形。让我们看看如何用一个法线贴图纹理创建一个环境反射的着色器。

Now the math is starting to get really involved, so we’ll do it in a few steps. In the shader above, the reflection direction was computed per-vertex (in the vertex shader), and the fragment shader was only doing the reflection probe cubemap lookup. However once we start using normal maps, the surface normal itself needs to be calculated on a per-pixel basis, which means we also have to compute how the environment is reflected per-pixel!
现在数学开始真正参与进来,我们将分几步来做。在上面的着色器中,反射方向是按每个顶点计算的(在顶点着色器中),而片段着色器仅执行反射探测cubemap查找。然而,一旦我们开始使用法线贴图,表面法线本身就需要按像素计算,这意味着我们还必须计算环境是如何按像素反射的!

So first of all, let’s rewrite the shader above to do the same thing, except we will move some of the calculations to the fragment shader, so they are computed per-pixel:
首先,让我们重写上面的着色器来做同样的事情,除了我们将移动一些计算到片断着色器,所以它们是逐像素计算:

Shader "Unlit/SkyReflection Per Pixel"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float3 worldPos : TEXCOORD0;
                half3 worldNormal : TEXCOORD1;
                float4 pos : SV_POSITION;
            };

            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                o.worldPos = mul(_Object2World, vertex).xyz;
                o.worldNormal = UnityObjectToWorldNormal(normal);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // compute view direction and reflection vector
                // per-pixel here
                half3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                half3 worldRefl = reflect(-worldViewDir, i.worldNormal);

                // same as in previous shader
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, worldRefl);
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}

That by itself does not give us much – the shader looks exactly the same, except now it runs slower since it does more calculations for each and every pixel on screen, instead of only for each of the model’s vertices. However, we’ll need these calculations really soon. Higher graphics fidelity often requires more complex shaders.
这本身并没有给我们太多的东西——着色器看起来完全一样,只是现在它运行速度变慢了,因为它对屏幕上的每个像素做了更多的计算,而不仅仅是对模型的每个顶点。然而,我们很快就需要这些计算。更高的图像保真度通常需要更复杂的着色器。

We’ll have to learn a new thing now too; the so-called “tangent space”. Normal map textures are most often expressed in a coordinate space that can be thought of as “following the surface” of the model. In our shader, we will need to to know the tangent space basis vectors, read the normal vector from the texture, transform it into world space, and then do all the math from the above shader. Let’s get to it!
我们现在也得学一种新东西;所谓的“切线空间”。法线贴图纹理通常表示在一个坐标空间中,它可以认为是“跟随模型表面”。在我们的着色器中,我们需要知道切空间基向量,从纹理中读取法向量,把它转换成世界空间,然后从上面的着色器中做所有的计算。让我们开始吧!

Shader "Unlit/SkyReflection Per Pixel"
{
    Properties {
        // normal map texture on the material,
        // default to dummy "flat surface" normalmap
        _BumpMap("Normal Map", 2D) = "bump" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float3 worldPos : TEXCOORD0;
                // these three vectors will hold a 3x3 rotation matrix
                // that transforms from tangent to world space
                half3 tspace0 : TEXCOORD1; // tangent.x, bitangent.x, normal.x
                half3 tspace1 : TEXCOORD2; // tangent.y, bitangent.y, normal.y
                half3 tspace2 : TEXCOORD3; // tangent.z, bitangent.z, normal.z
                // texture coordinate for the normal map
                float2 uv : TEXCOORD4;
                float4 pos : SV_POSITION;
            };

            // vertex shader now also needs a per-vertex tangent vector.
            // in Unity tangents are 4D vectors, with the .w component used to
            // indicate direction of the bitangent vector.
            // we also need the texture coordinate.
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                o.worldPos = mul(_Object2World, vertex).xyz;
                half3 wNormal = UnityObjectToWorldNormal(normal);
                half3 wTangent = UnityObjectToWorldDir(tangent.xyz);
                // compute bitangent from cross product of normal and tangent
                half tangentSign = tangent.w * unity_WorldTransformParams.w;
                half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
                // output the tangent space matrix
                o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);
                o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
                o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
                o.uv = uv;
                return o;
            }

            // normal map texture from shader properties
            sampler2D _BumpMap;
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the normal map, and decode from the Unity encoding
                half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));
                // transform normal from tangent to world space
                half3 worldNormal;
                worldNormal.x = dot(i.tspace0, tnormal);
                worldNormal.y = dot(i.tspace1, tnormal);
                worldNormal.z = dot(i.tspace2, tnormal);

                // rest the same as in previous shader
                half3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                half3 worldRefl = reflect(-worldViewDir, worldNormal);
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, worldRefl);
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}

Phew, that was quite involved. But look, normal mapped reflections!
唷,那太复杂了。但是看,法线映射反射!
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Adding more textures

Let’s add more textures to the normal-mapped, sky-reflecting shader above. We’ll add the base color texture, seen in the first unlit example, and an occlusion map to darken the cavities.
在天空-反射着色器上面让我们添加更多的纹理到法线贴图。我们将添加基本的颜色纹理,在第一个无光照的例子中可以看到,以及一个用于暗化的遮挡图。

Shader "Unlit/More Textures"
{
    Properties {
        // three textures we'll use in the material
        _MainTex("Base texture", 2D) = "white" {}
        _OcclusionMap("Occlusion", 2D) = "white" {}
        _BumpMap("Normal Map", 2D) = "bump" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            // exactly the same as in previous shader
            struct v2f {
                float3 worldPos : TEXCOORD0;
                half3 tspace0 : TEXCOORD1;
                half3 tspace1 : TEXCOORD2;
                half3 tspace2 : TEXCOORD3;
                float2 uv : TEXCOORD4;
                float4 pos : SV_POSITION;
            };
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                o.worldPos = mul(_Object2World, vertex).xyz;
                half3 wNormal = UnityObjectToWorldNormal(normal);
                half3 wTangent = UnityObjectToWorldDir(tangent.xyz);
                half tangentSign = tangent.w * unity_WorldTransformParams.w;
                half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
                o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);
                o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
                o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
                o.uv = uv;
                return o;
            }

            // textures from shader properties
            sampler2D _MainTex;
            sampler2D _OcclusionMap;
            sampler2D _BumpMap;
        
            fixed4 frag (v2f i) : SV_Target
            {
                // same as from previous shader...
                half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));
                half3 worldNormal;
                worldNormal.x = dot(i.tspace0, tnormal);
                worldNormal.y = dot(i.tspace1, tnormal);
                worldNormal.z = dot(i.tspace2, tnormal);
                half3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                half3 worldRefl = reflect(-worldViewDir, worldNormal);
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, worldRefl);
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);                
                fixed4 c = 0;
                c.rgb = skyColor;

                // modulate sky color with the base texture, and the occlusion map
                fixed3 baseColor = tex2D(_MainTex, i.uv).rgb;
                fixed occlusion = tex2D(_OcclusionMap, i.uv).r;
                c.rgb *= baseColor;
                c.rgb *= occlusion;

                return c;
            }
            ENDCG
        }
    }
}

Balloon cat is looking good!
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Texturing shader examples
Procedural checkerboard pattern 程序化棋盘格模式
Here’s a shader that outputs a checkerboard pattern based on texture coordinates of a mesh:
这是一个基于网格纹理坐标输出棋盘格模式的着色器:

Shader "Unlit/Checkerboard"
{
    Properties
    {
        _Density ("Density", Range(2,50)) = 30
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            float _Density;

            v2f vert (float4 pos : POSITION, float2 uv : TEXCOORD0)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(pos);
                o.uv = uv * _Density;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 c = i.uv;
                c = floor(c) / 2;
                float checker = frac(c.x + c.y) * 2;
                return checker;
            }
            ENDCG
        }
    }
}

The density slider in the Properties block controls how dense the checkerboard is. In the vertex shader, the mesh UVs are multiplied by the density value to take them from a range of 0 to 1 to a range of 0 to density. Let’s say the density was set to 30 - this will make i.uv input into the fragment shader contain floating point values from zero to 30 for various places of the mesh being rendered.
属性块中的密度滑块控制棋盘的密度。在顶点着色器中,网格UVs乘以密度值,使它们从0到1的范围到0到密度的范围。我们设密度为30,这就是i.uv输入到片断着色器包含浮点值从0到30为网格渲染的不同地方。

Then the fragment shader code takes only the integer part of the input coordinate using HLSL’s built-in floor function, and divides it by two. Recall that the input coordinates were numbers from 0 to 30; this makes them all be “quantized” to values of 0, 0.5, 1, 1.5, 2, 2.5, and so on. This was done on both the x and y components of the input coordinate.
然后,片段着色器代码使用HLSL的内置floor函数只接受输入坐标的整数部分,并将其除以2。回想一下输入坐标是从0到30的数字;这使得它们都被“量化”为0、0.5、1、1.5、2、2.5,等等。这是对输入坐标的x和y分量做的。

Next up, we add these x and y coordinates together (each of them only having possible values of 0, 0.5, 1, 1.5, …) and only take the fractional part using another built-in HLSL function, frac. Result of this can only be either 0.0 or 0.5. We then multiply it by two to make it either 0.0 or 1.0, and output as a color (this results in black or white color respectively).
接下来,我们将这些x坐标和y坐标相加(每个坐标的可能值分别为0、0.5、1、1.5、…),然后使用另一个内置的HLSL函数frac只取小数部分。结果只能是0.0或0.5。然后将其乘以2,使其为0.0或1.0,并输出为颜色(结果分别为黑色和白色)。
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Tri-planar texturing 三向贴图

For complex or procedural meshes, instead of texturing them using the regular UV coordinates, it is sometimes useful to just “project” texture onto the object from three primary directions. This is called “tri-planar” texturing. The idea is to use surface normal to weight the three texture directions. Here’s the shader:
对于复杂或程序化网格,替代用常规的UV坐标对它们进行纹理化,有时从三个主方向将纹理“投射”到对象上是有用的。这就是所谓的“三向”纹理化。其思想是使用表面的法线去权衡三个纹理方向。Shader:

Shader "Unlit/Triplanar"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Tiling ("Tiling", Float) = 1.0
        _OcclusionMap("Occlusion", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                half3 objNormal : TEXCOORD0;
                float3 coords : TEXCOORD1;
                float2 uv : TEXCOORD2;
                float4 pos : SV_POSITION;
            };

            float _Tiling;

            v2f vert (float4 pos : POSITION, float3 normal : NORMAL, float2 uv : TEXCOORD0)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(pos);
                o.coords = pos.xyz * _Tiling;
                o.objNormal = normal;
                o.uv = uv;
                return o;
            }

            sampler2D _MainTex;
            sampler2D _OcclusionMap;
            
            fixed4 frag (v2f i) : SV_Target
            {
                // use absolute value of normal as texture weights
                half3 blend = abs(i.objNormal);
                // make sure the weights sum up to 1 (divide by sum of x+y+z)
                blend /= dot(blend,1.0);
                // read the three texture projections, for x,y,z axes
                fixed4 cx = tex2D(_MainTex, i.coords.yz);
                fixed4 cy = tex2D(_MainTex, i.coords.xz);
                fixed4 cz = tex2D(_MainTex, i.coords.xy);
                // blend the textures based on weights
                fixed4 c = cx * blend.x + cy * blend.y + cz * blend.z;
                // modulate by regular occlusion map
                c *= tex2D(_OcclusionMap, i.uv);
                return c;
            }
            ENDCG
        }
    }
}

Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Calculating lighting

Typically when you want a shader that works with Unity’s lighting pipeline, you would write a surface shader. This does most of the “heavy lifting” for you, and your shader code just needs to define surface properties.
通常情况下,当你想要一个与Unity的光照管线一起工作的着色器,你会写一个表面着色器。这为您完成了大部分“繁重的工作”,您的着色器代码只需要定义表面属性。

However in some cases you want to bypass the standard surface shader path; either because you want to only support some limited subset of whole lighting pipeline for performance reasons, or you want to do custom things that aren’t quite “standard lighting”. The following examples will show how to get to the lighting data from manually-written vertex and fragment shaders. Looking at the code generated by surface shaders (via shader inspector) is also a good learning resource.
然而在某些情况下你想要绕过标准的表面着色路径;要么是因为出于性能原因,您只想支持整个光照管线的一些有限子集,要么是因为您想做一些不太“标准光照”的定制工作。下面的例子将展示如何从手动编写的顶点和片段着色器获得光照数据。查看由表面着色器(通过着色器检查器)生成的代码也是一个很好的学习资源。

Simple diffuse lighting

The first thing we need to do is to indicate that our shader does in fact need lighting information passed to it. Unity’s rendering pipeline supports various ways of rendering; here we’ll be using the default forward rendering one.
我们需要做的第一件事是着色器实际上需要光照信息的。Unity的渲染管线支持多种渲染方式;这里我们将使用默认的forward渲染。
We’ll start by only supporting one directional light. Forward rendering in Unity works by rendering the main directional light, ambient, lightmaps and reflections in a single pass called ForwardBase. In the shader, this is indicated by adding a pass tag: Tags {“LightMode”=“ForwardBase”}. This will make directional light data be passed into shader via some built-in variables.
我们将从支持一个方向光开始。Unity中的前向渲染是通过在一个叫做ForwardBase的单通道中渲染主方向光、环境光、光照图和反射来完成的。在着色器中,通过添加pass标签来表示: Tags{" LightMode " = " ForwardBase "}。这将使方向光数据通过一些内置变量传递到着色器。

Here’s the shader that computes simple diffuse lighting per vertex, and uses a single main texture:
这是一个着色器,计算每个顶点的简单漫反射照明,并使用一个主纹理:

Shader "Lit/Simple Diffuse"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            // indicate that our pass is the "base" pass in forward
            // rendering pipeline. It gets ambient and main directional
            // light data set up; light direction in _WorldSpaceLightPos0
            // and color in _LightColor0
            Tags {"LightMode"="ForwardBase"}
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc" // for UnityObjectToWorldNormal
            #include "UnityLightingCommon.cginc" // for _LightColor0

            struct v2f
            {
                float2 uv : TEXCOORD0;
                fixed4 diff : COLOR0; // diffuse lighting color
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                // get vertex normal in world space
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                // dot product between normal and light direction for
                // standard diffuse (Lambert) lighting
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                // factor in the light color
                o.diff = nl * _LightColor0;
                return o;
            }
            
            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                // sample texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // multiply by lighting
                col *= i.diff;
                return col;
            }
            ENDCG
        }
    }
}

This makes the object react to light direction - parts of it facing the light are illuminated, and parts facing away are not illuminated at all.
这使得物体对光的方向产生反应——部分面向光的部分被照亮,而未面向光的部分则完全没有被照亮。
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Diffuse lighting with ambient
The example above does not take any ambient lighting or light probes into account. Let’s fix this! It turns out we can do this by adding just a single line of code. Both ambient and light probe data is passed to shaders in Spherical Harmonics form, and ShadeSH9 function from UnityCG.cginc include file does all the work of evaluating it, given a world space normal.
上面的示例没有考虑任何环境照明或光照探头。让我们解决这个问题!我们可以通过添加一行代码来实现。环境和光照探头数据以球面谐波(Spherical Harmonics)的形式传递给着色器,UnityCG.cginc中的ShadeSH9函数做所有的评估工作,只需要给定一个世界空间法线。

Shader "Lit/Diffuse With Ambient"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Tags {"LightMode"="ForwardBase"}
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct v2f
            {
                float2 uv : TEXCOORD0;
                fixed4 diff : COLOR0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                o.diff = nl * _LightColor0;

                // the only difference from previous shader:
                // in addition to the diffuse lighting from the main light,
                // add illumination from ambient or light probes
                // ShadeSH9 function from UnityCG.cginc evaluates it,
                // using world space normal
                o.diff.rgb += ShadeSH9(half4(worldNormal,1));
                return o;
            }
            
            sampler2D _MainTex;

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

This shader is in fact starting to look very similar to the built-in Legacy Diffuse shader!
这个材质实际上开始看起来和内置的漫反射材质非常相似!
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Implementing shadow casting 实现阴影投射

Our shader currently can neither receive nor cast shadows. Let’s implement shadow casting first.
当前着色器既不能接收也不能投射阴影。让我们先实现投射吧。

In order to cast shadows, a shader has to have a ShadowCaster pass type in any of its subshaders or any fallback. The ShadowCaster pass is used to render the object into the shadowmap, and typically it is fairly simple - the vertex shader only needs to evaluate the vertex position, and the fragment shader pretty much does not do anything. The shadowmap is only the depth buffer, so even the color output by the fragment shader does not really matter.
为了投射阴影,着色器必须有ShadowCaster通道类型在任一subshaders或fallback。ShadowCaster通道被用于渲染对象到阴影贴图(shadowmap), 通常是相当简单的——顶点着色器只需要计算顶点的位置,和片段着色器几乎不做任何事。阴影贴图只是深度缓冲,所以即使片断着色器输出的颜色也不重要。

This means that for a lot of shaders, the shadow caster pass is going to be almost exactly the same (unless object has custom vertex shader based deformations, or has alpha cutout / semitransparent parts). The easiest way to pull it in is via UsePass shader command:
这意味着对于很多着色器,阴影投射通道几乎是完全一样的(除非对象有基于变形的自定义顶点着色器,或者有alpha剪切/半透明部分)。最简单的方法是引用UsePass着色器命令:

Pass
{
    // regular lighting pass
}
// pull in shadow caster from VertexLit built-in shader
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"

However we’re learning here, so let’s do the same thing “by hand” so to speak. For shorter code, we’ve replaced the lighting pass (“ForwardBase”) with code that only does untextured ambient. Below it, there’s a “ShadowCaster” pass that makes the object support shadow casting.
然而,我们在这里学习,所以让我们做同样的事情“手工”。为了简化代码,我们已经用无纹理与环境光的代码替换了lighting 通道(“ForwardBase”)。在它下面,有一个“ShadowCaster”通道,使物体支持阴影投射。

Shader "Lit/Shadow Casting"
{
    SubShader
    {
        // very simple lighting pass, that only does non-textured ambient
        Pass
        {
            Tags {"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct v2f
            {
                fixed4 diff : COLOR0;
                float4 vertex : SV_POSITION;
            };
            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                // only evaluate ambient
                o.diff.rgb = ShadeSH9(half4(worldNormal,1));
                o.diff.a = 1;
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                return i.diff;
            }
            ENDCG
        }

        // shadow caster rendering pass, implemented manually
        // using macros from UnityCG.cginc
        Pass
        {
            Tags {"LightMode"="ShadowCaster"}

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

            struct v2f { 
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}

Now there’s a plane underneath, using a regular built-in Diffuse shader, so that we can see our shadows working (remember, our current shader does not support receiving shadows yet!).
现在下面有一个平面,使用一个普通的内置漫反射着色器,这样我们可以看到我们的阴影在工作(记住,我们的当前材质还不支持接收阴影!)

We’ve used the #pragma multi_compile_shadowcaster directive. This causes the shader to be compiled into several variants with different preprocessor macros defined for each (see multiple shader variants page for details). When rendering into the shadowmap, the cases of point lights vs other light types need slightly different shader code, that’s why this directive is needed.
我们已经使用了#pragma multi_compile_shadowcaster指令。这使得着色器被编译成几个变体,每个变体都有不同的预处理器宏(详细信息请参阅多个着色器变量页面)。当渲染到阴影贴图时,点光源和其他光源的情况需要稍微不同的着色器代码,这就是为什么需要这个指令。

Receiving shadows 接收阴影

Implementing support for receiving shadows will require compiling the base lighting pass into several variants, to handle cases of “directional light without shadows” and “directional light with shadows” properly. #pragma multi_compile_fwdbase directive does this (see multiple shader variants for details). In fact it does a lot more: it also compiles variants for the different lightmap types, realtime GI being on or off etc. Currently we don’t need all that, so we’ll explicitly skip these variants.
实现对接收阴影的支持将需要将基础光照传递编译成几个变体,以正确地处理“没有阴影的方向光”和“带阴影的方向光”的情况。#pragma multi_compile_fwdbase指令可以做到这一点(详情请参阅多个着色器变体)。实际上它做的更多:它还为不同的lightmap类型编译变体,实时GI是开是关等等。目前我们不需要所有这些,所以我们将显式地跳过这些变体。

Then to get actual shadowing computations, we’ll #include “AutoLight.cginc” shader include file and use SHADOW_COORDS, TRANSFER_SHADOW, SHADOW_ATTENUATION macros from it.
然后,为了得到实际的阴影计算,我们将包括着色器文件 #include “AutoLight.cginc” 和使用SHADOW_COORDS, TRANSFER_SHADOW, SHADOW_ATTENUATION宏。

Here’s the shader:

Shader "Lit/Diffuse With Shadows"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Tags {"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            // compile shader into multiple variants, with and without shadows
            // (we don't care about any lightmaps yet, so skip these variants)
            #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
            // shadow helper functions and macros
            #include "AutoLight.cginc"

            struct v2f
            {
                float2 uv : TEXCOORD0;
                SHADOW_COORDS(1) // put shadows data into TEXCOORD1
                fixed3 diff : COLOR0;
                fixed3 ambient : COLOR1;
                float4 pos : SV_POSITION;
            };
            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                o.diff = nl * _LightColor0.rgb;
                o.ambient = ShadeSH9(half4(worldNormal,1));
                // compute shadows data
                TRANSFER_SHADOW(o)
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
                fixed shadow = SHADOW_ATTENUATION(i);
                // darken light's illumination with shadow, keep ambient intact
                fixed3 lighting = i.diff * shadow + i.ambient;
                col.rgb *= lighting;
                return col;
            }
            ENDCG
        }

        // shadow casting support
        UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
    }
}

Look, we have shadows now!
Vertex and fragment shader examples 顶点与片断着色器示例 - Unity Shader Reference 系列7

Other shader examples
Fog

Shader "Custom/TextureCoordinates/Fog" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            //Needed for fog variation to be compiled.
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct vertexInput {
                float4 vertex : POSITION;
                float4 texcoord0 : TEXCOORD0;
            };

            struct fragmentInput{
                float4 position : SV_POSITION;
                float4 texcoord0 : TEXCOORD0;
                
                //Used to pass fog amount around number should be a free texcoord.
                UNITY_FOG_COORDS(1)
            };

            fragmentInput vert(vertexInput i){
                fragmentInput o;
                o.position = UnityObjectToClipPos(i.vertex);
                o.texcoord0 = i.texcoord0;
                
                //Compute fog amount from clip space position.
                UNITY_TRANSFER_FOG(o,o.position);
                return o;
            }

            fixed4 frag(fragmentInput i) : SV_Target {
                fixed4 color = fixed4(i.texcoord0.xy,0,0);
                
                //Apply fog (additive pass are automatically handled)
                UNITY_APPLY_FOG(i.fogCoord, color); 
                
                //to handle custom fog color another option would have been 
                //#ifdef UNITY_PASS_FORWARDADD
                //  UNITY_APPLY_FOG_COLOR(i.fogCoord, color, float4(0,0,0,0));
                //#else
                //  fixed4 myCustomColor = fixed4(0,0,1,0);
                //  UNITY_APPLY_FOG_COLOR(i.fogCoord, color, myCustomColor);
                //#endif
                
                return color;
            }
            ENDCG
        }
    }
}

相关文章: