games101图形学学习笔记

2024-03-08 13:40

本文主要是介绍games101图形学学习笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 线性代数基础

1.1 向量

1.1.1 向量的表示

向量表示既有长度又有方向的量。
在这里插入图片描述
其中方向由箭头表示,长度就是线段的距离。
向量也可以用两个点相减表示:在这里插入图片描述
长度计算:在这里插入图片描述
单位向量:在这里插入图片描述

1.1.1 向量运算

  • 向量相加
    在这里插入图片描述

  • 向量的坐标系表示
    在这里插入图片描述

  • 向量相乘
    点乘
    1.点乘的定义
    在这里插入图片描述
    2.点乘的属性
    交换律、结合律、分配律
    在这里插入图片描述
    3.点乘的坐标系表示
    在这里插入图片描述
    4.点乘的作用
    1)计算两个向量的角度(如光线和平面的角度):
    在这里插入图片描述
    2)计算一个向量在另一个向量上的投影
    如:
    在这里插入图片描述
    b向量在a向量上的投影b-perpe(红色箭头)
    分析:首先b-perpe一定与a向量(或者a的单位向量)是同一方向,只是长度不一样,所以b-perpe可以表示为b-perpe = k · a的单位向量,接下来只要求k即可,
    而k就是b-perpe的长度,所以k=b的长度 · cos角度
    3)求投影的作用
    可以计算与投影垂直方向的向量(在任意方向上将一个向量进行垂直分解)
    在这里插入图片描述
    4)判断两个向量是相互靠近还是相互远离
    相互靠近,则单位向量点乘的结果逐渐接近1,当平行时等于1
    相互远离,则单位向量点乘的结果逐渐接近0,当垂直时等于0
    5)判断两个向量是同向还是反向
    在这里插入图片描述
    方向相同,则点乘的结果>0
    方向垂直,则点乘的结果=0
    方向相反,则点乘的结果<0

向量叉乘
1.叉乘的定义
在这里插入图片描述
2.叉乘的属性

2 光栅化

2.1 MVP变换

链接
MVP变换分别指Model变换、View变换、Projection变换。
打个比方:
Model变换:就是找好相机位置,确定相机坐标;让模特摆好造型
View变换:就是找好相机拍摄的角度
Projection变换:就是按下快门,拍照

首先定义一个相机,
e表示相机的位置坐标
g表示相机拍摄的方向
t表示相机向上摆放的方向

为了方便表示和计算,需要将任意一个相机(e,g,t)移到标准坐标系中,即将
e 移到原点坐标(0,0,0)
g 移到与 -Z 相同的方向
t 移到与 Y相同的方向
注意:默认相机与物体保持相对静止状态,也就是相机怎么移动,物体也要怎么移动
在这里插入图片描述
那么要怎么移动呢?
首先将相机坐标移动到原点,如下
在这里插入图片描述
然后将
g 移到与 -Z 相同的方向
t 移到与 Y 相同的方向
g x t 移到与 X 相同的方向
如果直接移动,计算非常复杂。所以用逆向思维,将
-Z 移到与 e 相同的方向
Y 移到与 t 相同的方向
X 移到与g x t 相同的方向,
得到如下矩阵,
在这里插入图片描述
对该矩阵求逆,即可得到目标矩阵,
在这里插入图片描述
以上就完成了Model变换和View变换,接下来进行Projection变换。

Projection变换有两种,分别是Perspective projection(透视投影) 和 orthographic projection(正交投影)

在这里插入图片描述
正交投影的理解:
将物体的Z坐标丢弃,然后缩小到[-1,1]^2
在这里插入图片描述
一般的正交投影实现方式:
先将物体移动到原点中心,再缩小到[-1,1]^3
在这里插入图片描述
透视投影需要先挤压成正交,再进行正交投影。
在这里插入图片描述
那怎么挤压呢?
在这里插入图片描述
我们从截面观察各点的变化情况,由于远平面f要“挤”到和近平面一样大,所以可推导出图中远平面上的红点的y值会向下移动到y’这么高,所以由相似原理可写出
在这里插入图片描述
同理求得x的变化
在这里插入图片描述
所以挤压前后坐标变化如下:
在这里插入图片描述
为什么乘以z值之后得到的结果还相同?
因为在齐次坐标中,各个维度乘以相同的值得到点和原来一样。

得出挤压后坐标后,就可以得出如下等式:
在这里插入图片描述
所以可以得出:
在这里插入图片描述
继续利用条件:
由于任何在近平面上的点是不会变的。
任何在远平面的点的z值也不会变。

由以上两条结论可推导出:
n:近平面
在这里插入图片描述
可以推出:
在这里插入图片描述
可以推出:
在这里插入图片描述
再由第二条已知条件,所有在远平面的点的z值不变,可得(f为远平面):
在这里插入图片描述
可以求得:
在这里插入图片描述
最后得出透视变正交矩阵:
在这里插入图片描述
最后得出透视投影变换矩阵:
在这里插入图片描述
做完这些变换后,怎么投射到屏幕上呢?需要做ViewPort(视口)变换。
假设屏幕的宽为width,高度为height,那么视口变换就是把[-1,1]^2的xy平面变到[0,width]x[0,height],这里为什么没有考虑z轴的变换是因为z轴在之后的其他地方有用。
根据上述可得出视口变换矩阵为:
在这里插入图片描述

