27.反射结合漫反射及阴影

27.高级纹理-立方体纹理-反射-反射结合漫反射及阴影


27.1 知识点

CG中的插值函数

CG 提供了内置函数 lerp 用于进行插值计算:

  • 函数原型: lerp(a, b, t)
    • a: 插值起点值
    • b: 插值终点值
    • t: 插值因子,范围在 0~1 之间

内部计算公式: a + t * (b - a)

  • t = 0 时,插值结果为 a
  • t = 1 时,插值结果为 b
  • t 在 0~1 之间变化时,返回一个 ab 之间的线性插值结果

我们利用该函数来决定反射效果的程度,控制漫反射颜色与反射颜色之间的插值。

实现带漫反射的反射效果

主要步骤

  1. 新建 Shader 文件
    创建一个 Shader 文件,复制 Lesson26_ReflectBase 的代码,命名为 Lesson27_ReflectDiffuseShadow

  2. 属性声明
    在 Shader 中加入以下关键属性:

    • 漫反射颜色
    • 反射颜色
  3. v2f 结构体扩展
    为了在片元着色器中处理光和阴影,扩展 v2f 结构体,加入以下内容:

    • 世界空间法线
    • 世界空间顶点位置
    • 阴影宏
  4. 顶点着色器实现
    关键步骤:

    • 顶点坐标转换为裁剪坐标
    • 顶点法线转换为世界坐标
    • 顶点坐标转换为世界坐标
    • 计算视角方向及反射方向
    • 进行阴影相关计算
  5. 片元着色器实现
    关键步骤:

    • 获取光的方向
    • 计算兰伯特漫反射颜色
    • 使用 texCUBE 函数进行立方体纹理采样
    • 进行光照衰减计算
    • 使用 lerp 函数计算漫反射颜色和反射颜色的混合效果
  6. 添加 FallBack
    设置 FallBack,以确保当 Shader 无法完全支持硬件功能时,可以使用备用反射 Shader。


代码实现

新建一个Shader,复制Lesson26_ReflectBase的代码,取名Lesson27_ReflectDiffuseShadow

Shader "Unlit/Lesson27_ReflectDiffuseShadow"
{
    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
        }
    }
}

加入两个关键属性,漫反射颜色以及反射颜色,以及对应的属性映射声明

Properties
{
    //漫反射颜色
    _Color("Color", Color) = (1,1,1,1)
    //反射颜色
    _ReflectColor("ReflectColor", Color) = (1,1,1,1)
    //立方体纹理
    _Cube("Cubemap", Cube) = ""{}
    //反射率
    _Reflectivity("Reflectivity", Range(0,1)) = 1
}
fixed4 _Color;
fixed4 _ReflectColor;
samplerCUBE _Cube;
float _Reflectivity;

因为要处理光和阴影,所以添加multi_compile_fwdbase指令和AutoLight内置文件

#pragma vertex vert
#pragma fragment frag
//Shader变体
#pragma multi_compile_fwdbase

#include "UnityCG.cginc"
#include "Lighting.cginc"
//灯光内置文件
#include "AutoLight.cginc"

因为要在片元着色器中处理光和阴影,v2f结构体需要加入世界空间法线。世界空间顶点位置和阴影宏

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

顶点函数把之前临时变量改成赋值到v2f的变量中,同时添加接受阴影处理宏

v2f vert(appdata_base appdata_base)
{
    v2f v2f;

    //顶点坐标转换
    v2f.pos = UnityObjectToClipPos(appdata_base.vertex);

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

    //阴影相关处理(接受阴影)
    TRANSFER_SHADOW(v2f);

    return v2f;
}

片元函数得到光的方向并进行兰伯特漫反射颜色计。保留立方体纹理采样(利用texCUBE函数),使用UNITY_LIGHT_ATTENUATION进行衰减计算。最终利用lerp函数计算最终的颜色,颜色在漫反射颜色和反射颜色之间

fixed4 frag(v2f v2f):SV_TARGET
{
    //漫反射光照相关计算
    //传入世界空间顶点 得到世界空间下光的方向并单位化 
    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(v2f.worldPos));
    //漫反射颜色
    fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(normalize(v2f.worldNormal), worldLightDir));

    //对立方体纹理利用对应的反射向量进行采样
    fixed3 cubemapColor = texCUBE(_Cube, v2f.worldRefl).rgb * _ReflectColor.rgb;

    //得到光照衰减以及阴影相关的衰减值
    UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);

    //利用lerp 在漫反射颜色和反射颜色之间 进行插值
    //_Reflectivity为0或1就是极限状态
    //_Reflectivity=0 没有反射效果
    //_Reflectivity=1只有反射效果
    //_Reflectivity在0~1之间就是两者叠加
    fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + lerp(diffuse, cubemapColor, _Reflectivity) * atten;

    //用采样颜色 * 反射率 决定最终的颜色效果
    return fixed4(color, 1.0);
}

