目录索引
译文
计算机图形学中,最常用的一种反射模型是由裴祥风(Bui Tuong Phong)发明的 Phong 模型,该模型根据模型的法线实现了镜面反射。在 Maya 3D 中有一种材质就叫这个名字,可以为模型产生闪亮的表面。
![图片[1]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-91-1024x398.jpeg)
我们可以通过下述等式实现 Phong 模型的镜面反射:
s = sₐ sₚ max(0, h · n)²
注意看,这个等式非常像我们在上一小节中学习的兰伯特漫反射公式:
D = Dᵣ Dₗ max(0, n · l) Diffuse reflection.
公式中的 [h] 指的是“半程向量”,当它越模型表面的法线 [n] 时,代表反射的亮度越亮。
为了理解这个概念,让我们从模拟镜面反射开始。假设现在我们有一个平面和一束打到平面上的平行光:
![图片[2]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-92-1024x319.jpeg)
根据上图中的结果,结合初中物理所学的知识,我们可以推断出入射角与反射角的角度相同。这个性质带来了一个问题:如果我们的眼睛(相机)不在反射光的方向上,我们就无法看到它。为了解决这个问题,我们可以根据观察方向,计算出法线和光源入射方向之间的一个中间向量:
![图片[3]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-90-1024x317.jpeg)
向量 [e] 指的是人眼(相机)的“观察方向”,向量 [h] 即法线和光源入射方向之间的“半程向量”。我们可以通过下面的公式计算半程向量 [h]:
h = (l + e) / ( || l + e || );
值得一提的是,我们的程序中需要使用归一化后的向量,这代表向量的大小为“1”。因此,上述公式可以写作:
h = normalize(l + e);
在反射的计算中,我们需要用到至少三个变量,分别是:
- 光照方向
- 表面法线
- 半程向量(包含了视线方向)
另外,如果我们想要加上高光贴图,我们将会需要计算反射颜色。
让我们创建一个名为 USB_specular_reflection 的无光照着色器。值得一提的是,这个着色器中的许多操作与上一小节中的 USB_diffuse_shading 着色器是一样的,因此我们可以从头开始编写,也可以继续使用上一节中的着色器。
在程序中,让我们创建一个名为 SpecularShading 的函数,包含了我们在前文中提到的几个属性:
Shader "USB/USB_specular_reflection"
{
Properties { ... }
SubShader
{
Pass
{
CGPROGRAM
...
// declare the function in the program
float3 SpecularShading() { ... }
...
ENDCG
}
}
}
// internal structure of the SpecularShading function
float3 SpecularShading
(
float3 colorRefl, // Sa,镜面反射颜色
float specularInt, // Sp,镜面反射系数
float3 normal, // n,模型表面法线
float3 lightDir, // l,光线角度
float3 viewDir, // e,观察角度
float specularPow // exponent,指数
)
{
float3 h = normalize(lightDir + viewDir); // 半程向量
return colorRefl* specularInt * pow(max(0, dot(normal, h)), specularPow);
}
在上面的代码中,我们声明了一个名为 SpecularShading 的函数,该函数返回一个包含 RGB 颜色的三维向量。在它的参数中,我们可以找到反射光颜色(colorReflRGB)、镜面反射强度(specularInt [0, 1])、模型表面法线(normal XYZ)、光照方向(lightDir XYZ)、观察方向(viewDir XYZ)和镜面反射指数(specularPow [1, 128])。
就像我们在上一小节中所做的那样,我们需要使用世界空间下的模型法线、观察方向和光照方向,因此需要在顶点着色器阶段完成一些变换操作。
让我们在着色器中配置三个属性:纹理类型的高光贴图、[0, 1] 范围的镜面反射强度和 [1, 128] 范围的镜面反射指数:
Shader "USB/USB_specular_reflection"
{
Properties
{
// mode "white"
_MainTex ("Texture", 2D) = "white" {}
// mode "black"
_SpecularTex ("Specular Texture", 2D) = "black" {}
_SpecularInt ("Specular Intensity", Range(0, 1)) = 1
_SpecularPow ("Specular Power", Range(1, 128)) = 64
}
}
与 _MainTex 属性不同的是,_SpecularTex 的默认值是黑色,这一点可以从语句末尾的“black”中得到印证。如果我们不从 Unity 检查器中指定纹理,那么模型看起来就会是黑色的。
请注意,镜面反射将被加到主纹理中,因此在这种情况下,黑色在图形上是不可见的,因为黑色等于“0”,而 0 + 1 = 1。
![图片[4]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-89-1024x325.jpeg)
接下来,我们需要声明三个属性的连接变量:
Pass
{
CGPROGRAM
...
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SpecularTex;
// float4 _SpecularTex_ST;
float _SpecularInt;
float _SpecularPow;
float4 _LightColor0;
...
ENDCG
}
为什么我们在上面的代码中注释掉了 _SpecularTex_ST?我们已经学习过,连接变量末尾的“_ST”为纹理加上了平铺和偏移,而一般来说,高光贴图 _SpecularTex 不需要这两种变换。
我们还写了一个名为 _LightColor[n] 的连接变量,改变量将与 _SpecularTex 的颜色相乘,这样镜面反射的颜色就会被场景中的光源颜色所影响了。
现在属性们已经准备好了,我们可以开始在片元着色器中使用 SpecularShading 函数了。让我们从计算反射光颜色开始:
float3 SpecularShading() { ... }
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 colorRefl = _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, 0, 0, 0, 0, 0);
return col;
}
SpecularShading 函数的第一个参数代表了反射光颜色(Sa)。我们将高光贴图 _SpecularTex 与光源颜色(colorRefl)相乘,并将结果储存到名为 specCol 的三维向量中,传入函数的第一个参数。
第二个参数代表反射强度,我们可以直接使用属性 _SpecularInt,范围为 [0, 1]:
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 colorRefl = _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, 0, 0, 0, 0);
return col;
}
函数的第三个参数代表世界空间下的模型法线。我们需要将法线同时加入顶点输入与顶点输出中,并在顶点着色器阶段将法线变换到世界空间,这和我们在上一小节中所做的步骤相同。
让我们从配置顶点输入开始:
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
接着,我们需要在顶点输出中配置法线。然而,顶点输出结构体并不包含 NORMAL 语义,因此我们需要为法线配置四维语义 TEXCOORD[n]:
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal_world : TEXCOORD1;
};
在回到片元着色器之前,我们还需要在顶点输出中配置最后一项输出,该属性将被用于计算观察方向的参考点。
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal_world : TEXCOORD1;
float3 vertex_world : TEXCOORD2;
};
如上述代码所示,我们添加了一个名为 vertex_world 的新属性,代表了模型顶点在世界空间中的位置。
接着,我们来到顶点着色器中,对顶点与法线进行变换。与之前不同的是,我们现在直接使用 UnityObjectToWorldNormal 函数直接将法线从模型空间变换到世界空间:
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
o.normal_world = UnityObjectToWorldNormal(v.normal);
o.vertex_world = mul(unity_ObjectToWorld, v.vertex);
return o;
}
UnityObjectToWorldNormal 函数被包含在 UnityCg.cginc 中,与将 _ObjectToWorld 矩阵与模型法线相乘的结果一致。让我们看看这个函数的内部结构(译者注:这里的norm不是范数,就是normal的简写):
inline float3 UnityObjectToWorldNormal(in float3 norm)
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
return normalize(mul(norm, (float3x3) unity_WorldToObject));
#endif
}
需要注意的一点是,函数对 normal_world 进行了归一化处理,因此现在法线是一个大小为“1”的三维向量,代表世界空间中的一个方向。而 vertex_world 世界空间中的一个点(位置)。
让我们回到 SpecularShading 函数:
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// 我们已经将法线变换到了世界空间
float3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, normal, 0, 0, 0);
return col;
}
我们可以看到,在片元着色器中我们创建了一个名为 normal 的新三维向量,获取了顶点输出中变换到了世界空间的法线。接着,我们将其传入到 SpecularShading 函数中作为第三个参数。
因为我们可以直接使用内部变量 _WorldSpaceLightPos[n] 来表示 世界空间 中的光照方向,因此不需要对光线进行任何变换。
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// 让我们来计算光线方向
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, normal, lightDir, 0, 0);
return col;
}
现在我们只需要计算观察方向就好了,因为 SpecularShading 函数的最后一个参数对应镜面反射指数(specularPow),用于增加或减少反射。
为了计算观察方向,我们需要用世界空间下的相机位置减去世界空间下的模型顶点。Unity 提供了一个名为 _WorldSpaceCameraPos 的内置变量,这样我们就能精确地获取世界空间下的相机位置。
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// 让我们来计算光线方向
float3 viewDir = normalize(_WorldSpaceCameraPos - i.vertex_world);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
// 将观察方向传入函数
half3 specular = SpecularShading(specCol, _SpecularInt, normal, lightDir, viewDir, _SpecularPow);
return col;
}
最后一步是将函数计算得到的镜面反射结果添加到主纹理上:
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float3 viewDir = normalize(_WorldSpaceCameraPos - i.vertex_world);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
half3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, normal, lightDir, viewDir, _SpecularPow);
// 让我们将镜面反射加到纹理上
col.rgb += specular;
return col;
}
请记住,我们不能直接将一个四维向量与一个三维向量相加,所以我们需要确保只将镜面反射结果加到了主纹理的 RGB 通道上。
由于镜面反射是一种光照传递,我们必须再次进入标签(Tags)语义块,以配置漫反射的方式配置渲染路径:
Shader "USB/USB_specular_reflection"
{
Properties { ... }
SubShader
{
Tags
{
"RenderType"="Opaque"
"LightMode"="ForwardBase"
}
}
}
原文对照
One of the most common reflection models in computer graphics is the Phong model (Bui Tuong Phong), which adds specular brightness to a surface according to its normal. In fact, in Maya 3D there is a material with this name and its precise function is to generate shiny surfaces.
![图片[1]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-91-1024x398.jpeg)
According to its author, to add specular reflection, you must carry out the following operation:
s = sₐ sₚ max(0, h · n)²
Note that this equation is very similar to the function that allows us to calculate diffuse reflection.
D = Dᵣ Dₗ max(0, n · l) Diffuse reflection.
The big difference lies in the calculation of the vector [h] which corresponds to a half vector called “halfway”. This allows us to appreciate the brightness of the reflection when it is close to [n]; where the latter corresponds to the surface normals.
To understand the concept, we will begin our study by demonstrating specular reflection, assuming we have a flat surface and directional light pointing towards it as follows:
![图片[2]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-92-1024x319.jpeg)
From the image above, we can deduce that specular reflection has the same angle as the light direction. This represents a problem given that if our eye/camera is not in the same direction of reflection, then we will not be able to see it. To solve this, we can calculate an intermediate vector between the normals and the light direction, following the view direction.
![图片[3]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-90-1024x317.jpeg)
The vector [e] corresponds to the “view direction”, while the vector [h] is the halfway value that we have calculated between the light direction and the surface normal. To determine the value of the vector [h] we can perform the following function.
h = (l + e) / ( || l + e || );
It is worth mentioning that for our program we are going to use normalized vectors, this means that their magnitude will equal “one”, therefore, the previous operation can be reduced to the following function:
h = normalize(l + e);
In the reflection calculation, there will be at least three variables that we will have to add in our code, these correspond to
- lighting direction
- surface normals
- the halfway value that includes the view direction
Additionally, if we want to add specular maps, we will have to calculate the reflection color.
We will start a new program to review these concepts, for this, we will create an Unlit Shader which we will call USB_specular_reflection. It is worth mentioning that many of the operations that we will perform in this section are the same as those carried out in USB_diffuse_shading, so we can start from scratch or continue from the shader that we developed in the previous section.
Within our program, we will create a function called “SpecularShading.” Within it, we will include the properties mentioned above as follows:
Shader "USB/USB_specular_reflection"
{
Properties { ... }
SubShader
{
Pass
{
CGPROGRAM
...
// declare the function in the program
float3 SpecularShading() { ... }
...
ENDCG
}
}
}
// internal structure of the SpecularShading function
float3 SpecularShading
(
float3 colorRefl, // Sa
float specularInt, // Sp
float3 normal, // n
float3 lightDir, // l
float3 viewDir, // e
float specularPow // exponent
)
{
float3 h = normalize(lightDir + viewDir); // halfway
return colorRefl* specularInt * pow(max(0, dot(normal, h)), specularPow);
}
On this occasion, we have declared a function called SpecularShading that returns a three-dimensional vector for its RGB colors. Among its arguments we can find the reflection color (colorReflRGB), specular intensity (specularInt [0, 1]), surface normals (normal XYZ), light direction (lightDir XYZ), view direction (viewDir XYZ) and the specular exponent of (specularPow [1, 128]).
In the same way that we did in the diffuse reflection calculation, the normals and the view and lighting directions will be calculated in world-space, therefore, some transformations will have to be made in the vertex shader stage.
We will start by configuring three properties for our shader: a texture property for the specular map, a reflection intensity range between zero and one, and a new specular exponent range between 1 and 128.
Shader "USB/USB_specular_reflection"
{
Properties
{
// mode "white"
_MainTex ("Texture", 2D) = "white" {}
// mode "black"
_SpecularTex ("Specular Texture", 2D) = "black" {}
_SpecularInt ("Specular Intensity", Range(0, 1)) = 1
_SpecularPow ("Specular Power", Range(1, 128)) = 64
}
}
Unlike the _MainTex property, _SpecularTex has a black color as a default. This can be corroborated in the definition “black” found at the end of the statement. Its operation is quite simple: If we do not assign a texture from the Unity Inspector, then the object will look black.
Note that specular is going to be added to the main texture, therefore, for this case black will not be visible graphically because, as we already know, black equals “zero”, and zero plus one equals one.
![图片[4]-《Unity着色器圣经》7.0.4 | 镜面反射-软件开发学习笔记](https://gamedevfan.cn/wp-content/uploads/2025/05/image-89-1024x325.jpeg)
Next, we must declare the connection variables for the three properties that we have added.
Pass
{
CGPROGRAM
...
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _SpecularTex;
// float4 _SpecularTex_ST;
float _SpecularInt;
float _SpecularPow;
float4 _LightColor0;
...
ENDCG
}
Why have we discarded the variable _SpecularTex_ST in the above example? As we already know, the connection variables ending in the suffix _ST add tiling and offset to their texture. In the case of _SpecularTex it will not be necessary to add this type of transformation because, generally, textures or specular maps do not need them due to their consistent nature.
Another connection variable that we have generated is _LightColor[n]. This variable will be used to multiply the color result of _SpecularTex, in this way, the specular color will be affected by the color of the light source that we have in our scene.
The properties are now functional, so we can start implementing the SpecularShading function in the fragment shader stage.
We will start by calculating the reflection color.
float3 SpecularShading() { ... }
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 colorRefl = _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, 0, 0, 0, 0, 0);
return col;
}
The first argument in the SpecularShading function corresponds to reflection color. To do this, we multiply the texture _SpecularTex by the lighting color (colorRefl), and the factor is stored within a three-dimensional vector called specCol, which is assigned as the first argument.
The second argument corresponds to specular intensity, for this, we can simply assign the property _SpecularInt, which is a range between zero and one [0, 1].
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 colorRefl = _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, 0, 0, 0, 0);
return col;
}
The third argument in the function refers to the object normals in world-space. To do this, we have to add the normals in both the vertex input and output and then transform their space into the vertex shader stage, in the same way we did in the diffuse reflection calculation.
We will start by configuring the normals in the vertex input.
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
Then we must assign the normals in the vertex output, however, we must remember that the NORMAL semantic does not exist for this process, therefore, we must use TEXCOORD[n] as it has up to four dimensions.
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal_world : TEXCOORD1;
};
Before returning to the fragment shader stage, we create one last property in the vertex output. This property will be used later in the calculation of the view direction, as a reference point.
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal_world : TEXCOORD1;
float3 vertex_world : TEXCOORD2;
};
As we can see, a new property called vertex_world has been included, which refers to the position of the object vertices in world-space.
We then go to the vertex shader stage to transform the coordinates’ space, however, unlike the previous processes, we now use the UnityObjectToWorldNormal function to transform the normals from object-space to world-space.
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
o.normal_world = UnityObjectToWorldNormal(v.normal);
o.vertex_world = mul(unity_ObjectToWorld, v.vertex);
return o;
}
UnityCg.cginc includes the UnityObjectToWorldNormal function, which is equivalent to inversely multiplying the unity_ObjectToWorld matrix by the object normal input. Next we can look at its internal structure.
inline float3 UnityObjectToWorldNormal(in float3 norm)
{
#ifdef UNITY_ASSUME_UNIFORM_SCALING
return UnityObjectToWorldDir(norm);
#else
return normalize(mul(norm, (float3x3) unity_WorldToObject));
#endif
}
One factor to consider is that normal_world is normalizing the transformation operation. This is because normals are a direction of space; a three-dimensional vector returning a maximum magnitude of “one”, while vertex_world remains a position in space, with the difference now being calculated in world-space.
We will continue with the SpecularShading function.
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// we implement the normals in world-space
float3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, normal, 0, 0, 0);
return col;
}
As we can see, a new three-dimensional vector called normal has been created. This vector has its normal output in world-space, that’s why it has been assigned as the third argument in the function.
It will not be necessary to generate any kind of lighting transformation because we can use the internal variable _WorldSpaceLightPos[n], which refers to the light direction in worldspace.
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// let’s calculate the light direction
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, normal, lightDir, 0, 0);
return col;
}
Now we only need to calculate the view direction since the last argument in the SpecularShading function corresponds to the exponential value (specularPow) which increases or decreases reflection.
To calculate the view direction, we must subtract the object vertices in world-space from the camera also in world-space. Unity has an internal variable called _WorldSpaceCameraPos, which gives us precise access to the scene’s camera position.
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// let’s calculate the light direction
float3 viewDir = normalize(_WorldSpaceCameraPos - i.vertex_world);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
// we pass the view direction to the function
half3 specular = SpecularShading(specCol, _SpecularInt, normal, lightDir, viewDir, _SpecularPow);
return col;
}
The only operation left is to add specularity to the main texture, to do this we perform the following operation:
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float3 viewDir = normalize(_WorldSpaceCameraPos - i.vertex_world);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
half3 normal = i.normal_world;
fixed3 colorRefl= _LightColor0.rgb;
fixed3 specCol = tex2D(_SpecularTex, i.uv) * colorRefl;
half3 specular = SpecularShading(specCol, _SpecularInt, normal, lightDir, viewDir, _SpecularPow);
// let’s add the specularity to the texture
col.rgb += specular;
return col;
}
Remember that we cannot add a four-dimensional vector to a three-dimensional vector, so we must make sure that specular reflection is only added to the main texture RGB channels.
By the fact that specular reflection is a lighting pass, we must once again go to the Tags and configure the render path in the same way as we did for diffuse reflection.
Shader "USB/USB_specular_reflection"
{
Properties { ... }
SubShader
{
Tags
{
"RenderType"="Opaque"
"LightMode"="ForwardBase"
}
}
}
暂无评论内容