工程GIT地址:https://gitee.com/yaksue/yaksue-graphics
目标
菲涅尔效应本身是一个光学现象,在PBR中有重要的作用(见 PBR理论 - LearnOpenGL CN )
暂且不提其物理学上的原理,仅从表现效果上来看,它可以计算出:在一个视线下物体 边缘 的部分,例如观察一个球:(越白表示菲涅尔效果越强)
(图片来源: PBR理论 - LearnOpenGL CN )
虽然这一现象在本质上或许较为复杂。但是从效果角度来说,可以简单地用视线方向与物体表面法线做点乘来近似模拟:
F
r
e
n
e
l
=
1.0
−
v
⃗
⋅
n
⃗
Frenel = 1.0-\vec{v} \cdot\vec{n}
Frenel=1.0−v⋅n
其中v
是归一化后的视线方向,n
是归一化后的物体表面法线方向,计算的结果还可以进行一些指数运算以调整效果的范围。
本篇的目标实现简单的菲涅尔效果。所需的代码量极小。
(本来没想专门为此记录一篇,但是其牵扯到的内容是我后续一些目标的前置,所以我必须先做这部分。而且,在实现时我发现有些东西是值得记录的。再加上本篇的内容相对独立且有效果展示,所以最终就为这些极小的代码量而专门记录一篇了)
1. 为计算视线传入数据
世界空间中的视线方向为:
v
⃗
=
P
眼
睛
在
世
界
空
间
中
的
位
置
−
P
像
素
在
世
界
空
间
中
的
位
置
\vec{v}=P_{眼睛在世界空间中的位置}-P_{像素在世界空间中的位置}
v=P眼睛在世界空间中的位置−P像素在世界空间中的位置
其中,眼睛在世界空间中的位置
实际就是相机的位置,我将需要在UniformBuffer中添加这项数据:
随后每帧更新:
//眼睛位置:
glm::vec4 eyePosition = glm::vec4(camera->GetPosition(), 0);
renderer->data_scene.eyePosition = eyePosition;
而对于像素在世界空间中的位置
,不能使用GLSL中的gl_Position
或是HLSL中的Pos : SV_POSITION
,因为他们都是屏幕空间的坐标,而非世界空间中的位置。要想获得世界空间中的位置,需要在顶点着色器中计算,并作为传入像素着色器的顶点插值数据。
最终,顶点着色器代码做了如下添加:
GLSL:
HLSL:
2. Shader中计算菲涅尔
计算菲涅尔效果是放在像素着色器中的。
GLSL:
void main()
{
//视线方向
vec3 EyeDir = ub_SceneData.eyePosition.xyz - vs_fs_worldPosition;
//法线点乘视线
float NdE = dot(normalize(vs_fs_normal),normalize(EyeDir));
//菲涅尔系数
float fresnel = 1.0 - NdE;
//将菲涅尔系数做指数变化使得效果更明显
fresnel = pow(fresnel,3);
fColor = vec4(fresnel,fresnel,fresnel,1.0);
}
HLSL:
float4 Main( VS_OUTPUT input ) : SV_Target
{
//视线方向
float3 EyeDir = eyePosition.xyz - input.WorldPosition;
//法线点乘视线
float NdE = dot(normalize(input.Normal),normalize(EyeDir));
//菲涅尔系数
float fresnel = 1.0 - NdE;
//将菲涅尔系数做指数变化使得效果更明显
fresnel = pow(fresnel,3);
return float4(fresnel,fresnel,fresnel,1.0);
}
效果