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

该文章以及代码主要来自
图形学论文解析与复现:【论文复现】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.rhkb.cn/news/126065.html

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

相关文章

【广州华锐互动】煤矿设备AR远程巡检系统实现对井下作业的远程监控和管理

煤矿井下作业环境复杂&#xff0c;安全隐患较多。传统的巡检方式存在诸多弊端&#xff0c;如巡检人员难以全面了解井下情况&#xff0c;巡检效率低下&#xff0c;安全隐患难以及时发现和整改等。为了解决这些问题&#xff0c;提高煤矿安全生产水平&#xff0c;越来越多的企业开…

win10系统配置vmware网络NAT模式

1&#xff0c;查看win10 IP地址&#xff1a;ipconfig 2, vmware设置&#xff1a;编辑>>虚拟网络编辑器>>点击添加网络&#xff08;选择NAT模式&#xff09; 3&#xff0c;虚拟机网络设置&#xff1a;点击VMware虚拟机>>设置>>网络适配器 4&#xff…

【autodl/linux配环境心得:conda/本地配cuda,cudnn及pytorch心得】-未完成

linux配环境心得&#xff1a;conda/本地配cuda&#xff0c;cudnn及pytorch心得 我们服务器遇到的大多数找不到包的问题一&#xff0c;服务器安装cuda和cudnn使用conda在线安装cuda和cudnn使用conda进行本地安装检查conda安装的cuda和cudnn本地直接安装cuda和cudnn方法一&#x…

【数据结构--顺序表】移除元素

题目描述&#xff1a; 代码实现&#xff1a; 1、指针实现 int removeElement(int* nums, int numsSize, int val) {int* dst nums, * src nums;int n1 0,n20;while (n1n2 < numsSize){if (*src ! val){*dst *src;dst;src;n1;//表示src走的步数}else{src;n2;//表示src走…

Matlab图像处理-迭代式阈值选择法

基本思想 迭代式阈值选择法的基本思想是&#xff1a;开始时&#xff0c;选择一个阈值作为初始估计值&#xff0c;然后按某种策略不断地改进这一估计值&#xff0c;直到满足给定的准则为止。在迭代过程中&#xff0c;关键之处在于选择什么样的阈值改进策略。好的阈值改进策略应…

游戏平台加盟该怎么做?需要准备什么?

游戏平台加盟是一种合作模式&#xff0c;允许个人或企业以加盟商的身份参与游戏平台&#xff0c;并从中获得一定的权益和收益。以下是一些步骤和需要准备的事项&#xff0c;来考虑如何进行游戏平台加盟&#xff1a; 步骤&#xff1a; 研究市场和平台&#xff1a;了解游戏市场和…

【AIGC】【图像生成】controlNet介绍(原理+使用)

文章目录 安装1、ControlNet&#xff1a;AI绘画1.1、ControlNet的本质是文生图(txt2img)2.2、预处理器 & 模型选择1.3、参数配置 2、ControlNet 模型分类2.1、草图类(6个)2.2、高级特征类(3个)3.3、高级类(5个) 3、配置参数4、基本原理&#xff1a;可控的SD模型5.可视化效果…

嵌入式IDE(2):KEIL中SCF分散加载链接文件详解和实例分析

在上一篇文章IAR中ICF链接文件详解和实例分析中&#xff0c;我通过I.MX RT1170的SDK中的内存映射关系&#xff0c;分析了IAR中的ICF链接文件的语法。对于MCU编程所使用的IDE来说&#xff0c;IAR和Keil用得比较多&#xff0c;所以这一篇文章就来分析一下Keil的分散文件.scf(scat…

flink的物理DataFlow图及Slot处理槽任务分配

背景 在flink中&#xff0c;有几个比较重要的概念&#xff0c;逻辑DataFlow图&#xff0c;物理DataFlow图以及处理槽执行任务&#xff0c;本文就来讲解下这几个概念 概念详解 假设有以下代码&#xff1a;数据源和统计单词算子的并行度是2&#xff0c;数据汇算子的并行度是1&…

学习Bootstrap 5的第四天

