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 之间变化时,返回一个a
到b
之间的线性插值结果
我们利用该函数来决定反射效果的程度,控制漫反射颜色与反射颜色之间的插值。
实现带漫反射的反射效果
主要步骤
新建 Shader 文件
创建一个 Shader 文件,复制Lesson26_ReflectBase
的代码,命名为Lesson27_ReflectDiffuseShadow
。属性声明
在 Shader 中加入以下关键属性:- 漫反射颜色
- 反射颜色
v2f 结构体扩展
为了在片元着色器中处理光和阴影,扩展 v2f 结构体,加入以下内容:- 世界空间法线
- 世界空间顶点位置
- 阴影宏
顶点着色器实现
关键步骤:- 顶点坐标转换为裁剪坐标
- 顶点法线转换为世界坐标
- 顶点坐标转换为世界坐标
- 计算视角方向及反射方向
- 进行阴影相关计算
片元着色器实现
关键步骤:- 获取光的方向
- 计算兰伯特漫反射颜色
- 使用
texCUBE
函数进行立方体纹理采样 - 进行光照衰减计算
- 使用
lerp
函数计算漫反射颜色和反射颜色的混合效果
添加 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