2.2 光栅化

光栅化就是将图像画在屏幕上,那么怎么画呢?
经过MVP变换后,得到了一个[-1,1]3的正方体。首先忽略Z坐标值,然后将[-1,1]2的xy平面映射到[0,width]x[0,height]屏幕上。
在这里插入图片描述
映射矩阵如下:
在这里插入图片描述

在这里先定义一下像素,一个像素可以理解为一个正方形。假设一个像素坐标为(x, y),那么这个像素的中心为(x+0.5, y+0.5)。
计算机图形学中一般都以三角形为画图单位。任务一个物体都可以由若干个三角形表达出来。因此只要考虑三角形与像素之间的关系即可。
现在经过MVP变换后,得到如下三个坐标组成的三角形。
在这里插入图片描述
那怎么判断一个像素点是在三角形内还是三角形外呢?
只要设计出如下函数即可:
在这里插入图片描述
然后一个像素一个像素的判断:

for (int x = 0; x < xmax; ++x) for (int y = 0; y < ymax; ++y) image[x][y] = inside(tri, x + 0.5, y + 0.5);

那inside(t, x, y)怎么实现呢?
利用向量的叉乘:
在这里插入图片描述
只要确定Q点同时在三角形的左边或右边,即可判断Q点是在三角形内还是在三角形外。
第二个问题:一个三角形只包含一部分像素,在判断内外时,没必要把所有像素都判断一遍。那怎么优化呢?
这时候需要用到包围盒的概念。在这里插入图片描述
蓝色的部分就是包围盒,首先判断像素是否在包围盒内,
如果在,则继续判断是否在三角形内,
如果不在,则直接跳过

2.2.1 Z-buffer

在一张图像中,可能有物体遮挡物体的情况,比如这张油画:
在这里插入图片描述
树木挡住了地面和山,这个时候如何去画呢?
一种画法是先把山画好,再画地面,再画树木。也就是先画远的物体,再画近的物体。
这个画法在遇到下图时就不管用了:
在这里插入图片描述
PQR之间相互遮挡,形成了闭环。

这个时候可以用到Z-buffer(深度缓冲区)。Z-buffer保存的是所有像素的Z坐标值,也就是深度值。
通过比较每个像素的深度值,决定在该像素画什么颜色。具体算法如下:
在这里插入图片描述
也就是先将Z-buffer全部初始化为无穷远(一个很大的数也行)。然后在每个三角形中的每个像素判断,
如果当前像素深度值小于Z-buffer中保存的深度值,则将该像素点画上该三角形的颜色。同时更新该像素点的Z-buffer的值。

一个简单的例子:
在这里插入图片描述

3 着色shader

着色其实就是给物体打个光,计算不同的光线对物体的明暗影响。比如:
着色前:
在这里插入图片描述
着色后:
在这里插入图片描述
可以发现,着色后有了明显的明暗对比。有光的地方就亮,没光的地方就暗。

3.1 Blinn-Phong着色模型

Blinn-Phong着色模型主要有3种光线,分别是镜面反射(Specular highlights)、漫反射(Diffuse reflection)和环境光(Ambient lighting)。
首先定义一些概念:
在这里插入图片描述
视线方向:v
平面法向量:n
光线方向:l
平面参数:颜色

3.1.1 漫反射Diffuse reflection

漫反射的特点:在所有的方向看平面都是一样的效果。
当物体与光线垂直时,物体会接收光线所有的能量。
在这里插入图片描述
当物体与光线有一定的角度时,物体吸收的能量与角度大小有关。
在这里插入图片描述
这里再定义一个光照强度的概念:
在这里插入图片描述距离光源为1的地方,光照强度为I
则距离光源为r的地方,光照强度为I/r2

所以可以得出如下漫反射的计算公式:
在这里插入图片描述
Ld:漫反射光照强度
kd:漫反射系数,可以是物体表面的颜色,值在[0,255]/255之间,也就是在[0,1]之间。
I/r2:光线到达物体表面时的光照强度
max(0, n·l):物体表面接收的光照强度

效果:
在这里插入图片描述

3.1.2 镜面反射Specular highlights

镜面反射的特点是:当视线方向与反射方向接近时,就能看到镜面反射光线。如图:
在这里插入图片描述
为了方便计算,提出了半程向量的概念:半程向量就是光线方向与视线方向的向量和。如图:
在这里插入图片描述
可以看出,可以用 半程向量与物体表面法向量的夹角 近似的表示 视线方向与镜面反射方向的夹角。
可以得出如上计算公式:
Ls:镜面反射光照强度
Ks:镜面反射系数,可以是物体表面的颜色,值在[0,255]/255之间,也就是在[0,1]之间。
I/r2:光线到达物体表面时的光照强度
max(0, n·h)p:在一定角度内才能看到镜面反射光线。
为什么要加个指数p?
因为指数p能够提高真实性。如果没有p,那么在45°时都能看到一定的光线,显然是不真实的。一般p的值在[100, 200]之间。
在这里插入图片描述
结合漫反射和镜面反射的效果图:
在这里插入图片描述
可以看出Ks越大,表面越亮。p越大,光点越小。

