GAMES101-闫令琪 https://www.bilibili.com/video/BV1X7411F744/

https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html

07 Shading 1 (Illumination, Shading and 
Graphics Pipeline)

Z-Buffer

从远到近绘制,覆盖帧缓冲区,但实际上会有互相遮挡的情况不可深度排序的情况,所以提出了深度缓存 Z-Buffer

  • 存储每个样本的当前最小z值(像素)
  • 需要额外的缓冲区来存储深度值
    • 帧缓冲区存储颜色值
    • 深度缓冲区(z-buffer)只存储存储深度信息

为简单起见,假设相机位于原点方向-Z(z越小越近,z越大越远)

 for (each triangle T)
  for (each sample (x,y,z) in T)
    if (z < zbuffer[x,y]) // closest sample so far
      framebuffer[x,y] = rgb; // update color
      zbuffer[x,y] = z; // update depth
    else
    ; // do nothing, this sample is occluded
  • 每一个三角形
  • 每一个三角形对应的像素
  • 如果小于之前记录的值,则把这个像素颜色画进去,并更新深度缓存的值

着色

着色:对不同物体应用不同材质的过程

  • 高光
  • 漫反射
  • 环境光

计算反射到相机的光线在特定的阴影点 Shading Point(在一个局部范围内是一个平面),这些向量都是单位向量;

  • 观测方向, v
  • 表面正常,n(可以在这个平面定义法线)
  • 光照方向, l(对于许多灯中的每一个)
  • 表面参数(颜色、光泽度等)

着色是局部的,不会生成阴影!(shading ≠ shadow)

漫反射

当光线照射到物体上某一点,光向各个方向均匀散射,所有观察方向的表面颜色都是相同的

  • 考虑单位面积接受了多少能量,夹角不同接受到光的单位面积能量要不同
  • 接收到的能量与光照方向和法线方向他们夹角的余弦是成正比的cosθ=l·n
  • 假设光是一个点光源,在任意一个时刻,点光源向外辐射的能量集中在一个球壳上
  • 根据能量守恒定律,距离中心近的球壳和距离中心远的球壳,能量是相同的,但面积变大了所以能量变小
  • 距离点光源 1 时定义强度为 I ,距离 r I/r²

这样我们知道了两个事情:出来有多少光从传播到了Shading Point;有多少光被Shading Point吸收;就可以得出这样一个公式算出漫反射Diffuse(漫反射与观察方向无关)


08 Shading 2 (Shading, Pipeline and Texture Mapping) 着色频率、图形管线、纹理映射

高光

观察方向和镜面反射方向接近的时候会看到高光,就意味着法线方向和半程向量很接近,v和R接近意味着n和h接近

  • 省略了受到光照的倾斜角度
  • n和h点乘要比计算v和R要简单
  • 增加一个指数p,来调整不同角度高光的大小(100~200)

环境光照

假设任何一个点接受的来自环境的光都是相同的,强度 Ia,环境光系数ka

  • 环境光与 光线方向l、观察角度v、法线n 无关;环境光是一个常数

环境光 + 漫反射 + 高光 = Blinn-Phong Reflection Model

着色频率

把着色应用在哪些点上;面、定点、像素

逐片元:Flat shading(平直着色)

  • 三角形面是平的——一个法向量
  • 不适合光滑表面

逐顶点:Gouraud shading

  • 从三角形的顶点插值颜色
  • 每个顶点都有一个法向量(如何?)

逐像素:Phong shading

  • 对每个三角形的法向量进行插值
  • 计算每个像素的全着色模型
  • 不是Blinn-Phong反射模型

着色频率取决于面、顶点、像素的频率,效果取决于具体物体

  • 逐定点法线:任何一个顶点会和很多个不同的三角形所关联,顶点的法线是相邻的面的法线的平均(简单平均、加权平均)
  • 逐像素法线:使用重心坐标

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

从3D场景到2D的图,经历的一系列流程

  1. 输入是空间中的一些点
  2. 将空间中的点投影到屏幕上
  3. 这些点会形成三角形
  4. 光栅化离散成像素
  5. 着色
  6. 输出显示

