优的深圳网站设计公司,jsp做网站都可以做什么,字体大全100种,程序员wordpress光照
在本章节中#xff0c;我们把重点放在将之前讨论的理论转化为实际的渲染器#xff0c;这个渲染器将使用直接的#xff08;或解析的#xff09;光源#xff1a;比如点光源#xff0c;定向灯或聚光灯。
我们先来看看上一个章提到的反射方程的最终版#xff1a; 我们…光照
在本章节中我们把重点放在将之前讨论的理论转化为实际的渲染器这个渲染器将使用直接的或解析的光源比如点光源定向灯或聚光灯。
我们先来看看上一个章提到的反射方程的最终版 我们大致上清楚这个反射方程在干什么但我们仍然留有一些迷雾尚未揭开。比如说我们究竟将怎样表示场景上的辐照度(Irradiance), 辐射率(Radiance) L我们知道辐射率L在计算机图形领域中表示光源的辐射通量(Radiant flux)或光源在给定立体角ω下发出的光能。在我们的情况下不妨假设立体角ω无限小这样辐射度就表示光源在一条光线或单个方向向量上的辐射通量。
基于以上的知识我们如何将其转化为之前的教程中所积累的一些光照知识呢 那么想象一下我们有一个点光源一个在所有方向都具有相同亮度的光源它的辐射通量为用RGB表示为23.47, 21.31, 20.79。该光源的辐射强度(Radiant Intensity)等于其在所有出射光线的辐射通量。 然而当我们为一个表面上的特定的点p着色时在其半球领域ΩΩ的所有可能的入射方向上只有一个入射方向向量ωi直接来自于该点光源。 假设我们在场景中只有一个光源位于空间中的某一个点因而对于p点的其他可能的入射光线方向上的辐射率为0 如果从一开始我们就假设点光源不受光线衰减光照强度会随着距离变暗的影响那么无论我们把光源放在哪入射光线的辐射率总是一样的除去入射角cosθ对辐射率的影响之外。 这是因为无论我们从哪个角度观察它点光源总具有相同的辐射强度我们可以有效地将其辐射强度建模为其辐射通量: 一个常量向量23.47, 21.31, 20.79。
然而辐射率也需要将位置p作为输入正如所有现实的点光源都会受光线衰减影响一样点光源的辐射强度应该根据点p所在的位置和光源的位置以及他们之间的距离而做一些缩放。 因此根据原始的辐射方程我们会根据表面法向量n和入射角度wi来缩放光源的辐射强度。
在实现上来说对于直接点光源的情况辐射率函数L先获取光源的颜色值 然后光源和某点p的距离衰减接着按照n⋅wi缩放但是仅仅有一条入射角为wi的光线打在点p上 这个wi同时也等于在p点光源的方向向量。写成代码的话会是这样
vec3 lightColor vec3(23.47, 21.31, 20.79);
vec3 wi normalize(lightPos - fragPos);
float cosTheta max(dot(N, Wi), 0.0);
float attenuation calculateAttenuation(fragPos, lightPos);
float radiance lightColor * attenuation * cosTheta;除了一些术语上的差异外这段代码对你们来说应该很熟悉这正是我们一直以来怎么计算漫反射光照的当涉及到直接光照(direct lighting)时辐射率的计算方式和我们之前计算只有一个光源照射在物体表面的时候非常相似。
请注意这个假设成立的条件是点光源体积无限小相当于在空间中的一个点。如果我们认为该光源是具有体积的它的辐射率会在不只一个入射光方向上非零。
对于其它类型的从单点发出来的光源我们类似地计算出辐射率。比如定向光(directional light)拥有恒定的wi而不会有衰减因子而一个聚光灯光源则没有恒定的辐射强度其辐射强度是根据聚光灯的方向向量来缩放的。
这也让我们回到了对于表面的半球领域(hemisphere)ΩΩ的积分∫∫上。由于我们事先知道的所有贡献光源的位置因此对物体表面上的一个点着色并不需要我们尝试去求解积分。我们可以直接拿光源的已知的数目去计算它们的总辐照度因为每个光源仅仅只有一个方向上的光线会影响物体表面的辐射率。这使得PBR对直接光源的计算相对简单因为我们只需要有效地遍历所有有贡献的光源。而当我们之后把环境照明也考虑在内的IBL教程中我们就必须采取积分去计算了这是因为光线可能会在任何一个方向入射。
一个PBR表面模型
现在让我们开始写片段着色器来实现上述的PBR模型吧~ 首先我们需要把PBR相关的输入放进片段着色器。
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;uniform vec3 camPos;uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;我们把通用的顶点着色器的输出作为输入的一部分。另一部分输入则是物体表面模型的一些材质参数。
然后在片段着色器的开始部分我们做一下任何光照算法都需要做的计算:
void main()
{vec3 N normalize(Normal); vec3 V normalize(camPos - WorldPos);[...]
}
直接光照
在本教程的例子中我们会采用总共4个点光源来直接表示场景的辐照度。为了满足反射率方程我们循环遍历每一个光源计算他们各自的辐射率然后求和接着根据BRDF和光源的入射角来缩放该辐射率。我们可以把循环当作在物体的半球领域内对所有直接光源求积分。首先我们来计算一些可以预计算的光照变量
vec3 Lo vec3(0.0);
for(int i 0; i 4; i)
{vec3 L normalize(lightPositions[i] - WorldPos);vec3 H normalize(V L);float distance length(lightPositions[i] - WorldPos);float attenuation 1.0 / (distance * distance);vec3 radiance lightColors[i] * attenuation; [...] 由于我们在线性空间内计算光照我们会在着色器的最后进行Gamma校正我们使用在物理上更为准确的平方倒数作为衰减。
相对于物理上正确来说你可能仍然想使用常量线性或者二次衰减方程他们在物理上相对不准确却可以为您提供在光的能量衰减更多的控制。
然后对于每一个光源我们都想计算完整的 Cook-Torrance specular BRDF项 首先我们想计算的是镜面反射和漫反射之间的比值或者说与表面折射的光线相比它反射了多少光线。 我们从上一个教程知道可以使用菲涅尔方程计算注意这里用的clamp是为了避免黑点
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{return F0 (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
} Fresnel-Schlick近似法接收一个参数F0被称为0°入射角的反射率或者说是直接(垂直)观察表面时有多少光线会被反射。 这个参数F0会因为材料不同而不同而且对于金属材质会带有颜色。在PBR金属流中我们简单地认为大多数的绝缘体在F0为0.04的时候看起来视觉上是正确的对于金属表面我们根据反射率特别地指定F0。 因此代码上看起来会像是这样
vec3 F0 vec3(0.04);
F0 mix(F0, albedo, metallic);
vec3 F fresnelSchlick(max(dot(H, V), 0.0), F0);可以看到对于非金属表面F0始终为0.04。对于金属表面我们根据初始的F0和表现金属属性的反射率进行线性插值。
我们已经算出F 剩下的项就是计算法线分布函数D和几何遮蔽函数G了。
在直接PBR光照着色器中D和G的计算代码类似于
float DistributionGGX(vec3 N, vec3 H, float roughness)
{float a roughness*roughness;float a2 a*a;float NdotH max(dot(N, H), 0.0);float NdotH2 NdotH*NdotH;float num a2;float denom (NdotH2 * (a2 - 1.0) 1.0);denom PI * denom * denom;return num / denom;
}float GeometrySchlickGGX(float NdotV, float roughness)
{float r (roughness 1.0);float k (r*r) / 8.0;float num NdotV;float denom NdotV * (1.0 - k) k;return num / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{float NdotV max(dot(N, V), 0.0);float NdotL max(dot(N, L), 0.0);float ggx2 GeometrySchlickGGX(NdotV, roughness);float ggx1 GeometrySchlickGGX(NdotL, roughness);return ggx1 * ggx2;
}这里比较重要的是和理论章节相比我们直接把粗糙度(roughness)作为参数传给了上述函数通过这种方式我们可以针对每一个不同的项对粗糙度做一些修改。根据迪士尼公司给出的观察以及后来被Epic Games公司采用的光照模型在几何遮蔽函数和法线分布函数中采用粗糙度的平方会让光照看起来更加自然。
现在两个函数都给出了定义在计算反射的循环中计算NDF和G项就变得很简单
float NDF DistributionGGX(N, H, roughness);
float G GeometrySmith(N, V, L, roughness); 这样我们就凑够了足够的项来计算Cook-Torrance BRDF:
vec3 nominator NDF * G * F;
float denominator 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) 0.001;
vec3 specular nominator / denominator; 注意我们在分母项中加了一个0.001为了避免出现除零错误。
现在我们终于可以计算每个光源在反射率方程中的贡献值了因为菲涅尔方程直接给出了kS 我们可以使用F表示所有打在物体表面上的镜面反射光的贡献。 从kS我们很容易计算折射的比值kD
vec3 kS F;
vec3 kD vec3(1.0) - kS;kD * 1.0 - metallic; 我们可以看作kS表示光能中被反射的能量的比例 而剩下的光能会被折射 比值即为kD。更进一步来说因为金属不会折射光线因此不会有漫反射。所以如果表面是金属的我们会把系数kD变为0。 这样我们终于集齐所有变量来计算我们出射光线的值 const float PI 3.14159265359;float NdotL max(dot(N, L), 0.0); Lo (kD * albedo / PI specular) * radiance * NdotL;
}最终的结果Lo或者说是出射光线的辐射率实际上是反射率方程的在半球领域ΩΩ的积分的结果。但是我们实际上不需要去求积因为对于所有可能的入射光线方向我们知道只有4个方向的入射光线会影响片段的着色。因为这样我们可以直接循环N次计算这些入射光线的方向(N也就是场景中光源的数目)。
剩下的工作就是加一个环境光照项给Lo然后我们就拥有了片段的最后颜色
vec3 ambient vec3(0.03) * albedo * ao;
vec3 color ambient Lo;
线性空间和HDR渲染
直到现在我们假设的所有计算都在线性的颜色空间中进行的因此我们需要在着色器最后做伽马矫正。 在线性空间中计算光照是非常重要的因为PBR要求所有输入都是线性的如果不是这样我们就会得到不正常的光照。另外我们希望所有光照的输入都尽可能的接近他们在物理上的取值这样他们的反射率或者说颜色值就会在色谱上有比较大的变化空间。Lo作为结果可能会变大得很快(超过1)但是因为默认的低动态范围LDR而取值被截断。所以在伽马矫正之前我们采用色调映射使Lo从LDR的值映射为HDR的值。
color color / (color vec3(1.0));
color pow(color, vec3(1.0/2.2)); 这里我们采用的色调映射方法为Reinhard 操作使得我们在伽马矫正后可以保留尽可能多的辐照度变化。 我们没有使用一个独立的帧缓冲或者采用后期处理所以我们需要直接在每一步光照计算后采用色调映射和伽马矫正。 采用线性颜色空间和HDR在PBR渲染管线中非常重要。如果没有这些操作几乎是不可能正确地捕获到因光照强度变化的细节这最终会导致你的计算变得不正确在视觉上看上去非常不自然。
完整的直接光照PBR着色器
现在剩下的事情就是把做好色调映射和伽马矫正的颜色值传给片段着色器的输出然后我们就拥有了自己的直接光照PBR着色器。 为了完整性这里给出了完整的代码
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
in vec3 WorldPos;
in vec3 Normal;// material parameters
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;// lights
uniform vec3 lightPositions[4];
uniform vec3 lightColors[4];uniform vec3 camPos;const float PI 3.14159265359;float DistributionGGX(vec3 N, vec3 H, float roughness);
float GeometrySchlickGGX(float NdotV, float roughness);
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness);
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness);void main()
{ vec3 N normalize(Normal);vec3 V normalize(camPos - WorldPos);vec3 F0 vec3(0.04); F0 mix(F0, albedo, metallic);// reflectance equationvec3 Lo vec3(0.0);for(int i 0; i 4; i) {// calculate per-light radiancevec3 L normalize(lightPositions[i] - WorldPos);vec3 H normalize(V L);float distance length(lightPositions[i] - WorldPos);float attenuation 1.0 / (distance * distance);vec3 radiance lightColors[i] * attenuation; // cook-torrance brdffloat NDF DistributionGGX(N, H, roughness); float G GeometrySmith(N, V, L, roughness); vec3 F fresnelSchlick(max(dot(H, V), 0.0), F0); vec3 kS F;vec3 kD vec3(1.0) - kS;kD * 1.0 - metallic; vec3 nominator NDF * G * F;float denominator 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) 0.001; vec3 specular nominator / denominator;// add to outgoing radiance Lofloat NdotL max(dot(N, L), 0.0); Lo (kD * albedo / PI specular) * radiance * NdotL; } vec3 ambient vec3(0.03) * albedo * ao;vec3 color ambient Lo;color color / (color vec3(1.0));color pow(color, vec3(1.0/2.2)); FragColor vec4(color, 1.0);
} 如果我们采用这个着色器加上4个点光源和一些球体同时令这些球体的金属性(metallic)和粗糙度(roughness)沿垂直和水平方向分别变化我们会得到这样的结果 (上述图片)从下往上球体的金属性从0.0变到1.0 从左到右球体的粗糙度从0.0变到1.0。你可以看到仅仅改变这两个值显示的效果会发生巨大的改变
下面是十行十列的效果 带贴图的PBR
把我们系统扩展成可以接受纹理作为参数可以让我们对物体的材质有更多的自定义空间
[...]
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;void main()
{vec3 albedo pow(texture(albedoMap, TexCoords).rgb, 2.2);vec3 normal getNormalFromNormalMap();float metallic texture(metallicMap, TexCoords).r;float roughness texture(roughnessMap, TexCoords).r;float ao texture(aoMap, TexCoords).r;[...]
}不过需要注意的是一般来说反射率(albedo)纹理在美术人员创建的时候就已经在sRGB空间了因此我们需要在光照计算之前先把他们转换到线性空间。一般来说环境光遮蔽贴图(ambient occlusion maps)也需要我们转换到线性空间。不过金属性(Metallic)和粗糙度(Roughness)贴图大多数时间都会保证在线性空间中。
只是把之前的球体的材质性质换成纹理属性就在视觉上有巨大的提升 注意金属表面会在场景中看起来有点黑因为他们没有漫反射。它们会在考虑环境镜面光照的时候看起来更加自然不过这是我们下一个教程的事情了。
相比起在网上找到的其他PBR渲染结果来说尽管在视觉上不算是非常震撼因为我们还没考虑到基于图片的光照(IBL)但我们现在也算是有了一个基于物理的渲染器了虽然还没考虑IBL但你会发现你的光照看起来更加真实了。
最终效果