当前位置: 首页 > ds >正文

山东大学计算机图形学期末复习10——CG12下

CG12下

Sutherland-Hodgman Clipping

Sutherland–Hodgman 算法介绍(简单易懂)_sutherland-hodgman-CSDN博客

算法思想(核心思想)

逐条边裁剪:

  1. 裁剪多边形的每一条边(从顶点A到顶点B)
  2. 针对裁剪窗口的每一条边,遍历原多边形的每一条边,判断:
    • 起点和终点是否在窗口内
    • 如果有交点就加进去
    • 如果顶点在窗口内就加进去
  3. 每处理一条裁剪边(如左边、右边、上边、下边),就更新一次多边形。
  4. 最终得到一个新多边形,完全处于裁剪区域内。

可视化概念图(逻辑)

多边形在裁剪窗口中可能:

  • 完全在内部 → 原样保留
  • 完全在外部 → 被完全裁掉
  • 部分在内部 → 生成一个新的多边形(用交点替换被切掉的部分)

我们将原多边形按顺序绕一圈处理,然后根据裁剪边来判断每一条边的两个顶点是否在边的内外。


四种情况(Sutherland–Hodgman 核心规则)

设一条多边形边为 from → to \text{from} \to \text{to} fromto

fromto动作
inin只输出 to
inout输出与裁剪边的交点
outin输出交点to
outout什么也不输出
裁剪流程(以矩形窗口为例)
假设裁剪窗口是矩形:
  • 左边界 x = x min x = x_{\text{min}} x=xmin
  • 右边界 x = x max x = x_{\text{max}} x=xmax
  • 下边界 y = y min y = y_{\text{min}} y=ymin
  • 上边界 y = y max y = y_{\text{max}} y=ymax

裁剪顺序通常是:


左 → 右 → 下 → 上

对于每一条边,我们将原多边形顶点当作输入列表,裁剪一次输出新的顶点列表,传给下一步使用。

光栅化与DDA算法

一、什么是光栅化(Rasterization)?
基本定义:

光栅化是把几何图形(如线段、三角形等)转换为**像素点(像素/片段)**的过程,是从“数学描述”到“图像像素”的关键步骤。

举例:一条直线 y = x + 1,在数学上是连续的,我们要决定哪些像素该被“点亮”来表示这条直线。

输出结果:

光栅化会输出许多片段(fragment),每个片段包含:

  • 像素位置 (x, y)
  • 插值得到的颜色、深度、纹理坐标等
二、传统光栅设备的工作方式(Raster Display)
显示器原理(以 CRT 显示器为例):
  • 电子枪依次扫描屏幕每一行(光栅扫描),从左到右、从上到下。
  • 隔行扫描:先扫描奇数行,再扫描偶数行,减少闪烁。
  • 图像显示原理就是——哪个像素该亮、亮多亮?
帧缓冲区(Frame Buffer):
  • 是内存中的一个二维数组,每个元素存储一个像素的信息:
    • 颜色(RGB)
    • 深度值(z-buffer)
  • 显卡会把帧缓冲区的内容通过 DAC(数字-模拟转换器) 传给显示器。
三、光栅化中的扫描转换(Scan Conversion)
目标:

将“线段”转化为一串最接近的像素点。

也叫“线段光栅化”,用于绘制几何图元在像素网格上的近似表示。

基本假设:
  • 线段起点/终点已转换为窗口坐标系中的整数坐标。
  • 使用一个函数:write_pixel(x, y, color) 将像素(x, y)设为某种颜色。
四、DDA算法(Digital Differential Analyzer)
基本思路:

从起点 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) 沿 x 轴每次走一步,用浮点步长更新 y。

算法步骤:
  1. 计算斜率 m = Δ y / Δ x m = \Delta y / \Delta x m=Δyx

  2. 对每个 x:
    y = y + m y = y + m y=y+m
    画点: writepixel(x, round(y)) \text{画点:} \texttt{writepixel(x, round(y))} 画点:writepixel(x, round(y))

