OpenGL 面经总结

OpenGL Pipline - 渲染管线

  1. 顶点数据的输入: 送入到渲染管线的数据包括顶点坐标、纹理坐标、顶点法线和顶点颜色等顶点属性。需要在绘制指令中传递相对应的图元信息。常见的图元包括:点(GL_POINTS)、线(GL_LINES)、线条(GL_LINE_STRIP)、三角面(GL_TRIANGLES)。
  2. 基于顶点的操作 (Vertex Shader): 顶点着色器处理顶点,每个顶点由空间矩阵变换,有效地将其3D坐标系统改变为新的2D坐标系统。
  3. 图元装配 (Geometry Shader - 几何着色器): 以指定的顺序连接顶点构建基本体状态。几何着色器输出一个或多个其他的图元(比如,三角面)。
  4. 图元处理 (Graphic Primitives): 屏幕外面的任何图元都被剪辑并在下一阶段忽略,以减少进入光栅化的图元的数量,加速渲染过程。
  5. 光栅化: 测试像素是否在图元的边内。如果他们不是,他们被丢弃。如果他们在图元内,他们被带到下一个阶段。通过测试的像素集被称为片段。
  6. 片面处理 (Fragment Shader): 进行光照计算以及阴影处理,决定屏幕上片段内的像素的最终颜色。
  7. 片元操作: 片段被提交到几个测试,如:透明度(Alpha)测试,模板(Stencil)测试,深度(Depth)测试等;没有经过测试的片段会被丢弃,不需要进行混合阶段;如果一个片元成功的通过了所有激活的测试,那么他就可以直接被绘制到帧缓存中了,它所对应的像素的颜色值会被更新。

OpenGL中 Shader  - 各种着色器在管线中的位置和作用

1. 顶点着色器 (Vertex Shader)

  • 输入:顶点数据。通过glVertex*()等函数指定的顶点数据和跟它相关的法向量(glNormal*())、颜色(glColor*())等属性信息都会经过顶点处理器的处理,传给下一个阶段。
  • 作用: 把输入的顶点坐标乘以一系列几何变换矩阵,Vertex Shader只知道处理顶点
    •   矩阵变换位置:MVP矩阵(模型--视图--投影矩阵)顶点着色器的位置输入保存的是物体坐标,而输出的坐标保存为裁剪坐标。
    •   计算光照公式生成逐顶点颜色
    •   生成或者变换纹理坐标
 1 //Simple example of vertex shader 
 2 layout (location = 0) in vec3 aPos; // the position variable has attribute position 0 
 3 out vec4 vertexColor; // specify a color output to the fragment shader
 4 void main()
 5 {
 6     gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor
 7     vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color
 8 }

2. 片段着色器 - Fragment Shader

  • 输入:光栅化后产生的图元。通过重复执行(每片元一次)将3D物体中输入值的颜色等属性计算出来送入后继阶段。
  • 作用:
    • 计算颜色
    • 获取纹理值
    • 往像素点中填充颜色值(颜色值或者纹理值)
1 // Simple example of Fragment Vartex
2 out vec4 FragColor;
3 in vec4 vertexColor; // the input variable from the vertex shader (same name and same type)  
4 void main()
5 {
6     gl_FragColor = vertexColor;
7 } 

3. 镶嵌控制着色器 - TESS  Control  Shader (TCS)  - OpenGL 4.*支持

  • 输入:Patch (一个patch是多个顶点的集合;它每个顶点的属性:坐标,颜色,纹理坐标等等)
  • 作用:把一个图元分割成很多图元,在TCS里储存:图元里每个边要被分割成多少段,在图元内部还要怎么继续分割;

N.B. glBegin函数的参数必须是GL_PATCHES

4. 镶嵌评估着色器 - TESS  Evaluation  Shader (TES) - OpenGL 4.*支持

  • 输入:一系列被分割后产生的新顶点。
  • 作用:把每个顶点的局部坐标(由Tessellation Primitive Generator从三角形内部的坐标转换而来的)变换成世界坐标,以及把顶点相应属性(颜色,纹理坐标等)转换成真正且有效的属性值。