shader:shader是通用的,只需要写一个顶点/像素如何运作

 uniform sampler2D myTexture; // program parameter
 uniform vec3 lightDir; // program parameter
 varying vec2 uv; // per fragment value (interp. by rasterizer)
 varying vec3 norm; // per fragment value (interp. by rasterizer)
 void diffuseShader()
 {
  vec3 kd;
  kd = texture2d(myTexture, uv); // material color from texture
  kd *= clamp(dot(–lightDir, norm), 0.0, 1.0); // Lambertian shading model
  gl_FragColor = vec4(kd, 1.0); // output fragment color
 }

纹理映射

希望在物体不同位置定义一个不同的属性

  • 定义在物体表面上,每个3D表面点在2D图像(纹理)中有位置映射
  • 纹理上有一个坐标系UV(都在0~1之内)
  • 纹理重复、无缝衔接

09 Shading 3 (Texture Mapping Cont.) 插值、高级纹理映射

差值

为了指定顶点处的值,在三角形内做平滑过渡,所以使用差值;插入纹理坐标、颜色、法向量等

重心坐标:在三角形内的任意坐标xy都可以表示成三个顶点ABC的线性组合(α,β,γ),同时满足两个条件

  • α+β+γ=1(限制在三角形的平面内)
  • α、β、γ都是非负的

重心坐标可以靠面积比求出来,从这一点分别连接三角形三个顶点,与当前顶点对应的三角形(不相邻)的面积与整个三角形面积的比值就是α、β、γ的值

重心坐标的计算公式

可以用重心坐标做任意一点在三角形内的差值,做差值的属性也应该用重心坐标去把它线性地组合出来

在投影下不能保持重心坐标不变

简单纹理映射:漫反射颜色:

屏幕→三维→纹理→漫反射系数;对于每个光栅化的屏幕样本 (x, y):

  1. 在 (x, y)处计算纹理坐标 (u, v)。
  2. 获取纹理采样颜色 texcolor = texture.sample(u, v)(使用重心坐标)
  3. 将样本的颜色设置为texcolor。(通常漫射反照率Kd)

临近差值:

对于一个需要插值的点,选择距离最近的已知像素值作为其值。优点是计算速度快,但可能导致图像出现锯齿状的边缘。

双线性插值:

过对四个最近的像素进行加权平均来计算未知点的值。这种方法比临近差值更平滑,但仍可能导致一些模糊。

  • 映射到一个点后,找到临近的四个点,
  • 得到距离左下角的水平s和垂直t距离
  • 首先在水平方向进行线性插值,然后在垂直方向进行插值。

双三次差值:

考虑16个邻近像素(4x4的网格)进行插值。使用三次多项式进行加权平均,结果比双线性插值更平滑。能够更好地保留图像细节,但计算量较大。

Mipmap

近处像素覆盖区域较小,远处覆盖区域太大,一个点就不能代表一个区域,会导致走样

超采样,增加采样频率可以得到一个比较好的结果,但是计算量会变大

如果不采用,只需要得到一个范围内的平均值

  • 点查询:一个点的值是多少
  • 范围查询:不做采样,直接得到范围内的平均值

Mipmap是一种用于提高纹理渲染效率和质量的技术。它预先生成了一系列分辨率逐渐降低的纹理图像。

从原始高分辨率纹理开始生成,逐级缩小,直到达到最小尺寸。每一级都是前一级的缩小版本。在渲染时,根据物体与摄像机的距离选择合适的纹理级别。这减少了计算负担,因为远处物体使用较低分辨率的纹理。通过使用更合适的纹理级别,避免了在缩放时出现的锯齿和闪烁。

求投影的像素与其相邻像素在UV上的距离,然后通过近似得到一个正方形区域(微分)

查询这个区域在第几层(D=log₂L)会变成一个像素的大小,然后取那一个像素,就可得平均值

三线性插值:

因为可能查询的并不是在同一层,所以可能不连续,所以可以使用双线性插值

分别查询 Mipmap Level D 和 Mipmap Level D+1 并分别进行差值,然后将两个结果再进行差值,这叫做三线性插值

因为只能查询正方形区域的平均值,所以会导致画面出问题

各向异性过滤:

可以部分解决这个问题,各向异性过滤可以允许我们对矩形区域做快速的范围查询,但是斜着的不行

各向异性:在不同方向上表现各不相同

EWA过滤:

将不规则形状拆成许多不同圆形去覆盖,多次查询这个圆形,耗时

