C++模拟OpenGL库——图形光栅化理论及实现(二):Brensenham直线算法

这篇博客详细介绍了Bresenham算法在实现像素级别直线绘制中的应用,包括如何处理浮点数问题和步进策略。内容涵盖从基本的直线绘制到画一圈线,再到实现彩色线条的线性插值。通过示例代码展示了C++实现的细节,并提供了测试用例以验证算法的正确性。
摘要由CSDN通过智能技术生成

目录

理论

实现:画一条线

实现:画一圈线

实现——画一圈彩色线:


理论

已知两点P1 P2,来推导直线算法

如:y=kx+b

但其中会产生浮点数,我们知道在像素级别的绘制中是不能出现浮点数的,因此我们需要想办法消灭浮点数。

先说结论:Brensenham直线算法就是不断判断下图中d1和d2的距离,取其中的最小值来判断要绘制哪一个像素。

下面进行一些数学的推导:

由图中:

d_1=k(x_i+1)+b-y_i

d_2=y_i+1-(k(x_i+1)+b)

\therefore d_1-d_2=2k(x_i+1)-2y_i+2b-1

\because k=dy/dx

\because dy=\frac{P_2.y-P_1.y}{P_2.x-P_1.x}(其中产生了浮点数)

\therefore P_i=dx(d_1-d_2)=2dy\cdot x_i-2dx\cdot y_i+(2dy+2b\cdot dx-dx)

其中为了方便设(2dy+2b\cdot dx-dx)=c

P_{i+1}=2dy\cdot (x_i+1)-2dx\cdot (y_{i+1})+c

\therefore P_{i+1}-P_i=2dy-2dx(y_{i+1}-y_i)

考察第一个点:

P_1(x_1,kx_{1}+b)

带入P_i=dx(d_1-d_2)=2dy\cdot x_i-2dx\cdot y_i+(2dy+2b\cdot dx-dx)

得:P_1=2dy-dx

由此可以判断它的正负,若为负,说明d1-d2<0,即d1<d2,说明下面的点近一些,则选择下面的点进行像素着色。反之则是上面的像素点。

通过P_1可以迭代P_2

P_2=P_1+2dy-2dx(y_2-y_1)=P_1+2dy-2dx;{\color{Red} (P_1>0,y_2=y_1+1)}

P_2=P_1+2dy-2dx(y_2-y_1)=P_1+2dy; {\color{Red} (P_1<0,y_2=y_1)}

整体的思路,就是说通过P1这个点,来消除浮点数的出现,然后判断P1的正负,来选择下一个像素是上面的还是下面的像素(看上面的图)

总结:

初始:P_1=2\Delta y-\Delta x

P_i\geq =0(也就是d1>d2,选择上面的像素点):

  • {\color{Red} y_{i+1}=y_i+1}
  • x_{i+1}=x_i+1
  • P_{i+1}=P_i+2(\Delta y-\Delta x)

否则(也就是d1<d2,选择下面的像素点):

  • {\color{Red} y_{i+1}=y_i}
  • x_{i+1}=x_i+1
  • P_{i+1}=P_i+2\Delta y

--------------------------------------------以上是在斜率k<1的情况下-------------------------------------------------

如果k>1,则将dx与dy进行交换,用y进行”步进“

实现:画一条线

namespace GT {
	void GT::Canvas::drawLine(intV2 pt1, intV2 pt2, RGBA _color){
		int disX = abs(pt2.x - pt1.x);
		int disY = abs(pt2.y - pt1.y);

		int xNow = pt1.x;
		int yNow = pt2.y;

		int stepX = 0;
		int stepY = 0;

		//判断两个方向步进的正负
		stepX = pt1.x < pt2.x ? 1 : -1;
		stepY = pt1.y < pt2.y ? 1 : -1;

		//对比xy偏移量,决定步进方向选取x or y
		int sumStep = disX;
		bool useXStep = true;
		if (disX < disY) {
			sumStep = disY;
			useXStep = false;
			SWAP_INT(disX, disY);
		}

		//初始化P
		int p = 2 * disY - disX;
		for (int i = 0; i < sumStep; i++) {
			drawPoint(xNow, yNow, _color);
			if (p >= 0) {
				if (useXStep) {
					yNow += stepY;
				}
				else {
					xNow += stepX;
				}
				p = p - 2 * disX;
			}
			//步进主坐标
			if (useXStep) {
				xNow += stepX;
			}
			else {
				yNow += stepY;
			}
			p = p + 2 * disY;
		}
	}
}

