32.菲涅尔反射基础实现

32.高级纹理-立方体纹理-菲涅尔反射-菲涅尔反射基础实现


32.1 知识点

菲涅耳近似等式

Schlick 菲涅耳近似等式:

[ R(θ) = R_0 + (1−R_0) (1−V·N)^5 ]

  • R(θ): 对应角度的菲涅耳反射率
  • R₀: 介质反射率
  • V: 视角单位向量
  • N: 法线单位向量

使用该等式可以计算菲涅耳反射率,并将其用于颜色计算。

菲涅耳反射的基础实现

主要步骤

  1. 新建 Shader
    复制 Lesson26_ReflectBase 的代码,基于其进行修改。

  2. 属性声明
    将反射率变量修改为 _FresnelScale,表示菲涅耳中介质的反射率。

  3. 结构体调整
    使用 Schlick 菲涅耳近似等式,需要世界空间下的视角方向和法线向量,在结构体中加入这两个变量。

  4. 顶点着色器
    在顶点着色器中为结构体变量赋值。

  5. 片元着色器
    利用 Schlick 菲涅耳近似等式计算菲涅耳反射率,并参与最终颜色计算。

新建Shader,取名Lesson32_FresnelBase,复制Lesson26_ReflectBase中代码,在其基础上进行修改

Shader "Unlit/Lesson32_FresnelBase"
{
    Properties
    {
        //立方体纹理
        _Cube("Cubemap", Cube) = ""{}
        //反射率
        _Reflectivity("Reflectivity", Range(0,1)) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" // 设置渲染类型为不透明(Opaque)
            "Queue"="Geometry" // 设置渲染队列为Geometry,通常用于普通几何物体
        }

        Pass
        {
            // 设置LightMode标签,指定光照模式为ForwardBase
            Tags
            {
                "LightMode"="ForwardBase" // 指定使用正向渲染中的基本光照模式
            }

            CGPROGRAM
            #pragma vertex vert     // 指定顶点着色器函数为vert
            #pragma fragment frag   // 指定片元着色器函数为frag

            // 包含Unity内置的CG库和光照相关的库
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            // 立方体纹理属性
            samplerCUBE _Cube;
            // 反射率属性
            float _Reflectivity;


            struct v2f
            {
                float4 pos:SV_POSITION; //裁剪空间下的顶点坐标
                //世界空间下的反射向量
                //我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来
                float3 worldRefl:TEXCOORD0;
            };

            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                
                //顶点坐标转换
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
                
                //计算反射光向量
                //1.计算世界空间下法线向量
                float3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
                //2.世界空间下的顶点坐标
                fixed3 worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
                //3.计算视角方向 内部是用摄像机位置 - 世界坐标位置 计算反射向量时要取反
                fixed3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
                //4.计算反射向量
                v2f.worldRefl = reflect(-worldViewDir, worldNormal);

                return v2f;
            }

            fixed4 frag(v2f v2f):SV_TARGET
            {
                //对立方体纹理利用对应的反射向量进行采样
                fixed4 cubemapColor = texCUBE(_Cube, v2f.worldRefl);
                //用采样颜色 * 反射率 决定最终的颜色效果
                return cubemapColor * _Reflectivity;
            }
            ENDCG
        }
    }
}

修改属性声明及其映射,将反射率变量修改为 _FresnelScale 菲涅耳中介质的反射率

Properties
{
    //立方体纹理
    _Cube("Cubemap", Cube) = ""{}
    //菲涅耳反射中 对应介质的反射率
    _FresnelScale("FresnelScale", Range(0,1)) = 1
}
samplerCUBE _Cube;
float _FresnelScale;

修改结构体v2f,由于使用Schlick菲涅耳近似等式,需要用到世界空间下视角方向、和法线向量,因此在结构体中加入这两个变量

struct v2f
{
    //裁剪空间下的顶点坐标
    float4 pos:SV_POSITION;
    //世界空间下的法线
    float3 worldNormal:NORMAL;
    //世界空间下视角的方向
    float3 worldViewDir:TEXCOORD0;
    //世界空间下的反射向量
    //我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来
    float3 worldRefl:TEXCOORD1;
};

顶点函数中,把临时的世界空间下视角方向和法线向量改成赋值给v2f

v2f vert(appdata_base appdata_base)
{
    v2f v2f;
    //顶点坐标转换
    v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
    //计算反射光向量
    //1.计算世界空间下法线向量
    v2f.worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
    //2.世界空间下的顶点坐标
    fixed3 worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
    //3.计算视角方向 内部是用摄像机位置 - 世界坐标位置 
    v2f.worldViewDir = UnityWorldSpaceViewDir(worldPos);
    //4.计算反射向量
    v2f.worldRefl = reflect(-v2f.worldViewDir, v2f.worldNormal);

    return v2f;
}

片元函数中,利用Schlick菲涅耳近似等式计算出菲涅耳反射率参与最终颜色计算

