Visual LabINTERACTIVE LEARNING
OpenGL 与 3D第 3 / 6 章

纹理、变换与坐标系统

把纹理采样、矩阵变换和多个坐标空间串成一条链路。

OpenGLGLSL渲染管线
进阶预计 62 分钟查看源文 ↗

纹理

本章资料 learnopengl-纹理

基础介绍
纹理坐标映射

纹理图片的坐标是(0,0)到(1,1),映射到顶点定义的坐标上去

需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样。之后在图形的其它片段上进行片段插值(Fragment Interpolation)
纹理的环绕方式

当纹理坐标超出默认范围时,OpenGL的行为方式

提示

超出默认范围】的理解 在顶点数据中,我们如果设置了纹理坐标(uv坐标),它的范围一般是0~1,但是如果配置了一个uv坐标的值(1.5,2),这个时候采样就超出了纹理的默认范围(0,1),这个情况下的重复方式,就是纹理的环绕方式

嵌入表格

环绕方式 描述
GL_REPEAT 对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。
纹理过滤 (纹理的采样方法)
线性过滤 GL_NEAREST(Nearest Neighbor Filtering)

提示

官方解释: 它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。 一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大

我的理解: 线性计算纹理像素点最终的颜色(按距离比例计算采样坐标周围4个像素点的颜色,计算出一个最终结果)更圆滑

邻近过滤 GL_NEAREST(Nearest Neighbor Filtering)

我的理解:采样坐标点离哪个像素最近,就返回哪个像素的颜色,锯齿感更强

多级渐远纹理
什么是多级渐远纹理?
为什么需要多级渐远纹理?

提示

  1. 纹理的分辨率是固定的
  2. 远的物体和近处的物体的顶点数量是相同的,但是渲染出来的像素点数量是不一样的(小的物体像素点少,片元少)
  3. 如果使用相同的纹理采样就是导致一个远处物体的像素点对应到纹理的一大片像素点,拾取的颜色失真会十分严重
  4. 多级渐远纹理就是把纹理根据远近的级别,对纹理进行变换
纹理单元

可以给片元着色器设置多个纹理,默认是0 至少有16个可以的纹理单元(0-15)

变换

向量

🏆 提示

向量是有方向有大小的量,两个特征,有大小 有方向,表示的是一个空间的方向

向量的长度
向量的运算
向量与标量(常量)运算
  • 加减乘除 向量每个分量和标量分别计算,得到新的向量

📍 提示

减和除都只能向量-/标量, 标量-/向量没有意义 个人理解的原因:常量是一维的,而向量是多维的,多维可以在每个维度上运算,即多维可以降维,单维无法升维

  • 向量取反 乘-1即可
向量之间的运算
  • 加减法

对应的分量加减即可

🏆 提示

加法表示的几何意义是两个向量平移后首尾相连,起点到终点的连线 减法表示的几何意义是被减数的向量指向减数向量的连线

  • 向量的乘法
  1. 点乘

定义:点乘等于它们的数乘结果(两个向量长度相乘)乘以两个向量之间夹角的余弦值

v⋅k=||v||⋅||k||⋅cosθ

🎉 提示

如果两个都是单位向量,则点乘的结果就是两个向量之间夹角的cos值,cos0是1,cos90是0,由此可以判断向量是否平行或相交

应用:单位向量的点乘可以用来计算两个向量之间的角度,从而判断垂直:

每个分量相乘结果相加,再通过反余弦函数即可求出角度,如果不是单位向量,可以先标准化,再去计算!!

13651734032

  1. 叉乘

🥛 提示

需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量,如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量

计算

矩阵

🥖 提示

定义:一个矩形的数字符号表达式 (i行j列),所谓的行列式

矩阵的加减:

🎉 提示

和向量同样的道理,只能矩阵加减标量,或者维度相同的矩阵,才是有意义的

矩阵的数乘

❤️ 提示

每个元素的与标量相乘得到新的矩阵,这个标量就相当于一个放大因子

矩阵的乘法

两个限制

只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。

🎼 提示

因为乘法的法则是拿左侧矩阵的第n行的每个数分别乘以右侧矩阵的第m列,结果相加,得到坐标为(n,m)的结果 所以:左侧矩阵的每一行的元素个数(列数),需要与右侧矩阵的每一列的元素数量(行数)相等,也就是左侧矩阵的列数需要与右侧矩阵的行数相等,两个矩阵才能相乘

矩阵相乘不遵守交换律(Commutative),也就是不能交换位置

✍️ 提示

也就是左乘和右乘的结果不同

但是,矩阵遵守乘法结合律,所以可以通过一个矩阵的逆矩阵来求出变换矩阵

矩阵与向量相乘

🍰 提示

向量可以看成N维的矩阵(只有一列) Nx1的矩阵

所以可以和一个MxN的矩阵相乘

单位矩阵: 对角线是1,其他值是0的矩阵

矩阵的作用

threejs中的matrix是一个列秩的数组,索引0,5,10的值是x,y,z的缩放因子

缩放

🎁 提示

对角线的值就是缩放的分量,

  • 均匀缩放 每个缩放的分量相等
  • 不均匀缩放,分量不相等
位移

