[译]Unity3D Shader教程(五)Surface Shader Basics

[译]Unity3D Shader教程(五)Surface Shader Basics

2021年8月5日 0 作者 老王

原文链接:
Shader Tutorials by Ronja


表面着色器基础。

1 Summary

概要。
除了几乎从头开始编写着色器之外,unity 还允许我们定义一些参数并让 unity 生成执行复杂光计算的代码。 这些着色器称为“表面着色器”。

要了解表面着色器,最好先了解基本的无光照着色器,我在这里有关于它们的教程。
Surface Shader Basics-01

2 Conversion to simple Surface Shader

转换为简单的表面着色器。
使用表面着色器时,我们不必做其他一些必须做的事情,因为 unity 会为我们生成它们。 为了转换为表面着色器,我们完全删除我们的顶点着色器。 删除顶点和片段函数的编译指示定义(如:#pragma vertex vert)。删除顶点着色器到片元着色的输入结构体,删除用于纹理缩放的 MainTex_ST 变量,删除包含 UnityCG 包含文件的内容。 同时删除Pass的开始结束括号,因为Unity 会为我们自动生成Pass。删除完成之后我们的着色器应该是这样的:

Shader "Tutorial/005_surface" {
    Properties {
        _Color ("Tint", Color) = (0, 0, 0, 1)
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

        CGPROGRAM

        sampler2D _MainTex;
        fixed4 _Color;

        fixed4 frag (v2f i) : SV_TARGET {
            fixed4 col = tex2D(_MainTex, i.uv);
            col *= _Color;
            return col;
        }
        ENDCG
    }
    FallBack "Standard"
}

我们的着色器被我们破坏了,但是我们可以添加一些东西让它作为表面着色器重新工作。
首先,我们添加一个新结构并将其命名为 Input,这将包含我们设置表面颜色所需的所有信息。 对于这个简单的着色器,只需要 UV 坐标。UV坐标的数据类型之前的着色器一样是一个二维浮点数(float2)。需要注意UV坐标的命名很重要,我们将其命名为 uv_MainTex,这样这个UV坐标(uv_MainTex)就已经拥有 MainTex 纹理的平铺和偏移。 如果纹理有不同的名称,我们必须使用 uvTextureName 来获取适合该纹理的坐标。

struct Input {
    float2 uv_MainTex;
};

接下来,我们将片元函数更改为表面函数。为了使更改显而易见,我们将其重命名为 surf。同时将返回类型(函数名前面的数据类型)替换为void,这样函数就什么都不返回了。
接下来我们将surf函数的输入参数扩展为采用 2 个。第一个参数为我们刚刚定义的输入结构,这样我们可以访问基于每个顶点定义的信息。第二个参数是一个名为 SurfaceOutputStandard 的结构体。顾名思义,我们将使用它来将信息返回到着色器的生成部分。为了让“返回”起作用,我们必须在它前面写上 inout 关键字。第二个结构体是 unity 将用于其照明计算的所有数据。光照计算是基于物理的(我将在本文后面解释参数)。
接下来,我们将从方法中删除 sv_target 属性,因为和其余的一样,它是由unity在其他地方完成的。
为了使表面方法起作用,我们必须进行的最后一次更改是删除 return 语句(这就是我们将返回类型更改为 void 的原因)。相反,我们将输出结构的albedo(反照率)部分设置为我们的颜色值。

void surf (Input i, inout SurfaceOutputStandard o) {
    fixed4 col = tex2D(_MainTex, i.uv_MainTex);
    col *= _Color;
    o.Albedo = col.rgb;
}

使着色器再次工作并使其正确处理光线的最后一步是添加 pragma 语句,声明着色器的类型和使用的方法。 (类似于我们在基本着色器中声明顶点和片段方法的方式)。
语句以#pragma 开头,然后是我们声明的着色器类型(surface),然后是表面方法的名称(surf),最后是我们希望它使用的光照模型(Standard)。
有了所有这些,我们的着色器应该可以再次工作并显示正确的照明。

Shader "Tutorial/005_surface" {
    Properties {
        _Color ("Tint", Color) = (0, 0, 0, 1)
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

        CGPROGRAM

        #pragma surface surf Standard

        sampler2D _MainTex;
        fixed4 _Color;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input i, inout SurfaceOutputStandard o) {
            fixed4 col = tex2D(_MainTex, i.uv_MainTex);
            col *= _Color;
            o.Albedo = col.rgb;
        }
        ENDCG
    }
}