3.1.3 环境光Ambient light

环境光不依赖任何东西。
在这里插入图片描述
La:环境光光照强度
ka:环境光系数,可以是物体表面的颜色,值在[0,255]/255之间,也就是在[0,1]之间。
Ia:到达物体表面的环境光光照强度。

结合三种光线的效果图:
在这里插入图片描述

3.2 着色频率

在这里插入图片描述
观察上面三个球,发现效果不一样。这是因为:
第一个球是以表面(三角形)为单位进行着色的。该方法称为Flat shading
第二个球是以顶点为单位进行着色的。该方法称为Gouraud shading
第三个球是以像素点为单位进行着色的。该方法称为Phong shading

看这个效果图:
在这里插入图片描述
可以发现,当顶点数量达到一定程度的时候,用Flat shding也可以满足要求。所以并不是Phong shading就一定是最好的选择。
要想以顶点和像素点进行着色,就需要知道顶点和像素点的法向量n,这个后面再说。

3.3 图形管线(实时渲染管线)

图形管线过程如下:
在这里插入图片描述
1 输入3D中的顶点坐标
2 通过MVP变化,计算出对应的在屏幕上的顶点坐标
3 将屏幕上的顶点连接成三角形
4 通过光栅化,将三角形画在屏幕上
5 像素着色
6 输出图形

目前GPU都采用可编程渲染管线shader,也就是用户决定着色。通过编写shader代码,实现着色功能。
OpenGL例子:

uniform sampler2D myTexture; 
uniform vec3 lightDir; 
varying vec2 uv; 
varying vec3 norm; 
void diffuseShader() 
{ vec3 kd; kd = texture2d(myTexture, uv); kd *= clamp(dot(–lightDir, norm), 0.0, 1.0);  //Blinn Phong Diffuse reflectgl_FragColor = vec4(kd, 1.0);  //输出颜色	
}

3.4 纹理映射

纹理就是一张图,纹理中的每个坐标由(u, v)表示。所谓映射,就是计算出与2D屏幕坐标(x, y)相对应的纹理坐标(u, v)。然后将(u, v)的颜色信息贴到2D屏幕中。
纹理的问题:
1 纹理比屏幕尺寸小
就用双线性插值匹配
2 纹理比屏幕尺寸大
用MipMap
纹理的其他作用:待补充。

3.5 三角形插值:重心坐标

一般都是知道三角形三个顶点的各种属性,然后要实现三角形内各个像素的属性的循序渐变。所谓的属性包括纹理坐标、颜色、法向量等等。这时候要用到重心坐标。
重心坐标定义如下:
在这里插入图片描述
三角形内部的每个点坐标都可以由三个顶点坐标的线性组合组成。A、B、C对应的权值分别是α、β、γ。α、β、γ就是所谓的重心坐标。
比如A的重心坐标:
在这里插入图片描述
比如重心点的重心坐标:
在这里插入图片描述
通用的重心坐标计算公式:
在这里插入图片描述
比如颜色插值:
在这里插入图片描述

3.6 阴影Shadow-Mapping

注意:着色点在阴影内,则该点不能被光源看到。
在光栅化中,想要实现阴影,主要有两步。
第一步,从光源看向场景,做一次光栅化,记录整个场景的深度图。
在这里插入图片描述
第二步,从视角处看向场景,将每个点重新投影到光源处,计算出此时的深度,再与第一步中得到的深度做比较:
如果第二步的深度与第一步的深度近似相等,则该点能被光源看到。
如果第二步的深度远大于第一步的深度,则该点不能被光源看到,即该点处有阴影。
在这里插入图片描述
在这里插入图片描述

4 几何Geometry

几何表示的是物体或模型的形状。
几何可以隐式(Implicit)表示和显示(Explicit)表示。

4.1 隐式表示

所谓的隐式表示,就是通过一个表达式或者函数来表达物体的形状。比如:
x2 + y2 + z2 = 1,或f(x, y, z) = x2 + y2 + z2 - 1表示一个球体。
隐式表示的优点是能够直接计算出特定的点是否在物体上。
在这里插入图片描述
如果函数计算出的结果小于0,则说明该点在球内
如果函数计算出的结果等于0,则说明该点在球上
如果函数计算出的结果大于0,则说明该点在球外

隐式表示的缺点是无法直接看出或算出物体的形状。
比如下图:直接通过表达式无法看出物体的形状。
在这里插入图片描述

4.2 显示表示

显示表示就是所有的点的坐标都给出来,或者通过映射的方式给出来。比如
在这里插入图片描述
通过左边的(u, v)坐标可以计算出右边的马鞍面的(x, y, z)坐标。
又比如:
在这里插入图片描述
显示表示的优点是可以直接看出物体的形状,但是很难判断指定的点与物体的位置关系,是在内还是在外。

4.3 更多的隐式表示

4.3.1 Constructive Solid Geometry(CSG)

通过布尔运算,将多个隐式几何组合到一起。
在这里插入图片描述

4.3.2 Signed Distance Functions

