软光栅算法和分型几何的实现

第一部分软光栅

(1)绘制线段
1 思路

image-20201114003525837

如上图所示,当画一条线时,首先计算增量dx,dy。如果dy<dx,则x方向增长快,每次必加1,而y看判别式。

取dx所在线(x轴平行线)和画的线所成角的角平分线,若新的y落在该平分线上面,则y递增,否则y不变。而角平分线方程为: d y = 1 2 d x dy=\frac{1}{2}dx dy=21dx,即平分斜率。即 d y − 1 2 d x > 0 ⟹ 2 d y − d x > 0 dy-\frac{1}{2}dx>0 \Longrightarrow 2dy-dx>0 dy21dx>02dydx>0时,y递增。此时下一个判别式的增量为 2 ( d y − d x ) 2(dy-dx) 2(dydx)。否则增量为 2 d y 2dy 2dy

为了将直线扩展到所有斜率使用,实现使用了动态增量。

function drawLine(p1, p2){
	var x1 = p1[0], y1 = p1[1];
	var x2 = p2[0], y2 = p2[1];
	/// 下方代码用以绘制线段

    //由(x1,y1)画到(x2,y2),设置绘制起始点
	var x = x1;
	var y = y1;
    //计算差值
	var dx = x2 - x1;      
	var dy = y2 - y1;
    //计算增量。如dx小于0,则x每次递增都是减1,反之加1.y同理
	var ix = (dx > 0) ? 1 : -1;
	var iy = (dy > 0) ? 1 : -1;
	dx = Math.abs(dx);
	dy = Math.abs(dy);
    //如果x比y增长的快
	if(dx > dy)
	{
        //初始化判别式
		var p = 2 * dy - dx;
		for(var i = 0; i <= dx; i++)
		{
			addPixel(x,y);
            //x增长的快,x每次都递增
			x += ix;
            //如果判别式大于0,y递增,p更新
			if(p >= 0)
			{
				y += iy;
				p += 2 * (dy - dx);
			}
			else
			{
				p += 2 * dy;
			}
		}
	}
    //y增长快的情况,x和y互换即可
	else
	{
		var p = 2 * dx - dy;
		for(var i = 0; i <= dy; i++)
		{
			addPixel(x,y);
			y += iy;
			if(p >= 0)
			{
				x += ix;
				p += 2 * (dx - dy);
			}
			else
			{
				p += 2 * dx;
			}
		}
	}
	
	/// 上方代码用以绘制线段
}
2重点难点

(1)实现全斜率直线绘制

(2)DDA浮点数远算到Bresenham整数远算的优化过程思考

3心得

掌握了DDA的基本思想和由其升级得到的Bresenman算法。

(2)画圆
1 思路

根据圆心在原点的圆的方程 R 2 = x 2 + y 2 R^2=x^2+y^2 R2=x2+y2和圆的性质得: F ( x , y ) = x 2 + y 2 − R 2 F(x,y)=x^2+y^2-R^2 F(x,y)=x2+y2R2

选取第二象限0-45度得圆弧作为绘制得基本圆弧,其他七个区域有基本圆弧对称得出。

在基本圆弧中,x得初始值为0,y得初始值为R。由于基本圆弧中,x的变化率大于y,令x每一步都递增1,而y由判别式判断是否递增。

考虑当前点 P ( x , y ) P(x,y) P(x,y),下一步递增有两个候选点 P b ( x + 1 , y − 1 ) P_b(x+1,y-1) Pb(x+1,y1), P u ( x + 1 , y ) P_u(x+1,y) Pu(x+1,y)。选取候选点的方式是判断两点的中点 P m ( x + 1 , y − 0.5 ) P_m(x+1,y-0.5) Pm(x+1,y0.5)是落在园内还是圆外。若落在圆内,说明 P u P_u Pu距离圆弧更接近,选择 P u P_u Pu,否则选择 P b P_b Pb

而判断方式由函数 F ( x , y ) F(x,y) F(x,y)判定。 F ( x , y ) < = 0 则 在 院 圆 内 或 圆 上 , F ( x , y ) > 0 则 在 圆 外 F(x,y)<=0则在院圆内或圆上,F(x,y)>0则在圆外 F(x,y)<=0,F(x,y)>0

若当前 F ( x , y ) < = 0 F(x,y)<=0 F(x,y)<=0,则当前的 F c u r r e n t ( x , y ) = ( x + 1 ) 2 + ( y − 0.5 ) 2 − R 2 F_{current}(x,y)=(x+1)^2+(y-0.5)^2-R^2 Fcurrent(x,y)=(x+1)2+(y0.5)2R2,下一个 F n e x t ( x , y ) = ( x + 1 + 1 ) 2 + ( y − 0.5 ) 2 − R 2 F_{next}(x,y)=(x+1+1)^2+(y-0.5)^2-R^2 Fnext(x,y)=(x+1+1)2+(y0.5)2R2。所以得到判别式的差值 Δ d = F n e x t ( x , y ) − F c u r r e n t ( x , y ) = 2 x + 3 \Delta d=F_{next}(x,y)-F_{current}(x,y)=2x+3 Δd=Fnext(x,y)Fcurrent(x,y)=2x+3