N.B. 图元装配器会将TES输出的坐标装配从一个个三角形 

5. 几何着色器 - Geometry Shader

  • 输入:一个图元 (Primitives)
  • 作用:根据新图元生成新一个或者多个图元

什么是model,view,project矩阵

  • 模型矩阵(Model Matrix): 将模型从自己局部的模型坐标系变换到OpenGL的世界坐标系。
  • 视图矩阵(View Matrix): 将世界坐标变换到从虚拟摄像机的视角所观察到的空间。
  • 投影矩阵(Projection Matrix):将3D物体的顶点坐标从观察空间变换到齐次裁剪空间。再将顶点的深度值z保存在顶点经过变换得到的齐次坐标的w分量中。最后,把顶点在齐次空间中的坐标通过将x,y,z分量除以w分量的方式,将齐次坐标转为NDC。

法线在顶点变化的时候需要注意什么

view space中的发现一般不能由Model matrix和view matrix计算得到,因为当模型发生缩放的时候,所谓的发现就已经不与模型表面垂直了,就不是法线了,如下图所示

 

 

 那么应该长什么样呢?

应该使用MV的逆矩阵的转置矩阵,即 [公式] ,具体的推导流程可以看这个,主要是遵循法线向量永远与切线向量垂直,即 [公式] 。 注意,计算逆矩阵的过程性能代价很大,不适合在vertex shader或pixel shader(或fragment shader)中对每一个顶点甚至像素都计算一遍法线变换矩阵。一般是在CPU上计算一次,然后把它放到shader中的一个uniform变量中。

齐次坐标

为什么要引入一个齐次坐标?为了让平移在仿射转换中不是特殊的例子。

从数学角度上来看,translation不能变换成一个matrix,因为不是线性关系

 

 但是数学家并不想让转换中有特殊case,所以他们添加了一个维度,来用来做unified matrix

齐次坐标就是用N+1维来代表N维坐标,经常用于3D场景映射到2D场景的过程中

向量 V= v1*a + v2*b + v3*c

点P - 点O = p1*a + p2*b + p3*c --> 点P = 点O + p1*a + p2*b + p3*c

转换成矩形表达式:

V= (v1, v2, v3, 0) x (a, b, c, o)

P = (p1, p2, p3, 1) x (a, b, c, o)

这里(a,b,c,o)是坐标basic矩阵,(v1, v2, v3, 0)和(p1, p2, p3, 1)分别是向量v和点p在基下的坐标

这样,向量和点在同一个基下就有了不同的表达:3D向量的第4个代数分量是0,而3D点的第4个代数分量是1。4个代数分量表示3D几何概念的方式,使得平移变换可以使用矩阵进行,仿射(线性)变换(指在几何中,对一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间)的进行更加方便

不管进行多少次变换,都可以表示成矩阵连乘的形式,将极大的方便计算和降低运算量。其次坐标 w = 1 为坐标,w = 0 为向量。向量是具有平移不变性,所以在平移matrix中,w = 0可以帮助它保持这个特性。

平移                     缩放           旋转 

其次坐标怎么实现透视:透视是通过投影矩阵变换,改变每一个向量中 W 分量的值来实现透视的。每个向量的 w 分量表示了距离相机的距离。每个向量的 Z 分量表示了距离相机的距离,因此,Z 分量越大,矢量应该越小。将每个点的在观察空间下的z坐标拿来,除以该点在齐次剪裁空间下的x和y的值(x = x/z, y = y/z),显示出来便是透视投影后的图像。 

齐次剪裁空间:裁剪坐标是-1到1,不在里面就不会显示

裁剪的位置:渲染管线中进行裁剪的位置是透视投影之后,齐次除法之前

为什么变换矩阵是4x4

如果我要让顶点坐标旋转一定角度后,再平移一段距离,那么这里面的操作就涉及 3x3 矩阵的计算和 4x4 矩阵的计算,如果不统一起来,这种连续变换的计算操作将很复杂。所以如果要用矩阵乘法来统一所有的平移、旋转等等变换计算,统一用 4x4 矩阵来计算既能满足场景又方便计算。