fixed4 frag(v2f v2f):SV_TARGET
{
    //对立方体纹理利用对应的反射向量进行采样
    fixed4 cubemapColor = texCUBE(_Cube, v2f.worldRefl);

    //利用schlick菲涅耳近似等式 计算菲涅耳反射率
    //  R(θ) = R0 + ( 1− R0 )( 1 − V·𝑵)^𝟓 
    //      R(θ):对应角度的菲涅耳反射率
    //      R0:介质反射率
    //      V:视角单位向量
    //      N:法线单位向量
    fixed fresnel = _FresnelScale + (1-_FresnelScale) * pow(1-dot(normalize(v2f.worldViewDir), normalize(v2f.worldNormal)), 5);

    //用采样颜色 * 反射率 决定最终的颜色效果
    return cubemapColor * fresnel;
}

创建材质复制shader查看效果,这样直接看,和反射的区别不明显,要下一节结合了漫反射和高光反射后更明显。把反射率都改成0之后可以看到有些细微的差别




32.2 知识点代码

Lesson32_高级纹理_立方体纹理_菲涅尔反射_菲涅尔反射基础实现.cs

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

public class Lesson32_高级纹理_立方体纹理_菲涅尔反射_菲涅尔反射基础实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 菲涅耳近似等式

        //Schlick 菲涅耳近似等式
        //  R(θ) = R0 + ( 1− R0 )( 1 − V·𝑵)^𝟓 
        //      R(θ):对应角度的菲涅耳反射率
        //      R0:介质反射率
        //      V:视角单位向量
        //      N:法线单位向量

        //使用该等式计算菲涅耳反射率,参与颜色计算

        #endregion

        #region 知识点二 菲涅尔反射的基础实现

        //1.新建Shader 复制Lesson26_ReflectBase中代码
        //  在其基础上进行修改

        //2.属性声明
        //  将反射率变量修改为 _FresnelScale 菲涅耳中介质的反射率

        //3.结构体
        //  由于使用Schlick菲涅耳近似等式
        //  需要用到世界空间下视角方向、和法线向量
        //  因此在结构体中加入这两个变量

        //4.顶点着色器
        //  关键步骤:
        //  结构体中变量赋值

        //5.片元着色器
        //  关键步骤:
        //  利用Schlick菲涅耳近似等式计算出菲涅耳反射率 参与最终颜色计算

        #endregion
    }
}

Lesson32_FresnelBase.shader

Shader "Unlit/Lesson32_FresnelBase"
{
    Properties
    {
        //立方体纹理
        _Cube("Cubemap", Cube) = ""{}
        //菲涅耳反射中 对应介质的反射率
        _FresnelScale("FresnelScale", Range(0,1)) = 1
    }
    SubShader
    {
        Tags{"RenderType"="Opaque" "Queue"="Geometry"}

        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            samplerCUBE _Cube;
            float _FresnelScale;

            struct v2f
            {
                //裁剪空间下的顶点坐标
                float4 pos:SV_POSITION;
                //世界空间下的法线
                float3 worldNormal:NORMAL;
                //世界空间下视角的方向
                float3 worldViewDir:TEXCOORD0;
                //世界空间下的反射向量
                //我们将把反射向量的计算放在顶点着色器函数中 节约性能 表现效果也不会太差 肉眼几乎分辨不出来
                float3 worldRefl:TEXCOORD1;
            };

            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                //顶点坐标转换
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
                //计算反射光向量
                //1.计算世界空间下法线向量
                v2f.worldNormal = UnityObjectToWorldNormal(appdata_base.normal);
                //2.世界空间下的顶点坐标
                fixed3 worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
                //3.计算视角方向 内部是用摄像机位置 - 世界坐标位置 
                v2f.worldViewDir = UnityWorldSpaceViewDir(worldPos);
                //4.计算反射向量
                v2f.worldRefl = reflect(-v2f.worldViewDir, v2f.worldNormal);

                return v2f;
            }

            fixed4 frag(v2f v2f):SV_TARGET
            {
                //对立方体纹理利用对应的反射向量进行采样
                fixed4 cubemapColor = texCUBE(_Cube, v2f.worldRefl);

                //利用schlick菲涅耳近似等式 计算菲涅耳反射率
                //  R(θ) = R0 + ( 1− R0 )( 1 − V·𝑵)^𝟓 
                //      R(θ):对应角度的菲涅耳反射率
                //      R0:介质反射率
                //      V:视角单位向量
                //      N:法线单位向量
                fixed fresnel = _FresnelScale + (1-_FresnelScale) * pow(1-dot(normalize(v2f.worldViewDir), normalize(v2f.worldNormal)), 5);

                //用采样颜色 * 反射率 决定最终的颜色效果
                return cubemapColor * fresnel;
            }

            ENDCG
        }
    }
}


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

×

喜欢就点赞,疼爱就打赏