PBR渲染流程
PBR材质通常由一组参数定义,这些参数描述了材质的物理属性。Albedo(反照率):材质的基本颜色,不包含任何光照信息。Normal Map(法线贴图):用于细化表面细节,改变表面的法线方向。Metallic(金属度):描述材质是否为金属。金属材质的值为1,非金属材质的值为0。Roughness(粗糙度):描述材质表面的光滑程度。值越高,表面越粗糙,反射越模糊。Ambient Occlusion(
物理基渲染(Physically Based Rendering, PBR)是一种基于物理原理的渲染方法,旨在实现更真实的光照和材质效果。PBR渲染流程通常包括以下几个主要步骤:
1. 材质定义
PBR材质通常由一组参数定义,这些参数描述了材质的物理属性。常见的PBR材质参数包括:
- Albedo(反照率):材质的基本颜色,不包含任何光照信息。
- Normal Map(法线贴图):用于细化表面细节,改变表面的法线方向。
- Metallic(金属度):描述材质是否为金属。金属材质的值为1,非金属材质的值为0。
- Roughness(粗糙度):描述材质表面的光滑程度。值越高,表面越粗糙,反射越模糊。
- Ambient Occlusion(环境光遮蔽):描述表面在环境光照下的遮蔽程度。
2. 几何处理
在渲染开始之前,需要对场景中的几何体进行处理。这包括顶点变换、法线计算等。通常在顶点着色器中完成这些操作。
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoords;
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoords = aTexCoords;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
3. 光照计算
光照计算是PBR渲染的核心部分。PBR通常使用基于物理的光照模型,如Cook-Torrance模型。这个模型包括以下几个部分:
- Fresnel效应:描述光在不同角度下的反射率变化。
- 法线分布函数(NDF):描述微表面法线的分布。
- 几何遮蔽函数(Geometry Function):描述光线在微表面之间的遮蔽和阴影。
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 camPos;
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;
struct Light {
vec3 position;
vec3 color;
};
uniform Light lights[4];
uniform int lightCount;
const float PI = 3.14159265359;
vec3 getNormalFromMap()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(FragPos);
vec3 Q2 = dFdy(FragPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 N = normalize(Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = -normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
return normalize(TBN * tangentNormal);
}
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;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2));
float metallic = texture(metallicMap, TexCoords).r;
float roughness = texture(roughnessMap, TexCoords).r;
float ao = texture(aoMap, TexCoords).r;
vec3 N = getNormalFromMap();
vec3 V = normalize(camPos - FragPos);
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 Lo = vec3(0.0);
for(int i = 0; i < lightCount; ++i)
{
vec3 L = normalize(lights[i].position - FragPos);
vec3 H = normalize(V + L);
float distance = length(lights[i].position - FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lights[i].color * attenuation;
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = numerator / max(denominator, 0.001);
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float 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. 环境光照
环境光照是PBR渲染中不可或缺的一部分。通常使用环境贴图(如HDR贴图)来模拟环境光照。环境光照可以分为两部分:
- 环境反射:使用预过滤的环境贴图来模拟镜面反射。
- 环境漫反射:使用辐照度贴图来模拟漫反射。
5. 后处理
在完成光照计算后,通常会进行一些后处理操作,如色调映射、伽马校正等,以提高最终图像的质量。
结语
PBR渲染流程通过物理模型和材质参数的结合,实现了更为真实的光照和材质效果。通过合理的几何处理、光照计算和环境光照模拟,可以在实时渲染中实现高质量的视觉效果。希望这些内容能够帮助你理解和实现PBR渲染。
12. 实践中的PBR实现
在实际项目中,PBR的实现不仅仅是理论和代码的结合,还需要考虑到艺术设计、资源管理和性能优化等多个方面。以下是一些实践中的建议和技巧:
12.1 资源管理
- 纹理压缩:使用纹理压缩技术(如BCn、ASTC)来减少显存占用和带宽消耗。
- Mipmap:为纹理生成mipmap,以提高渲染效率和减少纹理失真。
- 材质库:建立一个标准化的材质库,方便艺术家和开发者复用和管理材质。
12.2 艺术设计
- 一致性:确保所有材质的参数范围和单位一致,以避免不一致的视觉效果。
- 参考资料:使用真实世界的材质和光照作为参考,确保PBR材质的真实性。
- 调试工具:开发调试工具,帮助艺术家和开发者实时查看和调整材质参数。
12.3 性能优化
- 动态分辨率:根据场景复杂度和帧率动态调整渲染分辨率。
- 延迟渲染:使用延迟渲染技术,将光照计算推迟到后期处理阶段,以减少前期计算量。
- 多线程:利用多线程技术,将几何处理、光照计算和后处理分配到不同的线程中,提高渲染效率。
13. 高级PBR技术
13.1 次表面散射(Subsurface Scattering, SSS)
次表面散射是模拟光在半透明材质(如皮肤、蜡)内部散射的效果。常见的实现方法包括基于屏幕空间的次表面散射(SSSS)和基于体积的次表面散射。
vec3 subsurfaceScattering(vec3 color, vec3 lightDir, vec3 viewDir, vec3 normal)
{
// 简单的SSS实现
float scatter = max(dot(lightDir, normal), 0.0);
vec3 sssColor = color * scatter;
return sssColor;
}
13.2 体积光照(Volumetric Lighting)
体积光照用于模拟光在空气、雾等介质中的散射和吸收。常见的实现方法包括体积光照贴图和基于屏幕空间的体积光照。
vec3 volumetricLighting(vec3 color, vec3 lightDir, vec3 viewDir, vec3 normal)
{
// 简单的体积光照实现
float scatter = exp(-length(lightDir) * 0.1);
vec3 volumeColor = color * scatter;
return volumeColor;
}
13.3 反射和折射
反射和折射是模拟光在不同介质界面上的反射和折射效果。常见的实现方法包括环境贴图和屏幕空间反射(SSR)。
vec3 reflection(vec3 color, vec3 normal, vec3 viewDir)
{
vec3 reflectDir = reflect(-viewDir, normal);
vec3 reflectColor = texture(environmentMap, reflectDir).rgb;
return reflectColor;
}
vec3 refraction(vec3 color, vec3 normal, vec3 viewDir, float eta)
{
vec3 refractDir = refract(-viewDir, normal, eta);
vec3 refractColor = texture(environmentMap, refractDir).rgb;
return refractColor;
}
14. 实例:实现一个简单的PBR渲染器
以下是一个简单的PBR渲染器的完整实现,包括顶点着色器和片段着色器。
顶点着色器
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoords;
out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoords = aTexCoords;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
片段着色器
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 camPos;
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;
struct Light {
vec3 position;
vec3 color;
};
uniform Light lights[4];
uniform int lightCount;
const float PI = 3.14159265359;
vec3 getNormalFromMap()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(FragPos);
vec3 Q2 = dFdy(FragPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 N = normalize(Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = -normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
return normalize(TBN * tangentNormal);
}
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;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2));
float metallic = texture(metallicMap, TexCoords).r;
float roughness = texture(roughnessMap, TexCoords).r;
float ao = texture(aoMap, TexCoords).r;
vec3 N = getNormalFromMap();
vec3 V = normalize(camPos - FragPos);
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 Lo = vec3(0.0);
for(int i = 0; i < lightCount; ++i)
{
vec3 L = normalize(lights[i].position - FragPos);
vec3 H = normalize(V + L);
float distance = length(lights[i].position - FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lights[i].color * attenuation;
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = numerator / max(denominator, 0.001);
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float 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);
}
15. 结语
PBR渲染是一种强大且灵活的渲染技术,能够实现高度真实的光照和材质效果。通过理解PBR的基本原理和实现细节,并结合实际项目中的优化和艺术设计,你可以创建出令人惊叹的视觉效果。希望这些信息对你有所帮助,祝你在PBR渲染的学习和实践中取得成功!
物理基础渲染(PBR)逻辑框架
物理基础渲染(PBR,Physically Based Rendering)是一种基于物理原理的渲染方法,旨在模拟光与物体表面交互的真实效果。PBR的核心思想是通过物理模型来描述光的反射、折射和散射,从而实现更加逼真的渲染效果。以下是PBR渲染的逻辑框架。
1. 材质模型
PBR使用基于物理的材质模型来描述物体表面的光学特性。常见的PBR材质模型包括:
- 漫反射(Diffuse):描述光在表面上的散射。
- 镜面反射(Specular):描述光在表面上的反射。
- 金属度(Metalness):描述表面是金属还是非金属。
- 粗糙度(Roughness):描述表面的光滑程度。
- 环境光遮蔽(Ambient Occlusion, AO):描述表面在环境光下的遮蔽程度。
2. 光照模型
PBR使用基于物理的光照模型来计算光与物体表面的交互。常见的光照模型包括:
- 朗伯反射(Lambertian Reflection):用于计算漫反射光。
- 库克-托伦斯反射(Cook-Torrance Reflection):用于计算镜面反射光。
3. BRDF(双向反射分布函数)
BRDF(Bidirectional Reflectance Distribution Function)是PBR的核心,用于描述光在表面上的反射特性。常见的BRDF模型包括:
- 朗伯BRDF(Lambertian BRDF):用于漫反射。
- GGX BRDF:用于镜面反射,具有更好的物理真实性。
4. 环境光照
PBR通常使用环境光照(IBL,Image-Based Lighting)来模拟复杂的光照环境。环境光照包括:
- 环境漫反射(Diffuse IBL):使用环境贴图计算漫反射光。
- 环境镜面反射(Specular IBL):使用预过滤环境贴图计算镜面反射光。
5. 渲染流程
PBR渲染的基本流程如下:
- 材质参数准备:从材质贴图中获取漫反射颜色、金属度、粗糙度、环境光遮蔽等参数。
- 光照计算:使用光照模型计算直接光照和间接光照。
- BRDF计算:使用BRDF模型计算光的反射。
- 环境光照计算:使用环境贴图计算环境光照。
- 合成最终颜色:将直接光照、间接光照和环境光照合成,得到最终的像素颜色。
6. 代码示例
以下是一个简单的PBR渲染代码示例,使用GLSL编写的片段着色器:
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 camPos;
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;
uniform samplerCube irradianceMap;
uniform samplerCube prefilterMap;
uniform sampler2D brdfLUT;
struct Light {
vec3 position;
vec3 color;
};
uniform Light lights[4];
uniform int lightCount;
const float PI = 3.14159265359;
vec3 getNormalFromMap()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(FragPos);
vec3 Q2 = dFdy(FragPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 N = normalize(Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = -normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
return normalize(TBN * tangentNormal);
}
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;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2));
float metallic = texture(metallicMap, TexCoords).r;
float roughness = texture(roughnessMap, TexCoords).r;
float ao = texture(aoMap, TexCoords).r;
vec3 N = getNormalFromMap();
vec3 V = normalize(camPos - FragPos);
vec3 R = reflect(-V, N);
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 Lo = vec3(0.0);
for(int i = 0; i < lightCount; ++i)
{
vec3 L = normalize(lights[i].position - FragPos);
vec3 H = normalize(V + L);
float distance = length(lights[i].position - FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lights[i].color * attenuation;
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = nominator / max(denominator, 0.001);
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float 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);
}
结语
PBR渲染通过基于物理的材质模型和光照模型,实现了更加逼真的渲染效果。本文介绍了PBR渲染的逻辑框架,包括材质模型、光照模型、BRDF、环境光照和渲染流程,并提供了一个简单的代码示例。希望通过本文的介绍,读者能够对PBR渲染有更深入的理解,并在实际开发中应用这些知识,创建出高效、逼真的渲染效果。
进一步深入PBR渲染
在前面的介绍中,我们已经了解了PBR渲染的基本逻辑框架和一个简单的代码示例。接下来,我们将进一步深入探讨PBR渲染中的一些高级概念和技术。
1. 预计算环境贴图
为了提高渲染效率,PBR通常会预计算环境贴图。这包括:
- 辐照度贴图(Irradiance Map):用于计算环境漫反射光。
- 预过滤环境贴图(Prefiltered Environment Map):用于计算环境镜面反射光。
- BRDF查找表(BRDF LUT):用于优化BRDF计算。
这些预计算的贴图可以显著减少实时渲染时的计算量,从而提高渲染性能。
2. 重要性采样
在PBR渲染中,重要性采样(Importance Sampling)是一种优化技术,用于减少噪声并提高渲染质量。重要性采样通过优先采样对最终结果影响较大的方向,从而减少不必要的计算。
3. 多重散射
多重散射(Multiple Scattering)是指光在物体表面多次反射和散射的现象。在PBR渲染中,多重散射可以通过以下方法来模拟:
- 双向散射分布函数(BSDF):扩展BRDF以考虑多重散射。
- 次表面散射(Subsurface Scattering, SSS):用于模拟光在半透明材料(如皮肤、蜡)中的散射。
4. 微表面模型
微表面模型(Microfacet Model)是PBR渲染中的一个重要概念,用于描述表面微小不平整的反射特性。常见的微表面分布函数包括:
- GGX分布:具有更好的物理真实性,适用于大多数材质。
- 贝克曼分布(Beckmann Distribution):适用于光滑表面。
5. 线性空间渲染
PBR渲染通常在线性空间中进行计算,以确保光照和颜色的计算更加准确。线性空间渲染包括:
- 伽马校正(Gamma Correction):将颜色从伽马空间转换到线性空间进行计算,最后再转换回伽马空间显示。
- HDR渲染(High Dynamic Range Rendering):使用高动态范围的颜色表示,以捕捉更多的光照细节。
6. 实时PBR渲染优化
为了在实时应用中实现高效的PBR渲染,可以采用以下优化技术:
- 延迟渲染(Deferred Rendering):将几何信息和材质信息存储在G缓冲区中,延迟光照计算。
- 级联阴影贴图(Cascaded Shadow Maps, CSM):用于高效地渲染大范围场景中的阴影。
- 屏幕空间反射(Screen Space Reflections, SSR):用于模拟镜面反射,减少对环境贴图的依赖。
代码示例:高级PBR渲染
以下是一个包含预计算环境贴图和重要性采样的高级PBR渲染代码示例:
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 camPos;
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;
uniform samplerCube irradianceMap;
uniform samplerCube prefilterMap;
uniform sampler2D brdfLUT;
struct Light {
vec3 position;
vec3 color;
};
uniform Light lights[4];
uniform int lightCount;
const float PI = 3.14159265359;
vec3 getNormalFromMap()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(FragPos);
vec3 Q2 = dFdy(FragPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 N = normalize(Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = -normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
return normalize(TBN * tangentNormal);
}
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;
}
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2));
float metallic = texture(metallicMap, TexCoords).r;
float roughness = texture(roughnessMap, TexCoords).r;
float ao = texture(aoMap, TexCoords).r;
vec3 N = getNormalFromMap();
vec3 V = normalize(camPos - FragPos);
vec3 R = reflect(-V, N);
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 Lo = vec3(0.0);
for(int i = 0; i < lightCount; ++i)
{
vec3 L = normalize(lights[i].position - FragPos);
vec3 H = normalize(V + L);
float distance = length(lights[i].position - FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lights[i].color * attenuation;
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 nominator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 specular = nominator / max(denominator, 0.001);
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
float NdotL = max(dot(N, L), 0.0);
Lo += (kD * albedo / PI + specular) * radiance * NdotL;
}
vec3 F = fresnelSchlick(max(dot(N, V), 0.0), F0);
vec3 kS = F;
vec3 kD = 1.0 - kS;
kD *= 1.0 - metallic;
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * albedo;
const float MAX_REFLECTION_LOD = 4.0;
vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;
vec2 brdf = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
vec3 specular = prefilteredColor * (F * brdf.x + brdf.y);
vec3 ambient = (kD * diffuse + specular) * ao;
vec3 color = ambient + Lo;
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}
结语
通过进一步深入探讨PBR渲染中的高级概念和技术,我们可以实现更加逼真和高效的渲染效果。本文介绍了预计算环境贴图、重要性采样、多重散射、微表面模型、线性空间渲染和实时PBR渲染优化等内容,并提供了一个高级PBR渲染的代码示例。希望这些内容能够帮助读者在实际开发中应用PBR渲染技术,创建出更加逼真的视觉效果。
进一步优化和扩展PBR渲染
在前面的讨论中,我们已经涵盖了PBR渲染的许多重要方面。接下来,我们将探讨一些进一步优化和扩展PBR渲染的方法,以实现更高的性能和更逼真的效果。
1. 屏幕空间环境光遮蔽(SSAO)
屏幕空间环境光遮蔽(Screen Space Ambient Occlusion, SSAO)是一种用于模拟环境光遮蔽效果的技术。SSAO通过在屏幕空间中计算遮蔽因子,来增强场景的深度感和细节。
#version 330 core
out float FragColor;
in vec2 TexCoords;
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D texNoise;
uniform vec3 samples[64];
uniform mat4 projection;
uniform vec2 noiseScale;
const float radius = 0.5;
const float bias = 0.025;
void main()
{
vec3 fragPos = texture(gPosition, TexCoords).xyz;
vec3 normal = normalize(texture(gNormal, TexCoords).rgb);
vec3 randomVec = normalize(texture(texNoise, TexCoords * noiseScale).xyz);
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal);
float occlusion = 0.0;
for(int i = 0; i < 64; ++i)
{
vec3 samplePos = TBN * samples[i];
samplePos = fragPos + samplePos * radius;
vec4 offset = vec4(samplePos, 1.0);
offset = projection * offset;
offset.xyz /= offset.w;
offset.xyz = offset.xyz * 0.5 + 0.5;
float sampleDepth = texture(gPosition, offset.xy).z;
float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
occlusion += (sampleDepth >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;
}
occlusion = 1.0 - (occlusion / 64.0);
FragColor = occlusion;
}
2. 屏幕空间反射(SSR)
屏幕空间反射(Screen Space Reflections, SSR)是一种用于模拟镜面反射的技术。SSR通过在屏幕空间中追踪反射光线,来实现实时反射效果。
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;
uniform sampler2D gDepth;
uniform mat4 view;
uniform mat4 projection;
uniform vec3 camPos;
const int MAX_STEPS = 50;
const float STEP_SIZE = 0.1;
const float MAX_DISTANCE = 100.0;
vec3 getViewRay(vec2 uv)
{
vec4 clipPos = vec4(uv * 2.0 - 1.0, 1.0, 1.0);
vec4 viewPos = inverse(projection) * clipPos;
viewPos /= viewPos.w;
return normalize(viewPos.xyz);
}
void main()
{
vec3 fragPos = texture(gPosition, TexCoords).xyz;
vec3 normal = normalize(texture(gNormal, TexCoords).rgb);
vec3 albedo = texture(gAlbedoSpec, TexCoords).rgb;
float specular = texture(gAlbedoSpec, TexCoords).a;
vec3 viewDir = normalize(camPos - fragPos);
vec3 reflectDir = reflect(viewDir, normal);
vec3 rayOrigin = fragPos;
vec3 rayDir = reflectDir;
float currentDepth = texture(gDepth, TexCoords).r;
float rayLength = 0.0;
vec3 hitColor = vec3(0.0);
for(int i = 0; i < MAX_STEPS; ++i)
{
rayLength += STEP_SIZE;
vec3 rayPos = rayOrigin + rayDir * rayLength;
vec4 clipPos = projection * view * vec4(rayPos, 1.0);
clipPos /= clipPos.w;
vec2 uv = clipPos.xy * 0.5 + 0.5;
if(uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
break;
float sampleDepth = texture(gDepth, uv).r;
if(sampleDepth < rayPos.z)
{
hitColor = texture(gAlbedoSpec, uv).rgb;
break;
}
if(rayLength > MAX_DISTANCE)
break;
}
vec3 color = mix(albedo, hitColor, specular);
FragColor = vec4(color, 1.0);
}
3. 次表面散射(SSS)
次表面散射(Subsurface Scattering, SSS)用于模拟光在半透明材料(如皮肤、蜡)中的散射。SSS可以通过多种方法实现,包括屏幕空间技术和基于体积的技术。
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
uniform vec3 camPos;
uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D sssMap;
struct Light {
vec3 position;
vec3 color;
};
uniform Light lights[4];
uniform int lightCount;
const float PI = 3.14159265359;
vec3 getNormalFromMap()
{
vec3 tangentNormal = texture(normalMap, TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(FragPos);
vec3 Q2 = dFdy(FragPos);
vec2 st1 = dFdx(TexCoords);
vec2 st2 = dFdy(TexCoords);
vec3 N = normalize(Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = -normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
return normalize(TBN * tangentNormal);
}
float fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, vec3(2.2));
vec3 sssColor = texture(sssMap, TexCoords).rgb;
vec3 N = getNormalFromMap();
vec3 V = normalize(camPos - FragPos);
vec3 Lo = vec3(0.0);
for(int i = 0; i < lightCount; ++i)
{
vec3 L = normalize(lights[i].position - FragPos);
vec3 H = normalize(V + L);
float distance = length(lights[i].position - FragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = lights[i].color * attenuation;
float NdotL = max(dot(N, L), 0.0);
vec3 diffuse = albedo / PI;
vec3 F0 = vec3(0.04);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 kS = F;
vec3 kD = 1.0 - kS;
vec3 sss = sssColor * NdotL;
Lo += (kD * diffuse + sss) * radiance * NdotL;
}
vec3 color = Lo;
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}
结语
通过引入屏幕空间环境光遮蔽(SSAO)、屏幕空间反射(SSR)和次表面散射(SSS)等技术,我们可以进一步优化和扩展PBR渲染,实现更高的性能和更逼真的效果。这些技术在现代游戏和实时渲染中得到了广泛应用,能够显著提升视觉质量。希望这些内容能够帮助读者在实际开发中应用这些技术,创建出更加逼真的视觉效果。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)