在学习了一些games101的课程之后,我还是有点困惑,对于计算机图形学的基础知识,总感觉还是缺乏一些更加全面的认识,幸而最*在做games101的第五次作业时,查询资料找到了scratchpixel这个网站,看了一些文章,终于把脑子里的一团乱麻组织起来了,也就有了这篇关于图形学的第一篇博客。

想要更好的理解这篇博客,强烈推荐先学习games101中关于transformation,rasterization和ray tracing的第一部分

以下内容参考:https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/perspective-projection.html

https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points.html

https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-generating-camera-rays/generating-camera-rays.html

如果内容有误,欢迎指出

光栅化与光线追踪的理解

在图形学中一个很重要的问题就是,我们如何把一个三维空间中的物体,去展示到一个二维*面上。

这件事其实我们可以分成两步去做,第一步是解决visibility的问题,第二步是解决shading的问题。

怎么理解这两种方法呢?我们先想象,现在在一个无穷大的场景里面,有很多物体,还有一个摄像机,摄像机面前一定距离有个*面,现在我们想知道通过摄像机在它面前这个二维*面上看到的所有三维物体的形象。

光栅化方法,就是把场景内的所有物体先都投影到这个*面上,然后进行着色shading,这里就是光栅化比较麻烦的地方,我们需要设计着色模型(games101中的bling-phone模型),考虑到物体的空间先后顺序我们需要z-buffer,考虑到阴影效果我们需要shadow map,以及纹理贴图来帮助我们。

而光线追踪,模拟了光线在场景中的传播,在光线追踪中,从观察点(或相机)出发的光线被跟踪以确定它们与场景中的物体相交点,然后计算反射、透射和光照等效果,是一种基于物理的渲染方法。

通过上述描述,我们可以发现光栅化可以短时间内处理大量物体,但在光照效果等方面不如光线追踪,而光线追踪速度较慢,因为需要考虑到光线的种种复杂的传播情况,这也就导致光栅化主要用于实时渲染,而光线追踪主要用于离线渲染

我们再来考虑针对这两种方法的空间变换,我们会发现这两种方法其实在干相反的事情,光栅化的坐标变换是把三维变二维,光线追踪是需要摄像机到*面上每个像素点连接的光线,然后求光线与物体的交点,实际上需要我们把二维的点转换为三维,具体的过程我们在接下来的部分阐述

不同空间的介绍

要理解光栅化与光线追踪的空间变换,我们首先要搞清楚几个空间的定义

世界空间:世界空间就是我们之前提到的无穷大的场景,所有的点的定义最初都是在这个三维空间中,与之对应的是世界坐标系

相机空间:相机空间就是以相机为坐标原点的坐标系建立的空间,我们可以使用games101中提到的camera transformation对应的矩阵,实现世界坐标系到相机坐标系之间的转换,一般来说,我们的相机的位置在(0,0,0)这个坐标原点,朝向世界坐标系的-z方向。

屏幕空间:屏幕空间是一个二维空间,相机空间中的点经过透视变换可以到image plane上,然后我们根据画布(canvas)的范围,可以在image plane这个无穷大的*面上划分出屏幕空间

NDC空间:把屏幕空间坐标系标准化到【0-1】的空间中

栅格空间:NDC空间中的2D点被转换为2D像素坐标,为此,我们将标准化点的 x 和 y 坐标乘以像素宽度和高度,从 NDC 到栅格空间还需要反转该点的 y 坐标。 由于像素坐标是整数,因此最终坐标需要四舍五入到最接*的以下整数。

下面的图很形象地展示了上面的三个空间的区别:

其实我们电脑的屏幕就相当于栅格空间,不同的分辨率代表着不同的水*像素数与竖直像素数,也就代表着不同的像素宽度与高度,代表着不同的栅格空间计算方法

注意我们这里的讨论实际上简化了屏幕坐标系与NDC坐标系,games101中的投影变换实际上就是直接从相机空间变换到了NDC空间或者说变换到了裁剪空间然后经过齐次除法到NDC空间,空间是三维的,因为我们不仅要完成投影,还需要记录点的z坐标信息来进行深度缓存等等,确定物体的先后关系:



可以看到二维空间与三维空间的区别就是二维空间舍弃了z坐标,它们在xy坐标方面做的都是投影,三维空间相当于多做了z坐标的投影

注意games101其实并没有过多的讨论裁剪空间,而是推导了一个矩阵直接转换到NDC空间,这也是未来博客中要探讨的内容,同时z-fighting的现象也未提及(*远*面距离过大导致的问题)

在我们这里的讨论屏幕坐标系与NDC坐标系是二维的,这样可以把光栅化与光线追踪的visibility处理统一起来,因为光线追踪中我们并不需要使用投影矩阵,就不存在光栅化中的视锥体与立方体,只需要二维的屏幕空间。

光栅化做的就是从世界空间到栅格空间,实现每个物体的visibility,然后着色

而光线追踪做的是实现每个光线的可视化,然后着色,需要我们从栅格空间转换到世界空间。

光栅化中的空间变换

