CPT205 计算机图形 笔记
函数查阅:windows 开发文档
介绍课程
什么是computer graphics:
‘Computer Graphics’ is concerned with all aspects of producing pictures or images using a computer. There are three closely related meanings, each representing a different perspective on the same thing.
什么是framebuffer:
A block of memory, dedicated to graphics output, that holds the contents of what will be displayed.
什么是pixel:
an element of the framebuffer.
The most basic addressable image element in a screen
什么是bit depth :
number of bits allocated per pixel in a buffer.
计算机图形学的硬件软件:
Graphics Hardware | Graphics Software |
---|---|
Input, Processing and Output Devices | T echniques (Algorithms, Procedures) |
Framebuffers | Programming Library / API (OpenGL, JOGL and so on) |
Pixels and Screen Resolution | High level Interactive Systems (Maya, Studio Max, Unity, AutoCAD and so on) |
Image quality issues:
• Screen resolution • Colour • Refresh rate • Brightness • Contrast • Sensitivity of display to viewing • angle
什么是opengl:
The OpenGL graphics system is a software interface to graphics hardware (GL stands for Graphics Library).
OpenGL is designed as a streamlined, hardware-independent interface to be implemented on many different hardware platforms.
数学几何基本概念
计算机绘制图像时的坐标轴只能是整数,所以要尽量拟合实际的线条
优点之一是使用整数型计算,而不是DDA方法使用的浮点类型
PPT一页纸讲完了。。。贴个代码实现的帖子
Bresenham算法
deltax = x2 - x1; // The difference in the x's
deltay = y2 - y1; // The difference in the y's
y = y1; // Start y off at the first pixel value
ynum = deltax / 2; // The starting value for the numerator
for (x = x1; x <= x2; x++)
{
PutPixel(x, y); // Draw the current pixel
ynum += deltay; // Increase the numerator by the top of the fraction
if (ynum >= deltax) // Check if numerator >= denominator
{
ynum -= deltax; // Calculate the new numerator value
y++; // Increase the value in front of the numerator (y)
}
}
方法一:简单通过勾股计算描点,但这个是沿着x轴采样,在圆的左右两侧会出现断层。
解决方法是在切线斜率大于1的地方,再计算一遍沿着y轴采样的坐标。
方法二:采用极坐标画圆
原理:先用极坐标公式对圆进行大致描点(逐角度描点,一般步长设置为1/r),然后调用直线绘制两两点之间的直线去近似表现圆
tips:由于圆的对称,通常计算四分之一或八分之一个圆的坐标即可
详阅:区域填充算法和多边形填充
rasterization光栅化
方法一,洪水填充算法:
1)在内部选择一像素点
2)递归地访问并填充这个像素点周围的点,不访问边界像素
方法二,扫描线填充算法:
用水平或垂直的扫描线对多边形进行扫描,对于每次扫描
1)求扫描线与多边形的交点
2)筛选落在多边形内部的那些线段
3)填充落在多边形内部的线段
这里引用OpenGL学习专栏里的一段话解释openGL里的变换:
我们生活在一个三维的世界——如果要观察一个物体,我们可以:
1、从不同的位置去观察它。(视图变换Viewing Transformation)
2、移动或者旋转它,当然了,如果它只是计算机里面的物体,我们还可以放大或缩小它。(模型变换Modelling Transformation)
3、如果把物体画下来,我们可以选择:是否需要一种“近大远小”的透视效果。另外,我们可能只希望看到物体的一部分,而不是全部(剪裁)。(投影变换Projection Transformation)
4、我们可能希望把整个看到的图形画下来,但它只占据纸张的一部分,而不是全部。(视口变换Viewport Transformation)
这些,都可以在OpenGL中实现.
5, 计算机图形需要表现在二维的电脑屏幕上(Device Transformation)
三维变换是二维变换的扩展,这里只展示三维的矩阵变换
Rotation旋转:
旋转可以分步骤进行,但是当顺序发生变化时,旋转的角度需要改变
关于(x,y)旋转到(x’,y’)的推导
Scaling放缩:
改变比例项大小
如修改长宽高为原来的0.8,将三个1全改为0.8
Reflection翻转:
改变比例项为负值,如按照x-y平面反转,将z的比例项改为-1.
Tips:
回顾上面矩阵相乘图
函数:
//设置当前操作矩阵为模型视图矩阵
glMatrixMode(GL_MODELVIEW);
//设置当前矩阵为单位矩阵
glLoadIdentity();
//将栈顶矩阵设置为m
glLoadMatrixf(m);
//将栈顶矩阵右乘以m
glMultMatrixf(m);
//将当前操作矩阵入栈
glPushMatrix();
//从栈中出栈矩阵
glPopMatrix();
//以浮点数形式取出栈顶矩阵,存储为m
double m[16];
glGetFloatv(GL_MODELVIEW, m);
其他函数:
旋转样例:
Rotation about the z axis by 30 degrees with a fixed point of (1.0, 2.0, 3.0)
//模型视图变换
glMatrixMode(GL_MODELVIEW);
glPushMatrix(); //将当前矩阵的副本压入栈
glTranslatef(1.0, 2.0, 3.0); //操作栈顶矩阵
glRotatef(30.0, 0.0, 0.0, 1.0);
glTranslatef(-1.0, -2.0, -3.0);
//画图
drawsth();
glPopMatrix(); //移除栈顶矩阵,回到之前状态
由于gl采用栈和矩阵右乘存储变换矩阵,所以当程序执行画图并变换时,
依次执行的是
先移动(-1,-2,-3),旋转30度,移动(1,2,3)
详细可看链接
视图三要素:
平面几何投影 planar geometric projections
只为完成三件事:
步骤:
从物体上的参考点(look-at point)指向摄像机的向量叫做(viewing direction)
接下来根据openGL函数来分析不同视景体:
opengl默认的坐标系是,水平方向向右为x轴正向,竖直向上为y轴正向,由屏幕指向外面为z轴正向。
参考
函数:gluLookAt(eye_x, eye_y, eye_z, look_x, look_y, look_z, up_x, up_y, up_z)
摄像机架好之后开始搭建视景体,视景体决定了相机看物体的方法(有没有透视,看多大的画面,看多深的深度)
函数: glOrtho(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far)
各个参数决定相机能看多宽,多高,多远。视景体是个长方体
各个参数决定相机看多宽,多高,多远。视景体是个平截头体
以下两个函数通过长度和角度两种方式定义平截头体
平截头体内的物体根据映射关系,被投影到近截平面上,在这个平截头体之外的物体不会被看见。
函数: glFrustum(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near, GLfloat far)
函数:gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
摄像机坐标=视点=view point=view origiin=projection reference point
显示器区域=视口=viewport
近截平面Near Clipping Plane的像素与显示器窗口的像素数量不同
将裁剪平面映射到显示器窗口的某个区域(全屏或部分屏幕)该区域被称为视口viewport
函数:glViewport(GLint x,GLint y,GLsizei width,GLsizei height)
Tips:openGL中,切换矩阵类型后,先指定视景体,最后指定摄像机坐标。(因为openGL矩阵是右乘的,A x B x C=A x (B x C),计算过程是和添加过程反过来的。得到摄像机坐标后才能确定视景体在世界坐标的位置然后看东西)
glMatrixMode(GL_PROJECTION); //projection transformation
glLoadIdentity(); // clear the matrix
glOrtho / glFrustum / gluPerspective(); //视景体
gluLookAt(x_p, 0, z_p, 0, 0, 0, 0, 1, 0); //摄像机
为什么需要参数曲线:形容更贴切,两个变量比隐函数(implicit function)易于表现
参数化直线,三次曲线 cubic curve,圆:
A curve description should be used, which allows rapid computation, a polynomial can therefore be used
x(t) = a0 + a1t + a2t2 + a3t3, + … + antn
如果描述k个点,只需要n=k-1个参数a:
对于需要描述许多点的情况,使用样条splines
将点分成许多独立的部分(通常最少四个控制点),每部分使用低次多项式拟合,再平滑地拼接到一起
要求:
分类:
local control局部控制:设计曲线在完成一部分曲线后,在调整其他部分时,应当保持这一部分不动,the adjustment should influence only a small / local part of the curve。
{Natural splines,Bezier curves}不需要局部控制,{B-Splines,NURBS}需要局部控制。
除了移动控制点的位置控制曲线,还可以固定控制点并改变其他参数控制曲线,如张力和偏差(tension and bias)
种类:
由控制点的矩形阵列组合成控制网格(control grid),曲面将会被分解为曲面片(surface patches),通常最少片包含4 x 4=16个控制点,曲面片越多越曲面越精细。将曲面片拼接时需要考虑拼合处的连续性
曲面和曲线一样可以考移动点和控制张力等参数控制曲线
3D modelling techniques
• Wireframe
• Surface
• Solid
模型被认为是由复数的点与线的集合构成的
曲面表达(参数表示Parametric representation):
x = x(t), y = y(t), z = z(t)
曲面表达(隐式表示Implicit representation):
s1(x,y,z) = 0, s2(x,y,z) = 0
由面的组合可以表示3d物体
缺点: 模型的模糊性和验证模型困难性,此外,没法提供表面和体积信息
the ambiguity of the model and the severe difficulty in validating the model, does not provide surface and volume-
related information
由封闭环绕的曲线表示2d或3d的面
除了缺少与体积相关的信息外,曲面模型通常定义其相应对象的“几何属性geometric properties”。
可以同时表现几何属性和物理属性
geometric properties | physical properties |
---|---|
points, curves, surfaces, volume, centre of shape | mass, centre of gravity and inertia |
由二叉树的形式表达:
the non-terminal nodes : the operators 非叶子节点是操作符
the terminal nodes : the primitives or transformation 叶子节点代表物体原语或变换
操作符包含:rigid motions(刚体运动,旋转等) and regular Boolean operations(正则布尔运算,取相交等)
正则布尔运算包含:并,减,交
如果物体只能被一个特定的数据集表示,则称这种表达是唯一的
实体物体的表示通常是明确的,但很少有唯一的。
CSG没法只用唯一集表达一个物体。
B-Rep模型通过将实体的边界分割为有限个有界子集来表示实体。
它基本上是一种拓扑显式表示。几何和拓扑信息都存储在数据结构中。
几何图形关于边界图元(点、曲线和曲面 )的形状和大小
而拓扑topology保持了边界图元的连接性。
同一拓扑可能会表示不同的几何图形,所以需要拓扑和几何数据一起确定唯一的物体。
B-Rep可以被分为以下两种:
V | E | F | R | H | S |
---|---|---|---|---|---|
vertices | edges | faces | rings (inner loops on faces) | passages/holes(genus) | shells(disjoint bodies) |
对于任意流形:
V - E + F - R + 2H = 2S
V - E + F = 2
B-Rep的实现:
将拓扑表示为指针,图形表示为存储信息,即可在编程语言中实现
Baugmart’s winged edge data structure:
The edge has pointers to the vertices at its ends, and to the next edges.
The vertices have pointers to their coordinates (x, y, z)
Euler-operators欧拉算子:
These modify the face-edge-vertex pointer structure in such a way that the Euler formula is always kept true.
是一些数据操作,可以生成面,改变物体几何,使得欧拉公式保持为真
Lab:
什么是深度测试:
深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是对应的,颜色缓冲区是存储像素的颜色信息,而深度缓冲区存储像素的深度信息。在确定是否绘制一个物体表面的时候,首先要将表面对应的像素深度值与当前深度缓冲区中的值进行比较,如果大于深度缓冲区的值,则丢弃这部分。否则利用这个像素对应的深度值和颜色值分别更新深度缓冲区和颜色缓冲区,这个过程称为深度测试。
函数说明:
现实世界中没有原点,所有原点都是设出来的局部原点local origin:
Local basis,Local transformation,Local/model frame of reference等操作都是相对于正在被处理的大世界的的局部原点而言。
什么是local frame(局部框架):通过旋转由局部原点定义的局部框架,可以旋转所有基于此的世界
什么是relative motion(相对运动):相对于局部原点的运动,(同时局部原点可能相对于更大的参考系而运动)
从symbol(prototype)开始,作为一个实例instance,通过缩放定向和定位( scale, orient and position)来定义变换,以实现实例转变(instance transformation)
什么是symbol:例如盒子,球体,茶壶,三棱锥等
Copy和Instance的区别:copy创建一个与原型互不影响的克隆体,instance创建一个可影响原型的实例化对象。
代码实现:
Set up appropriate transformations from the model frame (frame of symbols) to the world frame
模型的存储:
存储在一张表中,以给每个Symbol标号,并记录相对局部原点缩放旋转平移操作,表内不记录物体的实际结构
缺点:
当遇到一个复杂的由多个部分组成的物体,没法确定部件之间的相对关系(因为该表每个标号代表的物件之间是毫不相关的)
考虑物件之间的相对关系后,易于讨论物件的摆放
对象被分组到一个层次化的树结构中 —》 **Direct Acyclic Graph (DAG)**有向无环图
Articulated model铰接模型是层次化模型的一种应用,选取中心点作为根,各个可移动部件作为节点。限制各个零部件之间的自由移动角度(degrees of freedom (DOFs) )以约束模型姿态
层次变换Hierarchical transformations :
树中的每个节点代表所有在这个节点之下的节点的局部世界,对父节点进行变换,会影响其所有子节点。
每个节点存储一个局部变换矩阵,维护相对于其父节点的变换。
为了计算某一节点的世界空间矩阵,需要根据根节点至该节点的路径上的局部变换矩阵进行推导
使用深度优先遍历计算所有节点的世界空间矩阵,进入子树时glPushMatrix(),退出时glPopMatrix()
一些例子:
A:列举了一个机械操作臂的例子
基于此,实现的opengl绘图函数
因为东西太少,是度为1的树所以不必push和pop matrix
树结构在实现中,每个节点需要存储{指向该节点模型的绘图函数,对于父节点的变换矩阵,指向子节点的指针}
B:举了一个机器人的例子,通过深度优先遍历每个部分,使用矩阵栈存储矩阵队列,采用出入栈代替一系列矩阵乘逆操作
光影和材质(MC党狂喜
表面照明效果surface lighting effects:光经过物体的反射,会产生方向,颜色上的变化。
The effect is a general background non-directed illumination. Each object is displayed using an intensity intrinsic to it, i.e. a world of non-reflective, self-luminous object.
Consequently each object appears as a monochromatic silhouette, unless its constituent parts are given different shades when the object is created.
一般来说,光线在物体表面没有反射,物体自身向摄像机发射光线
Attenuation 光的衰减:
平行光应当无衰减,点光源和位置光源应当按照距离进行衰减光强
什么是Lighting model 照明模型:
用于计算对象表面上某个位置的颜色,使用指定曲面的各种光学特性(例如透明度、颜色反射系数和纹理参数)计算曲面的照明效果。
对于曲面,可以高精度采样或低精度采样然后插值。
曲面渲染方法(surface rendering method)使用来自照明模型的颜色计算来确定场景中所有投影位置的像素颜色。
是一个简化,计算快捷的照明模型,维护以下系数:
表示物体:
漫反射Diffuse Lighting,镜面反射Specular Lighting,环境光Ambient Lighting。
光源,摄像机,面法线,完美反射系数
表示点光源:
Idr, Idg, Idb, Isr, Isg, Isb, Iar, Iag, Iab
表示材料特性:
计算三种光源到材质的结果:
I = kd Id l · n + ks Is (v · r )α + ka Ia
对于每个材质的采样点,叠加所有光源到该店的计算结果
又分为 顶点着色(Flat (constant) shading),平滑着色(Smooth (interpolative) shading),高洛德着色(Gouraud shading)
他们的区别可以看这:各种着色
控制三个量:环境光,漫反射,镜面反射
颜色通常由环境光,漫反射控制
表面的高光通常由镜面反射控制
if a light has components (RL, GL, BL), and a material has corresponding components (RM, GM, BM); then, ignoring all other reflectivity effects, the light that arrives at the eye is given by (RLRM, GLGM, BL*BM).
Similarly, if two lights, (R1, G1, B1) and (R2, G2, B2) are sent to the eye, these components are added up, giving (R1+R2, G1+G2, B1+B2). 大于1的算作1
什么是纹理贴图,纹理投影:
The method for incorporating object detail into a scene is called texture mapping or pattern mapping.
有时候需要将一个矩形纹理映射到非矩形区域中,投影在屏幕上的纹理也会因为各种变换而扭曲。
纹理贴图的种类:
纹理类型:
纹理中包含:
四个RGBA分量,三个RGB分量,阴影的强度值,对颜色表的索引,或单个亮度值
纹理数组中的单个值被称为texel
Magnification and minification放大和缩小:
放大:一个texel映射到多个pixel
缩小:一个pixel被多个texel覆盖
边界的越界需要对齐进行平均以拟合真实效果。
什么时候纹理映射:
在rendering pipeline渲染管道的末尾。减少通过裁切的多边形,比较高效
投影中涉及到的坐标系:
材质投影的方法:
一般会想材质投影是建立三个各个维度上从二维贴图到三维曲面的映射函数,但这样很难找到一个完美的函数如x=x(s,t)来拟合。不妨反向投影backward mapping,渲染三维曲面时去找二维贴图上的点,s=s(x,y,z),不过这个函数也很难找。
Area averaging面积采样:
可减少锯齿,但是会慢一点。把一个面积的纹理投影到曲面上,
opengl纹理和几何渲染是分开的,最后在fragment processor处合并
三个步骤:
Clipping 裁切– Remove objects or parts of objects that are outside the clipping window.
Rasterization光栅化 (scan conversion) – Convert high level object descriptions to pixel colours in the framebuffer.
为什么需要裁切:
光栅化耗费资源,创建的碎片越多,耗费资源越多。如果只光栅化实际可查看的内容,可以节省计算。
讨论不透明物体的渲染:
裁剪窗口与视口的区别:
裁剪窗口选择渲染空间中的显示范围,视口指示显示器窗口内查看图像的位置。
可以先考虑两个是否都在裁切区域内,如果有一个点不在或都不在,就应该考虑线中间某部分在裁切区域内–计算与裁切区域的两个交点
为每个端点创建一个输出代码,其中包含该点相对于剪裁窗口的位置信息,使用两个端点的out codes来确定线相对于剪裁窗口的配置,如有必要,计算新的端点。
分情况讨论:
算法中的out code的定义:
将平面切成9个区域,每个区域有自己的out code,【
考虑五种情况:
算法还可以用于三维裁切,这种情况需要6bit状态码。
如果有线的向量形式表现,可以求解两个方向上的交点的先后顺序考虑截断或拒绝线段。
p(α) = (1-α)p1+ αp2, 0≤α≤1
or two scale expressions
x(α) = (1-α)x1+ αx2
y(α) = (1-α)y1+ αy2
p(α) = (1-α)p1+ αp2, 0≤α≤1
n⋅(p(α) – p0) = 0
对于多面体的裁切,有两种:
可以分别依次考虑窗口顶部裁切,底部裁切,右侧裁切,左侧裁切,每个方向设立一个裁切器
对于复杂的多面形,可以设立一个边界框,来大概判断接受拒绝还是继续讨论
去除不可见的面与上周讲的裁切相似,都是在渲染管道前期尽可能去掉没用的区域,分为两种方法:Object-space methods and Image-space methods
检测处在其他对象之前的对象,利于静态场景,较难抉择
Determine which object are in front of others. Resize does not require recalculation; works for static scenes; may be difficult to determine.
在多边形对象之间成对测试,复杂度O(n2)
通过按照深度值对多边形排序,按从后到前的顺序渲染多边形,使位于其他物体之后的多边形只需简单绘制。
首先需要按照深度值排序,复杂度O(nlogn)。
按照各个各个多边形到COP(Centre Of Projection)的距离,对齐排序。
背面剔除把多边形的位置和方向与观察方向v进行比较,消除了背离摄影机的多边形(保留右手定则指向摄像头的多边形)。如果整个多边形朝向面背向于摄像机,则整个多边形面被删除
单个物体检测过程:叉乘多边形上两非平行线,得向量n,n与视角向量v之间夹角如果在-90到90度之间,则称可见。如果对象坐标已经是相对摄像机坐标,则只需接下来比较z轴
多个物体检测过程:对于每个多边形Pi,找到向量n,视角向量v,如果v·n<0,则消去该多边形
检测每个像素位置的可见对象,可用于动态场景
Determine which object is visible at each pixel. Resize requires recalculation; Works for dynamic scenes.
对于每个宽m高n像素后的k个多边形,找到距离摄像机最近的多边形所持的像素属性并绘制。复杂度O(nmk)
设置一个二维数组内存,记录每个像素点的深度信息。深度将以0-1表示,从近裁剪平面到远裁剪平面
最初,z-buffer被初始化为非常远的裁切平面。
随后,对于每个多边形,我们知道其覆盖的像素属性,对于每个像素,计算其z-value深度,与z-buffer上相应的值比较。如果z-value小于已有值,则将这个新的像素属性覆盖现在的帧缓冲区里的像素,同时更新z-buffer这个位置的值。
深度信息的变化公式a∆x + b∆y + c∆z = 0
如果将沿着x轴扫描:
物体在扫描线处不重合,不需要深度信息。如果有物体在扫描线的位置重合,则需要深度信息。减少资源访问。
bsp二叉空间分割树
尽可能减少物体以:
使用BSP数据结构
不难观察树左下角的节点一定在其右侧节点之前
空间划分后绘制多面体的顺序(这里分割线成节点了:
At each node (for back to front rendering):