PRT预计算辐射传输方法
PRT(Precomputed Radiance Transfer)技术是一种用于实时渲染全局光照的方法。它通过预计算光照传输来节省时间,并能够实时重现面积光源下3D模型的全局光照效果。
由于PRT方法的局限,它不能计算随机动态场景的全局光照,场景中物体也不可变动。
Basic Idea
- 光的传输与背景光照内容本身无关,因此两部分拆开计算。
- 环境光照使用球谐函数拟合,来近似表示
Diffuse Case
对于完全粗糙表面,brdf系数是一个常量,因此:
Precompute
1.球谐函数表达环境光照,离线计算和保存
公式:
\]
code:
float CalcPreArea(const float& x, const float& y)
{
return std::atan2(x * y, std::sqrt(x * x + y * y + 1.0));
}
float CalcArea(const float& u_, const float& v_, const int& width,
const int& height)
{
// transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
// ( 0.5 is for texel center addressing)
float u = (2.0 * (u_ + 0.5) / width) - 1.0;
float v = (2.0 * (v_ + 0.5) / height) - 1.0;
// shift from a demi texel, mean 1.0 / size with u and v in [-1..1]
float invResolutionW = 1.0 / width;
float invResolutionH = 1.0 / height;
// u and v are the -1..1 texture coordinate on the current face.
// get projected area for this texel
float x0 = u - invResolutionW;
float y0 = v - invResolutionH;
float x1 = u + invResolutionW;
float y1 = v + invResolutionH;
float angle = CalcPreArea(x0, y0) - CalcPreArea(x0, y1) -
CalcPreArea(x1, y0) + CalcPreArea(x1, y1);
return angle;
}
template <size_t SHOrder>
std::vector<Eigen::Array3f> PrecomputeCubemapSH(const std::vector<std::unique_ptr<float[]>>& images,
const int& width, const int& height,
const int& channel)
{
std::vector<Eigen::Vector3f> cubemapDirs;
cubemapDirs.reserve(6 * width * height);
for (int i = 0; i < 6; i++)
{
Eigen::Vector3f faceDirX = cubemapFaceDirections[i][0];
Eigen::Vector3f faceDirY = cubemapFaceDirections[i][1];
Eigen::Vector3f faceDirZ = cubemapFaceDirections[i][2];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
float u = 2 * ((x + 0.5) / width) - 1;
float v = 2 * ((y + 0.5) / height) - 1;
Eigen::Vector3f dir = (faceDirX * u + faceDirY * v + faceDirZ).normalized();
cubemapDirs.push_back(dir);
}
}
}
constexpr int SHNum = (SHOrder + 1) * (SHOrder + 1);
constexpr int Order = SHOrder;
cout << "SHNum:" << SHNum << endl;
cout << "Order:" << Order << endl;
std::vector<Eigen::Array3f> SHCoeffiecents(SHNum);
for (int i = 0; i < SHNum; i++)
SHCoeffiecents[i] = Eigen::Array3f(0);
float sumWeight = 0;
//float u_ = 0, v_ = 0;
int bindex = 0;
for (int i = 0; i < 6; i++)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// TODO: here you need to compute light sh of each face of cubemap of each pixel
// TODO: 此处你需要计算每个像素下cubemap某个面的球谐系数
Eigen::Vector3f dir = cubemapDirs[i * width * height + y * width + x];
int index = (y * width + x) * channel;
Eigen::Array3f Le(images[i][index + 0], images[i][index + 1],
images[i][index + 2]);
//Le[0] = 0.5f; Le[1] = 0.5f; Le[2] = 0.5f;//for test
// 从这里开始
//u_ = x / (float)width;
//v_ = y / (float)height;
float area = CalcArea(x, y, width, height);
// 对于每个基函数
// L^2 + M+L+1 = index
bindex = 0;
for (int L = 0; L <= Order; L++) {
//cout << "l:" << L << endl;
for (int M = -L; M <= L; M++) {
//cout << "m:" << M << endl;
//cout << "bindex:" << bindex << endl;
//if (bindex < SHNum)
{
// 对于每个颜色
Eigen::Vector3d dird = Eigen::Vector3d(dir.x(), dir.y(), dir.z());
dird.normalize();
float sh = sh::EvalSH(L, M, dird) * area;
SHCoeffiecents[bindex][0] += Le[0] * sh;
SHCoeffiecents[bindex][1] += Le[1] * sh;
SHCoeffiecents[bindex][2] += Le[2] * sh;
}
bindex++;
}
}
}
}
}
return SHCoeffiecents;
}
结果:
light.txt
三列分别对应r,g,b的9个基函数系数数据。
2.离线计算光传播并保存
T有9个分量,对应于分别使用2阶球谐函数的9个基函数作为光照,在式子中的积分结果。
code:
std::unique_ptr<std::vector<double>> ProjectFunction(
int order, const SphericalFunction& func, int sample_count) {
CHECK(order >= 0, "Order must be at least zero.");
CHECK(sample_count > 0, "Sample count must be at least one.");
// This is the approach demonstrated in [1] and is useful for arbitrary
// functions on the sphere that are represented analytically.
const int sample_side = static_cast<int>(floor(sqrt(sample_count)));
std::unique_ptr<std::vector<double>> coeffs(new std::vector<double>());
coeffs->assign(GetCoefficientCount(order), 0.0);
// generate sample_side^2 uniformly and stratified samples over the sphere
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> rng(0.0, 1.0);
for (int t = 0; t < sample_side; t++) {
for (int p = 0; p < sample_side; p++) {
double alpha = (t + rng(gen)) / sample_side;
double beta = (p + rng(gen)) / sample_side;
// See http://www.bogotobogo.com/Algorithms/uniform_distribution_sphere.php
double phi = 2.0 * M_PI * beta;
double theta = acos(2.0 * alpha - 1.0);
// evaluate the analytic function for the current spherical coords
double func_value = func(phi, theta);
// evaluate the SH basis functions up to band O, scale them by the
// function's value and accumulate them over all generated samples
for (int l = 0; l <= order; l++) {
for (int m = -l; m <= l; m++) {
double sh = EvalSH(l, m, phi, theta);
(*coeffs)[GetIndex(l, m)] += func_value * sh;
}
}
}
}
// scale by the probability of a particular sample, which is
// 4pi/sample_side^2. 4pi for the surface area of a unit sphere, and
// 1/sample_side^2 for the number of samples drawn uniformly.
double weight = 4.0 * M_PI / (sample_side * sample_side);
for (unsigned int i = 0; i < coeffs->size(); i++) {
(*coeffs)[i] *= weight;
}
return coeffs;
}
for (int i = 0; i < mesh->getVertexCount(); i++)
{
const Point3f& v = mesh->getVertexPositions().col(i);
const Normal3f& n = mesh->getVertexNormals().col(i);
double dot = 0.0f;
double* dd = ˙
bool intersect = false;
auto shFunc = [&](double phi, double theta) -> double {
Eigen::Array3d d = sh::ToVector(phi, theta);
const auto wi = Vector3f(d.x(), d.y(), d.z());
if (m_Type == Type::Unshadowed)
{
cout << "Unshadowed" << endl;
// TODO: here you need to calculate unshadowed transport term of a given direction
// TODO: 此处你需要计算给定方向下的unshadowed传输项球谐函数值
//*dd = 0.1f;//有奇怪的编译器优化
//cout << "dd:" << *dd << endl;
*dd = wi.x() * n.x() + wi.y() * n.y() + wi.z() * n.z();// wi.dot(n);
//cout << "dd:" << *dd << endl;
//cout << "wi:" << wi.x()<<" "<<wi.y()<<" " << wi.z() << endl;
//cout << "n:" << n.x() << " " << n.y() << " " << n.z() << endl;
if (*dd > 0) {
//cout << "dd:" << *dd << endl;
return *dd;
}
return 0;
}
else
{
// TODO: here you need to calculate shadowed transport term of a given direction
// TODO: 此处你需要计算给定方向下的shadowed传输项球谐函数值
cout << "Shadowed" << endl;
intersect = (*scene).rayIntersect(Ray3f(v, wi));
//cout << "intersect:"<< intersect << endl;
if (!intersect) {
*dd = wi.x() * n.x() + wi.y() * n.y() + wi.z() * n.z();// wi.dot(n);
//cout << "dd:" << *dd << endl;
//cout << "wi:" << wi.x()<<" "<<wi.y()<<" " << wi.z() << endl;
//cout << "n:" << n.x() << " " << n.y() << " " << n.z() << endl;
if (*dd > 0) {
//cout << "dd:" << *dd << endl;
return *dd;
}
}
return 0;
}
return 0;
};
auto shCoeff = sh::ProjectFunction(SHOrder, shFunc, m_SampleCount);
for (int j = 0; j < shCoeff->size(); j++)
{
m_TransportSHCoeffs.col(i).coeffRef(j) = (*shCoeff)[j];
}
}
result: transport.txt
行数为模型顶点数,行数据为每个顶点处的对应每个基函数光照的积分结果。
Runtime Render
code:
在顶点着色器中计算:
attribute mat3 aPrecomputeLT;
attribute vec3 aVertexPosition;
attribute vec3 aNormalPosition;
attribute vec2 aTextureCoord;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
uniform float uPrecomputeL[27];
varying highp vec2 vTextureCoord;
varying highp vec3 vFragPos;
varying highp vec3 vNormal;
varying highp vec3 vColor;
void main(void) {
vFragPos = (uModelMatrix * vec4(aVertexPosition, 1.0)).xyz;
vNormal = (uModelMatrix * vec4(aNormalPosition, 0.0)).xyz;
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix *
vec4(aVertexPosition, 1.0);
vTextureCoord = aTextureCoord;
float r = 0.0;
float g = 0.0;
float b = 0.0;
int index = 0;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
r += aPrecomputeLT[i][j]*uPrecomputeL[index];
g += aPrecomputeLT[i][j]*uPrecomputeL[index+1];
b += aPrecomputeLT[i][j]*uPrecomputeL[index+2];
index +=3;
}
}
//r=0.0;g=0.0;b=0.0;
//r = uPrecomputeL[26];
//r = aPrecomputeLT[0][0];
vColor = vec3(r,g,b);
}
其中uPrecomputeL是长度为3*9的float数组,对应于light.txt的数据。
aPrecompute是3x3矩阵,存储了transport.txt对应顶点的9个数据。
result:
优点:运行时计算非常简单和快速
缺点:
- 物体位置旋转不能发生变化,否则光传播发生改变
- 在shadow和inter-reflection case中考虑了物体的自遮挡和内部反射,但不会考虑物体之间的遮挡与反射,可以认为是一种局部(local)光照方法。
PRT预计算辐射传输方法的更多相关文章
- 基于预计算的全局光照(Global Illumination Based On Precomputation)
目录 基于图像的光照(Image Based Lighting,IBL) The Split Sum Approximation 过滤环境贴图 预计算BRDF积分 预计算辐射度传输(Precomput ...
- Session id实现通过Cookie来传输方法及代码参考
1. Web中的Session指的就是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间.因此从上述的定义中我们可以看到,Session实际上是一个特定的 ...
- Apache启用GZIP压缩网页传输方法
一.gzip介绍 Gzip是一种流行的文件压缩算法,如今的应用十分广泛,尤其是在Linux平台.当应用Gzip压缩到一个纯文本文件时,效果是很明显的,大约能够降低70%以上的文件大小.这取决于文件里的 ...
- Unity预计算光照的学习(速度优化,LightProb,LPPV)
1.前言 写这篇文章一方面是因为unity的微博最近出了关于预计算光照相关的翻译文章,另一方面一些美术朋友一直在抱怨烘培速度慢 所以抱着好奇的心态来学习一下unity5的PRGI预计算实时光照 2.基 ...
- 数据挖掘概念与技术15--为快速高维OLAP预计算壳片段
1. 论数据立方体预计算的多种策略的优弊 (1)计算完全立方体:需要耗费大量的存储空间和不切实际的计算时间. (2)计算冰山立方体:优于计算完全立方体,但在某种情况下,依然需要大量的存储空间和计算时间 ...
- Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)
1.基本参数与使用 1.1 常规介绍 使用预计算光照需要在Window/Lighting面板下找到预计算光照选项,保持勾选预计算光照并保证场景中有一个光照静态的物体 此时在编辑器内构建后,预计算光照开 ...
- Unity Lighting - The Precompute Process 预计算过程(二)
The Precompute Process 预计算过程 In Unity, precomputed lighting is calculated in the background - eith ...
- python tcp黏包和struct模块解决方法,大文件传输方法及MD5校验
一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...
- [RK3288][Android6.0] 调试笔记 --- 测试I2C设备正常传输方法【转】
本文转载自:http://blog.csdn.net/kris_fei/article/details/71515020 Platform: RockchipOS: Android 6.0Kernel ...
- tcp协议传输方法&粘包问题
socket实现客户端和服务端 tcp协议可以用socket模块实现服务端可客户端的交互 # 服务端 import socket #生成一个socket对象 soc = socket.socket(s ...
随机推荐
- 使用MySQL实现分布式锁
分布式锁开发中经常使用,在项目多节点部署或者微服务项目中,JAVA提供的线程锁已经不能满足安全的需求,需要使用全局的分布式锁来保证安全:分布式锁的实现的方式有很多种,最常见的有zookeeper,Re ...
- 亚马逊 vpc 子网 路由表 互联网网关 弹性ip
创建vpc,子网,路由表,互联网网关,弹性ip等网络资源 vpc和子网 创建互联网网关 附加到vpc 创建路由表 路由表编辑路由 此路由通过这个网关出去 编辑子网关联 保存关联 有关云主机 创建属于那 ...
- 【Hearts Of Iron IV】钢铁雄心4 安装笔记
一.解决Steam购买游戏和下载问题 我可能是正版受害者 Steam平台这个游戏本体是购买锁国区的 然后在淘宝上面买激活码激活的 都激活过了的Key,所以放出来也无所谓了 钢铁雄心4学员版本体:7B0 ...
- 【GPU】如何两周内零经验手搓一个GPU | 美国工程师极限挑战 | 重写三次 | CUDA | SIMD | ISA指令集 | Verilog | OpenLane
地址: https://www.youtube.com/watch?v=FTh-c2ek6PU
- 祝贺小鹏汽车Gallardot同学成为Apache DolphinScheduler Committer!
社区迎来新committer!这次是来自小鹏汽车的Gallardot,看看他与Apache DolphinScheduler社区的故事吧. 对话社区 Q1:您为Apache DolphinSchedu ...
- 零基础学习人工智能—Python—Pytorch学习(五)
前言 上文有一些文字打错了,已经进行了修正. 本文主要介绍训练模型和使用模型预测数据,本文使用了一些numpy与tensor的转换,忘记的可以第二课的基础一起看. 线性回归 结合numpy使用 首先使 ...
- Linux 上的 AppImage、Snap、Flatpak 之间的区别和联系
AppImage.Snap 和 Flatpak 是三种用于在 Linux 系统上分发和安装软件的包管理格式.它们都有助于解决软件依赖问题,使得应用程序可以在不同的 Linux 发行版上更容易地安装和运 ...
- Apache HTTP Server 使用
安装 macOS: brew install apache2 Ubuntu: sudo apt install apache2 使用 配置文件路径: macOS: /opt/homebrew/etc/ ...
- Windows 不小心把管理员帐户弄没了怎么办
今天折腾不小心把管理员帐号的权限给改没了,重启之后很多操作做不了.解决方法如下: Windows + R 打开运行,或者打开资源管理器,输入 control userpasswords2 命令打开用户 ...
- Linux 内核相关命令
Shell 命令: ipcs # 查看共享内存 dmesg # 显示内核消息 sudo dmesg -c # 清空内核消息 sudo mknod /dev/rwbuf c 60 0 sudo insm ...