【OpenGL的数学01】从窗口空间计算视空间

文章目录

  • 一、说明
  • 二、定义
  • 三、来自gl_FragCoord
  • 四、来自gl_FragCoord的XYZ
    • 4.1 从窗口到ndc
    • 4.2 从NDC到剪辑
    • 4.3 从剪辑到眼睛
    • 4.4 GLSL示例
  • 五、从gl_FragCoord的XYZ优化方法

一、说明

本文将解释如何在给定窗口空间顶点位置的情况下重新计算眼空间顶点位置。以及相反的计算。其中包括一些数学概念,需要仔细推敲。

二、定义

在开始之前,我们需要定义一些符号:

象征意义程序对应
M投影矩阵 M p r j M_{prj} Mprj
P眼空间位置,4D 矢量Lookat
C剪辑空间位置,4D 矢量
N归一化器件坐标空间位置,3D矢量模型坐标
W窗口空间位置,3D 矢量窗口中心的绝对位置
Vx, y传递给 glViewport 的 X 和 Y 值
Vw, h传递给 glViewport 的 width 和 height 值
Dn, f传递给 glDepthRange 的 near 和 far 值三维视线锥体的绝对位置

上表中,世界坐标就是绝对坐标,是一切度量的根本原点坐标。

三、来自gl_FragCoord

gl_FragCoord.xyz 是窗口空间位置 W,一个 3D 向量。gl_FragCoord.w 包含剪辑空间 W 的倒数:

g l _ F r a g C o o r d w = 1 C w . gl\_FragCoord_{w}={\tfrac {1}{C_{w}}}. gl_FragCoordw=Cw1.

给定这些值,我们有一个相当简单的方程组:
在这里插入图片描述

在 GLSL 片段着色器中,代码如下所示:

vec4 ndcPos;
ndcPos.xy = ((2.0 * gl_FragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1;
ndcPos.z = (2.0 * gl_FragCoord.z - gl_DepthRange.near - gl_DepthRange.far) /(gl_DepthRange.far - gl_DepthRange.near);
ndcPos.w = 1.0;vec4 clipPos = ndcPos / gl_FragCoord.w;
vec4 eyePos = invPersMatrix * clipPos;

这假定存在一个名为 viewport 的统一,该 viewport 是一个 vec4,按照传递给该函数的顺序与 glViewport 的参数匹配。此外,这假设 invPersMatrix 是透视投影矩阵的逆矩阵(不提倡在片段着色器中计算)。请注意,gl_DepthRange 是可用于片段着色器的内置变量。

四、来自gl_FragCoord的XYZ

这种情况对于延迟渲染技术非常有用,但最后一种方法也非常有用。在延迟渲染中,我们将对象的材质参数渲染为图像。然后,我们对这些图像进行多次传递,加载这些材料参数并对它们执行照明计算。

在光通道中,我们需要重建眼空间顶点位置,以便进行照明。但是,我们实际上没有gl_FragCoord;不适用于产生材料参数的片段。取而代之的是,我们有 gl_FragCoord.xy 中的窗口空间 X 和 Y 位置,并且我们有窗口空间深度,通过访问深度缓冲区进行采样,该缓冲区也是从延迟传递中保存的。

我们缺少的是原始的窗口空间 W 坐标。

因此,我们必须找到一种方法,从窗口空间 XYZ 坐标和透视投影矩阵来计算它。本讨论将假定您的透视投影矩阵采用以下形式:

[ xx xx xx xx ]
[ xx xx xx xx ]
[ 0 0 T1 T2 ]
[ 0 0 E1 0 ]
xx 表示“任何”,它们可以是您在投影中使用的任何值。0 在投影矩阵中必须为零。T1、T2 和 E1 可以是任意项,具体取决于投影矩阵的工作方式。

如果您的投影矩阵不适合此形式,则以下代码将变得更加复杂。

4.1 从窗口到ndc

我们有窗口空间的 XYZ:

W ⃗ = [ g l _ F r a g C o o r d . x g l _ F r a g C o o r d . y f r o m D e p t h T e x t u r e ] {\vec W}={\begin{bmatrix}gl\_FragCoord.x\\gl\_FragCoord.y\\fromDepthTexture\end{bmatrix}} W = gl_FragCoord.xgl_FragCoord.yfromDepthTexture

从窗口空间计算 NDC 空间与上述相同:

N ⃗ = [ ( 2 ∗ W x ) − ( 2 ∗ V x ) V w − 1 ( 2 ∗ W y ) − ( 2 ∗ V y ) V h − 1 ( 2 ∗ W z ) − D f − D n D f − D n ] {\displaystyle {\vec {N}}={\begin{bmatrix}{\tfrac {(2*W_{x})-(2*V_{x})}{V_{w}}}-1\\{\tfrac {(2*W_{y})-(2*V_{y})}{V_{h}}}-1\\{\tfrac {(2*W_{z})-D_{f}-D_{n}}{D_{f}-D_{n}}}\end{bmatrix}}} N = Vw2Wx2Vx1Vh2Wy2Vy1DfDn2WzDfDn

请记住:在本例中,视口和景深范围参数是用于渲染原始场景的参数。当然,视口不应该改变,但深度范围肯定可以改变(假设你甚至在延迟渲染器的光照通道中有一个深度范围)。

4.2 从NDC到剪辑

为简单起见,以下是从 NDC 空间到剪辑空间的公式:

C w = T 2 N z − T 1 E 1 C ⃗ x y z = N ⃗ ∗ C w {\begin{aligned}C_{w}&={\tfrac {T2}{N_{z}-{\tfrac {T1}{E1}}}}\\{\vec C}_{{xyz}}&={\vec N}*C_{w}\end{aligned}} CwC xyz=NzE1T1T2=N Cw

推导
推导这两个等价位是非常不平凡的;这是一个相当大的绊脚石。让我们从我们所知道的开始。

我们可以从剪辑空间转换为 NDC 空间,这样我们就可以返回:

N ⃗ = C ⃗ C w C ⃗ = N ⃗ ∗ C w {\begin{aligned}{\vec N}&={\tfrac {{\vec C}}{C_{w}}}\\{\vec C}&={\vec N}*C_{w}\end{aligned}} N C =CwC =N Cw

问题是我们没有 Cw。我们之前能够使用 gl_FragCoord.w 来计算它,但是当我们在延迟照明通过中事后执行此操作时,这不可用。

那么我们如何计算它呢?好吧,我们知道剪辑空间位置最初是这样计算的:

C ⃗ = M ∗ P ⃗ {\vec C}=M*{\vec P} C =MP

因此,我们知道 Cw 是由 P 的点积与 M 的第四行计算得出的。鉴于我们上面对 M 第四行的定义,我们可以得出结论:
C w = E 1 ∗ P z N ⃗ = C ⃗ E 1 ∗ P z {\begin{aligned}C_{w}&=E1*P_{z}\\{\vec N}&={\tfrac {{\vec C}}{E1*P_{z}}}\end{aligned}} CwN =E1Pz=E1PzC

当然,这只是用一个未知来换取另一个未知。但是我们可以使用它。事实证明,Nz 与此有一些共同点:
N z = C z E 1 ∗ P z N_{z}={\tfrac {C_{z}}{E1*P_{z}}} Nz=E1PzCz

看看 Cz 的来源很有趣。和以前一样,我们知道它是由 P 的点积与第三行 M 计算得出的。再一次,鉴于我们上面对 M 的定义,我们可以得出结论:

C z = T 1 ∗ P z + T 2 ∗ P w N z = T 1 ∗ P z + T 2 ∗ P w E 1 ∗ P z {\begin{aligned}C_{z}&=T1*P_{z}+T2*P_{w}\\N_{z}&={\tfrac {T1*P_{z}+T2*P_{w}}{E1*P_{z}}}\end{aligned}} CzNz=T1Pz+T2Pw=E1PzT1Pz+T2Pw

我们这里仍然有两个未知值,Pz 和 Pw。但是,我们可以假设 Pw 为 1.0,因为眼空间位置通常就是这种情况。给定这个假设,我们只有一个未知数 Pz,我们可以求解它:
在这里插入图片描述

KaTeX parse error: Expected & or \\ or \cr or \end at position 169: …{T2}{E1*P_{z}}}}̲ \\E1*P_{z}&={\…

现在有了 Pz,我们可以计算 Cw:

C w = E 1 ∗ P z C w = T 2 N z − T 1 E 1 {\begin{aligned}C_{w}&=E1*P_{z}\\C_{w}&={\tfrac {T2}{N_{z}-{\tfrac {T1}{E1}}}}\end{aligned}} CwCw=E1Pz=NzE1T1T2

因此,我们可以从中计算出 C 的其余部分:

C ⃗ x y z = N ⃗ ∗ C w C ⃗ x y z = N ⃗ ∗ ( T 2 N z − T 1 E 1 ) {\begin{aligned}{\vec C}_{{xyz}}&={\vec N}*C_{w}\\{\vec C}_{{xyz}}&={\vec N}*({\tfrac {T2}{N_{z}-{\tfrac {T1}{E1}}}})\end{aligned}} C xyzC xyz=N Cw=N NzE1T1T2

4.3 从剪辑到眼睛

计算完整的 4D 向量 C 后,我们可以像以前一样计算 P:
P ⃗ = M − 1 C ⃗ {\vec P}=M^{{-1}}{\vec C} P =M1C

4.4 GLSL示例

下面是一些 GLSL 示例代码,说明这将是什么样子的:

uniform mat4 persMatrix;
uniform mat4 invPersMatrix;
uniform vec4 viewport;
uniform vec2 depthrange;vec4 CalcEyeFromWindow(in vec3 windowSpace)
{vec3 ndcPos;ndcPos.xy = ((2.0 * windowSpace.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1;ndcPos.z = (2.0 * windowSpace.z - depthrange.x - depthrange.y) /(depthrange.y - depthrange.x);vec4 clipPos;clipPos.w = persMatrix[3][2] / (ndcPos.z - (persMatrix[2][2] / persMatrix[2][3]));clipPos.xyz = ndcPos * clipPos.w;return invPersMatrix * clipPos;
}

视口是包含视口参数的矢量。depthrange 是包含 glDepthRange 参数的 2D 向量。windowSpace 向量是 gl_FragCoord 的前两个分量,第三个坐标是从深度缓冲区读取的深度。

五、从gl_FragCoord的XYZ优化方法

前面的方法当然有用,但速度有点慢。我们可以通过使用顶点着色器来提供帮助,从而显着帮助计算眼距位置。这使我们能够完全避免使用逆透视矩阵。

此方法分为两步。我们首先计算 Pz,即眼空间 Z 坐标。然后用它来计算完整的眼距位置。

第一部分其实很简单。我们上面使用的大多数计算都是必要的,因为我们需要 Cw,我们必须这样做,因为我们需要一个完整的剪辑空间位置。这种优化的方法只需要得到 Pz,我们可以直接从 Wz、深度范围和投影矩阵的三个分量中计算出来:

N z = ( 2 ∗ W z ) − D f − D n D f − D n P z = T 2 E 1 ∗ N z − T 1 {\displaystyle {\begin{aligned}N_{z}&={\tfrac {(2*W_{z})-D_{f}-D_{n}}{D_{f}-D_{n}}}\\P_{z}&={\tfrac {T2}{E1*N_{z}-T1}}\end{aligned}}} NzPz=DfDn2WzDfDn=E1NzT1T2

请注意,这也意味着我们不需要片段着色器中的视口设置。我们只需要深度范围和透视矩阵项。

这种方法的诀窍如下:我们如何从 Pz 到完整的眼空间位置 P。要了解其工作原理,下面是一些快速的几何图形:
在这里插入图片描述

图中的 E 表示眼睛位置,这是眼睛空间的原点。P是我们想要的位置,Pz是我们拥有的位置。那么,我们需要什么才能从 Pz 中得到 P?我们所需要的只是一个指向 P 的向量方向,但 z 分量为 1.0。这样,我们只需将该向量乘以 Pz;结果必然是 P。

那么我们如何得到这个向量呢?

这就是顶点着色器的用武之地。在延迟渲染中,顶点着色器通常是一个简单的直通着色器,不执行实际计算,也不传递用户定义的输出。因此,我们可以自由地将其用于某些事情。

在顶点着色器中,我们简单地构造一个从原点到眼空间中每个顶点坐标的向量,即近平面上的相应位置。我们将向量的 Z 距离设置为 -1.0。这构建了一个矢量,该矢量指向眼睛空间中相机前方的场景。近平面的范围可以很容易地从对焦和纵横比中计算出来。

此值的线性插值将确保为片段计算的每个向量的 Z 值为 -1.0。线性插值也将保证它直接指向生成的片段。

我们本可以在片段着色器中计算出来,但何必呢?这需要向片段着色器提供视口转换(以便我们可以将 Wxy 转换为眼睛空间)。而且 VS 并没有做任何事情…

一旦我们有了这个值,我们只需将其乘以 Pz 即可得到我们的眼距位置 P。

下面是一些着色器代码。

// Vertex shader
// Half the size of the near plane {tan(fovy/2.0) * aspect, tan(fovy/2.0) }
uniform vec2 halfSizeNearPlane; layout (location=0) in vec2 clipPos;
// UV for the depth buffer/screen access.
// (0,0) in bottom left corner (1, 1) in top right corner
layout (location=1) in vec2 texCoord;out vec3 eyeDirection;
out vec2 uv;void main()
{uv = texCoord;eye_direction = vec3((2.0 * halfSizeNearPlane * texCoord) - halfSizeNearPlane , -1.0);gl_Position = vec4(clipPos, 0, 1);
}// Fragment shader
in vec3 eyeDirection;
in vec2 uv;uniform mat4 persMatrix;
uniform vec2 depthrange;uniform sampler2D depthTex;

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

1064 朋友数

solution 给出n个整数&#xff0c;统计可能的位数和&#xff0c;并按升序输出&#xff08;考虑用set实现&#xff09; #include<iostream> #include<set> using namespace std; int main(){set<int> st;int n, x, sum;scanf("%d", &n);while…

实验八 Linux虚拟内存 实验9.1:统计系统缺页次数成功案例

运行环境&#xff1a; VMware17.5.1 build-23298084Ubuntu 16.04LTS ubuntu版本下载地址Linux-4.16.10 linux历史版本下载地址虚拟机配置&#xff1a;硬盘一般不少于40G就行 内核版本不同内核文件代码也有出入&#xff0c;版本差异性令c文件要修改&#xff0c;如若要在linux6.7…

【智能算法应用】遗传粒子群算法(GA-PSO)求解选址问题

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】粒子群算法&#xff08;PSO&#xff09;原理及实现 经典PSO算法用于连续空间优化问题&#xff0c;选址问题作为组合优化问题&#xff0c;需要在离散空间中求解。因此&#xff0c;考虑遗传算…

springboot实现文件防盗链设计

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen &#x1f44b;&#x1f44b;&#x1f44b;hello&#xff0c;伙伴们好久不见&…

【JS 的数据类型】

JS 的数据类型 基本数据类型 js有8种基本数据类型&#xff0c;分别为&#xff1a;undefined、number、Object、null、Symbol、Boolean、String、BigInt&#xff1b; 其中Symbol和BigInt是ES6新增的数据类型&#xff1a; ● Symobol代表独一无二的值&#xff0c;可以用来代表对…

MFC重要的初始化函数InitInstance

MFC应用程序最早处理的类的初始化函数通常是CWinApp类的构造函数。CWinApp类是MFC应用程序的主类&#xff0c;负责整个应用程序的初始化和管理。 在MFC应用程序中&#xff0c;通常会创建一个派生自CWinApp类的应用程序类&#xff0c;例如CMyApp。在应用程序启动时&#xff0c;…

BGP综合大实验

实验要求 1.AS1中存在两个环回&#xff0c;一个地址是192.168.1.0/24&#xff0c;改地址不能在任何协议中宣告&#xff1b;AS3中存在两个环回&#xff0c;一个地址为192.168.2.0/24&#xff0c;该地址不能在任何协议中宣告&#xff0c;最终要求这两个环回可以ping通&#xff1b…

清华发布Temporal Scaling Law,解释时间尺度对大模型表现的影响

众所周知&#xff0c; 语言模型调参&#xff01; 预训练语言模型调参&#xff01;&#xff01; 预训练大语言模型调参&#xff01;&#xff01;&#xff01; 简直就是一个指数级递增令人炸毛的事情&#xff0c;小编也常常在做梦&#xff0c;要是只训练几步就知道现在的超参…

【ALM】ALM解决方案系列:质量保证

1软件的质量管理现状与痛点 在软件开发中&#xff0c;质量被视为软件产品的生命。保证软件质量&#xff0c;是贯穿整个软件生命周期的重要问题。如果在软件开发早期忽视质量管理&#xff0c;会导致软件项目管理出现问题。因此&#xff0c;重视并规范软件管理在软件项目管理中起…

springboot整合rabbitmq的不同工作模式理解

前提是已经安装并启动了rabbitmq&#xff0c;并且项目已经引入rabbitmq&#xff0c;完成了配置。 不同模式所需参数不同&#xff0c;生产者可以根据参数不同使用重载的convertAndSend方法。而消费者均是直接监听某个队列。 不同的交换机是实现不同工作模式的关键组件.每种交换…

Spring6 的JdbcTemplate的JDBC模板类的详细使用说明

1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明 文章目录 1. Spring6 的JdbcTemplate的JDBC模板类的详细使用说明每博一文案2. 环境准备3. 数据准备4. 开始4.1 从数据表中插入(添加)数据4.2 从数据表中修改数据4.3 从数据表中删除数据4.4 从数据表中查询一个对象4.5 从数…

C++--类与对象(二)

类的六个成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员 函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生…

crossover24中文破解版百度云免费下载 crossover永久免激活汉化包安装使用教程 crossover24激活码分享

原则上&#xff0c;我们不提倡各位使用破解版&#xff0c;这是处于对知识产权的保护&#xff0c;也是为了各位的长远利益。使用正版你可以获得更优质的服务和完善的产品功能。 但仍然有部分用户由于预算、使用习惯等原因&#xff0c;需要破解版。所以本文不讲原则&#xff0c;…

华为交换机配置导出备份python脚本

一、脚本编写思路 &#xff08;一&#xff09;针对设备型号 主要针对华为&#xff08;Huawei&#xff09;和华三&#xff08;H3C&#xff09;交换机设备的配置备份 &#xff08;二&#xff09;导出前预处理 1.在配置导出前&#xff0c;自动打开crt软件或者MobaXterm软件&am…

NIOS II实现LED流水灯以及串口输出(DE2-115开发板)

NIOS II实现LED流水灯以及串口输出&#xff08;DE2-115开发板&#xff09; 前言什么是Qsys?什么是NIOSII?注意事项1、管脚配置2、配置NIOSII时的连接3、注意中断配置好后是这样的4、注意名称的配置5、设置双功能引脚 NIOS II的报错代码以及效果演示流水灯输出到电脑串口助手 …

Python模块之Numpy(二)-- 生成各种随机数

对于 NumPy&#xff0c;与随机数相关的函数都在 random 模块中&#xff0c;其中包括可以生成服从多种概率分布随机数的函数&#xff0c;示例如下&#xff1a; #生成10个服从0-1均匀分布的随机数 arr1 np.random.random([2,5]) #也生成10个服从0-1均匀分布的随机数 arr2 np.ra…

数据分析思维——数据埋点笔记,以电商为例

数据埋点 数据分析前提是有数据&#xff0c;数据从哪里来&#xff0c;要选择采集哪些数据都需要考虑。如某些app上的商品推荐&#xff0c;是基于哪些信息来预判的呢&#xff1f;因此作为数据分析师有必要系统的了解用户行为到用户数据的整个过程 何为数据埋点 每当用户在客户端…

字节码基础

基本概念 java中的字节码&#xff0c;英文bytecode。是java代码编译后的中间代码格式。JVM需要读取并解析字节码才能执行相应的任务。java字节码是JVM的指令集。JVM加载字节码格式的class文件。校验之后通过JIT编译器转换成本机机器代码执行。 java字节码简介 1、java byteco…

【Qt 学习笔记】Qt常用控件 | 布局管理器 | 水平布局Horizontal Layout

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 布局管理器 | 水平布局Horizontal Layout 文章编号&…

leetcode刷题:买卖股票的最佳时机

题目 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大…