来源:《UNITY SHADER入门精要》
1、代码理解
我们现在要注意光源的 5 个属性:位置、方向、颜色、强度、衰减。
在理解代码之前,我们依然需要熟悉我们的理论,主要我们要设置两个 Pass,注意它们的不同的特性,和要做的事情。
注意,据书中所说,注意两个 Pass 中的 #pragma multi_complie_fwdbase
命令和 #pragma multi_complie_fwdadd
命令,在官方文档中没有说明,但是,实验表明,只有使用了这两个编译指令,我们才可以在相关的 Pass 访问到光照变量、、光照衰减值等等的变量。
Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
第 17 句,我们使用了 #pragma 编译命令。#pragma multicomplie_fwdbase
确保我们在 Shader 中使用光照衰减等光照变量可以被正确赋值。这个 Pass 我们称之为 BasePass,正如我们之前概念里提到的那样。
#pragma vertex vert
#pragma fragment frag
#include "Lighting.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 = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
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
}
所有的工作都在片元着色器中完成,顶点着色器只是在做了最简单的坐标转换而已。我们这里依然使用了 _LightColor0
来获取光源的强度和 _WorldSpaceLightPos0
来获取场景中的位置。平行光的强度不会衰减,所以,我们这里 atten
赋值为1。
如果一个场景中包含了多个平行光,Unity 会选择最亮的平行光传递给 Base Pass 进行逐像素处理,其他的平行光会按照逐顶点活在 Additional Pass 中按照住像素的方式处理。
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
我们第二个 Pass ,按照理论知识,第 3 行,我们定义为 Addtional Pass,为此,我们首先需要设置 Pass 的渲染路径标签:"LightMode" = "ForwardAdd"
。
第 5 行,我们使用 Blend One One
命令来对结果进行混合,而亲测,选择更容易理解的 Blend SrcAlpha DstAlpha
也能得到正确的效果。
第10 行,我们还要给给出宏指令 #prgma multi_complie_fwdadd
指令,这样才能保证我们在 Addtional Pass 中获得正确的光照变量。
#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 = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
同样,顶点着色器做的事情几乎就是常规的操作。
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
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;
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
代码中共分为两个部分,第一个部分,第 3-13 行,我们进行第一个部分的处理:
首先,我们仍然使用 _LightColor0
来得到光源的颜色和强度。我们使用 宏定义 #ifdef USING_DIRECTIONAL_LIGHT
来确定当前是否是平行光。因为,如果 Pass 处理的光源是萍乡光,那么 Unity 底层就会定义 USING_DIRECTIONAL_LIGHT
。如果是平行光,那么可以直接使用 _WorldSpaceLightPos0.xyz
得到光源方向。如果是点光源或者聚光灯的话,那么 _WorldSpaceLightPos0.xyz
表示的是世界空间下光源的位置。
第二个部分,第 15-27 行,我们处理不同光源的衰减,如果是平行光的话,那 atten = 1
那就不衰减。如果是点光源或聚光灯,处理更加复杂,本来会涉及大量的开根号、除法等运算,但是为了节省效率,Unity 选择了使用一张纹理作为查找表(Lookup Table, LUT),对这个表取样,以获得光源的衰减值。文章来源:https://www.toymoban.com/news/detail-419659.html
例子中的场景有 5 个光源,其中 1 个是平行光,其他 4 个都是点光源。平行光会按照 Base Pass 逐像素的方式处理,其他四个点光源都会按照 Addtional Pass 中逐像素的方式处理,每一个光源都会调用一次 Additional Pass。
但是如果我们手动把场景中的所有光源设置为 Not Important 那么,因为没有在 Bass Pass 中计算逐顶点 和 SH光源,因此场景中的 4 个点光源实际上不会对物体造成任何影响。文章来源地址https://www.toymoban.com/news/detail-419659.html
到了这里,关于【图形学】30 前向渲染多光照场景代码理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!