环境光照

可以将纹理理解成一块数据,可以快速地做点查询、范围查询

将任何一个方向的光照都记录下来,环境贴图,纹理可以用来表示环境光;

假设光线距离无限远,不记录深度信息

可以将环境光记录在球形贴图上,并且展开(会有扭曲现象)

把环境光记录在立方体上,

凹凸贴图

应用凹凸贴图,可以再不把几何形体变复杂的情况下,定义三角形上任意一个点的相对高度

相对高度变化 → 法线发生变化 → 着色结果发生变化

法线贴图:

  • 通过法线贴图,可以定义一个复杂的纹理,但却不更改几何信息
  • 把任何一个像素的对法线进行一个扰动(通过定义不同的高度,根据临近位置的高度差重新计算法线)

一维:假设原版是平面,定义了一张凹凸贴图

  • 求得某一个点的切线,法线法向(0,1)(导数,用相邻两个点的高度差除以间隔,引入常数控制影响幅度)
  • 法线垂直于切线,把切线逆时针旋转90度((-dp,1),需要归一化长度得是1)

在二维情况下:定义局部法线位置为(0,0,1),计算完成后再通过坐标变换回到全局坐标,平面上两个偏切向量叉乘得到法向量

  • 求出UV两个方向上的切线,
  • 通过算出来的导数/梯度/微分 算出法线

位移贴图:

位移贴图会真的移动顶点位置,而法线贴图并没有;

要求三角形足够细致(Direct X提供了一个动态曲面细分的方法Dynamic tessellation)

三维纹理:

可以通过三维空间中的噪声函数来定义

三维扫描纹理

环境光遮蔽:

提供预计算阴影,计算好后写进一张纹理图


10 Geometry 1 (Introduction) 几何介绍

  • 隐式几何:隐式几何则是通过一个函数来定义几何体的内部和外部区域,而不是直接给出边界的方程。隐式几何通常使用一个标量场来表示,形状是通过等值面来定义的。 判断有哪些点很难,但判断在不在这个物体内是很容易的一件事
  • 显式几何:显式几何是指通过明确的数学方程或函数来描述几何体的形状。几何体的边界是通过明确的方程来定义的,通常容易进行计算和渲染。 几何体的边界是通过明确的方程来定义的,显示判断是否在物体内很难

隐式几何

代数曲面:

使用代数式来描述几何

构造性立体几何:

通过基本几何的基本运算(布尔),得到新的几何

距离函数:

不直接描述表面,描述任意一个点到这个表面的任意距离

  • 假如想得到A-B运动中间的结果,直接混合会导致中间变成灰色,
  • 分别求得A、B的距离函数,因为两者都是最短距离,然后将其混合就可以得到中间值,通过混合两个的SDF就等于混合他们两个的边界

水平集:

跟距离函数表示形式不一样,但结果都是函数等于0得到的曲线

水平集不仅可以是二维也可以是三维

分形:

自己的一个部分和自己整体比较像

与计算机中的递归一个道理

显示几何

点云:

只要点足够密,就可以变成任何物体;最简单的表示方式:点列表(x,y,z);可以轻松表示任何类型的几何图形适用于大型数据集(>>1点/像素);经常转换为多边形网格;在样本不足的地区难以绘制

多边形网格:

存储顶点和多边形(通常是三角形或四边形);更易于处理/模拟;自适应采样;更复杂的数据结构;也许是图形中最常见的表示

OBJ:通过文本文件存储,(以正方体为例)定义8个点的位置v、6个法线vn、12个UV纹理坐标vt、连接关系(f 顶点索引 / UV索引 / 法线索引)(有冗余&共用)


11 Geometry 2 (Curves and Surfaces) 曲线与曲面

贝塞尔曲线

贝塞尔曲线一定经过起止点

画出贝塞尔曲线:

  • 假设这条曲线的起点是在时间0,终点在时间1,任意一个时间t在0~1之间的时间t,求出所对应的点在空间中的位置
  • 一直 t 在 0~1 之间的位置,找到 b0 到 b1之间找到 t 的位置,b1 到 b2 之间t的位置早到 t 的点,将新得到的两个点连起来,再在这条线上找 t 的位置,最后这个点就是时间 t 在贝塞尔曲线上的位置