Signed Distance Functions,即有向距离函数。
使用距离函数,可以得到如下效果:
在这里插入图片描述
也就是两个物体融合到一起,融合的程度由距离函数决定。
那什么是距离函数呢?
距离函数定义:空间中任意一点到所在物体表面任意一点的最近距离。距离函数的值可以是正的也可以是负的。如果是正的,则空间中的点在物体外,如果是负的,则该点在物体内。
看下图:
在这里插入图片描述
假设物体A坐标1/3被挡住了是黑色的,物体B左边2/3被挡住了是黑色的。如果把A和B直接线性组合就得到右上角的图,左黑,中间灰,右白。如果我们想得到右下角左黑右白,就要用SDF。
可以看出
A物体的1/3左边的SDF是负的,1/3右边是正的。
B物体的2/3左边的SDF是负的,2/3右边是正的。
将A和B的SDF值相加,等于0处的地方就是两者融合的终点位置。也就是融合的程序。
再看下图:
在这里插入图片描述

假设正方体和球体中间空间中有个点,离正方体的距离为1,此时正方体在该点的SDF值为1。那么找到球体内一个点的SDF值为-1。再将正方体的右上角平移到该点,则实现了正方体与球体的融合。

4.3.3 Level Set Methods

Level Set Methods,即水平集。(没有理解)

4.4 更多的显示表示

4.4.1 Polygon Mesh多边形表示

Polygon Mesh,即多边形面。基本上都是用三角形或四边形。
比如下图:
在这里插入图片描述
在图形学中用一种特定的文件格式表达一个物体的形状。
文件格式称为The Wavefront Object File,简称.obj文件。通过指定顶点坐标、法向量、纹理坐标。然后将他们联合起来。如下图:
在这里插入图片描述
v:表示顶点坐标
vt:表示纹理坐标
vn:表示每个面法向量
f:表示三角形
比如第一个f,就是由顶点(5,1,3)、纹理(1,2,3)、法向量(1)组成的一个三角形。将所有的三角形粘合到一起,就是一个正方体。

对于多边形表示有一个问题,就是多边形的数量或者密集程度问题。如下图:
在这里插入图片描述
可以看出,多边形的数量直接影响了模型的真实程度。所有就有了提高多边形数量和降低多边形数量的方法。
提高多边形数量叫做网格细分(Mesh Subdivision)。如下图
在这里插入图片描述降低多边形数量称为网格简化(Mesh Simplification)。如下图:
在这里插入图片描述

4.4.1.1 网格细分

4.4.1.1.1 Loop细分

注意:Loop细分只能用于三角形的细分。
在这里插入图片描述
Loop细分,主要分为两步,
第一步,将大的三角形划分成更多的小的三角形。
在这里插入图片描述
第二步,更新新的顶点和旧的顶点的坐标。
在这里插入图片描述
划分三角形是非常容易的,就是取每条边的中点,分别连起来就可以了。顶点坐标怎么更新呢?
对于新的顶点,方法如下:
在这里插入图片描述
如图,其中白点代表新的顶点,这个顶点在两个三角形的共线上(不在边界线上)。要求该点的坐标,就是把周围4个顶点的坐标做一个加权求和。公式就是3/8 * (A + B) + 1/8 * (C + D)

对于旧的顶点,方法如下:
在这里插入图片描述
如图,其中白点表示待更新的旧顶点。在这里要定义两个新的概念,分别是:
n:表示顶点的度,也就是该顶点连接了几条线。
u:权值。
最后得出计算公式:(1 - n*u) * original_position + u * neighbor_position_sum
通过以上公式,可以看出,当该点的连接的线越多,也就是n越大,该点自身所占的权值就越低。反之,当该点的连接的线越少,也就是n越小,该点自身所占的权值就越大。

4.4.1.1.2 Catmull-Clark细分

注意:Catmull-Clark细分可以处理三角形和四边形,以及多边形。
对于下图所示的包含四边形和三角形的形状:
在这里插入图片描述
只需要取每个边的中点,以及每个多边形的中点,并将中点两两连接起来,就可以得到细分后的图形。如下图所示:
在这里插入图片描述
在上图中,顶点位置坐标已经更新了。
那顶点位置坐标怎么更新的呢?如图:
在这里插入图片描述
其中f表示多边形的中点,e表示边的中点,p表示就顶点。

细分效果图如下:
在这里插入图片描述

4.4.1.2 网格简化

网格简化要用到边坍缩的方法。那什么是边坍缩呢?如图
在这里插入图片描述
就是将一条边的两个顶点融合到一起,这样两个顶点就变成了一个顶点。
那模型中边有那么多,怎么确认哪些边坍缩后影响最小呢?
那就要用计算每条边的二次度量误差的值,值最小的最先坍缩。
那什么是二次度量误差?待补充。

4.4.2 贝塞尔曲线

链接1
链接2
我的理解:贝塞尔曲线就是通过确定的几个点,按照某种规则计算出一个点。如果确定的几个点有n个,就能按照规则计算出n个点。再将这n个点连接就是贝塞尔曲线。
举个例子:
在这里插入图片描述