若当前 F ( x , y ) > 0 F(x,y)>0 F(x,y)>0,则当前的 F c u r r e n t ( x , y ) = ( x + 1 ) 2 + ( y − 0.5 ) 2 − R 2 F_{current}(x,y)=(x+1)^2+(y-0.5)^2-R^2 Fcurrent(x,y)=(x+1)2+(y0.5)2R2,下一个 F n e x t ( x , y ) = ( x + 1 + 1 ) 2 + ( y − 0.5 − 1 ) 2 − R 2 F_{next}(x,y)=(x+1+1)^2+(y-0.5-1)^2-R^2 Fnext(x,y)=(x+1+1)2+(y0.51)2R2。所以得到判别式的差值 Δ d = F n e x t ( x , y ) − F c u r r e n t ( x , y ) = 2 ( x − y ) + 5 \Delta d=F_{next}(x,y)-F_{current}(x,y)=2(x-y)+5 Δd=Fnext(x,y)Fcurrent(x,y)=2(xy)+5

而判别式的初值 d 0 = F ( 1 , R + ( R − 1 ) 2 ) = 1.25 − R d_0=F(1,\frac{R+(R-1)}{2})=1.25-R d0=F(1,2R+(R1))=1.25R

然后对判别式进行浮点数优化。由于判别式的差值为整数,所以 d 0 = D 0 − 0.25 = 1 − R d_0=D_0-0.25=1-R d0=D00.25=1R。而 0.25 ∈ ( 0 , 1 ) 0.25 \in (0,1) 0.25(0,1),直接舍去。

这样就计算出了基本圆弧的每一个点。然后对基本圆弧的点以圆心 ( c x , c y ) (cx,cy) (cx,cy)进行八个方向对称: ( c x ± x , c y ± y ) , ( c x ± y , c y ± x ) (cx \pm x,cy \pm y),(cx \pm y,cy \pm x) (cx±x,cy±y),(cx±y,cy±x)。画出圆。

	let d = 1-radius;//初始化差值
	let y = radius;//初始化y
	let xMax = radius/Math.sqrt(2);//计算x的右边界
	//x开始增量迭代
	for(let x = 0;x<=xMax;++x){
		
        //8次调用对应八个方向对称
		addPixel(centerx+x,centery+y);
		addPixel(centerx+x,centery-y);
		addPixel(centerx-x,centery+y);
		addPixel(centerx-x,centery-y);
		
		addPixel(centerx+y,centery+x);
		addPixel(centerx+y,centery-x);
		addPixel(centerx-y,centery+x);
		addPixel(centerx-y,centery-x);
		//判别式更新,x递增在for循环里
		if(d<=0){
			d += 2*x+3;
		}
		else{
			d += 2*(x-y)+5;
			--y;
		}
		
	}
2重点难点

(1)浮点数优化。

(2)判别式去浮点数的推导。

3心得

(1)对浮点数优化成整数运算有了一些启蒙性的感悟。

(2)明白了最终代码都是算法精简后的结果,研究算法时要先实现完整过程在优化。否则不知道那一步出错或中间过程不具有可调试性。降低效率。

(3)实践理解了Bressenham中点画圆法及其优化。

(3)绘制多边形
1 思路

(1)建立边表ET

将每一个边加入边表,获取y的最大最小值。

function initEdgeTable(points){
	let ET = {};
	let yMin = 1000000000;
	let yMax = 0;
	for(var index = 0;index<points.length - 1;++index){
		let result = addToET(points[index],points[index + 1],ET);//加入边表
		ET = result.ET;
	
		yMin = Math.min(yMin,result.yMin);
		yMax = Math.max(yMax,result.yMax);
	}
	
	let result = addToET(points[0],points[points.length - 1],ET);
	ET = result.ET;
	
	yMin = Math.min(yMin,result.yMin);
	yMax = Math.max(yMax,result.yMax);
	
	return {ET: ET, yMin: yMin, yMax: yMax};
}

一条边加入边表的方法:

function addToET(point1,point2,ET){
    //另point1始终未y较小的点
	if(point2[Loc.Y] < point1[Loc.Y])
		[point1,point2] = [point2,point1];
    //构造边节点,ivK为增量
	let edge = {
		x: point1[Loc.X],
		yMax: point2[Loc.Y],
		ivK: (point2[Loc.X]-point1[Loc.X])/(point2[Loc.Y] - point1[Loc.Y]),
	};
		
	if(ET[point1[Loc.Y]] == undefined){
		ET[point1[Loc.Y]] = [];
	}
	
	ET[point1[Loc.Y]].push(edge);
	
	return {ET: ET, yMin: point1[Loc.Y], yMax: point2[Loc.Y]};
}

