games101 hw1

原理

背景

將三維空間中一個旋轉的三角形,顯示在屏幕上。
假設三角形初始點位為 v0(2.0, 0.0, ?2.0), v1(0.0, 2.0, ?2.0), v2(?2.0, 0.0, ?2.0), 你需要將這三個點的坐標變換為屏幕坐標并在屏幕上繪制出對應的線框三角形。

mvp

AI回答:
在計算機圖形領域,MVP指的是模型-視圖-投影矩陣(Model-View-Projection matrix)。它是將三維模型空間的點變換到二維屏幕上的關鍵變換矩陣鏈,包括以下幾個步驟:

  1. 模型變換(Model Transformation):將物體從其本地模型空間變換到世界空間,比如平移、旋轉、縮放。
  2. 視圖變換(View Transformation):將世界空間變換到相機(觀察者)空間,模擬相機位置和方向。
  3. 投影變換(Projection Transformation):將相機空間變換到裁剪空間,實現透視投影或正交投影。

總結:
MVP矩陣 = 投影矩陣 × 視圖矩陣 × 模型矩陣


如果一個運動的物體要實現“動起來”,通常需要對物體應用變換(Transformation),即:

  • 平移(Translation):使物體移動到新位置
  • 旋轉(Rotation):改變物體的方向
  • 縮放(Scaling):改變物體大小

這些變換可以組合在一起,形成一個變換矩陣,然后乘到模型矩陣上,實現物體在空間中的運動。

世界空間(World Space)是三維計算機圖形中的一個坐標系,用來描述場景中的所有物體相對于一個統一的坐標系統。在這個空間里,場景中的每個物體都以其在整個場景中的位置、方向和大小進行定義。

簡單來說:

  • 模型空間(Model Space):每個物體本地的坐標系統,描述物體的相對結構。
  • 世界空間(World Space):所有物體的模型空間經過模型變換(如平移、旋轉、縮放)后,放在一個共同的坐標系中。
  • 相機空間(View Space):從相機的視角來看場景。
  • 裁剪空間/投影空間(Clip/Projection Space):經過投影變換后,用于最終渲染到屏幕。

一個運動的物體要“動起來”,通常需要修改模型矩陣,是因為模型矩陣控制著物體在模型空間到世界空間的變換。

具體來說:

  • 模型矩陣包含平移、旋轉和縮放等變換,定義了物體在場景中的位置、方向和大小。
  • 改變模型矩陣就相當于“移動”物體,或者調整它的朝向和比例,從而實現物體在空間中的運動。

通過不斷更新模型矩陣,物體就能在場景中“動起來”。

代碼架構

rasterizer

用于實現基本的線框和三角形光柵化(繪制)功能,主要用于軟件渲染管線中。核心內容包括加載頂點和索引數據、設置變換矩陣、繪制線條和三角形、以及管理幀緩沖和深度緩沖。

主要結構和功能簡介

1. 枚舉和類型定義
  • Buffers:定義了緩沖區類型(顏色、深度),支持按位操作組合。
  • Primitive:定義了圖元類型(線、三角形)。
  • pos_buf_id、ind_buf_id:封裝了頂點位置緩沖和索引緩沖的ID,確保類型安全。
2. 類:rasterizer
  • 成員變量:

    • 變換矩陣(model、view、projection)
    • 緩沖區(頂點位置、索引、幀緩沖、深度緩沖)
    • 窗口寬度和高度
    • 一個自動遞增的ID生成器
  • 核心方法:

    • load_positions()load_indices():加載頂點和索引數據,返回對應的ID。
    • set_model(), set_view(), set_projection():設置變換矩陣。
    • clear():清空顏色緩沖和深度緩沖。
    • draw():繪制三角形(支持線框輪廓作業通過 rasterize_wireframe() 實現)。
    • set_pixel():設置像素顏色到幀緩沖。
    • frame_buffer():返回當前幀緩沖內容。
  • 私有方法:

    • draw_line():實現了 Bresenham 線段繪制算法,用于繪制線段。
    • rasterize_wireframe():繪制三角形的邊框線。
    • get_index():根據像素點坐標計算緩沖區索引。

深度緩沖區作用