特点:
  • 简单易实现
  • 每次只需一次加法和一次四舍五入
五、DDA 的问题与对称性优化
问题:

当线段很陡峭(|m| > 1)时:

  • 每次 x 只加 1,但 y 要跳很多像素
  • 可能会漏掉中间像素,造成“断线”现象
解决办法:利用对称性

对陡峭线段,交换 x 和 y,改为沿 y 方向遍历:

  • 如果 |m| ≤ 1:x += 1, y += m
  • 如果 |m| > 1:y += 1, x += 1/m(x 和 y 角色互换)

画点时注意也要交换:write_pixel(round(x), y)

Bresenham 算法

一、Bresenham 算法的动机与优势
为什么需要 Bresenham?
  • DDA(Digital Differential Analyzer) 虽然原理简单,但需要:
    • 浮点加法
    • 浮点取整
  • 在早期计算机或图形芯片中,这些操作代价昂贵。
Bresenham 的目标:
  • 完全使用整数运算
  • 每个像素只需一次加法 + 一次比较。
  • 尤其适合低成本硬件和高性能绘图。
二、基本假设与适用范围

为便于分析,先限定一种情况:

  • 线段起点 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)、终点 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)
  • 只考虑:斜率 0 ≤ m ≤ 1 0 \leq m \leq 1 0m1(其他情况通过对称处理)
  • 沿 x 轴递增,逐像素绘制

其他斜率如 m > 1 m > 1 m>1 m < 0 m < 0 m<0 等可通过坐标交换或反转处理。

三、像素选择策略:两种候选像素

在绘图中,当前像素为 ( x k , y k ) (x_k, y_k) (xk,yk),下一个像素只可能是:

  • 右边像素 ( x k + 1 , y k ) (x_k + 1, y_k) (xk+1,yk)
  • 右上像素 ( x k + 1 , y k + 1 ) (x_k + 1, y_k + 1) (xk+1,yk+1)

要选择哪个像素?靠近线段的那个。

在这里插入图片描述

四、引入决策变量(Decision Variable)
核心思想:

用一个变量 d 判断哪一个候选像素更接近真实的线段。

几何解释:
  • 设当前像素为 ( i , j ) (i, j) (i,j),像素中心是 ( i + 0.5 , j + 0.5 ) (i+0.5, j+0.5) (i+0.5,j+0.5)
  • 计算线段在 x = i + 1 x = i+1 x=i+1 处的真实 y 值 y line y_{\text{line}} yline
  • y line < j + 0.5 y_{\text{line}} < j+0.5 yline<j+0.5,靠下像素
  • y line ≥ j + 0.5 y_{\text{line}} \geq j+0.5 ylinej+0.5,靠上像素
构造决策变量 d:

定义:
d = 2 Δ y ⋅ x − 2 Δ x ⋅ y + 常数 d = 2\Delta y \cdot x - 2\Delta x \cdot y + \text{常数} d=yxxy+常数
但我们不每次都重新算,而是用**增量更新(incremental update)**来高效计算。


五、Bresenham 核心算法逻辑

设:

  • Δ x = x 1 − x 0 \Delta x = x_1 - x_0 Δx=x1x0
  • Δ y = y 1 − y 0 \Delta y = y_1 - y_0 Δy=y1y0
  • 初始点为 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)
初始决策变量(d):

d 0 = 2 Δ y − Δ x d_0 = 2\Delta y - \Delta x d0=yΔx

每次迭代:
  • 如果 d < 0 d < 0 d<0
    • 选择下像素 ( x + 1 , y ) (x+1, y) (x+1,y)
    • 更新: d = d + 2 Δ y d = d + 2\Delta y d=d+y
  • 如果 d ≥ 0 d \geq 0 d0
    • 选择上像素 ( x + 1 , y + 1 ) (x+1, y+1) (x+1,y+1)
    • 更新: d = d + 2 ( Δ y − Δ x ) d = d + 2(\Delta y - \Delta x) d=d+2(ΔyΔx)