有b0, b1,b2三个点组成的两条线段。首先在线段b0b1上距离b0点t位置处取一点b01,在线段b1b2上距离b1点t位置处取一点b11,连接b01和b11,再在该线段t位置处取一点b02。则b02就是最终所求的点。
按照同样的规则,将线段上所有的点求出对应的最终点,连起来就是贝塞尔曲线。
动图展示:
在这里插入图片描述
更多的点:
在这里插入图片描述

4.4.2.1 贝塞尔曲线代数表示

先看三个点组成的贝塞尔曲线的代数表示:
在这里插入图片描述
其中的b都代表坐标值。可以发现,最终展开式是一个(s + t)2 的形式,其中s=1-t
可以推出,当有n+1个点组成的贝塞尔曲线表达式:
在这里插入图片描述
其中用到伯恩斯坦(Bernstein)多项式。

4.4.2.2 分段贝塞尔曲线

对于下图10个控制点画出来的贝塞尔曲线,想要移动点控制线的形状比较麻烦。因此可以应用分段贝塞尔曲线。
在这里插入图片描述

分段贝塞尔曲线,最常用的就是每4个控制点得到一条贝塞尔曲线。如下图
在这里插入图片描述
使用分段贝塞尔曲线,只需要注意连接点处的平滑性。所谓平滑性,就是两条线在连接点处的一阶导数相等。
在这里插入图片描述

4.4.3 贝塞尔曲面

贝塞尔曲面效果图如下:
在这里插入图片描述
通过4x4个控制点,就可以得到一个贝塞尔曲面。那怎么实现的呢?
在这里插入图片描述
通过最左边4个点就可以得到一条贝塞尔曲线,同样的其他3x4个点,也能得到3条贝塞尔曲线。
然后在4条贝塞尔曲线上分别取得4个点,又可以组成一条贝塞尔曲线。
将4条贝塞尔曲线上所有的点都组成贝塞尔曲线,就形成了贝塞尔曲面。如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5 光线追踪Ray Trace

在这里插入图片描述
假设从眼睛中射出一条光线,经过成像平面中的某个像素后,射到场景中的最近的某物体上的某一点。("最近"这个词就解决了光栅化中Shadow Mapping的阴影的深度测试问题)。
确定了光线的位置后,就要看这个点会不会被光源照亮。怎么做呢?
就是从该点连接一条线到光源。如果没有任何物体阻挡,则可以确定该点可以被光源照亮。
然后就可以计算该点的着色,并将颜色值写到成像平面的像素中。
在这里插入图片描述

5.1 Whitted-Style光线追踪

Whitted-Style光线追踪可以用来出来光线在场景中不断弹射和折射的情况。
在这里插入图片描述
光线在射到物体上时,会发生反射和折射。经过反射和折射后会照射到别的物体上的某个点。然后将这些点与光源连接,如果没有物体遮挡,则说明该点能够被光源照亮,则对该点进行着色计算,并将计算的颜色值加到成像平面的像素上。(其他的所有点都是一样的)
在这里插入图片描述
当然,光的能量在反射和折射后,是不断损失的。
重新定义光线名称:
在这里插入图片描述
效果图:
在这里插入图片描述

5.1.1 光线与平面的交点

首先定义光线:光线是一条射线,并随着时间越来越远。
在这里插入图片描述
O:代表光源位置
d:代表照射方向

5.1.1.1 光线与隐式表面的交点

交点就意味着该点既在光线上也在球上。那么知道表达出球的等式,代入即可求解。
在这里插入图片描述
其中,p是光线与球的交点。

在这里插入图片描述
经过计算,可以得到两个时间t。要选择实数且大于0的。通过t也可以看出光线与球有几个交点。
同样,光线与其他隐式几何,可以用同样的方法计算。如下图:
在这里插入图片描述

5.1.1.2 光线与显示表面交点

要求光线与三角形交点,可以分为两步:
第一步,计算光线与三角形所在平面的交点
第二部,判断该点是否在三角形内。
先完成第一步:
首先定义一个平面:一个点和一条法线就可以定义一个平面。
在这里插入图片描述
平面内的任意一点与p 的连线都与法线垂直。

因此,可以代入后,可以得出t值。
在这里插入图片描述
这种方法比较麻烦,下面Möller Trumbore 算法比较简洁,可以直接得出t值。
在这里插入图片描述
等式左边是光线的表达式,右边是三角形内任意一点的重心坐标表达式。可以看出,只要计算出t、b1、b2即可。计算结果如下:
在这里插入图片描述

5.1.2 AABB包围盒

因为一个场景内三角形的数量有很多,我们在处理的时候,不可能遍历每一个三角形判断光线是否与它相交。因此,需要提出一些加速方法,包围盒就是一种加速方法。所谓的包围盒就是用一个封闭空间将场景内的某些物体包围起来,如果光线都不与包围盒相交,则说明光线不会与包围盒内的物体相交。如光线与包围盒相交,再继续判断光线是否与物体相交。包围盒内有一种名为AABB的包围盒。
AABB,即Axis-Aligned Bounding Box,翻译过来就是轴对称包围盒。
在二维平面中,包围盒是一个矩形。在三维世界中,包围盒是一个长方体结构。
可以认为这个长方体是由3对平行的面包围而成,且每对平面之间都是垂直的。如下图:
在这里插入图片描述
那么怎么判断光线是否与这个包围盒相交呢?
由于3D的比较复杂,由繁入简,先考虑2D的情况。
在这里插入图片描述
在上图中,O点表示光源起点,d表示光线方向。
最左边表示光线与轴平面x相交的情况,显然可以计算出,光线进入该平面的时间txmin 和光线从该平面离开的时间txmax
同理,可以计算出光线进入轴平面y的时间tymin 和光线从轴平面y离开的时间tymax
接下来只要选择进入时间的最大值(即tenter = MAX(txmin ,tymin ))和离开时间的最小值(即texit = MIN(txmax ,tymax ))。
为什么要这样选择呢?
因为光线只有进入所有的平面才算进入包围盒,所以进入的时间选最大的。只要光线离开第一个平面,就算离开包围盒,所以离开的时间选最小的。

