十六,镜面IBL--预滤波环境贴图
又到了开心的公式时刻了。
先看看渲染方程

现在关注第二部分,镜面反射。

其中
这里很棘手,与输入wi和输出w0都有关系,所以,再近似

其中第一部分,就是预滤波环境贴图,形式上与前面的辐照度图很相似,那么能不能用同样的方法呢?
先看看镜面反射和漫反射的图

可以看到,镜面反射是绕着出射向量的一个范围(成为波瓣),而漫反射是绕着法线方向均匀分布的。
再想想积分辐照度图时,是以法线向量为中心,进行积分的。
那很自然的想到,积分镜面反射的预滤波环境贴图可以以出射向量为中心,在波瓣范围内积分。
然而, 波瓣有大有小,是因为粗糙度不同,

所以,不能只积分一次,而是多次,按照不同粗糙度积分后写到mipmap,或者单独的纹理中。这里为了方便,分别写到不同的纹理中。
那么该如何积分呢?辐照度图是在经度0到360,纬度0到90内均匀积分。

而镜面反射中,给定入射方向,波瓣指向方向就是微平面半向量的反射方向。所以,只在波瓣内积分就可以了,即重要性采样。
这时就可以使用蒙特卡洛积分,即在大数定律基础上,采取N样本即可。N越大越准。pdf为概率密度函数。

比如

采样样本越多,越靠近中间范围。因为中间范围概率大。
以上为均匀采样,
如果采样样本有偏,则会更快收敛。比如通过低差异序列获取样本。
float RadicalInverse_VdC(uint 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; // / 0x100000000
}
vec2 Hammersley(uint i, uint N)
{
return vec2(float(i)/float(N), RadicalInverse_VdC(i));
}
或者无位运算的
"float VanDerCorpus(uint n, uint base) "
"{ "
" float invBase = 1.0 / float(base); "
" float denom = 1.0; "
" float result = 0.0; "
" for (uint i = 0u; i < 32u; ++i) "
" { "
" if (n > 0u) "
" { "
" denom = mod(float(n), 2.0); "
" result += denom * invBase; "
" invBase = invBase / 2.0; "
" n = uint(float(n) / 2.0); "
" } "
" } "
"return result; "
"} "
" "
"vec2 HammersleyNoBitOps(uint i, uint N) "
"{ "
" return vec2(float(i) / float(N), VanDerCorpus(i, 2u)); "
"} "
然后根据法线方向,粗糙度和低差异序列生成采样向量,该向量大体围绕着预估的波瓣方向。
“vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness)”
“{”
“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);”
“vec3 H;”
“H.x = cos(phi) * sinTheta;”
“H.y = sin(phi) * sinTheta;”
“H.z = cosTheta;”
“vec3 up = abs(N.z) < 0.999 ? vec3(0.0,0.0,1.0) : vec3(1.0,0.0,0.0);”
“vec3 tangent = normalize(cross(up,N));”
“vec3 bitangent = cross(N,tangent);”
“vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;”
“return normalize(sampleVec);”
“}”
运行结果如下

