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*} (Awawa)(Bwbwb)(Cwcwc)(Pwpwp)=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)(Pwpwp)=α(A1)+β(B1)+γ(C1)=αM(A1)+βM(B1)+γM(C1)=α(Awawa)+β(Bwbwb)+γ(Cwcwc)(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*} Pwpwp=α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αwaA+αwa+βwb+γwcβwbB+αwa+βwb+γwcγwcC

所以有:

{ α ′ = α 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+γwc1

则有:

{ α ′ = α 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} αβγ=αwak=βwbk=γwck

{ α = α ′ 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} αβγ=wakα=wbkβ=wckγ(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=α+β+γ=wakα+wbkβ+wckγ=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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;有效降低寄…