深度緩沖區(Depth Buffer,也叫Z緩沖區)用來管理和實現隱藏面消除,確保在渲染場景時,前面(離觀察者近)的像素遮擋后面(離觀察者遠)的像素。

  • 隱藏面處理:在繪制每個像素時,比較當前像素的深度值(距離相機的遠近)與已存儲的深度值。
  • 遮擋關系:只保留離觀察者更近的像素,屏蔽被遮擋的遠處的像素,從而實現正確的遮擋關系。

具體流程

  1. 當繪制一個像素時,計算其深度(z值)。
  2. 將該深度值與深度緩沖區中對應位置的值進行比較。
  3. 如果新像素更接近(深度值小),則更新顏色和深度緩沖區中的值。
  4. 如果遠離(深度值大),則跳過該像素,不更新。

代碼中的表現

clear() 函數中,深度緩沖區被初始化為無窮大(std::numeric_limits<float>::infinity()),表示還沒有進行任何像素繪制,所有像素的深度都比任何實際像素都遠。

if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
{std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
}

處理流程

  • 加載頂點和索引后,設置變換矩陣。
  • 調用 draw() 方法,進行模型-視圖-投影變換,投影到屏幕空間。
  • 裁剪、歸一化后,調用線段繪制或三角形光柵化函數。
  • 圖元邊界由 draw_line()rasterize_wireframe() 實現。

注釋亮點

  • 使用 Eigen 作為線性代數庫。
  • 提供類型安全的緩沖區識別方式。
  • 支持基礎的線段繪制(Bresenham算法)。
  • 支持深度測試,啟用深度緩沖區。
  • 簡單、直接、易擴展的設計架構。

基礎版

注:只顯示三角形,不旋轉

\\main.cpp 中只有下面的代碼完成基礎版while (key != 27) {r.clear(rst::Buffers::Color | rst::Buffers::Depth);r.set_model(get_model_matrix(angle));r.set_view(get_view_matrix(eye_pos));r.set_projection(get_projection_matrix(45, 1, 0.1, 50));r.draw(pos_id, ind_id, rst::Primitive::Triangle);cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());image.convertTo(image, CV_8UC3, 1.0f);cv::imshow("image", image);key = cv::waitKey(10);std::cout << "frame count: " << frame_count++ << '\n';if (key == 'a') {angle += 10;}else if (key == 'd') {angle -= 10;}}

我們根據代碼重新理解一下上面的知識。

  1. 模型變換(Model Transformation):將物體從其本地模型空間變換到世界空間。可以看到get_model_matrix(angle)的函數只是構建了一個旋轉矩陣。所以原本的三角形,即std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};就是在世界坐標系下面表示的。旋轉三角形,即直接在世界坐標系下面旋轉,就可以做到在屏幕上旋轉
  2. 視圖變換(View Transformation):將世界空間變換到相機(觀察者)空間,模擬相機位置和方向。代碼在get_view_matrix(eye_pos)中,也可以看到相機的位置用世界坐標表示,但是經過視圖變換后坐標系將以相機為原點。
  3. 投影變換(Projection Transformation):將相機空間變換到裁剪空間,實現透視投影或正交投影。需要實現:get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) 輸入是 eyefov表示視野角度,aspect_ratio表示xy的比例。需要轉換一下才能套公式。

三個矩陣怎么計算的很多博主都寫過,我這里就不寫了

在這里插入圖片描述
正交基之間的過渡矩陣非常好求解
xyz 對應的正交基是(1,0,0) (0,1,0),(0,0,1) 所以矩陣A是單位矩陣E
$\hat{g} \times \hat{t} , \hat{t}, \hat{g} $是另一組正交基,矩陣 B = ( g ^ × t ^ , t ^ , g ^ ) B=(\hat{g} \times \hat{t} , \hat{t}, \hat{g}) B=(g^?×t^,t^,g^?)
則, A P = B ? P = A ? 1 B = E B = B AP=B \Rightarrow P=A^{-1}B = EB=B AP=B?P=A?1B=EB=B
而B是正交基組成的,它逆矩陣是它的轉置

所以這個要求是給定的 t ^ , g ^ \hat{t}, \hat{g} t^,g^?是單位向量,且正交,才能推出老師給的公式

在這里插入圖片描述

