12.表面着色器输出结构体

12.表面着色器-结构体-输出结构体


12.1 知识点

表面着色器中的输出结构体指什么

表面着色器中,我们在编译指令中使用的表面函数参数有三种固定格式:

  • void 表面函数名(Input IN, inout SurfaceOutput o)
  • void 表面函数名(Input IN, inout SurfaceOutputStandard o)
  • void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)

其中的 SurfaceOutputSurfaceOutputStandardSurfaceOutputStandardSpecular 结构体就是我们的输出结构体。
我们可以利用上节课学习的输入结构体 Input 中的数据,在表面函数中进行计算后将结果赋值给输出结构体。
之后,Unity 会利用我们输出的数据作为光照模型函数的输入,进行各种光照相关的计算。
这三个输出结构体是由 Unity 提前声明好的,不能自行增加或减少;如果没有对其中的某些变量赋值,则会使用默认值。

输出结构体中有哪些成员

SurfaceOutput 结构体(在 Unity 内置文件 Lighting.cginc 中声明):
当在编译指令中使用 Lambert 或 BlinnPhong 光照模型时,需要使用该结构体。

struct SurfaceOutput
{
    fixed3 Albedo;    // 漫反射
    fixed3 Normal;    // 切线空间法线
    fixed3 Emission;  // 自发光:一般 Unity 会在片元着色器最后输出前,在输出颜色上直接叠加自发光颜色
    half Specular;    // 镜面反射指数,范围 0~1
    fixed Gloss;      // 镜面反射强度  
    // 镜面相关的两个参数,如果使用了 BlinnPhong 光照模型,
    // 会使用该公式计算高光反射强度:float spec = pow(nh, s.Specular * 128) * s.Gloss;
    fixed Alpha;      // 透明通道:如果开启了透明度,会用该值和颜色进行混合
};

SurfaceOutputStandard 结构体(在 Unity 内置文件 UnityPBSLighting.cginc 中声明):
当在编译指令中使用 Standard 光照模型时,需要使用该结构体,也称为金属工作流输出结构体。

struct SurfaceOutputStandard
{
    fixed3 Albedo;    // 漫反射
    fixed3 Normal;    // 切线空间法线
    fixed3 Emission;  // 自发光
    half Metallic;    // 0 表示非金属,1 表示金属
    half Smoothness;  // 0 表示最粗糙,1 表示最光滑
    half Occlusion;   // 环境光遮蔽,默认为 1
    fixed Alpha;      // 透明通道      
};

SurfaceOutputStandardSpecular 结构体(在 Unity 内置文件 UnityPBSLighting.cginc 中声明):
当在编译指令中使用 StandardSpecular 光照模型时,需要使用该结构体,也称为高光工作流输出结构体。

struct SurfaceOutputStandardSpecular
{
    fixed3 Albedo;    // 漫反射
    fixed3 Normal;    // 切线空间法线
    fixed3 Emission;  // 自发光
    half Smoothness;  // 0 表示最粗糙,1 表示最光滑
    half Occlusion;   // 环境光遮蔽,默认为 1
    fixed Alpha;      // 透明通道      
};

表面着色器的渲染流程

在表面着色器中,我们已经了解了编译指令、结构体以及自定义函数(包括表面函数、光照模型、顶点函数和最终颜色修改函数)的作用。
这些部分共同决定了表面着色器如何工作。
下面通过一张示意图来展示表面着色器最终的渲染流程如何将这些部分串联起来:

  • 黄色部分:表示可以自定义的函数
  • 灰色部分:表示 Unity 自动生成的计算步骤

为什么不一开始学习表面着色器

表面着色器是对顶点/片元着色器的一层封装,只有了解了顶点/片元着色器,才能真正理解表面着色器的本质原理。
学会了更“底层”的顶点/片元着色器编写,之后在学习其它 Shader 语言时会更加得心应手!


12.2 知识点代码

Lesson12_Shader.shader

Shader "Custom/Lesson12_Shader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("BumpMap", 2D) = "white"{}
        _BumpScale("BumpScale", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        //vertex:FunVertex finalcolor:FunFinalcolor 
        #pragma surface surf Lambert
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;
        float _BumpScale;//凹凸程度
        fixed4 _Color;

        struct Input
        {
            //1.uv相关
            //规则:uv纹理变量名 若存在次纹理坐标,也可以用uv2作为前缀
            float2 uv_MainTex;
            float2 uv_BumpMap;
            
            //2.视角方向(可以用于处理边缘光照等)
            float3 viewDir;
            
            //3.屏幕空间坐标(可以用于处理反射或屏幕特效等)
            float4 screenPos;
            
            //4.世界空间下的位置
            float3 worldPos;
            
            //5.世界空间下的反射方向,没有修改o.Normal时
            //如果修改了表面法线 o.Normal
            //在表面函数中,需要使用 WorldReflectionVector(IN, o.Normal) 来得到世界空间下的反射方向
            float3 worldRefl;
            
            //6.世界空间下的法线方向,没有修改o.Normal时
            //如果修改了表面法线 o.Normal
            //在表面函数中,需要使用 WorldNormalVector(IN, o.Normal) 来得到世界空间下的法线方向
            float3 worldNormal;
            
            //7.使用COLOR语义定义的float4变量,表示插值后的逐顶点颜色
            float4 vertexColor:COLOR;
        };

        //1. void 表面函数名(Input IN, inout SurfaceOutput o)
        //2. void 表面函数名(Input IN, inout SurfaceOutputStandard o)
        //3. void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)
        void surf (Input IN, inout SurfaceOutput o)
        {
            // 我们在Input结构体中声明了uv_MainTex
            // 就可以.出来直接使用
            // 只要满足命名规则 像视角方向 世界空间下的位置 也可以直接使用
            // Unity已经帮我们转换好了
            fixed4 tex = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = tex.rgb * _Color;
            o.Alpha = tex.a * _Color.a;
            
            //乘以凹凸程度的系数
            float3 tangentNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            tangentNormal.xy *= _BumpScale;
            tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
            o.Normal = tangentNormal;
        }

        void FunVertex(inout appdata_full v)
        {
            //处理顶点逻辑
            
        }

        void FunFinalcolor(Input IN, SurfaceOutput o, inout fixed4 color)
        {
            //对最终处理完的颜色 再次进行额外处理
        }

        //不依赖视角的光照模型,比如漫反射
        //half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half atten)
        //依赖视角的光照模型,比如高光反射
        //half4 Lighting自定义名字 (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
        //然后只需要在光照模型处填写 自定义名字 即可
        //就会自动调用函数中的逻辑来处理光照相关逻辑了
        half4 LightingMyLight (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
        {

        }

        ENDCG
    }
    FallBack "Diffuse"
}

