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 ...
随机推荐
- 神经网络之卷积篇:详解计算机视觉(Computer vision)
详解计算机视觉 计算机视觉是一个飞速发展的一个领域,这多亏了深度学习.深度学习与计算机视觉可以帮助汽车,查明周围的行人和汽车,并帮助汽车避开它们.还使得人脸识别技术变得更加效率和精准,即将能够体验到或 ...
- Fiddler使用界面介绍-右侧面板
右侧面板是对左侧请求进行解析的面板,点击左侧的请求右侧面板就会出现分析数据 1.Statistics关于HTTP请求的性能 2.Inspectors请求内容,包含请求数据和响应数据 3. AutoRe ...
- pycham配置GitHub环境【一文了解window上GitHub的基本操作】
基础用户设置[包含用户登录.密钥生成] 网络配置 外观->系统设置->https代理->检查连接 我这里测试网址是GitHub,连接成功即可后续操作[不成功别找我,我也不知道] gi ...
- 【Java】Spring5学习
基础环境与文档资料: 见黑马视频: https://www.bilibili.com/video/BV1P44y1N7QG 依赖坐标: <?xml version="1.0" ...
- 【Mybatis】06 Session获取 & 配置参数总结
会话获取 SqlSessionFactory 最佳的获取方式就是使用Mybatis提供的资源类加载配置文件 调用会话工厂建造者实例的建造方法注入读取流 要注意的是建造者生成了了实例就可以不需要了 这里 ...
- 【官网链接】 REPRODUCIBILITY —— pytorch的可复现性
官网链接地址: https://pytorch.org/docs/stable/notes/randomness.html ====================================== ...
- Linux环境变量,知识点汇总
一.什么是环境变量? 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数. 环境变量本质就是一张表,保存在内存当中. 该表在用户登录系统的时候 ...
- linux中级——libcurl库访问百度
libcurl简介: libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议.libcurl同样支持H ...
- 10. 从0学ARM-基于Exynos4412的pwm详解
一.什么是PWM PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码 ...
- WPF 设备输入事件封装
本文主要介绍WPF应用对鼠标输入.触摸屏触笔以及触摸事件的封装 之前有简单说明设备输入类型 WPF 屏幕点击的设备类型 - 唐宋元明清2188 - 博客园 (cnblogs.com) 1.鼠标 - 通 ...