我采用的编译方案是
cmake 3.22.2-windows.msi
然后vs2019 使用release模式编译
产生报错
prt\src\prt.cpp(219): error C4716:
“::operator()”: 必须返回一个值
论坛大神给出解决方案
MSVC 对于代码中的中文字符支持有问题(应该是会吞换行符),需要启用 utf-8 编译选项:
在 prt/CMakeLists.txt 112 行添加: target_compile_options(nori PUBLIC
/utf-8) # MSVC unicode support
Win
nori.exe path/to/scene.xml
预计算环境光贴图的球面谐波系数。
Cubemap形式的环境贴图。
这一步是使用球谐函数来表示环境光。
已知:
环境贴图,是一个图像空间,每个像素定义了一个方向向量,使得整张贴图所有像素定义的方向构成一个球面,f(ω) 就是这个方向上像素的RGB值,
我们是想将环境贴图用 SH 进行编码,其中环境贴图的每个像素都定义了一个方向向量,等同于 ω \omegaω,整张贴图的所有像素定义的方向就构成了一个球面函数,它的 f ( ω ) f(\omega)f(ω) 通过每个像素决定。所以,根据上述黎曼积分,我们已知每个像素的 RGB值(f ( ω ) f(\omega)f(ω)),方向向量(用于构建 basic function)
————————————————
版权声明:本文为CSDN博主「CCCCCCros____」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36242312/article/details/118490949
SHCoeffiecents = [];
for pixel in Env(环境贴图):
// 获取该像素的方向向量
dir = getDir(pixel);
light = getLight(pixel);
// 将方向向量投影至basic function
for band in bands:
basicFunction = getBasicFunction(band, dir);
SHCoeffiecents[band] += light * basicFunction * dw;
end
方向向量的增量明明是三维的,为什么可以用一个float表示
这里的语义已经转化为了立体角
立体角是一个值,一个球面的立体角一共有4pi,所以通过每个立体角都可以唯一找到一个球面的法线方向与之对应。
锥体的立体角大小定义为,以锥体的顶点为球心作球面,该锥体在球表面截取的面积与球半径平方之比,单位为球面度。
沿用百度百科的立体角定义,立体角是面积与球半径平方之比
而此时函数返回的是投影到单位球面的面积,单位球面半径是1,所以这个比值的结果还是面积的值。
.auto shCoeff = sh::ProjectFunction(SHOrder, shFunc, m_SampleCount);
我找到了一个SH基函数生成器,可能是
所以先把代码写上,再比对一下看看
auto angle = CalcArea(x, y, width, height);
for (int band = 0;band < SHOrder;band++)
{
auto shCoeff = sh::ProjectFunction(band, shFunc, dir);
SHCoeffiecents[band] += Le * shCoeff * angle;
}
// 像素所代表的矩形区域投影到单位球面的面积
float wOmege = CalcArea(x, y, width, height);
// 投影
for (int l = 0; l <= SHOrder; l++)
{
for (int m = -l; m <= l; m++)
{
int k = sh::GetIndex(l, m);
double basisFunc = sh::EvalSH(l, m, dir.cast<double>().normalized());
// 对于 cubemap 中的每一处光线,都去累加它们把灯光 Le,以 wOmege面积投影在 basisFunc 上的系数
// 得到的结果一系列近似了环境光球面的 SH 系数
SHCoeffiecents[k] += Le * wOmege * basisFunc;
}
}
————————————————
版权声明:本文为CSDN博主「CCCCCCros____」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36242312/article/details/118490949
对比之后确实发现了我的问题
首先我们根据 SHorder = 2 知道,我们用的是二阶球面谐波函数
第零阶球面谐波函数是有1个系数
第一阶球面谐波函数有3个系数
第二阶球面谐波函数有5个系数
一共9个系数需要计算,但是我的函数只算了2个系数,是有问题的
auto angle = CalcArea(x, y, width, height);
for (int l = 0; l <= SHOrder; l++)
{
for (int m = -l; m <= l; m++)
{
int k = sh::GetIndex(l, m);
double basisFunc = sh::EvalSH(l, m, dir.cast<double>().normalized());
SHCoeffiecents[k] += Le * basisFunc * angle;
}
}
在第一次修正之后,我的代码变成了这样
正确了
上一步做的预计算是给lighting做SH投影,
因为整个环境光Environment Map 描述的是一个二维函数,这个函数投影到每个基函数上对应一个值,不是投影到每个像素上。
计算的时候要对每个方向进行采样,算力消耗较大,要放到预计算处理。
由球谐函数的性质,我们可以将积分项内的光照辐射度和传输函数项分离开了,
光照函数的球谐系数由前一节计算出来的球谐系数提供,而本节将要计算每个 顶点 的传输项球谐系数。
注意重点,这里是顶点,我们计算的light transport是基于顶点的,而lighting环境光照是基于空间方向的。
这一步是计算light transport的SH,
对于漫反射传输项来说,分为 unshadowed, shadowed, interreflection 三种情况,我们将分别计算这三种情况的漫反射传输球谐系数。
BRDF(漫反射项) * Lighting * cos项(高光项)
对于表面处处相等的漫反射表面,BRDF项取消。
对于最简单的 Unshadowed 情况,我们需要的仅仅是将几何项
投影到球谐系数里
也就是 dot(射线采样的vec3,法线)
对于上半球的射线,投影到SH space
系数 += dot(射线采样的vec3,法线) * SH基函数
一开始没有看懂C++11的lambda函数,以为这里还在循环,return 0就退出循环了
看了代码才知道 这么简单,循环执行lambda,每个lambda返回不同的值就可以了
double H = wi.dot(n);
if (m_Type == Type::Unshadowed)
{
// TODO: here you need to calculate unshadowed transport term of a given direction
// TODO: 此处你需要计算给定方向下的unshadowed传输项球谐函数值
if (H > 0.0)
return H;
return 0;
}
else
{
// TODO: here you need to calculate shadowed transport term of a given direction
// TODO: 此处你需要计算给定方向下的shadowed传输项球谐函数值
return 0;
}
计算出H项 也就是几何项、cos项
对于有自阴影的 Shadowed 漫反射传输,预计算方程多加了一项可见性(Visibility term)
double H = wi.dot(n);
if (m_Type == Type::Unshadowed)
{
// TODO: here you need to calculate unshadowed transport term of a given direction
// TODO: 此处你需要计算给定方向下的unshadowed传输项球谐函数值
if (H > 0.0)
return H;
return 0;
}
else
{
// TODO: here you need to calculate shadowed transport term of a given direction
// TODO: 此处你需要计算给定方向下的shadowed传输项球谐函数值
Ray3f ray(v, wi.normalized());
if (H > 0 && !scene->rayIntersect(ray))
return H;
return 0;
}
判断当前pos 发出的射线没有跟其他东西相交
也就是看光是否照射到这个点了,如果照射到就有贡献,否则没有
Lds就是 Diffuse Shadowed 和 Diffuse Unshadowed项
这里我们采用Diffuse Shadowed项,考虑自遮挡
发射光线用之前的采样方案,
如果当前光线与其他三角形相交,(最好优化一下,每次相交都记录一下,只保存第一个相交的位置)
Intersection its;
if (!scene->rayIntersect(ray, its))
return Color3f(0.0f);
用这个函数来获取一个交点
its.tri_index.x()
its.tri_index.y()
its.tri_index.z()
const Vector3f &bary = its.bary;
一个交点中包含的数据
• Intersection::tri_index 记录了当前交点的三个三角形顶点的序号
• Intersection::bary 记录了当前交点的重心坐标
两次bounce的伪代码
for each 采样光照方向1 from each vertex
与场景求交
如果有交点 Intersection 1
进入 bounce 1
for each 采样光照方向2 from Intersection 1
与场景求交
如果有交点 Intersection 2 (不是初始vertex)
进入 bounce 2
for each 采样光照方向3 from Intersection 2
与场景求交
如果有交点 Intersection 3
返回 0
如果没有交点
返回 (初始vertex法线 和 采样光照方向1)的cos项 * (Intersection 1 和 采样光照方向2)的cos项 * (Intersection 2 和 采样光照方向3)的cos项
如果没有交点
返回 (初始vertex法线 和 采样光照方向1)的cos项 * (Intersection 1 和 采样光照方向2)的cos项
如果没有交点
返回 0
我实现的时候只实现一个bounce
if (m_Type == Type::Interreflection)
{
// TODO: leave for bonus
// from each vertex
for (int i = 0; i < mesh->getVertexCount(); i++)
{
const Point3f& v = mesh->getVertexPositions().col(i);
const Normal3f& n = mesh->getVertexNormals().col(i);
auto shFunc = [&](double phi, double theta) -> double {
Eigen::Array3d d = sh::ToVector(phi, theta);
const auto wi = Vector3f(d.x(), d.y(), d.z());
double H = wi.dot(n);
Ray3f ray(v, wi.normalized());
// 与场景求交
Intersection its;
if (H > 0.0 && scene->rayIntersect(ray, its))
{
// 进入bounce 1
auto vertex11 = mesh->getVertexNormals().col(its.tri_index.x());
auto vertex12 = mesh->getVertexNormals().col(its.tri_index.y());
auto vertex13 = mesh->getVertexNormals().col(its.tri_index.z());
auto normal11 = mesh->getVertexNormals().col(its.tri_index.x());
auto normal12 = mesh->getVertexNormals().col(its.tri_index.y());
auto normal13 = mesh->getVertexNormals().col(its.tri_index.z());
const Vector3f &bary1 = its.bary;
auto vertex1 = vertex11 * bary1.x() + vertex12 * bary1.y() + vertex13 * bary1.z();
auto shFunc1 = [&](double phi, double theta) -> double {
Eigen::Array3d d1 = sh::ToVector(phi, theta);
const auto wi1 = Vector3f(d1.x(), d1.y(), d1.z());
auto H11 = wi1.dot(normal11);
auto H12 = wi1.dot(normal12);
auto H13 = wi1.dot(normal13);
double H1 = H11 * bary1.x() + H12 * bary1.y() + H13 * bary1.z();
Ray3f ray1(vertex1, wi1.normalized());
// 与场景求交
Intersection its;
if (H > 0.0 && scene->rayIntersect(ray1, its))
{
return 0;
}
return H1;
};
// for each 采样光照方向2
auto shCoeff1 = sh::ProjectFunction(SHOrder, shFunc1, m_SampleCount);
double Bounce1_H = 0.0;
for (int j = 0; j < shCoeff1->size(); j++)
{
Bounce1_H += (*shCoeff1)[j];
}
return H * Bounce1_H;
}
return 0;
};
// for each 采样光照方向1
auto shCoeff = sh::ProjectFunction(SHOrder, shFunc, m_SampleCount);
for (int j = 0; j < shCoeff->size(); j++)
{
m_TransportSHCoeffs.col(i).coeffRef(j) += (*shCoeff)[j];
}
}
}
将预计算的 light.txt 以及 transport.txt 放入对应的 assets/cubemap/贴图文件夹/下
engine.js 中的 88-114 行 117 -118行 解除注释
解析后的数据储存在了两个全局变量 precomputeL、precomputeLT 。
precomputeL 保存长度为 SH系数数量 的array ,每个元素都是代表着 RGB 的三维数组
precomputeLT 保存长度为 VertexCount ∗ SH系数数量 的array , 每个元素应该是个double吧
创建 src/materials/PRTMaterial.js
class PRTMaterial extends Material {
constructor(vertexShader, fragmentShader) {
super({
'uPrecomputeLR': { type: 'updatedInRealTime', value: null },
'uPrecomputeLG': { type: 'updatedInRealTime', value: null },
'uPrecomputeLB': { type: 'updatedInRealTime', value: null },
}, ['aPrecomputeLT'], vertexShader, fragmentShader, null);
}
}
async function buildPRTMaterial(vertexPath, fragmentPath) {
let vertexShader = await getShaderString(vertexPath);
let fragmentShader = await getShaderString(fragmentPath);
return new PRTMaterial(vertexShader, fragmentShader);
}
uPrecomputeLR、uPrecomputeLG、uPrecomputeLB 在更新时传入,放在 WebGLRenderer.js 即可
WebGLRenderer.js 在62行插入
if (k == 'uPrecomputeLR') {
gl.uniformMatrix3fv(
this.meshes[i].shader.program.uniforms[k],
false,
precomputeL_RGBMat3[0]);
}
if (k == 'uPrecomputeLG') {
gl.uniformMatrix3fv(
this.meshes[i].shader.program.uniforms[k],
false,
precomputeL_RGBMat3[1]);
}
if (k == 'uPrecomputeLB') {
gl.uniformMatrix3fv(
this.meshes[i].shader.program.uniforms[k],
false,
precomputeL_RGBMat3[2]);
}
aPrecomputeLT 是通过 MeshRender.js 里的 draw() 传入:
208行已有
for (var ii = 0; ii < 3; ++ii) {
gl.enableVertexAttribArray(this.shader.program.attribs['aPrecomputeLT'] + ii);
// void gl.vertexAttribPointer(index, size, type, normalized, stride, offset);
gl.vertexAttribPointer(this.shader.program.attribs['aPrecomputeLT'] + ii, 3, gl.FLOAT, false, 36, ii * 12);
}
其中步长为 36 ,因为每个顶点的漫反射传输项的 SH 系数是 9 个 float, 4x9=36; offset 为 ii * 12,因为在 shader 里, aVertexPosition 包含 3个float,相比 aPrecomputeLT 先传入
创建好了后就可以在 loadOBJ.js 里加载对应的材质了
61行插入
case 'PRTMaterial':
material = buildPRTMaterial("./src/shaders/prtShader/prtVertex.glsl", "./src/shaders/prtShader/prtFragment.glsl");
break;
}
src/shaders/prtShader/prtVertex.glsl
attribute vec3 aVertexPosition;
attribute mat3 aPrecomputeLT;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
varying highp mat3 vPrecomputeLT;
void main(void) {
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
// 这里的 aPrecomputeLT 实际上已经是每个顶点的漫反射传输项了,因为shader里是针对每个顶点进行并行计算
vPrecomputeLT = aPrecomputeLT;
}
src/shaders/prtShader/prtFragment.glsl
#ifdef GL_ES
precision mediump float;
#endif
uniform mat3 uPrecomputeLR;
uniform mat3 uPrecomputeLG;
uniform mat3 uPrecomputeLB;
varying highp mat3 vPrecomputeLT;
vec3 SHrecon(mat3 uPrecomputeLR, mat3 uPrecomputeLG, mat3 uPrecomputeLB, mat3 vPrecomputeLT){
vec3 result = vec3(uPrecomputeLR[0][0], uPrecomputeLG[0][0], uPrecomputeLB[0][0]) * vPrecomputeLT[0][0] +
vec3(uPrecomputeLR[0][1], uPrecomputeLG[0][1], uPrecomputeLB[0][1]) * vPrecomputeLT[0][1] +
vec3(uPrecomputeLR[0][2], uPrecomputeLG[0][2], uPrecomputeLB[0][2]) * vPrecomputeLT[0][2] +
vec3(uPrecomputeLR[1][0], uPrecomputeLG[1][0], uPrecomputeLB[1][0]) * vPrecomputeLT[1][0] +
vec3(uPrecomputeLR[1][1], uPrecomputeLG[1][1], uPrecomputeLB[1][1]) * vPrecomputeLT[1][1] +
vec3(uPrecomputeLR[1][2], uPrecomputeLG[1][2], uPrecomputeLB[1][2]) * vPrecomputeLT[1][2] +
vec3(uPrecomputeLR[2][0], uPrecomputeLG[2][0], uPrecomputeLB[2][0]) * vPrecomputeLT[2][0] +
vec3(uPrecomputeLR[2][1], uPrecomputeLG[2][1], uPrecomputeLB[2][1]) * vPrecomputeLT[2][1] +
vec3(uPrecomputeLR[2][2], uPrecomputeLG[2][2], uPrecomputeLB[2][2]) * vPrecomputeLT[2][2];
return result;
}
void main(void){
vec3 result = SHrecon(uPrecomputeLR, uPrecomputeLG, uPrecomputeLB, vPrecomputeLT);
gl_FragColor = vec4(result,1);
}
uPrecomputeLR其实是SH的9个系数,应该是9维向量,但是用mat3 三维矩阵来存储,所以要构造对应的矩阵传递到fragment shader