15.阴影-不透明物体阴影-让不透明物体投射阴影
15.1 知识点
感受 Fallback 的作用
多光源shader不能投射阴影和接受阴影
- 复制之前编写的多种光源综合实现的
ShaderLesson11_ForwardLighting.shader
。命名为Lesson15_ForwardLighting.shader
。 - 新建一个材质球,将其 Shader 设置为
Lesson15_ForwardLighting.shader
。 - 将该材质球赋值给较大的立方体,发现该立方体:
- 不再投射阴影。
- 不再接受阴影。
原因分析
不投射阴影的原因:
- 该 Shader 中没有 LightMode 为 ShaderCaster 的 Pass,无法参与光源的阴影映射纹理的计算。
不接受阴影的原因:
- 该 Shader 并没有对阴影映射相关纹理进行采样,也没有进行阴影相关颜色运算。
解决方案 给多光源 Shader 添加 Fallback “Specular”
- Unity 会寻找 LightMode 为 ShaderCaster 的 Pass 来处理。如果该 Shader 没有该 Pass,会在其 Fallback 指定的 Shader 中寻找,直到找到为止。
- 在该 Shader 最后加上
Fallback "Specular"
,即可让该立方体投射阴影。
Shader "Unlit/Lesson15_ForwardLighting"
{
Properties
{
//...
}
SubShader
{
//...
}
//找不到LightMode为ShaderCaster的Pass 使用Specular的shader
Fallback "Specular"
}
让物体投射阴影
主要步骤
让物体投射阴影
主要步骤
- 实现 LightMode(灯光模式)为 ShadowCaster(阴影投射)的 Pass(渲染通道),这样物体才能参与到光源的阴影映射纹理计算中。
- 使用以下内容实现阴影效果:
- 编译指令:
#pragma multi_compile_shadowcaster
告诉 Unity 编译器生成多个着色器变体,用于支持不同类型的阴影(如 SM、SSSM 等)。确保着色器在所有可能的阴影投射模式下正确渲染。 - 内置文件:
#include "UnityCG.cginc"
包含关键的阴影计算相关宏。 - 三个关键宏:
- V2F_SHADOW_CASTER:
- 用于定义顶点到片元着色器的阴影投射结构体数据。
- 提供标准成员变量,用于在阴影投射路径中传递顶点数据到片元着色器。
- TRANSFER_SHADOW_CASTER_NORMALOFFSET:
- 用于在顶点着色器中计算并传递阴影投射所需变量:
- 将对象空间的顶点位置转换为裁剪空间的位置。
- 考虑法线偏移,减轻阴影失真问题(特别是自阴影)。
- 传递顶点的投影空间位置,用于后续的阴影计算。
- 用于在顶点着色器中计算并传递阴影投射所需变量:
- SHADOW_CASTER_FRAGMENT:
- 在片元着色器中使用,将深度值写入到阴影映射纹理中。
- V2F_SHADOW_CASTER:
- 编译指令:
- 利用这些内容在Shader中实现代码
复制Lesson15_ForwardLighting.shader,改名叫。注释掉Fallback “Specular”,我们自己手动实现让物体投射阴影
Shader "Unlit/Lesson15_ForwardLighting"
{
Properties
{
//...
}
SubShader
{
//...
}
// //找不到LightMode为ShaderCaster的Pass 使用Specular的shader
// Fallback "Specular"
}
实现 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道),设置一些预编译指令
//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
投射阴影Pass中添加multi_compile_shadowcaster编译指令和引用UnityCG内置文件
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
// 其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"
声明v2f结构体,结构体内声明一个顶点到片元着色器阴影投射结构体数据宏,这个宏定义了一些标准的成员变量,相当于不用我们自己声明阴影效果的变量了
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;
};
顶点函数使用转移阴影投射器法线偏移宏
v2f vert(appdata_base v)
{
v2f v2f;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
片元函数使用阴影投射片元宏
float4 frag(v2f v2f):SV_Target
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
创建材质赋值shader并给立方体,可以看到我们自己写的Pass能让立方题投射阴影了
关于投射阴影相关代码的建议
- 投射阴影相关的代码较为通用。
- 建议直接通过 Fallback 调用 Unity 默认 Shader 中的相关代码,而不必自己实现这些 Shader 代码。
15.2 知识点代码
Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影 : MonoBehaviour
{
void Start()
{
#region 知识点一 感受Fallback的作用
//我们复制之前编写的多种光源综合实现Shader Lesson11_ForwardLighting.shader
//命名为Lesson15_ForwardLighting.shader
//我们新建一个材质球
//将其的Shader设置为Lesson15_ForwardLighting.shader
//并将该材质球赋值给较大的立方体使用
//我们会发现该立方体不再投射阴影也不再接受阴影
//1.不投射阴影的原因
// 该Shader中没有LightMode为ShaderCaster的Pass,无法参与光源的阴影映射纹理的计算
//2.不接收阴影的原因
// 该Shader并没有对阴影映射相关纹理进行采样,没有进行阴影相关颜色运算
//我们之前学习理论知识时提到过
//Unity会寻找LightMode为ShaderCaster的Pass来进行处理,如果该Shader没有该Pass
//会在它FallBack指定的Shader中寻找,直到找到为止
//我们现在 在该Shader 最后加上FallBack "Specular"
//便可以让该立方体投射阴影
#endregion
#region 知识点二 让物体投射阴影
//物体向其它物体投射阴影的关键点是:
//1. 需要实现 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道)
// 这样该物体才能参与到光源的阴影映射纹理计算中
//2. 一个编译指令,一个内置文件,三个关键宏
// 编译指令:
// #pragma multi_compile_shadowcaster
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 内置文件:
// #include "UnityCG.cginc"
// 其中包含了关键的阴影计算相关的宏
// 三个关键宏:
// 2-1.V2F_SHADOW_CASTER
// 顶点到片元着色器阴影投射结构体数据宏
// 这个宏定义了一些标准的成员变量
// 这些变量用于在阴影投射路径中传递顶点数据到片元着色器
// 我们主要在结构体中使用
// 2-2.TRANSFER_SHADOW_CASTER_NORMALOFFSET
// 转移阴影投射器法线偏移宏
// 用于在顶点着色器中计算和传递阴影投射所需的变量
// 主要做了
// 2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
// 2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
// 2-2-3.传递顶点的投影空间位置,用于后续的阴影计算
// 我们主要在顶点着色器中使用
// 2-3.SHADOW_CASTER_FRAGMENT
// 阴影投射片元宏
// 将深度值写入到阴影映射纹理中
// 我们主要在片元着色器中使用
//3.利用这些内容在Shader中实现代码
#endregion
#region 知识点三 关于投射阴影相关的代码的建议
//由于投射阴影相关的代码较为通用
//因此建议大家不用自己去实现相关Shader代码
//直接通过FallBack调用Unity中默认Shader中的相关代码即可
#endregion
}
}
Lesson15_OpaqueObjectCastShadows
Shader "Unlit/Lesson15_OpaqueObjectCastShadows"
{
Properties
{
//材质的漫反射光照颜色
_MainColor("MainColor", Color) = (1,1,1,1)
//高光反射颜色
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
//光泽度
_SpecularNum("SpecularNum", Range(0, 20)) = 0.5
}
SubShader
{
//Bass Pass 基础渲染通道
Pass
{
//设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdbase
//引用对应的内置文件
//主要是为了之后 的 比如内置结构体使用,内置变量使用
#include "UnityCG.cginc"
#include "Lighting.cginc"
//材质的漫反射颜色
fixed4 _MainColor;
//对应属性当中的颜色和光泽度
fixed4 _SpecularColor;
float _SpecularNum;
//顶点着色器传递给片元着色器的内容
struct v2f
{
//裁剪空间的位置
float4 pos:SV_POSITION;
//基于世界坐标系下的顶点位置
float3 wPos:TEXCOORD0;
//基于世界坐标系下的法线
float3 wNormal:NORMAL;
};
//得到兰伯特光照模型计算的颜色 (逐片元)
fixed3 getLambertColor(in float3 wNormal)
{
//_WorldSpaceLightPos0.xyz 世界坐标系下光源方向 _WorldSpaceLightPos0是四维向量 只需要xyz即可
//normalize 把向量归一化
//lightDir 得到光源单位向量
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//_LightColor0光 照颜色
//.rgb 代表只使用颜色进行计算 透明度不考虑
//color 计算出了兰伯特光照的漫反射颜色
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir));
return color;
}
//得到Blinn Phong式高光反射模型计算的颜色(逐片元)
fixed3 getSpecularColor(in float3 wPos, in float3 wNormal)
{
//1.视角单位向量
//用观察者的位置(摄像机的位置)减去世界空间下顶点坐标 得到的就是视角方向 并且进行单位化
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos);
//2.光的反射单位向量
//光的方向
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//半角方向向量
float3 halfA = normalize(viewDir + lightDir);
//color = 光源颜色 * 材质高光反射颜色 * pow( max(0, dot(视角单位向量, 光的反射单位向量)), 光泽度 )
fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum);
return color;
}
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//转换模型空间下的顶点到裁剪空间中
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//转换模型空间下的法线到世界空间下
v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);
//转换模型空间下的顶点到世界空间
v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//计算兰伯特光照颜色
fixed3 lambertColor = getLambertColor(v2f.wNormal);
//计算BlinnPhong式高光反射颜色
fixed3 specularColor = getSpecularColor(v2f.wPos, v2f.wNormal);
//衰减值
//因为我们这是基础Pass 一般是处理场景中最亮的平行光 所以可以认为不存在衰减值的 衰减值默认是1
//但是之后附加Pass的衰减值就会变化了
fixed atten = 1;
//物体表面光照颜色 = 环境光颜色 + 兰伯特光照模型所得颜色 + 布林Phong式高光反射光照模型所得颜色
//衰减值 会和 漫反射颜色 + 高光反射颜色 后 再进行乘法运算
fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten;
return fixed4(blinnPhongColor.rgb, 1);
}
ENDCG
}
//Additional Pass 附加渲染通道
Pass
{
//设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
Tags
{
"LightMode"="ForwardAdd"
}
//线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdadd
//引用对应的内置文件
//主要是为了之后 的 比如内置结构体使用,内置变量使用
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
//材质的漫反射颜色
fixed4 _MainColor;
//对应属性当中的颜色和光泽度
fixed4 _SpecularColor;
float _SpecularNum;
//顶点着色器传递给片元着色器的内容
struct v2f
{
//裁剪空间的位置
float4 pos:SV_POSITION;
//基于世界坐标系下的顶点位置
float3 wPos:TEXCOORD0;
//基于世界坐标系下的法线
float3 wNormal:NORMAL;
};
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//转换模型空间下的顶点到裁剪空间中
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//转换模型空间下的法线到世界空间下
v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);
//转换模型空间下的顶点到世界空间
v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//兰伯特漫反射
//公式:漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
//世界标准法线
fixed3 worldNormal = normalize(v2f.wNormal);
#if defined(_DIRECTIONAL_LIGHT)
//平行光 光的方向 其实就是它的位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
//点光源和聚光灯 光的方向 是 光的位置 - 顶点位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - v2f.wPos);
#endif
// 漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));
//BlinnPhong高光反射
//公式:高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
//视角方向 摄像机位置 - 顶点位置
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - v2f.wPos.xyz);
//半角方向向量 光方向 + 视角方向 平行四边形法则
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(
max(0, dot(worldNormal, halfDir)), _SpecularNum);
//衰减值
#ifdef USING_DIRECTIONAL_LIGHT
//平行光
fixed atten = 1;
#else
#if defined(POINT)
//点光源
//将世界坐标系下顶点转到光源空间下
float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
//利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
#elif defined(SPOT)
//聚光灯
//将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
#else
fixed atten = 1;
#endif
#endif
// 衰减值 不准确的判断
// #if defined(_DIRECTIONAL_LIGHT)
// //平行光
// fixed atten = 1;
// #elif defined(_POINT_LIGHT)
// //点光源
// //将世界坐标系下顶点转到光源空间下
// float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
// //利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
// fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
// #elif defined(_SPOT_LIGHT)
// //聚光灯
// //将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
// float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
// fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
// tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
// tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
// #else
// fixed atten = 1;
// #endif
//在附加渲染通道中不需要在加上环境光UNITY_LIGHTMODEL_AMBIENT.rgb颜色了
//因为它只需要计算一次 在基础渲染通道中已经计算了
return fixed4((diffuse + specular) * atten, 1);
}
ENDCG
}
//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
// 其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f v2f;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
float4 frag(v2f v2f):SV_Target
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
ENDCG
}
}
// //找不到LightMode为ShaderCaster的Pass 使用Specular的shader
// Fallback "Specular"
}
Lesson15_ForwardLighting
Shader "Unlit/Lesson15_ForwardLighting"
{
Properties
{
//材质的漫反射光照颜色
_MainColor("MainColor", Color) = (1,1,1,1)
//高光反射颜色
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
//光泽度
_SpecularNum("SpecularNum", Range(0, 20)) = 0.5
}
SubShader
{
//Bass Pass 基础渲染通道
Pass
{
//设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdbase
//引用对应的内置文件
//主要是为了之后 的 比如内置结构体使用,内置变量使用
#include "UnityCG.cginc"
#include "Lighting.cginc"
//材质的漫反射颜色
fixed4 _MainColor;
//对应属性当中的颜色和光泽度
fixed4 _SpecularColor;
float _SpecularNum;
//顶点着色器传递给片元着色器的内容
struct v2f
{
//裁剪空间的位置
float4 pos:SV_POSITION;
//基于世界坐标系下的顶点位置
float3 wPos:TEXCOORD0;
//基于世界坐标系下的法线
float3 wNormal:NORMAL;
};
//得到兰伯特光照模型计算的颜色 (逐片元)
fixed3 getLambertColor(in float3 wNormal)
{
//_WorldSpaceLightPos0.xyz 世界坐标系下光源方向 _WorldSpaceLightPos0是四维向量 只需要xyz即可
//normalize 把向量归一化
//lightDir 得到光源单位向量
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//_LightColor0光 照颜色
//.rgb 代表只使用颜色进行计算 透明度不考虑
//color 计算出了兰伯特光照的漫反射颜色
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir));
return color;
}
//得到Blinn Phong式高光反射模型计算的颜色(逐片元)
fixed3 getSpecularColor(in float3 wPos, in float3 wNormal)
{
//1.视角单位向量
//用观察者的位置(摄像机的位置)减去世界空间下顶点坐标 得到的就是视角方向 并且进行单位化
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos);
//2.光的反射单位向量
//光的方向
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//半角方向向量
float3 halfA = normalize(viewDir + lightDir);
//color = 光源颜色 * 材质高光反射颜色 * pow( max(0, dot(视角单位向量, 光的反射单位向量)), 光泽度 )
fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum);
return color;
}
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//转换模型空间下的顶点到裁剪空间中
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//转换模型空间下的法线到世界空间下
v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);
//转换模型空间下的顶点到世界空间
v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//计算兰伯特光照颜色
fixed3 lambertColor = getLambertColor(v2f.wNormal);
//计算BlinnPhong式高光反射颜色
fixed3 specularColor = getSpecularColor(v2f.wPos, v2f.wNormal);
//衰减值
//因为我们这是基础Pass 一般是处理场景中最亮的平行光 所以可以认为不存在衰减值的 衰减值默认是1
//但是之后附加Pass的衰减值就会变化了
fixed atten = 1;
//物体表面光照颜色 = 环境光颜色 + 兰伯特光照模型所得颜色 + 布林Phong式高光反射光照模型所得颜色
//衰减值 会和 漫反射颜色 + 高光反射颜色 后 再进行乘法运算
fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten;
return fixed4(blinnPhongColor.rgb, 1);
}
ENDCG
}
//Additional Pass 附加渲染通道
Pass
{
//设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
Tags
{
"LightMode"="ForwardAdd"
}
//线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdadd
//引用对应的内置文件
//主要是为了之后 的 比如内置结构体使用,内置变量使用
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
//材质的漫反射颜色
fixed4 _MainColor;
//对应属性当中的颜色和光泽度
fixed4 _SpecularColor;
float _SpecularNum;
//顶点着色器传递给片元着色器的内容
struct v2f
{
//裁剪空间的位置
float4 pos:SV_POSITION;
//基于世界坐标系下的顶点位置
float3 wPos:TEXCOORD0;
//基于世界坐标系下的法线
float3 wNormal:NORMAL;
};
v2f vert(appdata_base appdata_base)
{
v2f v2f;
//转换模型空间下的顶点到裁剪空间中
v2f.pos = UnityObjectToClipPos(appdata_base.vertex);
//转换模型空间下的法线到世界空间下
v2f.wNormal = UnityObjectToWorldNormal(appdata_base.normal);
//转换模型空间下的顶点到世界空间
v2f.wPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
return v2f;
}
fixed4 frag (v2f v2f) : SV_Target
{
//兰伯特漫反射
//公式:漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
//世界标准法线
fixed3 worldNormal = normalize(v2f.wNormal);
#if defined(_DIRECTIONAL_LIGHT)
//平行光 光的方向 其实就是它的位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
//点光源和聚光灯 光的方向 是 光的位置 - 顶点位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - v2f.wPos);
#endif
// 漫反射颜色 = 光颜色 * 属性中颜色 * max(0, dot(世界坐标系下的法线, 世界坐标系下的光方向));
fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));
//BlinnPhong高光反射
//公式:高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
//视角方向 摄像机位置 - 顶点位置
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - v2f.wPos.xyz);
//半角方向向量 光方向 + 视角方向 平行四边形法则
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 高光颜色 = 光颜色 * 属性中的高光颜色 * pow(max(0, dot(世界坐标系法线, 世界坐标系半角向量)), 光泽度);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularNum);
//衰减值
#ifdef USING_DIRECTIONAL_LIGHT
//平行光
fixed atten = 1;
#else
#if defined(POINT)
//点光源
//将世界坐标系下顶点转到光源空间下
float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
//利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
#elif defined(SPOT)
//聚光灯
//将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
#else
fixed atten = 1;
#endif
#endif
// 衰减值 不准确的判断
// #if defined(_DIRECTIONAL_LIGHT)
// //平行光
// fixed atten = 1;
// #elif defined(_POINT_LIGHT)
// //点光源
// //将世界坐标系下顶点转到光源空间下
// float3 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1)).xyz;
// //利用这个坐标得到距离的平方 然后再再光源纹理中映射得到衰减值
// fixed atten = tex2D(_LightTexture0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL;
// #elif defined(_SPOT_LIGHT)
// //聚光灯
// //将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
// float4 lightCoord = mul(unity_WorldToLight, float4(v2f.wPos, 1));
// fixed atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
// tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
// tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
// #else
// fixed atten = 1;
// #endif
//在附加渲染通道中不需要在加上环境光UNITY_LIGHTMODEL_AMBIENT.rgb颜色了
//因为它只需要计算一次 在基础渲染通道中已经计算了
return fixed4((diffuse + specular)*atten, 1);
}
ENDCG
}
}
//找不到LightMode为ShaderCaster的Pass 使用Specular的shader
Fallback "Specular"
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com