參考 :https://www.cnblogs.com/fijiisland/p/13993749.html

函數void rst::rasterizer::draw(...)解析

傳入的參數是之前生成的“頂點坐標緩沖id”以及“頂點索引緩沖id”。
首先三角形有三個頂點

std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};
auto pos_id = r.load_positions(pos);

把頂點使用map存起來,并返回頂點的id

rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f> &positions)
{auto id = get_next_id();//std::map<int, std::vector<Eigen::Vector3f>> pos_buf;pos_buf.emplace(id, positions);return {id};
}

然后這三個頂點有三個索引,同樣存在map里面


std::vector<Eigen::Vector3i> ind{{0, 1, 2}};
auto ind_id = r.load_indices(ind);rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i> &indices)
{auto id = get_next_id();// std::map<int, std::vector<Eigen::Vector3i>> ind_buf;ind_buf.emplace(id, indices);return {id};
}

使用的時候,用id在map里取頂點與索引

// 向draw函數傳入之前生成的“頂點坐標緩沖id”以及“頂點索引緩沖id”
void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)
{// 當前作業中,代碼支持的圖元(Primitive)類型僅為rst::Primitive::Triangle,即三角形if (type != rst::Primitive::Triangle){throw std::runtime_error("Drawing primitives other than triangle is not implemented yet!");}// 容易得出,auto推導出的類型為std::vector<Eigen::Vector3f>// buf取得對應的圖元頂點坐標vectorauto& buf = pos_buf[pos_buffer.pos_id];// ind取得對應的圖元頂點索引vectorauto& ind = ind_buf[ind_buffer.ind_id];// 下面會解釋f1、f2的含義float f1 = (100 - 0.1) / 2.0;float f2 = (100 + 0.1) / 2.0;// 最終的變換矩陣為投影、視圖、模型矩陣的點乘Eigen::Matrix4f mvp = projection * view * model;for (auto& i : ind){// 實例化一個TriangleTriangle t;// 構造一個元素為4行向量的數組v,向內插入mvp矩陣對頂點索引對應頂點坐標的變換點// 為了和mvp進行運算,將每個頂點坐標轉為一個Eigen::Vector4f,并規定w值為1Eigen::Vector4f v[] = {mvp * to_vec4(buf[i[0]], 1.0f),mvp * to_vec4(buf[i[1]], 1.0f),mvp * to_vec4(buf[i[2]], 1.0f)};// 透視除法for (auto& vec : v) {vec /= vec.w();}// 視口變換操作for (auto & vert : v){vert.x() = 0.5*width*(vert.x()+1.0);vert.y() = 0.5*height*(vert.y()+1.0);vert.z() = vert.z() * f1 + f2;}// 將變換好的頂點坐標傳入三角形實例tfor (int i = 0; i < 3; ++i){t.setVertex(i, v[i].head<3>());t.setVertex(i, v[i].head<3>());t.setVertex(i, v[i].head<3>());}// 根據頂點索引設置每個頂點的顏色t.setColor(0, 255.0,  0.0,  0.0);t.setColor(1, 0.0  ,255.0,  0.0);t.setColor(2, 0.0  ,  0.0,255.0);rasterize_wireframe(t);}
}

三個頂點的位置都計算完畢,現在開始畫三角形:

rasterize_wireframe(t);
void rst::rasterizer::rasterize_wireframe(const Triangle& t)
{draw_line(t.c(), t.a());draw_line(t.c(), t.b());draw_line(t.b(), t.a());
}

這里使用的是Bresenham’s line drawing algorithm。就不多寫了。
計算出畫在屏幕上的點的位置之后使用函數:

void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)
{//old index: auto ind = point.y() + point.x() * width;if (point.x() < 0 || point.x() >= width ||point.y() < 0 || point.y() >= height) return;auto ind = (height-point.y())*width + point.x();frame_buf[ind] = color;
}

這里在圖像上對應的位置填上顏色。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/909215.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/909215.shtml
英文地址,請注明出處:http://en.pswp.cn/news/909215.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mybatis逆向工程詳解(附源碼文件)動態創建實體類、條件擴展類、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的學習進度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步楊帆旗航。 那么接下來就給大家出一期有關 Mybatis 逆向工程的教學&#xff0c;希望能對大家有所幫助&#xff0c;也特別歡迎大家指點不足之處&#xff0c;小生很樂意接受正確的建議&…