剔除算法

  • 视锥体剔除(应用程序阶段):依据主要是根据摄像机的视野(field of view)以及近裁减面和远裁剪面的距离,将可视范围外的物体排除出渲染
  • 背面剔除(光栅化阶段):先判定多边形的朝向,并和当前的观察方向进行比较。opengl中设置背面剔除相关函数:glFrontFace(GL_CW)设置顺时针或者逆时针为正面,glCullFace(GL_BACK)设置剔除正面或者背面
  • 提前深度剔除(early -z)

Early-z 

  • 定义:在光栅化之后,fragment之前进行一次深度测试,如果深度测试失败,就不必进行fragment阶段的计算了,因此在性能上会有很大的提升。但是最终的ZTest仍然需要进行,以保证最终的遮挡关系结果正确。先绘制较近的物体,再绘制较远的物体(仅限不透明物体)
  • 实现:对于所有不透明的物体(透明的没有用,本身不会写入深度),首先用一个超级简单的shader进行渲染,这个shader不写入颜色缓冲区,只写深度缓冲区,第二个pass关闭深度写入,开启深度测试,用正常的shader进行渲染。
  • 若开启了alpha test,则不支持early-z。因为alpha test是在光栅化之后才测试对应的像素是否可见而且最终需要靠Z-Check进行判断这个像素点最终的颜色。若用了early-z则会提前遮挡了。
  • Unity:为shader指定渲染队列是一种从前向后渲染功能的工具。使用pass预传Z值渲染。

OpenGL中要用到哪几种Buffer?

缓冲区保存在GPU内存中,它们提供高速和高效的访问。

  • 帧缓冲 (Frame Buffer) :帧缓冲区是下面几种缓冲的合集,他可以保存其他确实有内存存储并且可以进行渲染的对象,例如纹理或渲染缓冲区。
  • 颜色缓冲 (Color Buffer) :存储所有片段的颜色:即视觉输出的效果。
  • 模板缓冲 (Stencil Buffer) :为屏幕上的每个像素点保存一个无符号整数值。用于模板测试中。决定是否丢弃片段。
  • 顶点缓冲 (Vertice Buffer):管理顶点数组数据。
  • 深度缓冲 (Depth Buffer):处理图像深度坐标,确定渲染场景中哪部分可见、哪部分不可见的。

阴影 (Shadow) 应该怎么实现

纹理映射 - Shadow Mapping

  • 原理:以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中。
  • 步骤:把光源位置设置一个相机,然后相机显示的视角存到深度缓存中。渲染正常模型并且计算深度缓存和模型的深度值,如果此时的深度值大于光源视觉下的深度值,那就显示阴影反之则显示正常的亮面。计算的时候需要减去一个bias值,否则会出现阴影计算不准的情况。
  • 深度贴图 (Depth Map):从光的透视图里渲染的深度纹理,用它计算阴影。由于光是平行的定向光,我们将为光源使用正交投影矩阵。阴影的顶点着色器将一个单独模型的一个顶点,使用lightProject * lightView 变换到光空间中。片段着色器为空,因为无颜色缓冲。

Shadow map的问题

  • 阴影失真(Shadow Acne):在距离光源比较远的情况下,多个片元可能从深度贴图的同一个值中去采样。
  • 阴影偏移(shadow bias):对表面的深度(或深度贴图)应用一个偏移量,这样片元就不会被错误地认为在表面之下了。
// 方法一:简单的减去偏移量
float bias = 0.005;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
// 方法二:根据表面朝向光线的角度更改偏移量
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
  • 悬浮(Peter Panning):使用阴影偏移的值有可能足够大,以至于可以看出阴影相对实际物体位置的偏移
  • 正面剔除(front face culling):当渲染深度贴图时候必须开启GL_CULL_FACE(GL_FRONT),正面剔除完全移除了地板。

浮点数精度会不会导致这个距离判断出现问题,导致没有阴影的表面也上了阴影?

GPU的阴影图只能保存0-1之间的数值,而不能直接保存计算得到的距离,为了更好的控制精度。如果是纯浮点数的精度,整数部分会造成极大的浪费。最终保存的depth是除以镜头的最远距离。当渲染物体离灯光都比较近的时候可以用depth的二次方,为了拉大之间的精度,当渲染物体离灯光都比较远时,可以用depth的log(n)。