添加FallBack,保证当当前 Shader 不支持某些特性或图形硬件无法完全支持时,可以使用备用 的反射Shader

//内置的阴影投射的Pass
FallBack "Reflective/VertexLit"

创建材质赋值观察效果

创建材质并赋值,可以观察到以下效果:

  • 漫反射效果与上节课的基础实现有明显区别。
  • 修改反射率:
    • Reflectivity = 0 时,没有反射效果。
    • Reflectivity = 1 时,完全反射。
    • Reflectivity 在 0~1 之间时,颜色在漫反射颜色和反射颜色之间混合。
  • 支持阴影投射和接收。




27.2 知识点代码

Lesson27_高级纹理_立方体纹理_反射_反射结合漫反射及阴影.cs

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

public class Lesson27_高级纹理_立方体纹理_反射_反射结合漫反射及阴影 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 CG中的插值函数

        //插值函数
        //  CG中提供了内置函数  lerp 用于进行插值计算
        //  lerp(a, b, t) 
        //  a:插值起点值
        //  b:插值终点值
        //  t:插值因子,0~1之间
        //  内部计算公式:a + t * (b - a)
        //  当 t = 0 时,插值结果为a
        //  当 t = 1 时,插值结果为b
        //  当 t 在 0~1 之间变化时,返回一个a到b之间的一个线性插值结果
        //  我们将利用该函数来决定反射效果的程度
        //  用它在漫反射颜色和反射颜色之间进行插值控制反射程度

        #endregion

        #region 知识点二 实现带漫反射的 反射效果

        //新建一个Shader,复制Lesson26_ReflectBase的代码,取名Lesson27_ReflectDiffuseShadow

        //1.属性声明
        //  我们将加入2个关键属性
        //  1-1:漫反射颜色
        //  1-2:反射颜色

        //2.v2f结构体
        //  因为要在片元着色器中处理光和阴影
        //  需要加入
        //  2-1:世界空间法线:
        //  2-2:世界空间顶点位置
        //  2-3: 阴影宏

        //3.顶点着色器
        //  关键步骤:
        //  3-1:顶点坐标转裁剪坐标
        //  3-2:顶点法线转世界坐标
        //  3-3:顶点坐标转世界坐标
        //  3-4:世界空间下 视角方向计算
        //  3-5:视角反向逆向得到反射方向
        //  3-6:阴影相关计算

        //4.片元着色器
        //  关键步骤:
        //  4-1:得到光的方向
        //  4-2:兰伯特漫反射颜色计算
        //  4-3:立方体纹理采样(利用texCUBE函数)
        //  4-4:衰减计算
        //  4-5:最终颜色计算(利用lerp函数)

        //4.FallBack "Reflective/VertexLit"

        #endregion
    }
}

Lesson27_ReflectDiffuseShadow.shader

Shader "Unlit/Lesson27_ReflectDiffuseShadow"
{
    Properties
    {
        //漫反射颜色
        _Color("Color", Color) = (1,1,1,1)
        //反射颜色
        _ReflectColor("ReflectColor", Color) = (1,1,1,1)
        //立方体纹理
        _Cube("Cubemap", Cube) = ""{}
        //反射率
        _Reflectivity("Reflectivity", Range(0,1)) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" "Queue"="Geometry"
        }

        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //Shader变体
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            //灯光内置文件
            #include "AutoLight.cginc"

            fixed4 _Color;
            fixed4 _ReflectColor;
            samplerCUBE _Cube;
            float _Reflectivity;

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

            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                //顶点坐标转换
                v2f.pos = UnityObjectToClipPos(appdata_base.vertex);

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

                //阴影相关处理(接受阴影)
                TRANSFER_SHADOW(v2f);

                return v2f;
            }

            fixed4 frag(v2f v2f):SV_TARGET
            {
                //漫反射光照相关计算
                //传入世界空间顶点 得到世界空间下光的方向并单位化 
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(v2f.worldPos));
                //漫反射颜色
                fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(normalize(v2f.worldNormal), worldLightDir));

                //对立方体纹理利用对应的反射向量进行采样
                fixed3 cubemapColor = texCUBE(_Cube, v2f.worldRefl).rgb * _ReflectColor.rgb;

                //得到光照衰减以及阴影相关的衰减值
                UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);

                //利用lerp 在漫反射颜色和反射颜色之间 进行插值
                //_Reflectivity为0或1就是极限状态
                //_Reflectivity=0 没有反射效果
                //_Reflectivity=1只有反射效果
                //_Reflectivity在0~1之间就是两者叠加
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + lerp(diffuse, cubemapColor, _Reflectivity) * atten;

                //用采样颜色 * 反射率 决定最终的颜色效果
                return fixed4(color, 1.0);
            }
            ENDCG
        }
    }

    //内置的阴影投射的Pass
    FallBack "Reflective/VertexLit"
}


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

×

喜欢就点赞,疼爱就打赏