tinyrenderer筆記(透視矯正)

  • tinyrenderer
  • 個人代碼倉庫:tinyrenderer個人練習代碼

引言

還要從上一節知識說起,在上一節中我為了調試代碼,換了一個很簡單的正方形 obj 模型,配上紋理貼圖與法線貼圖進行渲染,得了下面的結果:

image.png|475

what?這是啥,為什么正方形中間紋理出現了扭曲,當我調大旋轉的角度,這個扭曲越發明顯:

image.png

很明顯這是紋理坐標出了問題,這就引出了另外一個知識:透視矯正插值,也就是 tinyrenderer 的這篇文章:Technical difficulties: linear interpolation with perspective deformations

透視矯正插值

首先思考我們現在的插值是怎么做的?

image.png

如上圖所示,假設我們渲染一個 △ A B C \bigtriangleup ABC ABC 內的 P P P 點,經透視投影后,被投影為 △ a b c \bigtriangleup abc abc 內的 p p p 點。我們是如何計算重心坐標的?

Vec3f bc_screen = glm::barycentric(viewport_coords[0], viewport_coords[1], viewport_coords[2], P);

上述代碼中,我們拿的是屏幕空間的坐標去計算重心坐標。思考這樣一個問題:假設觀察空間下 △ A B C \bigtriangleup ABC ABC P P P 點的重心坐標為 ( α , β , γ ) (\alpha,\beta,\gamma) (α,β,γ) △ a b c \bigtriangleup abc abc p p p 點為 ( α ′ , β ′ , γ ′ ) (\alpha^\prime,\beta^\prime,\gamma^\prime) (α,β,γ)。你覺得 ( α , β , γ ) = ( α ′ , β ′ , γ ′ ) (\alpha,\beta,\gamma)=(\alpha^\prime,\beta^\prime,\gamma^\prime) (α,β,γ)=(α,β,γ) 嗎?

答案為否,因為透視投影是一種非線性變換, △ A B C \bigtriangleup ABC ABC 經過透視投影之后會發生畸變,整體比例會發生變化。但我們卻用 ( α ′ , β ′ , γ ′ ) (\alpha^\prime,\beta^\prime,\gamma^\prime) (α,β,γ) 去插值,希望得到 △ A B C \bigtriangleup ABC ABC 內點 P P P 的屬性,這肯定會造成誤差!

可能有同學很快會想出一種思路:既然計算 △ a b c \bigtriangleup abc abc p p p 點的重心坐標去插值不準確,那么我是否可以將屏幕空間下的 p p p 點經過逆變換得到 P P P 點,然后計算 P P P 點關于 △ A B C \bigtriangleup ABC ABC 的重心坐標呢?

理論上沒有問題,但是思考一下我們是如何獲得 p p p 點的 z z z (深度值)的,我們是通過插值獲得的(這不套娃嗎?),所以屏幕空間下 p p p 點的 z z z 坐標是無法準確得到的。同時,在經過透視投影后,我們進行了透視除法, w w w 分量也被丟棄了。所以,我們將 p p p 逆變換成 P P P 困難重重。

雖然 p p p 點的信息我們無法確定,但是三角形三個頂點的所有信息我們是能夠知道的,那么我們是否能夠通過已知變量來建立 ( α ′ , β ′ , γ ′ ) (\alpha^\prime,\beta^\prime,\gamma^\prime) (α,β,γ) ( α , β , γ ) (\alpha,\beta,\gamma) (α,β,γ) 的映射關系呢?前輩們已經為我們做好了這個工作,下面將開始推導。

推導

推導過程來自:perspective-correct-interpolation.dvi

下圖展示了透視投影在視圖空間形成的視錐體,相機位于原點:

image.png

設點 A , B , C , P A,B,C,P A,B,C,P 經過齊次矩陣 M M M 轉化為了 A ′ , B ′ , C ′ , P ′ A^\prime,B^\prime,C^\prime,P^\prime A,B,C,P(經過透視投影與透視除法的坐標):

( A ′ w a w a ) = M ( A 1 ) ( B ′ w b w b ) = M ( B 1 ) ( C ′ w c w c ) = M ( C 1 ) ( P ′ w p w p ) = M ( P 1 ) \begin{align*} \begin{pmatrix} A'w_a \\ w_a \end{pmatrix} &= \mathbf{M} \begin{pmatrix} A \\ 1 \end{pmatrix}\\ \begin{pmatrix} B'w_b \\ w_b \end{pmatrix} &= \mathbf{M} \begin{pmatrix} B \\ 1 \end{pmatrix}\\ \begin{pmatrix} C'w_c \\ w_c \end{pmatrix} &= \mathbf{M} \begin{pmatrix} C \\ 1 \end{pmatrix}\\ \begin{pmatrix} P'w_p \\ w_p \end{pmatrix} &= \mathbf{M} \begin{pmatrix} P \\ 1 \end{pmatrix} \end{align*} (Awa?wa??)(Bwb?wb??)(Cwc?wc??)(Pwp?wp??)?=M(A1?)=M(B1?)=M(C1?)=M(P1?)?

