# 基本光栅图形的生成
# 直线的生成算法
画一条从 (x1,y1) 到 (x2,y2) 的直线。实质上是一个发现最佳逼近直线的像素序列,并填入色彩数据的过程。这一过程称为直线的光栅化。
# DDA 算法
DDA(Digital Differential Analyzer)算法是一种基本的直线生成算法。它的基本思想是:直线的起点坐标为 P1(x1,y1),终点坐标为 P2(x2,y2),x 和 y 的增量分别为 Δx=x2−x1 和 Δy=y2−y1,则直线的斜率为 m=ΔxΔy。
- 当 Δx>Δy 时,以 x 为基准,每次递增 1,即 xk+1=xk+1,计算出 y 的增量 Δy=m,则 yk+1=yk+m。
- 当 Δx≤Δy 时,以 y 为基准,每次递增 1,即 yk+1=yk+1,计算出 x 的增量 Δx=m1,则 xk+1=xk+m1。
当 ∣Δx∣>∣Δy∣ 时,x 的增量为 1,y 的增量为 m;
当 ∣Δx∣≤∣Δy∣ 时,x 的增量为 m1,y 的增量为 1。
增量的符号由 Δx 和 Δy 的符号决定。
| void DDA(int x1, int y1, int x2, int y2) { |
| int dx = x2 - x1, dy = y2 - y1; |
| int steps = abs(dx) > abs(dy) ? abs(dx) : abs(dy); |
| float xIncrement = dx / (float)steps; |
| float yIncrement = dy / (float)steps; |
| float x = x1, y = y1; |
| for (int i = 0; i <= steps; i++) { |
| putPixel(round(x), round(y)); |
| x += xIncrement; |
| y += yIncrement; |
| } |
| } |
# Bresenham 算法
设直线从起点 (x1,y1) 到终点 (x2,y2),直线方程为 y=mx+b,其中 m=ΔxΔy=x2−x1y2−y1,b=y1−mx1。
# Bresenham 算法计算公式
yd1d2=m(xi+1)+b=y−yi=yi+1−y(1)(2)(3)
如果 d1−d2>0,则 yi+1=yi+1,否则 yi+1=yi。
将式 (1)、(2)、(3) 代入 d1−d2,再用 Δx 乘等式两边,并以 Pi=(d1−d2)Δx 代入上述等式,得
Pi=2xidy−2yidx+2dy+(2b−1)dx(4)
d1−d2 是用以判断符号的误差。由于在第一象限,Δx 总大于 0,所以 Pi 仍旧可以用作判断符号的误差。Pi+1 为
Pi+1=Pi+2dy−2(yi+1−yi)dx(5)
求误差的初值 P1,可将 x1、y1 和 b 代入式 (2.4) 中的 xi、yi 而得到
P1=2dy−dx
# 第 1a 象限内的直线 Bresenham 算法思想:
-
画点 (x1,y1),dx=x2−x1,dy=y2−y1,计算误差初值P1=2dy−dx,i=1;
-
求直线的下一点位置 xi+1=xi+1,如果 Pi>0,则 yi+1=yi+1,否则 yi+1=yi;
-
画点 (xi+1,yi+1);
-
求下一个误差 Pi+1,如果 Pi>0,则Pi+1=Pi+2dy−2dx,否则Pi+1=Pi+2dy
-
i=i+1;如果 i<dx+1 则转步骤 2;否则结束操作。
# Bresenham 算法的优点:
- 不必计算直线的斜率,因此不做除法。
- 不用浮点数,只用整数。
- 只做整数加减运算和乘 2 运算,而乘 2 运算可以用移位操作实现。Bresenham 算法的运算速度很快,并适于用硬件实现。
注意:以上讨论的是 0<Δy<Δx 的情况,对于适用所有 8 个方向的直线的生成算法,则要考虑以判断条件 ∣dx∣>∣dy∣ 为分支,并分别将 2a、3a 象限的直线和 3b、4b 象限的直线变换到 1a、4a 和 2b、1b 象限方向去,以实现程序处理的简洁。
# 圆的生成算法
# 基础知识
给出圆心 (xc,yc) 和半径 r,则圆上任意一点 (x,y) 满足
(x−xc)2+(y−yc)2=r2
由上式导出:
y=±r2−(x−xc)2+yc
也可以使用极坐标方程来表示圆:
x=xc+rcosθ,y=yc+rsinθ
利用圆周坐标的对称性,此算法还可以简化。将圆周分为 8个象限,只要将第 1a 象限中的圆周光栅点求出,其余 7部分圆周就可以通过对称法则计算出来。
# 圆的 Bresenham 算法
设圆的半径为 r。考虑圆心在 (0,0),并从 x=0、y=r 开始的顺时针方向的 1/8 圆周的生成过程。在这种情况下,x 每步增加 1,从 x=0 开始,到 x=y 结束。
即: xi+1=xi+1
相应的 yi+1 则在两种可能中选择:
yi+1=yioryi+1=yi−1
# 圆的 Bresenham 计算公式
计算式为
y2d1d2=r2−(xi+1)2=yi2+(xi+1)2−r2=r2−((xi+1)2+(yi−1)2)
令 pi=d1−d2,并代入 d1、d2,则有
pi=2(xi+1)2+yi2+(yi−1)2−2r2(6)
pi 称为误差。如果 pi<0 则 yi+1=yi,否则 yi+1=yi−1。
pi 的递归式为
pi+1=pi+4xi+6+2(yi2+1−yi2)−2(yi+1−yi)(7)
pi 的初值由式 (6) 代入 xi=0,yi=r 而得
p1=3−2r(8)
# Bresenham 圆周生成算法思想
-
求误差初值,p1=3−2r,i=1,画点 (0,r);
-
求下一个光栅位置,其中 xi+1=xi+1,如果 pi<0 则 yi+1=yi,否则 yi+1=yi−1;
-
画点 (xi+1,yi+1);
-
计算下一个误差,如果 pi<0 则 pi+1=pi+4xi+6,否则 pi+1=pi+4(xi−yi)+10;
-
i=i+1,如果 x=y 则结束,否则返回步骤 2。
| void BresenhamCircle(int r, int xc, int yc) { |
| int x = 0, y = r; |
| int p = 3 - 2 * r; |
| while (x < y) { |
| plotCirclePoints(x, y, xc, yc); |
| if (p < 0) { |
| p += 4 * x + 6; |
| } else { |
| p += 4 * (x - y) + 10; |
| y--; |
| } |
| x++; |
| } |
| if (x == y) { |
| plotCirclePoints(x, y, xc, yc); |
| } |
| } |
| |
| void plotCirclePoints(int x, int y, int xc, int yc) { |
| putPixel(xc + x, yc + y); |
| putPixel(xc - x, yc + y); |
| putPixel(xc + x, yc - y); |
| putPixel(xc - x, yc - y); |
| putPixel(xc + y, yc + x); |
| putPixel(xc - y, yc + x); |
| putPixel(xc + y, yc - x); |
| putPixel(xc - y, yc - x); |
| } |
# 圆的中点画圆法
定义圆函数
f(x,y)=x2+y2−r2
⎩⎨⎧f(x,y)>0,f(x,y)=0,f(x,y)<0,点在圆外点在圆上点在圆内
# 圆的中点画圆法计算公式
pk=f(xk+1,yk−21)=(xk+1)2+(yk−21)2−r2
若 pk<0,则 yk+1=yk,否则 yk+1=yk−1。
pk+1={pk+2(xk+1)+1,pk+2(xk+1)+1−2(yk−1),pk<0pk≥0
# 圆的中点画圆法思想
-
输入圆半径 r;
-
求误差初值,p0=45−r,画点 (0,r);
-
求下一个光栅位置,其中 xi+1=xi+1,如果 pi<0 则 yi+1=yi,否则 yi+1=yi−1;
-
画点 (xi+1,yi+1);
-
计算下一个误差,如果 pi<0 则 pi+1=pi+2xi+1+1,否则 pi+1=pi+2(xi+1−yi+1)+1;
-
确定其他七个八分圆中的对称点;
-
i=i+1,如果 x=y 则结束,否则返回步骤 3。
# 区域填充算法
# 区域填充基础知识
区域填充即给出一个区域的边界,要求对边界范围内的所有像素单元赋予指定的颜色代码。区域填充中最常用的是多边形填色。
-
计算机图形学中,多边形区域有两种重要的表示方法:顶点表示和点阵表示。
-
所谓顶点表示,即是用多边形的顶点序列来表示多边形。这种表示直观、几何意义强、占内存少,易于进行几何变换,但由于它没有明确指出哪些像素在多边形内,故不能直接用于区域填充。
-
所谓点阵表示,则是用位于多边形内的像素集合来刻画多边形。这种表示丢失了许多几何信息,但便于进行填充。
根据区域的定义,可以采用不同的填充算法,其中最具代表性的是:适应于顶点表示的扫描线类算法和适应于点阵表示的种子填充类算法。
# 扫描线填色算法
算法的基本思想:
多边形以 n
、 x_array
、 y_array
的形式给出,其中, x_array
、 y_array
中存放着多边形的 n 个顶点的 x,y 坐标。
用水平扫描线从上到下扫描由点线段构成的多段定义成的多边形。每根扫描线与多边形各边产生一系列交点。这些交点按照 x 坐标进行分类,将分类后的交点成对取出,作为两个端点,以所需要填的色彩画水平直线。多边形被扫描完毕后,填色也就完成。
对于一条扫描线,多边形的填充过程可以分为四个步骤:
- 求交:计算扫描线与多边形各边的交点;
- 排序:把所有交点按 x 值递增顺序排序;
- 配对:第一个与第二个,第三个与第四个等等;每对交点代表扫描线与多边形的一个相交区间;
- 填色:把相交区间内的像素置成多边形颜色,把相交区间外的像素置成背景色。
# 需要解决或改进的几个问题
# 左、右顶点处理
当以 1,2,3 的次序画多边形外框时,多边形的左顶点和右顶点如右图中所示的顶点 2。它们具有以下性质:
- 左顶点 2:y1<y2<y3
- 右顶点 2:y1>y2>y3
其中 y1,y2,y3 是 3 个相邻的顶点的 y 坐标。
当扫描线与多边形的每个顶点相交时,会同时产生 2 个交点。这时,填色就会因扫描交点的奇偶计数出错而出现错误。因此,对所有左、右顶点作如下处理:
y1y2y2−1m=mx1+b=mx2+b⟹b=y2−mx2=mx+b⟹x=x2−m1=x2−x1y2−y1
对于左顶点,入边端点 (x1,y1)、(x2,y2) 修改为 (x1,y1)、(x2−m1,y2−1);
对于右顶点,入边端点 (x1,y1)、(x2,y2) 修改为 (x1,y1)、(x2−m1,y2+1);
其中,m=x2−x1y2−y1,即入边的斜率。
对于多边形的上顶点 (y2>y1,y2>y3) 或下顶点 (y2<y1,y2<y3),奇偶计数保持正确。
# 水平边处理
水平边 (y1=y2) 与水平扫描线重合无法求交点。因此,将
水平边画出后删去,不参加求交点及求交点以后的操作。
# 扫描线与边的求交点
采用递归算法,以(x1,y1)、(x2,y2) 为端点的边与第i+1 条扫描线的交点为
{yi+1=yi−1xi+1=xi+y2−y1x2−x1
# 减少求交计算量
采用活性边表。
对于一根扫描线而言,与之相交的边只占多边形全部边的一部分,每根扫描线与多边形所有边求交的操作是一种浪费,需要加以改进。
活性边表 (Active List of Side) 的采用将多边形的边分成两个子集:与当前扫描线相交的边的集合,以及与当前扫描线不相交的边的集合。对后者不必进行求交运算,这样就提高了算法的效率。
为了方便活性边表的建立与更新,可为每一条扫描线建立一个新边表 (NET),存放在该扫描线第一次出现的边。也就是说,若某边的较低端点为ymin,则该边就放在扫描线ymin 的新边表中,下图是新边表的示意图。
# 种子填充算法
种子填色又称边界填色 (Boundary Filling)。它的功能是,给出多边形光栅化后的边界位置及边界色代码 boundary_color
,以及多边形内的一点 (x, y) 位置,要求将颜色 fill_color
填满多边形。
# 区域连通性
# 4 连通:
从区域内任意一点出发,可通过上、下、左、右四个方向到达区域内的任意象素;
# 8 连通
从区域内任意一点出发,可通过上、下、左、右、左上、左下、右上、右下八个方向到达区域内的任意象素;
# 字符的生成
# 点阵式字符
点阵式字符将字符形状表示为一个矩形点阵,由点阵中点的不同值表达字符的形状。
使用点阵式字符时,需将字库中的矩形点阵复制到缓冲器中指定的单元中去。在复制过程中,可以施加变换,以获得简单的变化。
# 矢量式字符
矢量式字符将字符表达为点坐标的序列,相邻两点表示一条矢量,字符的形状便由矢量序列刻画。
# 点阵字符与矢量字符的比较
- 字符变换不同。对点阵字符的变换需要对每一个像素进行,变换、放大或旋转时会失真;表示矢量字符的是终点坐标,对矢量字符的变换是对端点的变换,属于几何变换,不会影响效果。
- 占用空间不同。矢量字符占用空间少,矢量字符只需保存一套字符,其他不同型号的字符可以通过相应的几何变换来产生。
- 矢量字符美观。除了直线段外,还可以用二次曲线段、三次曲线段等来表示,使字符更加美观,变换更方便。
# 三角形的光栅化
# 选择三角形的原因
- 最基础的多边形,任意多边形都可以由三角形组成;
- 保证三点在一个平面上
- 便于判断内部
- 便于插值计算(baricentric interpolation)
# 三角形的采样
| 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); |
inside(tri,x,y)={10if x,y is inside the triangleotherwise
如何判断点是否在三角形内
V1V2V3=P0P1×P0Q=P1P2×P1Q=P2P0×P2Q
如果 V1,V2,V3 的方向相同,则点 Q 在三角形内。
使用包围盒判断三角形是否在屏幕内
# 反走样(Antialiasing)
走样是由于图像的分辨率有限,导致图像中的曲线或者对比度较高的区域出现了锯齿状的现象。
# 先模糊再采样(Blurring Before Sampling)
傅立叶变换是一种信号处理的方法,可以将信号分解为不同频率的正弦波。在图像处理中,可以将图像分解为不同频率的正弦波,然后对每个频率的正弦波进行模糊处理,最后再将这些正弦波合成为图像。
F(ω)F(u,v)eix=∫−∞∞f(x)e−2πiωxdx=∬−∞∞f(x,y)e−2πi(ux+vy)dxdy=cosx+isinx
# 卷积(Convolution)
空间域中的卷积等于频域中的乘法,反之亦然
# 如何减少走样错误
-
增加采样率
- 本质上是增加傅立叶域中副本之间的距离
- 更高分辨率的显示器、传感器、帧缓冲区……
- 但:成本高且可能需要非常高的分辨率
-
反走样
- 在重复之前使傅立叶内容 “变窄”
- 即在采样之前滤除高频
# 通过平均值减少走样
- 对 f (x,y) 进行 1 像素的方框模糊卷积
- 然后在每个像素的中心进行采样
# 多重采样抗锯齿(MSAA)
- 通过在每个像素中心周围的多个位置进行采样来减少走样
- 例如,4x MSAA 会在每个像素中心周围的 4 个位置进行采样
- 通过对这些采样进行平均值来减少走样
# 图形变换
# 图形的几何变换
# 二维图形的基本变换
# 基本变换
# 平移
x′y′=x+tx=y+ty
# 旋转
x′y′=xr+(x−xr)cosθ−(y−yr)sinθ=yr+(x−xr)sinθ+(y−yr)cosθ
# 缩放
x′y′=x⋅Sx=y⋅Sy
当 SxorSy<0 时,图形会发生镜像变换
# 齐次坐标
齐次坐标表示法就是由 n+1 维向量表示一个 n 维向量。如 n 维向量 (P1,P2,…,Pn) 表示为 (hP1,hP2,…,hPn,h),其中 h 称为哑坐标。
- h 可以取不同的值,所以同一点的齐次坐标不是唯一的。
- 普通坐标与齐次坐标的关系为 “一对多”:
- 由普通坐标 ×h→ 齐次坐标
- 由齐次坐标 ÷h→ 普通坐标
- 当 h=1 时产生的齐次坐标称为 “规格化坐标”,因为前 n 个坐标就是普通坐标系下的 n 维坐标。
(x,y) 的齐次坐标为 (xh,yh,h),其中 xh=x⋅h,yh=y⋅h,h=0。
(x,y) 对应的齐次坐标为三维空间中的一直线:
⎩⎨⎧xh=hxyh=hyzh=h
齐次坐标的优点
- 将各种变换用阶数统一的矩阵来表示。提供了用矩阵运算把二维、三维甚至高维空间上的一个点从一个坐标系变换到另一坐标系的有效方法。
- 便于表示无穷远点。例如:(x×h,y×h,h),令 h 等于 0。
- 齐次坐标变换矩阵形式把直线变换成直线段,平面变换成平面,多边形变换成多边形,多面体变换成多面体。
- 变换具有统一表示形式的优点:便于变换合成,便于硬件实现
# 变换矩阵
# 平移矩阵
平移矩阵运算表示为
[x′y′1]=[xy1]10Tx01Ty001
简记为 p′=p⋅T(Tx,Ty)。其中,p=[xy1],p′=[x′y′1],T(Tx,Ty) 为平移矩阵 T(Tx,Ty)=10Tx01Ty001。
# 旋转矩阵
旋转矩阵运算表示为
[x′y′1]=[xy1]cosθ−sinθ0sinθcosθ0001
简记为p′=p⋅R(θ)。其中,R(θ) 为旋转矩阵 R(θ)=cosθ−sinθ0sinθcosθ0001。
# 缩放矩阵
缩放矩阵运算表示为
[x′y′1]=[xy1]Sx000Sy0001
简记为p′=p⋅S(Sx,Sy)。其中,S(Sx,Sy) 为缩放矩阵 S(Sx,Sy)=Sx000Sy0001。
# 错切矩阵
[x′y′1]=[xy1]1b0d10001=[x+byy+dx1]
- 当 d=0 时,(x′,y′,1)=(x+by,y,1):图形的 y 坐标不变;
- 当 b>0:图形沿 +x 方向作错切位移。ABCD→A1B1C1D1
- 当 b<0:图形沿 −x 方向作错切位移。ABCD→A2B2C2D2
-
当 b=0 时,(x′,y′,1)=(x,y+dx,1):图形的 x 坐标不变;
- 当 d>0:图形沿 +y 方向作错切位移。ABCD→A1B1C1D1
- 当 d<0:图形沿 −y 方向作错切位移。ABCD→A2B2C2D2
-
当 b=0,d=0 时,(x′,y′,1)=(x+by,y+dx,1):图形同时在 x 和 y 方向作错切位移。
# 三维图形的基本变换
# 三维旋转矩阵
# 绕 z 轴旋转
[x′y′z′1]=[xyz1]cosθ−sinθ00sinθcosθ0000100001
# 绕 x 轴旋转
[x′y′z′1]=[xyz1]10000cosθ−sinθ00sinθcosθ00001
# 绕 y 轴旋转
[x′y′z′1]=[xyz1]cosθ0sinθ00100−sinθ0cosθ00001
# 绕任意轴旋转
R(θ)=T(−x1,−y1,−z1)×Rx(α)×Ry(β)×Rz(θ)×Ry(−β)×Rx(−α)×T(x1,y1,z1)
# 三维缩放矩阵
[x′y′z′1]=[xyz1]Sx0000Sy0000Sz00001
# 三维平移矩阵
[x′y′z′1]=[xyz1]100Tx010Ty001Tz0001
# 坐标系统
# 世界坐标系(World Coordinates)
为了描述被处理的对象,要在对象所在的空间中定义一个坐标系,这个坐标系的长度单位和坐标轴的方向要适合对被处理对象的描述,这个坐标系通常就称之为世界坐标系或用户坐标系。世界坐标系一般采用右手三维笛卡儿坐标系。
# 观察坐标系(View Coordinates)
产生三维物体的视图,必须规定观察点 (视点) 和观察方向。
好比照相时选择拍摄的位置和方向。
- 左手笛卡儿坐标系:观察坐标系的原点通常设置在观察点 (视点),Z 轴作为观察方向。
- 右手笛卡儿坐标系:视点确定在 Z 轴上的某一个位置,Z 轴仍为观察方向。
# 设备坐标系 (Device Coordinates)
与图形设备相关连的坐标系叫设备坐标系。
- 显示器以分辨率确定坐标单位,原点在左下角或左上角;
- 绘图机绘图平面以绘图精度确定坐标单位,原点一般在左下角。
# 规格化设备坐标系 (Normal Device Coordinates)
为了使图形处理过程做到与设备无关,通常采用一种虚拟设备的方法来处理,也就是图形处理的结果是按照一种虚拟设备的坐标规定来输出的。这种设备坐标规定为0≤X≤1,0≤Y≤1,这种坐标系称之为规格化设备坐标系。
# 规格化变换
世界坐标系指定的显示范围叫窗口。规格化设备坐标系上与窗口对应的区域叫视区(视口)。
从窗口到视区的变换,称为规格化变换 (Normalization Transformation)。
[vxvy1]=[wxwy1]×N
其中,N 为规格化变换矩阵。
NSxSy=10−wxL01−wyB001Sx000Sy000110vxL01vyB001=wxR−wxLvxR−vxL=wyT−wyBvyT−vyB
# 投影变换
投影 (project) 是一种使三维对象映射为二维对象的变换。它可描述为
project(object(x,y,z))→object(x′,y′)
投影的要素包括投影对象、投影面、投影线。
# 平行投影
# 正交平行投影(Orthographic Parallel Projection)
正投影的投影面与某一坐标轴垂直,而投影方向与该坐标轴的方向一致。正投影的图形,在长宽高三个方向上的比例与实物保持一致,因此,常用于工程制图。
# 斜交平行投影(Oblique Parallel Projection)
投影线与投影平面成α 交角
# 透视投影
- 投影中心与投影平面之间的距离为有限
- 参数:投影方向
- 灭点:不平行于投影平面的平行线,经过透视投影之后收敛于一点,称为灭点
- 主灭点:平行于坐标轴的平行线的灭点。
- 特点:产生近大远小的视觉效果,由它产生的图形深度感强,看起来更加真实。
- 投影中主灭点数目由与观察平面相交的主轴数目确定。
# 透视投影矩阵
MorthoMortho→perspMpersp=r−l20000t−b20000n−f200001100−2r+l010−2t+b001−2n+f0001=n0000n0000n+f100−nf0=Mortho×Mortho→persp
# 图形裁剪
# 直线的裁剪
直线和窗口的关系可以分为如下 3 类:
- 整条直线在窗口内。此时,不需剪裁,显示整条直线。
- 整条直线在窗口外。此时,不需剪裁,不显示整条直线。
- 部分直线在窗口内,部分直线在窗口外。此时,需要求出直线与窗框的交点,并将窗口外的直线部分剪裁掉,显示窗口内的直线部分。
直线剪裁算法有两个主要步骤:
- 首先将不需剪裁的直线挑出,即删去在窗外的直线。
- 然后,对其余直线,逐条与窗框求交点,并将窗口外的部分删去。
# Cohen-Sutherland 算法
Cohen-Sutherland 算法是一种直线裁剪算法,它将平面分为 9 个区域,每个区域用 4 位二进制数表示,称为区域码。
- 内域:区域 (0000)。
- 上域:区域 (1001, 1000, 1010)。
- 下域:区域 (0101, 0100, 0110)。
- 左域:区域 (1001, 0001, 0101)。
- 右域:区域 (1010, 0010, 0110)。
当线段的两个端点的编码的逻辑 “与” 非零时 ,线段为不可见的。因此,对某线段的两个端点的区号进行位与运算,可知这两个端点是否同在视区的上、下、左、右.
算法的主要思想是,对每条直线 P1P2:
-
对直线两端点 P1、P2 编码分别记为 C1(P1)={a1,b1,c1,d1},C2(P2)={a2,b2,c2,d2},其中,ai、bi、ci、di 取值范围为 {1,0},i∈{1,2}。
-
如果 ai=bi=ci=di=0,则显示整条直线,取出下一条直线,返回步骤 (1)。否则,进入步骤 (3)。
-
如果 ∣a1−a2∣=1,则求直线与窗上边 (y=ymax) 的交点,并删去交点以上部分。如果 ∣b1−b2∣=1,则求直线与窗下边 (y=ymin) 的交点,并删去交点以下部分。如果 ∣c1−c2∣=1,则求直线与窗右边 (x=xmax) 的交点,并删去交点以右部分。如果 ∣d1−d2∣=1,则求直线与窗左边 (x=xmin) 的交点,并删去交点以左部分。
-
返回步骤 (1)。
与裁剪边界的交点计算可以使用斜率 — 截距式的直线方程。
对于端点坐标为 (x1,y1) 和 (x2,y2) 的直线,与垂直边界交点的 y 坐标可以由下列等式计算得到:
y=y1+m(x−x1),m=x2−x1y2−y1
其中,x 的值为 xmin 或 xmax。
与水平边界交点的 x 坐标可以由下列等式计算得到:
x=x1+my−y1,m=x2−x1y2−y1
其中,y 的值为 ymin 或 ymax。
# Liang-Barsky 算法
对直线的参数化方程{x=x1+uΔxy=y1+uΔy,其中,0≤u≤1。
首先按参数化形式写出裁剪条件:
xminymin≤x1+uΔx≤xmax≤y1+uΔy≤ymax
整理得到:upk≤qk,其中,k=1,2,3,4。其中
p1p2p3p4=−Δx,=Δx,=−Δy,=Δy,q1q2q3q4=x1−xmin=xmax−x1=y1−ymin=ymax−y1
算法的基本思想是从 A,B 和 P0 三点中找出最靠近 P1 的点,图中要找的点是 P0。从 C,D 和 P1 中找出最靠近 P0 的点,图中要找的点是 C 点。那么 P0C 就是 P0P1 线段上的可见部分。
- 任何平行于裁剪边界之一的直线 pk=0,其中 k 对应于裁剪边界(k=1,2,3,4 对应于左、右、下、上边界),
- 如果 qk<0,则线段完全在边界外,舍弃该线段。
- 如果 qk≥0,则该线段平行于裁剪边界并且在窗口内。
- 当 pk<0,线段从裁剪边界延长线的外部延伸到内部。
- 当 pk>0 时,线段从裁剪边界延长线的内部延伸到外部。
- 当 pk=0 时,可以计算出线段与边界 k 的延长线的交点的 u 值:u=pkqk。
梁友栋 - Barsky 裁剪算法在寻找线段可见部分(如果有可见线段)时可以归结为四个步骤:
- 如果对所有的 k 都有 pk=0 和 qk<0,删除线段并结束。否则继续执行下一步。
- 对所有 pk<0 的 k,计算 rk=pkqk,将 0 和各个 rk 值之中的最大值赋给 u1。
- 对所有 pk>0 的 k,计算 rk=pkqk,将 1 和各个 rk 值之中的最小值赋给 u2。
- 如果 u1>u2,则线段完全落在裁剪窗口之外,不可见,可直接舍弃。否则,裁剪后线段的端点由参数 u 的两个值 u1、u2 计算出来。
# 多边形的裁剪
# Sutherland-Hodgman 算法
思路:将多边形的各边先相对于窗口的某一条边界进行裁剪,然后将裁剪结果再与另一条边界进行裁剪,如此重复多次,便可得到最终结果。
实现方法:
- 设置两个表:
- 输入顶点表(向量):用于存放被裁剪多边形的顶点 p1-pm。
- 输出顶点表(线性链表):用于存放裁剪过程中及结果的顶点 q1-qn。
- 输入顶点表中各顶点要求按一定顺序排列,一般可采用顺时针或逆时针方向。
- 相对于裁剪窗口的各条边界,按顶点表中的顺序,逐边进行裁剪。
具体操作:
- 若 Pi 位于边界线的可见一侧,则 Pi 送输出顶点表。
- 若 Pi 位于边界线的不可见一侧,则将其舍弃。
- 除第一个顶点外,还要检查每一个 Pi 和前一顶点 Pi−1 是否位于窗口边界的同一侧,若不在同一侧,则需计算出交点送输出顶点表。
- 最后一个顶点 Pn 则还要与 P1 一起进行同样的检查。
沿着多边形依次处理顶点会遇到四种情况。在将相邻的一对多边形顶点传到窗口的边界裁剪程序时,可以进行下列测试:
- 如果第一点在窗口边界外而第二点在窗口边界内,则将多边形的这条边与窗口边界的交点和第二点加到输出顶点表中。
- 如果两顶点都在窗口边界内,则只有第二点加到输出顶点表中。
- 如果第一点在窗口边界内而第二点在窗口外,则只有与窗口边界的交点加到输出顶点表中。
- 如果两个点都在窗口边界外,那么输出顶点表中不增加任何点。
# 真实感图形的生成技术
# 线消隐
线消隐是一种物空间消隐技术:在物体空间中,对物体的各个线段进行消隐处理,以确定哪些线段是可见的,哪些是不可见的。
| for (每个多边形) { |
| for (每个多边形的边) { |
| if (边在窗口内) { |
| if (边不在任何多边形的背面) { |
| 画线 |
| } |
| } |
| } |
| } |
# 凸多面体的隐藏线 / 面消隐
设平面方程为 aix+biy+ciz+di=0,变换方程的系数,使 (ai,bi,ci) 指向物体外部。
将视线向量 Vview 与面法向量 Ni 的点积 Pi=Vview⋅Ni 代入平面方程,若 Pi>0,则该面是背面。
# 凹多面体的隐藏线 / 面消隐
假设凹多面体用它的表面多边形的集合表示,消除隐藏线的问题可归结为:对于一条空间线段 P1P2 和一个多边形 a,判断线段是否被多边形遮挡。如果被遮挡,求出隐藏部分。
# 直线与多边形的遮挡判断
-
若线段的两端点与视点在给定平面的同一侧,则线段位于给定平面的前面,是可见的,转步骤 9。
-
求线段与相应的无限平面的交。若无交点,转步骤 4;否则,转步骤 3。
-
交点将该线段分成两段,与视点同侧的一段是可见的,它没有被遮挡;另一段的可见性还不能确定,转步骤 4,继续测试。
-
若线段的投影与给定平面的投影的包围盒不相交,则线段可见,它不被该平面遮挡,转步骤 9。
-
求所剩线段的投影与平面边界投影的所有交点。
-
将线段的投影在交点处依次分成若干段,根据交点在原直线参数方程中的参数值的大小对线段进行排序。
-
求出第一投影线段的中点,若第一段的中点在多边形的投影内,则相应的线段被遮挡,否则不被遮挡。
-
其它各段的遮挡关系可依次按 "遮挡 / 可见" 交替地取值。例如:
- 如果第一段被遮挡,则后面线段的可见性依次为 "可见,遮挡,可见,..."
- 若第一段可见,则后面的线段依次为 "遮挡,可见,遮挡,..."
-
结束。
# 面消隐
面消隐是一种像素空间消隐技术:在屏幕空间中,对屏幕上的各个像素进行消隐处理,以确定哪些像素是可见的,哪些是不可见的。
| for (每个像素) { |
| 确定像素对应的距离视点最近的物体 |
| 以该物体的颜色填充像素 |
| } |
# 深度缓存法(Z-Buffer)
Z-Buffer 算法的基本思想是:在屏幕上为每个像素点建立一个深度缓存,用来存放该像素点对应的物体表面的深度值。在绘制物体时,对于每个像素点,首先计算该像素点对应的物体表面的深度值,然后将该深度值与深度缓存中的值进行比较,若该深度值小于深度缓存中的值,则将该深度值存入深度缓存,并将该像素点的颜色值存入颜色缓存;否则,不更新深度缓存和颜色缓存。
步骤
- 初始化 ZB 和 CB,使得 ZB(i,j)=∞,CB(i,j)=0。
- 对于每个多边形,计算其在屏幕上的投影,对于每个像素 (i,j),计算其对应的深度值 Z(i,j)。
- 如果 Z(i,j)<ZB(i,j),则更新 ZB(i,j)=Z(i,j) 和 CB(i,j)。
- 最后,将 CB(i,j) 的值填充到屏幕上的像素 (i,j)。
- 重复步骤 2-4,直到所有多边形都被处理。
- 判断点 (i,j) 是否在多边形 Fk 在投影面上的投影内:采用包含测试(射线法)
- 计算多边形Fk 在点(i,j) 处的深度值 Zi,j:若多边形Fk 的平面方程为:ax+by+cz+d=0,则有:Zi,j=−caxi+byj+d,c=0。
相邻点(i+1,j) 处Zi+1,j=Z−CA
# 光照模型
# 光源特性
- 光的色彩
- 光的强度:0.30∗R+0.59∗G+0.11∗B
- 光的方向
# 物体的表面特性
- 反射系数
- 透射系数:0≤T≤1
- 表面方向
# 光照模型的计算
# 漫射光照模型
# 漫反射光照模型
该公式表示区域内的漫反射光强,其计算方式为:
EPd=RPId
- RP 表示物体在点 P 的反射系数
- Id 表示漫反射光强
# 直射光照模型
该公式表示直接光照成分,其计算方式为:
EPs=RPcosiIPs+WP(i)cosnsIPs
- cosi 为光线入射与法线方向的夹角余弦
- WP(i) 为与高光相关的权重函数
- n (会聚系数) 用于控制高光的聚散程度
- IPs 表示光源直射光强
# 透射光照模型
EPt=TpIPb
- EPt:物体表面 P 点处透射出的光强
- TP:P 点的透射系数(取值范围为 0~1)
- IPb:到达 P 点背后的光强
# 合并光照模型
EP=EPd+EPs+EPt