Kulla-Conty BRDF
question:


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

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



引入颜色
注意Kulla-Conty实现的是:1次弹射+能量补充,而一次弹射的内容里面是已经有考虑颜色的菲涅尔项了,但是能量的补充我们始终认为是对于一个没有颜色的,F值为1的东西进行的补充【上边推导过程假设了F为1】,这对于有颜色的东西会补充过多,因为F小于1时,多次弹射时每次都会被吸收一部分。因此我们这里考虑颜色是对于补充的那一部分考虑颜色,对补充的能量进行衰减!
现在我们要把这个菲涅尔项考虑进来了,但是这里注意,这个菲涅尔的值,是和角度有关系的,如果在这里再考虑上角度,其实最后我们是很难得到一个实时的结果的。
所以这里再次采用了一种近似的方案,就是把菲涅尔选择一个各角度下的平均值,不管角度是多少都用这一个菲涅尔值。
这个平均和我们之前的E_avg
是一样的思路:
\]

代码
离线计算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的更多相关文章
- 图形学理论知识 BRDF 双向反射分布函数(Bidirectional Reflectance Distribution Function)
图形学理论知识 BRDF 双向反射分布函数 Bidirectional Reflectance Distribution Function BRDF理论 BRDF表示的是双向反射分布函数(Bidire ...
- Ward BRDF实现心得
最近做了Ward BRDF的实现,相对于之前的lambert,phong来说,Ward是一个真正意义上的各向异性BRDF,但同样的,Ward模型也是一个基于经验的模型,并不是物理上正确的.它由ward ...
- Unity3D ShaderLab BRDF模拟
Unity3D ShaderLab BRDF模拟 在上一篇,说到了使用渐变纹理着色,使用一个值来控制纹理的uv坐标,但是这也就表示我们只能得到一个线性的光照效果. 那么我们能不能通过观察方向的向量结合 ...
- Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF
在实时渲染中Physically-Based Rendering(PBR)中文为基于物理的渲染它能为渲染的物体带来更真实的效果,而且能量守恒 稍微解释一下字母的意思,为对后文的理解有帮助,从右到左L为 ...
- 【Unity Shaders】Diffuse Shading——使用2D ramp texture来创建一个假的BRDF(双向反射分布函数)
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- Lambert漫反射的BRDF
Lambert漫反射brdf=Albedo/pi. 推导: 如图,设Lambert面元获得入射照度Ei,各方向均匀释放亮度,每个方向释放的亮度都是Lo. 又设此表面反射率为Albedo,根据反射率定义 ...
- (z转)基于CPU的Bank BRDF经验模型,实现各向异性光照效果!
摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文 名“GPU编程与CG语言之阳春白雪下里巴人” BRDF 光照模型 10.2.1 什么 ...
- (转)图形学理论知识 BRDF 双向反射分布函数(Bidirectional Reflectance Distribution Function)
BRDF理论 BRDF表示的是双向反射分布函数(Bidirectional Reflectance Distribution Function),它描述了光线如何在物体表面进行反射,可以用来描述材质属 ...
- 使用RampTexture实现BRDF效果
[使用RampTexture实现BRDF效果] BRDF stands for bidirectional reflectance distribution function. While that ...
- standard pbr(三)-BRDF
// Default BRDF to use: #if !defined (UNITY_BRDF_PBS) // allow to explicitly override BRDF in custom ...
随机推荐
- .NET周刊【7月第4期 2024-07-28】
国内文章 .NET 高性能缓冲队列实现 BufferQueue https://mp.weixin.qq.com/s/fUhJpyPqwcmb3whuV3CDyg BufferQueue 是一个用 . ...
- Java还是C#?我该如何选择?给年轻人的建议...
一.年轻人应该通吃 其实这不应该是我们真正的主题,而且入了行的也很少会java还是c#这么比,但初学的,java和c#往往就代表了两大流派,java代替了j2ee,c#代替了.net,ok,没有关系, ...
- 【Layui】03 按钮 Button
文档位置: https://www.layui.com/doc/element/button.html 01.[按钮主题] <div> <button type="butt ...
- 亚信科技基于 Apache SeaTunnel 的二次开发应用实践
亚信科技在Apache SeaTunnel的实践分享 自我介绍 各位同学好,很荣幸通过Apache SeaTunnel社区和大家进行分享交流.我是来自亚信科技的潘志宏,主要负责公司内部数据中台产品的开 ...
- [题解] [ABC221H] Count Multiset - DP
[ABC221H] Count Multiset 题面翻译 输入两个正整数 \(N,M\),并存在一个集合,问你一个长度为 \(k\) 的合法集合存在多少个?你需要回答 \(k\) 的值为 \(1\) ...
- 2.2 Memory model
1. 内存区域.类型及属性 内存被分成不同的区域,不同区域有着不同的类型及属性:内存的类型及属性决定着访问这些区域时的行为. 内存的类型有: Normal,处理器可以为了效率而重新排序事务,或者执行推 ...
- 使用 setResponseStatus 函数设置响应状态码
title: 使用 setResponseStatus 函数设置响应状态码 date: 2024/8/25 updated: 2024/8/25 author: cmdragon excerpt: 通 ...
- Linux 常见编辑器
命令行编辑器 Vim Linux 上最出名的编辑器当属 Vim 了.Vim 由 Vi 发展而来,Vim 的名字意指 Vi IMproved,表示 Vi 的升级版.Vim 对于新手来说使用比较复杂,不过 ...
- Shell 目录栈操作
Shell 目录栈允许你将一系列目录压入栈中,然后方便地在这些目录之间进行切换.以下是一些常见的命令及其用途: 常见命令 pushd:将当前目录压入栈中,并切换到指定目录. popd:从栈中移除顶部的 ...
- python pyqt6 颜色弹窗 QColorDialog
def setColor(self): # 避免窗口置顶后,Dialog被主窗口覆盖,所以需要传递self # 设定默认颜色使用getColor的第一个参数(使用setCurrentColor不生效) ...