怎么使阴影边缘虚化

采样了中心点,再采样了四个顶点,求平均值。假设只有B、C两个点是有阴影的,AED是没有阴影的,算起来,这个点就会有淡淡的阴影。

PSSM平行分割阴影 - 平行光阴影

一种根据距离远近采用多个深度纹理渲染阴影的方法,适合用于室外大场景中的平行光比如太阳形成的阴影。在shader里面,根据不同的距离,采样不同的阴影图。(将视锥体平行划分为3个区域,代号分别为1、2、3。这三个区域在渲染阴影的时候分别采用不同的阴影图sm1、 sm2、sm3)。弥补了大场景阴影细节可能出现的不足。

 

点阴影 - Point Shadows

流程:可以生成来自四面八方的光源的shadow map,将整个场景渲染到cube map贴图的每个面上,把它们当作点光源四周的深度值来采样。生成后的深度立方体贴图被传递到光照像素着色器,它会用一个方向向量来采样立方体贴图,从而得到当前的fragment的深度(从光的透视图)

算法和阴影映射差不多:从光的透视图生成一个深度贴图,基于当前fragment位置来对深度贴图采样,然后用储存的深度值和每个fragment进行对比,看看它是否在阴影中。

常见的光照模型:Lambert模型、Blinn-Phong模型与Phong模型

Lambert模型 (漫反射)

粗糙的物体表面向各个方向等强度地反射光,这种等同度地散射现象称为光的漫反射。

 

 

  • Iad: 环境光
  • K:材质对环境光的反射系数
  • Ia: 环境光强度
  • I_ld: 方向光 = 光线方向与法向量的点积
  • Il: 点光源强度,
  • N: 顶点单位法向量
  • L: 从顶点指向光源的单位向量
  • Idiff: 漫射光

Phong和Blinn-Phong是计算镜面反射光的两种光照模型,Blinn-Phong比Phong模型效率更高为默认镜面光,但写实效果没有Phong好。

Phong模型 (镜面反射)

  • Ks:物体对于反射光线的衰减系数
  • V:从顶点到视点的观察方向
  • R:光入射方向I的反射光
  • Shininess:高光系数。shininess 越高,反射光的能力越强,散射得越少,高光点越小

Blinn-Phong模型 (修正镜面光):有环境光照、漫反射光照和镜面光照三部分组成

  • lightColor: 一个常量来代表环境光的颜色
  • dot(N, L): 光照方向向量与法线方向向量点乘来计算光源对当前片段漫反射的影响
  • H = normal(L + V): 先用光照方向向量和观察方向向量计算出半程向量
  • dot(N, H)^shininess: 用半程向量法线向量求点积,再用他们的点积结果进行反光度次方来计算出镜面光照
  • Ks:物体对于反射光线的衰减系数

GLSL的如何传递数据?

  • uniform 变量 :uniform变量是外部application程序传递给 vertex shader 和 fragment shader的变量。它是application通过函数glUniform**()函数赋值的。在 vertex shader 和fragment shader内部,uniform变量不能被shader程序修改。一般用来表示:变换矩阵,材质,光照参数和颜色等信息。
  • attribute 变量:能在vertex shader中使用的变量且是只读的。在application中,一般用函数glBindAttribLocation()来绑定每个attribute变量的位置,然后用函数glVertexAttribPointer()为每个attribute变量赋值。来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标等。
  • varying 变量:是vertex和fragment shader之间做数据传递用的,并在光栅化(Rasterization)的时候,这些变量也会跟着一起被光栅插值。一般vertex shader修改varying变量的值,然后fragment shader使用该varying变量的值。

顶点法线和面法线的作用。

  • 面法线:垂直于平面,位于中央,经常用于flat着色。
  • 点的法线:在使用Phone或Gouraud模型时计算光照使用。如果一个面上的所有法线都一样,他们的光照也就一样,就会产生 flatness 效果。而如果把每个顶点的法向设置不同,则更平滑。

CPU和GPU之间的调度