(2)开始迭代

	let AET = [];
//从最小的y开始到最大的y
	for(let currentY = result.yMin;currentY<=result.yMax;++currentY){
        //遍历活动边表的所有边
		for(let index = 0;index < AET.length;++index){
            //如果扫描线已经在边的上面,把边删去
			if(currentY > AET[index].yMax){
				AET.splice(index,1);
				--index;
			}
            //否则这条边的x坐标增加一个增量
			else{
				AET[index].x += AET[index].ivK;
			}
		}
		//查看当前扫描线是否扫描到新的边
		let edges = ET[currentY];
		if(edges != undefined){
            //将新的边加入边表
			for(let index = 0;index < edges.length;++index){
				AET.push(edges[index]);
			}
		}
		
        //将互动边表根据x坐标排序,若x相等则更具增量排序
		AET.sort(function(left,right){
			if(left.x == right.x) return left.ivK - right.ivK;
			return left.x - right.x;
		});
		let isDraw = false;
        //遍历活动边表开始填充
		for(let index = 0;index < AET.length - 1;++index){
            //交替填充
			isDraw = !isDraw;
			if(!isDraw)continue;
			drawLine(
			[AET[index].x,currentY],
			[AET[index + 1].x,currentY]
			);
		}
	}
2重点难点

边表数据结构的建立

3心得

(1)实践了js的许多语法和debug手段,加强了js编码能力。

(2)学会了js实现链表,哈希表数据结构。

(3)实践理解了有序边表法。理解了先前有些疑惑的地方。

(4)绘制贝塞尔曲线
1 思路

通过三次贝塞尔多项式:image-20201114001026575

对0到1进行采样计算出多个点的函数值,然后连接起来。

function drawCurve(p1, p2, p3, p4){
    //确定路径起点
	let lineP = [p1,];
    //在0到1中进行采样
    for (let dt=0;dt<=1;dt+=0.001)
    {
        //计算函数值
        let yt=1-dt;
		let c1=yt*yt;
		let c2=3*yt*dt;
		let dt3 = dt*dt*dt;
        let xt=p1[0]*c1*yt+p2[0]*c2*yt+p3[0]*c2*dt+p4[0]*dt3;
        yt=p1[1]*yt*c1+p2[1]*c2*yt+p3[1]*c2*dt+p4[1]*dt3;
        //将当前点加入路径
		lineP.push([xt,yt]);
    }
	//一次绘制这些路径
	for(let index = 0;index < lineP.length - 1;++index){
		drawLine(lineP[index],lineP[index+1]);
	}
}
2重点难点

(1)贝塞尔曲线公式的展开和推到

(2)采样率的选择

3心得

(1)了解了不同采样率选取对绘图的影响,这是一个性能和效果的权衡。不过就本题的例子而言,,最高的效果性能也远远足够。

(2)加深了对贝塞尔曲线的理解和使用

第二部分 gasket分形

1 思路

image-20201114001405923

如上图所示,每一次分型实际上就是绘制四个三角形。三个彩色一个白色。每个三角形有三个顶点。只要确定着12个顶点的位置和颜色即可。

//p1,p2,p3为大三角形的三个顶点
function recurse(p1,p2,p3,depth){
    //深度为0返回
    if(depth==0){
        return;
    }
    //分别计算三个顶点的中点
    let mid12 = [(p1[0]+p2[0])/2,(p1[1]+p2[1])/2];
    let mid23 = [(p2[0]+p3[0])/2,(p2[1]+p3[1])/2];
    let mid13 = [(p1[0]+p3[0])/2,(p1[1]+p3[1])/2];
    //为这4个三角形12个顶点指定坐标和颜色
    //p1,mid12,mid13为左下角三角形三个点
    //mid12,p2,mid23为上方角三角形三个点
    //mid13,mid23,p3为右下角三角形三个点
    //mid12,mid23,mid13为中间白色三角形三个点
    triangles.push(p1,mid12,mid13,mid12,p2,mid23,mid13,mid23,p3,mid12,mid23,mid13);
    //分别指定颜色
    triangleColors.push(red,green,blue,red,green,blue,red,green,blue,white,white,white);
	//分别将外围三个彩色三角形作为大三角形继续递归
    recurse(p1,mid12,mid13,depth-1);
    recurse(mid12,p2,mid23,depth-1);
    recurse(mid13,mid23,p3,depth-1);
}
2重点难点

(1)递归算法设计

(2)代码精简和可读性维护

3心得

(1)加强了自己递归算法的设计能力

(2)学会了webgl颜色的设置

(3)实践了分形几何的自相似性的递归实现。自相似性本身就是递归的特性。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值