球谐函数实现环境光照漫反射实践

该文章以及代码主要来自
图形学论文解析与复现:【论文复现】An Efficient Representation for Irradiance Environment Maps
作者:Monica的小甜甜

与原文的不同

  • 对一些有问题的地方进行了修改
  • 添加了注释
  • 对有疑问的地方添加了疑问点
  • 引入了其他一些Blog填充了原文中忽略的信息

1、预计算球面谐波函数系数

首先根据上一篇【球谐函数在环境光照中的使用原理】得到的最终公式:

在这里插入图片描述
我们需要预计算 L l m L_l^m Llm的值。计算公式为:
在这里插入图片描述

Ω \Omega Ω为球面积分,这里对应对天空盒逐像素积分。
积分代码为:

void Harmonics::Evaluate()//求值
{m_Coefs = vector<glm::vec3>(m_Degree, glm::vec3());//6张图for (int k = 0; k < 6; k++){cv::Mat img = m_Images[k];int w = m_Images[k].cols;int h = m_Images[k].rows;//逐像素for (int j = 0; j < w; j++){for (int i = 0; i < h; i++){// 像素点位置float px = (float)i + 0.5;float py = (float)j + 0.5;// 像素点UV 【-1,1】:以摄像机正对位置的(0,0)float u = 2.0 * (px / (float)w) - 1.0;float v = 2.0 * (py / (float)h) - 1.0;// 像素间UV的一半的偏移量float d_x = 1.0 / (float)w;// (x0,y0)像素左下角 (x1,y1)像素右上角float x0 = u - d_x;float y0 = v - d_x;float x1 = u + d_x;float y1 = v + d_x;// 计算Cubemap的一个像素对应的立体角的大小float d_a = surfaceArea(x0, y0) - surfaceArea(x0, y1) - surfaceArea(x1, y0) + surfaceArea(x1, y1);// 纹理像素点 转化为 世界坐标点u = (float)j / (img.cols - 1);v = 1.0f - (float)i / (img.rows - 1);glm::vec3 p = CubeUV2XYZ({ k, u, v });// 获取当前像素颜色auto c = img.at<cv::Vec3f>(i, j);glm::vec3 color = {c[2], c[1], c[0]};// 得到基函数计算结果列表vector<float> Y = Basis(p);// 计算系数for (int i = 0; i < m_Degree; i++){m_Coefs[i] = m_Coefs[i] + Y[i] * color * d_a;}}}}
}

其中 计算Cubemap的一个像素对应的立体角的大小原理可参照
Solid Angle of A Cubemap Texel - 计算Cubemap的一个像素对应的立体角的大小

我们将得到的积分结果保存在一个文件中【SHCoefficients.txt】,用于之后读取。

2、预计算BRDF的LUT图

LUT(Look up Table)图,预计算了任意一个天空盒下,已知法线和视口的夹角以及材质粗糙度,查找得到Frenel项

然而这个LUT图和IBL中的LUT有一些不同。
因为IBL中的LUT加入了 n ⋅ w n\cdot w nw 光照衰减项。
而在球谐函数中, n ⋅ w n\cdot w nw 作为 t l 参与运算 t_l参与运算 tl参与运算,因此在球谐函数的IBL中删除了 n ⋅ w n\cdot w nw

main函数计算

	for(int i = 0; i < N; i++){for (int j = 0; j < N; j++){float NoV = (i + 0.5f) * (1.0f / N);float roughness = (j + 0.5f) * (1.0f / N);glm::vec2 eval = IntegrateBRDF(NoV, roughness);tex.store<glm::vec2>({ i, N - j - 1 }, 0, eval);}}

其他被调用函数