GLSL运行在GPU,其通过接口实现和CPU之间的数据转换。OpenGL主程序有CPU调用运行,图像处理部分通过GLSL交给GPU执行。

CPU和GPU之间的数据传递分三步骤:

  1. 首先利用内置的OpenGL函数生成一个ID号码
  2. 根据需要对该ID号码进行内存类型的绑定,绑定完成后GPU中用于接收系统内存中数据的"标识符"就准备好了,
  3. 对这部分内存进行初始化,初始化的内容来自于系统内存中,这一部分功能利用glBufferData函数完成.数据由提交到GPU专用的内存中之后,需要根据应用场景对数据进行适应的分配,比如有的数据当做顶点,有的作为颜色,有的用于控制光照等等

此外,由于GPU具有高并行结构,所有GPU在处理图形和复杂算法方面计算效率较高.CPU大部分面积为控制器和寄存器,而GPU拥有更多的ALU,逻辑运算单元用于数据处理,而非数据的高速缓存和流控制.

深度测试

  • 深度:是在坐标系中像素Z坐标距离观察者的距离
  • 深度测试:解决遮盖问题。通过检查Z值,同一个区域靠近观察者的进行绘制,其他被遮盖的区域不予绘制,与绘制的顺序无关. 用glEnable(GL_DEPTH_TEST),Z值小的情况下会被覆盖
  • 深度冲突:为两个物体靠的很近时确定谁在前,谁在后时出现了歧义
    • 解决
      1. 在第二次绘制相同Z值时,稍微偏移一点点解决遮盖问题;
      2. 启用Polygon Offset方式解决,让深度值之间产生间隔。
    • 预防
      1. 让物体之间不要离得太近;
      2. 将近裁剪面设置得离观察者远一些。
      3. 使用更高位数的深度缓冲区,使精确度提高。 

如果判断点在三角形内

内角和法:

连接点P和三角形的三个顶点得到三条线段PA,PB和PC,求出这三条线段与三角形各边的夹角,如果所有夹角之和为180度,那么点P在三角形内,否则不在,

 

同向法:

当选择某一条边时,只需验证点P与该边所对的点在同一侧即可。通过叉积来实现。连接PA,将PA和AB做叉积,再将CA和AB做叉积,如果两个叉积的结果方向一致,那么两个点在同一测。判断两个向量的是否同向可以用点积实现,如果点积大于0,则两向量夹角是锐角,否则是钝角。

 

 

 

  • 点乘的几何意义:可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影,值是标量
  • 叉积的几何意义:3维空间中,可以通过两个向量的叉乘,生成第三个垂直于a,b的法向量,值是向量。2维中,两个向量的叉乘等于由向量a和向量b构成的平行四边形的面积。

如何判断两个三角形相交

1. 检测两个三角形边与边之间是否相交。

    • 设有2个线段 a , b 。线段 a 的2个端点为:a1 , a2 , 线段 b 的 2个端点为:b1 , b2 。
      条件1:是否 向量 a1->b1 、a1->b2 分别位于向量 a1->a2 的左右2端。
      条件2:是否 向量 b1->a1 、b1->a2 分别位于向量 b1->b2 的左右2端。
      当条件1和条件2同时满足时,线段 a , b 相交。叉乘完成。

2. 检测一个三角形是否完全在一个三角形内部:判断三角形的三个点是否在三角形内。

如何判断一个凸边形

用两边的叉积,如果有一边是不是同向的则就是凹边形

叉积的一个非常重要性质是可以通过它的符号判断两矢量相互之间的顺逆时针关系:

  • 若 P × Q > 0 , 则P在Q的顺时针方向
  • 若 P × Q < 0 , 则P在Q的逆时针方向
  • 若 P × Q = 0 , 则P与Q共线,但可能同向也可能反向

PBR是什么

Physically Based Rendering:基于物理的反射。与我们原来的Phong或者Blinn-Phong光照算法相比总体上看起来要更真实一些。

 PBR光照模型的满足条件

  1. 基于微平面(Microfacet)的表面模型。
    1. 假设一个粗糙度(Roughness)参数,用统计学的方法来概略的估算微平面的粗糙程度。基于一个平面的粗糙度来计算出某个向量的方向与微平面平均取向方向一致的概率。这个向量便是位于光线向量 和视线向量 之间的中间向量。
  2. 能量守恒:出射光线的能量永远不能超过入射光线的能量
  3. 应用基于物理的BRDF。