Lesson12_表面着色器_输出结构体.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson12_表面着色器_输出结构体 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 表面着色器中的输出结构体指什么

        //我们上节课主要学习了编译指令
        //#pragma surface 表面函数名 光照模型 可选额外参数
        //其中在讲解表面函数名时提到,需要按规则声明函数
        //函数的参数有三种固定格式
        //1. void 表面函数名(Input IN, inout SurfaceOutput o)
        //2. void 表面函数名(Input IN, inout SurfaceOutputStandard o)
        //3. void 表面函数名(Input IN, inout SurfaceOutputStandardSpecular o)
        //而其中的SurfaceOutput、SurfaceOutputStandard、SurfaceOutputStandardSpecular结构体
        //就是我们的输出结构体

        //我们可以利用上节课中学习的输入结构体Input中的数据在表面函数中进行计算后
        //将计算结果赋值存储在输出结构体中
        //之后Unity会利用我们输出的数据作为光照模型函数的输入来进行各种光照相关的计算
        //这三个输出结构体是由Unity提前声明好的,不能自己增加和减少
        //如果我们没有对其中的某些变量赋值,会使用默认值

        #endregion

        #region 知识点二 输出结构体中有哪些成员

        //SurfaceOutput结构体(在Unity内置文件 Lighting.cginc 中声明)
        //当我们在编译指令中使用 Lambert 和 BlinnPhong 光照模型时,就需要使用该结构体
        //struct SurfaceOutput
        //{
        //    fixed3 Albedo;    //漫反射
        //    fixed3 Normal;    //切线空间法线
        //    fixed3 Emission;  //自发光:一般Unity会在片元着色器最后输出前 在输出颜色上直接叠加自发光颜色
        //    half Specular;    //镜面反射指数,范围0~1
        //    fixed Gloss;      //镜面反射强度
        //    镜面相关的两个参数,如果使用了BlinnPhong光照模型
        //    会使用该公式计算高光反射强度:float spec = pow(nh, s.Specular*128) * s.Gloss;
        //    fixed Alpha;      //透明通道:如果开启了透明度的话,会用该值和颜色进行混合
        //}

        //SurfaceOutputStandard结构体(在Unity内置文件 UnityPBSLighting.cginc 中声明)
        //当我们在编译指令中使用 Standard 光照模型时,就需要使用该结构体
        //我们一般称它为金属工作流输出结构体
        //struct SurfaceOutputStandard
        //{
        //    fixed3 Albedo;    //漫反射
        //    fixed3 Normal;    //切线空间法线
        //    fixed3 Emission;  //自发光
        //    half Metallic;    //0表示非金属,1表示金属
        //    half Smoothness;  //0表示最粗糙,1表示最光滑
        //    half Occlusion;   //环境光遮蔽,默认为1
        //    fixed Alpha;      //透明通道      
        //}

        //SurfaceOutputStandardSpecular结构体(在Unity内置文件 UnityPBSLighting.cginc 中声明)
        //当我们在编译指令中使用 StandardSpecular 光照模型时,就需要使用该结构体
        //我们一般称它为高光工作流输出结构体
        //struct SurfaceOutputStandardSpecular
        //{
        //    fixed3 Albedo;    //漫反射
        //    fixed3 Normal;    //切线空间法线
        //    fixed3 Emission;  //自发光
        //    half Smoothness;  //0表示最粗糙,1表示最光滑
        //    half Occlusion;   //环境光遮蔽,默认为1
        //    fixed Alpha;      //透明通道      
        //}

        #endregion

        #region 知识点三 表面着色器的渲染流程

        //我们已经了解了
        //编译指令、结构体、自定义函数(编译指令中的表面函数、光照模型、顶点函数、最终颜色修改函数)
        //我们只需要了解他们的具体作用便可以知道如何编写表面着色器
        //那现在我们通过一张图来了解下
        //表面着色器最终的渲染流程是如何把他们串联起来的

        #endregion

        #region 知识点四 为什么不一开始学习表面着色器

        //表面着色器是顶点/片元着色器的封装,要了解了顶点/片元着色器,才能了解表面着色器的本质原理
        //学会了更“底层”的顶点/片元着色器编写,以后学习其它的Shader语言,才会更加得心应手!

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