可以通过线性插值将公式列出来,起点*(1-t)+终点*t,再将求出的两个点继续运算,整合得到一个展开式(s+t的n次方的展开式)

给一个n+1个控制点,可以得到一个n阶的贝塞尔曲线,曲线在任意时间 t 都是给定的这些控制点的线性组合,组合的系数是一个与时间有关的多项式(伯恩斯坦多项式)

任意一个时间 t ,这个点的位置就是由伯恩斯坦多项式作为系数,对给的的控制点的加权

这样的话也可以输入三维的点,求空间中的贝塞尔曲线

这个多项式相当于对 1 自己的 n阶展开,把几个同一阶上多项式加起来等于1

  • 贝塞尔曲线规定了必须过起点和终点,所以t=0时一定在起点,t=1一定在终点
  • 四个控制点的贝塞尔曲线,起始位置的切线一定是三倍的(b1-b0)
  • 如果相对贝塞尔曲线做仿射变换,可以直接对控制点做仿射变换然后再画出来(限制仿射变换,对投影不是)
  • 画出来的贝塞尔曲线一定在几个控制点形成的凸包内

逐段贝塞尔曲线:控制点太多不容易控制形状,可以将贝塞尔曲线分段(通常是4个点)

连续线:

  • C0连续:第一段终止点等于第二段起点
  • C1连续:切线连续,两个控制点距离相同且共线
  • ……

曲面

在两个方向上分别利用贝塞尔曲线就得到了贝塞尔曲面

输入:4x4个控制点,输出是由[0,1]2中的(u,v)参数化的二维曲面;

找到贝塞尔曲面找到任意一点,需要两个不同的时间 t ,先求出水平四条曲线上的四个点U,再在这四个点形成的曲线上找V

因为贝赛尔曲线是UV映射过去的,所以是显示的表示


12 Geometry 3

  • 网格细分(上采样)
  • 网格简化(降采样)
  • 网格正则化(相同的三角形)

网格细分

  • 分出更多的三角形
  • 让三角形位置发生变化,使其更圆滑

Loop细分:

增加新的三角形,然后新产生的顶点和老的顶点分别处理

  • 新的顶点:取周围几个点的加权平均,3/8*(A+B)+1/8*(C+D)
  • 旧的顶点:一部分保留自己位置,另一部分取周围顶点的平均值,二者加权

Catmull-Clark细分:

  • 非四边形面
  • 奇异点(极点):度不为4的点(度:与这个点相连的线)

取边和面的中点,并且把它们相连。

在 Catmull-Clark细分前有多少个非四边形面,在一次细分后就增加多少个奇异点,非四边形面都消失了,所以再细分就不会再增加奇异点了

  • 新生成的顶点,面的中心点和边的中心点分别考虑;
  • 老的点使用面中心、边中心和自己做加权平均

Loop细分只能用于三角形面做细分,Catmull-Clark细分可以用作各种不同的面做细分

曲面简化

边坍缩:将点合并

  • 对顶点进行局部平均不是一个好主意
  • 二次误差:新顶点 与先前相关三角形平面的平方和(L2距离)最小

假设坍缩每一条边,将坍缩后的顶点放在什么位置上,得出一个多大二次误差度量,,给每一条边打上一个分数,从小的开始逐个做坍缩,

坍缩一条边会影响其他边,使用堆 / 优先队列,既能取最小值又能动态更新这些元素

相当于在通过对局部做最优解的方式,找到一个全局的最优解(贪心算法)

阴影贴图 Shadow Mapping

一种图像空间算法:阴影时不知道场景的几何形状。会产生走样现象。

关键思想:不在阴影中的点必须同时被光线和相机看到

点光源生成硬阴影:

  1. 从光源看向场景,记录你所看到任何点的深度
  2. 从摄像机看向场景,投影看到的点,投影回光源视角的哪一个位置上,
    • 两者深度一致,不在阴影中
    • 两者深度不一致,在阴影中

软阴影:

  • 一个地方完全看不到光源,叫做本影Umbra
  • 一个地方可以部分看到光源,叫做半影Penumbra
  • 完全看到光源就没有阴影

软阴影就是本影、半影、到没有阴影的影子过度。点光源不可能拥有软阴影,有软阴影光源一定有大小


THE END