反射率方程:

计算了点p 在w0 方向上被反射出来的辐射率L0(p, w0)的总和。

 
  • w: 观察方向,wi : 入射方向,Li : 辐射率,Ω:以点p为球心的半球领域Ω内所有方向上的入射光wi.
  • fr : cook-torrance BRDF 函数:求出每束光线对一个给定了材质属性的平面上最终反射出来的光线所作出的贡献程度(射出光线的辐照度和摄入光线辐射率的比值)
  • flambert : Lambert 漫反射, k: 入射光线中被折射部分的能量所占的比率, c : 表面颜色, pi : 为了对漫反射光进行标准化.
  • fcook-torrance : 镜面反射, DFG: 三种不同的分布函数,正态分布,几何函数,菲涅尔函数,

与phong光照模型的区别

  • 将光源分为间接光源和直接光源这两部分。
    • 间接光源:来自于物体反射或者大气散射等的光能。
    • 直接光:来自于场景光源直接照射的部分
  • 将材质分为金属和非金属分别进行处理,PBR模型在渲染金属材质方面与phong模型相比特别突出
    • 金属度是一个0到1范围内的浮点数,表示被渲染物的表面材质是不是金属,0表示非金属,1表示金属,0和1之间的值的作用是表现诸如沾有沙子的金属表面之类的复杂材质。
  • 导入粗糙度(roughness)和环境光遮蔽(ambient occlution,简称ao)来描述材质表面的微小结构对其光学特性的影响。PBR模型使用粗糙度代替高光因子,并使用环境光遮蔽模拟微小表面产生的细小阴影;
  • 漫反射和高光反射的能量守恒。

法线贴图

法线贴图:为每个fragment提供一个法线,光照使表面拥有了自己的细节。主要适用于凹凸不太明显,细节很多,需要表现实时光照效果,不会太靠近观察的物体的情况 。

生成:采取纹理的灰度图,根据两个像素间的灰度差,形成U,V两个向量,然后两个向量的叉积就是法线的方向。

为什么是蓝色:在切线坐标系里,定义顺序是Tangent、Binormal、Normal,而Normal处于z方向,法线贴图的法线值大多数时候是接近于(0,0,1)。

为什么要有切线空间:我们需要把viewDir、lightDir在Vertex Shader中转换到Tangent Space中,然后在Fragment Shader对法线纹理采样后,直接进行光照计算。Tangent-Space Normal Map记录的是相对法线信息,这意味着,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果。

如何绘制半透明物体

方法一:使用混合来实现半透明效果,glEnable(GL_BLEND); 只有在RGBA模式下,才可以使用混合功能,颜色索引模式下是无法使用混合功能的

方法二:三维绘图:

  • 在绘制半透明物体时将深度缓冲区设置为只读,glDepthMask(GL_FALSE);
  • 虽然半透明物体被绘制上去了,深度缓冲区还保持在原来的状态。再要绘制不透明物体时,只需要再将深度缓冲区设置为可读可写的形式即可。
  • 总结:首先绘制所有不透明的物体。如果两个物体都是不透明的,则谁先谁后都没有关系。然后,将深度缓冲区设置为只读。接下来,绘制所有半透明的物体。如果两个物体都是半透明的,则谁先谁后只需要根据自己的意愿。