android studio中修改java邏輯對應配置的xml文件

在android studio新建一個activity&#xff0c;自動生成一個xml&#xff0c;可以更改到另一個xml嗎&#xff1f; 可以。 新建一個activity&#xff1a; 如下 上圖中的activity_second為xml文件&#xff0c;SecondActivity為java文件。點擊后&#xff0c; AndroidManifest.x…

@SchedulerLock處理Spring Task在分布式環境下的重復執行問題

本文大綱&#x1f4d6; 1、背景&#x1f342;2、SchedulerLock注解3、實現原理 1、背景&#x1f342; Spring生態下&#xff0c;日常開發定時任務&#xff0c;使用Spring Task框架還是很常見的選擇&#xff0c;但Spring Task并不是為分布式環境設計的&#xff0c;分布式環境下…

Prompt工程指南中文版

Prompt-Engineering-Guide-zh Prompt工程指南中文版 github 本文翻譯改編自 Dair-ai/Prompt-Engineering-Guide 中文原帖地址為 大型語言模型Prompt書寫指南 為了方便理解以及補充缺少的知識點&#xff0c;內容有所添改。除非另有說明&#xff0c;本文中所有的例子都是使用te…

「pandas 與 numpy」數據分析與處理全流程【數據分析全棧攻略:爬蟲+處理+可視化+報告】

- 第 106 篇 - Date: 2025 - 06 - 12 Author: 鄭龍浩&#xff08;仟墨&#xff09; 文中使用的所有文件在文章頂部的資源展示 數據分析與處理 「pandas 與 numpy」 文章目錄 數據分析與處理 「pandas 與 numpy」一了解數據處理1 數據處理2 數據分析第三方庫 二 numpy1 基本介紹…

Fastapi + vue3 自動化測試平臺(6):AI + Web UI的完美結合

&#x1f916;? AI Web自動化革命&#xff1a;用自然語言重塑測試體驗&#xff01; 基于FastAPI Vue3的下一代Web自動化測試平臺誕生&#xff01;將大語言模型 與 Web UI自動化深度結合&#xff0c;讓測試腳本維護進入自然語言時代 —— 告別繁瑣代碼&#xff0c;擁抱智能測…

【QT】 QGraphicsItem 獲取點坐標的幾種方法

目錄 1. 獲取圖元在場景中的位置 對于 QGraphicsEllipseItem&#xff08;點圖元&#xff09; 2. 從 QMap 獲取所有點坐標 3. 響應點擊事件獲取坐標 4. 獲取選中點的坐標 5. 坐標轉換說明 注意事項 在 Qt 圖形視圖框架中&#xff0c;從 QGraphicsItem&#xff08;特別是點…

伊吖學C筆記(6、數、求和、排列)

一、數 1.自然數、奇偶數 自然數也就是非負整數&#xff0c;C的循環語句很容易輸出自然數&#xff0c;比如&#xff1a;輸出100以內的自然數。 奇數、偶數也都是自然數&#xff1a; 2. 約數、因數 題目&#xff1a;一個數如果恰好等于它的因子之和&#xff0c;這個數就稱為“…

SpringMVC與Struts2對比教學

SpringMVC 和 Struts2 就像武林中的兩大門派&#xff0c;雖然都是處理 Web 請求的高手&#xff08;MVC 框架&#xff09;&#xff0c;但招式風格和內功心法大不相同。來&#xff0c;咱們用最接地氣的方式掰扯掰扯&#xff0c;保準你笑著記住&#xff01; 核心區別一句話概括&a…

Nginx配置指南與最佳實踐

Nginx 的配置文件通常位于 /etc/nginx/nginx.conf&#xff0c;并通過 include 指令加載其他目錄&#xff08;如 /etc/nginx/conf.d/ 或 /etc/nginx/sites-enabled/&#xff09;中的配置片段。以下是一個結構化指南&#xff1a; 核心配置結構 # 全局配置 (主上下文) user nginx…

Apache 反向代理Unity服務器

