question:

brdf中的几何因子考虑了微表面的自遮挡,当表面粗糙度较大或者与法线夹角越大时,这个因子越小,导致颜色越暗。这部分能量相等于直接忽略掉了,实际上被遮挡的光线会被反射,然后经过若干次反射,从另一点以另一角度重新进入视线。因此需要将缺失的能量重新补回来。

Kulla-Conty近似

Kulla-Conty近似额外添加了一个补充的brdf系数,使得总能量守恒。

引入颜色

注意Kulla-Conty实现的是:1次弹射+能量补充,而一次弹射的内容里面是已经有考虑颜色的菲涅尔项了,但是能量的补充我们始终认为是对于一个没有颜色的,F值为1的东西进行的补充【上边推导过程假设了F为1】,这对于有颜色的东西会补充过多,因为F小于1时,多次弹射时每次都会被吸收一部分。因此我们这里考虑颜色是对于补充的那一部分考虑颜色,对补充的能量进行衰减!

现在我们要把这个菲涅尔项考虑进来了,但是这里注意,这个菲涅尔的值,是和角度有关系的,如果在这里再考虑上角度,其实最后我们是很难得到一个实时的结果的。

所以这里再次采用了一种近似的方案,就是把菲涅尔选择一个各角度下的平均值,不管角度是多少都用这一个菲涅尔值。

这个平均和我们之前的E_avg

是一样的思路:

\[F_{avg} = \frac{\int_0^1{F(u)udu}}{\int_0^1{udu}}=2\int_0^1{F(u)udu}
\]

代码

离线计算E(u)

Vec3f IntegrateBRDF(Vec3f V, float roughness) {
float A = 0.0;
float B = 0.0;
float C = 0.0;
const int sample_count = 2048;
Vec3f N = Vec3f(0.0, 0.0, 1.0);
for (int i = 0; i < sample_count; i++) {
Vec2f Xi = Hammersley(i, sample_count);
Vec3f H = ImportanceSampleGGX(Xi, N, roughness);
Vec3f L = normalize(H * 2.0f * dot(V, H) - V); float NoL = std::max(L.z, 0.0f);
float NoH = std::max(H.z, 0.0f);
float VoH = std::max(dot(V, H), 0.0f);
float NoV = std::max(dot(N, V), 0.0f); // TODO: To calculate (fr * ni) / p_o here - Bonus 1
float g = GeometrySmith(roughness, NoV, NoL);
float w = VoH * g / (NoV*NoH);
A += w;
B += w;
C += w;
// Split Sum - Bonus 2
}
//return { 1.0f - A / sample_count,1.0f - B / sample_count, 1.0f - C / sample_count };
return { A / static_cast<float>(sample_count), B / static_cast<float>(sample_count), C / static_cast<float>(sample_count) };
//return Vec3f(1.0f);
}

结果:

离线计算E_avg:

Vec3f IntegrateEmu(Vec3f V, float roughness, float NdotV, Vec3f Ei) {
Vec3f Eavg = Vec3f(0.0f);
const int sample_count = 1024;
Vec3f N = Vec3f(0.0, 0.0, 1.0);
/*
for (int i = 0; i < sample_count; i++)
{
Vec2f Xi = Hammersley(i, sample_count);
Vec3f H = ImportanceSampleGGX(Xi, N, roughness);
Vec3f L = normalize(H * 2.0f * dot(V, H) - V); float NoL = std::max(L.z, 0.0f);
float NoH = std::max(H.z, 0.0f);
float VoH = std::max(dot(V, H), 0.0f);
float NoV = std::max(dot(N, V), 0.0f); // TODO: To calculate Eavg here - Bonus 1 }
*/
return Ei * NdotV * 2;
//NdotV = 1.0f - NdotV;
//return Ei * sqrt(1 - NdotV * NdotV) * 2;
//return Vec3f(1.0);
} int main() {
unsigned char *Edata = stbi_load("./GGX_E_LUT.png", &resolution, &resolution, &channel, 3);
if (Edata == NULL)
{
std::cout << "ERROE_FILE_NOT_LOAD" << std::endl;
return -1;
}
else
{
std::cout << resolution << " " << resolution << " " << channel << std::endl;
// | -----> mu(j)
// |
// | rough(i)
// Flip it, if you want the data written to the texture
uint8_t data[128 * 128 * 3];
float step = 1.0 / resolution;
Vec3f Eavg = Vec3f(0.0);
for (int i = 0; i < resolution; i++)
{
float roughness = step * (static_cast<float>(i) + 0.5f);
for (int j = 0; j < resolution; j++)
{
float NdotV = step * (static_cast<float>(j) + 0.5f);
Vec3f V = Vec3f(std::sqrt(1.f - NdotV * NdotV), 0.f, NdotV);
//resolution - 1 - i
//这里非常隐晦,与产生图像是顺序相反
//产生图像时,theta从2/Pi到0
//这里theta从0到2/Pi进行访问
//这里的NdotV范围从0到1均匀采样,相当于sin(theta)
Vec3f Ei = getEmu((resolution - 1 - i), j, 0, Edata, NdotV, roughness);
Eavg += IntegrateEmu(V, roughness, NdotV, Ei) * step;
setRGB(i, j, 0.0, data);
} for(int k = 0; k < resolution; k++)
{
setRGB(i, k, Eavg, data);
} Eavg = Vec3f(0.0);
}
stbi_flip_vertically_on_write(true);
stbi_write_png("GGX_Eavg_LUT.png", resolution, resolution, channel, data, 0);
}
stbi_image_free(Edata);
return 0;
}

