todaylg

《Mathematics for 3D Game Programming and Computer Graphics》读书笔记 (上)

2019-07-24 todaylg读书流水

学习的过程中深感自己的地基不够牢固,决定补习巩固一波基础知识。

这本书涵盖了图形学相关的各方面数学知识,希望通过阅读这本书籍能补回丢掉的数学知识,也可以提高自己英语书籍的疯狂查词典阅读能力。

本着实践为主的目的,尽量能实践的就搂个Example,慢慢整吧~

公式编辑使用LateX,在线预览可在这里,养成好习惯从现在开始,强烈推荐。

Chapter 1:渲染管线

首先复习一波最重要的渲染管线(From:RTR4):

定义:给定虚拟摄像机、三维物体、光源等的条件下,生成或者渲染出一幅二维图像的过程。

阶段划分:

  • 应用程序阶段(Application) :开发者能够完全控制的阶段,通常在此阶段实现视椎体裁剪、碰撞检测、输入检测、加速算法、动画等。

  • 几何处理阶段(Geometry Processing)

    • 顶点处理(不同坐标系的转换)

    • 图元装配(顶点组成图元)

    • 裁剪(对图元进行视体裁剪,舍弃无法成像的图元)

  • 光栅化阶段(Rasterization):由图元输出片元,三维顶点转二维像素

    • 三角形设定阶段(Triangle Setup):计算三角形表面到差异和三角形表面到其他相关数据

    • 三角形遍历阶段(Triangle Traversal):进行逐像素检查操作,找到哪些采样点或者像素位于三角形中到过程

  • 像素处理阶段(Pixel Processing)

    • 像素着色(Pixel Shading):所有逐像素的着色计算都在像素着色阶段进行

    • 融合阶段(Merging):合成当前缓冲区中由之前的像素着色阶段产生的信息(颜色/深度/Alpha/模板/帧缓存)

图形处理

OpenGL支持绘制的图元有10种:

  • Points

  • Lines、Line Strip、Line Loop

  • Triangles、Triangle Strip、Triangle Fan

  • Quads、Quad Strip、Polygon

WebGL只支持其中的7种图元(除开Quads、Quad Strip、Polygon)

CPU与GPU间的通信:

CPU => App(Rendering Commands) => OpenGL/DX => Graphics Driver (Command Buffer)=> GPU

CPU内存与GPU内存的通信

CPU内存 => Vertex/Texture Data、Shader Parameters => GPU内存 VRAM(Video Random Access Memory),存储例如Image Buffer、Depth/Stencil Buffer、Texture Maps、Vertex Buffers等数据,并支持front/back buffer swap

顶点变换

坐标空间:

  • 局部空间(Local Space/Object Space):局部坐标是对象相对于局部原点的坐标

  • 世界空间(World Space):世界空间坐标相对于世界的全局原点

  • 观察空间(View Space/Eye Space):使每个坐标都是从摄像机角度进行观察得到的

  • 裁剪空间(Clip Space):裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上

  • 屏幕空间(Window Space/Screen Space):视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内

坐标变换:

  • 模型(Model)矩阵:局部空间=>世界空间

  • 观察(View)矩阵:世界空间=>观察空间

  • 投影(Projection)矩阵:观察空间=>裁剪空间

MVP矩阵将Object Space转为齐次的Clip Space之后,最后经过视口变换(Viewport transform)最终转到 Window Space:

  • 齐次的Clip Space中顶点需转换为NDC(Normalized Device Coordinates) 即x、y、z => [-1,1]

  • 最终的viewPort Transform将规范化坐标映射到视图所覆盖的像素坐标实际范围,z坐标映射到浮点范围 => [0,1]

片元计算

片元集合了经过GPU计算的深度信息、插值后的顶点信息、插值后的UV坐标信息、像素本身的位置信息。

Face Culling => Rasterization => Fragment Shading => Fragment Operations