const float PI = 3.14159265358979323846264338327950288;float RadicalInverse_VdC(unsigned int bits)
{bits = (bits << 16u) | (bits >> 16u);bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);return float(bits) * 2.3283064365386963e-10;
}glm::vec2 Hammersley(unsigned int i, unsigned int N)
{return glm::vec2(float(i) / float(N), RadicalInverse_VdC(i));
}glm::vec3 ImportanceSampleGGX(glm::vec2 Xi, float roughness, glm::vec3 N)
{float a = roughness * roughness;float phi = 2.0 * PI * Xi.x;float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));float sinTheta = sqrt(1.0 - cosTheta * cosTheta);// from spherical coordinates to cartesian coordinatesglm::vec3 H;H.x = cos(phi) * sinTheta;H.y = sin(phi) * sinTheta;H.z = cosTheta;// from tangent-space vector to world-space sample vectorglm::vec3 up = abs(N.z) < 0.999 ? glm::vec3(0.0, 0.0, 1.0) : glm::vec3(1.0, 0.0, 0.0);glm::vec3 tangent = normalize(cross(up, N));glm::vec3 bitangent = cross(N, tangent);glm::vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;return normalize(sampleVec);
}float GeometrySchlickGGX(float NdotV, float roughness)
{float a = roughness;float k = (a * a) / 2.0;float nom = NdotV;float denom = NdotV * (1.0 - k) + k;return nom / denom;
}float GeometrySmith(float roughness, float NoV, float NoL)
{float ggx2 = GeometrySchlickGGX(NoV, roughness);float ggx1 = GeometrySchlickGGX(NoL, roughness);return ggx1 * ggx2;
}glm::vec2 IntegrateBRDF(float NdotV, float roughness, unsigned int samples = 1024)
{glm::vec3 V;V.x = sqrt(1.0 - NdotV * NdotV);V.y = 0.0;V.z = NdotV;float A = 0.0;float B = 0.0;glm::vec3 N = glm::vec3(0.0, 0.0, 1.0);for (unsigned int i = 0u; i < samples; ++i){glm::vec2 Xi = Hammersley(i, samples);glm::vec3 H = ImportanceSampleGGX(Xi, roughness, N);glm::vec3 L = normalize(2.0f * dot(V, H) * H - V);float NoL = glm::max(L.z, 0.0f);float NoH = glm::max(H.z, 0.0f);float VoH = glm::max(dot(V, H), 0.0f);float NoV = glm::max(dot(N, V), 0.0f);if (NoL > 0.0){float G = GeometrySmith(roughness, NoV, NoL);float G_Vis = (G * VoH) / (NoH * NoV) / NoL;float Fc = pow(1.0 - VoH, 5.0);A += (1.0 - Fc) * G_Vis;B += Fc * G_Vis;}}return glm::vec2(A / float(samples), B / float(samples));
}

在这里插入图片描述

3、将计算数据传入Shader

  • 传入BRDFLUT纹理
  • 传入球谐函数系数列表
void CShadingPass::initV()
{auto m_LUTTexture = std::make_shared<ElayGraphics::STexture>();loadTextureFromFile("../Textures/BRDFLUT/BRDFLut.dds", m_LUTTexture);getCoefs();ElayGraphics::Camera::setMainCameraFarPlane(100);ElayGraphics::Camera::setMainCameraPos({ -1.57278, 0.244948, 0.367194 });ElayGraphics::Camera::setMainCameraFront({ 0.967832, -0.112856, -0.224865 });ElayGraphics::Camera::setMainCameraMoveSpeed(0.5);m_pShader = std::make_shared<CShader>("Sponza_VS.glsl", "Sponza_FS.glsl");m_pSponza = std::dynamic_pointer_cast<CSponza>(ElayGraphics::ResourceManager::getGameObjectByName("Sponza"));m_pShader->activeShader();m_pShader->setTextureUniformValue("u_BRDFLut", m_LUTTexture);m_pShader->setMat4UniformValue("u_ModelMatrix", glm::value_ptr(m_pSponza->getModelMatrix()));for (int i = 0; i < m_Coefs.size(); i++){m_pShader->setFloatUniformValue("u_Coef[" + std::to_string(i) + "]", m_Coefs[i].x, m_Coefs[i].y, m_Coefs[i].z);}m_pSponza->initModel(*m_pShader);
}

4、 Draw