测试:

void Render() {
    _canvas->clear();

    逐像素绘制
    //for (int i = 0; i < wWidth; i++) {
    //    for (int j = 0; j < wHeight; j++) {
    //        GT::RGBA _color(rand() % 255, rand() % 255, rand() % 255);
    //        _canvas->drawPoint(i, j, _color);
    //    }
    //}
    _canvas->drawLine(GT::intV2(100, 100), GT::intV2(150, 150),GT::RGBA(255,0,0));

    //在这里画到设备上,hMem相当于缓冲区
    BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}

实现:画一圈线

void Render() {
    _canvas->clear();
    
    GT::RGBA _color(rand() % 255, rand() % 255, 0);
    GT::intV2 pt1(100, 100);
    float r = 50;
    for (int i = 0; i < 360; i += 20) {
        float radian = DEG2RAD(i);
        int x = r * sin(radian) + pt1.x;
        int y = r * cos(radian) + pt1.y;
        GT::intV2 pt2(x, y);

        _canvas->drawLine(pt1, pt2, _color);
    }

    //在这里画到设备上,hMem相当于缓冲区
    BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}

实现——画一圈彩色线:

		//=========画线算法Brensenhem===
		void drawLine(Point pt1,Point pt2);

		//=========线性插值Lerp=========
		inline RGBA colorLerp(RGBA _color1, RGBA _color2, float _scale) {
			RGBA _color;
			_color.m_r = _color.m_r + (float)(_color2.m_r - _color1.m_r) * _scale;
			_color.m_g = _color.m_g + (float)(_color2.m_g - _color1.m_g) * _scale;
			_color.m_b = _color.m_b + (float)(_color2.m_b - _color1.m_b) * _scale;
			_color.m_a = _color.m_a + (float)(_color2.m_a - _color1.m_a) * _scale;
			return _color;
		}
#include "Canvas.h"
#include <math.h>
#include "GTMATH.hpp"

namespace GT {
	void GT::Canvas::drawLine(Point pt1, Point pt2) {
		int disX = abs(pt2.m_x - pt1.m_x);
		int disY = abs(pt2.m_y - pt1.m_y);

		int xNow = pt1.m_x;
		int yNow = pt1.m_y;

		int stepX = 0;
		int stepY = 0;

		//判断两个方向步进的正负
		stepX = pt1.m_x < pt2.m_x ? 1 : -1;
		stepY = pt1.m_y < pt2.m_y ? 1 : -1;

		//对比xy偏移量,决定步进方向选取x or y
		int sumStep = disX;
		bool useXStep = true;
		if (disX < disY) {
			sumStep = disY;
			useXStep = false;
			SWAP_INT(disX, disY);
		}

		//初始化P
		int p = 2 * disY - disX;
		for (int i = 0; i < sumStep; i++) {
			RGBA _color;
			float _scale = 0;
			if (useXStep) {
				_scale = (float)(xNow - pt1.m_x) / (float)(pt2.m_x - pt1.m_x);
			}
			else {
				_scale = (float)(yNow - pt1.m_y) / (float)(pt2.m_y - pt1.m_y);
			}
			_color = colorLerp(pt1.m_color, pt2.m_color, _scale);
			drawPoint(xNow, yNow, _color);

			if (p >= 0) {
				if (useXStep) {
					yNow += stepY;
				}
				else {
					xNow += stepX;
				}
				p = p - 2 * disX;
			}
			//步进主坐标
			if (useXStep) {
				xNow += stepX;
			}
			else {
				yNow += stepY;
			}
			p = p + 2 * disY;
		}
	}
}


void Render() {
    _canvas->clear();
    
    GT::Point pt1(100, 100, GT::RGBA(255, 0, 0));
    float r = 100;
    for (int i = 0; i < 360; i += 20) {
        float radian = DEG2RAD(i);
        int x = r * sin(radian) + pt1.m_x;
        int y = r * cos(radian) + pt1.m_y;
        GT::Point pt2(x, y, GT::RGBA(0, 255, 0));

        _canvas->drawLine(pt1, pt2);
    }

    //在这里画到设备上,hMem相当于缓冲区
    BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值