一 基础简介
1.1 光源类型
【平行光】场景中唯一的全局光,光源信息可以影响场景中所有物体。
【环境光】是Edit->Render Setting里面的Ambient Light的值。在Shader中获取它只需要访问全局变量UNITY_LIGHTMODEL_AMBIENT即可。它是全局变量,因此在在哪个Pass里访问都可以。
【点光源】以一个中心点向周围扩散的光源,有衰退。
【聚光灯】像聚光灯一样从一个点向某个方向和范围散发光亮。
1.2 灯光渲染模式(Render Mode)
决定一个灯光是哪种处理模式取决于它的Render Mode,设置方式为点击灯光后,在Inspector中设置Render Mode:
【Not Important】所有非平行光源,会逐顶点或者球谐函数处理
【Important】所有非平行光源,会逐像素处理
【设置任何模式】场景中的平行光源都是按逐像素处理
1.3 渲染模式(LightMode)
渲染模式是关于渲染方式的设置。
渲染模式需要在shader中的 Pass > Tags中设置
pass内的tags有别与subshader中的tags,主要用于渲染模式设置
pass内的tags说明
取值 | 例子 | 说明 |
---|---|---|
Always | “LightMode”=“Always” | 不管是用哪种渲染路径,该pass总是会被渲染。但不计算任何光照 |
Forwardbase | “LightMode”=“ForwardBase” | 用于向前渲染,该pass会计算环境光,重要的平行光,逐顶点/SH光源和lightmaps |
ForwardAdd | “LightMode”=“ForwardAdd” | 用于向前渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源 |
Deferred | “LightMode”=“Deferred” | 用于向前渲染,该pass会渲染G缓冲,G-buffer |
ShadowCaster | “LightMode”=“ShadowCaster” | 把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中,用于渲染产生阴影的物体 |
ShadowCollector | “LightMode”=“ShadowCollector” | 用于收集物体阴影到屏幕坐标Buff里 |
PrepassBase | 用于遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分 | |
PrepassFinal | 用于遗留的延迟渲染,该pass通过合并纹理、光照和自发光来渲染得到最后的颜色 | |
Vertex、VertexLMRGBM和VertexLM | 用于遗留的顶点照明渲染 |
二 正向渲染(Forward Rendering )
2.1 向前渲染的基础说明
【说明】正向渲染即正向渲染是物体渲染顺序的一种。
【原理】先着色后深度测试。即将舞台中所有光照与有效物体进行着色计算后,再使用深度测试决定像素有效性。
【优点】容易理解,实现方便。显卡兼容性较好。
【缺点】能损耗较大。因为在最终输出的时候那些没有通过深度测试的像素就白计算了。
【正向渲染与延迟渲染的区别】
正向渲染与延迟渲染都是关于光照计算的渲染方法。他们的最终目的都是进行光照处理。他们最大的区别是正向渲染先计算光照信息再处理深度计算。而延迟渲染正相反,先进行包括深度在内的着色计算,再处理光照计算。
【向前渲染中的三种处理光照的方式】1. 逐顶点处理,2.逐 像素处理 3,球谐函数(Spherical Harmonics,SH)SH光处理
2.2 向前渲染的 Pass
向前渲染的代码必须写在两种Pass中。ForwardBase专门用于处理平行光、环境光,使用逐顶点/SH处理,而ForwardAdd用于处理其他光源,使用逐像素处理。
2.2.1 ForwardBase
【作用】:该Pass中会计算最重要的平行光源、逐顶点/SH光源LightMaps和环境光。只有ForwardBase中处理的第一个平行光可以有阴影效果。另外有多少光源会按照逐顶点光源来处理要取决于光源的属性【Pixel Light Count】,例如Pixel Light Count 是4,那么就只有4个光源会按逐点光源处理,其他只能按SH光源处理
【启用方法】:pass中 tag 设置 “LightMode”=“ForwardBase”
使用:可以使用_WorldSpaceLightPos0,_LightColor0等属性
2.2.2 ForwardAdd
【作用】:该Pass中会计算诸如点光源锥光源等其他的逐像素光源。可以有多个ForwardAdd的Pass,每个符合标准的光源都会调用一次相匹配的ForwardAdd Pass。如果场景中有m个光源,shader中有n个ForwardAdd Pass,那么关于光源的渲染就是m*n次。如果shader中没有光源相关的ForwardAdd处理,则物体不受该光源的影响
【启用方法】:pass中要使用 tag 为 “LightMode”=“ForwardAdd”
2.2.3 阴影处理
- 处理光照阴影必须添加#pragma multi_compile_fwdbase
- 在顶点输入参数中使用 SHADOW_COORDS(2)来定义阴影通道
- 在顶点处理器里调用TRANSFER_SHADOW(v2f)处理片元像素
- 在片元着色器调用SHADOW_ATTENUATION(v2f) 返回的就是这个像素是否存在阴影中。
2.3 向前渲染的实现
2.3.1 实例说明
场景中包含一个唯一的平行光,4个点光源。其中环境光、平行光在ForwardBase 的Pass中逐顶点计算(frag片元着色器中还可以计算光照贴图等)。4个设置为Important的点光源 在ForwardAdd的Pass中逐像素计算,每个点光源执行一次ForwardAdd 的Pass。
2.3.2 使用步骤
0,在shader脚本中分别实现ForwardBase、ForwardAdd两个pass
//1,引入必要宏
#pragma multi_compile_fwdbase
// unity 的光源相关工具函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
//2, 在ForwardBase中使用SHADOW_COORDS(2)、TRANSFER_SHADOW(v2f)、SHADOW_ATTENUATION(color) 处理平行光自然光。
//2,在ForwardAdd中通过计算得到worldLightDir、atten衰减等信息处理像素
1,点击场景中需要启用的光源,然后在Inspactor窗口中设置RenderMode 为Important或Auto,如果设置为Not Important 就不会被shader处理了
2,然后创建Shader和metaril并在shader中写入如下代码,再将shader赋给metaril
Shader "Custom/3d"
{
//此处设置对unity可见的参数
Properties
{
//贴图入口
_MainTex ("Texture", 2D) = "white" {}
_ParallelColor ("平行光、环境光颜色叠加", Color) = (1,1,1,1)
_DiffuseColor ("其他光源 颜色叠加", Color) = (1,1,1,1)
}
SubShader
{
//usb shader的队列,此处设置为非透明物体
Tags { "RenderType"="Opaque" }
LOD 100
//正向渲染 用于处理平行光、环境光、顶点光的pass
Pass
{
// 设置渲染方式
// 用于向前渲染,ForwardBase会计算环境光,重要的平行光,逐顶点/SH光源和lightmaps
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
// 定义顶点、片元着色器
#pragma vertex vert
#pragma fragment frag
// 用于得到光源衰减等信息
#pragma multi_compile_fwdbase
// unity 的光源相关工具函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
// shader变量,用于对接unity、在shader中使用
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _DiffuseColor;
fixed4 _ParallelColor;
// 程序传入到顶点着色器的数据,包括获取贴图uv、顶点位置、法线信息、阴影坐标
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float3 normal : NORMAL;
//SHADOW_COORDS这个宏后面的参数是指第几个通道,要改变投影的颜色话必须要占用一个通道,不要和其他的冲突
SHADOW_COORDS(2)
};
// 顶点着色器
v2f vert (appdata_base v)//appdata_base是内置的参数结构体
{
v2f o;
//将顶点从模型坐标转为裁剪坐标
o.pos = UnityObjectToClipPos(v.vertex);
// 应用在unity中设置的uv坐标的信息
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
//获取世界坐标中的法线信息
o.normal = mul(v.normal,(float3x3)unity_WorldToObject);
// 阴影处理 在顶点处理器里调用TRANSFER_SHADOW
TRANSFER_SHADOW(o);
return o;
}
//片元着色器
fixed4 frag (v2f i) : SV_Target
{
// 获取uv与贴图处理后的当前像素信息
fixed4 col = tex2D(_MainTex, i.uv);
// 世界灯光方向 = 灯光的世界坐标归一化
float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz );
//模型法线
float3 normal = normalize(i.normal);
// 环境光 = 通过 #pragma multi_compile_fwdbase 获取的换进骨光颜色,透明度为1
fixed4 ambient = fixed4(UNITY_LIGHTMODEL_AMBIENT.xyz, 1);
// 漫反射 = 灯光颜色 * 0-1之间(dot(像素世界法线, 世界灯光方向))
// y = saturate(x)的作用:如果x < 0,y = 0。如果x > 1,y = 1。否则 y = x
// dot:点乘, ab为向量,dot(a, b) = a1b1+a2b2+...
fixed4 diffuse = fixed4(_LightColor0.xyz * saturate(dot(normal, worldLightDir)), 1);
// 计算像素颜色 漫反射与环境光融合
col = col * (ambient + diffuse);
// 阴影 = 通过 #pragma multi_compile_fwdbase 片元着色器调用SHADOW_ATTENUATION,返回的就是这个像素是否存在阴影中。
fixed shadow = SHADOW_ATTENUATION(i) * _ParallelColor;
// 最终颜色 = 阴影 * 当前像素颜色
col = shadow * col;
return col;
}
ENDCG
}
//正向渲染 用于处理点光源锥光源等的pass
Pass{
// ForwardAdd 该Pass中会计算诸如点光源锥光源等其他的逐像素光
Tags {"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
// 定义顶点、片元着色器
#pragma vertex vert
#pragma fragment frag
// 用于得到光源衰减等信息
#pragma multi_compile_fwdadd
// unity 的光源相关工具函数
#include "unitycg.cginc"
#include "lighting.cginc"
#include "autolight.cginc "
fixed4 _DiffuseColor;
// 顶点传入片元的数据、包括像素坐标、法线信息、uv
struct v2f{
float4 pos : SV_POSITION;
float3 normalWorld : NORMAL;
float4 uv : TEXCOORD0;
};
// 顶点着色器
v2f vert(appdata_base v)
{
v2f o;
// 将像素坐标从模型空间转为裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
// 世界法线
o.normalWorld = mul(v.normal, (float3x3)unity_WorldToObject);
// uv = 世界uv
o.uv = mul(v.vertex, unity_ObjectToWorld);
return o;
}
// 片元着色器
fixed4 frag (v2f i) : SV_Target
{
// 衰减
fixed atten;
// 灯管反向
fixed3 worldLightDir;
// 分别处理平行光的衰减,与其他光源的衰减
// 如果是平行光源 用 normalize(_WorldSpaceLightPos0.xyz);就可以获取到方向
// 如果是其他光源 需要用normalize(_WorldSpaceLightPos0.xyz - i.uv.xyz);来获取
#ifdef USING_DIRECTIONAL_LIGHT
//平行光的衰减
// 世界灯光方向 = 归一化(世界灯光坐标)
worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 衰减 = 1
atten = 1;
#else
//点光源的衰减
// 世界灯光方向 = 归一化(世界灯光坐标 - 像素uv)
worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 光的坐标
float3 lightCoord = mul(unity_WorldToLight, i.uv).xyz;
// 计算衰减
atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord)).UNITY_ATTEN_CHANNEL;
#endif
// 漫反射 = 灯光颜色 * 衰减 * dot(灯光方向,模型法线)
fixed3 diffuse = _LightColor0.xyz * atten * saturate(dot(worldLightDir,normalize( i.normalWorld)));
return fixed4(diffuse, 1) * _DiffuseColor;
}
ENDCG
}
}
FallBack "Specular"
}
不错的参考文章:
https://www.cnblogs.com/xiegaosen/p/10886404.html
https://www.jianshu.com/p/80a932d1f11e
https://blog.youkuaiyun.com/yangxuan0261/article/details/90182143