目录 表格 基础表格 实例 条纹表格 实例 带边框表格 实例 有悬停效果的行 实例 黑色/深色表格 实例 黑色/深色条纹表格 实例 可悬停的黑色/深色表格 实例 无边框表格 实例 上下文类 可用的上下文类&#xff1a; 实例 表头颜色 实例 小型表格 实例 响应…

Python 递归、迷宫问题、八皇后问题

递归应用场景 各种数学问题&#xff0c;如八皇后问题、汉诺塔、阶乘问题、迷宫问题、球和篮子问题等各种算法中也会使用到递归&#xff0c;比如快排、归并排序、二分查找、分治算法等能够用栈解决的问题递归的优点就是代码比较简洁 迷宫问题&#xff08;Python版&#xff09;…

pcie 总结

用户空间pci 常用命令 lspci 查看所有pci 设备 lspci -t 树形查看所有设备 lspci -s 00:1f.6 -vvv 查看某个设备所有信息 lspci -s 00:1f.6 -vvv -xxx 增加16进制看看 sudo cat /proc/iomen | grep PCI 查看所有地址映射 如何确定pcie io空间 内存空间大小 (1)读取出基地址…

什么是RPA机器人?RPA机器人能做什么?RPA机器人的应用场景

什么是RPA机器人&#xff1f; RPA机器人是一种使用软件机器人来模拟和执行人类操作的技术。RPA代表Robotic Process Automation&#xff08;机器人流程自动化&#xff09;。它是一种自动化技术&#xff0c;可以使用预定规则和预定流程来执行重复性、繁琐或规定任务的工作。 RP…

[论文笔记] Gunrock: A High-Performance Graph Processing Library on the GPU

Gunrock: A High-Performance Graph Processing Library on the GPU Gunrock: GPU 上的高性能图处理库 [Paper] [Code] PPoPP’16 摘要 Gunrock, 针对 GPU 的高层次批量同步图处理系统. 采用了一种新方法抽象 GPU 图分析: 实现了以数据为中心(data-centric)的抽象, 以在结点…

机房运维管理软件不知道用哪个好?

云顷网络还原系统V7.0是一款专业的机房运维管理产品&#xff0c;基于局域网络环境&#xff0c;针对中高端机房中电脑运维管理需求所设计开发的。网络还原系统软件通过全面的规划和设计&#xff0c;遵从机房部署、使用到维护阶段化使用方式&#xff0c;通过极速网络同传/增量对拷…

智能电话机器人的出现,能够解决哪些问题?

经济的繁荣与高速的发展&#xff0c;使得电销这个方式快速地融合在房地产与金融投资等大部分行业上。在电销人员与客户的沟通上&#xff0c;难免会出现很多问题&#xff0c;毕竟所面对的客户都是各行各业&#xff0c;他们有着不同的经历和身份。 对于时常需要处理客户投诉、安…

2023数模国赛C 题 蔬菜类商品的自动定价与补货决策-完整版创新多思路详解(含代码)

题目简评&#xff1a;看下来C题是三道题目里简单一些的&#xff0c;考察的点比较综合&#xff0c;偏数据分析。涉及预测模型和运筹优化(线性规划)&#xff0c;还设了一问开放型问题&#xff0c;适合新手入门&#xff0c;发挥空间大。 题目分析与思路&#xff1a; 背景&#x…

MJ绘制「酱香拿铁」可爱壁纸;LLM产品团队招聘预告;FlowGPT提示词大赛第3季;台大深度学习音乐分析与生成最新课程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f525; 蹭「酱香拿铁」热点的Midjouney绘图创意&#xff0c;好可爱的手机壁纸 小红书作者 美学孤诣 使用 Midjourney 制作了「上个茅班」的手…

Emgu调用摄像头

1&#xff0c;安装EMgu 2,调用摄像头 public FaceLoad(){InitializeComponent();try{capture new Capture();capture.Start();//摄像头开始工作capture.ImageGrabbed frameProcess;//实时获取图像}catch (NullReferenceException excpt){//MessageBox.Show(excpt.Message);}}…

原生JavaScript+PHP多图上传实现

摘要 很多场景下需要选择多张图片上传&#xff0c;或者是批量上传以提高效率&#xff0c;多图上传的需求自然就比较多了&#xff0c;本文使用最简单的XMLHttpRequest异步上传图片。 界面 上传示例 代码 index.html <!DOCTYPE html> <html><head><titl…