Mipmap 是什么

  • 定义:贴图渲染中常有的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一条列被预算和优化过的图片组成的文件。
  • 原理:把一张贴图按照2的倍数进行缩小。直到1X1。把缩小的图都存储起来。在渲染时,根据一个像素离眼睛为之的距离,来判断从一个合适的图层中取出texel颜色赋值给像素
  • 图像压缩算法
    1. 最近相邻插值:计算量很小,算法简单,运算速度较快,但重新采样后颜色值有明显的不连续性,图像质量损失较大,会产生明显的马赛克和锯齿现象。
    2. 两次线性插值:基本克服了最近相邻插值算法颜色值不连续的特点,因为它考虑了待测采样点周围四个直接邻点对该采样点的相关性影响。但缩放后图像的高频分量受到损失, 图像边缘在一定程度上变得较为模糊。
    3. 两次立方插值:不仅考虑到周围四个直接相邻像素点颜色值的影响,还考虑到它们颜色值变化率的影响,产生比两次线性插值更为平滑的边缘,计算精度很高,处理后的图像像质损失最少。但计算量最大,算法也最为复杂的
  • 优点:会优化显存带宽,用来减少渲染,因为可以根据实际情况,会选择适合的贴图来渲染,距离摄像机越远,显示的贴图像素越低,反之,像素越高。
  • 缺点:会占用内存,因为mipmap会根据摄像机远近不同而生成对应的八个贴图

怎么选择mipmap的level:由于gpu每次计算的不是一个像素,它会同时计算四个屏幕像素,可以利用这个特点,通过差分法求当前周围四个屏幕像素在在uv上投影后的纹素后在水平方向和垂直方向便宜的最大距离,取得这个最大距离的2的对数结果就是当前像素的mipmap级别。这个偏移的距离L大于1,表示一个屏幕像素覆盖了超过一个纹素,为了让这个一个像素覆盖一个纹素,所以需要减少图片的大小而且由于mipmap级别间图片的水平或者竖直方向比例大小为2倍关系,所以用这个取2的对数,就可以实现屏幕一个像素覆盖一个纹素了

LOD是什么?作用是什么?优缺点是什么?

  • 全称Level Of Detail,“多层级细节”
  • LOD会根据模型距离摄影机的距离,选择不同精度的模型,摄影机越近则选择高精度的模型,远则反之。
  • 优点:提高渲染效率,优化了性能。  缺点:增加了内存占用。

四叉树

定义:一种树状数据结构,在每一个节点上会有四个子区块。四元树常应用于二维空间数据的分析与分类。它将数据区分成为四个象限。

应用:图像处理、空间数据索引、2D中的快速碰撞检测、稀疏数据等

四元数(Quaternion)和欧拉角

矩阵变换:

  • 优点
    • 旋转轴可以是任意向量
  • 缺点
    • 旋转其实只需要知道一个向量+一个角度,一共4个值的信息,但矩阵法却使用了16个元素
    • 而且在做乘法操作时也会增加计算量,造成了空间和时间上的一些浪费

欧拉旋转:所有的向量都绕着同一个轴旋转相同的角度,达到改变刚体朝向的目的

  • 优点
    • 容易理解
    • 表示更方便,只需要3个值(分别对应x、y、z轴的旋转角度)
  • 缺点
    • 要按照一个固定的坐标轴的顺序旋转的,因此不同的顺序会造成不同的结果;
    • 会造成万向节锁(Gimbal Lock)的现象。这种现象的发生就是由于上述固定坐标轴旋转顺序造成的。理论上,欧拉旋转可以靠这种顺序让一个物体指到任何一个想要的方向,但如果在旋转中不幸让某些坐标轴重合了就会发生万向节锁,这时就会丢失一个方向上的旋转能力,也就是说在这种状态下我们无论怎么旋转(当然还是要原先的顺序)都不可能得到某些想要的旋转效果,除非我们打破原先的旋转顺序或者同时旋转3个坐标轴。
    • 由于万向节锁的存在,欧拉旋转无法实现球面平滑插值;

四元数旋转:unity中的Quaternion.EulerQuaternion.eulerAngles。其中x,y,z 代表的是向量的三维坐标,w代表的是角度。q = (x, y, z, w) = xi+yj+zk+w (i= j2 = k= -1)

  • 优点
    • 可以避免万向节锁现象;
    • 只需要一个4维的四元数就可以执行绕任意过原点的向量的旋转,方便快捷,在某些实现下比旋转矩阵效率更高;
    • 可以提供平滑插值;
  • 缺点
    • 比欧拉旋转稍微复杂了一点点,因为多了一个维度;
    • 理解更困难,不直观;
    • 单个四元数不能表示在任何方向上超过180度的旋转

