首页 > 其他分享 >Kulla-Conty BRDF

Kulla-Conty BRDF

时间:2024-01-22 21:44:19浏览次数:26  
标签:Vec3f NdotV 1.0 0.0 BRDF float Kulla vec3 Conty

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的要更亮一些。

标签:Vec3f,NdotV,1.0,0.0,BRDF,float,Kulla,vec3,Conty
From: https://www.cnblogs.com/bluebean/p/17981150

相关文章

  • 光学成像系统 Part III - BRDF测试数据使用 (二)
    一、BRDF实验数据使用方法1.数据集-下载I.数据集格式(AnisotropicBRDFDataFileFormat)解压后的数据集以.dat尾缀结束,文件包括了64Bytes的文件头,用来表示文件中数据的维度,存储格式等信息,在表头之后的便是对应的数据值,具体如下所示:\[3(RGB)\timesdim[0]\timesdim......
  • 物体反射光场BRDF模型
    一、基本概述    双向反射分布函数(BidirectionalReflectanceDistributionFunction,BRDF)是用来定义给定入射方向上的辐射照度如何影响给定出射方向上的辐射率。更笼统地说,它描述了入射光线经过某个表面反射后如何在各个出射方向上分布这可以是从理想镜面反射到漫反射、......
  • 【基于物理的渲染(PBR)白皮书】(三)迪士尼原则的BRDF与BSDF相关总结
    基于物理的渲染(PhysicallyBasedRendering,PBR)技术,自迪士尼在SIGGRAPH2012上提出了著名的“迪士尼原则的BRDF(DisneyPrincipledBRDF)”之后,由于其高度的易用性以及方便......