GAMES202-高质量实时渲染(Assignment 2)

目录

  • 作业介绍
  • 环境光贴图预计算
  • 传输项的预计算
    • Diffuse unshadowed
    • Diffuse shadowed
    • Diffuse Inter-reflection(bonus)
  • 实时球谐光照计算

GitHub主页:https://github.com/sdpyy1
作业实现:https://github.com/sdpyy1/CppLearn/tree/main/games202

作业介绍

物体在不同光照下的表现不同,PRT(Precomputed Radiance Transfer) 是一个计算物体在不同光照下表现的方法。光线在一个环境中,会经历反射,折射,散射,甚至还会物体的内部进行散射。为了模拟具有真实感的渲染结果,传统的Path Tracing 方法需要考虑来自各个方向的光线、所有可能的传播形式并且收敛速度极慢。PRT 通过一种预计算方法,该方法在离线渲染的 Path Tracing 工具链中预计算 lighting 以及 light transport 并将它们用球谐函数拟合后储存,这样就将时间开销转移到了离线中。最后通过使用这些预计算好的数据,我们可以轻松达到实时渲染严苛的时间要求,同时渲染结果可以呈现出全局光照的效果。

PRT 方法存在的限制包括:
• 不能计算随机动态场景的全局光照
• 场景中物体不可变动

本次作业的工作主要分为两个部分:cpp 端的离线预计算部分以及在 WebGL框架上使用预计算数据部分

PRT课上最终得出的结论是对渲染方程的计算,可以先把光照和其余部分分别计算球谐展开后系数相乘(针对BRDF是diffuse的情况),所以我们只需要针对光照算球谐展开的系数,然后针对其余部分算一个球谐展开的系数,传递给顶点着色器后相乘就是顶点的着色

环境光贴图预计算

