一、问答题
1. 数字图像处理、计算机图形学、模式识别的关系
图像处理
利用计算机对图像进行分析处理,继而再现图像
模式识别
计算机对图形信息进行的识别和分析描述,是从图形(图像)到描述表述的过程
计算机图形学
研究图形的计算机生成和基本图形操作,是从数据描述到图形生成的过程
2. 图形管线的各个阶段工作
3. 传统成像方法与计算机绘制成像的原理
传统成像如小孔成像;外界光通过反射经过角膜进入眼睛最后到达视网膜由内部能量刺激产生神经信号。
计算机将存储在内存中的形状转换成实际绘制在屏幕上的对应的过程称为 渲染。渲染过程中最常用的技术就是光栅化。
4. 计算机图形处理架构(有GPU模式和无GPU模式)
有GPU模式,显示指令和数据通过总线发送到GPU,GPU执行的结果,存储在显存中,视频控制器自主东显存的framebuffer中获取数据进行显示
无CPU模式,所有的指令都在CPU上执行,结果存储在主存储器的特定区域。视频控制器自主根据频率从该内存区域获取数据然后发送到显示端。
5. 简述光栅式扫描显示系统
现代计算机图像学对图像结果的展示是采用点阵像素的方式,类似于数字图像的表达,是标准的栅格方式。 图像的动态显示是以动态扫描的方式来进行,按照一秒多少帧作为帧率,每个帧分为相应的行,每个行又包括相应的像素点。
6. 屏幕分辨率三种描述,扫描频率,带宽计算
光点直径
光点的直径,显示器的物理光点尺寸。
荧光屏上两个相邻的相同颜色磷光点之间的最短距离,单位:mm
水平方向上的光点数×垂直方向上的光点数 r(x×y)
显示器精度dpi
带宽计算
•行频(水平扫描频率)
电子枪每秒在屏幕上扫描过的水平线数,单位: KHz
•帧频(重绘率/垂直扫描频率/场频)
每秒钟重复绘制显示画面的次数,单位: Hz
•显示带宽
理论带宽 B= r(x) ×r(y)×d ×v
7. GPU 与 CPU 的执行区别
CPU是按照指令队列的方式来执行,只要编写好指令就能够完成几乎所有的任务,包括图形绘制等。
GPU的处理方式不同于CPU,而是按照管线的方式进行,固定的管线流程确定了具体的操作,而相关的指令只是改变管线的参数和属性,获取输入要绘制对象的模型数据。管线根据数据处理分为两个阶段:顶点处理阶段和像素处理阶段。 顶点阶段的处理包括:几何变换,光照运算,裁剪等。经过投影变换之后进入像素处理阶段,包括图元生成,光栅处理以及纹理和buffer的处理等
8. 深度检测与深度buffer的作用机理
在绘制3D场景的时候,我们需要决定哪些部分对观察者是可见的,或者说哪些部分对观察者不可见。
对于不可见的部分,我们应该及早地丢弃,例如在一个不透明的墙壁后的物体就不应该渲染。但是这种方法无法解决物体存在互相重叠的情况如下图。
因而,出现了像素级别的深度缓冲方法:当图元光栅化时,一些插值属性被写入输出缓存中。其中一个属性就是顶点坐标的 z 值,被写入称为深度缓存的缓存中。开始时,深度缓存初始化为可能的最大值,然后光栅化每个图元,对于每个被覆盖的像素,计算它的 z 值,如果 z 值小于深度缓存中的当前值,则用新值替换深度缓存中的值
9. vertex shader 与 fragment shader 分别实现什么样的内容
顶点着色器程序用来描述顶点需要执行的模型变换、视变换、投影变换、光照(Transform and lighting)处理的顶点着色器程序源代码/可执行文件。
在顶点着色器进行的业务处理有:
- 矩阵变换的计算
- 计算光照公式生成逐顶点颜色
- 生成/变换纹理坐标
片元着色器程序是用来描述片段上执行操作(如颜色混合)的片元着色器程序源代码/可执行文件。
在片元着色器的业务处理有:
- 计算颜色
- 获取纹素
- 往像素点中填充颜色值 它可以用于图片/视频中每个像素的颜色填充【比如给视频添加滤镜,实际上就是将视频中每个图片的像素点颜色填充进行修改】
10. shader 编程中,变量标识 uniform,attribute,varying 分别代表什么意思
uniform 变量
uniform 变量是外部 application 程序传递给( vertex 和 fragment )shader 的变量。因此它是 application 通过函数
glUniform()
函数赋值的。在(vertex和fragment)shader 程序内部,uniform 变量就像是 C 语言里面的常量(const),它不能被 shader 程序修改。(shader只能用,不能改)uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。
attribute 变量
attribute 变量是只能在 vertex shader 中使用的变量。(在 fragment shader 中不能声明或引用)
一般用attribute变量来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标,顶点颜色等。
varying 变量
varying 变量是 vertex 和 fragment shader 之间做数据传递用的。一般 vertex shader 修改 varying 变量的值,然后 fragment shader 使用该 varying 变量的值(不能修改)。因此 varying 变量在vertex 和 fragment shader 二者之间的声明必须是一致的。
11. 简述 framebuffer 的概念,以及在渲染的时候的作用
在 OpenGL 以及大部分的渲染管线中,帧缓存(Frame buffer)是在实际渲染之前的最后一个步骤。帧缓存本质上是一块内存或者硬件中的空间,负责保存需要渲染图像的像素相关信息。
帧缓存是一个集合概念,因为帧缓存的内部包括(但不局限于):
- 颜色缓存 Color buffer:记录像素颜色信息,很多帖子也将颜色缓冲当作帧缓冲的主体。
- 深度缓存 Z buffer:记录像素深度值。
- 模块缓存 Stencil buffer:限制渲染区域,可以和深度缓存一起创造不同的渲染效果 。
帧缓存的存在是为了在渲染到屏幕或者说传到窗口缓冲区之前能对图像进行后期处理 (Post-processing)(反相,灰度,核效果)等其他操作。帧缓存也实现了离屏渲染。
12. bresenham算法
1 | dx = x2 - x1 |
13. 画图描述圆对称性坐标计算
运用对称性之后,整个圆的产生只需要计算
14. 描述扫描线多边形填充算法流程
- 扫描线从下向上遍历,即y从小向大的方向遍历
- 每条扫描线与多边形边之间完成下列四个任务
(1)求交:计算扫描线与多边形各边的交点;
(2)排序:把所有交点按x值递增顺序排序;
(3)配对:第一个与第二个,第三个与第四个等等;每对交点代表扫描线与多边形的一个相交区间;
(4)填色:把相交区间内的象素置成多边形颜色;
15. 利用多边形顶点,创建有序边表,能够描述动态更新的活化边表
把与当前扫描线相交的边称为活化边,并把它们按与扫描线交点x坐标递增的顺序存放在一个链表中,形成活化边表。
活化边表的操作包括插入边、删除边,更新边信息。
活性边表的结点中至少应为对应边保存如下内容:
第 1 项存当前扫描线与边的交点坐标
第 2 项存从当前扫描线到下一条扫描线间
第 3 项存该边所交的最高扫描线号
第 4 项存指向下一条边的指针(Λ代表一条边的退出,即结束或抛弃)
扫描线6的活性边表 :
扫描线7的活性边表:
为了方便活性边表的建立与更新,我们为每一条扫描线建立一个新边表(NET),存放在该扫描线第一次出现的边。也就是说,若某边的较低端点为 ymin,则该边就放在扫描线 ymin 的新边表中。
16. 分别用奇偶规则和非零环绕规则判断下列内外区域
奇偶规则
•从任意位置P作不经过顶点的射线
•计算射线穿过的多边形边的数目
•奇数为内部点,否则为外部点
非零环绕规则
•环绕数初始为零
•从位置P作不经过顶点的射线
•多边形边从右至左穿过射线,加1
•多边形边从左至右穿过射线,减1
•非零为内部点;否则为外部点
17. 构造大矩阵实现观察变换
视点从(0,0,0)up(0,1,0)方向z轴负方向,到y轴上一点(0,5,0)向(0,0,0)up(0,0,1)观察变换。
沿平面
应该是
18.
在二维平面上,构造大矩阵实现,绕 轴旋转 45 度角的旋转矩阵
?
19. 描述观察变换的坐标变换关系
(Rx, Ry, Rz)表示摄像机的X轴方向向量,(Ux, Uy, Uz)表示摄像机坐标系的Y轴方向,(Dx, Dy, Dz)表示摄像机的视线方向及Z轴。 (Px, Py, Pz)表示摄像机位置的世界坐标。
20. 求视口映射点坐标
已知
21. 用 CS 算法对线段进行剪裁
计算直线端点区域编码: c1 和 c2;
判断
- c1 和 c2 均为 0000,保留直线
- c1 & c2 不为零,同在某一边界外,删除该直线
- c1 & c2 为零,需要进一步求解交点
以左、右、下、上为序,找出端点区域码中第一位为1的位,将窗口边界方程
或 或 或 代入直线方程,计算直线与窗口边界的交点,将交点和另一端点形成新的直线,重复上述过程,直至线段保留或删除
已知线段的两个端点
22. 用 LB 算法实现线段剪裁
如果直线在窗口内, 则
计算
判断是否存在
, 如果存在, 进一步判断 ,表示直线平行于窗口某边界 if ,直线完全在窗口外,被剪裁
else 直线在边界内对
的情形, 用 计算交点所对应的U值对每条线计算参数
- 如果
, 则直线在窗口外,否则计算交点坐标
23. 平行投影,透视投影概念
所有投影线都平行的投影叫平行投影。若投影线正交于投影平面,称为正投影。
假如站在一个无限大的窗户后面,用一只眼睛望向外面。可以从每个点到眼睛画一条线,且对于每个看到的点相应的线会穿过窗户。视点(眼睛)称为投影中心
24. 投影的三要素
形体、投影方向或者投射中心以及投影面
25. 主灭点,一点透视,两点透视,三点透视
灭点:将物体的边无限延长时最后消失的点。
因为一点透视只有一个消失点(灭点)所以也叫:平行透视
两点透视有两个消失点(灭点),所以也叫:成角透视。
三点透视在竖直线上有一个灭点
26. 观察体调整
调整目的
投影不是绘制流水线最后一步操作,对于基于光栅化的绘制流水线,所采用的算法应该是以特定视见体位参数,所以一个更简洁高效的选择是在投影和流水线其余部分之间建立一个公共接口。事实上投影总是要求将对应的视见体转换为规范视见体。为
到 的立方体调整的意义
计算、处理方便快捷
如何调整
Z轴错切+缩放变换,缩放系数随着z的不同而变化
27. 多边形网格模型表示中,基本数据表形式,顶点表,边表,面表
三维对象的表示方法可以分为面表示和体表示
面表示
表示三维对象的表面,包括多边形网格、隐式曲面和参数曲面
体表示
表示三维对象的体积。常用的体数据表示方法包括体素和构造实体几何
顶点表
序号 | 点坐标 |
---|---|
1 | x1,y1,z1 |
2 | x2,y2,z2 |
3 | x3,y3,z3 |
4 | x4,y4,z4 |
5 | x5,y5,z5 |
边表
序号 | 顶点号 |
---|---|
1 | v1,v2 |
2 | v2,v3 |
3 | v3,v1 |
4 | v3,v4 |
5 | v4,v5 |
6 | v5,v1 |
面表
序号 | 边序号 |
---|---|
1 | E1,E2,E3 |
2 | E3,E4,E5,E6 |
28. 用函数描述的二次曲面模型,如球体表面,如何进行绘制
一旦给定函数,图形包将指定曲线方程投影到显示平面上,且沿着投影函数路径绘制像素位置。
对曲面而言,函数式描述通常嵌入到生成曲面的多边形网格逼近中。
通常使用三角形的多边形曲面片可以确保任一多边形的顶点在一个平面上。有四个或四个以上顶点的多边形其顶点可能会不在一个平面上。
由函数式描述而生成的显示曲面的例子有二次曲面和超二次曲面
29. 样条曲线,样条曲面
样条
通过一组指定点集而生成平滑曲线的柔性带。
样条曲线在计算机图形学中的含义
由多项式曲线段连接而成的曲线
在每段的边界处满足特定的连续性条件
样条曲面
使用两组正交样条曲线进行描述
样条在图形学中的应用
设计曲线、曲面
30. 样条曲线的两种类型
插值样条曲线:选取的多项式使得曲线通过每个控制点
逼近样条曲线:选取的多项式不一定使曲线通过每个控制点
31. 凸壳的概念
凸壳
包含一组控制点的凸多边形边界
凸壳的作用
提供了曲线或曲面与包围控制点的区域之间的偏差的测量
以凸壳为界的样条保证了多项式沿控制点的平滑前进
32. 分段连续中连续的定义
参数连续性条件
两个相邻曲线段在相交处的参数导数相等
零阶连续(C0连续):简单地表示曲线连接
一阶连续(C1连续):说明代表两个相邻曲线的方程在相交点处有相同的一阶导数(切线)
二阶连续(C2连续):两个曲线段在交点处有相同的一阶和二阶导数,交点处的切向量变化率相等
几何连续性条件
两个相邻曲线段在相交处的参数导数成比例
零阶连续(G0连续):与0阶参数连续性相同,即两个曲线必在公共点处有相同的坐标
一阶连续(G1连续):表示一阶导数在两个相邻曲线的交点处成比例
二阶连续(G2连续):表示两个曲线段在相交处的一阶和二阶导数均成比例
33. 简述 Bezier 的几个特点
Bezier 多项式次数=控制点个数-1
Bezier 曲线总是通过第一个和最后一个控制点
Bezier 曲线在第一个控制点
处与直线 相切,在最后一个控制点 处与直线 相切。Bezier曲线总是落在控制点的凸壳内
保证了曲线沿控制点的平稳前进
34. 漫反射特性,镜面反射特性
漫反射
- 理想漫反射表面在各个方向以相同强度反射光线,亦称为 Lambert 表面(Lambertian surface)。类似于投影幕布
- 光的反射量与入射光强度以及夹角有关
考虑到余弦为负的情况,常用
对一般非理想镜面的光滑表面,反射光集中在一个方向范围内,并且反射定律决定的反射方向光强最大。
物体表面越光滑,镜面反射光就越集中于以反射定律决定的反射方向为中心的更小的一个角度范围内,超出这个范围镜面反射光将迅速衰减。
环境光与光源的方向、位置没有关系;
环境光没有位置或方向上的特征,只有一个颜色亮度值,强度不会衰减。
35. 解释渲染方程
其中
36. Phong 光照模型
漫反射
37. Gouraud 明暗处理
- 计算顶点法向量,
- Phong 光照明模型计算顶点的光强
- 光强插值
38. threejs 的浏览器中的调试,文件路径概念
1 | const axesHelper = new AxesHelper( 700 ); //创建AxesHelper,700是三条线的长度 |
folder/data
当前文件夹的 folder
子目录下的
data
文件
../folder/data
上层文件夹下的folder
子目录下的 data
文件
/folder/data
根目录下 folder
文件夹下的
data
文件
39. VBO,PBO,FBO 的概念
VBO(Vertex Buffer Object)是指顶点缓冲区对象
VBO 作用是在显存中提前开辟好一块内存,用于缓存顶点数据或者图元索引数据,从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以改进渲染性能,降低内存带宽和功耗。
FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO)。
FBO 本身不能用于渲染,只有添加了纹理或者渲染缓冲区之后才能作为渲染目标,它仅且提供了 3 个附着(Attachment),分别是颜色附着、深度附着和模板附着。
PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念,称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。
PBO 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。
40. 创建一个 vbo,并基于 vbo 进行绘制
顶点缓冲对象是我们在 OpenGL 教程中第一个出现的 OpenGL 对象。就像 OpenGL 中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用 glGenBuffers 函数和一个缓冲 ID 生成一个 VBO 对象:
1 | unsigned int VBO; |
OpenGL 有很多缓冲对象类型,顶点缓冲对象的缓冲类型是 GL_ARRAY_BUFFER。OpenGL 允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用 glBindBuffer 函数把新创建的缓冲绑定到GL_ARRAY_BUFFER 目标上:
1 | glBindBuffer(GL_ARRAY_BUFFER, VBO); |
从这一刻起,我们使用的任何(在 GL_ARRAY_BUFFER 目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用 glBufferData 函数,它会把之前定义的顶点数据复制到缓冲的内存中:
1 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
glBufferData
是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到
GL_ARRAY_BUFFER
目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof
计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
- GL_STATIC_DRAW :数据不会或几乎不会改变。
- GL_DYNAMIC_DRAW:数据会被改变很多。
- GL_STREAM_DRAW :数据每次绘制时都会改变。
使用 glVertexAttribPointer 函数告诉 OpenGL 该如何解析顶点数据(应用到逐个顶点属性上)了:
1 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); |
glVertexAttribPointer函数的参数非常多,所以我会逐一介绍它们:
- 第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用 layout(location=0) 定义了 position 顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入 0。
- 第二个参数指定顶点属性的大小。顶点属性是一个 vec3,它由 3 个值组成,所以大小是 3。
- 第三个参数指定数据的类型,这里是 GL_FLOAT (GLSL 中 vec* 都是由浮点数值组成的)。
- 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为 GL_TRUE,所有数据都会被映射到 0(对于有符号型signed数据是 -1 到 1 之间)。我们把它设置为 GL_FALSE。
- 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在 3 个 float 之后,我们把步长设置为 3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为 0 来让 OpenGL 决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子。
- 最后一个参数的类型是 void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。
每个顶点属性从一个 VBO 管理的内存中获得它的数据,而具体是从哪个 VBO(程序中可以有多个 VBO)获取则是通过在调用 glVertexAttribPointer 时绑定到 GL_ARRAY_BUFFER 的 VBO 决定的。由于在调用glVertexAttribPointer 之前绑定的是先前定义的 VBO 对象,顶点属性 0 现在会链接到它的顶点数据。
现在我们已经定义了 OpenGL 该如何解释顶点数据,我们现在应该使用 glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。自此,所有东西都已经设置好了:我们使用一个顶点缓冲对象将顶点数据初始化至缓冲中,建立了一个顶点和一个片段着色器,并告诉了OpenGL如何把顶点数据链接到顶点着色器的顶点属性上。在OpenGL中绘制一个物体,代码会像是这样:
1 | // 0. 复制顶点数组到缓冲中供OpenGL使用 |
41. 深度缓存的意义,基于深度检测的方法
在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。
实际上,只要存在深度缓冲区,无论是否启用深度测试,在像素被绘制时都会尝试将深度数据写入到缓冲区内。这些深度数据除了用于常规的测试外,还可以有一些有趣的用途,比如绘制阴影等等。
42. 描述固定管线中的光照模型
顶点处理主要进行顶点齐次坐标变换和光照(固定管线功能只有逐顶点光照)。
若光照被关闭,顶点的颜色将直接设置成当前颜色( glColor()
指定),若打开,顶点将根据当前材料颜色(glMatiral(),可分正面背面分别设置)和法向量(glNormal())来计算环境、散射、高光、发射光的颜色,并叠加。光照分正背面进行(由
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TURE[FALSE])
控制),也即顶点处理完成后每个顶点将有两个颜色属性值(见顶点着色器内置输出变量,gl_FrontColor/BackColor),正面颜色的计算依据正面材料及法向量
二、实验题
1. threejs 的程序主框架(初始化和主循环)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 100)
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild( renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material);
scene.add(cube);
camera.position.z = 5;
function animate(){
requestAnimationFrame(animate);
renderer.render( scene, camera);
cube.rotation.x+=0.01;
cube.rotation.y+=0.01;
}
animate();
2. 利用 threejs 实现某个场景的绘制,写出设计和具体代码
同上
3. 利用 threejs 实现对象的运动控制的设计,如太阳系运动,以及汽车与轮子运动等。
每个 animate 帧里更新就行
4. 利用 threejs 实现天空盒的搭建
1 | scene.background = new THREE.CubeTextureLoader().load([ |
5. 利用 threejs 设计实现一个简单游戏:用鼠标控制旋转方向,键盘控制向前向后移动,空格键发射子弹,射线检测碰撞结果
1 | import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'; |
6. 利用 threejs 设计实现一个函数曲线的绘制
1 | const scene = new THREE.Scene(); |
7. 利用 threejs 设计实现一个曲面的绘制
1 | var qwq = function(u,v){ |
另附一个网上抄的克莱因瓶 https://blog.51cto.com/u_15948039/6027103
1 | var klein = function (u, v) { |
8. 利用 threejs 设计实现一个模型文件的加载和场景绘制
模型加载注意浏览器的 same origin policy
1 | import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; |
9. 利用 threejs 实现三维文字信息tip注释的功能。
1 | function makeTextSprite( message, parameters ){ |
10. 利用 threejs 的 raycaster.setFromCamera(pointer, camera) 实现鼠标选中三维物体进行交互
1 | function click(event){ |