Surface Shader Basic-2

3 Standard Lighting Properties

为了扩展着色器,我们现在可以更多地使用材质属性。 输出结构(SurfaceOutputStandard)中的不同值分别如下:

  • Albedo – (漫反射率)Albedo 是材质的基色。 它将被照着它的灯光的颜色着色,并且在阴影中变暗(正如我们所期望的)。 反照率颜色不会影响镜面光,因此您可以制作仍然有明显光泽的黑色材料。 它存储为 3 维颜色向量(fixed3)。
  • Normal-(法线)这是材质(Material)的法线。 法线在“切线空间”中,这意味着返回它们后,它们将变为相对于世界的法线。 将法线置于切线空间意味着如果我们将 (0,1,0) 写入该变量,则法线实际上不会指向上方,而是远离表面(这是将法线编码为法线贴图的方式,因此我们可以直接从法线贴图Normal Map复制法线信息到这个变量)。 法线存储为 3 维方向向量(vector3)。
  • Emission– (自发光)有了这个,你可以让你的材料发光。 如果你只使用它,你的着色器看起来像我们之前制作的无光着色器一样,但更性能消耗更高。自发光的颜色不受光的影响,因此你可以制作始终明亮的斑点。 如果您使用 HDR 颜色进行渲染(您可以在相机设置中设置),您可以将值大于 1 的值写入发射通道,在使用bloom屏幕后处理效果时,物体看起来会非常亮,同时向外扩散更多。自发光颜色也存储为 3维颜色向量(vector3)。
  • Metallic-(金属度)金属材料与非金属材料看起来是非常不同的。 要使材质看起来更像金属,您可以调高此值。 它将使对象以不同的方式反射,并且Albedo(反照率)值将为反射着色,而不是非金属时的漫反射。 Metallic(金属度值)存储为标量(一维)值,其中 0 表示非金属材料,1 表示完全金属材料。
  • Smoothness-(平滑度)使用此值,我们可以指定材质的平滑程度。 平滑度为 0 的材质看起来很粗糙,光线会反射到各个方向,我们看不到镜面高光或环境反射。 具有 1 平滑度的材料看起来非常抛光。 当你正确设置你的环境时,你可以看到它反映在你的材料上。 它也非常抛光,你也看不到镜面高光,因为镜面高光变得无限小。 当您将平滑度设置为略低于 1 的值时,您开始看到周围灯光的镜面高光。 当您降低平滑度时,高光会变大并变得不那么强烈。 平滑度也存储为标量值。
  • Occlusion-(遮罩)遮罩将从您的材质中去除光线。 有了它,您可以假装光线不会进入模型的裂缝,但您可能几乎不会使用它,除非您追求超写实风格。 遮挡也存储为标量值,但违反直觉的是 1 表示像素具有完全亮度,而 0 表示它处于黑暗中。
  • Alpha-(透明度)Alpha 是材质的透明度。 我们当前的材质是“不透明的”,这意味着不能有任何透明像素,并且 alpha 值不会做任何事情。 在制作透明着色器时,alpha 将定义我们可以在该像素处看到多少材质,1 是完全可见的,而 0 是完全透明的。 Alpha 也存储为标量值。

    4 Implement a few Lighting Properties

    实现一些灯光属性。
    我们现在可以将其中一些功能添加到我们的着色器中。 我现在将使用发射、金属和平滑度值,但您显然也可以实现其他值。
    首先,我们添加 2 个标量值,平滑度和金属度。 我们首先将值作为half类型(即表面输出结构中使用的数据类型)添加到我们的全局范围中(在函数或结构之外)。

half _Smoothness;
half _Metallic;

