文章目录
- 前言
- 一、在编写Shader时,有一些隐蔽的Bug不会直接报错,我们需要编译一下让它显示出来,方便修改
- 我们选择我们的Shader,点击编译并且展示编译后的Shader后的内容,隐蔽的Bug就会暴露出来了。
- 二、我们大概回顾一下,之前实现的内容,和应用场景(然后,可以在以后的项目中按需选择取舍)
- 1、第一个Pass是我们模型的主要效果
- 2、第二个Pass是我们模型阴影的投射(在不需要时可以剔除该Pass)
- 3、第三个Pass是我们模型烘焙计算(在不需要时可以剔除该Pass)
前言
Unity中全局光照GI的总结,我们对之前文章中,实现的 全局光照 GI Shader 总结一下
-
Unity中Shader的全局照明简介
-
Unity中Shader自定义cginc文件
-
Unity中Shader的GI相关数据的准备
-
Unity中Shader的烘培分支的判断
-
Unity中Shader的GI的直接光实现
-
Unity中Shader的GI的间接光实现
-
Unity中Shader再议ATTENUATION
-
Unity中Shader光照探针的支持
-
Unity中Shader的间接光的产生Meta Pass
一、在编写Shader时,有一些隐蔽的Bug不会直接报错,我们需要编译一下让它显示出来,方便修改
我们选择我们的Shader,点击编译并且展示编译后的Shader后的内容,隐蔽的Bug就会暴露出来了。
二、我们大概回顾一下,之前实现的内容,和应用场景(然后,可以在以后的项目中按需选择取舍)
1、第一个Pass是我们模型的主要效果
- 在该Pass的片元着色器中,对于计算 GI 的这个函数,我们可以选择使用Unity自带的函数(在项目确定只用某一套 GI 方案时,可以只选择该函数中的部分功能使用)
- 在该Pass的片元着色器中,对于同时计算了 Lambert 和 Phone 光照模型的这个函数(我们可以按照公式自定义实现,也可以直接使用)
2、第二个Pass是我们模型阴影的投射(在不需要时可以剔除该Pass)
3、第三个Pass是我们模型烘焙计算(在不需要时可以剔除该Pass)
该GI的最终代码:
MyGlobalIllumination.cginc
#ifndef MYGLOBALILLUMINATION_INCLUDE
#define MYGLOBALILLUMINATION_INCLUDE
//Lambert光照模型
inline fixed4 UnityLambertLight1 (SurfaceOutput s, UnityLight light)
{
fixed diff = max (0, dot (s.Normal, light.dir));
fixed4 c;
c.rgb = s.Albedo * light.color * diff;
c.a = s.Alpha;
return diff;
}
inline fixed4 LightingLambert1 (SurfaceOutput s, UnityGI gi)
{
fixed4 c;
c = UnityLambertLight1 (s, gi.light);
//如果是在 BackedGI 或者 RealtimeGI的情况下,进行以下计算
#ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
c.rgb += s.Albedo * gi.indirect.diffuse;
#endif
return c;
}
inline void ResetUnityLight1(out UnityLight outLight)
{
outLight.color = half3(0, 0, 0);
outLight.dir = half3(0, 1, 0); // Irrelevant direction, just not null
outLight.ndotl = 0; // Not used
}
inline void ResetUnityGI1(out UnityGI outGI)
{
ResetUnityLight1(outGI.light);
outGI.indirect.diffuse = 0;
outGI.indirect.specular = 0;
}
inline UnityGI UnityGI_Base1(UnityGIInput data, half occlusion, half3 normalWorld)
{
UnityGI o_gi;
ResetUnityGI1(o_gi);
//计算在Distance Shadowmask 中实时阴影与烘培阴影的混合过程
// Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
#if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos);
float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz);
float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist);
data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist));
#endif
//将主平行灯的信息存储起来
o_gi.light = data.light;
//将衰减用于灯光颜色中
o_gi.light.color *= data.atten;
//是否进行球谐光照(即是否使用光照探针)
#if UNITY_SHOULD_SAMPLE_SH
o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
#endif
//这个是进行静态 GI 的计算(BackedGI)
#if defined(LIGHTMAP_ON)
// Baked lightmaps
//光照图的采样
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
half3 bakedColor = DecodeLightmap(bakedColorTex);
//当开启 Unity 中的 Directional 模式 (定向光模式)时,进行的计算
#ifdef DIRLIGHTMAP_COMBINED
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
ResetUnityLight(o_gi.light);
o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
#endif
#else // not directional lightmap
o_gi.indirect.diffuse += bakedColor;
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
ResetUnityLight(o_gi.light);
o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
#endif
#endif
#endif
//这个是进行动态 GI 的计算(RealtimeGI)
#ifdef DYNAMICLIGHTMAP_ON
// Dynamic lightmaps
fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
#ifdef DIRLIGHTMAP_COMBINED
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
#else
o_gi.indirect.diffuse += realtimeColor;
#endif
#endif
//这里是使物体表面的颜色 乘以 环境光遮蔽,以实现环境光线被阻碍后物体表面的颜色
o_gi.indirect.diffuse *= occlusion;
return o_gi;
}
inline UnityGI UnityGlobalIllumination1 (UnityGIInput data, half occlusion, half3 normalWorld)
{
return UnityGI_Base1(data, occlusion, normalWorld);
}
inline void LightingLambert_GI1 (SurfaceOutput s,UnityGIInput data,inout UnityGI gi)
{
gi = UnityGlobalIllumination1 (data, 1.0, s.Normal);
}
#endif
GI Shader 代码:
//在这里里面使用 自定义的 cginc 来实现全局GI
//GI数据的准备
//烘培分支的判断
//GI的直接光实现
//GI的间接光实现
//再议ATTENUATION
//光照探针的支持
//间接光的产生Meta Pass
Shader "MyShader/P1_8_9"
{
Properties
{
_Color("Color",Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"
#include "CGIncludes/MyGlobalIllumination.cginc"
struct appdata
{
float4 vertex : POSITION;
//定义第二套 UV ,appdata 对应的固定语义为 TEXCOORD1
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
float4 texcoord1 : TEXCOORD1;
#endif
half3 normal : NORMAL;
float4 texcoord2 : TEXCOORD2;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD;
//定义第二套UV
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
float4 lightmapUV : TEXCOORD1;
#endif
half3 worldNormal : NORMAL;
half3 sh : TEXCOORD2;
//1、使用 阴影采样 和 光照衰减的方案的 第一步
//同时定义灯光衰减以及实时阴影采样所需的插值器
UNITY_LIGHTING_COORDS(3, 4)
//UNITY_SHADOW_COORDS(2)
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//对第二套UV进行纹理采样
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
o.lightmapUV.xy = v.texcoord1 * unity_LightmapST.xy + unity_LightmapST.zw;
#endif
//实现 球谐 或者 环境色 和 顶点照明 的计算
//SH/ambient and vertex lights
#ifndef LIGHTMAP_ON //当此对象没有开启静态烘焙时
#if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
o.sh = 0;
//近似模拟非重要级别的点光在逐顶点上的光照效果
#ifdef VERTEXLIGHT_ON
o.sh += Shade4PointLights(
unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
unity_LightColor[0].rgb,unity_LightColor[1].rgb,unity_LightColor[2].rgb,unity_LightColor[3].rgb,
unity_4LightAtten0,o.worldPos,o.worldNormal);
#endif
o.sh = ShadeSHPerVertex(o.worldNormal,o.sh);
#endif
#endif
//2、使用 阴影采样 和 光照衰减的方案的 第二步
UNITY_TRANSFER_LIGHTING(o, v.texcoord2.xy)
//TRANSFER_SHADOW(o)
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//1、准备 SurfaceOutput 的数据
SurfaceOutput o;
//目前先初始化为0,使用Unity自带的方法,把结构体中的内容初始化为0
UNITY_INITIALIZE_OUTPUT(SurfaceOutput, o)
o.Albedo = 1;
o.Normal = i.worldNormal;
//1、代表灯光的衰减效果
//2、实时阴影的采样
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
//2、准备 UnityGIInput 的数据
UnityGIInput giInput;
//初始化
UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
//修改用到的数据
giInput.light.color = _LightColor0;
giInput.light.dir = _WorldSpaceLightPos0;
giInput.worldPos = i.worldPos;
giInput.worldViewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
giInput.atten = atten;
giInput.ambient = 0;
#if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
giInput.ambient = i.sh;
#else
giInput.ambient = 0.0;
#endif
#if defined(DYNAMICLIGHTMAP_ON) || defined(LIGHTMAP_ON)
giInput.lightmapUV = i.lightmapUV;
#endif
//3、准备 UnityGI 的数据
UnityGI gi;
//直接光照数据(主平行光)
gi.light.color = _LightColor0;
gi.light.dir = _WorldSpaceLightPos0;
//间接光照数据(目前先给0)
gi.indirect.diffuse = 0;
gi.indirect.specular = 0;
//GI的间接光照的计算
LightingLambert_GI1(o, giInput, gi);
//查看Unity源码可知,计算间接光照最主要的函数就是
//inline UnityGI UnityGI_Base1(UnityGIInput data, half occlusion, half3 normalWorld)
//所以我们直接给 gi 赋值,可以不使用 LightingLambert_GI1
gi = UnityGI_Base1(giInput, 1, o.Normal);
//GI的直接光照的计算
//我们在得到GI的数据后,对其进行Lambert光照模型计算,即可得到结果
fixed4 c = LightingLambert1(o, gi);
return c;
//return fixed4(gi.indirect.diffuse,1);
//return 1;
}
ENDCG
}
//阴影的投射
Pass
{
//1、设置 "LightMode" = "ShadowCaster"
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//需要添加一个 Unity变体
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
//声明消融使用的变量
float _Clip;
sampler2D _DissolveTex;
float4 _DissolveTex_ST;
//2、appdata中声明float4 vertex:POSITION;和half3 normal:NORMAL;这是生成阴影所需要的语义.
//注意:在appdata部分,我们几乎不要去修改名字 和 对应的类型。
//因为,在Unity中封装好的很多方法都是使用这些标准的名字
struct appdata
{
float4 vertex:POSITION;
half3 normal:NORMAL;
float4 uv:TEXCOORD;
};
//3、v2f中添加V2F_SHADOW_CASTER;用于声明需要传送到片断的数据.
struct v2f
{
float4 uv : TEXCOORD;
V2F_SHADOW_CASTER;
};
//4、在顶点着色器中添加TRANSFER_SHADOW_CASTER_NORMALOFFSET(o),主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象.
v2f vert(appdata v)
{
v2f o;
o.uv.zw = TRANSFORM_TEX(v.uv, _DissolveTex);
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
return o;
}
//5、在片断着色器中添加SHADOW_CASTER_FRAGMENT(i)
fixed4 frag(v2f i) : SV_Target
{
//外部获取的 纹理 ,使用前都需要采样
fixed4 dissolveTex = tex2D(_DissolveTex, i.uv.zw);
//片段的取舍
clip(dissolveTex.r - _Clip);
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
//在常规的渲染时,是不会被使用的。一般使用时,是在烘焙贴图
// Extracts information for lightmapping, GI (emission, albedo, ...)
// This pass it not used during regular rendering.
Pass
{
Name "META"
Tags
{
"LightMode" = "Meta"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityMetaPass.cginc"
fixed4 _Color;
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f,o)
o.pos = UnityMetaVertexPosition(v.vertex, v.texcoord1.xy, v.texcoord2.xy, unity_LightmapST,
unity_DynamicLightmapST);
return o;
}
half4 frag(v2f i) : SV_Target
{
UnityMetaInput metaIN;
UNITY_INITIALIZE_OUTPUT(UnityMetaInput, metaIN);
metaIN.Albedo = 1;
metaIN.Emission = _Color;
return UnityMetaFragment(metaIN);
}
ENDCG
}
}
CustomEditor "LegacyIlluminShaderGUI"
}