Fragment Operations:

  • 像素所有权测试(pixel ownership test):像素所有权测试只是确定片元是否位于当前可见的viewport区域内,比如其他窗口挡住了当前viewPort的一部分的情况,此测试不可diable

  • 裁剪测试(scissor test):应用程序可以在视图端口中指定一个矩形,称为剪切矩形,任何落在剪切矩形外的碎片都将被丢弃。

  • 透明度测试(alpha test):alpha测试将片段的最终alpha值与应用程序预先设置的常量值进行比较

  • 模板测试(stencil test):根据模板缓冲的值决定是否通过

  • 深度测试(depth test):比对当前片元的深度值与深度缓冲中的深度值

  • 混合(Blending):当所有的测试通过后,片元的最终颜色经过混合(Blending)最终输出到 Image Buffer。Blending操作会通过结合当前片元的最终颜色和片段所在位置的图像缓冲区中已经存储的颜色来计算新的颜色(可以实现透明度效果之类的效果),Alpha值也是如此。

Chapter 2:向量

点乘

点乘公式:

image

image

证明过程:

由三角函数公式:

image

可得:

image

化简即可得。

常用作用:

1.点乘结果正负表示两个向量的相对方向

  • PQ = 0 : 二者互相垂直

  • PQ > 0 : 二者同向

  • PQ < 0 : 二者反向

    2.点乘结果表明两个向量角度的接近程度 (即cos(a)的大小)

向量投影

P在Q上的投影(projection)向量:

image

其中Q/|Q|代表Q向量的单位长度。

向量相减可得垂线(perpendicular)向量:

image

叉乘

叉乘公式:

image

image

也可写作:

image

或伪行列式(好记):

image

其中i=(1,0,0)、j=(0,1,0)、k=(0,0,1)

常用作用:

叉乘返回垂直于两个向量的新向量,常用与求解表面法线。

向量空间

施密特正交化(实践中暂未发现要用到施密特正交化的地方。。)

Chapter 3:矩阵

矩阵相乘:

image

  • 列行:左列=右行为相乘前提条件

  • 行列:左i行 * 右j列 = Cij

最简形矩阵条件:

  • 对于每一个非零行,最左边的非零项(前导项)为 1

  • 无零行在有零行之前

  • 前导项所在的列上不再有其他的非零项

  • 任意一对非零行i1,i2(i2>i1)对应的包含其前导项的列j1,j2需要满足j2>j1(即列位置在右方)

初等行变换:

  • 交换矩阵的某两行

  • 矩阵的某一行乘以非零数

  • 将矩阵的某一行的倍数加到另一行

初等变换化简矩阵步骤

  • A.将第一列中最大数值(绝对值)的行交换至第一行

  • B.将第一行的先导项化为1(乘系数)

  • C.通过相乘相加第一列的方法将其他行的首个元素化为0

  • D.继续将第二行的先导项化为1

  • F.通过相乘相加第二列的方法将其他行的第二个元素化为0(包括第一行)

  • G.如此循环

求逆矩阵:

前提:含有全零行的矩阵不可逆

  • A.构造分块矩阵(A|E),E为单位阵

  • B.对矩阵(A|E)实施初等行变换,将其化为行最简形矩阵(即A=>E)

  • C.如果A不能行等价于E,则A不可逆。若A能行等价于E,则A可逆,且E就行等价于A^-1

行列式

计算公式:

image

其中:

image

M^{i,j}表示从矩阵M中去掉第i行和第j列后的矩阵。

上面两个式子递归即可求得结果,比如2x2矩阵:

image

3x3矩阵:

image

矩阵可逆的充分必要条件为:

image

方程组有非零解的充分必要条件是

使用行列式计算逆矩阵的方法:

image

初等行变换对行列式结果的影响:

  • 交换两行 => 行列式结果取反

  • 行乘系数a => 行列式结果也乘系数a

  • 行与行之间相加对行列式结果没有影响