根据现实情况,可以得出下列结论:
如果texit小于0,则包围盒在光源后面,也就是光线与包围盒没有交点。
如果tenter小于0且texit大于等于0,则光源在包围盒中,则一定有交点。
如果tenter小于texit,则光线一定经过包围盒。

5.1.2.1 使用包围盒加速
5.1.2.1.1 均匀网格 Uniform grids

第一步:创建一个包围盒
在这里插入图片描述
第二步:在包围盒内划分网格
在这里插入图片描述
第三步:保存每个与物体相交的网格
在这里插入图片描述
第四步:判断光线是否与网格相交,若相交,则判断光线是否与物体相交。
在这里插入图片描述
注意:划分表格的密度应该是物体的27倍左右。

使用实例:
对于这种物体铺满画面的可以用该方法。
在这里插入图片描述

5.1.2.1.2 空间划分Spatial partitions

空间划分主要有3种,如图:
在这里插入图片描述
Oct-Tree:八叉树划分,就是按照等分的方式划分。
KD-Tree:与八叉树类似,只是不在等分划分。
BSP-Tree:不再横平竖直划分。
主要说明KD-Tree。
假设下图是KD-Tree的一种划分结果:
在这里插入图片描述
假设从坐上往右下有一条光线,如图:
在这里插入图片描述
那么怎么判断光线是否与物体相交呢?
第一步,首先判断与根节点包围盒A是否有交点。发现有交点,同时A节点还有字节点,则继续判断与A的两个子节点是否相交。如图:
在这里插入图片描述
在这里插入图片描述
第二步:与A的两个子节点分别判断,发现1节点是叶子节点,则判断光线是否与1节点内的物体相交。发现B节点不是叶子节点,则分别判断光线是否与B的两个叶子节点相交。
第三步:以此类推。。。最终判断到最外层叶子节点
在这里插入图片描述
这种方法有两个问题:
1 三角形与包围盒是否相交难以判断
2 一个物体可能在多个空间中

使用实例:
对于这种空间有大部分空白的图像,适合这种方法。
在这里插入图片描述

5.1.2.1.3 物体划分Object partitions

为了解决上述两个问题,可以使用物体划分的方式,物体划分也叫Bounding Volume Hierarchy (BVH)。
如图,一个包围盒内有多个三角形:
在这里插入图片描述
将包围盒内的三角形划分成两队,并重新形成两个包围盒。
在这里插入图片描述
可以发现物体只存在一个包围盒中。
继续划分:
在这里插入图片描述
当每个包围盒中有5个左右的三角形时,就可以停止划分。
在这里插入图片描述
说明一下二叉树的数据结构:
根节点和中间节点保存:包围盒和子节点
叶子节点保存:包围盒和物体列表
伪代码:

Intersect(Ray ray, BVH node) {if (ray misses node.bbox) return; //如果没有遇到包围盒则返回if (node is a leaf node) //如果是一个叶子节点test intersection with all objs; //判断光线是否与包围盒内所有物体相交return closest intersection; //返回最近的交点hit1 = Intersect(ray, node.child1); //递归判断字节点1hit2 = Intersect(ray, node.child2); //递归判断字节点2return the closer of hit1, hit2;
}

5.2 辐射度量学Radiometry

辐射度量学可以按照正确的物理规律进行光线的能量计算。
首先看几个概念:
Radiant Energy:就是能量Q,单位是焦耳J
Radiant flux (power):就是功率,用Φ表示,单位是瓦特W或者流明lm
还有3个概念如图:
在这里插入图片描述
Radiant Intensity:光从光源射出来的能量(power)。
Irradiance:光在接收处的能量(power)
Radiance:光在传播过程中的能量(power)

5.2.1 Radiant Intensity

首先解释Radiant Intensity,定义:在单位立体角上的功率(power)。公式表示如下:
在这里插入图片描述
Radiant Intensity的单位是W/sr或lm/sr或cd。

那什么是立体角呢?先考虑二维的情况。
在这里插入图片描述
在二维的圆中,角度可以由弧长/半径来表示。整个圆的角度为2Π。
在三维世界中,
在这里插入图片描述
立体角可以由球表面面积/半径的平方来表示。整个球的立体角为4Π。
接下来计算单位立体角,
在这里插入图片描述
矩形A的面积=边长x边长。
在这里插入图片描述
通过下列公式,可以求出Radiant Intensity:I
在这里插入图片描述
所以可以计算灯泡的Radiant Intensity:
在这里插入图片描述

