十六,镜面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等…...
除静电离子风机在无尘车间的应用
除静电离子风机在无尘车间中的应用非常广泛,主要是用来控制车间内的静电荷,防止静电对车间内的电子元器件、电路板等敏感部件产生损害。 具体来说,除静电离子风机通常采用电离器产生大量负离子,将车间内的静电荷中和成无害的水蒸气…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