特征值与特征向量:

n阶矩阵M,若数λ和n维非零列向量α使下列的关系式成立:

image

那么数λ称为矩阵M的特征值,非零向量α称为M对应于特征值λ的特征向量,比如:

image

那3就是矩阵M的特征值,α是M对应于特征值3的特征向量。

将式子移到左边可得:

image

α为非零向量的话,(M-λI)必须不可逆,即:

image

求得非零解λ后重新代入方程:

image

即可求得特征向量α。

举例说明,例如:

image

矩阵M-λI即为:

image

可得:

image

所以矩阵M的特征值为λ1=2、λ2=-2。将其代入方程求特征向量可得:

image

image

可求得特征向量α1与α2:

image

image

对角化

如果V1,V2,…Vn是一个n×n矩阵M的线性无关特征向量,那么相似变换矩阵A可以表示为:

image

相似对角化矩阵M为:

image

其中λ1、λ2…λn为矩阵M的特征值。

Chapter 4:变换

正交矩阵

正交矩阵的条件:

image

正交矩阵保持其长度和角度,所以只受旋转和反射变换影响。?

平移(Translate)矩阵:

image

缩放(Scale)矩阵:

image

旋转(Rotation)矩阵:

首先求二维旋转矩阵,由球坐标系公式:

image

假设旋转的角度为θ,由三角函数正弦余弦公式可得:

image

image

代入x、y可得:

image

image

使用矩阵表示即为:

image

绕任意点的二维旋转可以通过级联两次位移矩阵计算(旋转点位移至原点 => 绕原点旋转 => 旋转点移回原位)

通过齐次坐标引入绕三个轴的三维旋转矩阵:

image

image

image

围绕任意轴的旋转:

有两种思路:

  • 通过将旋转轴旋转到与X/Y/Z某一轴重合,再执行基本旋转,之后再执行反向旋转恢复旋转轴方向。

  • 计算旋转点在旋转轴上的平行和垂直分量,再分别计算分量绕A轴旋转角度后的所增加的分量

求点P绕任意轴A(单位向量,|A|=1)旋转的旋转矩阵:

首先求得在轴A上的投影及垂线分量:

image

image

垂线分量(与A垂直)绕A旋转θ后的结果为:

image

再加上A轴上的投影分量:

image

最后代入前面的点乘叉乘的矩阵表示可得:

image

最终围绕任意旋转轴A的旋转矩阵:

image

转换为Shader代码在顶点着色器中使用:

mat4 rotationMatrix(vec3 axis, float angle) {
    axis = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float oc = 1.0 - c;

    return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
                oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
                oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
                0.0,                                0.0,                                0.0,                                1.0);
}

vec3 rotate(vec3 v, vec3 axis, float angle) {
    mat4 m = rotationMatrix(axis, angle);
    return (m * vec4(v, 1.0)).xyz;
}

齐次坐标:

三维表示的点和向量可能会被搞混,齐次坐标可以解决这个问题,其对三维空间中的点和向量的表示都是四维的:

任意点(w分量为1):

image

任意向量(w分量为0):

image

两点相减即表示向量(w分量变为0,不受平移矩阵影响)。

所有仿射变换(具有保持直线的特性)都可以借助齐次坐标表示成矩阵相乘的形式。

比如三维表示的平移变换为矩阵加法,采样齐次坐标的话就可以统一表示为矩阵乘法。

3X3的变换矩阵M和3维平移矩阵T可以结合为4X4的变换矩阵F:

image

法向量矩阵:

在经过非正交矩阵变换之后,法向量通常会不再垂直于表面。

法向量(N)与切向量(T)互相垂直,而切向量经过变换后仍是切向量,则可得:

image

由N·T=0(互相垂直)=> G·MT = T => G·M = I(I为单位阵) =>

image

因此法线变换矩阵应该使用变换矩阵的逆转置矩阵

