作者:王子饼干 ——镇江虹视游戏科技有限公司联合创始人、《多洛可小镇》制作人
原文链接:UnityShader入门精要-URP管线3. Blinn-Phong高光模型
原文发布时间:2022年6月22日
文章类型:授权转载
笔记当前的Unity版本:2019.3
当前最新的Unity版本:2020.2
上节我们讲了一下漫反射模型,这节我们补充一下高光模型,在该节中我们主要通过两个公式来简单回顾一下高光模型是怎么实现的。
高光的计算主要是通过视角方向与光的反射方向的余弦值来控制的,而反射方向可以通过对光的方向和物体法线进行反射计算获得。简单来说,高光强度因子可以用下面的公式来表达。
注意上面的,反射方向是负的。有了高光因子之后,我们用它作为底,高光参数_Gloss作为指数来计算幂。之后与表面颜色混合,即为普通的phong高光模型。
其中G为高光系数。spec为高光因子。在这个任务当中,反射方向的计算比较困难,不过HLSL直接提供了reflect函数可以直接计算反射方向,参数为光源方向和法线方向。
half3 reflectDir = normalize(reflect(lightDir,worldNormal));
注意对于reflect函数来说,lightDir在前面,而worldNormal在后面。有了反射方向和视角方向,我们就可以计算得到Phong高光。
float spec = pow(saturate(dot(viewDir,-reflectDir)),_Gloss);
看起来比较简单,注意反射方向要反向。
下面是全部的代码。
Shader "URPNotes/Specular"{
Properties{
_BaseColor("Base Color",Color) = (1.0,1.0,1.0,1.0)
_Gloss("Gloss",Range(8,20)) = 8.0
}
SubShader{
Tags{
"RenderType"="Opaqua"
"RenderPipeline"="UniversalRenderPipeline"
}
pass{
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _BaseColor;
half _Gloss;
CBUFFER_END
#pragma vertex Vertex
#pragma fragment Pixel
struct vertexInput{
float3 vertex:POSITION;
float3 normal:NORMAL;
};
struct vertexOutput{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
};
vertexOutput Vertex(vertexInput v){
vertexOutput o;
o.pos = TransformObjectToHClip(v.vertex.xyz);
o.worldNormal = TransformObjectToWorldNormal(v.normal);
o.worldPos = TransformObjectToWorld(v.vertex.xyz);
return o;
}
half4 Pixel(vertexOutput i):SV_TARGET{
/* 首先计算基础信息 */
Light light = GetMainLight();
half3 lightDir = normalize(TransformObjectToWorldDir(light.direction));
half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
half3 reflectDir = normalize(reflect(lightDir,i.worldNormal));
/* 计算漫反射 */
half3 diffuse = _BaseColor.xyz * saturate(dot(lightDir,i.worldNormal));
/* 计算高光 */
float spec = pow(saturate(dot(viewDir,-reflectDir)),_Gloss);
half3 specular = _BaseColor.xyz * spec;
/*叠加后输出*/
return half4(specular + diffuse,1);
}
ENDHLSL
}
}
}
这里稍微注意一下,由于计算视角方向需要顶点位置,所以我们在顶点着色器的输出中增加了worldPos。
其他没有太多要注意的地方,下面是最终的效果图
Blinn-Phong高光和原始Phong高光的区别主要是Blinn-Phong高光采用半程向量来计算高光,省去了原来计算反射角的函数。
半角向量的计算可以通过下面的公式来实现。
有了半角向量之后,可以直接通过半角向量和法线相乘得到高光系数。
非常简单哦,这里就不再阐述原理了,大家可以在我的UnityShader入门精要笔记第6章找到半程向量的原理。下面是Blinn-Phong高光的所有代码,也就是更换了一下高光系数的计算方式。
Shader "URPNotes/Blinn-Phong"{
Properties{
_BaseColor("Base Color",Color) = (1.0,1.0,1.0,1.0)
_Gloss("Gloss",Range(8,20)) = 8.0
}
SubShader{
Tags{
"RenderType"="Opaqua"
"RenderPipeline"="UniversalRenderPipeline"
}
pass{
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _BaseColor;
half _Gloss;
CBUFFER_END
#pragma vertex Vertex
#pragma fragment Pixel
struct vertexInput{
float3 vertex:POSITION;
float3 normal:NORMAL;
};
struct vertexOutput{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
};
vertexOutput Vertex(vertexInput v){
vertexOutput o;
o.pos = TransformObjectToHClip(v.vertex.xyz);
o.worldNormal = TransformObjectToWorldNormal(v.normal);
o.worldPos = TransformObjectToWorld(v.vertex.xyz);
return o;
}
half4 Pixel(vertexOutput i):SV_TARGET{
/* 首先计算基础信息 */
Light light = GetMainLight();
half3 lightDir = normalize(TransformObjectToWorldDir(light.direction));
half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
//half3 reflectDir = normalize(reflect(lightDir,i.worldNormal));
/* 计算漫反射 */
half3 diffuse = _BaseColor.xyz * saturate(dot(lightDir,i.worldNormal));
/* 计算高光 */
half3 halfDir = normalize(viewDir + lightDir);
float spec = pow(saturate(dot(i.worldNormal,halfDir)),_Gloss);
half3 specular = _BaseColor.xyz * spec;
/*叠加后输出*/
return half4(specular + diffuse,1);
}
ENDHLSL
}
}
}
前面的几章会尽快的掠过,毕竟不是重点,而且也不能老把时间浪费在这些简单的东西上,所以可能比较粗糙。等到后面讲一些专题的时候,会比较细节,我现在有几个想做的专题。其中之一就是真实的水体Shader。
本次所有的代码都已经上传到了GitHub,可以在下面的链接中找到,喜欢这个系列可以点个关注,或者给我的github贡献一颗⭐