结果:

运行时,KullaContyFragment.glsl:

#ifdef GL_ES
precision mediump float;
#endif uniform vec3 uLightPos;
uniform vec3 uCameraPos;
uniform vec3 uLightRadiance;
uniform vec3 uLightDir; uniform sampler2D uAlbedoMap;
uniform float uMetallic;
uniform float uRoughness;
uniform sampler2D uBRDFLut;
uniform sampler2D uEavgLut;
uniform samplerCube uCubeTexture; varying highp vec2 vTextureCoord;
varying highp vec3 vFragPos;
varying highp vec3 vNormal; const float PI = 3.14159265359; float DistributionGGX(vec3 N, vec3 H, float roughness)
{
// TODO: To calculate GGX NDF here
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 / max(denom, 0.0001);
} float GeometrySchlickGGX(float NdotV, float roughness)
{
// TODO: To calculate Schlick G1 here
float a = roughness;
//float k = (a +1.0)*(a+1.0) / 8.0;
float k = (a * a) / 2.0;//用这个才能得到assignment4.pdf中的效果图
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k; return nom / denom;
} float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
// TODO: To calculate Smith G here
return GeometrySchlickGGX(max(dot(N,V),0.0),roughness)*GeometrySchlickGGX(max(dot(N,L),0.0),roughness);
} vec3 fresnelSchlick(vec3 F0, vec3 V, vec3 H)
{
// TODO: To calculate Schlick F here
float HoV = max(dot(V,H), 0.0);
vec3 f = F0 + (vec3(1.0) - F0) * pow((1.0 - HoV), 5.0);
// TODO: To calculate Schlick F here
return f;
} //https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf
vec3 AverageFresnel(vec3 r, vec3 g)
{
return vec3(0.087237) + 0.0230685*g - 0.0864902*g*g + 0.0774594*g*g*g
+ 0.782654*r - 0.136432*r*r + 0.278708*r*r*r
+ 0.19744*g*r + 0.0360605*g*g*r - 0.2586*g*r*r;
} vec3 MultiScatterBRDF(float NdotL, float NdotV)
{
vec3 albedo = pow(texture2D(uAlbedoMap, vTextureCoord).rgb, vec3(2.2)); vec3 E_o = texture2D(uBRDFLut, vec2(NdotL, uRoughness)).xyz;
vec3 E_i = texture2D(uBRDFLut, vec2(NdotV, uRoughness)).xyz; vec3 E_avg = texture2D(uEavgLut, vec2(0, uRoughness)).xyz;
// copper
vec3 edgetint = vec3(0.827, 0.792, 0.678);
vec3 F_avg = AverageFresnel(albedo, edgetint); // TODO: To calculate fms and missing energy here
float fms = (1.0-E_o.x)*(1.0-E_i.x)/(PI*(1.0-E_avg.x));
vec3 fadd = F_avg*E_avg/(vec3(1.0)-F_avg*(vec3(1.0)-E_avg)); return vec3(fms)*fadd; } void main(void) {
vec3 albedo = pow(texture2D(uAlbedoMap, vTextureCoord).rgb, vec3(2.2)); vec3 N = normalize(vNormal);
vec3 V = normalize(uCameraPos - vFragPos);
float NdotV = max(dot(N, V), 0.0); vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, uMetallic); vec3 Lo = vec3(0.0); // calculate per-light radiance
vec3 L = normalize(uLightDir);
vec3 H = normalize(V + L);
float distance = length(uLightPos - vFragPos);
float attenuation = 1.0 / (distance * distance);
vec3 radiance = uLightRadiance; float NDF = DistributionGGX(N, H, uRoughness);
float G = GeometrySmith(N, V, L, uRoughness);
vec3 F = fresnelSchlick(F0, V, H); vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
vec3 Fmicro = numerator / max(denominator, 0.001); float NdotL = max(dot(N, L), 0.0); vec3 Fms = MultiScatterBRDF(NdotL, NdotV);
vec3 BRDF = Fmicro + Fms; Lo += BRDF * radiance * NdotL;
vec3 color = Lo; color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0/2.2));
gl_FragColor = vec4(color, 1.0); }

结果:

上一排是使用了Kulla-Cony BRDF,下一排使用了没有添加补充的BRDF,可以看到在低粗糙度时,使用了Kulla-Cony BRDF的要更亮一些。