然后我们还将这些值添加到我们的Properties中,以便能够在Inspector(检视面板)中更改它们。 Properties没有half 类型,所以我们告诉它们变量是 float 类型。 这已经可以让变量显示在Inspector中,但我们还没有使用它们。

Properties {
    _Color ("Tint", Color) = (0, 0, 0, 1)
    _MainTex ("Texture", 2D) = "white" {}
    _Smoothness ("Smoothness", float) = 0
    _Metallic ("Metalness", float) = 0
}

类似于我们如何将颜色变量分配给材质的反照率,我们现在可以将平滑度分配给输出结构的平滑度,将金属度分配给输出结构的金属度。

void surf (Input i, inout SurfaceOutputStandard o) {
    fixed4 col = tex2D(_MainTex, i.uv_MainTex);
    col *= _Color;
    o.Albedo = col.rgb;
    o.Metallic = _Metallic;
    o.Smoothness = _Smoothness;
}

这很好用,但是很容易将大于 1 或小于 0 的值分配给这些值并得到非常错误的结果,并且很难看出一个值有多高。 为了解决这个问题,我们可以将值分配为Range属性而不是浮点属性。Range属性允许我们定义最小值和最大值,并且 unity 将在它们之间显示一个滑块。

Properties {
    _Color ("Tint", Color) = (0, 0, 0, 1)
    _MainTex ("Texture", 2D) = "white" {}
    _Smoothness ("Smoothness", Range(0, 1)) = 0
    _Metallic ("Metalness", Range(0, 1)) = 0
}

Inspector
接下来我们添加自发光颜色。 首先添加 hlsl 代码中的变量,然后添加Properties。 Properties类型我们像 tint 一样使用 color 类型。 变量我们使用 half3类型 ,因为它是没有 alpha 的 RGB 颜色,并且它的值可以大于 1(输出结构也使用 half3)。 然后我们也像其他属性一样将变量指指定给表面输出中的值。

// ...

_Emission ("Emission", Color) = (0,0,0,1)

// ...

half3 _Emission;

// ...

o.Emission = _Emission;

Emissive
除了到处发光的物体看起来有点奇怪之外,我们还只能为我们的材质指定正常颜色,而不是值超过 1 的 HDR 颜色。为了解决这个问题,我们在发射属性前面添加了 hdr 标签。 通过这些更改,我们现在可以将亮度设置为更高的值。 为了更好地利用发射,您可能应该使用纹理,您可以像实现我们用于反照率值的主纹理一样实现其他纹理。

[HDR] _Emission ("Emission", Color) = (0,0,0,1)

HdrInspector

5 Minor Improvements

小改进。
最后,我将向您展示两个让您的着色器看起来更好的小东西。 首先,您可以在子着色器下添加回退着色器。 这允许 unity 使用其他着色器的功能,而我们不必自己实现它们。 为此,我们将标准着色器设置为备用着色器,Unity 将从中借用“阴影通道”,使我们的材质在其他对象上投射阴影。 接下来我们可以扩展我们的 pragma 指令。 我们将 fullforwardshadows 参数添加到表面着色器指令中,这样我们就可以获得更好的阴影。 我们还添加了一个指令,将构建目标设置为 3.0,这意味着 unity 将使用更高的精度值,这会有更漂亮的光照。

Shader "Tutorial/005_surface" {
    Properties {
        _Color ("Tint", Color) = (0, 0, 0, 1)
        _MainTex ("Texture", 2D) = "white" {}
        _Smoothness ("Smoothness", Range(0, 1)) = 0
        _Metallic ("Metalness", Range(0, 1)) = 0
        [HDR] _Emission ("Emission", color) = (0,0,0)
    }
    SubShader {
        Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

        CGPROGRAM

        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;
        fixed4 _Color;

        half _Smoothness;
        half _Metallic;
        half3 _Emission;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input i, inout SurfaceOutputStandard o) {
            fixed4 col = tex2D(_MainTex, i.uv_MainTex);
            col *= _Color;
            o.Albedo = col.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Smoothness;
            o.Emission = _Emission;
        }
        ENDCG
    }
    FallBack "Standard"
}

Surface Shader Basics-01