真正的重心坐標權重為 α , β , γ \alpha,\beta,\gamma α,β,γ,經過透視投影變為了 α ′ , β ′ , γ ′ \alpha^\prime,\beta^\prime,\gamma^\prime α,β,γ

P = α A + β B + γ C P ′ = α ′ A ′ + β ′ B ′ + γ ′ C ′ \begin{align*} P &= \alpha A + \beta B + \gamma C\\ P' &= \alpha' A' + \beta' B' + \gamma' C' \end{align*} PP?=αA+βB+γC=αA+βB+γC?

在光柵化階段我們可以直接計算 α ′ , β ′ , γ ′ \alpha^\prime,\beta^\prime,\gamma^\prime α,β,γ,但我們需要 α , β , γ \alpha,\beta,\gamma α,β,γ 來正確的插值頂點的屬性。

( P 1 ) = α ( A 1 ) + β ( B 1 ) + γ ( C 1 ) M ( P 1 ) = α M ( A 1 ) + β M ( B 1 ) + γ M ( C 1 ) ( P ′ w p w p ) = α ( A ′ w a w a ) + β ( B ′ w b w b ) + γ ( C ′ w c w c ) \begin{align*} \begin{pmatrix} P \\ 1 \end{pmatrix} &= \alpha \begin{pmatrix} A \\ 1 \end{pmatrix} + \beta \begin{pmatrix} B \\ 1 \end{pmatrix} + \gamma \begin{pmatrix} C \\ 1 \end{pmatrix}\\ \mathbf{M} \begin{pmatrix} P \\ 1 \end{pmatrix} &= \alpha \mathbf{M} \begin{pmatrix} A \\ 1 \end{pmatrix} + \beta \mathbf{M} \begin{pmatrix} B \\ 1 \end{pmatrix} + \gamma \mathbf{M} \begin{pmatrix} C \\ 1 \end{pmatrix}\\ \begin{pmatrix} P'w_p \\ w_p \end{pmatrix} &= \alpha \begin{pmatrix} A'w_a \\ w_a \end{pmatrix} + \beta \begin{pmatrix} B'w_b \\ w_b \end{pmatrix} + \gamma \begin{pmatrix} C'w_c \\ w_c \end{pmatrix} \tag{1} \end{align*} (P1?)M(P1?)(Pwp?wp??)?=α(A1?)+β(B1?)+γ(C1?)=αM(A1?)+βM(B1?)+γM(C1?)=α(Awa?wa??)+β(Bwb?wb??)+γ(Cwc?wc??)?(1)?

將 1 式分解則有:

P ′ w p = α A ′ w a + β B ′ w b + γ C ′ w c w p = α w a + β w b + γ w c \begin{align*} P' w_p &= \alpha A' w_a + \beta B' w_b + \gamma C' w_c \tag{2}\\ w_p &= \alpha w_a + \beta w_b + \gamma w_c \tag{3}\\ \end{align*} Pwp?wp??=αAwa?+βBwb?+γCwc?=αwa?+βwb?+γwc??(2)(3)?

將 3 式帶入 2 式可得:

P ′ = α A ′ w a + β B ′ w b + γ C ′ w c α w a + β w b + γ w c P ′ = α w a α w a + β w b + γ w c A ′ + β w b α w a + β w b + γ w c B ′ + γ w c α w a + β w b + γ w c C ′ \begin{align*} P' &= \frac{\alpha A' w_a + \beta B' w_b + \gamma C' w_c}{\alpha w_a + \beta w_b + \gamma w_c}\\ P' &= \frac{\alpha w_a}{\alpha w_a + \beta w_b + \gamma w_c} A' + \frac{\beta w_b}{\alpha w_a + \beta w_b + \gamma w_c} B' + \frac{\gamma w_c}{\alpha w_a + \beta w_b + \gamma w_c} C' \end{align*} PP?=αwa?+βwb?+γwc?αAwa?+βBwb?+γCwc??=αwa?+βwb?+γwc?αwa??A+αwa?+βwb?+γwc?βwb??B+αwa?+βwb?+γwc?γwc??C?

所以有:

{ α ′ = α w a α w a + β w b + γ w c β ′ = β w b α w a + β w b + γ w c γ ′ = γ w c α w a + β w b + γ w c \begin{cases} \begin{align*} \alpha' &= \frac{\alpha w_a}{\alpha w_a + \beta w_b + \gamma w_c}\\ \beta' &= \frac{\beta w_b}{\alpha w_a + \beta w_b + \gamma w_c}\\ \gamma' &= \frac{\gamma w_c}{\alpha w_a + \beta w_b + \gamma w_c} \end{align*} \end{cases} ? ? ??αβγ?=αwa?+βwb?+γwc?αwa??=αwa?+βwb?+γwc?βwb??=αwa?+βwb?+γwc?γwc????

但我們希望用 α ′ , β ′ , γ ′ \alpha',\beta',\gamma' α,β,γ 來表示 α , β , γ \alpha,\beta,\gamma α,β,γ,設分母為 k k k

k = 1 α w a + β w b + γ w c k=\frac{1}{\alpha w_a + \beta w_b + \gamma w_c} k=αwa?+βwb?+γwc?1?

則有:

{ α ′ = α w a k β ′ = β w b k γ ′ = γ w c k \begin{cases} \begin{align*} \alpha' &= \alpha w_ak\\ \beta' &= \beta w_bk\\ \gamma' &= \gamma w_ck \end{align*} \end{cases} ? ? ??αβγ?=αwa?k=βwb?k=γwc?k??

{ α = α ′ w a k β = β ′ w b k γ = γ ′ w c k \begin{cases} \begin{align*} \alpha &= \frac{\alpha'}{w_ak} \tag{4}\\ \beta &= \frac{\beta'}{w_bk}\tag{5}\\ \gamma &= \frac{\gamma'}{w_ck}\tag{6} \end{align*} \end{cases} ? ? ??αβγ?=wa?kα?=wb?kβ?=wc?kγ??(4)(5)(6)??

又因為 α + β + γ = 1 \alpha+\beta+\gamma=1 α+β+γ=1

1 = α + β + γ = α ′ w a k + β ′ w b k + γ ′ w c k k = α ′ w a + β ′ w b + γ ′ w c \begin{align*} 1&=\alpha+\beta+\gamma=\frac{\alpha'}{w_ak}+\frac{\beta'}{w_bk}+\frac{\gamma'}{w_ck}\\ k&=\frac{\alpha'}{w_a}+\frac{\beta'}{w_b}+\frac{\gamma'}{w_c} \tag{7} \end{align*} 1k?=α+β+γ=wa?kα?+wb?kβ?+wc?kγ?=wa?α?+wb?β?+wc?γ??(7)?

將 7 式帶入 4、5、6 式即可得到:

{ α = α ′ w a α ′ w a + β ′ w b + γ ′ w c β = β ′ w b α ′ w a + β ′ w b + γ ′ w c γ = γ ′ w c α ′ w a + β ′ w b + γ ′ w c \begin{cases} \begin{align*} \alpha &= \frac{\frac{\alpha'}{w_a}}{\frac{\alpha'}{w_a}+\frac{\beta'}{w_b}+\frac{\gamma'}{w_c}}\\ \beta &= \frac{\frac{\beta'}{w_b}}{\frac{\alpha'}{w_a}+\frac{\beta'}{w_b}+\frac{\gamma'}{w_c}}\\ \gamma &= \frac{\frac{\gamma'}{w_c}}{\frac{\alpha'}{w_a}+\frac{\beta'}{w_b}+\frac{\gamma'}{w_c}} \end{align*} \end{cases} ? ? ??αβγ?=wa?α?+wb?β?+wc?γ?wa?α??=wa?α?+wb?β?+wc?γ?wb?β??=wa?α?+wb?β?+wc?γ?wc?γ????

現在,我們就可以使用 α , β , γ \alpha,\beta,\gamma α,β,γ 來正確插值頂點的屬性了。

代碼

來完善這個函數,barycentricCorrect,它會返回經過透視矯正后的重心坐標。這是在二位平面上的插值,A、B、C 的 z 分量會存儲它們經過透視投影后的 w 分量。

Vec3f glm::barycentricCorrect(const Vec3f& A, const Vec3f& B, const Vec3f& C, const Vec3f& P)
{if (IsNearlyZero(A.z) && IsNearlyZero(B.z) && IsNearlyZero(C.z)){std::cout << "glm::barycentricCorrect A, B, C w is zero!" << std::endl;return barycentric(A, B, C, P);}Vec3f bc = barycentric(A, B, C, P);float det = bc.x/A.z + bc.y/B.z + bc.z/C.z;if (IsNearlyZero(det)){std::cout << "glm::barycentricCorrect det is zero" << std::endl;return bc;}bc.x = bc.x / A.z / det;bc.y = bc.y / B.z / det;bc.z = bc.z / C.z / det;return bc;
}

剩下要做的就只有在 glProgram::Draw 函數內,把對 barycentric 改為 barycentricCorrect

Vec3f bc_screen = glm::barycentricCorrect(Vec3f(viewport_coords[0].x, viewport_coords[0].y, vertexs_w[0]),Vec3f(viewport_coords[1].x, viewport_coords[1].y, vertexs_w[1]),Vec3f(viewport_coords[2].x, viewport_coords[2].y, vertexs_w[2]), P);

結果:

image.png

最后還需要提一點,本文提到的透視矯正只針對透視投影來說,正交投影不存在這個問題。

本次代碼提交記錄:

image.png

這個版本的 LookAt 函數存在錯誤!2025-4-29 16.23 提交修復

參考

  • Technical difficulties: linear interpolation with perspective deformations
  • perspective-correct-interpolation.dvi

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

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

相關文章

MySQL的內置函數與復雜查詢

目錄 前言 一、聚合函數 1.1日期函數 1.2字符串函數 1.3數學函數 1.4其它函數 二、關鍵字周邊 2.1關鍵字的生效順序 2.2數據源 2.3可以使用聚合函數的關鍵字 前言 在前面幾篇文章中&#xff0c;講解了有關MySQL數據庫、數據庫表的創建、數據庫表的數據操作等等。本文我…

見多識廣4:Buffer與Cache,神經網絡加速器的Buffer

目錄 前言傳統意義上的Buffer與Cache一言以蔽之定義與主要功能BufferCache 數據存儲策略二者對比 神經網絡加速器的bufferInput BufferWeight BufferOutput Buffer與傳統buffer的核心區別總結 前言 知識主要由Qwen和Kimi提供&#xff0c;我主要做筆記。 參考文獻&#xff1a; …

內存的位運算

示例&#xff1a;提取和設置標志位 假設我們有一個32位的整數&#xff0c;其中不同的位代表不同的標志。例如&#xff1a; 位0&#xff1a;是否開啟日志&#xff08;0表示關閉&#xff0c;1表示開啟&#xff09; 位1&#xff1a;是否啟用調試模式&#xff08;0表示禁用&#…

linux -shell原理與運用

1---shell的工作方式和功能 shell的工作方式: shell本身也是一個應用程序,存儲在/bin 或者是/user/bin中 登錄的時候 會根據/etc/passwd文件載入shell默認執行 shell啟動后,就會顯示命令提示符,等待用戶輸入命令 命令的邏輯: 首先會判斷時內部命令還是外部命令,如果是內部…

js獲取uniapp獲取webview內容高度

js獲取uniapp獲取webview內容高度 在uni-app中&#xff0c;如果你想要獲取webview的內容高度&#xff0c;可以使用uni-app提供的bindload事件來監聽webview的加載&#xff0c;然后通過調用webview的invokeMethod方法來獲取內容的高度。 以下是一個示例代碼&#xff1a; <te…

Windows系統升級Nodejs版本

什么是nodejs Node.js 是一個免費、開源、跨平臺的 JavaScript 運行時環境, 它讓開發人員能夠創建服務器 Web 應用、命令行工具和腳本。 NodeJs官網 網址&#xff1a;Node.js — 在任何地方運行 JavaScript 可以通過網址下載安裝&#xff0c;通過官網可以看到現在最新版本為22…

Relay算子注冊(在pytorch.py端調用)

1. Relay算子注冊 (C層) (a) 算子屬性注冊 路徑: src/relay/op/nn/nn.cc RELAY_REGISTER_OP("hardswish").set_num_inputs(1).add_argument("data", "Tensor", "Input tensor.").set_support_level(3).add_type_rel("Identity…

【JavaEE】網絡原理之初識(1.0)

目錄 ?編輯 局域網與廣域網 IP地址和端口號 實現簡單的服務器客戶端交互 簡單理解socket TCP和UDP的差別&#xff08;初識&#xff09; socket面對udp DatagramSocket API DatagramSocket 構造方法 DatagramSocket 方法&#xff1a; DatagramPacket API Data…

Redis數據結構ZipList,QuickList,SkipList

目錄 1.ZipList 1.2.解析Entry&#xff1a; 1.3Encoding編碼 1.4.ZipList連鎖更新問題 2.QuickList SkipList跳表 RedisObject 五種數據類型 1.ZipList redis中的ZipList是一種緊湊的內存儲存結構&#xff0c;主要可以節省內存空間儲存小規模數據。是一種特殊的雙端鏈表…

laravel 12 監聽syslog消息,并將消息格式化后存入mongodb

在Laravel 12中實現監聽Syslog消息并格式化存儲到MongoDB&#xff0c;需結合日志通道配置、Syslog解析和MongoDB存儲操作。以下是具體實現方案&#xff1a; 一、環境配置 安裝MongoDB擴展包 執行以下命令安裝必要的依賴&#xff1a; composer require jenssegers/mongodb ^4.0確…

【STM32項目實戰】一文了解單片機的SPI驅動外設功能

前言&#xff1a;在前面我有文章介紹了關于單片機的SPI外設CUBEMX配置&#xff0c;但是要想使用好SPI這個外設我們還必須對其原理性的時序有一個詳細的了解&#xff0c;所以這篇文章就補充一下SPI比較偏向底層的時序性的邏輯。 1&#xff0c;SPI簡介 SPI是MCU最常見的對外通信…

【挖洞利器】GobyAwvs解放雙手

【滲透測試工具】解放雙手&Goby配合Awvs滲透測試利器\x0a通過Goby和Awvs 解放雙手https://mp.weixin.qq.com/s/SquRK8C5cRpWmfGbIOqxoQ

LangChain4j(15)——RAG高級之跳過檢索

之前的文章中&#xff0c;我們介紹了RAG的使用&#xff0c;但是&#xff0c;每次提問時&#xff0c;都會通過RAG進行檢索。有時&#xff0c;檢索是不必要執行的&#xff0c;比如&#xff0c;當用戶只是說“你好”時。于是&#xff0c;我們需要有條件的跳過檢索過程。 跳過決策…

【SDRS】面向多模態情感分析的情感感知解糾纏表征轉移

abstract 多模態情感分析(MSA)旨在利用多模態的互補信息對用戶生成的視頻進行情感理解。現有的方法主要集中在設計復雜的特征融合策略來整合單獨提取的多模態表示,忽略了與情感無關的信息的干擾。在本文中,我們提出將單模表征分解為情感特定特征和情感獨立特征,并將前者融…

Sui 上線兩周年,掀起增長「海嘯」

兩年前的 5 月 3 日&#xff0c;Sui 的主網正式發布&#xff0c;將在開發網和測試網上驗證過的下一代技術承諾變為現實。這一新興網絡旨在優化現有區塊鏈技術&#xff0c;結合高性能計算環境與安全性、可驗證性及韌性。 隨著 Sui 迎來兩周年&#xff0c;這股浪潮已成長為「海嘯…

深入理解 mapper-locations

mybatis-plus.mapper-locations: classpath*:/mapper/**/*.xml 是 MyBatis/MyBatis-Plus 在 Spring Boot 配置文件&#xff08;如 application.yml 或 application.properties&#xff09;中的一項關鍵配置&#xff0c;用于指定 MyBatis Mapper XML 文件的存放路徑。以下是詳細…

電容的作用

使用多個電容是從電容的實際等效模型去考慮的(也就是從SI&#xff0c;信號完整性方面&#xff09;。只考慮一個實際電容時&#xff0c;它的阻抗曲線是一個類似于倒三角形的形狀&#xff0c;只在諧振頻率點(與等效串聯電感形成)處的阻抗最小。因此相當于只在這一個頻率點處及附近…

移植的本質是什么

有斷時間我就在想&#xff0c;為什么freertos&#xff0c;lvgl等等的移植都是把庫文件放進來&#xff0c;直接點擊編譯&#xff0c;然后把bug都處理完成就移植成功了&#xff0c;為什么呢&#xff1f; 明明我一個函數都沒調用&#xff0c;為什么會有一堆錯誤&#xff0c;莫名其…

廣告場景下的檢索平臺技術

檢索方向概述 數據檢索領域技術選型大體分為SQL事務數據庫、NoSQL數據庫、分析型數據庫三個類型。 SQL數據庫的設計思路是采用關系模型組織數據&#xff0c;注重讀寫操作的一致性&#xff0c;注重數據的絕對安全。為了實現這一思路&#xff0c;SQL數據庫往往會犧牲部分性能&…

高頻PCB設計如何選擇PCB層數?

以四層板為例&#xff0c;可以第一層和第二層畫信號&#xff0c;作為信號層。 第三層可以走電源&#xff0c;然后第四層走GND 但是更可以第一層和第三層畫信號。第二層可以走電源&#xff0c;然后第四層走GND 用中間的電源層以及地層可以起到屏蔽的作用&#xff0c;有效降低寄…