threejs中的matrix是一个列秩的数组,索引12,13,14的值是x,y,z的位移值

📍 提示

单位矩阵中,使用齐次坐标实现位移,在第4个分量(w)上,书写需要位移的分量,通过矩阵相乘即可

旋转 略复杂,使用四元数
矩阵的组合

矩阵变换的力量是可以把多个变换组合到一个矩阵中

  • 注意1:矩阵的乘法不遵守乘法交换律

所以这里矩阵A * B 得到的结果与向量D相乘,即 A * B *D 由于B离矩阵D最近,所以实际的变换顺序是 先应用了矩阵B,然后才是矩阵A

亦即:

矩阵左乘向量,因为向量总是在最后,所以越在后面的矩阵,越早进行变换

  • 注意2:

缩放的顺序很重要,先缩放再位移和先位移再缩放得到的结果不一样!!

在组合矩阵时,先进行缩放操作,然后是旋转,最后才是位移,否则它们会(消极地)互相影响。比如,如果你先位移再缩放,位移的向量也会同样被缩放

坐标系统

实际上,每经过一个矩阵变换,就已经是进入另一个坐标系统了

坐标类型
物体坐标 又称局部坐标 (local coordinate or object coordinate)

🚅 提示

每个物体建模的时候都有自己的坐标系

世界坐标 (world coordinate)

🎨 提示

3D世界的坐标,其他的模型都按照这个坐标的位置放置,是经过model-matrix转换得到

投影坐标 视图坐标 观察坐标 view coordinate /eye coordinate

🎼 提示

投影就是把坐标转换到相对于观察者的视角下的坐标,也就是以观察者为坐标原点的坐标,经过观察矩阵得到

view-matrix

观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标

裁剪坐标 clip coordinate

projection-matrix 远近平面-fov 以及对z的非线性转换

🎁 提示

把特定的坐标转换成标准设备坐标的过程,舍弃标准设备坐标范围外的坐标,经过投影矩阵(projection matrix)得到, 投影矩阵与相机的平截头体的参数定义相关,由这些参数计算得来,用来计算深度缓冲的变换 平截头体用来获取z轴的范围,以及xy的范围 这个投影矩阵其实还包含了对z值的精度非线性调整 这给了近处的物体很大的深度精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了 这个矩阵后得到的仍然是观察空间的坐标 得到的已经是裁剪空间了,因为虽然视角上仍然是观察空间的,但是坐标的值已经发生变换

屏幕坐标 screen coordiate

🎨 提示

经过viewport transform 最后变换出来的坐标将会送到光栅器,将其转化为片段,提供给下一阶段使用(片段着色器),最后得到屏幕像素(和屏幕坐标有区别,屏幕坐标不一定是整数)

几个坐标的关系概述

以上1 -4步发生在顶点着色器中,顶点着色器输出的是经过投影矩阵计算得到的裁剪空间的顶点坐标,被赋值到顶点着色器中的gl_Position ,然后进入下一阶段的管线,OpenGL将会自动进行透视除法和裁剪(根据相机定义的远近平面和fov),经过视口变换后,最后才会传递到片元着色器,片段着色器后才是混合与深度测试(也有提前深度测试,发生在片段着色器之前

OpenGL然后对裁剪坐标执行透视除法从而将它们变换到**标准化设备坐标,**然后用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标

透视除法: 顶点着色器之后,OpenGL会自动执行的一步,将xyz向量分别除以齐次向量w,

裁剪空间中坐标,只是计算了值,并没有进行裁剪,裁剪发生再透视除法之后,超出范围的值会被丢弃

这里具体过程下次复习时,需要结合 【十、高级OpenGL】中关于深度的知识

  • 总结关于渲染管线的,所知道的概念的一个执行流程
左右手坐标系

Threejs使用右手坐标系

右手坐标系

👍 提示

  1. 沿着正y轴方向伸出你的右臂,手指着上方。
  2. 大拇指指向右方。
  3. 食指指向上方。
  4. 中指向下弯曲90度。
左手坐标系

👍 提示

  1. 沿着正y轴方向伸出你的左臂,手指着上方。
  2. 大拇指指向右方。
  3. 食指指向上方。
  4. 中指向下弯曲90度。

🥛 提示

左右手坐标系的区别:z轴的方向相反

深度测试

深度缓冲 z-buffer 储存所有的深度信息。更详细的描述查看【十、高级OpenGL/深度测试】

🎨 提示

输出时将每个片段的深度信息和z-buffer对比,如果片段在后面,z值会被抛弃,这个过程被称为深度测试 深度测试只是测试深度值,从而决定是否去更新颜色缓冲中的值 渲染的过程中有几个重要的步骤需要明确,深度测试深度缓冲写入颜色缓冲写入深度测试】如果开启了,只有通过深度测试的片段的颜色才能写入颜色缓冲中,没有开启,则每次都会写入颜色缓冲深度缓冲写入】把当前片段的**深度值,更新到深度缓冲中去,不开启时,即使深度测试通过了,也不更新 【颜色缓冲写入】不开启深度测试,或者深度测试通过的情况下会把片段颜色写入到颜色缓冲**中,(不开启深度缓冲写入也不影响颜色缓冲写入)