要做的就是把L(wi)项用球谐函数表示,因为球谐函数都一样,不一样的只有系数,所以只需要预计算出系数,系数求法如下,针对球谐函数的任何一项求他的系数都是算一个积分
请添加图片描述
根据作业提示,需要完成函数,输入为天空盒的6个面图片

    std::vector<Eigen::Array3f> PrecomputeCubemapSH(const std::vector<std::unique_ptr<float[]>> &images,const int &width, const int &height,const int &channel){

下面这一步是把6张贴图每个像素的方向向量都存起来了

        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);std::vector<Eigen::Array3f> SHCoeffiecents(SHNum);for (int i = 0; i < SHNum; i++)SHCoeffiecents[i] = Eigen::Array3f(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某个面的球谐系数![请添加图片描述](https://i-blog.csdnimg.cn/direct/f30ab4af6a6c4b1d9ba4c82953bd31d1.png)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]);}}}

请添加图片描述
计算方法就是遍历每一个像素,通过黎曼积分的方法来说,每个像素点都对每个球谐函数的系数有贡献

        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;// 当前像素的RGB值Eigen::Array3f Le(images[i][index + 0], images[i][index + 1],images[i][index + 2]);// 计算当前像素的面积float delta_wi = CalcArea(x, y, width, height);Eigen::Vector3d _dir(Eigen::Vector3d(dir[0], dir[1], dir[2]).normalized());//这里dir要变成Eigen::Vector3d类型// 计算当前像素点对每个基函数系数的黎曼积分求法的贡献for(int l = 0;l < SHNum; l++){for(int m = -l; m <= l; m++){SHCoeffiecents[sh::GetIndex(l,m)] += Le * sh::EvalSH(l,m,_dir)*delta_wi;}}}}}

对于作业提到的伽马矫正,可以参考我之前的博客伽马矫正

传输项的预计算

对于漫反射传输项来说,分为 unshadowed, shadowed, interreflection 三
种情况,我们将分别计算这三种情况的漫反射传输球谐系数。

Diffuse unshadowed

这种情况下渲染方程的BRDF项为常数,此时渲染方程为
请添加图片描述
Li项已经处理掉了,就剩下max()项了
请添加图片描述
作业中只需要写出transport部分在给定一个方向时的值

                if (m_Type == Type::Unshadowed){// TODO: here you need to calculate unshadowed transport term of a given direction// TODO: 此处你需要计算给定方向下的unshadowed传输项球谐函数值return 0;}

这里就只剩一个点乘和max了

                float dot_product = wi.dot(n);if (m_Type == Type::Unshadowed){// TODO: here you need to calculate unshadowed transport term of a given direction// TODO: 此处你需要计算给定方向下的unshadowed传输项球谐函数值return dot_product > 0 ? dot_product : 0;}

Indoor的数据

0.518558 0.510921 0.498186
-0.0139227 -0.0198673 -0.0233177
-0.0229861 -0.0361469 -0.0237983
0.0263383 0.0681837 0.0585552
-0.0508792 -0.0607283 -0.0570984
0.0515054 0.035726 0.0207611
0.0147266 0.0112063 -0.026747
0.00411617 0.0257427 0.0428588
0.0642155 0.0399902 0.0190308

每一行代表一个基函数的参数,可以理解为把原光照函数投影到某一个基函数后的RGB分量分别为多少

Diffuse shadowed

相对于unshadowed,就多出来一项Visibility请添加图片描述

                    // 从顶点位置发射一条光线,与场景相交说明被遮挡了if(dot_product > 0.0f && !scene->rayIntersect(Ray3f(v, wi.normalized()))){return dot_product;}else{return 0.0f;}

当定义好函数后调用了

            auto shCoeff = sh::ProjectFunction(SHOrder, shFunc, m_SampleCount);

这行代码根据SH的阶数、被展开的函数、采样数来得到展开后SH的系数
到这里 光照项和转移项都分别计算了它们的SH展开的系数并存储在txt文件中(通过跑该程序)

7905
0.213508 0.153329 0.206834 -0.0845127 -0.060769 0.144391 0.0588847 -0.0591535 -0.0178413 
0.219123 0.147477 0.190788 -0.134417 -0.0519802 0.135719 0.0171978 -0.101493 0.0164001 
0.206635 0.160885 0.201914 -0.0758193 -0.0472086 0.167402 0.0471802 -0.052247 -0.00859244 
0.185821 0.153003 0.162114 -0.119748 -0.0931989 0.143295 0.00899377 -0.0984071 -0.0184536 
0.206635 0.160885 0.201914 -0.0758193 -0.0472086 0.167402 0.0471802 -0.052247 -0.00859244 

每一行代表一个顶点的球谐展开系数。因为T部分不仅与入射方向有关,也与顶点的具体位置有关,所以每固定一个顶点,球谐展开系数都是不一样的

Diffuse Inter-reflection(bonus)

这里就需要考虑光线的多次弹射,渲染方程变成
请添加图片描述
计算一个顶点的系数时,不仅考虑到来自环境光的光照,还考虑来自别的地方弹射过来的光的影响,仿照光线追踪的写法,从着色点射出采样光线,若击中物体,则把光线反过来求出它对着色点的贡献(如果递归的写就可以求出击中物体的值,递归到最后一层就是本身着色点的值)

std::unique_ptr<std::vector<double>> computeInterreflectionSH(Eigen::MatrixXf* directTSHCoeffs, const Point3f& pos, const Normal3f& normal, const Scene* scene, int bounces)
{std::unique_ptr<std::vector<double>> coeffs(new std::vector<double>());coeffs->assign(SHCoeffLength, 0.0);if (bounces > m_Bounce)return coeffs;const int sample_side = static_cast<int>(floor(sqrt(m_SampleCount)));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;double phi = 2.0 * M_PI * beta;double theta = acos(2.0 * alpha - 1.0);//这边模仿ProjectFunction函数写Eigen::Array3d d = sh::ToVector(phi, theta);const auto wi = Vector3f(d.x(), d.y(), d.z());double H = wi.normalized().dot(normal);Intersection its;if (H > 0.0 && scene->rayIntersect(Ray3f(pos, wi.normalized()), its)){MatrixXf normals = its.mesh->getVertexNormals();Point3f idx = its.tri_index;Point3f hitPos = its.p;Vector3f bary = its.bary;Normal3f hitNormal =Normal3f(normals.col(idx.x()).normalized() * bary.x() +normals.col(idx.y()).normalized() * bary.y() +normals.col(idx.z()).normalized() * bary.z()).normalized();auto nextBouncesCoeffs = computeInterreflectionSH(directTSHCoeffs, hitPos, hitNormal, scene, bounces + 1);for (int i = 0; i < SHCoeffLength; i++){auto interpolateSH = (directTSHCoeffs->col(idx.x()).coeffRef(i) * bary.x() +directTSHCoeffs->col(idx.y()).coeffRef(i) * bary.y() +directTSHCoeffs->col(idx.z()).coeffRef(i) * bary.z());(*coeffs)[i] += (interpolateSH + (*nextBouncesCoeffs)[i]) * H;}}}}for (unsigned int i = 0; i < coeffs->size(); i++) {(*coeffs)[i] /= sample_side * sample_side;}return coeffs;
}
            for (int i = 0; i < mesh->getVertexCount(); i++){const Point3f& v = mesh->getVertexPositions().col(i);const Normal3f& n = mesh->getVertexNormals().col(i).normalized();auto indirectCoeffs = computeInterreflectionSH(&m_TransportSHCoeffs, v, n, scene, 1);for (int j = 0; j < SHCoeffLength; j++){m_TransportSHCoeffs.col(i).coeffRef(j) += (*indirectCoeffs)[j];}std::cout << "computing interreflection light sh coeffs, current vertex idx: " << i << " total vertex idx: " << mesh->getVertexCount() << std::endl;}

实时球谐光照计算

这里我不展示如何跑通代码,只展示主要的逻辑点。跑通代码可以参考博客:https://zhuanlan.zhihu.com/p/596050050

对于预计算数据使用就是在顶点着色器中,要求一个顶点的着色,就要把光照项的每一个系数与T项对应的系数相乘后相加即可

从下面代码可以看出,三个颜色通道单独计算

//prtVertex.glslattribute vec3 aVertexPosition;
attribute vec3 aNormalPosition;
attribute mat3 aPrecomputeLT;uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat3 uPrecomputeL[3];varying highp vec3 vNormal;
varying highp mat3 vPrecomputeLT;
varying highp vec3 vColor;float L_dot_LT(mat3 PrecomputeL, mat3 PrecomputeLT) {vec3 L_0 = PrecomputeL[0];vec3 L_1 = PrecomputeL[1];vec3 L_2 = PrecomputeL[2];vec3 LT_0 = PrecomputeLT[0];vec3 LT_1 = PrecomputeLT[1];vec3 LT_2 = PrecomputeLT[2];return dot(L_0, LT_0) + dot(L_1, LT_1) + dot(L_2, LT_2);
}void main(void) {// 无实际作用,避免aNormalPosition被优化后产生警告vNormal = (uModelMatrix * vec4(aNormalPosition, 0.0)).xyz;for(int i = 0; i < 3; i++){vColor[i] = L_dot_LT(aPrecomputeLT, uPrecomputeL[i]);}gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
}

以R通道举例
请添加图片描述
请添加图片描述
一行(顶点的系数)乘一列(环境光贴图系数的R通道)结果作为着色点的R通道值

请添加图片描述
请添加图片描述
请添加图片描述
至于还有一个作业要做旋转。
我的理解是如果环境光贴图进行了旋转,其实修改的就只是环境光贴图的球谐展开的系数,其他的不会变,而且因为球谐函数的特性,很容易就能求旋转后的系数。先理解了就行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年- H21-Lc129-160. 相交链表(链表)---java版

1.题目描述 2.思路 当pa&#xff01;pb的时候&#xff0c;执行pa不为空&#xff0c;遍历pa链表。执行pb不为空&#xff0c;遍历pb链表。 3.代码实现 // 单链表节点定义 class ListNode {int val;ListNode next;ListNode(int x){valx;nextnull;}}public class H160 {// 主方法…

win10系统安卓开发环境搭建

一 安装jdk 下载jdk17 ,下载路径:https://download.oracle.com/java/17/archive/jdk-17.0.12_windows-x64_bin.exe 下载完毕后,按照提示一步步完成,然后接着创建环境变量, 在cmd控制台输入java -version 验证: 有上面的输出代表jdk安装并配置成功。 二 安装Android stu…

【算法基础】选择排序算法 - JAVA

一、算法基础 1.1 什么是选择排序 选择排序是一种简单直观的排序算法&#xff0c;它的工作原理是&#xff1a;首先在未排序序列中找到最小&#xff08;或最大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后再从剩余未排序元素中继续寻找最小&#xf…

LabVIEW异步调用VI介绍

在 LabVIEW 编程环境里&#xff0c;借助结合异步 VI 调用&#xff0c;并使用 “Open VI Reference” 函数上的 “Enable simultaneous calls on reentrant VIs” 选项&#xff08;0x40&#xff09;&#xff0c;达成了对多个 VI 调用执行效率的优化。以下将从多方面详细介绍该 V…

Leetcode刷题 | Day50_图论02_岛屿问题01_dfs两种方法+bfs一种方法

一、学习任务 99. 岛屿数量_深搜dfs代码随想录99. 岛屿数量_广搜bfs100. 岛屿的最大面积101. 孤岛的总面积 第一类DFS&#xff08;主函数中处理第一个节点&#xff0c;DFS处理相连节点&#xff09;&#xff1a; 主函数中先将起始节点标记为已访问DFS函数中不处理起始节点&…

深入理解网络安全中的加密技术

1 引言 在当今数字化的世界中&#xff0c;网络安全已经成为个人隐私保护、企业数据安全乃至国家安全的重要组成部分。随着网络攻击的复杂性和频率不断增加&#xff0c;保护敏感信息不被未授权访问变得尤为关键。加密技术作为保障信息安全的核心手段&#xff0c;通过将信息转换为…

旧版本NotionNext图片失效最小改动解决思路

旧版本NotionNext图片失效最小改动解决思路 契机 好久没写博客了&#xff0c;最近在notion写博客的时候发现用notionNext同步到个人网站时&#xff0c;图片无法预览。猜测是notion加了防盗链措施&#xff0c;去notionNext官方github上寻找解决方案&#xff0c;需要升级到4.8.…

深度学习笔记40_中文文本分类-Pytorch实现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、我的环境 1.语言环境&#xff1a;Python 3.8 2.编译器&#xff1a;Pycharm 3.深度学习环境&#xff1a; torch1.12.1cu113torchvision…

010302-oss_反向代理_负载均衡-web扩展2-基础入门-网络安全

文章目录 1 OSS1.1 什么是 OSS 存储&#xff1f;1.2 OSS 核心功能1.3 OSS 的优势1.4 典型使用场景1.5 如何接入 OSS&#xff1f;1.6 注意事项1.7 cloudreve实战演示1.7.1 配置cloudreve连接阿里云oss1.7.2 常见错误1.7.3 安全测试影响 2 反向代理2.1 正向代理和反向代理2.2 演示…

【 Node.js】 Node.js安装

下载 下载 | Node.js 中文网https://nodejs.cn/download/ 安装 双击安装包 点击Next 勾选使用许可协议&#xff0c;点击Next 选择安装位置 点击Next 点击Next 点击Install 点击Finish 完成安装 添加环境变量 编辑【系统变量】下的变量【Path】添加Node.js的安装路径--如果…

Python基本语法(自定义函数)

自定义函数 Python语言没有子程序&#xff0c;只有自定义函数&#xff0c;目的是方便我们重复使用相同的一 段程序。将常用的代码块定义为一个函数&#xff0c;以后想实现相同的操作时&#xff0c;只要调用函数名就可以了&#xff0c;而不需要重复输入所有的语句。 函数的定义…

OpenGL-ES 学习(11) ---- EGL

目录 EGL 介绍EGL 类型和初始化EGL初始化方法获取 eglDisplay初始化 EGL选择 Config构造 Surface构造 Context开始绘制 EGL Demo EGL 介绍 OpenGL-ES 是一个操作GPU的图像API标准&#xff0c;它通过驱动向 GPU 发送相关图形指令&#xff0c;控制图形渲染管线状态机的运行状态&…

极简5G专网解决方案

极简5G专网解决方案 利用便携式即插即用私有 5G 网络提升您的智能创新。为您的企业提供无缝、安全且可扩展的 5G 解决方案。 提供极简5G专网解决方案 Mantiswave Network Private Limited 提供全面的 5G 专用网络解决方案&#xff0c;以满足您企业的独特需求。我们创新的“…

html:table表格

表格代码示例&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><!-- 标准表格。 --><table border"5"cellspacing&qu…

tkinter 电子时钟 实现时间日期 可实现透明

以下是一个使用Tkinter模块创建一个简单的电子时钟并显示时间和日期的示例代码&#xff1a; import tkinter as tk import time# 创建主窗口 root tk.Tk() root.overrideredirect(True) # 隐藏标题栏 root.attributes(-alpha, 0.7) # 设置透明度# 显示时间的标签 time_labe…

【报错问题】 macOS 的安全策略(Gatekeeper)阻止了未签名的原生模块(bcrypt_lib.node)加载

这个错误是由于 macOS 的安全策略&#xff08;Gatekeeper&#xff09;阻止了未签名的原生模块&#xff08;bcrypt_lib.node&#xff09;加载 导致的。以下是具体解决方案&#xff1a; 1. 临时允许加载未签名模块&#xff08;推荐先尝试&#xff09; 在终端运行以下命令&#x…

AI实现制作logo的网站添加可选颜色模板

1.效果图 LogoPalette.jsx import React, {useState} from react import HeadingDescription from ./HeadingDescription import Lookup from /app/_data/Lookup import Colors from /app/_data/Colors function LogoPalette({onHandleInputChange}) { const [selectOptio…

云原生后端架构的挑战与应对策略

📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 随着云计算、容器化以及微服务等技术的快速发展,云原生架构已经成为现代软件开发和运维的主流趋势。企业通过构建云原生后端系统,能够实现灵活的资源管理、快速的应用迭代和高效的系统扩展。然而,尽管云原…

【C++】模板为什么要extern?

模板为什么要extern&#xff1f; 在 C 中&#xff0c;多个编译单元使用同一个模板时&#xff0c;是否可以不使用 extern 取决于模板的实例化方式&#xff08;隐式或显式&#xff09;&#xff0c;以及你对编译时间和二进制体积的容忍度。 1. 隐式实例化&#xff1a;可以不用 ex…

中小企业MES系统数据库设计

版本&#xff1a;V1.0 日期&#xff1a;2025年5月2日 一、数据库架构概览 1.1 数据库选型 数据类型数据库类型技术选型用途时序数据&#xff08;传感器读数&#xff09;时序数据库TimescaleDB存储设备实时监控数据结构化业务数据关系型数据库PostgreSQL工单、质量、设备等核心…