光源辐射率:
辐射率(radiance)表示光源在给定立体角ω下的辐射通量(或光源发射的能量)。
那么假设立体角ω无限小时,辐射率就表示单束光线(或说某个单一方向)的辐射通量。
点光源:point light,在所有方向都有相同的亮度,辐射强度(radiant intensity)等于其发射出来的所有方向的辐射通量(radiant flux)。
对于场景中的一个点p,只会有一束光直接照射到点p,其他 光线的辐射率都为0。
辐射强度:点光源无论从任何角度看,点光源都有相同的辐射强度,所以可以简单地使用其辐射通量表示辐射强度,也就是一个RGB常亮。
辐射率:需要考虑点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;
基本和普通diffuse漫反射光照一样。
前提:假设点光源无限小,如果有体积,点光源会有一个以上的入射光线辐射率不为0。
方向光:directional light,辐射率拥有恒定的入射方向,而且不会有衰减。
聚光灯:spotlight,没有恒定的辐射强度,而是会根据照射方向有所不同。
直接光照:
辐照度=所有光源的辐射率,所以直接光照的计算非常简单,只需要逐个光源计算辐射率,然后加在一起。接着根据BRDF和光源的入射角来缩放该辐射率。
这个算法也是符合反射率方程(The reflectance equation)的积分运算的。
vec3 Lo = vec3(0.0);
for(int i = ; i < ; ++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;
[...]
由于我们在线性空间内计算光照,我们使用在物理上更为准确的平方倒数(inverse-square law)作为衰减因子。
对于每一个光源都需要计算完整的BRDF项:
aaarticlea/png;base64,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" alt="" width="146" />
Ks = F,也就是菲涅尔系数,表示光线给反射的百分比:
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
Fo表示0度入射角的反射(surface relfection at zero incidence),也就是垂直看向材质时的反光率。Fo会因材质的不同而不同,而且金属的反光还会带有颜色。大多数非金属材质取0.04都能取得视觉上物理可信的效果。对于金属材质则直接取albedo贴图的颜色值。
vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
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 nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / 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;
}
根据Disney公司的观察和Epic采用经验,在D和G的计算公式中使用roughness * roughness来进行计算会有更正确的光照效果。
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是为了防止除0。
菲涅尔方程求出了Ks,那么Kd=1-Ks,但同时考虑到金属没有折射光线,也就是没有漫反射,所以金属的Kd=0,代码如下:
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;
最终的反射率:
const float PI = 3.14159265359;
float NdotL = max(dot(N, L), 0.0);
Lo += (kD * albedo / PI + specular) * radiance * NdotL;
}
可以看到specular没有再乘一次Ks,是因为DFG里面的F实际上就是Ks。
再加一个环境光项(ao贴图):
vec3 ambient = vec3(0.03) * albedo * ao;
vec3 color = ambient + Lo;
IBL环境光照:
需要计算积分,因为光线会在任何一个方向入射到物体表面。
线性空间和HDR渲染:
我们之前所有的运算都必须在线性空间(linear space)进行,所以我们需要在shader的最后做伽马校正(gamma correct)。
线性空间:PBR要求所有的输入都是线性的,不然计算的结果会不正确。
我们希望输入的光照都尽可能接近真实,那么计算出的辐射率范围可能会非常大,也就是说Lo是一个HDR的值(大于1.0)。但最终的颜色输出范围是LDR的,所以在gamma correct之前,我们会先通过色调映射(tone or exposure map)将HDR值映射到LDR的值。
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0/2.2));
这里我们使用了Reinhard方法来进行色调映射,我们没有使用framebuffer或post-processing的方式来进行色调映射,所以我们直接在shader的最后添加了这两步。
PBR pipeline需要考虑线性空间和HDR的因素,不然可能渲染出来的画面会丢失细节,并且视觉上不正确看起来也不好看。
完整的直接光照PBR shader:
#version 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[];
uniform vec3 lightColors[];
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 fresnelSchlick(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 equation
vec3 Lo = vec3(0.0);
for(int i = ; i < ; ++i)
{
// calculate per-light radiance
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;
// cook-torrance brdf
float 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 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);
// add to outgoing radiance Lo
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:
将主要参数都放到贴图中,这样可以带来更大的灵活性。
[...]
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空间,所以需要在计算光照之前手动转到线性空间。ao贴图也一样。不过metallic和roughness多数都会直接使用线性空间来保存。
- LearnOpenGL.PBR.IBL
概述: IBL:image based lighting,一种间接光照(indirect lighting)技术,将周围的环境存在一张环境贴图(基于现实世界或3D场景生成)里面,然后将环境贴图上的每一 ...
- LearnOpenGL.PBR.理论
判断一种PBR光照模型是否是基于物理的,必须满足以下三个条件: ()基于微平面(Microfacet)的表面模型.Be based on the microfacet surface model. ( ...
- LearnOpenGL.PBR.工作流贴图
- CSharpGL(54)用基于图像的光照(IBL)来计算PBR的Specular部分
CSharpGL(54)用基于图像的光照(IBL)来计算PBR的Specular部分 接下来本系列将通过翻译(https://learnopengl.com)这个网站上关于PBR的内容来学习PBR(P ...
- 由浅入深学习PBR的原理和实现
目录 一. 前言 1.1 本文动机 1.2 PBR知识体系 1.3 本文内容及特点 二. 初阶:PBR基本认知和应用 2.1 PBR的基本介绍 2.1.1 PBR概念 2.1.2 与物理渲染的差别 2 ...
- Specular Aliasing与Specular Leaking
最近做高质量实时HDR PBR渲染中碰到了2个关键问题,若干思考如下: 问题1: 极高的动态范围HDR+高级BRDF+相对较低的采样率(比方说不考虑子像素的原始分辨率),在这3项因素的综合作用下,Sp ...
- 剖析虚幻渲染体系(12)- 移动端专题Part 3(渲染优化)
目录 12.6 移动端渲染优化 12.6.1 渲染管线优化 12.6.1.1 使用新特性 12.6.1.2 管线优化 12.6.1.3 带宽优化 12.6.2 资源优化 12.6.2.1 纹理优化 1 ...
- LearnOpenGL
---------------------------------------------- LearnOpenGL ----------------------------------------- ...
- 基于物理的渲染——间接光照
在前面的文章中我们已经给出了基于物理的渲染方程: 并介绍了直接光照的实现.然而在自然界中,一个物体不会单独存在,光源会照射到其他的物体上,反射的光会有一部分反射到物体上.为了模拟这种环境光照的形式,我 ...
随机推荐
- itest(爱测试) 3.3.7 发布,开源BUG 跟踪管理& 敏捷测试管理软件
v3.3.7 下载地址 :itest下载 itest 简介:查看简介 V3.3.7 增加了 5个功能增强,和8个BUG修复 ,详情如下所述. 5个功能增强 :(1)任务看板中,除了显示任务外,增加测试 ...
- springmvc+strut2比较
常见web框架中Struts2和SpringMVC独占鳌头,SpringMVC和Struts有什么不同? 我们可以从各个方面进行对比: 一:框架的思想设计上 SpringMVC控制器是基于方法上拦截, ...
- FPDF_CloseDocument(doc);
FPDF_CloseDocument(doc); 创建不加载就会段错误 必须创建 doc = FPDF_CreateNewDocument();
- 【转】Redis相关
1. 什么是redis? Redis 是一个使用 C 语言写成的,开源的基于内存的高性能key-value数据库. Redis的值可以是由string(字符串).hash(哈希).list(列表) ...
- SpringBoot 全局异常配置
在日常web开发中发生了异常,往往是需要通过一个统一的异常处理来保证客户端能够收到友好的提示. 一.默认异常机制 默认异常处理(SpringBoot 默认提供了两种机制,一种是针对于web浏览器访问的 ...
- 震惊!CCF改名为中国沙雕化学学会!!!
震惊!中国沙雕计算机学会要改名中国沙雕化学学会??? Ak元素 据传,CCF,发现了一种新元素,元素符号暂命名为为Ak,中文名称暂未命名,据说是第250号元素. Ak 元素的发现 珂学家在一个叫洛谷的 ...
- Navicat Premium Mac 12 破解(亲测可用!!!)
今天不知怎的,出于强迫症的我就是要强行搞个Navicat Premium Mac 12 破解版本. 历经了种种种种种种磨难与艰辛与火海,终于破解成功了. 因为要经常使用MySQL,使用命令行那是相当的 ...
- LeetCode28——实现strStr()
6月中下旬辞职在家,7 月份无聊的度过了一个月.8 月份开始和朋友两个人写项目,一个后台和一个 APP ,APP 需要对接蓝牙打印机.APP 和蓝牙打印机都没有搞过,开始打算使用 MUI 开发 APP ...
- 简明了解apply()和call()
apply()和call()都是ES6语法的,并且都是函数的方法. function foo() { alert(this.name) } var obj = { name: '小明' } foo() ...
- vue+element 动态表单验证
公司最近的项目有个添加动态表单的需求,总结一下我在表单验证上遇到的一些坑. 如图是功能的需求,这个功能挺好实现的,但是表单验证真是耗费了我一些功夫. vue+element在表单验证上有一些限制,必须 ...