#version 430 corein  vec3 v2f_FragPosInViewSpace;
in  vec2 v2f_TexCoords;
in  vec3 v2f_ViewSpaceNormal;
in  vec3 v2f_WorldSpaceNormal;layout (location = 0) out vec4 Albedo_;const float PI = 3.1415926535897932384626433832795;
uniform vec3 u_Coef[16];
uniform vec3 u_DiffuseColor;
uniform sampler2D u_BRDFLut;vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
}  void main()
{	if((abs(v2f_ViewSpaceNormal.x) < 0.0001f) && (abs(v2f_ViewSpaceNormal.y) < 0.0001f) && (abs(v2f_ViewSpaceNormal.z) < 0.0001f)){Albedo_ = vec4(0, 0, 0, 1);return;}float Basis[9];float x = v2f_WorldSpaceNormal.x;float y = v2f_WorldSpaceNormal.y;float z = v2f_WorldSpaceNormal.z;float x2 = x * x;float y2 = y * y;float z2 = z * z;//这里所有系数应该为乘PI------------------个人认为Basis[0] = 1.f / 2.f * sqrt(1.f / PI);Basis[1] = 2.0 / 3.0 * sqrt(3.f / 4.f * PI) * z;Basis[2] = 2.0 / 3.0 * sqrt(3.f / 4.f * PI) * y;Basis[3] = 2.0 / 3.0 * sqrt(3.f / 4.f * PI) * x;Basis[4] = 1.0 / 4.0 * 1.f / 2.f * sqrt(15.f * PI) * x * z;Basis[5] = 1.0 / 4.0 * 1.f / 2.f * sqrt(15.f * PI) * z * y;Basis[6] = 1.0 / 4.0 * 1.f / 4.f * sqrt(5.f * PI) * (-x2 - z2 + 2 * y2);Basis[7] = 1.0 / 4.0 * 1.f / 2.f * sqrt(15.f * PI) * y * x;Basis[8] = 1.0 / 4.0 * 1.f / 4.f * sqrt(15.f * PI) * (x2 - z2);vec3 Diffuse = vec3(0,0,0);vec3 F0 = vec3(0.2,0.2,0.2);float Roughness = 0.5;vec3 N = normalize(vec4(v2f_ViewSpaceNormal,1.0f)).xyz;//viewMatrix * vec3 V = -normalize(v2f_FragPosInViewSpace);//vec3 R = reflect(-V, N); F0        = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, Roughness);vec2 EnvBRDF  = texture(u_BRDFLut, vec2(max(dot(N, V), 0.0), Roughness)).rg;vec3 LUT = (F0 * EnvBRDF.x + EnvBRDF.y);for (int i = 0; i < 9; i++)Diffuse += u_Coef[i] * Basis[i] * (1-LUT);Albedo_ = vec4(Diffuse);
}

结果展示

在这里插入图片描述
在这里插入图片描述
只有漫反射的效果:
在这里插入图片描述
只有镜面反射的效果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/79028.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于python解决鸡兔同笼问题

一、什么是鸡兔同笼问题&#xff1f; 鸡兔同笼问题是一个经典的数学问题。问题描述&#xff1a;鸡和兔子共有头数a和脚数b&#xff0c;求鸡和兔子的数量。 解析&#xff1a;设鸡的数量为x&#xff0c;兔子的数量为y&#xff0c;那么可以得到以下两个方程&#xff1a; 1. x y…

对抗生成网络总结

对一些基本的对抗生成网络的总结。部分内容整理自Teeyohuang’s blog 文章目录 GAN (NeurIPS, 2014)CGANDCGANStackGANPix2Pix (CVPR, 2017)CycleGAN (ICCV, 2017)SRGAN (CVPR, 2017)StyleGAN (CVPR, 2019) GAN (NeurIPS, 2014) Generative adversarial nets m i n G m a x D …

Tokenview X-ray功能:深入探索EVM系列浏览器的全新视角

Tokenview作为一家领先的多链区块浏览器&#xff0c;为了进一步优化区块链用户的使用体验&#xff0c;我们推出了X-ray&#xff08;余额透视&#xff09;功能。该功能将帮助您深入了解EVM系列浏览器上每个地址的交易过程&#xff0c;以一种直观、简洁的方式呈现地址的进出账情况…

002 Linux 权限

前言 本文将会向您介绍关于linux权限方面的内容&#xff0c;包括文件类型&#xff0c;如何切换用户、基本权限、粘滞位等等 Linux具体的用户 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 普通用户&#xff1a;在linux下做有限的事情。 超级用户的…

SSM - Springboot - MyBatis-Plus 全栈体系(八)

第二章 SpringFramework 四、SpringIoC 实践和应用 4. 基于 配置类 方式管理 Bean 4.4 实验三&#xff1a;高级特性&#xff1a;Bean 注解细节 4.4.1 Bean 生成 BeanName 问题 Bean 注解源码&#xff1a; public interface Bean {//前两个注解可以指定Bean的标识AliasFor…

思科的简易配置

vlan 划分配置 1. 拓扑连接 2. 终端设备配置&#xff0c;vlan(v2, v3)配置&#xff0c;模式设置 然后设置交换机 fa 0/5 口为 trunk 模式&#xff0c;使得不同交换机同一 vlan 下 PC 可以互连 3.测试配置结果 用 ip 地址为 192.168.1.1 的主机(PC0)向同一 vlan(v2)下的 192.…

如何统计iOS产品不同渠道的下载量?

一、前言 在开发过程中&#xff0c;Android可能会打出来很多的包&#xff0c;用于标识不同的商店下载量。原来觉得苹果只有一个商店&#xff1a;AppStore&#xff0c;如何做出不同来源的统计呢&#xff1f;本篇文章就是告诉大家如何做不同渠道来源统计。 二、正文 先看一下苹…

算法——快乐数

