Games101 笔记
Games101 笔记
导论
图形学应用场景
- 电子游戏:
- - PBR:之狼
- 卡通渲染:无主之地
- 电影:黑客帝国
- 动画:疯狂动物城、冰雪奇缘
- 设计:概念图
- 可视化:虚拟现实
- 数码插画:模拟仿真
- GUI图形用户接口
- 字体设计:矢量图
全局光照做的好不好影响游戏美术品质,同时体现在场景的亮度
图形学的技术挑战
光栅化(rasterization):
将三维空间中的几何形体显示在屏幕上
实时:30fps 否则被成为离线
曲线和网格(Curves and Meshes) :各种细分方法
光纤追踪(ray tracing):动画电影广泛使用,但是速度较慢
目前使用游戏使用的是实时光线追踪
仿真和模拟(animation/simutatoin)
OpenGL、dx是图形学API不是图形学
计算机图形学与计算机视觉
- 图形学不是计算机视觉。
- 计算机视觉:猜测、预测、分析处理
- 计算机视觉是理解这个世界,计算机图形学是创造这个世界
线性代数复习
图形学默认使用列向量,课程默认为右手系
点乘在图形学的应用
AB=|A||B|cosθ
- 点乘主要应用于求两个单位向量的夹角,
- 观察两个向量之间是同向、垂直还是反向,可以观察两个向量的接近,若两个向量的点乘接近1则离得很近,若接近0则离得很远
- 利用投影可将一个向量分解成两个(多个)向量和
叉乘在图形学的应用
- AxB=-BxA
- 判定左右(内外)
- - 若AxB为正则点A在点B在A左侧,若点P在点A、B、C内,则ABXAP,BCxBP,CAxCP结果都为外(内)则P在ABC内
- 若任意一个结果不同则P在ABC外
- 定义坐标系
- - 要求:单位向量、互相垂直(点乘为0且叉乘结果为另外一轴)
- 可以获得任意一个向量分解为多个投影
矩阵知识点
- (MxN)(NxP)=(MxP)
- 矩阵没有交换律,只有结合律
- 转置:(AB)T=BT AT
- 单位矩阵I(对角阵I):可以算出矩阵A-1(A逆),可以用于返回变换前的结果
- 向量的点乘、叉乘都可以转换为矩阵相乘
- 点乘:A·B=ATB
- 叉乘:AxB=A*B
概率论复习(用于路径追踪)
X:随机变量
pi一定是非负的,所有pi相加等于1
EX:数学期望
X~P(X):概率密度函数,概率的连续分布情况
变换
2D 变换
缩放
- X'=SX
- Y=SY
X轴翻转
- X'=-X
- Y'=Y
切变
旋转
R-θ=RθT
齐次坐标
为了解决平移产生必须要用加法的问题,加入齐次坐标。
- 矩阵没有交换律:同时需要平移和线性变换的时候,需要先线性变换再平移
- 仿射变换=线性变换+平移
3D 变换
齐次坐标
3D点:(x,y,z,1)T
3D向量:(x,y,z,0)T
旋转向量与欧拉角
RXYZ(α,β,γ)=RX(α)Ry(β)Rz(γ)
利用右手螺旋定则
在三维坐标系中:
XxY=Z XxZ=-Y YxZ=X
因此RY(α)中为X或Y的转置
同时,去掉分别代表XYZ运算的行列即可变换为最基本的旋转式
罗德里格旋转公式
这是一种可以表示任意旋转后的向量
四元数解决了两个旋转角度中插值的问题,本课中不具体讲解
视图变换
什么是视图变换?
视图想想如何拍一张照片:
- 找一个好的地方并安排好拍照的人(模型变换)
- 找一个好角度并放好相机(视图变化)
- 茄子!(投影变换)
定义相机
1. position(相机在哪)e
2. look-at/gaze direction(朝哪个方向)g
3. up direction(向上方向:控制歪斜)t
同时,相机默认的向上方向为+Y。永远朝着-Z方向看,永远在原点上。
把任意点相机移到默认点
- 先平移
- 再旋转(利用逆矩阵的性质,先算从原点到任意点再转置过来)
(g x t = e)
投影变换
正交投影:更像工程制图,没有近大远小的性质 透视投影:近大远小,更类似于现实中的形态
正交投影
一种简单的理解方式
- 把z轴去掉
- 相机位于原点,看向-Z轴,向上为Y轴
- 将生成的矩形平移并缩放为[-1,1]²
标准方式
总的来说,要将任意的立方体变成标准(canonical)立方体
先做平移,再做缩放
透视投影
如何做透视投影?
- 将视锥体压扁成立方体
- 做正交变换
“压扁”操作
其中:
n为近平面
f为远平面
利用相似三角形求出y轴的位置
同理可以得到x‘=(n/z)x
Z轴如何处理?
当我们完成X、Y轴处理后,发现Z轴仍不知道,同时乘z后得到右下的矩阵
同时可以通过矩阵叉乘得到未知矩阵的一部分:
(?,?,?,?))->(0,0,?,?)
再通过下图的方法得出原本“still unknown”的数值为n²
以此得到:
An+B=n²
同时z轴在远平面上的数值并不会改变,因此得到:
Af+B=f²
通过解上面两个方程得到:
A=n+f
B=-nf
因此Z轴一行的矩阵为:
(0,0,n+f,-nf)
光栅化
如何定义视椎体宽高比和垂直可视角度?
fov:垂直可视角度
t:在y轴的高度
n:近平面上的z轴上的点
tan(fov/2)= t/|n|
r:中心点到右边的距离
宽高比(aspect) = 2r/2t = r/t
如何将投射完成的标准立方体显示到屏幕上?
什么是屏幕?
- 二维数组
- 每个元素是一个像素
- 一种经典的光栅成像设备
光栅化:在屏幕上绘画
像素:
- 在屏幕上最小单位的小方块
- 由红绿蓝三原色混合而成
屏幕空间
- 像素都是以(x,y)的形式表示,其中x、y都是整数
- 像素的范围从(0,0)到(宽度-1,长度-1)
- 像素的中心在(x+0.5,y+0.5)
- 屏幕覆盖范围为(0, 0) 到 (宽, 高)
视口变换
- Z轴被忽略
- 将原本【-1,1】²的正方体变换为【0,宽】x【0,高】(视口变换)
- 视口变换矩阵
光栅显示设备
- 阴极射线管
- - 隔行扫描方法
- CRT显示器
- 示波器
- 帧缓冲器
- 平板显示设备
- - LCD(液晶显示器)
- OLED
- LED(发光二极管)
- 电子墨水屏
三角形-基本形状单元
- 最基础的多边形
- - 任何多边形都可以
- 独特的性质
- - 除非折成两个三角形,否则永远是一个面
- 三角形的内外很明确
- 可以利用重心插值进行三角形顶点插值
采样
在某个点对函数求值就是采样,我们通过采样将函数离散化。
Inside函数
inside(tri,x,y)
1:point(x,y)在三角形内
0:其余情况
遍历所有点,判断所有点是否在像素内
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);
}
}
通过叉乘计算出(见叉乘在图形学的应用)点是否在三角形内
如果碰巧点在三角形的边界,本课程中不做处理,也可以特殊处理
包围盒优化
利用包围盒(Bounding Box)对一定不会包含三角形的像素进行优化
对于窄长的三角形并不友好
采样的瑕疵
- 锯齿
通过inside函数渲染出来的三角形有明显锯齿
抗锯齿是图形学中重要的难题
通过对原本三角形做模糊处理再进行采样可以抗锯齿(反走样)
不能先采样再做模糊
之所以会出现锯齿(走样)是因为出现了频谱混叠(后面有讲)
- 摩尔纹
- 车轮效应
信号时间变化太快以至于采样跟不上变化的速度
时域与频域
频域是描述信号在频率方面特性时用到的一种坐标系
时域是描述数学函数或物理信号对时间的关系的一种坐标系。
傅里叶级数展开
傅里叶变换会将时域转化为频域
将函数表示为正弦余弦的加权和
随着展开式越来越多,越来越接近我们想要表达的函数
如果采样的频率不够,还原过来的函数就会越来越不精准
频域和时域可以通过傅里叶变换和逆变换互相转换
傅里叶频域图
滤波
删除特定的频率被称之为滤波
高通滤波:只显示高频信息(只显示边界-锐化,将低频信息盖住
低通滤波:只显示低频滤波(画面变模糊,将高频信息盖住
卷积
滤波=卷积(=平均)
简化的定义:结果为相邻数的平均值
定理:时域的卷积等于频域的乘积
频谱混叠
由于采样稀疏,因此出现频谱混叠从而出现锯齿(走样)如果屏幕中像素非常多,密集的采样就不容易出现走样。
因此使用分辨率高的显示器,频谱的搬移间隔大,不容易出现频谱混叠。
同时,将信息进行低通采样再进行采样即可反走样。
反走样(抗锯齿)
解决方法:
- 通过将每个像素进行模糊卷积f(x,y)
- - 卷积=滤波=平均
- 然后再对灭个像素的中心取样
在光栅化一个三角形时,像素颜色的平均值f(x,y)= 三角形的覆盖像素的面积
超采样抗锯齿(MSAA)
MSAA: Antialiasing By Supersampling
这是一种对反走样的近似
将每个像素的内部多增加采样点再进行模糊卷积
MSAA X4
缺点:增加了很多的计算量
其他的抗锯齿方法
- FXAA (Fast Approximate AA)
- - 快速近似抗锯齿
- 图像的快速处理
- TAA (Temporal AA)
- - 对上一帧进行处理
超分辨率
从低分辨率处理成高分辨率
与反走样类似,也是解决了样本不足的问题
目前可使用DLSS(深度学习的方法)进行超分辨率处理
画家算法
灵感来源于画优化
先画(渲染)距离远的,再画(渲染)距离近的。
问题:难以确定谁在前谁在后
深度缓冲(Z-Buffer)
- 深度图-储存每个像素对应的最浅的深度
- 结果图-储存最终的结果
特别定义:Z越小(越黑)越近,越大越远(与通常的右手系不同,仅仅为了便于理解)
算法:
//默认深度为无限远
for(each triangle T)
{
for(each sample(x,y,z) in T)//遍历任意一个三角形中的任意一个像素
{
if(z<zbuffer[x,y])//如果此时的深度小于之前记录好的深度
{
framebuffer[x,y] = rgb;//三角形着色
zbuffer[x,y] = z;//更新小的深度
}
else
{
//...
}
}
}
着色
定义:对不同的物体应用不同的材质
Blinn-Phong反射模型
裴祥风(Bùi Tường Phong)先生改进的反射模型。
- 高光(Specular highlights)
- 漫反射(Diffuse reflection)
- 间接/环境光照(Ambient lighting)
漫反射
兰伯特余弦定律(Lambert)
兰伯特余弦定律:cosθ=l·n
n:法线方向
l:光照方向
不同角度的物体反射的光不同
光照衰减
I1=I0/r²
I:光线强度
朗伯着色器
Kd:颜色扩散系数
Ld:漫反射反射光
漫反射只和物体本身与光线有关,与观察方向v无关
引入max(0,n·l)是因为若点乘小于0说明是从下面射过来的,没有意义。
若两个向量的点乘接近1则离得很近,若接近0则离得很远。
高光
Ls:高光反射光
Ks:镜面反射系数(通常认为是白色的)
引入半程向量h,如果镜面反射方向与观察点接近,则半程向量h与法线方向n接近
Blinn-Phong反射模型是对Phong反射模型得改进,引入半程向量h比使用镜面反射方向r计算量更小
由于cosα的容忍度太大,导致高光太大,所以引入p次幂,一般使用100-200次幂
环境光照
环境光与入射方向、法线方向、观察方向无关,是一个常数
总结
着色频率
着色频率不同,着色效果也不同
- 以顶点为单位着色(Gouraud shading)
- 以三角形平面为单位着色(Flat shading)
- 以像素为单位着色(Phong shading)
如果面足够多,逐顶点未必比逐像素效果好。
从一个球来获取法线方向是容易的,
复杂的模型通过将相邻的四个面进行对面积的加权平均得到四个面的平均法向量。
逐像素着色通过顶点法线的重心插值来实现
实时渲染管线
简化的流程
- 输入空间中一系列的点
- 顶点处理
- 三角形处理
- 光栅化
- 着色
- - 片段(像素)处理
- 帧缓冲区处理
- 输出
目前渲染管线都是在GPU中被编程完成了,只有顶点处理和片段处理可以编程
Shader编程
利用GLSL对顶点着色器和片段着色器进行编程。
片段着色器对每个片段都执行一次。
以下是一个GLSL片段着色器的程序代码:
uniform sampler2D myTexture; // 获取纹理 uniform是全局变量
uniform vec3 lightDir; //获取光照方向
varying vec2 uv;//获取uv坐标
varying vec3 norm; //获取法线坐标
void diffuseShader()
{
vec3 kd;//获取kd系数
kd = texture2d(myTexture, uv);
kd *= clamp(dot(–lightDir, norm), 0.0, 1.0); //Phong模型漫反射
gl_FragColor = vec4(kd, 1.0); //输出该像素的颜色
}
图形管线实现工具
- 集成显卡
- 独立显卡
GPU:多核心进行多现成并行计算
纹理映射
纹理映射就是定义任意点的基本属性。
每个三维的模型上的任意点都能对应在uv坐标上的某个点上。
纹理可以被重复使用
重心坐标
插值
为什么要插值?
为了获取平滑的过渡
插值的内容有哪些?
纹理坐标、颜色、法向量
如何做插值?
定义与性质
重心坐标是定义在三角形上的,在三角形ABC所形成的平面内任意一个点(x,y)都可以表示为三个顶点ABC坐标的线性组合。
仅需要满足α+β+γ=1的条件。若αβγ均非负数,则(x,y)点一定在三角形内。
A点的重心坐标为(1,0,0)
B点为(0,1,0)
C点为(0,0,1)
同时,重心坐标的αβγ可以使用它占三角形的总面积来表示,
例如AA的面积为(x,y)点与BC点的连线形成的小三角形,
因此也说明了αβγ相加为什么需要等于1。
重心可以表示为(α,β,γ)=(1/3,1/3,1/3)
插值的应用
因此可以通过获得ABC点的值来获取到三角形内任意坐标的位置、颜色、法线、深度等信息。
值得注意的是在投影下不能保证坐标不变,所以要线插值再进行投影。
应用材质
将原本在各个顶点上的值,通过重心坐标插值到uv以及纹理坐标上的频幕上的每个采样点上。
纹理定义的值就是漫反射系数Kd
问题1:纹理太小了怎么办?-双线性插值
双线性插值(中)和双三线性插值(右)
双线性插值(Bilinear)
- 找到(s,t),s、t都在0-1之间
- 进行线性插值
- - 先进行上下两点的水平插值,例如u0 =(u00+u10)s
- 再进行竖直的插值(u0+u1)t
- 得到红点对应的值,例如rgb值
问题2:纹理太大怎么办?-Mipmap
会造成进处出现锯齿,远处出现摩尔纹。
Mipmap只能做近似的正方形的范围查询。
- level0是原始图像,每提高一个level,分辨率小一倍,利用相邻的四个像素的rgb做平均操作。
- 做mipmap比会增加原本图像1/3的额外存储量。
Mipmap操作:
- 在屏幕空间中取当前像素相邻的像素带你并查询其对应的uv坐标。
- 计算出当前像素点与其他像素点距离其他像素点的最大值L。
- 根据最大值L通过上面公式计算得到该点所处的层数D。
通过mipmap操作得到哪些区域的像素要使用第几层进行平均操作。
过渡不平滑-三线性插值
如果算出来的D值是一个小数,这会造成图片Mipmap做错不平滑的问题。
如何解决——三线性插值
- 对该D值分别进行向下和向上取整。如D=1.2Z则取1和2
- 对两个D值分别进行双线性插值(见纹理太小)。
- 对两个插值的结果再做一次线性插值,如0.8xD1+0.2xD2
过度模糊-各向异性过滤
如果只使用Mipmap则远处会出现过度模糊的问题(完全糊成一块)。
如何解决——各向异性过滤
纹理中未必都是正方形的像素,因此运用mipmap查询会造成查询范围过大。
对矩形进行x轴或者y轴的压缩来进行各向异性过滤。
各项异性过滤可以解决矩形的纹理,但无法解决斜向的纹理。
解决斜向的纹理-EWA过滤。通过多次圆形的采样来解决过度模糊的问题。
纹理映射的应用
纹理=内存+范围查询(例如mipmap)
纹理是GPU上的一块内存,我们可以对内存做范围查询。
环境映射(Environment Map)
球面环境映射(Spherical Environment Map):
将环境光反射在球上就可以获得该场景的环境光。
球面映射(Spherical Map):
将球面环境映射可以展开为一张图,但是上下会被扭曲(类似世界地图)
立方体映射(CubeMap):
为了解决球面扭曲的问题,使用立方体来进行环境光照的纹理映射。
凹凸/法线贴图(Bump/Normal Map)
- 其最大的意义是为了表现相对高度来展示凹凸效果减少面数。
- 凹凸、法线贴图仅表示凹凸效果不会改变几何形体。
计算法线贴图(一维)
假设下图中的蓝点为p点()
- p点原来的法线朝上,即n(p) = (0, 1)
- 下图蓝色曲线为使用法线贴图后的效果。
- 通过dp = (c[h(p+1) - h(p)])/1求出两点的高度差。其中c为常数表示凹凸贴图的影响程度,h为高度p点、p+1点对应高度。
- 因此切线可表示为(1,dp)。
- 切线与法线为垂直的关系因此n(p) = (-dp, 1)
计算法线贴图(二维)
二维的情况下有u、v两个方向的变换。
实际情况下法线方向不一定朝上,这里的例子是基于一个局部坐标系确定的。
- n(p) = (0, 0,1)
- dp/du = c1 * [h(u+1) - h(u)]
- dp/dv = c2 * [h(v+1) - h(v)]
- n = (-dp/du, -dp/dv, 1)
位移贴图(Displacement mapping)
- 位移贴图会真实改变模型,会展现出凸起部分的投影。
- 模型需要足够细致,采样需要足够高。
DirectX使用曲面细分来提高计算效率。
三维纹理
- 利用三维空间中的噪声函数进行纹理映射(例如Perlin Noise)
- 预先进行环境光遮蔽计算模型阴影
- 体渲染通过三维纹理记录信息,然后进行渲染
几何
几何表示方法的分类
- 隐式
- - 可以通过一个函数来表示的几何体。
- 例如圆可以表示为f(x,y,z)=0
- 优点:可以很容易判断某个点是否在几何体上
- 缺点:难以通过函数判断出几何体的真实形状
- 显式
- - 通过参数映射表示的几何体(uv坐标转换为xyz坐标)
- - 参数映射:通过某个带有u,v的函数分别表示出x,yz的坐标
- 直接给出几何体
- 缺点:难以表示出某个点是否在几何体上。
- 优点:容易看出来几何体的真实形状
隐式表示
对于复杂的几何体十分不友好。
隐式几何
CSG(Constructive Solid Geometry)
复杂的几何体通过简单几何体进行集合运算(交并补)得到,该操作被称之为CSG。
距离函数
下图A与B相进行融合操作后得到blend(A,B),左边三分之一完全被挡住,中间被挡住一半,最后边完全没被挡住。
距离函数:任何一个点到达边界的最短距离。
通过AB距离函数相加得到融合后的SDF图,可以转化为blend(A,B)这张图。
距离函数可以将两个靠近的集合体进行融合。
距离函数通过水平集(LevelSet)得到F(X)=0(边界)
分型
分型这种递归问题在渲染中会引发严重的走样
显式几何
点云
- 用密集的点放在空间中
- 是某个坐标系下的数据集
- 每个点包含了坐标、颜色等一系列信息
- 只要采样足够密集,理论上可以表示任意集合体
- 如果采样不够密集,将会无法分辨模型的形状
- 应用:激光扫描
多边形网格
简介
- 讲面拆解为多边形(大多是三角形和四边形),存储顶点和多边形信息
- 在图形学中应用的最为广泛
如何储存多边形信息?
使用OBJ格式讲几何体的点、法线、纹理坐标分别表示,然后再表示,面与面的连接关系。
下图定义了一个立方体,有八个顶点(V),六个面(Vn)多个纹理坐标(vt)表示,然后使用f表示他们之间的关系((f V/Vt/Vn)
贝塞尔曲线
只要求一定要经过起止点,起止点之间的若干个控制点用于控制曲线弯曲的方向,最终形成一条经过起止点的光滑曲线被成为贝塞尔曲线。
德卡斯特里奥算法
通过德卡斯特里奥算法法来绘制贝塞尔曲线。
- 引入参数t(范围 为0-1)
- 取b0到b1,b1到b2上t位置的点b0‘,b1’
- 将b0‘,b1’连接
- 取b0‘到b1’t位置上的带你b0‘’
- 将所有的0-1所有的b0‘’点都遍历一份相连即可得到贝塞尔曲线
- 若有n个控制点则将上面步骤进行递归操作直到找到最终位移b0n
总结得到公式:bn(t)=b0n(t)=Σb0Bn(t)
可以求得一个以t为自变量的函数,由这些点形成的集合构成贝塞尔曲线
其中:
- b为n个贝塞尔控制点
- Bn为伯恩斯坦多项式
其中:
(排列组合Cni)
例子:b0、b1、b2、b3为3d空间中的点,通过函数求得n个离散的点
- 对贝塞尔曲线做仿射变换只需要对控制点、起止点做仿射变换再重新绘制一遍即可。
- 对投影变换没有这样的性质
凸包
贝塞尔曲线拥有凸包的性质。
连接贝塞尔曲线最外围的控制点,将其相互连接形成一个封闭空间,画出来的贝塞尔曲线一定在凸包范围内。
若贝塞尔曲线是一个直线则凸包也是一个直线。
逐段贝塞尔曲线
当控制点太多会影响控制点的效果。
每四个控制点定义一条贝塞尔曲线,然后再将他们连接起来。
类似PhotoShop的钢笔工具
若想要逐段贝塞尔曲线平滑过渡则需要将相邻控制点共线(如3,5)否则会出现该曲线后边段的不平滑现象。
CN连续
将两个逐段贝塞尔曲线中的两部分连接,连接点被称之为C0连续
若相邻两个控制点距离连接点相同且共线则该连接点被称之为C1连续(再连接处一阶连续可导)
贝塞尔曲面
与二维的贝塞尔曲线思想类似,但是需要定义两个参数u和v(取代之前的参数t)
先对f(u)进行遍历得到曲线,再嵌套遍历得到曲面f(u,v),类似于两层for循环进行遍历
网格操作
网格操作的分类
- 网格细分
- - 让网格的面数更多
- Loop细分
- Catmull-Clark细分
- 网格简化
- - 让网格面数更少
- 边坍缩
- - 通过“二次误差度量”得到坍缩后最优的点
- 网格正规化
- - 让网格中的三角形趋近于正三角形
Loop细分
- 将每个三角形变为4个三角形
- 根据权重指定新的顶点位置
- - 新的顶点和老的顶点以不同的规则来改变自己的位置
对于新的顶点:
V'=3/8 * (A + B) + 1/8 * (C + D)
- V'为新的顶点变换后的位置
- A、B分别为于两个面被共享边的的老顶点
- C、D为非共享边的两个顶点。
对于旧的顶点:
V'=(1 - n*u) * V + original_position * neighbor_position_sum
- V'为旧的顶点变换后的位置
- n为顶点的度(链接的顶点数)
- 如果n=3则u=3/16 其他情况u=3/(8n)
- neighbor_position_sum为邻居点的平均位置
- original_position为旧顶点原本的位置
问题:只能对完全为三角形的几何体进行细分
Catmull-Clark细分
相对于Loop细分的优势:可以用于任意不同的面的细分。
奇异点:顶点的度!=4
- 将面的中点和面上线的中点连起来
- - 在第一次细分之后,非四边形面数量会加到原本奇异点的数量上
- 面上的中点(f)、边上的中点(e)、老的顶点(v)变化情况如下图
边坍缩算法
通过不断迭代进行边坍缩操作达到简化模型的目的。
边坍缩面临的问题:
- 坍缩哪些面?
- 如果优先坍缩不重要的面,那如何界定不重要?
- 坍缩后的顶点位置如何描述
二次误差度量
如果将减面时候的点直接平均将会得到左边的图,显然不理想。
通过二次误差度量得到右边的点得到理想效果。
边坍缩算法的步骤
- 对每一条边打一个分数,分数就是他坍缩后的二次误差度量
- 对分数最低(误差最小)的边做边坍缩
- - 用到了Dijkstra最短路径算法
- 重新执行第一步知道完成整个模型的边坍缩
阴影映射(Shadows mapping)
基本流程
- 从光源看向场景并做深度测试
- 从视锥体位置看向场景,从场景中看到的点投影回光源得到该点在深度图中的位置
- - 有些点可以被视锥体看到也可以被光源看到(视锥体投影到的深度图与光源中的深度图相同)
- 有些点可以被视锥体看到但不能被光源看到(视锥体投影到的深度图与光源中的深度图不符)
- 不能被光源看到的位置但可以被视锥体看到的位置就是阴影的位置
出现的问题
- 只能做硬阴影不能做软阴影(目前以及有技术可以做到)
- 由于shadow map的分辨率问题,阴影可能出现锯齿
- 浮点精度问题,可能在是否能同时被视锥体与光源看到的界定上出现问题引发误差
软阴影
软阴影的边缘比较模糊,没有硬阴影锐利的边缘
下图以日食为例子介绍了软阴影在真实物理中形成的原因:
光线追踪
为什么要使用光线追踪?
光栅化难以将以下的效果做好:
- 软阴影
- 毛玻璃材质的反射
- 间接光照
光栅化虽然较快,但是质量较低。
光线追踪的几个假设
- 光沿直线传播(虽然这是错的)
- 光线与光线之间不会发生碰撞(虽然这是错的)
- 光线从是从光源不断传播直到视锥体的(光线的可逆性)
光线投射
光线投射的假设:
- 出射点是一个点
- 光源点光源
- 场景物体中的反射为镜面反射
光线投射的步骤:
- 从初射点穿过成像平面打出一根光线到场景中
- 找到与场景的最近交点
- 将交点和光源连接, 判断物体是否在阴影中
- 计算着色情况写回像素中
Whitted-Style光线追踪
渲染速度
对于下面这张图:
- 1979年需要74分钟
- 2016年需要6秒
- 2012年只需要1/30秒
递归光线追踪
- 光线不仅仅只会反射,还会折射、然后再与其他物体进行反射
- 光线每次反射折射都会有能量损耗,不然经过无限次的累加只会变成白色
- 递归过程需要设置一个最大次数
- 两次反射和折射情况如下图可视
确定光线与场景的交点
光线方程
对于每一束光线都满足以下方程:
其中o为开始点,d为方向(单位向量),t为时间
r(t)=o+td 0<=t<∞
求交点:
对于任何隐式的集合体,将r(t)以p点带入隐式的方程中算出f(o+td)=0即可算出t求出交点。
显式几何
- 几何体由若干个面组成
- 在几何体的面上判断是否与他的面相交
- 几何上的平面由一个法线和一个点p‘表示
步骤:
- 可使用(p-p')·N=0表示一个平面
- 将光线方程以p带入光线方程中
- 求出t算出交点
- 判断交点在三角形的内还是外
- 若在内则是三角形的交点
Möller Trumbore算法
中文名: 射线三角相交算法
可以更快的求三角形与射线的交点
- 已知 光线满足r(t)=o+td
- P0、P1、P2为三角形三个顶点
- 可以得到以下等式
- 通过以下E1、E2等的参数定义可以解出等式得到交点
光线追踪加速
如果三角形面特别多,以上面的算法进行计算将会特别慢
轴对齐包围盒(AABB)
特殊的包围盒定义
三对面的交集形成的立方体,三对面分别于xyz轴平行
使用包围盒包围与光一定轴对齐,减少了计算量
原始方案中:
需要3次减法,6次乘法和1次除法。
AABB中:
每一个轴只需要一次除法和一次减法,一共只需要3次减法和三次除法
这么定义包围盒的好处:
只有三对面都有光线相交(进和出)时间才能证明光线已经进入包围盒
这也定义可以先判断光线是否经过该包围盒,如果不经过就不再进行更多的操作,节省了计算时间。
判定光线与包围盒的交点
对于二维的包围盒
- 先判断与x这对面与光线相交(进和出)的时间tmin、tmax
- 再判断y与光线的相交时间
- 取交集(求出tmin的最大值,tmax的最小值)
三维与二维类似:
- 增加了一对平面,判断三对面的相交时间tmin、tmax
- 求出tmin的最大值,tmax的最小值
- 若tmax-tmin大于0则说明光线与包围盒相交
几种负值的情况的处理:
- tmax<0则说明包围盒在光线背面(无交点)
- tmin>=0且tmin<0则说明光源再包围盒中(有交点)
总结:AABB有交点当且仅当tmin<tmax且tmax>=0
空间划分
AABB的均匀划分
AABB均匀划分的步骤:
- 找到包围盒(最外层的正方体)
- 建立网格(黑色网格)
- 标记与包围盒相交的网格(灰色标记)
- 从光线发射方向逐个遍历网格
- 将每个遍历到的网格测试与其的交点
建立网格的目的:
- 通过网格可以判断有tmin、tmax
- 若有则对网格内的物体进行交点判断
网格的密度认为数量大约为27x物体数量
但是这种网格定义方式对物体分布不均匀的场景中不友好(即使这样的网格也很常用)。
因此引入空间划分来切割网格。
空间划分的分类
八叉树:
- 八叉树是在每个子树下面画十字,划分为四块(在二维下是分为四块,三维是八块)
- 由于是均匀划分,会出现将同一个物体划分为两块的问题。
KD树:
- 每次划分只划分为两块(类似二叉树)
- 在二维中第一次为水平的划分,第二次为竖直的,然后循环划分
- 在三维中类似,以xyz轴顺序进行划分
- 这样可以保证划分比较均匀
- 我们在AABB中主要使用KD树(曾经)
BSP树:
- 划分和kd树类似
- 由于划分不是横平竖直的不能用于AABB的划分
KD树
建立KD树
通过一次对x,y(二维)进行递归划分,得到下面的KD树
遍历KD树
- 假设有一条光线射出
- 逐个对每个子树进行判断是否有与包围盒相交
- 若相交则对他的子树继续遍历知道将找到所有与光线相交的包围盒
- 将其包围盒下的物体查找交点
KD树的问题:
- 难以判断物体与包围盒边界的相交问题
- 一个物体容易同时穿过多个包围盒被重复计算影响性能
因此KD树的应用场景越来越小。
目前广泛使用的技术名叫层次包围盒(BVH)
层次包围盒(BVH)
BVH的特点:
- 按照三角形进行划分,因此一个物体只可能出现在一个包围盒内
- 由于按照三角形进行划分,因此不会出现那一判断包围盒边界的问题
- BVH的思想更像分组,只需将三角形进行分组并对三角形较多的组进行递归分组
- 每次的划分都选择XYZ中最长的轴进行划分,这样保证划分的大小比较平均
- 总是选择中间的三角形进行划分,这样划分出来树更加接近平衡二叉树
- 划分到一个比较小的数量后停止划分(比如5个三角形)
BVH的伪代码:
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);//如果不是叶子节点则递归找到最近的那个
hit2 = Intersect(ray, node.child2);
return the closer of hit1, hit2;
}
辐射度量学
由于Whitted-Style形成的光线追踪体系其实并不够真实,引入辐射度量学
同时定义了以下几个属性来描述光照:
- Radiant Energy(辐射能量)
- Radiant Flux(辐射通量)功率
- Radiant Intensity(辐射强度)光源发出的某一方向上的亮度
- Radiant Irradiance(辐射光照度)某一平面所接受到的光线亮度
- Radiant Radiance(辐射亮度)一条传播光线所具有的亮度
Radiant Energy
光线照射下辐射出来的能量,单位为焦耳,用Q表示。
Radiant flux
- 单位时间内的能量消耗,类似于功率,单位为瓦/流明*
- 单位时间内通过一个感光平面光子的数量
Radiant Intensity
角
弧度制:θ=l/r
l:弧长
r:半径
一个圆形有2π个弧度
立体角
Ω=A/r2
A:球面上的面积
立体角是弧度制在三维上的延申
一个球有4π个立体弧度
微分立体角
- 首先确定空间中的方向θ和
- 其中rdθ是微分面积元的高,rsinθdϕ 是微分面积元的宽
- 两者相乘即可计算出球的投影面积A
同时可以验证立体角在球上的积分为4π
在辐射度量学中用Ω来表示方向,用θϕ定义方向
定义
- 每微分立体角(某个方向)上的功率(光强),单位为W/sr又被称作为cd(坎德拉)
- (光源在某个方向上的亮度)
应用:点光源
- 定义一个点光源所有方向的亮度都相同
- flux为I在所有方向上的积分
- 得出I=ϕ/4π
Radiant Irradiance
单位面积上(某个点)接收到的光照功率,单位为W/m2 或者lm/m2 又称Lux(勒克斯)
在此可以解释布林冯模型中光线亮度需要乘cosθ
同时也能解释模型中光线强度的衰减
Radiant Radiance
定义
单位微分立体角,单位面积的功率,单位为nit(尼特),1nit=1 cd/m²
Irradiance与Radiance的区别
- Irradiance是对于一个点所接受到的所有的光线
- Radiance是对于一个点面向某个方向接受到的光线
注意:H²为半球
双向反射分布函数(BRDF)
- BRDF定义了某个点接受到能量后反射能量与入射光的比例
- 表示指定方向的反射光和入射光的比例关系
- 材质的不同决定BRDF方程的不同
反射方程
借助BRDF可以定义出反射方程,即在某个反向接受到的所有的反射光线。
如上图,反射到的光线是由入射光乘以BRDF这一反射比例得到的。
渲染方程
定义
- 渲染方程由两部分组成,自发光Le和反射光
- 其中反射光可以由光源直射也可以由其他的物体反射
- 在反射方程的基础上加入自发光,定义了渲染方程
- 渲染方程定义了所有的光线传播规律
- n·ωi与cosθ一致
- Ω为半球
单个光源的反射方程
点光源只有一个方向有入射光,所以不用积分
多个光源的反射方程
面光源的反射方程
面光源是点光源的集合因此对面光源所在立体角进行积分可以得出面光源的反射方程
面光源的渲染方程
- 将其他物体反射过来的光当成光源,得到渲染方程
- 只需要计算反射光源其余的自发光、BRDF(材质)、cosθ均已知
方程简化
- 目前只有出射点反射出去的能量和入射点反射过来的能量未知
- 可以将渲染方程最终简化为下面的形式方便理解
最终可以将渲染方程:L=E+KL
- 在一个场景内能量守恒
- L为所有的物体辐射出的所有能量
- E为光源分辐射出来的能量
- KL为光源辐射出来的能量被反射出来的能量
解出L并通过二项式定理得到下面的式子。
- L=E为场景内自身辐射的能量
- L=KE为场景内辐射的能量+一次反射的能量
- L=KE+K²E为场景内辐射的能量+一次反射的能量+二次反射的能量
- 以此类推......
蒙特卡洛路径追踪
蒙特卡洛积分
对函数的积分域多次采样求均值作为积分的近似值
例子:
将积分均匀采样,每个采样的概率都是1/b-a,得出以下式子。
其中将b-a移到后面可以得到最开始蒙特卡洛积分的式子。
Witted-Style的问题
路径追踪解决了在Witted-Style中不正确的部分
- 无法完成Glossy的材质,在Glossy材质中光线打到材质上不完全沿着Specular的方向走
- 漫反射后仍然会多次反射,需要引入全局光照
蒙特卡洛积分应用到渲染方程
将某个渲染点p的ωi方向进行多次采样找到反射光源的角度得到蒙特卡洛积分
通过这样的方法可以算出任意着色点的渲染方程
直接光照的算法
shade(p, wo)
Randomly choose N directions wi~pdf
Lo = 0.0
For each wi
Trace a ray r(p, wi)
If ray r hit the light
Lo += (1 / N) * L_i * f_r * cosine / pdf(wi)
Return Lo
全局光照的算法
shade(p, wo)
Randomly choose N directions wi~pdf
Lo = 0.0
For each wi
Trace a ray r(p, wi)
If ray r hit the light
Lo += (1 / N) * L_i * f_r * cosine / pdf(wi)
Else If ray r hit an object at q
Lo += (1 / N) * shade(q, -wi) * f_r * cosine / pdf(wi)
Return Lo
如果采样中反射过来的不是光源而是物体则将物体也当做光源,将物体反射过来的能量来进行计算。
上述算法的产生的问题
反弹次数的上升产生射线数量爆炸
一根光线打到物体后会反射很多个光线到同一个物体,以此类推产生指数爆炸、
因此如果蒙特卡洛积分采样次数为1则不会出现指数爆炸的现象。
路径追踪算法:
shade(p, wo)
Randomly choose ONE direction wi~pdf(w)
Trace a ray r(p, wi)
If ray r hit the light
Return L_i * f_r * cosine / pdf(wi)
Else If ray r hit an object at q
Return shade(q, -wi) * f_r * cosine / pdf(wi
只采样一次的光线追踪被称之为路径追踪,上面采样N次的被称之为分布式光线追踪(Distributed Ray Tracing)
生成多个路径进行路径追踪:
对于每个像素发射N条光线(采样)做以下的算法
将每个射出去的采样接收到能量的点做蒙特卡洛积分得到平均值
ray_generation(camPos, pixel)
Uniformly choose N sample positions within the pixel
pixel_radiance = 0.0
For each sample in the pixel
Shoot a ray r(camPos, cam_to_sample)
If ray r hit the scene at p
pixel_radiance += 1 / N * shade(p, sample_to_cam)
Return pixel_radiance
由于路径追踪递归产生的死循环
- 首先增加一个结束的概率P_RR
- 每次调用shade函数的时候做一个随机数判断
- 若大于随机数则return0反之继续执行shade函数并返回L0/P_RR
- 这样算下来数学期望不会变,E(L0)=P∗(L0/P)+(1−P)∗0=Lo
- 且路径追踪总会停下来
shade(p, wo)
Manually specify a probability P_RR
Randomly select ksi in a uniform dist. in [0, 1]
If (ksi > P_RR) return 0.0;
Randomly choose ONE direction wi~pdf(w)
Trace a ray r(p, wi)
If ray r hit the light
Return L_i * f_r * cosine / pdf(wi) / P_RR
Else If ray r hit an object at q
Return shade(q, -wi) * f_r * cosine / pdf(wi) / P_RR
提高效率
每个像素中的采样点越多,形成的效果越好,比如下图中最右边的例子需要50000个采样才能找到光线,大多数的采样被浪费了
因此为了提高效率我们可以通过找到光源与方向的关系,改写渲染方程,将渲染方程写成对光源的积分
光源与方向的关系式:
改写的渲染方程:
我们最终渲染出来的光纤传播分解为两个部分
- 光源的直接光照
- 光源对其他物体的弹射(需要用到上面的随机算法)
如果光源中有物体挡住则不能渲染光源的直接光照,通过一个if来解决
shade(p, wo)
# 光源的直接光照
L_dir = 0.0
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
# 光源对其他物体的弹射
L_indir = 0.0
Test Russian Roulette with probability P_RR
Uniformly sample the hemisphere toward wi (pdf_hemi = 1 / 2pi)
Trace a ray r(p, wi)
If ray r hit a non-emitting object at q
L_indir = shade(q, -wi) * f_r * cos θ / pdf_hemi / P_RR
Return L_dir + L_indir
其他的知识
- 点光源不容易处理,因此建议写成一个很小的面光
- 路径追踪可以做到几乎100%的真实
- 光线追踪有很多类型
- - (单向和双向)路径跟踪
- 光子映射
- Metropolis light transport(MLT)
- VCM / UPBP
- 课程中光线追踪未涉及的部分
- - 函数采样理论
- 选择什么样的PDF(重要性采样)
- 随机数的生成
- 结合不同的采用结果(如光源和着色点)
- 像素发出多个路径,是否平均其着色效果即可(pixel reconstruction filter)
- 像素的radiance和color的区别(伽马矫正)
材质与外观(BRDF)
材质由BRDF决定,或者说材质就是BRDF。
漫反射材质
- 对于一个漫反射材质的物体,我们认为物体本身没有自发光(因此没有Le),且不会吸收光线,照射过来的光线会均匀的反射到四周。
- 且假设入射光是均匀的
- 因此Lo=Li,反射的能量等于收到的能量,因此fr(BRDF)=1/π
- 我们可以引入反射率(albedo)ρ,取值位于0~1让物体能够接受能量获得不同颜色的BRDF
- 下面Lo得出了漫反射材质的渲染方程
镜面反射
- 在正视图中(左边的图),镜面反射的入射角和出射角可以通过平行四边形法则借助法线方向得出左边的关系
- 在俯视图,又称方位角(右边的图)可以得出右边的关系
- 通过下面的公式可以算出出射角的方向
折射定律
斯涅耳定律
- 引入折射率,右边是常用介质的折射率
- 比如从空气射入水则ηi为1,ηt为1.333
- 通过上面的公式可以计算出出射角的角度
全反射
- 当出射角无意义即根号小于0((ηi/ηt)>1)时候,则物体不会发生折射,这种现象被称为全反射
- 例如人在水底往上看,由于发生全反射现象,只能看到一小块锥形的区域,这也被称为斯内尔窗
菲涅尔项
- 当入射角与物体相互垂直的时候,反射会更加明显
- 菲涅尔项对绝缘体会更加明显(下1图),对导体来说并不明显(下2图)-因为导体的折射率是负数。
菲涅尔项公式:
- 上面的公式是准确的,但是计算十分麻烦,需要考虑极化
- 下面的公式名为 Schlick’s approximation,是一个近似解,图形学中广泛使用。
微表面材质
- 物体表面即使是粗糙的,但是从远处看,我们可以将其看做是一个平的表面
- 例如在空间站中看地球,也可以看到高光反射的地方
- 从远处看是材质,从近处看是几何
- 如果一个表面法线分布集中我们认为他是glossy材质,如果分布分散则认为是漫反射材质
第一个F是菲涅尔项,决定多少能量被反射(垂直时候被反射的能量多,反之少)
第二个G是几何项,计算微表面上的相互遮挡(在grazing angle平着照射微表面的时候容易出现互相遮挡)
第三个D是法线分布,当微表面的法线方向与给定的出入射角的半程向量一定才证明这个微表面的入射方向出射方向正确
各向同性/各向异性材质
材质的性质进行累加
- 可逆性,对换入射角和出射角可以得到同样的BRDF
- 能量守恒,出射的能量不可能比入射的能量少
- 各向同性和各向异性
BRDF的测量
实际测量出来的BRDF和公式推算出来的BRDF经常会有很大差距
- 可以通过仪器来枚举出测试样例的所有测试点进行BRDF的测量
- 当然这样测量十分的麻烦,我们可以利用BRDF的性质来简化测量
- 比如各向同性的材质只需要测量半圈(由于只有三维且利用可逆性可以只测量半球
- 各种不同的方法来提高BRDF的测量效率......
算法:
foreach outgoing direction wo
move light to illuminate surface with a thin beam from wo
for each incoming direction wi
move sensor to be at direction wi from surface
measure incident radiance
高级图形渲染概要
高级光线传播
- 无偏光线传播
- - 双向路径追踪(BDPT)
- Metropolis光线传播 (MLT)
- 有偏光线传播
- - 光子映射
- Vertex connection and merging (VCM) -结合双向追踪和光子映射
- 实时辐射度算法(IR)
有偏是随着样本的增加,结果会变化,如果样本无限多,结果会收敛到正确值。
无偏是不管样本有多少,都会得到正确的结果。
只要得出的结果会模糊就是有偏。
双向路径追踪(BDPT)
思想:从光源和相机各打出一条光线,然后连接端点。
应用场景:在光源由漫反射形成的场景中效果较好。
Metropolis光线传播 (MLT)
思想:使用马尔科夫链和蒙特卡罗方法,通过一定的概率扰动路径的传播,得到一条新的路径,由一条路径追踪为种子生成其他的路径。
应用场景:在复杂场景中的光线追踪
缺点:难以估计渲染完成画面的时间,局部的结果可能会比较"脏"
光子映射
应用场景:特别适合渲染水中焦散的现象
一种实现方法之一:
- 从光源打出光线,在路径中发生反射折射,直到打到漫反射的物体上停止
- 再从相机打出光线,在路径中发生反射折射,直到打到漫反射的物体上停止
- 做局部的密度估计,对任意一个着色点取他周围的确定光子数并通过其占据的面积计算密度
缺点:如果当光子取值,画面会很脏,如果光子取值很大,画面会变糊。
如果光子数量无限多,那么就可以收敛成正确的画面。
Vertex connection and merging (VCM)
思想:结合光子映射和双向路径追踪,如果BDPT中两边打出的光线不能连接,利用光子映射将一定范围的光线做融合,提高光线利用率。
实时辐射度算法(IR)
思想:光源先进行若干次弹射到某个物体上,将这些物体当做是新的光源再进行路径追踪。
优点:渲染速度快,对漫反射场景效果较好。
缺点:无法处理glossy材质且场景缝隙会产生亮点。
材质建模
非表面模型
散射介质:光在传播时穿过会发生散射的介质比如雾、云等,通过相位函数决定了光如何被散射。
光通过散射介质的两种情况:
- 被吸收一部分能量,被介质削减了一部分能量
- 被反射一部分能量,能量从其他地方的能量照射进来或者介质本身会发光
头发/毛发:毛发可以理解为一个柱状模型,可以直接反射,也可以发生折射进入内部。
介绍了几种模型:
- Kajiya-Kay Model:认为头发只有反射没有折射
- Marschner Model:认为头发是一个半透明的玻璃圆柱体,有反射也有折射
- Double Cylinder Model:动物与人类的头发不同,动物的头发髓质很大因此更容易发生散射,所以需要重新考虑模型,增加了散射这一参考系
颗粒材质:可以制作出比如沙子城堡的材质
表面模型
次表面散射(BSSRDF):
- 光线通过材质内部发生了散射,比如皮肤、玉石等等。
- BSSRDF是BRDF的延伸,下图为BSSRDF的渲染方程和示意图。
- 使用Dipole Approximation,让物体内外各有一个光源来模拟光的扩散。
衣服:
衣物的渲染主要有三种方法:
- 将衣物当作表面,根据不同的织法使用不同的BRDF,相对来说效果较差但是简单快捷
- 将衣服当做散射介质,根据单根纤维的特性与他的分布来构建散射参数
- 将衣物的每一根纤维为单位进行渲染,计算量十分巨大。
表面细节
- 渲染出来的场景太过于完美,反而没那么真实,真实的世界有磨损有划痕。
- 微表面模型的BRDF的法线分布不该是均匀的,满足大体趋势的同时有自己的波动,但是会造成运算量巨大。
- 这是因为在法线分布复杂的情况下,很难建立有效的的光线通路(光源→表面→摄像机)
- 通过一个像素对应了一块表面,把整块范围内的法线分布整合起来获得(P-NDF),以此简化计算
- 如果表面足够微小,需要考虑波粒二象性使用波动光学,传统的几何光学反而不太够用了。
- 使用波动光学得到的BRDF和几何光学得到的BRDF类似又有自己的特点(光的干涉和衍射)
程序化生成
- 通过使用噪声函数,不需要真正生成复杂的材质,可以通过查询的方法来简化计算
- 通过查询的方法可以生成各种各样的模型
- 工业界使用Houdini专门来做程序化生成材质
照相机、镜头和光场
成像
- 成像 = 合成+捕捉
- 最早的相机是通过小孔成像的原理来成像的,现在的微型相机也是使用这个原理
- 快门控制光能在相机中待多长时间
- 传感器(Sensor)记录的是光的Irradiance信息
- 微型摄像机没有透镜,因此做不成景深
视场(FOV)
- FOV是相机的可视角度范围
- FOV=2arctan(h/2f)其中h为传感器高度,f为焦距
- 因此视场与传感器大小、焦距均有关
- 手机摄像头通过同时减少焦距和传感器大小以求得到和相机类似的FOV
曝光
- Exposure = time * irradiance,曝光=时间*Irradiance
- 时间由快门控制,irradiance由光圈的大小和焦距控制
- 快门速度慢,曝光时间长,光在感光器上停留时间长,会产生运动模糊的现象
- 对于机械快门,如果拍摄高速物体会产生运动扭曲的现象
- 光圈的大小控制单位时间内的进光量,光圈可以制造景深,光圈越大景深越明显
- 通过F数(F/N)来描述光圈的大小,F数是焦距/光圈直径(N=f/D)
- 因此运动模糊和景深无法同时出现
- ISO是一个后期的处理值通过乘上ISO来改变整体的明暗
- ISO也可以在硬件上进行改变,比如改变感光器的灵敏度
- ISO过大会产生噪声,因此一般不直接大范围调整ISO来提高亮度
- 通过很长的曝光时间应用于延时摄影上
镜头(透镜)
- 真实的透镜是由很多块玻璃叠在一起制作而成的
- 薄透镜对于平行光会集中在一个点,被称之为焦点
- 目前的相机通过多块透镜的组合可以制作出不同的焦距
- 课程中我们考虑的是理想的薄透镜,同时可以任意改变焦距
- 物距、相距、焦距之间的关系:1/f=(1/zi)+(1/z0)其中f为焦距,zi为相距,z0为物距
- 在焦点前后,光线开始聚集和扩散,点的影象变成模糊的,形成一个扩大的圆,这个圆就叫做CoC
- 离焦模糊是在拍照时,被摄物体不在焦点,使得成像不是清晰的一个点,而是变成coc,coc随着光圈的增加而增大。
- coc的宽度:C/A=d'/zi=|zs-zi|/zi
- 其中zs为透镜到coc的距离,A为镜头的直径,d'为zs-zi的绝对值
光线追踪与薄透镜
实现步骤:
- 首先确定成像平面的大小、透镜本身的焦距和光圈大小
- 然后定义透镜与拍摄平面的物距z0
- 根据前两项确定的数据算出像距zi
- 在成像平面上选一个点x''
- 在透镜上选另一个点x''
- 连接x'和透镜中心点并延长,与subject plane相交得到交点x''',这样我们就可以知道x'''→x''这条光线最终会被记录在x'点上
- 因此我们只需要考虑x''→x''这条光线上的radiance,算出来记到x'上。
景深
- 场景中的光经过透镜,打到成像平面,在成像平面附近的某个区域,这个区域内CoC都是足够小的。
- 当CoC足够小,我们就认为这个地方是清晰的,而这一区域就被我们成为景深(DOF)
- 景深外的模糊的范围会因光圈大小而变化
- 下面是DOF的计算公式
光场
- 我们通过全光函数P(θ,φ)用于描述我们能看到的所有东西
- 引入波长λ让其拥有色彩,P(θ,φ ,λ)
- 引入时间t让画面可以看到不同时间的场景,P(θ,φ ,λ,t)
- 引入摄像机的位置Vx、Vy、Vz,可以在任意位置观察场景,P(θ,φ ,λ,t,Vx,Vy,Vz)
- 可以将视觉定义成一个七维的函数这个函数也就是全光函数,P(θ,φ ,λ,t,Vx,Vy,Vz)
- 光场是全光函数的一部分P(θ,φ ,Vx,Vy,Vz),是在任意位置任意方向光的物理量
- 人们法线只需要四维信息就可以定义光的物理量: 2D位置信息(u,v)+ 2D方向信息(θ,φ)
- 我们同样可以通过获取一束光在两个平行平面(s,t)和(u,v)上的位置来确定光的位置
- 一般相机的传感器接收到信息是三维到二维的投影,损失了深度,记录的是Irradiance
- 光场相机通过棱镜分光将的每一块单位面积记录了光场的所有信息Radiance(u,v,s,t)
- 由于记录了光线的Radiance,可以通过仅观察某个方向的光线,同时可以观察特定方向的光线实现聚焦的功能
- 光场相机由于需要多个传感器才能记录一个像素的信息因此分辨率不足,同时由于棱镜精密因此高成本
颜色与感知
颜色
- 图形学仅关心光谱中的可见光(400~700nm)
- 谱功率密度(SPD) :描述一束光再波长上的分布
- 颜色是一种人的感知不是光的一种属性
- 人眼的结构也类似于相机,瞳孔对应光圈、晶状体调节焦距,视网膜相当于传感器
- 人眼视网膜上分为视杆和视锥细胞分别感知光强和颜色
- 其中视锥细胞分为SML三种对不同波长的光线敏感
- SML也分别为对应光谱波长的积分
- 同色异谱:不同的SPD的同时拥有相同的SML因此产生相同的颜色
- RGB:通过调整RGB三原色的比例来混合出想要的有颜色
- 有些颜色怎么混合也混不出来(红色在一段波长的贡献的负的),可以通过给原色加色的方法来混合颜色
色彩空间
- sRGB:广泛使用的色彩标准,色域有限,是一种加色系统
- CIEXYZ:一种人为定义的色彩标准,覆盖了所有可见光
- - 其中Y指的是亮度
- 通过归一化得到xyz,并固定Y,这样可以制作出下面的这一张图得到所有色域
- HSV:通过色调(H)、饱和度(S)、明度(V)来定义颜色空间的一种颜色标准
- CIELAB:通过三个轴,分别控制亮度和两对互补色(红-绿、蓝-黄)
- CMYK:减色系统,由靛蓝、品红、黄色三种颜色混合出其他颜色再加上黑色组成,广泛应用于印刷
模拟与动画
简介
动画是一种信息传递的工具。
常用帧率:
- 电影:24FPS
- 视频:30FPS、29.994FPS
- VR:90FPS+(不晕的基本要求)
- 游戏:30、60FPS+
动画里程碑
- 3200BC-远古时期人类的壁画-Shahr-e Sukhteh, 伊朗
- 1931-Phenakistoscope圆盘,通过转动圆盘来做动画
- 1878-第一部电影Sallie Gardner
- 1937-第一部剧场版手绘电影-白雪公主与七个小矮人
- 1963-第一个计算机生成的动画
- 1972-早期计算机动画,人脸动画
- 1993-侏罗纪公园
- 1995-玩具总动员,第一部完全计算机生成的动画电影(光栅化)
- 2009-天降美食
- 2019-冰雪奇缘2
关键帧动画
- 最早是由最厉害的原画画出关键帧,其他助手画出之间的差值(渐变帧)
- 现在通过插值补充两个动画中的差值
- - 线性插值一般过渡不那么自然
- 使用b样条之类的平滑插值技术
物理模拟
介绍
- 通过建立真实的物理模型来模拟出真实的动画系统
- 例子:布料模拟、流体模拟
质点弹簧系统
简单定义:一系列相互连接的质点和弹簧
- 理想弹簧:
- - 胡克定律:fa-b=-k·(b-a),k:劲度系数
- 牛顿第三定律:fb-a=-fa-b
- 问题:没有初始长度
- 非零长弹簧:
- - 引入原长l||b-a||
- ||b-a||-l 为弹簧被拉伸的长度
- b-a/||b-a||表示a->b力的方向,是归一化
- 问题:由于没有阻力会永远震荡
- 加入阻尼力:
- - 再讲内容之前先普及一点:x为位移,x上一点是速度(一阶导数)点两个点是加速度(二阶导数)
- f是阻尼力,b加上一点是速度,kd是劲度系数
- 问题:无法描述弹簧内部的力
- 完善的阻尼力:
- - 红框为b与a相对速度在b与a方向上的投影,是一个标量
- 红框右边是a到b的方向
问题:这样的弹簧系统无法应对对折的情况(比如讲一块布对折)
解决:通过加入斜向的弹簧来解决这样的问题。
问题:这样的弹簧系统无法应对沿着弹簧竖着对折的情况
解决:加入跳跃连接的点,隔一个支点加一个弹簧,这个弹簧十分的弱
有限元法FEM
- 方便制作力的传导
- 应用于车辆碰撞的模拟
粒子系统
粒子生成步骤
- 生成粒子
- 计算每个粒子的力(难点)
- 更新每个粒子的位置速度
- 将死亡的粒子移除
- 渲染
粒子之间的作用力
- 引力与斥力
- - 重力
- 电磁力
- 阻尼力
- - 摩擦
- 空气阻力
- 粘度
- 碰撞
- - 墙壁
- 容器
- 物体
应用
通过制定规则让粒子表现群体的个体之间的关系
- 鸟群
- - 每个个体的鸟不愿意落单
- 同时不愿意离群体太近
- 方向相对统一
- 细胞结构
- ...
单个粒子的模拟
获取单个粒子的速度场,获取场内任意位置的速度参数
计算速度场内粒子的位置需要计算一阶常微分方程
欧拉方法
对时间做离散化的操作,利用欧拉方法(前向欧拉)获取每一个时刻的速度和位移
问题:误差大、稳定性不足,如果步长不足则会不准确,且对于一些曲线的模拟十分不准确,一定会正反馈,其中不稳定的问题很致命
解决:
- 中点法
- 自适应改变步长的方法
- 隐式法
- 不基于物理的方法:基于位置法、韦尔莱积分
中点法
- 在初始点使用欧拉方法计算出下一位置的a点
- 在初始点与a点总取中点b
- 再使用b点的速度来求出初始点下一位置c点
中点法完整公式:
自适应步长
计算两遍,一次是Δt,一次是Δt/2来进行欧拉方法
若两遍的结果相差较大,则应将步长减半重新计算
隐式欧拉方法
联立方程组解出结果(假设下一个时刻的加速度已知)
龙格库塔法
是一种四阶的方法,稳定性相对来说比较好。
中点集合k、k2、k3、k4通过下面的方法进行计算得到下一时刻的位置。
不基于物理的方法
- 比如基于位置法、韦尔莱积分等方法
- 该方法不保证能量守恒
- 只通过调整某些位置,使其能够满足模型中的一些特定性质。
- 是一种不稳定的方法,但是简单且实现时间快。
如何判断方法是否稳定?
- 通过研究局部截断误差O(h2)和总体累积误差O(h)来确定稳定性
- 当在局部上讲Δt变为1/2Δt时,在全局上Δt会变为1/4Δt
- 因此阶数越高越好,减少阶数,误差可以成倍的减小
运动学
分为正向和反向,模拟的是真实的骨骼运动
正向运动学
关节类型:
- 可以绕着关节2维的旋转
- 可以绕着关节3维的旋转
- 可以拉长一部分的关节
我们可以通过参数很容易计算出p点的坐标,通过改变参数来实现骨骼的动画就被称为正向运动学
因为这样的操作十分不直观,艺术家们不愿意通过这样的方式来调节模型的骨骼,因此逆运动学应运而生
逆向运动学
通过直观的控制尖端p点来直接控制参数,这样操作的计算量相对正运动学相比明显增大
问题:
- 解并不唯一:有多个可能性造成p点在所在坐标
- 无解:p点不可能在指定的坐标
解决:我们通过随机化算法(优化方法、梯度下降)等方法来解决问题,并不使用纯数学的方法来暴力解决
例子:Style-Based IK
骨骼绑定
在模型上增加控制点,让人们能够方便的调整模型的动作
对于同一个模型的两个动作,通过控制点间的位置插值计算来完成,这被称为“Blend Shapes”
动作捕捉
在真人身上放置控制点
优点:
- 可以快速捕捉大量数据
- 较为真实
缺点:
- 复杂、昂贵
- 动作常常会不满足要去,需要后期调整
- 捕捉条件限制
捕捉方法:
- 光学
- 磁力
- 机械
动画的制作流程:
刚体模拟
刚体不会发生形变,通过求得下面的参数通过上面粒子模拟的方法计算出任意时刻的位置
流体模拟
定义
- 水体是由多个刚体小球形成的,不可压缩
- 模拟与渲染是分开的过程
实现思路
- 通过获取任意时刻小球的分布位置来获取小球单位面积的密度
- 如果密度如果发生改变(不同于平静的水面),就通过通过改变小球的位置来修正位置,让水总有一个趋于平静的趋势
- 通过加入能量损失让水最终停下来
质点法和网格法
质点法和网格法流体模拟中的两个不同的思路
质点法(拉格朗日法):逐个模拟每个粒子
网格法(欧拉方法):将一个空间定义为一个网格,通过观察以网格为单位的时间如何变换来模拟
混合方法MPM
- 质点:将不同的粒子的不同属性储存在粒子上
- 网格:模拟变换的过程在网格中完成
- 将每一个时刻网格的信息写回网格内的粒子上
参考资料
GAMES101-现代计算机图形学入门-闫令琪
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~