5.2.2 Irradiance

Irradiance的定义:单位面积上的功率(power)。用字母E表示,单位是W/m2 或lm/m2 或lux。
在这里插入图片描述
当光线与面有一个角度时,就需要乘以这个角度的余弦。
在这里插入图片描述

5.2.3 Radiance

Irradiance结合了Radiant Intensity和 Irradiance。
Radiance定义:在单位立体角和单位面积上的功率(power)。
单位是W/sr m2 或lm/sr m2 或cd/m2 或nit。
在这里插入图片描述
所以Radiance可以说是单位面积的Radiant Intensity(也就是输出光线能量);有可以说是单位立体角的Irradiance(也就是输入光线能量)。

所以出射(反射)Radiance可以表示为:
单位面积的Radiant Intensity
在这里插入图片描述

所以入射Radiance可以表示为:
单位立体角的Irradiance
在这里插入图片描述

5.2.4 Bidirectional Reflectance Distribution Function (BRDF)

Bidirectional Reflectance Distribution Function (BRDF):双向反射分布函数

Irradiance vs. Radiance

Irradiance :表达的是dA面积上接收到的来及各个方向的power。
Radiance:表达的是dA面积上接收到的来自dw方向的power。
所以有:
在这里插入图片描述
Reflection at a Point
在这里插入图片描述

BRDF
备注:没有理解
BRDF是用来计算每个入射方向经反射后从wr 方向射出的power。
在这里插入图片描述
反射方程
在这里插入图片描述
渲染方程
在这里插入图片描述

渲染方程的理解
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3 概率论复习

概率
在这里插入图片描述
期望
在这里插入图片描述
概率密度函数
链接
在这里插入图片描述
随机变量函数
在这里插入图片描述

5.3.1 Monte Carlo积分

链接
在这里插入图片描述
对于任何形状的概率密度函数,若想求[a, b]范围上的概率,也就是[a, b]上的定积分。都可以转换为下列等式:
在这里插入图片描述
举例:
设一个函数为f(x)=3x2 ,计算它在[a, b]上的积分。假设[a, b]为[1, 3],则积分结果为3x32 - 1x12 = 26。
若用Monte Carlo积分,可以得出等式:
在这里插入图片描述
那么,若样本是{2},则F1(x) = 24;若样本是{1, 2, 3},则F3(x) = 28;若样本是{1.0, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3.0},则F9(x) = 26.5。以这个趋势,不断在逼近的积分结果26。若随机采样10k个均匀的随机采样点,那么它的结果一定是逼近26的。

5.4 PathTracing

前面说过Whitted-style光线追踪,效果挺好的。但是它有两个问题:
第一,它可以处理镜面反射,但无法处理Glossy反射。
在这里插入图片描述
第二,它可以处理直接光照(光源直接照射目标像素后反射),但不能处理间接光照(光源在照射某个物体后反射出来的光再照射目标像素)。
在这里插入图片描述
左边是直接光照,右边包含间接光照

Whitted-style光线追踪是有问题的,但是前面说过的渲染方程是完全正确的,因为它是按照物理规律计算出来的。
在这里插入图片描述
要想使用渲染方程,还要解决两个问题:
1 在半球上面进行积分
2 递归的执行

5.4.1 在半球上积分

为简化计算,首先只考虑直接光照,不考虑物体本身亮度和间接光照。
计算过程如下:
在这里插入图片描述
可以转化为:
在这里插入图片描述
所以可以写出算法:

shade(p, wo)Randomly choose N directions wi~pdf //随机选择N个方向Lo = 0.0 //初始化为0For each wi //对于每一个方向wiTrace a ray r(p, wi) //跟踪p到wi的方向If ray r hit the light //如果碰到光源,则计算LoLo += (1 / N) * L_i * f_r * cosine / pdf(wi)Return Lo

关于方向wi,如下图:
默认光源是一个面积光源,不是点光源
在这里插入图片描述

接下来,把间接光照考虑进去。
在这里插入图片描述
此时P接收的是经过Q点反射后的光线。对于Q反射后的光线,可以认为是光源经过Q点后直接光照的结果。所以只要在算法中,加一层对Q点的直接关照计算即可。

shade(p, wo)Randomly choose N directions wi~pdfLo = 0.0For each wiTrace a ray r(p, wi)If ray r hit the lightLo += (1 / N) * L_i * f_r * cosine / pdf(wi)Else If ray r hit an object at q//如果光线打到物体q则计算q点直接光照Lo += (1 / N) * shade(q, -wi) * f_r * cosine / pdf(wi)
Return Lo

这样就完美了吗?还没有。
因为在上述算法中,考虑的是对半球上随机N个方向进行计算(N越多,Monte Carlo积分越准确)。但N越多,一旦经过间接光照,就会发散成N反射次数 条光线,这个计算量是非常大的。因此,只需要考虑N=1时,就不会有指数级的计算量。因为1n = 1。
那么可以修改算法:

shade(p, wo)Randomly choose ONE direction wi~pdf(w) //只考虑一个方向的光线Trace a ray r(p, wi)If ray r hit the lightReturn L_i * f_r * cosine / pdf(wi)Else If ray r hit an object at qReturn shade(q, -wi) * f_r * cosine / pdf(wi)