2维碰撞检测

  • 轴对称包围盒(Axis-Aligned Bounding Box):判断任意两个(无旋转)矩形的任意一边是否无间距,从而判断是否碰撞。
  • 圆形碰撞(Circle Collision):通过判断任意两个圆形的圆心距离是否小于两圆半径之和,若小于则为碰撞。
  • 速度过快/体积过小导致物理引擎检测不到碰撞,如何解决。
    • 连续碰撞检测:在移动时先判断移动后的位置与现在的位置作射线检测,若有碰撞则移动失败否则移动成功。
    • 把Mesh包含在胶囊体里面,可以保证不会穿模。

ECT1压缩方式实现alpha

因此遇到半透明的图时, 需要将原贴图的alpha抽离,一分为二并别压缩,最后在渲染时再合并计算。将一个图片生成两个纹理,一个是rgb,一个rgb中的r存储着图片的alpha数据。在绘制的时候使用纹理单元,将带有alpha数据的rgb做为另个带rgb纹理的alpha。

1024*1024的图片有多大, 32 位

一个像素:4个管道 * 8位字符 = 32 bit = 4 bytes

大小=宽*高*位深(24位,16位,8位)/8 = 1024 * 1024 * 4 bytes = 4MB 

前向渲染 v.s 延后渲染

前向渲染 (Forward Rendering):把空间的点进行各种剪裁后,进行处理。先渲染一遍物体,把法线和高光存在ARGB32的渲染纹理中(法线用rgb通道,高光用a通道),存在了z buffer里;然后通过深度信息,法线和高光信息计算光照(屏幕空间),光照信息缓存在Render Texture中;最后混合。

延后渲染 (Deferred Rendering):将摄像机空间的点光栅化转化成屏幕坐标后再进行处理。先不进行光照运算,对每个像素生成一组数据(G-buffer),包括位置,法线,高光等,然后用这些数据将每个光源以2D后处理的方式施加在最后图像上(屏幕空间),然后只进行了一次光照计算就实现了最终效果。缺点:不适用于半透明

点积dot product和叉积cross product的几何意义

点积:a * b = dot(a, b) = |a| * |b| * cosθ

几何意义:测试两个向量是否为同向是否垂直,也可以用来求投影

  1. dot(a, b) == 0,则 a ⊥ b
  2. dot(a, b) > 0,a b 同向
  3. dot(a, b) < 0,a b 异向

 

叉积: cross(a, b) = |a| * |b| * sinθ * n (n 是根据右手法则得出的 cross(a, b) 方向上的单位向量,长度为1)

几何意义:可以用来求一个平面的法向量

  1. cross(a, b) 的结果是一个向量,垂直于 a 和 b,方向由右手法则得出
  2. cross(a, b) !=cross(a, b),这是两个方向相反的平行向量
  3. |cross(a, b)| 是 cross(a, b) 向量的长度,同时也是 a 和 b 所形成的平行四边形的面积
  4. |cross(a, b)| == 0,则 a // b
  5. |cross(a, b)| = |a| * |b| * sinθ,所以当 normalize a 和 b 的时候,sinθ = |a ^ b|

参考材料

  1. https://zhuanlan.zhihu.com/p/79183044
  2. https://www.zybuluo.com/cxm-2016/note/536179
  3. https://learnopengl.com/Getting-started/Shaders
  4. https://juejin.im/post/6858793605151227917
  5. https://learnopengl-cn.readthedocs.io/zh/latest/05%20Advanced%20Lighting/03%20Shadows/01%20Shadow%20Mapping/
  6. https://zhuanlan.zhihu.com/p/43899251
  7. https://juejin.im/post/6847902220982337544
  8. https://gameinstitute.qq.com/community/detail/132935
  9. https://learnopengl-cn.github.io/07%20PBR/01%20Theory/
  10. https://www.cnblogs.com/zhenbianshu/p/7061550.html
  11. https://www.cnblogs.com/wiki3d/p/shadow1.html
  12. https://www.cnblogs.com/ibingshan/p/10563002.html

 

posted @ 2020-09-10 15:58  cancantrbl  阅读(3806)  评论(2编辑  收藏  举报