11.多种光源-综合实现
11.1 知识点
重要知识回顾
前向渲染路径中如何处理光源
- 两个Pass:
- Base Pass(基础渲染通道)
- Additional Pass(附加渲染通道)
在Shader当中如何判断多种光源
#if defined(_DIRECTIONAL_LIGHT)
// 平行光逻辑
#elif defined (_POINT_LIGHT)
// 点光源逻辑
#elif defined (_SPOT_LIGHT)
// 聚光灯逻辑
#else
// 其他逻辑
#endif
点光源衰减值计算
float3 lightCoord = mul(unity_WorldToLight, float4(worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL;
聚光灯衰减值计算
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * // 第一部分
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * // 第二部分
tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; // 第三部分
前向渲染路径中处理多种光源的综合实现
主要步骤
- 新建一个Shader文件,删除其中无用代码。
- 复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照
Lesson20_BlinnPhong_Pixel.shader
。 - 已存在的Pass即为Base Pass(基础渲染通道)。添加编译指令
#pragma multi_compile_fwdbase
,确保光照衰减等变量被正确赋值并编译所有变体。 - 复制Base Pass并修改为Additional Pass(附加渲染通道)。
- 修改
LightMode
为ForwardAdd
。 - 添加混合命令
Blend One One
,开启线性减淡效果。 - 添加编译指令
#pragma multi_compile_fwdadd
,确保附加渲染通道变量正确赋值并编译所有变体。 - 修改相关代码,计算光照方向与衰减值。
新建Shader文件并删除无用代码
Shader "Unlit/Lesson11_ForwardLighting"
{
}
复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照 Lesson20_BlinnPhong_Pixel.shader。其中已存在的Pass,就是我们的BasePass(基础渲染通道)
Shader "Unlit/Lesson11_ForwardLighting"
{
Properties
{
//材质的漫反射光照颜色
_MainColor("MainColor", Color) = (1,1,1,1)
//高光反射颜色
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
//光泽度
_SpecularNum("SpecularNum", Range(0, 20)) = 0.5
}
SubShader
{
Pass
{
//设置我们的光照模式 ForwardBase这种向前渲染模式 主要是用来处理 不透明物体的 光照渲染的
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//引用对应的内置文件
//主要是为了之后 的 比如内置结构体使用,内置变量使用
#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);
//物体表面光照颜色 = 环境光颜色 + 兰伯特光照模型所得颜色 + 布林Phong式高光反射光照模型所得颜色
fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + lambertColor + specularColor;
return fixed4(blinnPhongColor.rgb, 1);
}
ENDCG
}
}
}
基础渲染通道加上一个编译指令#pragma multi_compile_fwdbase
,该指令可以保证我们在Shader中使用光照衰减等光照等变量可以被正确赋值,并且会帮助我们编译 BasePass 中所有变体
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdbase
//...
ENDCG
在基础渲染通道中的片元函数感受衰减值的作用,因为BasePass一般是处理场景中最亮的平行光,所以可以认为不存在衰减值的,衰减值默认是1。但是之后AdditionalPass的衰减值就会变化了
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);
}
复制BasePass(基础渲染通道),基于它来修改我们的Additional Pass(附加渲染通道)
SubShader
{
//Bass Pass 基础渲染通道
Pass
{
//...
}
//Additional Pass 附加渲染通道
Pass
{
//...
}
}
附加渲染通道LightMode 改为 ForwardAdd
//设置我们的光照模式 ForwardAdd这种向前渲染模式 主要是用来处理 附加光照渲染的
Tags
{
"LightMode"="ForwardAdd"
}
附加渲染通道加入混合命令Blend One One 表示开启混合 线性减淡效果
//线性减淡的效果 进行 光照颜色混合
Blend One One
附加渲染通道删除编译指令multi_compile_fwdbase,加入编译指令#pragma multi_compile_fwdadd。该指令保证我们在附加渲染通道中能访问到正确的光照变量,并且会帮助我们编译Additional Pass中所有变体
//用于帮助我们编译所有变体 并且保证衰减相关光照变量能够正确赋值到对应的内置变量中
#pragma multi_compile_fwdadd
附加渲染通道删除getLambertColor函数和getSpecularColor函数,等一下自己重新在片元函数手写加深印象,并且会有一些修改
修改片元函数相关代码,基于不同的光照类型来计算兰伯特,BlinnPhong和衰减值
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);
//衰减值
#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);
}
创建材质,创建多种光源可以查看到多光源shader可以收到不同类型光源叠加的效果。导入Shader开发入门的纹理_遮罩纹理_综合实现的shader,发现只能收到平行光
可能会出现无法准确判断光源类型的问题
主要问题
目前我们采用的宏判断,可能会出现无法准确判断光源类型的问题,导致探照灯在平面上显示时会出现问题。因此我们采用AutoLight内置文件中的宏来进行判断。需要对代码做出修改。
修改解决方案
- 引用
AutoLight.cginc
内置文件。 - 使用其光源类型判断宏替代现有判断逻辑。
- 修改代码确保渲染效果正确。
附加渲染通道中添加引用AutoLight内置文件。我们将使用其中的光源类型判断宏,来判断光源类型。
#include "AutoLight.cginc"
修改光源类型判断宏相关代码。将光源类型判断宏判断逻辑进行修改,否则在处理聚光灯等光源时无法正确判断类型导致渲染效果错误
#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
可以看到聚光灯被正确识别了
总结
- 需要理解光源类型判断宏的逻辑。
- 注意附加通道的混合与光衰减值计算。
- 可扩展模型到动态范围。
11.2 知识点代码
Lesson11_多种光源_综合实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson11_多种光源_综合实现 : MonoBehaviour
{
void Start()
{
#region 重要知识回顾
#region 1.前向渲染路径中如何处理光源
//两个Pass
//Base Pass(基础渲染通道)
//Additional Pass(附加渲染通道)
#endregion
#region 2.在Shader当中如何判断多种光源
//#if defined(_DIRECTIONAL_LIGHT)
// 平行光逻辑
//#elif defined (_POINT_LIGHT)
// 点光源逻辑
//#elif defined (_SPOT_LIGHT)
// 聚光灯逻辑
//#else
// 其他逻辑
//#endif
#endregion
#region 3.点光源衰减值计算
// float3 lightCoord = mul(unity_WorldToLight, float4(worldPos, 1)).xyz;
// fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL;
#endregion
#region 4.聚光灯衰减值计算
// float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
// fixed atten = (lightCoord.z > 0) * //第一步
// tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //第二步
// tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; //第三步
#endregion
#endregion
#region 知识点一 前向渲染路径中处理多种光源的综合实现
//主要步骤:
//1.新建一个Shader文件,删除其中无用代码
//2.复制Shader开发入门中的Blinn-Phong光照模型的逐片元光照 Lesson20_BlinnPhong_Pixel.shader
//3.其中已存在的Pass,就是我们的BasePass(基础渲染通道)
// 我们需要为它加上一个编译指令#pragma multi_compile_fwdbase
// 该指令可以保证我们在Shader中使用光照衰减等光照等变量可以被正确赋值
// 并且会帮助我们编译 BasePass 中所有变体
//4.复制BasePass,基于它来修改我们的Additional Pass(附加渲染通道)
//5.LightMode 改为 ForwardAdd
//6.加入混合命令Blend One One 表示开启混合 线性减淡效果
//7.加入编译指令#pragma multi_compile_fwdadd
// 该指令保证我们在附加渲染通道中能访问到正确的光照变量
// 并且会帮助我们编译Additional Pass中所有变体
//8.修改相关代码,基于不同的光照类型来计算衰减值
// 8-1:光的方向计算方向修改
// 8-2:基于不同光照类型计算衰减值
#endregion
#region 知识点二 可能会出现无法准确判断光源类型的问题
// 主要问题:
// 目前我们采用的宏判断,可能会出现无法准确判断光源类型的问题
// 导致探照灯在平面上显示时会出现问题,因此我们采用AutoLight内置文件中的宏来进行判断
//
// 需要对代码做出如下修改:
// 1.添加引用一个内置文件
// 2.修改光源类型判断宏相关代码
#endregion
}
}
Lesson11_ForwardLighting.shader
Shader "Unlit/Lesson11_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
}
}
}
Lesson11_Mask_Texture_Comprehensive.shader
Shader "Unlit/Lesson11_Mask_Texture_Comprehensive"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)
_MainTex("MainTex", 2D) = ""{}
_BumpMap("BumpMap", 2D) = ""{}
_BumpScale("BumpScale", Range(0,1)) = 1
_SpecularMask("SpecularMask", 2D) = ""{}//高光遮罩纹理
_SpecularScale("SpecularScale", Float) = 1 //遮罩系数
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
_SpecularNum("SpecularNum", Range(8,256)) = 18
}
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
float4 _MainColor; //漫反射颜色
sampler2D _MainTex; //颜色纹理
float4 _MainTex_ST; //颜色纹理的缩放和平移
sampler2D _BumpMap; //法线纹理
float4 _BumpMap_ST; //法线纹理的缩放和平移
float _BumpScale; //凹凸程度
sampler2D _SpecularMask; //高光遮罩纹理
float4 _SpecularMask_ST; //高光遮罩纹理的缩放和平移
float _SpecularScale; //遮罩系数
float4 _SpecularColor; //高光颜色
fixed _SpecularNum; //光泽度
struct v2f
{
float4 pos:SV_POSITION;
//float2 uvTex:TEXCOORD0;
//float2 uvBump:TEXCOORD1;
//我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
//也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下的
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下的
float3 viewDir:TEXCOORD2;
};
v2f vert(appdata_full appdata_full)
{
v2f v2f;
//把模型空间下的顶点转到裁剪空间下
v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
//计算纹理的缩放偏移
v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
//切线、副切线、法线
//计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
tangent.w;
//转换矩阵
float3x3 rotation = float3x3(appdata_full.tangent.xyz,
binormal,
appdata_full.normal);
//模型空间下的光的方向
//data.lightDir = ObjSpaceLightDir(v.vertex);
//乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));
//模型空间下的视角的方向
//data.viewDir = ObjSpaceViewDir(v.vertex);
v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));
return v2f;
}
fixed4 frag(v2f v2f) : SV_Target
{
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//接下来就来处理 带颜色纹理的 布林方光照模型计算
//颜色纹理和漫反射颜色的 叠加
fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
//兰伯特
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb
* max(0, dot(tangentNormal, normalize(v2f.lightDir)));
//半角向量
float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
//1.从纹理中取出对应的遮罩掩码值(颜色的RGB值都可以使用)
// fixed maskNum = tex2D(_SpecularMask, v2f.uv.xy).r;
//2.用该掩码值和遮罩系数(我们自己定义的)相乘得到遮罩值
// fixed specularMaskNum = maskNum * _SpecularScale;
fixed specularMaskNum = tex2D(_SpecularMask, v2f.uv.xy).r * _SpecularScale;
//3.用该遮罩值和高光反射计算出来的颜色相乘 * specularMaskNum
//高光反射
fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb
* pow(max(0, dot(tangentNormal, halfA)), _SpecularNum) * specularMaskNum;
//布林方
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com