202. 快乐数 - 力扣&#xff08;LeetCode&#xff09; 由图可知&#xff0c;其实这也是一个判断循环的过程&#xff0c;要用到快慢指针&#xff0c;且相遇后&#xff0c;若在全为1的循环里&#xff0c;那么就是快乐数&#xff0c;若相遇后不为1&#xff0c;说明这不是快乐数。 …

备份数据重删

重复数据删除&#xff1a; 在计算中&#xff0c;重复数据删除是一种消除重复数据重复副本的技术。此技术用于提高存储利用率&#xff0c;还可以应用于网络数据传输以减少必须发送的字节数。在重复数据删除过程中&#xff0c;将在分析过程中识别并存储唯一的数据块或字节模式。…

HAlcon例子

气泡思想 * This example shows the use of the operator dyn_threshold for * the segmentation of the raised dots of braille chharacters. * The operator dyn_threshold is especially usefull if the * background is inhomogeneously illuminated. In this example, *…

vue3的生命周期

1.vue3生命周期官方流程图 2.vue3中的选项式生命周期 vue3中的选项式生命周期钩子基本与vue2中的大体相同&#xff0c;它们都是定义在 vue实例的对象参数中的函数&#xff0c;它们在vue中实例的生命周期的不同阶段被调用。生命周期函数钩子会在我们的实例挂载&#xff0c;更新…

竞赛 基于机器视觉的火车票识别系统

文章目录 0 前言1 课题意义课题难点&#xff1a; 2 实现方法2.1 图像预处理2.2 字符分割2.3 字符识别部分实现代码 3 实现效果最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器视觉的火车票识别系统 该项目较为新颖&#xff0c;适合作为竞赛…

23下半年学习计划

大二上学期计划 现在已经是大二了&#xff0c;java只学了些皮毛&#xff0c;要学的知识还有很多&#xff0c;新的学期要找准方向&#xff0c;把要学的知识罗列&#xff0c;按部就班地完成计划&#xff0c;合理安排时间&#xff0c;按时完成学习任务。 学习node.js&#xff0c…

运维学习之部署Grafana

sudo nohup wget https://dl.grafana.com/oss/release/grafana-10.1.1.linux-amd64.tar.gz &后台下载压缩包&#xff0c;然后按一下回车键。 ps -aux | grep 15358发现有两条记录&#xff0c;就是还在下载中。 ps -aux | grep 15358发现有一条记录&#xff0c;并且tail …

一百七十八、ClickHouse——海豚调度执行ClickHouse的.sql文件

一、目的 由于数仓的ADS层是在ClickHouse中&#xff0c;即把Hive中DWS层的结果数据同步到ClickHouse中&#xff0c;因此需要在ClickHouse中建表&#xff0c;于是需要海豚调度执行ClickHouse的.sql文件 二、实施步骤 &#xff08;一&#xff09;第一步&#xff0c;海豚建立Cl…

Python in Visual Studio Code 2023年9月更新

作者&#xff1a;Courtney Webster - Program Manager, Python Extension in Visual Studio Code 排版&#xff1a;Alan Wang 我们很高兴地宣布 Visual Studio Code 的 Python 和 Jupyter 扩展将于 2023 年 9 月发布&#xff01; 此版本包括以下内容&#xff1a; • 将 Python …

使用 Nginx 实现企业微信域名配置中的校验文件跳转

背景 在企业微信中配置业务域名时&#xff0c;通常需要在该域名的根路径下放置一个校验文件&#xff0c;以验证域名的所有权。然而&#xff0c;如果该域名是第三方的&#xff0c;你可能无法直接在根路径下放置文件。在这种情况下&#xff0c;你可以使用 Nginx 来实现校验文件的…

2023 Google 开发者大会|Mobile开发专题追踪

文章目录 前言大会介绍涉及内容MobileWebAICloud Mobile开发专题多终端应用的开发适配大屏视频流可穿戴设备电视新的设计中心 构建高质量的应用高级相机和媒体功能用户的安全和隐私更精细的视觉体验 小结 前言 哈喽大家好&#xff0c;我是阿Q。近期&#xff0c;【2023 Google …

python: excel假期时间提取统计

# encoding: utf-8 # 版权所有 2023 涂聚文有限公司 # 许可信息查看&#xff1a; # 描述&#xff1a; # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2023.1 python 311 # Datetime : 2023/9/3 7:04 # User : geovindu # Product : PyCharm # Proje…

Redis 数据一致性方案的分析与研究

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ 一般的业务场景都是读多写少的&#xff0c;当客户端的请求太多&#xff0c;对数据库的压力越来越大&#xff0c;引入缓存来降低数据库的压力是必然选择&#xff0c;目前业内…