四元数:

介绍可看之前LGL总结的Math部分,这里不再重复,了解一下就好..

绕A轴旋转角度θ的四元数表示(没有对比就没有伤害…):

image

应用到点P:

image

相当于3x3矩阵:

image

球内插值(0<=t<=1):

image

Chapter 5:几何体

点线距离(直接计算其垂直分量即可):

image

面:

给定法线向量N和点P,则由二者确定的平面需满足(Q为面内点):

image

再由平面方程:

image

可知:A、B、C为法向量N的xyz分量,D=-N·P:

image

当:

  • d=0时:点Q在平面中

  • d>0时,点Q在正面上

  • d<0时,点Q在背面下

线面交点:

设直线过点S,平行于向量V,则直线方程可以表示为:P(t) = S + tV,可得与面交点方程:

image

可得:

image

视椎体:

假设水平视野的角度为α,相机到视椎体近平面的距离e为:

image

垂直视野角度β可以表示为:

image

透视插值校正:

深度插值公式:

image

结果表明在透视投影中,深度值z1和z2需要通过对它们的倒数进行线性插值来正确地插值:。

顶点插值校正:

image

透视矩阵:

假设点P(Px,Py,Pz,1)是在视椎体中的一个点(视椎体中n为负),可得在视椎体近平面的坐标x,y:

image

同理y:

image

透视矩阵需要将视椎体映射成齐次裁剪空间的立方体,即需要将X轴[l,r] => [-1,1]、Y轴[b,t]=>[-1,1],可得关系式:

image

image

代入近平面上点xy的关系式并化简后可得:

image

image

Z轴坐标的变换复杂一些,为了保证线性,我们需要构造z的倒数关系式:

image

由-n => -1、-f => 1可得:

image

可计算出A、B:

image

最终z坐标的变换公式:

image

对xyz化简后可得:

image

image

image

使用矩阵表示即为:

image

正交矩阵:

与投影矩阵同理,只不过x’/y/’z’与x/y/z之间的关系在正交投影下的关系式是直接相等的:

image

image

Z轴也是同理:

image

最终的变换矩阵即为:

image

斜视锥体深度投影和裁剪:

涉及反射场景的情况一般是通过一个反射平面,反射主相机生成一个虚拟相机对场景进行渲染。

需要注意的是当有物体横跨反射平面的时候,渲染反射的虚拟相机中的场景就会出现问题(会把平面以下的部分渲染到反射结果中)。

最简单的解决方法是让GPU暴露一个自定义选定剪切平面的方法,截断反射表面下的所有几何体。但是很不幸的是并不是所有GPU都支持这个自定义的剪切选项,并且即使支持改起来也会很麻烦(因为要改动代码)。

这时候斜视锥体裁剪的办法就派上用场了,对视椎体进行裁剪,用反射平面替代原来的近裁剪面(即新的斜视椎体),这样反射平面以下的物体都不会渲染(因为在视椎体以外了),其中变换后的新投影矩阵M’与原投影矩阵M的关系:

image

其中:

image

image

image

代码实现:

//reflectorPlane.setFromNormalAndCoplanarPoint
reflectorPlane.normal.copy(normal);
reflectorPlane.constant = -reflectorWorldPosition.dot(reflectorPlane.normal);

//reflectorPlane.applyMatrix4
reflectorPlane.m1.getNormalMatrix(virtualCamera.viewMatrix);
reflectorPlane.v1.copy(reflectorPlane.normal).multiply(-reflectorPlane.constant);
reflectorPlane.v1.applyMatrix4(virtualCamera.viewMatrix);
reflectorPlane.normal.applyMatrix3(reflectorPlane.m1).normalize();
reflectorPlane.constant = -reflectorPlane.v1.dot(reflectorPlane.normal);

//C'
clipPlane.set(reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant);

