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.pswp.cn/pingmian/79205.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79205.shtml
英文地址,請注明出處:http://en.pswp.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工單、質量、設備等核心…