Apache 反向代理Unity服務器 前言項目使用PHPStudy開啟服務修改配置文件修改配置負載均衡&#xff08;可選&#xff09;重啟 總結 前言 使用Unity開了個后臺服務器&#xff0c;但是另一個Java服務器進行大量異步請求時會導致服務器回復過慢&#xff0c;所以開一個Apache緩沖一…

【力扣 簡單 C++】94. 二叉樹的中序遍歷

目錄 題目 解法一&#xff1a;遞歸 解法二&#xff1a;迭代 解法三&#xff1a;Morris遍歷 題目 解法一&#xff1a;遞歸 class Solution { private:void traverse(TreeNode* root, vector<int>& inorder){if (!root)return;traverse(root->left, inorder);i…

idea2024版本設置TODO快捷鍵

直接開干&#xff1a; 首先打開File–>Settings…–>Editor–>Live Templates 復制文本&#xff1a;//wk TODO $data$ 定義自定義todo使用范圍&#xff1a; 設置自定義todo的過濾器&#xff1a; 正式開始設置todo的過濾器&#xff1a; 復制文本&#xff1a; \bwk TO…

云原生核心技術 (12/12): 終章:使用 GitLab CI 將應用自動部署到 K8s (保姆級教程)

大家好&#xff0c;歡迎來到《云原生核心技術》系列的最終章&#xff01; 我們一起走過了漫長而充實的旅程。從 Docker 的集裝箱&#xff0c;到 K8s 這座自動化的數字港口&#xff1b;從部署單個 Pod&#xff0c;到構建復雜的有狀態應用。現在&#xff0c;我們站在了實現全自動…

DEVICENET轉MODBUS TCP網關連接ABB機器人配置案例

在工業自動化場景中&#xff0c;DeviceNet和Modbus TCP是兩種常見的通信協議。DeviceNet通常用于連接現場設備&#xff08;如傳感器、執行器等&#xff09;&#xff0c;而Modbus TCP則廣泛應用于以太網環境下的遠程監控和數據采集。當需要將基于DeviceNet協議的ABB機器人集成到…

達夢數據庫單機部署dmhs同步復制(dm8->kafka)

本文討論了達夢數據實時同步軟件DMHS的相關內容&#xff0c;包括概念總結、環境模擬及部署實現從達夢數據庫到Kafka隊列的同步復制。關鍵要點包括&#xff1a; 1.DMHS系統概述&#xff1a; 達夢公司推出的異構環境高性能數據庫實時同步系統&#xff0c;可應用于應急、容災等多…

爬蟲+動態代理助力 AI 訓練數據采集

文章目錄 引言新手之選&#xff1a;網頁抓取API可靠之選&#xff1a;動態住宅代理總結 引言 近年來&#xff0c;AI 技術飛速發展&#xff0c;很多朋友都投身于 AI 模型的訓練。然而&#xff0c;相較于模型的獲取&#xff0c;高質量的數據往往更加難以收集。一方面&#xff0…

OpenEuler服務器警告郵件自動化發送:原理、配置與安全實踐

OpenEuler服務器警告郵件自動化發送&#xff1a;原理、配置與安全實踐 在服務器的運維管理過程中&#xff0c;及時感知系統異常狀態至關重要。當OpenEuler系統運行時&#xff0c;將服務器的警告信息實時推送至郵箱&#xff0c;能幫助運維人員快速響應潛在問題&#xff0c;保障…

使用vite-plugin-html在 HTML 文件中動態注入數據,如元數據、環境變量、標題

vite-plugin-html 是一個用于 Vite 構建工具的插件&#xff0c;它可以幫助你在構建過程中動態注入一些 HTML 內容&#xff0c;比如標題、元數據、環境變量等。通過使用這個插件&#xff0c;你可以根據項目的配置和環境變量自動生成帶有動態內容的 HTML 文件&#xff0c;適用于 …

學習筆記087——Java接口和抽象類的區別和使用

文章目錄 1、主要區別2、使用場景2.1 使用接口的情況&#xff1a;2.1 使用抽象類的情況&#xff1a; 3、Java 8及以后的接口增強4、設計建議 1、主要區別 特性接口(Interface)抽象類(Abstract Class)定義方式使用interface關鍵字使用abstract class關鍵字方法實現Java 8前不能…