世界空间到观察空间或者说是摄像机空间:

这一步很简单,和games100课程中的一样,实际上就是进行坐标系变换,进行旋转与*移,值得注意的是默认,相机正对的是z的负半轴,所以我们可见的物体的z坐标也是负的

那么在进行从观察空间到屏幕空间的过程中,我们要进行投影,将3维的点投影到2维的屏幕上:



这样我们根据相似关系,可以得到屏幕空间与观察空间的xy坐标映射,注意这里我们假设和**面的距离是1,可以得到:

\(\begin{array}{l} P'.x = \dfrac{P_{camera}.x}{P_{camera}.z}\\ P'.y = \dfrac{P_{camera}.y}{P_{camera}.z}. \end{array}\)

但是注意我们之前提到过物体的z坐标是负的,所以这样进行除法会导致xy发生颠倒,因此,我们要再加上负号:

\(\begin{array}{l} P'.x = \dfrac{P_{camera}.x}{-P_{camera}.z}\\ P'.y = \dfrac{P_{camera}.y}{-P_{camera}.z}. \end{array}\)

之前提到过屏幕空间是有范围的,由宽度与高度决定,所以我们要加上这个限制

\(\text {visible} = \begin{cases} yes & |P'.x| \le {W \over 2} \text{ or } |P'.y| \le {H \over 2}\\ no & \text{otherwise} \end{cases}\)

从屏幕空间到NDC空间,需要我们把原来在[-width/2]--[width/2]和[-height/2]到[height/2]之间的点转换到[0-1]的点(有的NDC空间采用的是[-1-1]):

\(\begin{array}{l} P'_{normalized}.x = \dfrac{P'.x + width / 2}{ width }\\ P'_{normalised}.y = \dfrac{P'.y + height / 2}{ height } \end{array}\)

从NDC空间到栅格空间,我们需要乘以像素宽度与高度,因为我们认为像素点是一个个小矩形来进行栅格化,并且取整,这一步也被称为viewport变换

\(\begin{array}{l} P'_{raster}.x = \lfloor{ P'_{normalized}.x * \text{ Pixel Width} }\rfloor\\ P'_{raster}.y = \lfloor{ P'_{normalized}.y * \text{Pixel Height} }\rfloor \end{array}\)

同时我们注意到栅格空间的y是向下的,所以改变方向取反:

\(\begin{array}{l} P'_{raster}.x = \lfloor{ P'_{normalized}.x * \text{ Pixel Width} }\rfloor\\ P'_{raster}.y = \lfloor{ (1 - P'_{normalized}.y) * \text{Pixel Height} }\rfloor \end{array}\)

以上我们就将三维空间中的点可视化到了二维空间中

光线追踪中的空间变换

针对光线追踪,我们需要的是可视化我们的光线,因为我们需要实际计算光线与物体相交,所以我们的光线需要世界坐标下的三维表示,简单来说一个光线可以如下定义:



我们已经知道了O的位置,它就是坐标原点(0,0,0)

现在需要知道P的位置

现在考虑下面这张图:

第一步转换到NDC空间:

\(\begin{array}{l}
PixelNDC_x = \dfrac{(Pixel_x + 0.5)}{ImageWidth},\\
PixelNDC_y = \dfrac{(Pixel_y + 0.5)}{ImageHeight}.
\end{array}\)

注意这里我们只考虑光线穿过像素点的中心,所以我们要加上0.5,然后再分别除以栅格空间的宽度与高度

第二步转换到屏幕空间:

注意屏幕空间的y轴发生了反转,同时由于我们是将原本的图像压缩到了-1-1的这个空间中,所以我们现在要将其还原回去,即宽度从[-1--1]转换成[-width/2---width/2],高度同理,我们使用宽高比以及fov角度来表示就可以得到:

\(\begin{array}{l} PixelCamera_x = (2 * {PixelNDC_x } - 1) * ImageAspectRatio * tan(\dfrac{\alpha}{2}),\\ PixelCamera_y = (1 - 2 * {PixelNDC_y }) * tan(\dfrac{\alpha}{2}). \end{array}\)

其中tan(a/2)表示图像高度的一半,因为我们在这里假设**面坐标是-1

最后转换到世界空间中,只需要加入z坐标-1,然后从相机空间转换到世界空间即可

上述过程也就是games101第五次作业求光线的实现

