这节主要研究多光源,对应 cat-likeCoding 渲染部分的第五章
暂时没想到有什么必要的前置知识
渲染多光源首先要考虑一下多 Pass。
我们将 Pass 内的代码直接复制一遍,放到后面。
然后把 fragment 变简单点,只返回一个固定颜色
另外我们需要区分一下两个 Pass 的名字,一个叫做 LightMain,一个叫做 LightAdd
现在代码大概长这样
1Shader "Unlit/BaseLight"
2{
3 Properties
4 {
5 _MainTex ("Texture", 2D) = "white" {}
6 _Gloss("Gloss", Range(0,1)) = 10.0
7 _Metallic ("Metallic", Range(0, 1)) = 0
8 }
9 SubShader
10 {
11 Tags { "RenderType"="Opaque" }
12
13 LOD 100
14
15 Pass
16 {
17 Name "LightMain"
18
19
20 HLSLPROGRAM
21
22 #pragma vertex vert
23 #pragma fragment frag
24
25 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
26 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
27 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/BRDF.hlsl"
28
29 struct appdata
30 {
31 float4 vertex : POSITION;
32 float2 uv : TEXCOORD0;
33 float3 normal : NORMAL;
34 };
35
36 struct v2f
37 {
38 float2 uv : TEXCOORD0;
39 float4 vertex : SV_POSITION;
40 float3 normal : NORMAL;
41 float3 worldPos : TEXCOORD1;
42 };
43
44 TEXTURE2D(_MainTex);
45 SAMPLER(sampler_MainTex);
46 float4 _MainTex_ST;
47
48 float _Gloss;
49 float _Metallic;
50
51 // 定义非金属材质的镜面反射颜色常量
52 const float3 kDieletricSpec = float3(0.04, 0.04, 0.04);
53
54 v2f vert (appdata v)
55 {
56 v2f o;
57 o.vertex = TransformObjectToHClip(v.vertex);
58 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
59
60 o.normal = mul(transpose((float3x3)unity_WorldToObject), float4(v.normal,0));
61
62 o.worldPos = TransformObjectToWorld(v.vertex.xyz);
63
64 return o;
65 }
66
67 float DotClamped(float3 a, float3 b) {
68 return saturate(dot(a, b));
69 }
70
71 half4 frag (v2f i) : SV_Target
72 {
73 i.normal = normalize(i.normal);
74
75 // sample the texture
76 float3 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv).rgb;
77
78 // 设置表面属性
79 BRDFData brdfData;
80 // 初始化BRDF数据
81 half oneMinusReflectivity = 1 - _Metallic;
82 half3 specColor = lerp(kDieletricSpec, albedo, _Metallic);
83 InitializeBRDFData(albedo, _Metallic, specColor, _Gloss, oneMinusReflectivity, brdfData);
84
85 // 获取主光源信息
86 Light mainLight = GetMainLight();
87
88 // 计算视线方向
89 float3 viewDir = normalize(GetCameraPositionWS() - i.worldPos);
90
91 // 计算直接光照
92 half3 color = LightingPhysicallyBased(brdfData, mainLight, i.normal, viewDir);
93
94 // 添加环境光照(可选)
95 // half3 ambient = SampleSH(i.normal) * albedo * oneMinusReflectivity;
96 // color += ambient;
97
98 return half4(color, 1.0);
99 }
100 ENDHLSL
101 }
102
103Pass
104 {
105 Name "LightAdd"
106
107 HLSLPROGRAM
108
109 #pragma vertex vert
110 #pragma fragment frag
111
112 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
113 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
114 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/BRDF.hlsl"
115
116 struct appdata
117 {
118 float4 vertex : POSITION;
119 float2 uv : TEXCOORD0;
120 float3 normal : NORMAL;
121 };
122
123 struct v2f
124 {
125 float2 uv : TEXCOORD0;
126 float4 vertex : SV_POSITION;
127 float3 normal : NORMAL;
128 float3 worldPos : TEXCOORD1;
129 };
130
131 TEXTURE2D(_MainTex);
132 SAMPLER(sampler_MainTex);
133 float4 _MainTex_ST;
134
135 float _Gloss;
136 float _Metallic;
137
138 // 定义非金属材质的镜面反射颜色常量
139 const float3 kDieletricSpec = float3(0.04, 0.04, 0.04);
140
141 v2f vert (appdata v)
142 {
143 v2f o;
144 o.vertex = TransformObjectToHClip(v.vertex);
145 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
146
147 o.normal = mul(transpose((float3x3)unity_WorldToObject), float4(v.normal,0));
148
149 o.worldPos = TransformObjectToWorld(v.vertex.xyz);
150
151 return o;
152 }
153
154 float DotClamped(float3 a, float3 b) {
155 return saturate(dot(a, b));
156 }
157
158 half4 frag (v2f i) : SV_Target
159 {
160 i.normal = normalize(i.normal);
161
162 half3 color = half3(1.0,0,0);
163
164 return half4(color, 1.0);
165 }
166 ENDHLSL
167 }
168 }
169}
我知道可以单独抽一个文件出来 include,但是我现在懒得这么干
这时候大概是这样子,其实完全没变,只是相同的东西渲染了两次
为了方便观察,我们可以启用 FrameDebugger 来看下具体的渲染流程,非常简单方便
Urp 的渲染管线稍稍有一点点复杂,印象中有一个 Blit。不过这里我们只需要找到我们要看的部分即可
(另外现在我有一些插件,导致我的 FrameDebugger 可能看起来不完全一样,忽略这些,不重要)
我们看到在绘制时,只绘制了一次
下方的信息可以看到,只渲染了第一 Pass
可以看到我们之前有一个 Tags,标记了 LightMode
Tags { "LightMode" = "UniversalForward" }
我还没有弄明白具体每一个 LightMode 是用来做什么,现在粗浅的理解是,用来标记这个 Pass 主要用来做什么,并且 urp 内部应该有一些逻辑会管理这些 Tag 下的渲染。这里直接贴一下 Urp 官方文档的解释,虽然我觉得它也并没有完全解释清楚这件事
明显我们直接复制代码的情况下,两个 Pass 的 LightMode 似乎都是 SRPDefaultUnlit,这种情况下只有一条渲染调用,因此我们尝试改动第二个 Pass 的 LightMode 为 UniversalForward
明显这次渲染命令变成了两条,并且 UniversalForward 渲染顺序再 SRPUnlitDefault 之后
不同的 Pass 之间,需要设置混合方式来达到想要的效果
简单理解来说就是,在渲染当前 PASS 的时候,之前肯定也有 PASS 渲染过了,那么如何处理之前 PASS 渲染下来的结果和当前的 PASS 渲染结果的关系,就是 Blend 的功能和意义所在
在 FrameDebugger 中我们看到,Color 和 Alpha 的混合都是 One/Zero.前者指的是源颜色(也就是我们本 Pass 渲染的颜色),后者指的是目标颜色(即之前渲染出来的 Pass)
现在的混合模式就代表了:完全舍弃掉之前 Pass 渲染的结果,只用我们当前的 Pass
那我们尝试改一下,改成 One One
会看到我们第二个 Pass 的红色和之前的 PBR 着色融在了一起,等同于我们在第一个 Pass 里直接乘上一个(1,0,1)的颜色
当然还有很多其他的 Blend 方案。如果做过 2D 游戏或者接触过其他半透明渲染的应该会非常熟悉。但是这里我们就不展开描述了,因为目前我们关心的渲染基本都还是不透明物体。对我们来说,One One 暂时也够用了
比如 Blend One Zero 就代表完全舍弃之前帧缓冲的颜色,只用现在的颜色来覆盖
另外在帧缓冲之间,另外一个特别重要的东西就是深度
深度可以简单理解为到摄像机的距离。对于不透明物体渲染来说,如果有多个帧缓冲,那么在逐像素处理时,只需要处理距离摄像机近的像素即可。因此在渲染帧缓冲的时候,顺便会把代表了距离摄像机距离的深度渲染到深度缓冲里。
在接下来的帧缓冲里,如果还是只是渲染不透明物体,那么会有一个深度的比较过程,如果设置了深度比较小于等于就舍弃,那么距离摄像机更远的帧缓冲将被舍弃。
深度值一般来说不是直接用摄像机距离的
要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在 z 值很小的时候提供非常高的精度,而在 z 值很远的时候提供更少的精度。
因为用距离比较的精度就越低,所以深度值一般是不会写入摄像机的距离值,一般要正比于摄像机距离的倒数,也就是 1/z,这样就在远距离比较的时候精度更高
具体的计算可以看这里,写得非常清楚
在我们进行多光源的尝试之前,我们需要注意下 urp 的设置。
原文在 built-in 管线下,直接改 shader-tag 就可以了。但是在 urp 下这样是行不通的。所以我们需要看一下 urp 的设置如何引入多光源。
找到 project Setting 里的 Rendering Pipline Asset
寻找 lighting 设置里的 AdditonalLights,将之设置为 PerPiexel
接下来我们就可以在 shader 拿到其他光源了。
我们通过一些 urp 内置的接口就可以搞定多光照了,大概代码如下
1half4 frag (v2f i) : SV_Target
2{
3 i.normal = normalize(i.normal);
4
5 // sample the texture
6 float3 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv).rgb;
7
8 // 设置表面属性
9 BRDFData brdfData;
10 // 初始化BRDF数据
11 half oneMinusReflectivity = 1 - _Metallic;
12 half3 specColor = lerp(kDieletricSpec, albedo, _Metallic);
13 InitializeBRDFData(albedo, _Metallic, specColor, _Gloss, oneMinusReflectivity, brdfData);
14
15 // 计算视线方向
16 float3 viewDir = normalize(GetCameraPositionWS() - i.worldPos);
17
18 // 初始化颜色为0
19 half3 color = half3(0, 0, 0);
20
21 // 处理额外的光源(包括点光源)
22 // 获取额外光源的数量
23 int additionalLightsCount = GetAdditionalLightsCount();
24
25 for (int lightIndex = 0; lightIndex < additionalLightsCount; lightIndex++)
26 {
27 // 获取额外光源(点光源、聚光灯等)
28 Light light = GetAdditionalLight(lightIndex, i.worldPos);
29
30 // 计算这个光源的贡献
31 color += LightingPhysicallyBased(brdfData, light, i.normal, viewDir);
32 }
33
34 return half4(color, 1.0);
35}
其中引入的两个新函数 GetAdditionalLightsCount()和 GetAdditionalLight()帮助我们解决了光照的问题。代码很简单易懂。
简单看下代码会发现,在 GetAdditionalLightsCount()中:
1int GetAdditionalLightsCount()
2{
3#if USE_FORWARD_PLUS
4 // Counting the number of lights in clustered requires traversing the bit list, and is not needed up front.
5 return 0;
6#else
7 // TODO: we need to expose in SRP api an ability for the pipeline cap the amount of lights
8 // in the culling. This way we could do the loop branch with an uniform
9 // This would be helpful to support baking exceeding lights in SH as well
10 return int(min(_AdditionalLightsCount.x, unity_LightData.y));
11#endif
12}
如果使用了 Forward+ 的渲染,是拿不到这些实时光源的。Forward+ 后面也许会讲到。
其他的接口应该都非常熟悉,给 LightingPhysicallyBased 这个函数传一个 Light 进去,就能得到这个光照对应的颜色。
在场景的球附近加两个 PointLight,随便调两个颜色,大概得到这样的效果。
明显 FrameDebugger 是两次 DrawCall,第一次是我们上一章写的主光源计算,第二次是我们刚加上的其他光源计算。最后以 One One 的方式混合
因为是混合,所以只能看出来哪些部分由主光源渲染(LightMain Pass),而看不出来哪些是 LightBack Pass 渲染的。所以我们修改一下 LightBack Pass 的混合模式,让 LightBack Pass 完全丢弃掉前面的帧缓冲
1Blend Zero One
这样就比较能看出来哪些光是哪个 Pass 渲染的了
经过实验,我们可以得到:
⚠️ 注意:这部分代码和解释部分是通过资料得来的,并不能保证正确,仅仅用来存档记录使用
因为涉及比较细节的实现,没有解释得特别清楚。主要留在这里,告诉你有这个东西存在。哪天需要的时候查相关内容方便些。
原文关于光衰减的实现其实也是比较简单地带过了
关于比较详细的实现光衰减的(点光源,聚光灯),可以参考 LearnOpenGLCN 的对应章节。写得非常好
计算衰减的主要流程是这个
1Light GetAdditionalPerObjectLight(int perObjectLightIndex, float3 positionWS)
2{
3 // ... 获取光源数据 ...
4
5 // 计算从光源到表面的向量
6 float3 lightVector = lightPositionWS.xyz - positionWS * lightPositionWS.w;
7 float distanceSqr = max(dot(lightVector, lightVector), HALF_MIN);
8
9 half3 lightDirection = half3(lightVector * rsqrt(distanceSqr));
10 float attenuation = DistanceAttenuation(distanceSqr, distanceAndSpotAttenuation.xy) * AngleAttenuation(spotDirection.xyz, lightDirection, distanceAndSpotAttenuation.zw);
11
12 // ... 构建并返回Light结构体 ...
13}
这个函数为场景中的每个额外光源创建 Light 结构体。主要步骤:
这里的计算小技巧:Directional 光的 w 分量是 0.lightPositionWS 直接就是方向,所以能够直接得到方向。而对于其他光源,则是真实的光源位置。
物理上,光照强度随距离平方反比衰减(平方反比定律):
所以计算光衰减用的是倒数
1float lightAtten = rcp(distanceSqr); // rcp = 1/x,即倒数
然后在 DistanceAttenuation
这个函数中,计算了距离衰减
1float DistanceAttenuation(float distanceSqr, half2 distanceAttenuation)
2{
3 float lightAtten = rcp(distanceSqr);
4 float2 distanceAttenuationFloat = float2(distanceAttenuation);
5
6 half factor = half(distanceSqr * distanceAttenuationFloat.x);
7 half smoothFactor = saturate(half(1.0) - factor * factor);
8 smoothFactor = smoothFactor * smoothFactor;
9
10 return lightAtten * smoothFactor;
11}
使用了一个 smoothFactor,有点类似于 smoothLerp 的那个思路,一个边缘比较平滑的映射函数
在 AngleAttenuation
计算了角度衰减
聚光灯需要根据光照方向和表面的角度关系计算衰减,也是一个简化的公式:
1half AngleAttenuation(half3 spotDirection, half3 lightDirection, half2 spotAttenuation)
2{
3 // 具有线性衰减的聚光灯衰减可以定义为
4 // (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)
5 // 这可以重写为
6 // invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)
7 // SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)
8 // SdotL * spotAttenuation.x + spotAttenuation.y
9
10 // 如果我们在一个乘加(MAD)指令中预计算这些项
11 half SdotL = dot(spotDirection, lightDirection);
12 half atten = saturate(SdotL * spotAttenuation.x + spotAttenuation.y);
13 return atten * atten;
14}
原文关于逐像素光源的优化谈的是针对 built-in 的,在制作变体的情况下,多光源会导致 drawcall 增多。urp 虽然没有这个问题(只要在限制范围内,不管多少 AdditionalLight 都是统一是一个 draw call)
即便如此,在 fragment 里做逐像素的光照依然是一个非常消耗 GPU 性能的操作,尤其是在移动端。
更加节省效率的方案是:做逐顶点光照,然后由 vertex 到 fragment 自由插值。
首先修改下设置
另外,我们需要声明一下着色器变体
1 #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
上面这行代表了三种可能的变体:_代表普通,_ADDITIONAL_LIGHTS_VERTEX 代表逐顶点光源,_ADDITIONAL_LIGHTS 代表逐像素光照
声明着色器变体可以让我们通过外部的设置,让着色器自动决定编译的结果。因此我们的着色器代码也需要用这个宏来控制,例如:
1#ifdef _ADDITIONAL_LIGHTS_VERTEX
2 // 在顶点着色器中采样贴图
3 float4 texColor = SAMPLE_TEXTURE2D_LOD(_MainTex, sampler_MainTex, o.uv, 0);
4 ...
5#endif
上面的代码就代表了,只有在设置为逐顶点光照的时候,这个宏才被激活,才有这些代码结果
其实很简单,我们直接将定点颜色声明到 v2f 数组中:
1struct v2f
2{
3 float2 uv : TEXCOORD0;
4 float4 vertex : SV_POSITION;
5 float3 normal : NORMAL;
6 float3 worldPos : TEXCOORD1;
7 #ifdef _ADDITIONAL_LIGHTS_VERTEX
8 // 逐顶点光照计算
9 float3 vertexLightColor : TEXCOORD2;
10 #endif
11};
然后,直接把像素着色器代码照搬到顶点着色器里,只需要把对应的参数换成 appdata 的参数即可
1#ifdef _ADDITIONAL_LIGHTS_VERTEX
2 // 在顶点着色器中采样贴图
3 float4 texColor = SAMPLE_TEXTURE2D_LOD(_MainTex, sampler_MainTex, o.uv, 0);
4 float3 albedo = texColor.rgb;
5
6 // 设置表面属性
7 BRDFData brdfData;
8 // 初始化BRDF数据
9 half oneMinusReflectivity = 1 - _Metallic;
10 half3 specColor = lerp(kDieletricSpec, albedo, _Metallic);
11 InitializeBRDFData(albedo, _Metallic, specColor, _Gloss, oneMinusReflectivity, brdfData);
12
13 // 计算视线方向
14 float3 viewDir = normalize(GetCameraPositionWS() - o.worldPos);
15
16 // 初始化颜色为0
17 half3 color = half3(0, 0, 0);
18
19 // 处理额外的光源(包括点光源)
20 // 获取额外光源的数量
21 int additionalLightsCount = GetAdditionalLightsCount();
22
23 for (int lightIndex = 0; lightIndex < additionalLightsCount; lightIndex++)
24 {
25 // 获取额外光源(点光源、聚光灯等)
26 Light light = GetAdditionalLight(lightIndex, o.worldPos);
27
28 // 计算这个光源的贡献
29 color += LightingPhysicallyBased(brdfData, light, o.normal, viewDir);
30 }
31
32 o.vertexLightColor = color;
33 #endif
34
35
36 return o;
37}
这里要特别注意的是这里:
1 float4 texColor = SAMPLE_TEXTURE2D_LOD(_MainTex, sampler_MainTex, o.uv, 0);
在顶点着色器中,是没有 MipMap 的,原来像素着色器的是考虑了这一点的,因此顶点着色器要做相应的修改
最后在片元着色器中我们只需要进行插值
1half4 frag (v2f i) : SV_Target
2{
3 i.normal = normalize(i.normal);
4
5 // sample the texture
6 float3 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv).rgb;
7
8 // 初始化颜色为0
9 half3 color = half3(0, 0, 0);
10
11 #ifdef _ADDITIONAL_LIGHTS_VERTEX
12
13 color = i.vertexLightColor;
14
15 #endif
16
17
18 return half4(color, 1.0);
19}
然后我们就会得到一个简陋的顶点光源
看的出来,边缘非常粗糙,非常满意
事已至此,我们一起把逐像素光照的片元着色器的也改写一下好了
虽然代码是非常重复的,但是暂时就这样把。
1half4 frag (v2f i) : SV_Target
2{
3
4 // sample the texture
5
6 // 初始化颜色为0
7 half3 color = half3(0, 0, 0);
8
9 # ifdef _ADDITIONAL_LIGHTS
10 i.normal = normalize(i.normal);
11
12 float3 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv).rgb;
13 // 设置表面属性
14 BRDFData brdfData;
15 // 初始化BRDF数据
16 half oneMinusReflectivity = 1 - _Metallic;
17 half3 specColor = lerp(kDieletricSpec, albedo, _Metallic);
18 InitializeBRDFData(albedo, _Metallic, specColor, _Gloss, oneMinusReflectivity, brdfData);
19
20 // 计算视线方向
21 float3 viewDir = normalize(GetCameraPositionWS() - i.worldPos);
22
23 // 处理额外的光源(包括点光源)
24 // 获取额外光源的数量
25 int additionalLightsCount = GetAdditionalLightsCount();
26
27 for (int lightIndex = 0; lightIndex < additionalLightsCount; lightIndex++)
28 {
29 // 获取额外光源(点光源、聚光灯等)
30 Light light = GetAdditionalLight(lightIndex, i.worldPos);
31
32 // 计算这个光源的贡献
33 color += LightingPhysicallyBased(brdfData, light, i.normal, viewDir);
34 }
35
36 #endif
37
38
39 #ifdef _ADDITIONAL_LIGHTS_VERTEX
40
41 color = i.vertexLightColor;
42
43 #endif
44
45
46 return half4(color, 1.0);
47}
说实话,我没看懂,但是大概意思应该是用一堆正弦波函数来模拟近似一个函数,可能类似于信号中非常常见的傅里叶分解?
但总之,就是用这个函数 SampleSH 来采集环境光。
1half3 ambient = SampleSH(i.normal) * albedo;
2return half4(ambient, 1.0);
然后调整一下(MainLight 和 BackLight 的结果都要加)
另外需要注意的是,这个环境光需要注意金属度的影响,金属度越高,环境光越小
1half3 ambient = SampleSH(i.normal) * albedo * oneMinusReflectivity;
2
3color+=ambient;
(这部分代码似乎不太对)
然后把 Lighting 里的环境光照改一改
最后就会得到和环境光加起来的光照效果
看的出来,球体亮了不少
最后我们将所有的内容合并成一个 Pass,然后用着色器变体来控制
1Shader "Unlit/BaseLight"
2{
3 Properties
4 {
5 _MainTex ("Texture", 2D) = "white" {}
6 _Gloss("Gloss", Range(0,1)) = 10.0
7 _Metallic ("Metallic", Range(0, 1)) = 0
8 }
9 SubShader
10 {
11 Tags { "RenderType"="Opaque" }
12
13 LOD 100
14
15 Pass
16 {
17 Name "LightMain"
18
19 ZWrite On
20
21 HLSLPROGRAM
22
23 #pragma multi_compile _ _ADDITIONAL_LIGHTS
24
25 #pragma vertex vert
26 #pragma fragment frag
27
28 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
29 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
30 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/BRDF.hlsl"
31
32 struct appdata
33 {
34 float4 vertex : POSITION;
35 float2 uv : TEXCOORD0;
36 float3 normal : NORMAL;
37 };
38
39 struct v2f
40 {
41 float2 uv : TEXCOORD0;
42 float4 vertex : SV_POSITION;
43 float3 normal : NORMAL;
44 float3 worldPos : TEXCOORD1;
45 };
46
47 TEXTURE2D(_MainTex);
48 SAMPLER(sampler_MainTex);
49 float4 _MainTex_ST;
50
51 float _Gloss;
52 float _Metallic;
53
54 // 定义非金属材质的镜面反射颜色常量
55 const float3 kDieletricSpec = float3(0.04, 0.04, 0.04);
56
57 v2f vert (appdata v)
58 {
59 v2f o;
60 o.vertex = TransformObjectToHClip(v.vertex);
61 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
62
63 o.normal = mul(transpose((float3x3)unity_WorldToObject), float4(v.normal,0));
64
65 o.worldPos = TransformObjectToWorld(v.vertex.xyz);
66
67 return o;
68 }
69
70 float DotClamped(float3 a, float3 b) {
71 return saturate(dot(a, b));
72 }
73
74 half4 frag (v2f i) : SV_Target
75 {
76 i.normal = normalize(i.normal);
77
78 // sample the texture
79 float3 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv).rgb;
80
81 // 设置表面属性
82 BRDFData brdfData;
83 // 初始化BRDF数据
84 half oneMinusReflectivity = 1 - _Metallic;
85 half3 specColor = lerp(kDieletricSpec, albedo, _Metallic);
86 InitializeBRDFData(albedo, _Metallic, specColor, _Gloss, oneMinusReflectivity, brdfData);
87
88 // 获取主光源信息
89 Light mainLight = GetMainLight();
90
91 // 计算视线方向
92 float3 viewDir = normalize(GetCameraPositionWS() - i.worldPos);
93
94 // 计算直接光照
95 half3 color = LightingPhysicallyBased(brdfData, mainLight, i.normal, viewDir);
96
97 // 环境光计算需要考虑金属度
98 half3 ambient = SampleSH(i.normal) * albedo * oneMinusReflectivity;
99 color += ambient;
100
101 # ifdef _ADDITIONAL_LIGHTS
102 int additionalLightsCount = GetAdditionalLightsCount();
103
104 for (int lightIndex = 0; lightIndex < additionalLightsCount; lightIndex++)
105 {
106 // 获取额外光源(点光源、聚光灯等)
107 Light light = GetAdditionalLight(lightIndex, i.worldPos);
108
109 // 计算这个光源的贡献
110 color += LightingPhysicallyBased(brdfData, light, i.normal, viewDir);
111 }
112
113 # endif
114
115 return half4(color, 1.0);
116
117 }
118 ENDHLSL
119 }
120 }
121}
总觉得,最后的光照效果不太对劲的样子。。。。
另外就是,好像球谐函数也没太理解作用原理
但是先这样吧,有时间好好回来研究一下