图形学(2)光栅图形学的直线绘制(上)
来源:互联网 发布:iphone4移动数据开关 编辑:程序博客网 时间:2024/05/17 00:58
本模块内容绝大部分是在慕课上看中国农业大学网客时的笔记,因此算作转载,在此鸣谢赵明、李振波两位老师,感谢他们录制该门课程供大家学习!
光栅图形学
为适应光栅显示器,需发展一套和他相适应的算法来处理、显示图形。光栅图形学算法大多数属于计算机图形的底层算法,虽然编程过程中我们一般不会亲自去写它,但它包含很多图形学的基本概念和思想,了解这些算法会对更高级、更深层次内容的理解有很大帮助,亦是本模块重要的第一课。直线段的扫描转换算法
计算机上不像数学,直线的显示是由有限个像素点来拟合直线,因此要使用一些算法来进行图形的显示。
最容易想到的直线绘制算法
首先需要知道这些离散的像素点的坐标
采用斜截式方程表示直线 y=kx+b,已知直线始末两点坐标,可得k和b,然后求x和y。
此时我们令x在x0~x1之间变化,求y的值。(像素坐标都是整数,所以y值要进行取整处理)
这里我们为了将数学上的点扫描成屏幕上的像素点使用了四舍五入法,即x、y各加0.5,然后取整。大概的代码如下:
public class TraditionalMethod extends JFrame{ private int x1,x2,y1,y2; private float k,b; public TraditionalMethod( int x1 , int y1 , int x2 , int y2 ){ super(); setTitle("最易想到的直线绘制算法"); setSize(800,600); setVisible(true); this.x1=x1; this.x2=x2; this.y1=y1; this.y2=y2; traditionalDrawLine(x1,y1,x2,y2); } private void traditionalDrawLine( int x1 , int y1 , int x2 , int y2 ){ if (x1==x2){ for (int i=y1;i<y2;i++){ drawPixel(x1,i); } }else { if (x1>x2){//保证x1对应的点在x2对应点的左边 int temp=x1; x1=x2; x2=temp; temp=y1; y1=y2; y2=temp; } k=(y2-y1)/(x2-x1); b=y1-k*x1; for (int i=x1;i<x2;i++){ int j=(int)(k*i+b+0.5); drawPixel(i,j); } } } private void drawPixel(int x1,int y1) { //暂时没找到Java中的点绘制方法,虽然可以用drawLine或drawOval等代替但懒得搞了 }}
直线是最基本的图形,一幅图往往要画成千上万条直线,因此直线算法的好坏会直接影响图形的质量和显示速度。上面的算法虽然直观,但效率实在感人,且许多细节未考虑。于是考虑是否能将乘法取消掉从而提高效率?答案当然是可以,所以有下面三种经典直线绘制算法之一——DDA(Digital Differential Analyzer,数值微分法)
三种经典直线绘制算法
1.数值微分法(DDA)
介绍DDA之前需要先引进图形学中一个重要的“增量思想”:
在直线中,x值每次运算固定+1,由于x,y的一次线性关系,所以有像y4=y3+k这样的递推关系,就可以取消掉相对加法慢一些的乘法运算,而用加法代替它达到相同效果。
运用了该思想的DDA算法步骤如下:
- 计算k值,并与1比较(*)
- 四舍五入由x算y
- 递推循环
public class DDADrawLine extends JFrame { private int x1,x2,y1,y2; private float k; public DDADrawLine( int x1 , int y1 , int x2 , int y2 ){ super(); setTitle("DDA直线绘制算法"); setSize(800,600); setVisible(true); this.x1=x1; this.x2=x2; this.y1=y1; this.y2=y2; DDA(x1,y1,x2,y2); } private void DDA( int x1 , int y1 , int x2 , int y2 ){ if (x1==x2){ for (int i=y1;i<y2;i++){ drawPixel(x1,i); } } else if (y1==y2){ //对于特殊斜率无需计算k值 for (int i=x1;i<x2;i++){ drawPixel(i,y1); } }else { if (x1>x2){ //令x1在x2左边 int temp=x1; x1=x2; x2=temp; temp=y1; y1=y2; y2=temp; } k=(y2-y1)/(x2-x1); if (k<1&&k>-1){ int j=y1; for (int i=x1;i<x2;i++){ drawPixel(i,j); j+=k; } }else { //按y步进,因y1和y2大小关系未知所以需要分别进行 k=1/k; if (k>0){ int i=x1; for (int j=y1;j<y2;j++) { drawPixel(i,j); i+=k; } }else { int i=x1; for (int j=y1;j>y2;j--){ drawPixel(i,j); i+=k; } } } } } private void drawPixel(int x1,int y1) { //暂时没找到Java中的点绘制方法,虽然可以用drawLine或drawOval等代替但懒得搞了 }}
其实,这样还不是最优,进一步改进的方向如下:
- 改进效率:将浮点运算改进为整数运算
- 修改直线方程类型-->中点画线法
2.中点画线法
中点画线法使用了直线的一般式方程F(x,y)=0或Ax+By+C=0,其中A=-△y,B=△x,C=-B△x此时,直线将坐标系分为3部分:直线上方(F>0),直线下方(F<0),直线(F=0)。我们每次在最大位移方向上走一步,在另一个方向上走还是不走取决于中间误差项的判断。比如0≤|k|≤1时,x++后,y++或者y不变要看与中点M的y值大小
判断方法:
将M代入F,(x=xi+1,y=yi+0.5)得到一个值d,看d与0的大小关系就能判断中点在直线的上面还是下面了
中点画线法的计算量:
目前情况,为得到d,需要2个乘法,4个加法
public class MidPoint extends JFrame { private int x1,x2,y1,y2,d,A,B,C; public MidPoint( int x1 , int y1 , int x2 , int y2 ){ super(); setTitle("Mid Point直线绘制算法"); setSize(800,600); setVisible(true); this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; this.A = y2 - y1; this.B = x1 - x2; this.C = x1*(y1 - y2) + y1*(x2 - x1); midPointDrawLine(x1 , y1 , x2 , y2 , A , B , C); //基于直线的一般方程Ax+By+C=0 } private void midPointDrawLine(int x1 , int y1 , int x2 , int y2 , int A , int B , int C){ if (abs(A) < abs(B)){ //判断按x还是y步进的依据,此处为按x步进 if (x1 > x2){ int temp = x1; x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; } int j = y1; if (y1 < y2){ //判断从x1递增还是从x2递减,递增情况: for (int i = x1 ; i < x2 ; i++){ d=2*A*i+B*(2*j+1)+2*C; //扩大2倍消去浮点数0.5后再与0比较 if (d >= 0){ j++; drawPixel(i,j); }else { drawPixel(i,j); } } }else { //递减情况: for (int i = x2 ; i > x1 ; i--){ d = 2*A*i + B*(2*j + 1) + 2*C; if (d >= 0){ j++; drawPixel(i,j); }else { drawPixel(i,j); } } } }else{ //按y步进情况 if (y1 >y2){ int temp = y1; y1 = y2; y2 = temp; temp = x1; x1 = x2; x2 = temp; } int i = x1; if (x1 < x2) { for (int j = y1; j < y2 ; j++){ d = 2*A*i + B*(2*j + 1) + 2*C; if (d >= 0){ i++; drawPixel(i,j); }else { drawPixel(i,j); } } }else { for (int j = y2 ; j > y1 ; j--){ d = 2*A*i + B*(2*j + 1) + 2*C; if (d < 0){ i++; drawPixel(i,j); }else { drawPixel(i,j); } } } } } private void drawPixel(int x,int y){ //暂时没找到Java中的点绘制方法,虽然可以用drawLine或drawOval等代替但懒得搞了 }}
优化:
类比前面,可以使用增量计算提高效率
推导d的递推公式如下图所示:
d<0时
d>=0时
步骤:
1)计算d的初始值F(x0+1,y=y0+0.5),有d0=Ax0+By0+C+A+0.5B=>d0=A+0.5B
2)d2=d1+A+B,d<0;d2=d1+A,d≥0……此时,效率至少与DDA算法一样高
**可以用2d0=2A+B,解决掉浮点数运算,全部转化为整数加法
该改进较为简单,偷个懒代码省略嘿嘿
- 图形学(2)光栅图形学的直线绘制(上)
- 图形学(3)光栅图形学的直线绘制(下)
- [图形学]光栅直线算法
- 光栅图形学算法(一)
- 光栅图形学——直线段的扫描转换算法
- 计算机图形学 学习笔记(一):光栅图形学算法:直线扫描算法(DDA,中点画线算法,Bresenham算法)
- 《计算机图形学》实验一:利用OpenGL实现直线光栅化的DDA算法
- 光栅图形学之直线段扫描算法(DDA)
- 光栅图形学之直线段扫描算法(中点画线法)
- 计算机图形学——绘制直线
- 计算机图形学—判断点在直线上的方法
- 控制台“图形学”(2)
- 计算机图形学 学习笔记(六):消隐算法:Z-buffer,区间扫描线,Warnock,光栅图形学小结
- 图形学(4)多边形的扫描转换(上)
- 计算机图形学:3种画直线算法(转)
- 计算机图形学之--直线生成算法(一)
- 计算机图形学-直线裁剪(Cohen-Sutherland编码裁剪算法)
- 真实感图形学算法(2)
- 电商营销方式抢购,秒杀Redis原子减decr方法作为剩余库存判断条件的实现方式(1)
- Java 8 之 行为参数化
- Scrapy 命令的使用之一:全局命令
- inner join,left join和right join 的区别
- (noip 2015 斗地主)<搜索+贪心>
- 图形学(2)光栅图形学的直线绘制(上)
- ES6:解构赋值
- 2018京东前端笔试题笔试题
- JAVAEE之web基础知识
- 494. Target Sum
- 非常详细的 Docker 学习笔记
- Spark transformation算子案例
- 透明Textbox的简单实现!
- HDU