光源辐射率: 
    辐射率(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.光照的更多相关文章

  1. LearnOpenGL.PBR.IBL

    概述: IBL:image based lighting,一种间接光照(indirect lighting)技术,将周围的环境存在一张环境贴图(基于现实世界或3D场景生成)里面,然后将环境贴图上的每一 ...

  2. LearnOpenGL.PBR.理论

    判断一种PBR光照模型是否是基于物理的,必须满足以下三个条件: ()基于微平面(Microfacet)的表面模型.Be based on the microfacet surface model. ( ...

  3. LearnOpenGL.PBR.工作流贴图

  4. CSharpGL(54)用基于图像的光照(IBL)来计算PBR的Specular部分

    CSharpGL(54)用基于图像的光照(IBL)来计算PBR的Specular部分 接下来本系列将通过翻译(https://learnopengl.com)这个网站上关于PBR的内容来学习PBR(P ...

  5. 由浅入深学习PBR的原理和实现

    目录 一. 前言 1.1 本文动机 1.2 PBR知识体系 1.3 本文内容及特点 二. 初阶:PBR基本认知和应用 2.1 PBR的基本介绍 2.1.1 PBR概念 2.1.2 与物理渲染的差别 2 ...

  6. Specular Aliasing与Specular Leaking

    最近做高质量实时HDR PBR渲染中碰到了2个关键问题,若干思考如下: 问题1: 极高的动态范围HDR+高级BRDF+相对较低的采样率(比方说不考虑子像素的原始分辨率),在这3项因素的综合作用下,Sp ...

  7. 剖析虚幻渲染体系(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 ...

  8. LearnOpenGL

    ---------------------------------------------- LearnOpenGL ----------------------------------------- ...

  9. 基于物理的渲染——间接光照

    在前面的文章中我们已经给出了基于物理的渲染方程: 并介绍了直接光照的实现.然而在自然界中,一个物体不会单独存在,光源会照射到其他的物体上,反射的光会有一部分反射到物体上.为了模拟这种环境光照的形式,我 ...

随机推荐

  1. 三、排序算法总结一(冒泡排序,插入排序,选择排序)(C++版本)

    一.引言 对于各种排序算法也算是有了一定的了解,所以这里做一个总结. 二.冒泡排序法. 这是比较经典的排序算法,主要是通过内外两层的循环比较,使得乱序变为顺序. 下面是一个测试代码 #include ...

  2. angular6.x 引入echarts

    因为angular2+ 使用 ==typescript==开发,所以想要使用echarts,必须安装echarts针对angular的插件ngx-echarts.本文案列实际效果如上图. 安装ngx- ...

  3. Linux学习笔记-第18天 有点迷茫。。

    有点迷茫学的这些知识的实用性..但愿今天可以用得到这些吧

  4. R语言算法 ▪ 计算随意输入的两数之间的区域和

    sumfu<-function(a,b,n=){ if(a<b){ for(i in a:b){n=n+i} }else for(i in b:a){n=n+i} return <- ...

  5. k8s云集群混搭模式落地分享

    在 <k8s云集群混搭模式,可能帮你节省50%以上的服务成本>一文中,介绍了使用k8s + 虚拟节点混合集群的方式,为负载具有时间段波峰.波谷交替规律的业务节约成本,提高服务伸缩效率的部署 ...

  6. TreeMap 原理

    基于jdk1.8 TreeMap第一个想到的就是有序,当然也不是线程安全 TreeMap实现NavigableMap接口,说明支持一系列的导航方法 一.构造方法 public TreeMap() { ...

  7. TestNg之XMl形式实现多线程测试

    为什么要使用多线程测试? 在实际测试中,为了节省测试时间,提高测试效率,在实际测试场景中经常会采用多线程的方式去执行,比如爬虫爬数据,多浏览器并行测试. 关于多线程并行测试 TestNG中实现多线程并 ...

  8. Mac终端常用快捷键

    Ctrl + a 跳到行首Ctrl + e 跳到行尾Ctrl + d 删除一个字符,相当于通常的Delete键(命令行若无所有字符,则相当于exit:处理多行标准输入时也表示eof)Ctrl + h ...

  9. Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解

    Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...

  10. skeleton在心意web上的实践

    通过手动编写skeleton,在fetch数据时显示skeleton loading,数据拉取成功隐藏skeleton 先看下效果图 在component下创建页面对应的skeleton,然后通过在i ...