PRT(Precomputed Radiance Transfer)技术是一种用于实时渲染全局光照的方法。它通过预计算光照传输来节省时间,并能够实时重现面积光源下3D模型的全局光照效果。

由于PRT方法的局限,它不能计算随机动态场景的全局光照,场景中物体也不可变动。

Basic Idea

  1. 光的传输与背景光照内容本身无关,因此两部分拆开计算。
  2. 环境光照使用球谐函数拟合,来近似表示

Diffuse Case

对于完全粗糙表面,brdf系数是一个常量,因此:

Precompute

1.球谐函数表达环境光照,离线计算和保存

公式

\[l_i = \int_{\Omega}{L(\omega)B_i(\omega)d\omega}
\]

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 = &dot;
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:

优点:运行时计算非常简单和快速

缺点

  1. 物体位置旋转不能发生变化,否则光传播发生改变
  2. 在shadow和inter-reflection case中考虑了物体的自遮挡和内部反射,但不会考虑物体之间的遮挡与反射,可以认为是一种局部(local)光照方法。

PRT预计算辐射传输方法的更多相关文章

  1. 基于预计算的全局光照(Global Illumination Based On Precomputation)

    目录 基于图像的光照(Image Based Lighting,IBL) The Split Sum Approximation 过滤环境贴图 预计算BRDF积分 预计算辐射度传输(Precomput ...

  2. Session id实现通过Cookie来传输方法及代码参考

    1. Web中的Session指的就是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间.因此从上述的定义中我们可以看到,Session实际上是一个特定的 ...

  3. Apache启用GZIP压缩网页传输方法

    一.gzip介绍 Gzip是一种流行的文件压缩算法,如今的应用十分广泛,尤其是在Linux平台.当应用Gzip压缩到一个纯文本文件时,效果是很明显的,大约能够降低70%以上的文件大小.这取决于文件里的 ...

  4. Unity预计算光照的学习(速度优化,LightProb,LPPV)

    1.前言 写这篇文章一方面是因为unity的微博最近出了关于预计算光照相关的翻译文章,另一方面一些美术朋友一直在抱怨烘培速度慢 所以抱着好奇的心态来学习一下unity5的PRGI预计算实时光照 2.基 ...

  5. 数据挖掘概念与技术15--为快速高维OLAP预计算壳片段

    1. 论数据立方体预计算的多种策略的优弊 (1)计算完全立方体:需要耗费大量的存储空间和不切实际的计算时间. (2)计算冰山立方体:优于计算完全立方体,但在某种情况下,依然需要大量的存储空间和计算时间 ...

  6. Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)

    1.基本参数与使用 1.1 常规介绍 使用预计算光照需要在Window/Lighting面板下找到预计算光照选项,保持勾选预计算光照并保证场景中有一个光照静态的物体 此时在编辑器内构建后,预计算光照开 ...

  7. Unity Lighting - The Precompute Process 预计算过程(二)

      The Precompute Process 预计算过程 In Unity, precomputed lighting is calculated in the background - eith ...

  8. python tcp黏包和struct模块解决方法,大文件传输方法及MD5校验

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

  9. [RK3288][Android6.0] 调试笔记 --- 测试I2C设备正常传输方法【转】

    本文转载自:http://blog.csdn.net/kris_fei/article/details/71515020 Platform: RockchipOS: Android 6.0Kernel ...

  10. tcp协议传输方法&粘包问题

    socket实现客户端和服务端 tcp协议可以用socket模块实现服务端可客户端的交互 # 服务端 import socket #生成一个socket对象 soc = socket.socket(s ...

随机推荐

  1. 浅谈 MySQL 连表查询

    浅谈 MySQL 连表查询 连表查询是一把双刃剑, 优点是适应范式, 减少数据冗余; 缺点是连表查询特别是多张表的连表会增加数据库的负担, 降低查询效率. 简介 连表查询就是 2 张表或者多张表的联合 ...

  2. 关于SpringCloud Bus RemoteApplicationEvent 使用注意事项

    最近使用SpringCloud Bus 用于服务直接消息通信,遇到一些问题,记录下来给一样碰到问题的你一个解决方案 开发环境 : springboot 2.3.9.RELEASE spring-clo ...

  3. "基础模型时代的机器人技术" —— Robotics in the Era of Foundation Models

    翻译: 2023年是智能机器人规模化的重要一年!对于机器人领域之外的人来说,要传达事物变化的速度和程度是有些棘手的.与仅仅12个月前的情况相比,如今人工智能+机器人领域的大部分景观似乎完全不可识别.从 ...

  4. 如何访问SCI-Hub上的资源?

    答案: 使用tor访问.onion网络资源. tor 下载地址: https://www.torproject.org/ 如果不使用tor方式访问可能会无法访问,被提示:

  5. 向日葵的平替:一款个人开发的远程工具——fastnat

    相关资料: https://www.cnblogs.com/thinkingmore/p/14317505.html https://www.cnblogs.com/thinkingmore/p/15 ...

  6. CPU利用率为多少时可以兼顾计算效率和时间效率?—— 75% —— 科学计算时如何正确的使用超线程CPU——使用超线程CPU进行计算密集型任务时的注意事项

    2023年12月28日  更新 现在这个AI火热的时代科学计算任务占比越来越大,但是平时使用时也有一些不为人注意的地方需要知道,本文就讨论一下使用超线程CPU时的注意事项. 超线程CPU就是现在的多线 ...

  7. python代码实现将PDF文件转为文本及其对应的音频

    代码地址: https://github.com/TiffinTech/python-pdf-audo ============================================ imp ...

  8. [POI2015] MOD 题解

    前言 题目链接:洛谷. 题意简述 给定一棵树,求断掉一条边再连上一条边所得的新树直径最小值和最大值,以及相应方案(你可以不进行任何操作,即断掉并连上同一条边). 题目分析 假设我们枚举断掉某一条边,得 ...

  9. condition字符串匹配问题

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. fs使用dialplan配置文件执行业务流程,condition条件变量的配置是必然会使用的,这里记录一次配置过程中的错误示范. 环 ...

  10. Kotlin 面向对象编程 (OOP) 基础:类、对象与继承详解

    什么是面向对象编程 (OOP)? OOP 代表面向对象编程. 过程式编程是编写执行数据操作的过程或方法,而面向对象编程则是创建包含数据和方法的对象. 与过程式编程相比,面向对象编程具有以下几个优势: ...