let projectionMatrix = virtualCamera.projectionMatrix;
q.x = (Math.sign(clipPlane.x) + projectionMatrix[8]) / projectionMatrix[0];
q.y = (Math.sign(clipPlane.y) + projectionMatrix[9]) / projectionMatrix[5];
q.z = - 1.0;
q.w = (1.0 + projectionMatrix[10]) / projectionMatrix[14];

// Calculate the scaled plane vector
let res = 2.0 / clipPlane.dot(q);
clipPlane.multiply(res);

// Replacing the third row of the projection matrix
projectionMatrix[2] = clipPlane.x;
projectionMatrix[6] = clipPlane.y;
projectionMatrix[10] = clipPlane.z + 1.0 - clipBias;
projectionMatrix[14] = clipPlane.w;

实践反射场景:

Examples: Reflector

相关文章:

Paper

reflect向量计算

Chapter 6:光线追踪

相关介绍文章

//Todo:待实践 + WebWorker学习

Chapter 7:光照与着色

Light Source

环境光(Ambient Light):

环境光从各个方向以相同的强度照射过来,并且均匀地照亮物体的每一部分。在场景中环境光的系数通常是一个常量(也可以读纹理)。

定向光(Directional Light ):

定向光源是一种从无限远的地方向单一方向照射的光源。定向光通常用于模拟太阳,它的光线可以认为是平行的,且其光强不会随着距离的增加而减弱。

点光源(Point Light):

image

其中光源强度为C,d为距离光源的距离,C0为灯的颜色,常量Kc、Kl、Kq控制衰减。

聚光灯(Spot Light):

image

其中R为聚光灯方向,L为照射点指向光源的单位向量,指数p控制聚光灯的集中度,控制光照强度随R和- L夹角的增大而减小的速率。

环境光与漫反射光(Ambient and Diffuse Light):

漫反射(Lambertian)指入射光在表面某一点上的光的一部分向随机方向散射,因为光在每个方向上都是均匀反射的,所以朗伯反射的计算不要考虑观察者的位置因素。

考虑环境光强度与n个光源的贡献,可以将漫反射分量表示为:

image

其中D是表面的漫反射颜色,A代表环境光的颜色,N是反射表面的法向量,L是指向第i个光源的单位向量,Ci是第i个光源的光照强度。

镜面光照(Specular Lighting):

image

其中S表面的镜面反射颜色,Hi观察者与光源Li的半角向量,m控制反射的锐度,N与Li的点乘结果决定表面是正对光源还是背对光源(背对的话就不用计算了)。

实践可见:Exmaple

纹理映射(Texture Mapping)

Projective Texture Maps:

投射纹理映射的一个应用是模拟聚光灯将图像投射到环境中,随着与聚光灯距离的增加,投影图像变大。这种效果是通过使用一个4×4纹理矩阵将一个物体的顶点位置映射到纹理坐标(s, t, 0, q),这样除以q就得到了正确的二维纹理坐标(s,t),用于对投影图像进行采样。

首先需要乘以转换矩阵,将每个顶点的坐标先转换为以聚光灯位置为原点的坐标系下的坐标,再乘以投影矩阵(仅xy)

//Todo: 待实践

Cube Texture Maps:

立方体纹理贴图通常用于近似环境周围的反射,纹理的采样坐标为三维向量,表示从立方体中心发出的指向要采样的texel的方向向量。

着色模型(Shading Models):

Blinn-Phong Shading:

实践可见:Example

凹凸映射(Bump Mapping):

  • Bump Map Construction

  • Tangent Space

  • Calculating Tangent Vectors

  • Implementation

//Todo: 实践

物理反射模型

  • Bidirectional Reflectance Distribution Functions(BRDF)

  • Cook-Torrance Illumination

  • The Fresnel Factor

  • The Microfacet Distribution Function

  • The Geometrical Attenuation Factor

  • Todo:Implementation

PBR实践可见:Example