games101-1 光栅化与光线追踪中的空间变换的更多相关文章

  1. OpenGL中的空间变换

    OpenGL中的空间变换          在使用OpenGL的三维虚拟程序中.当我们指定了模型的顶点之后.在屏幕上显示它们之前,一共会发生3种类型的变换:视图变换.模型变换.投影变换.        ...

  2. 3D游戏中各种空间变换到底是怎么回事

    每一个游戏可以呈现炫丽效果的背后,需要进行一系列的复杂计算,同时也伴随着各种各样的顶点空间变换.渲染游戏的过程可以理解成是把一个个顶点经过层层处理最终转化到屏幕上的过程,本文就旨在说明,顶点是经过了哪 ...

  3. OpenGL中着色器,渲染管线,光栅化

    https://www.zhihu.com/question/29163054   光栅(shan一声)化(Rasterize/rasteriztion).这个词儿Adobe官方翻译成栅格化或者像素化 ...

  4. 回顾Games101图形学(一)几何变换中一些公式的推导

    回顾Games101 chatper1 - 6 前言 本文只写回顾后重新加深认识的知识 透视除法的意义 经过MVP矩阵之后,将模型空间下某点的坐标,转换成了裁剪空间下的坐标,此时因为裁剪空间的范围是x ...

  5. opengles2.0之图元装配和光栅化

    光栅化的过程就是把三维世界中的物体转换成屏幕上像素的过程. glGetfloatv();    --------v表示的是数组 gles2.0里面有两种绘图命令.glDrawArrays和glDraw ...

  6. DirectX11 With Windows SDK--11 混合状态与光栅化状态

    前言 虽然这一部分的内容主要偏向于混合(Blending),但这里还需提及一下,关于渲染管线可以绑定的状态主要有如下四种: 光栅化状态(光栅化阶段) 采样器状态(像素着色阶段) 混合状态(输出合并阶段 ...

  7. OpenGL ES 3.0 图元组合和光栅化(三)

    图元是能够被OpenGL ES 绘制的几何物体,如三角形.线条或者精灵.在图元组合过程 中,对每个图元必须判断是否位于投影 截体内,如果图元不完全在平截体内部,将被视图平截体剪贴,如果完全在平截体外, ...

  8. GPU大百科全书 第二章 凝固生命的光栅化

    光栅化——死神来了……   前言:在上一期的GPU大百科全书里,我们目睹了可爱的香草从抽象世界走向现实,从方程还原成实体的全过程.可以说香草活了,因为几何单元,我们赋予了她完整的灵魂. 如果你正在为G ...

  9. 光栅化规则(Rasterization Rules)

    光栅化规则不是唯一的,只要能满足在扫描线填充过程中,对于一条分割线两边的像素能够被不重复不遗漏地填充即可. 在gdi3d中目前使用的是下面光栅化规则: xLeft_int=ceil(xLeft-0.5 ...

  10. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 前言 对于3D游戏来说,合理的光照可以让游戏显得更加真实.接下来会介绍光照的各种分量,以及常见的 ...

随机推荐

  1. 2023CCPC大学生程序设计竞赛-nhr

    新生菜菜第一次参加这种大型比赛,还是有点紧张的,CCPC我们队就A了三题,铜牌.第一道,以为是签到,然后就交给clk了,我和crf看下一道过的题比较多的,然后感觉是一个滑动窗口,另一道题是纯数学公式. ...

  2. linux 查看进程使用的内存大小

    你可以使用 ps 命令结合 grep 命令来查看进程使用的内存大小.以下是示例代码: ps aux | grep <进程名> 这个命令会列出所有匹配 <进程名> 的进程,并显示 ...

  3. 借助 mkcert 和批处理命令生成局域网证书

    借助 mkcert 和批处理命令生成局域网证书 自动获取ipv4,一键生成很方便 cd /d %~dp0 ipconfig |find "IPv4" > ipv4 set / ...

  4. javascript报错: TypeError: (0 , _api_music.default) is not a function

    报错截图 错误原因 从其他文件引入变量时,未添加花括号 错误写法 解决方案 效果图 至此问题解决

  5. [golang]使用mTLS双向加密认证http通信

    前言 假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信.为了避免在公网进行 http 明文通信造成的信息泄露,nginx与客户端之间的通信应当使用 https 协议 ...

  6. 知识图谱(Knowledge Graph)根本概念

    目录 知识图谱 定义 基础概念: 知识图谱构建的关键技术 知识图谱的构建 实体命名识别 知识抽取 实体统一 指代消解 知识图谱的存储 RDF和图数据库的主要特点区别 知识图谱能干什么 反欺诈 不一致性 ...

  7. 【NestJS系列】核心概念:Module模块

    theme: fancy highlight: atelier-dune-dark 前言 模块指的是使用@Module装饰器修饰的类,每个应用程序至少有一个模块,即根模块.根模块是Nest用于构建应用 ...

  8. 《SQL与数据库基础》06. 函数

    目录 函数 字符串函数 数值函数 日期函数 流程函数 本文以 MySQL 为例 函数 函数是指一段可以直接被另一段程序调用的程序或代码. 要查看函数操作的结果,可以使用 SELECT 函数(参数); ...

  9. C++算法之旅、04 基础篇 | 第一章

    常用代码模板1--基础算法 - AcWing ios::sync_with_stdio(false) 提高 cin 读取速度,副作用是不能使用 scanf 数据输入规模大于一百万建议用scanf 快速 ...

  10. 使用 OpenTelemetry 构建 .NET 应用可观测性(2):OpenTelemetry 项目简介

    前世今生 OpenTracing OpenTracing 项目启动于 2016 年,旨在提供一套分布式追踪标准,以便开发人员可以更轻松地实现分布式追踪. OpenTracing 定义了一套 Traci ...