Kulla-Conty BRDF的更多相关文章

  1. 图形学理论知识 BRDF 双向反射分布函数(Bidirectional Reflectance Distribution Function)

    图形学理论知识 BRDF 双向反射分布函数 Bidirectional Reflectance Distribution Function BRDF理论 BRDF表示的是双向反射分布函数(Bidire ...

  2. Ward BRDF实现心得

    最近做了Ward BRDF的实现,相对于之前的lambert,phong来说,Ward是一个真正意义上的各向异性BRDF,但同样的,Ward模型也是一个基于经验的模型,并不是物理上正确的.它由ward ...

  3. Unity3D ShaderLab BRDF模拟

    Unity3D ShaderLab BRDF模拟 在上一篇,说到了使用渐变纹理着色,使用一个值来控制纹理的uv坐标,但是这也就表示我们只能得到一个线性的光照效果. 那么我们能不能通过观察方向的向量结合 ...

  4. Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF

    在实时渲染中Physically-Based Rendering(PBR)中文为基于物理的渲染它能为渲染的物体带来更真实的效果,而且能量守恒 稍微解释一下字母的意思,为对后文的理解有帮助,从右到左L为 ...

  5. 【Unity Shaders】Diffuse Shading——使用2D ramp texture来创建一个假的BRDF(双向反射分布函数)

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  6. Lambert漫反射的BRDF

    Lambert漫反射brdf=Albedo/pi. 推导: 如图,设Lambert面元获得入射照度Ei,各方向均匀释放亮度,每个方向释放的亮度都是Lo. 又设此表面反射率为Albedo,根据反射率定义 ...

  7. (z转)基于CPU的Bank BRDF经验模型,实现各向异性光照效果!

    摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文 名“GPU编程与CG语言之阳春白雪下里巴人” BRDF 光照模型 10.2.1 什么 ...

  8. (转)图形学理论知识 BRDF 双向反射分布函数(Bidirectional Reflectance Distribution Function)

    BRDF理论 BRDF表示的是双向反射分布函数(Bidirectional Reflectance Distribution Function),它描述了光线如何在物体表面进行反射,可以用来描述材质属 ...

  9. 使用RampTexture实现BRDF效果

    [使用RampTexture实现BRDF效果] BRDF stands for bidirectional reflectance distribution function. While that ...

  10. standard pbr(三)-BRDF

    // Default BRDF to use: #if !defined (UNITY_BRDF_PBS) // allow to explicitly override BRDF in custom ...

随机推荐

  1. Java还是C#?我该如何选择?给年轻人的建议...

    一.年轻人应该通吃 其实这不应该是我们真正的主题,而且入了行的也很少会java还是c#这么比,但初学的,java和c#往往就代表了两大流派,java代替了j2ee,c#代替了.net,ok,没有关系, ...

  2. 硬件开发笔记(二十九):TPS54331电源设计(二):12V转3.3V和12V转4V原理图设计

    前言   电源供电电路设计很重要,为了更好的给对硬件设计有需求的人,特意将电源设计的基础过程描述出来.  紧接前一篇12V转5V的,本篇设计常用的12V转3.3V电路,不常用的12V转4V电路.   ...

  3. 【JavaWeb】封装一个MVC框架

    框架参考自: https://www.bilibili.com/video/BV1gV411r7ct 在老师的基础上添加了 1.POST参数处理 2.Tomcat8版本下中文乱码处理 3.可声明请求方 ...

  4. 【SpringBoot】06 探索配置方式 Part2 占位符的使用

    配置占位符? 随机数配置生成 RandomValuePropertySource 在配置文件中使用随机数 uid = ${random.value} uid = ${random.int} uid = ...

  5. 【SpringMVC】10 对Ajax的应用

    编写一个AjaxController package cn.dai.controller; import org.springframework.web.bind.annotation.GetMapp ...

  6. 暑假自学Java进度总结04

    一.今日所学: 1.下载并使用idea开发工具 1>了解idea的发展历史 2>尝试用idea编写代码 3>学习idea中的项目和模块操作 2.学习赋值运算符 加后赋值:" ...

  7. 国产首款IDE环境:数字广东公司联合麒麟软件打造的国内首款适配国产操作系统、蜘蛛创新的集成开发环境CEC-IDE正式亮相

    参考: https://www.youtube.com/watch?v=fOpBEWZVKU0 在中国it历史上继"木兰编程语言(实际上套壳Python),红旗操作系统(实际上套壳Chrom ...

  8. 【转载】 CPU漏洞 —— 一步一步理解CPU芯片漏洞:Meltdown与Spectre

    原文: https://www.freebuf.com/articles/system/159811.html 参考: https://blog.csdn.net/xy010902100449/art ...

  9. vscode下如何把缩进为2个空格的python项目改为4个空格的缩进

    最近在看老项目的代码,是python2.7年代的项目,那个时候很多的python项目都是使用2个空格,不过现在估计大多数人写python项目都是使用4个空格的了,而我看这两个空格的项目代码也是感觉十分 ...

  10. Vue Vine:带给你全新的 Vue 书写体验!

    你好,我是 Kagol,个人公众号:前端开源星球. 上个月和 TinyVue 的小伙伴们一起参加了 VueConf 24 大会,有幸认识沈青川大佬,并了解了他的 Vue Vine 项目,Vue Vin ...