[UnityShader学习日志]5.第九章实验记录
目录
提要
- 本章节对渲染路径,多光源及阴影的实现进行了讲解
渲染路径
- 渲染路径——Rendering Path决定了光照是如何应用到UnityShader的,其实就是告诉Shader如何计算光照,实现光照效果
前向渲染路径
- 前向渲染路径是最传统最常用的一种渲染路径,每进行一次完整的前向渲染,首先需要渲染对象的渲染图元,并计算颜色缓冲区和深度缓冲区的信息,其中深度缓冲决定了一个片元是否可见(比较深度值),以此来进行光照等计算,更新颜色缓冲区中的颜色值
- 多光源的情况:对于每个逐像素的光源,都需要一个Pass,也就是说如果一个物体在多个逐像素光源的影响范围内,这个物体就要执行多个Pass。渲染引擎通常会限制每个物体的逐像素光照数目
Render Mode
- 在光源的Light组件中,有一个Render Mode属性,设置为Not Important的光源会按逐顶点/SH处理,Important则会按逐像素处理,其余的则会根据是否超过Quality Setting中逐像素光源数量来进行处理,默认是逐像素
Base Pass和Additional Pass
- Base Pass:主要计算环境光,自发光,以及一个逐像素的平行光,默认支持阴影,只计算一次,第六章中都是在Bass Pass中进行的
- Additional Pass:对于其他影响物体的逐像素光源,每个光源执行一次Pass,在Additional Pass中会开启混合模式——与上一次的光照结果在帧缓存中进行叠加,从而实现多个光照的渲染效果
- 对于前向渲染,一个Unity Shader会定义一个Base Pass(也可以定义多次,如双面渲染),以及一个Additional Pass
顶点照明渲染路径
- 是前向渲染路径的一个子集,在一个Pass中就可以完成对物体的渲染,所有光源都是逐顶点处理的,是最快速的渲染路径,也具有最广泛的硬件支持,不常用
延迟渲染路径
- 延迟渲染利用了额外的缓冲区——G缓冲,它存储了表面的法线,位置,以及用于光照计算的材质属性,并且利用了一个额外的Pass,我们只计算看得见的片元
Unity的光源类型
- 平行光:Directional Light:也就是之前一直用的,平行光可以照亮的范围是无限制的,类似于太阳,它没有一个位移的位置,几何属性只有光源的方向,光照强度也不会随着距离而发生改变
- 点光源,由一个点发出,向所有方向衍生(球体)
- 聚光灯:由一个特定位置向特定方向(锥形区域)延申的光
实验1-1:前向渲染体验:多光源
- Base Pass,其实跟之前差不多(因为之前就是默认场景里只有一个平行光源),不过这次要加上编译指令
#pragma multi_compile_fwdbase
,它保证在Shader中使用光照衰减等光照变量可以被正确赋值 - 关于衰减:平行光可以认为没有衰减,所以衰减值为1.0
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#pragma multi_compile_fwdbase
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_TARGET0
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 LightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 halfDir = normalize(LightDir + viewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,LightDir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
fixed atten = 1.0;//衰减
return fixed4(ambient + (diffuse + specular) * atten,1.0);
}
ENDCG
}
- Additional Pass,加入编译指令
#pragma multi_compile_fwdbase
,并且开启混合模式,不再计算环境光。
Pass
{
Tags{"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_TARGET0
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 halfDir = normalize(lightDir + viewDir);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,lightDir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4((diffuse + specular) * atten,1.0);
}
ENDCG
}
- 光照衰减纹理:Unity在内部使用一张名为_LightTexture()的纹理来计算光照衰减
Unity的阴影
实现阴影的原理
- 现实生活中,当一个光源发射的一条光线遇到一个不透明的物体时,若不考虑反射,这条光线就会被该物体遮挡,无法继续照亮其他物体,那么这个物体就会向周围投射阴影,也就是说阴影就是光线无法到达的区域。
- 在实时渲染中,一般使用Shadow Map来实现阴影,这种技术就是首先把摄像机的位置放在和光源重合的位置上,那么场景中该光源的阴影区域就是摄像机看不到的地方,这就是阴影映射纹理,本质上它也是一张深度图,因为它记录了从该光源的位置出发,能看到的场景中距离它最近的表面位置(也就是深度信息)。
ShadowCaster
- 为了得到阴影映射纹理,可以按照正常的渲染流程,调用Base Pass和Additional Pass来更新深度信息,但这种方式会对性能浪费(因为又会进行光照的计算),实际上需要的只是深度信息,因此Unity选择使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是LightMode标签被设置为ShadowCaster的Pass,它仅仅负责计算阴影映射纹理(输出深度信息)
投射与接收阴影
- 投射阴影:若我们想要一个物体向其他物体投射阴影,就必须把该物体加入到指定光源的阴影映射纹理的计算中。
- 接收阴影:若我们想要一个物体接收其他物体的阴影,就必须在Shader中对阴影映射纹理进行采样,把采样结果和光照结果相乘来产生阴影的效果
实验2-1:不透明物体接收阴影
- 不透明物体接收阴影,只需要几个步骤:
- 加入头文件
#include "AutoLight.cginc"
- 在输出结构体v2f中,添加一个内置宏
SHADOW_COORDS
,注意参数为插值寄存器的索引值 - 在顶点着色器中,使用
TRANSFER_SHADOW()
,计算阴影纹理坐标 - 在片元着色器中,计算阴影值,使用内置宏
SHADOW_ATTENUATION
- 在输出光照计算结果时,让阴影值乘上漫反射与高光反射值
Shader "Unlit/Shadow"
{
Properties
{
_Diffuse("Diffuse",color) = (1,1,1,1)
_Specular("Specular",color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
#pragma multi_compile_fwdbase
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_TARGET0
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 LightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 halfDir = normalize(LightDir + viewDir);
fixed shadow = SHADOW_ATTENUATION(i);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,LightDir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
fixed atten = 1.0;//衰减
return fixed4(ambient + (diffuse + specular)* shadow * atten,1.0);
}
ENDCG
}
Pass
{
Tags{"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_TARGET0
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 halfDir = normalize(lightDir + viewDir);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,lightDir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4((diffuse + specular) * atten,1.0);
}
ENDCG
}
}
FallBack "Specular"
}