GAMES 202 作业2

第一步 编译

在这里插入图片描述

我采用的编译方案是
cmake 3.22.2-windows.msi
然后vs2019 使用release模式编译

产生报错

prt\src\prt.cpp(219): error C4716:
“<lambda_9ed74708f63acbd4deb1a7dc36ea3ac3>::operator()”: 必须返回一个值

论坛大神给出解决方案

MSVC 对于代码中的中文字符支持有问题(应该是会吞换行符),需要启用 utf-8 编译选项:

在 prt/CMakeLists.txt 112 行添加: target_compile_options(nori PUBLIC
/utf-8) # MSVC unicode support

加上之后,果然解决了
在这里插入图片描述

在这里插入图片描述
在生成目录找到 nori.exe
编译部分完成

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

为什么使用CalcArea产生的float值作为 dwi

方向向量的增量明明是三维的,为什么可以用一个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 三种情况,我们将分别计算这三种情况的漫反射传输球谐系数。

Diffuse Unshadowed

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项

Diffuse Shadowed

对于有自阴影的 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 发出的射线没有跟其他东西相交
也就是看光是否照射到这个点了,如果照射到就有贡献,否则没有

Diffuse Inter-reflection(bonus)

对于具有相互反射的传输项情况,我们需要考虑光线的多次弹射
在这里插入图片描述
在这里插入图片描述

  1. Lds就是 Diffuse Shadowed 和 Diffuse Unshadowed项
    这里我们采用Diffuse Shadowed项,考虑自遮挡

  2. 发射光线用之前的采样方案,
    如果当前光线与其他三角形相交,(最好优化一下,每次相交都记录一下,只保存第一个相交的位置)

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

一个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);
}

第六步 修正fragment参数

uPrecomputeLR其实是SH的9个系数,应该是9维向量,但是用mat3 三维矩阵来存储,所以要构造对应的矩阵传递到fragment shader

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值