代码如下
#include <osg/TextureCubeMap>
#include <osg/TexGen>
#include <osg/TexEnvCombine>
#include <osgUtil/ReflectionMapGenerator>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osg/NodeVisitor>
#include <osg/ShapeDrawable>
static const char * vertexShader =
{
//“#version 120 core\n”
“in vec3 aPos;\n”
“varying vec3 localPos;\n”
“void main(void)\n”
“{\n”
“localPos = aPos;\n”
" gl_Position = ftransform();\n"
//“gl_Position = view * view * vec4(aPos,1.0);”
“}\n”
};
static const char psShader =
{
“varying vec3 localPos;\n”
“uniform samplerCube environmentMap;”
“uniform float roughness;”
“const float PI = 3.1415926;”
“float VanDerCorpus(uint n, uint base) "
“{ "
" float invBase = 1.0 / float(base); "
" float denom = 1.0; "
" float result = 0.0; "
" for (uint i = 0u; i < 32u; ++i) "
" { "
" if (n > 0u) "
" { "
" denom = mod(float(n), 2.0); "
" result += denom * invBase; "
" invBase = invBase / 2.0; "
" n = uint(float(n) / 2.0); "
" } "
" } "
“return result; "
“} "
" "
“vec2 HammersleyNoBitOps(uint i, uint N) "
“{ "
" return vec2(float(i) / float(N), VanDerCorpus(i, 2u)); "
“} "
//“float RadicalInverse_Vdc(uint bits)\n”
//”{”
//“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;”
//”}”
//“vec2 Hammersley(uint i, uint N)”
//”{”
//“return vec2(float(i) / float(N), RadicalInverse_Vdc(i));”
//”}"
“vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness)”
“{”
“float a = roughness * roughness;”
“float phi = 2.0 * PI * Xi.x;”
"float cosTheta = sqrt((1.0 - Xi.y)/(1.0+(aa-1.0) * Xi.y));"
“float sinTheta = sqrt(1.0 - cosTheta * cosTheta);”
“vec3 H;”
“H.x = cos(phi) * sinTheta;”
“H.y = sin(phi) * sinTheta;”
“H.z = cosTheta;”
“vec3 up = abs(N.z) < 0.999 ? vec3(0.0,0.0,1.0) : vec3(1.0,0.0,0.0);”
“vec3 tangent = normalize(cross(up,N));”
“vec3 bitangent = cross(N,tangent);”
“vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;”
“return normalize(sampleVec);”
“}”
"void main() "
"{ "
" vec3 N = normalize(localPos); "
" vec3 R = N; "
" vec3 V = R; "
" "
" const uint SAMPLE_COUNT = 1024u; "
" float totalWeight = 0.0; "
" vec3 prefilteredColor = vec3(0.0); "
" for (uint i = 0u; i < SAMPLE_COUNT; ++i) "
" { "
" vec2 Xi = HammersleyNoBitOps(i, SAMPLE_COUNT); "
" vec3 H = ImportanceSampleGGX(Xi, N, roughness); "
" vec3 L = normalize(2.0 * dot(V, H) * H - V); "
" "
" float NdotL = max(dot(N, L), 0.0); "
" if (NdotL > 0.0) "
" { "
" prefilteredColor += texture(environmentMap, L).rgb * NdotL; "
" totalWeight += NdotL; "
" } "
" } "
" prefilteredColor = prefilteredColor / totalWeight; "
" "
" gl_FragColor = vec4(prefilteredColor, 1.0); "
"} "
};
class MyNodeVisitor : public osg::NodeVisitor
{
public:
MyNodeVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
{
}
void apply(osg::Geode& geode)
{int count = geode.getNumDrawables();for (int i = 0; i < count; i++){osg::ref_ptr<osg::Geometry> geometry = geode.getDrawable(i)->asGeometry();if (!geometry.valid()){continue;}osg::Array* vertexArray = geometry->getVertexArray();geometry->setVertexAttribArray(1, vertexArray);}traverse(geode);
}
};
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;osg::ref_ptr<osg::TextureCubeMap> tcm = new osg::TextureCubeMap;
tcm->setTextureSize(128, 128);
tcm->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
tcm->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
tcm->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
tcm->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
tcm->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);std::string strImagePosX = "D:/hdr/Right face camera.bmp";
osg::ref_ptr<osg::Image> imagePosX = osgDB::readImageFile(strImagePosX);
tcm->setImage(osg::TextureCubeMap::POSITIVE_X, imagePosX);
std::string strImageNegX = "D:/hdr/Left face camera.bmp";
osg::ref_ptr<osg::Image> imageNegX = osgDB::readImageFile(strImageNegX);
tcm->setImage(osg::TextureCubeMap::NEGATIVE_X, imageNegX);std::string strImagePosY = "D:/hdr/Front face camera.bmp";;
osg::ref_ptr<osg::Image> imagePosY = osgDB::readImageFile(strImagePosY);
tcm->setImage(osg::TextureCubeMap::POSITIVE_Y, imagePosY);
std::string strImageNegY = "D:/hdr/Back face camera.bmp";;
osg::ref_ptr<osg::Image> imageNegY = osgDB::readImageFile(strImageNegY);
tcm->setImage(osg::TextureCubeMap::NEGATIVE_Y, imageNegY);std::string strImagePosZ = "D:/hdr/Top face camera.bmp";
osg::ref_ptr<osg::Image> imagePosZ = osgDB::readImageFile(strImagePosZ);
tcm->setImage(osg::TextureCubeMap::POSITIVE_Z, imagePosZ);std::string strImageNegZ = "D:/hdr/Bottom face camera.bmp";
osg::ref_ptr<osg::Image> imageNegZ = osgDB::readImageFile(strImageNegZ);
tcm->setImage(osg::TextureCubeMap::NEGATIVE_Z, imageNegZ);
tcm->setUseHardwareMipMapGeneration(true);
float minMipMapLevel = 0.0;
float maxMipMapLevel = 4.0;
tcm->setMinLOD(minMipMapLevel);
tcm->setMaxLOD(maxMipMapLevel);osg::ref_ptr<osg::Box> box = new osg::Box(osg::Vec3(0, 0, 0), 10);
osg::ref_ptr<osg::ShapeDrawable> drawable = new osg::ShapeDrawable(box);
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(drawable);
MyNodeVisitor nv;
geode->accept(nv);
osg::ref_ptr<osg::StateSet> stateset = geode->getOrCreateStateSet();
stateset->setTextureAttributeAndModes(0, tcm, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);//shaderosg::ref_ptr<osg::Shader> vs1 = new osg::Shader(osg::Shader::VERTEX, vertexShader);
osg::ref_ptr<osg::Shader> ps1 = new osg::Shader(osg::Shader::FRAGMENT, psShader);
osg::ref_ptr<osg::Program> program1 = new osg::Program;
program1->addShader(vs1);
program1->addShader(ps1);
program1->addBindAttribLocation("aPos", 1);osg::ref_ptr<osg::Uniform> environmentMapUniform = new osg::Uniform("environmentMap", 0);
stateset->addUniform(environmentMapUniform);
float theMip = 3.0;
float roughness = theMip / maxMipMapLevel;
osg::ref_ptr<osg::Uniform> roughnessUniform = new osg::Uniform("roughness", roughness);
stateset->addUniform(roughnessUniform);stateset->setAttribute(program1, osg::StateAttribute::ON);viewer->setSceneData(geode);
viewer->realize();
return viewer->run();
}
相关文章:
十六,镜面IBL--预滤波环境贴图
又到了开心的公式时刻了。 先看看渲染方程 现在关注第二部分,镜面反射。 其中 这里很棘手,与输入wi和输出w0都有关系,所以,再近似 其中第一部分,就是预滤波环境贴图,形式上与前面的辐照度图很相似&#…...
信息安全:恶意代码防范技术原理.
信息安全:恶意代码防范技术原理. 恶意代码的英文是 Malicious Code, 它是一种违背目标系统安全策略的程序代码,会造成目标系统信息泄露、资源滥用,破坏系统的完整性及可用性。 目录: 恶意代码概述: (1&a…...
开源媒体浏览器Kyoo
什么是 Kyoo ? Kyoo 是一款开源媒体浏览器,可让您流式传输电影、电视节目或动漫。它是 Plex、Emby 或 Jellyfin 的替代品。Kyoo 是从头开始创建的,它不是一个分叉。一切都将永远是免费和开源的。 软件特性: 管理您的电影、电视剧…...
人脸解锁设备时出现相机报错
(1)背景分析 这是项目当中实际遇到的问题,如下代码仅用作分析和记录。 现在问题的现象是:刚亮屏大概在2s以内对着人脸一般是能解锁的,但是超过2s之后在对着人脸,是无法解锁成功的。 (2&#…...
【广州华锐互动】利用VR开展工业事故应急救援演练,确保救援行动的可靠性和有效性
在工业生产中,事故的突发性与不可预测性常常带来巨大的损失。传统的应急演练方式往往存在场地限制、成本高、效果难以衡量等问题。然而,随着虚拟现实(VR)技术的快速发展,VR工业事故应急救援演练应运而生,为…...
还不知道数据类岗位的相关技能和职责吗?涤生大数据告诉你(二)
续接上文:还不知道数据类岗位的相关技能和职责吗?涤生大数据告诉你(一) 1.数据治理工程师 工作职责 数据治理工程师的工作职责主要包括以下几个方面: 1. 数据管理策略制定:制定和实施数据管理策略&#…...
常见应用层协议
一.HTTP(超文本传输协议) HTTP 和 HTTPS 二.FTP(文件传输协议) 三.SMTP(简单邮件传输协议) 四.POP3(邮局协议版本3) 五.IMAP(互联网消息访问协议) 六.DNS&am…...
解决docker容器无法关闭的问题
一般正常关闭: docker stop 容器ID解决方法 方法1:强制停止docker kill 容器ID方法2:直接重启dockersudo service docker stop方法3:直接删除容器,重新创建docker rm -f my_container...
2023-09-27 LeetCode每日一题(餐厅过滤器)
2023-09-27每日一题 一、题目编号 1333. 餐厅过滤器二、题目链接 点击跳转到题目位置 三、题目描述 给你一个餐馆信息数组 restaurants,其中 restaurants[i] [idi, ratingi, veganFriendlyi, pricei, distancei]。你必须使用以下三个过滤器来过滤这些餐馆信息…...
梯度下降法(SGD)原理
目录 梯度下降法(SGD)原理:求偏导 1. 梯度(在数学上的定义) 2. 梯度下降法迭代步骤 BGD批量梯度下降算法 BGD、SGD在工程选择上的tricks 梯度下降法(SGD)原理:求偏导 1. 梯度(在数学上的定义) 表示某一函数在该点处的方向导数沿着该方向取得最大值…...
QQ表情包存储位置解析
一些常见的设备和系统的QQ表情包存储位置: Windows系统: 路径:C:\Users[用户名]\Documents\Tencent Files[QQ号码]\Image\Image\CustomFace 在这个文件夹中,您可以找到所有自定义的QQ表情包。 Android系统: 路径&am…...
软件架构的演化和维护
软件架构的演化和维护 定义 定义 顶不住了,刷题去了,不搞这个了,想吐。。。...
C语言数组和指针笔试题(四)(一定要看)
目录 二维数组例题一例题二例题三例题四例题五例题六例题七例题八例题九例题十例题十一 结果 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 🐒🐒🐒个人主页 🥸🥸🥸C语言 🐿️…...
FragmentManager is already executing transactions
本文解决问题: java.lang.IllegalStateException: FragmentManager is already executing transactions 问题背景描述: 在Fragment中 用tablayoutviewpagerfragment,即Fragment嵌套Fragment场景、或者ViewPager2嵌套ViewPager2时。 执行生命…...
Matlab中clear,close all,clc功能详细说明
背景: 我们在写matlab程序时,首行总是先敲入:clear; close all; clc;,但你真的知道这三句话的具体作用嘛,下面进行详细说明和演示。 一、clear的功能 clear的功能:清理工作区变量,不清理前是…...
Typora安装无需破解免费使用
Typora简介: 在介绍Typora软件之前,需要先介绍一下MARKDOWN。 MARKDOWN是一种轻量型标记语言,它具有“极简主义”、高效、清晰、易读、易写、易更改纯文本的特点。 Typora 是一款支持实时预览的 Markdown 文本编辑器。它有 OS X、Windows、…...
LuatOS-SOC接口文档(air780E)--errDump - 错误上报
示例 -- 基本用法, 10分钟上报一次,如果有的话 if errDump thenerrDump.config(true, 600) end-- 附开源服务器端: https://gitee.com/openLuat/luatos-devlogerrDump.dump(zbuff, type, isDelete) 手动读取异常日志,主要用于用户将日志发送给自己的服务器而不是I…...
低代码平台如何助力国内企业数字化转型?
数字化是什么 数字化(Digitalization)是将许多复杂多变的信息转变为可以度量的数字、数据,再以这些数字、数据建立起适当的数字化模型,把它们转变为一系列二进制代码,引入计算机内部,进行统一处理…...
SI3262—高度集成的低功耗SOC芯片
Si3262是一款高度集成的低功耗SOC芯片,其集成了基于RISC-V核的低功耗MCU和工作在13.56MHz的非接触式读写器模块。 MCU模块具有低功耗、Low Pin Count、宽电压工作范围,集成了13/14/15/16位精度的ADC、LVD、UART、SPI、I2C、TIMER、WUP、IWDG、RTC、TSC等…...
除静电离子风机在无尘车间的应用
除静电离子风机在无尘车间中的应用非常广泛,主要是用来控制车间内的静电荷,防止静电对车间内的电子元器件、电路板等敏感部件产生损害。 具体来说,除静电离子风机通常采用电离器产生大量负离子,将车间内的静电荷中和成无害的水蒸气…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