这样就完美了吗?还没有。
前面提到,N越大,Monte Carlo积分越准确。现在N只有1,那么Monte Carlo积分将会严重的失真,对整个画面将产生很多的噪声。那怎么办呢?
这时候需要计算经过同一个像素的多个光线,并对他们的值求平均。如图
在这里插入图片描述
算法如下:

ray_generation(camPos, pixel) //输入相机位置和像素位置Uniformly choose N sample positions within the pixel //在像素内均匀选择N个位置pixel_radiance = 0.0For each sample in the pixel //对于像素内的每一个位置Shoot a ray r(camPos, cam_to_sample) //连接相机和像素位置,形成一条光线If ray r hit the scene at p //如果光线打到物体p,则计算pixel_radiance pixel_radiance += 1 / N * shade(p, sample_to_cam) Return pixel_radiance

5.4.2 递归问题

这样就完美了吗?还没有。
回到shade算法

shade(p, wo)Randomly choose ONE direction wi~pdf(w) //只考虑一个方向的光线Trace a ray r(p, wi)If ray r hit the lightReturn L_i * f_r * cosine / pdf(wi)Else If ray r hit an object at qReturn shade(q, -wi) * f_r * cosine / pdf(wi)

我们发现在这个递归算法中,它没有一个结束的点。这会导致程序一直往下执行,不会返回。
那我们能给它指定递归的次数吗?先看效果图:
递归3次:
在这里插入图片描述
递归17次:
在这里插入图片描述
可以发现,递归17次比递归3次更亮一些。而真实的光线是不会停下来的,会一直反射。也就是说递归次数越多,图像越真实。但又不能真的无限递归,那怎么办呢?
这里就需要提高俄罗斯轮盘赌。
在这里插入图片描述
这个枪的弹夹能放6颗子弹,如果放进去2颗子弹。则射出子弹的概率是2/6,没有子弹射出的概率是4/6。假设没有子弹射出概率为P, 则有子弹射出概率为1-P
同样,我们同样设置一个概率P
让光线递归后,有P的概率继续递归下去,有1-P的概率停止递归。
同时规定:
如果递归计算下去,则返回Lo / P
如果没有递归计算下去,则返回0
这样就可以得出期望仍然是LoE = P * (Lo / P) + (1 - P) * 0 = Lo

那么修改算法:

shade(p, wo)Manually specify a probability P_RR //指定P_RR的值Randomly select ksi in a uniform dist. in [0, 1] //随机选择一个[0,1]范围内的数ksiIf (ksi > P_RR) return 0.0; //假设P_RR=0.8,若ksi大于0.8,就返回0// Randomly choose ONE direction wi~pdf(w)Trace a ray r(p, wi)If ray r hit the lightReturn L_i * f_r * cosine / pdf(wi) / P_RRElse If ray r hit an object at qReturn shade(q, -wi) * f_r * cosine / pdf(wi) / P_RR

这样就完美了吗?还没有。
看效果图:
在这里插入图片描述
左边的像素采样少,图像质量差。右边像素采样多,图像质量好。
那怎么解决这个问题呢?
首先分析问题的原因,先看下图:
在这里插入图片描述
每个图的面光源的大小不一样,左边的最大,那么只要射出5条光线就能打到光源;中间要500条光线打到光源;右边的要50000条光线才能打到光源。那么可以想象出,光源越大,打到光源概率越大,对该像素着色概率越大。
这就可以解释上面效果图的差别:低SPP的时候,打到光源概率低,着色概率小。高SPP,打到光源概率高,着色概率高。

那怎么在低SPP的时候,提高打到光源的概率呢?
解决方法就是:不在随机任意方向wi上采样了,在光源上面采样。那怎么做呢?
在这里插入图片描述

假设光源的面积大小为dA, 则Monte Carlo积分公式中的p(Xi) = 1 / dA
在这里插入图片描述
可是渲染方程中是对wi进行积分的,怎么将对w积分转为对A积分呢?如图:
在这里插入图片描述
根据立体角的定义:球表面面积/半径的平方。因此可以得出:
在这里插入图片描述
所以渲染公式可以变成:
在这里插入图片描述

对于直接光照可以用上面的方法转换,对于间接光照还是按照前面的方法不变。所以可以得出新的算法:

shade(p, wo)// Contribution from the light source. 直接光照Uniformly sample the light at x’ (pdf_light = 1 / A)Shoot a ray from p to x’ //判断直接光照光线是否被别的物体遮挡If the ray is not blocked in the middle //若没有遮挡L_dir = L_i * f_r * cos θ * cos θ’ / |x’ - p|^2 / pdf_light // Contribution from other reflectors. 间接光照L_indir = 0.0Test Russian Roulette with probability P_RRUniformly sample the hemisphere toward wi (pdf_hemi = 1 / 2pi)Trace a ray r(p, wi)If ray r hit a non-emitting object at qL_indir = shade(q, -wi) * f_r * cos θ / pdf_hemi / P_RRReturn L_dir + L_indir

这就完美了吗?是的,完美了!

这篇关于games101图形学学习笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/787262

相关文章

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个