六、完整伪代码
void drawLine(int x0, int y0, int x1, int y1) {int dx = x1 - x0;int dy = y1 - y0;int d = 2*dy - dx;int y = y0;for (int x = x0; x <= x1; x++) {write_pixel(x, y);  // 画当前像素if (d > 0) {y += 1;d += 2*(dy - dx);} else {d += 2*dy;}}
}

推导 Bresenham 算法中的 决策变量的构造、更新公式及增量更新方式

背景:我们要解决的问题

我们希望在屏幕上绘制一条线段,从 ( x 0 , y 0 ) (x_0, y_0) (x0,y0) ( x 1 , y 1 ) (x_1, y_1) (x1,y1),其中斜率满足 0 ≤ m ≤ 1 0 \leq m \leq 1 0m1。这意味着:

  • 主方向是 x 增加
  • 对每个 x k x_k xk,我们希望确定对应的最合适的 像素 y 坐标

目标是用 整数加法和判断,替代 DDA 中的 浮点加法和取整

核心思想:每步选择两个像素中的一个

设当前绘制像素为 ( x k , y k ) (x_k, y_k) (xk,yk),下一个像素只能是:

  1. 右边像素 ( x k + 1 , y k ) (x_k + 1, y_k) (xk+1,yk)
  2. 右上像素 ( x k + 1 , y k + 1 ) (x_k + 1, y_k + 1) (xk+1,yk+1)

我们要判断线段在 x = x k + 1 x = x_k + 1 x=xk+1 处更靠近哪一个像素 —— 这就需要构造一个决策依据,也就是所谓的决策变量 d k d_k dk

第一步:直线的真实位置

假设直线是:
y = m x + b y = mx + b y=mx+b
那么在 x = x k + 1 x = x_k + 1 x=xk+1 处,真实的 y 值为:
y line = m ( x k + 1 ) + b y_{\text{line}} = m(x_k + 1) + b yline=m(xk+1)+b

第二步:构造一个“判断”变量

我们想知道:

  • y line y_{\text{line}} yline 更接近哪个像素?是 y k y_k yk 还是 y k + 1 y_k + 1 yk+1

设当前像素中心为 ( x k + 1 , y k + 0.5 ) (x_k + 1, y_k + 0.5) (xk+1,yk+0.5)

于是,我们定义一个变量:
δ = y line − ( y k + 0.5 ) \delta = y_{\text{line}} - (y_k + 0.5) δ=yline(yk+0.5)

  • 如果 δ < 0 \delta < 0 δ<0:直线在像素中心下方 → 选 右边像素
  • 如果 δ > 0 \delta > 0 δ>0:直线在像素中心上方 → 选 右上像素

但是这个 δ \delta δ 是浮点数!我们不能直接用 —— 所以我们要构造一个整数形式的决策变量 d k d_k dk,它的符号等价于 δ \delta δ

第三步:放大消除小数,构造整数决策变量

δ \delta δ 乘以 2 Δ x 2\Delta x x 得到整数形式:
d k = 2 Δ y ( x k + 1 ) − 2 Δ x ( y k + 0.5 ) d_k = 2\Delta y (x_k + 1) - 2\Delta x (y_k + 0.5) dk=y(xk+1)x(yk+0.5)
整理:
d k = 2 Δ y ⋅ x k − 2 Δ x ⋅ y k + C d_k = 2\Delta y \cdot x_k - 2\Delta x \cdot y_k + C dk=yxkxyk+C
其中 C 是常数(来自 b、0.5 等项)

我们只关心的是 d_k 的正负号,所以 C 无关紧要,我们可以简化为:
d k = 某种形式的整数表达式 ∝ 是否选上像素 d_k = \text{某种形式的整数表达式} \propto \text{是否选上像素} dk=某种形式的整数表达式是否选上像素

第四步:递推式(增量公式)推导

为了避免每次都从头计算 d k d_k dk,我们希望递推计算 d k + 1 d_{k+1} dk+1

情况一:选择右边像素(不变 y)
  • 当前: ( x k , y k ) (x_k, y_k) (xk,yk),下一个: ( x k + 1 , y k ) (x_{k+1}, y_k) (xk+1,yk)
  • 决策变量更新公式:

d k + 1 = d k + 2 Δ y d_{k+1} = d_k + 2\Delta y dk+1=dk+y

情况二:选择右上像素(y 增 1)
  • 当前: ( x k , y k ) (x_k, y_k) (xk,yk),下一个: ( x k + 1 , y k + 1 ) (x_{k+1}, y_k + 1) (xk+1,yk+1)
  • 决策变量更新公式:

d k + 1 = d k + 2 Δ y − 2 Δ x d_{k+1} = d_k + 2\Delta y - 2\Delta x dk+1=dk+yx

统一更新逻辑(总结)

我们设置:
{ d = 2 Δ y − Δ x (初始值) 如果  d < 0 , 选右边像素 ⇒ d ← d + 2 Δ y 否则,选右上像素 ⇒ d ← d + 2 ( Δ y − Δ x ) \begin{cases} d = 2\Delta y - \Delta x \quad \text{(初始值)} \\ \text{如果 } d < 0, \text{选右边像素} \Rightarrow d \leftarrow d + 2\Delta y \\ \text{否则,选右上像素} \Rightarrow d \leftarrow d + 2(\Delta y - \Delta x) \end{cases} d=yΔx(初始值)如果 d<0,选右边像素dd+y否则,选右上像素dd+2(ΔyΔx)

伪代码实现(只用整数!)
void bresenham_line(int x0, int y0, int x1, int y1) {int dx = x1 - x0;int dy = y1 - y0;int d = 2 * dy - dx;  // 初始决策变量int y = y0;for (int x = x0; x <= x1; x++) {write_pixel(x, y);if (d < 0) {d += 2 * dy;} else {y += 1;d += 2 * (dy - dx);}}
}
http://www.xdnf.cn/news/6939.html

相关文章:

  • Redis设计与实现——分布式Redis
  • 共享内存【Linux操作系统】
  • Go语言语法---输入控制
  • Node.js 源码架构详解
  • [system-design] ByteByteGo_Note Summary
  • 如何开发专业小模型
  • 强化学习赋能医疗大模型:构建闭环检索-反馈-优化系统提升推理能力
  • 数据库实验报告 数据定义操作 3
  • 【leetcode】逐层探索:BFS求解最短路的原理与实践
  • 使用Python和Selenium打造一个全网页截图工具
  • CSS- 4.1 浮动(Float)
  • Echart地图数据源获取
  • hysAnalyser 从MPEG-TS导出ES功能说明
  • 【C++详解】string各种接口如何使用保姆级攻略
  • Kotlin变量与数据类型详解
  • C++ - 仿 RabbitMQ 实现消息队列(2)(Protobuf 和 Muduo 初识)
  • DeepSeek 赋能军事:重塑现代战争形态的科技密码
  • Florence2代码实战
  • 初识计算机网络。计算机网络基本概念,分类,性能指标
  • 终端和shell , 以及XShell 用ssh命令登陆主机的过程
  • Spring三级缓存的作用与原理详解
  • 内网环境下如何使用ntpdate实时同步时间
  • java+selenum专题(一)
  • Java 与 面向对象编程(OOP)
  • dify知识库支持图文回复实践
  • 【Win32 API】 lstrcpynA()
  • 浮动静态路由配置实验
  • 使用 Cookie 实现认证跳转功能
  • 用Python绘制梦幻星空
  • 5.9/Q1,GBD数据库最新文章解读