参考:https://www.bilibili.com/video/BV1Kr4y1Q7kq?p=10
RGB颜色定义
struct RGB24
{
public:
RGB24():red(0), green(0), blue(0) {};
RGB24(double red,double green,double blue):red(red), green(green), blue(blue){};
friend RGB24 operator + (const RGB24& clr0, const RGB24& clr1)
{
return RGB24(clr0.red + clr1.red, clr0.green + clr1.green, clr0.blue + clr1.blue);
};
friend RGB24 operator * (const RGB24& clr, double scalar)
{
return RGB24(clr.red * scalar, clr.green * scalar, clr.blue * scalar);
}
friend RGB24 operator * ( double scalar, const RGB24& clr)
{
return clr * scalar;
}
public:
double red;
double green;
double blue;
};
颜色线性插值
RGB24 Interp(double m,double m0,double m1,RGB24 c0,RGB24 c1)
{
return (m1 - m) / (m1 - m0) * c0 + (m - m0) / (m1 - m0) * c1;
}
扫描填充点
for (int y = P[0].y; y < P[2].y; y++)
{
int n = y - P[0].y;
for (int x = _pSpanLeft[n].x; x < _pSpanRight[n].x; x++)
{
RGB24 color;
color = Interp(x, _pSpanLeft[n].x, _pSpanRight[n].x, _pSpanLeft[n].c, _pSpanRight[n].c);
SetPixel(hdc, x, y, RGB(color.red * 255, color.green * 255, color.blue * 255));
}
}
完成代码
// 10-光滑着色的三角形填充算法
// 参考 https://www.bilibili.com/video/BV1Kr4y1Q7kq?p=10
#define UNICODE
#include <Windows.h>
#define WINDOW_TEXT L"10-光滑着色的三角形填充算法"
#define ROUND(d) int(d+0.5) // 四舍五入
#define LEFT true
#define RIGHT false
struct RGB24 // RGB颜色
{
public:
RGB24():red(0), green(0), blue(0) {};
RGB24(double red,double green,double blue):red(red), green(green), blue(blue){};
friend RGB24 operator + (const RGB24& clr0, const RGB24& clr1)
{
return RGB24(clr0.red + clr1.red, clr0.green + clr1.green, clr0.blue + clr1.blue);
};
friend RGB24 operator * (const RGB24& clr, double scalar)
{
return RGB24(clr.red * scalar, clr.green * scalar, clr.blue * scalar);
}
friend RGB24 operator * ( double scalar, const RGB24& clr)
{
return clr * scalar;
}
public:
double red;
double green;
double blue;
};
struct Point2 // 带颜色二维点
{
Point2() :x(0), y(0) {}
Point2(double x, double y) :x(x), y(y) {}
Point2(double x, double y, RGB24 c) :x(x), y(y), c(c) {}
double x;
double y;
RGB24 c;
};
class Triangle // 填充三角形
{
public:
Triangle() {};
Triangle(Point2 p0, Point2 p1, Point2 p2)
{
P[0] = p0;
P[1] = p1;
P[2] = p2;
}
~Triangle() {};
RGB24 Interp(double m,double m0,double m1,RGB24 c0,RGB24 c1) // 颜色线性插值
{
return (m1 - m) / (m1 - m0) * c0 + (m - m0) / (m1 - m0) * c1;
}
void Fill(HDC hdc)
{
// 顶点按照y坐标 由小到大排序
SortPoint();
int nTotalScanLine = P[2].y - P[0].y + 1; // 扫描线数量
// 定义span起点与终点数字
_pSpanLeft = new Point2[nTotalScanLine];
_pSpanRight = new Point2[nTotalScanLine];
// 判断三角形与P0 P1的位置关系,0-1-2 左手系
int nDeltz = (P[2].x - P[0].x) * (P[1].y - P[0].y) - (P[2].y - P[0].y) * (P[1].x - P[0].x); // 叉积
if (nDeltz > 0) // 左三角形
{
_nIndex = 0;
EdgeFlag(P[0], P[1], LEFT);
EdgeFlag(P[1], P[2], LEFT);
_nIndex = 0;
EdgeFlag(P[0], P[2], RIGHT);
}
else // 右三角形
{
_nIndex = 0;
EdgeFlag(P[0], P[2], LEFT);
_nIndex = 0;
EdgeFlag(P[0], P[1], RIGHT);
EdgeFlag(P[1], P[2], RIGHT);
}
for (int y = P[0].y; y < P[2].y; y++)
{
int n = y - P[0].y;
for (int x = _pSpanLeft[n].x; x < _pSpanRight[n].x; x++)
{
RGB24 color;
color = Interp(x, _pSpanLeft[n].x, _pSpanRight[n].x, _pSpanLeft[n].c, _pSpanRight[n].c);
SetPixel(hdc, x, y, RGB(color.red * 255, color.green * 255, color.blue * 255));
}
}
if (_pSpanLeft)
{
delete[] _pSpanLeft;
_pSpanLeft = NULL;
}
if (_pSpanRight)
{
delete[] _pSpanRight;
_pSpanRight = NULL;
}
}
private:
void EdgeFlag(Point2 pStart, Point2 pEnd, bool bFeature) // 边标记算法
{
int dx = pEnd.x - pStart.x;
int dy = pEnd.y - pStart.y;
double m = double(dx) / dy;
double x = pStart.x;
for (int y = pStart.y; y < pEnd.y; y++)
{
RGB24 color = Interp(y, pStart.y,pEnd.y,pStart.c,pEnd.c);
if (bFeature)
{
_pSpanLeft[_nIndex++] = Point2(ROUND(x), y, color);
}
else
{
_pSpanRight[_nIndex++] = Point2(ROUND(x), y, color);
}
x += m;
}
}
void SortPoint() // 顶点排序
{
Point2 pt; // 排序后 P[0].y < P[1].y < P[2].y
if (P[0].y > P[1].y)
{
pt = P[0];
P[0] = P[1];
P[1] = pt;
}
if (P[0].y > P[2].y)
{
pt = P[0];
P[0] = P[2];
P[2] = pt;
}
if (P[1].y > P[2].y)
{
pt = P[1];
P[1] = P[2];
P[2] = pt;
}
}
private:
Point2 P[3]; // 顶点
Point2* _pSpanLeft; // 跨度起点
Point2* _pSpanRight; // 跨度终点
int _nIndex; // 扫描线索引
};
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rc;
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, rc.right, rc.bottom, NULL);
SetViewportExtEx(hdc, rc.right, -rc.bottom, NULL);
SetViewportOrgEx(hdc, rc.right / 2, rc.bottom / 2, NULL);
{
Point2 p1(0, 250, RGB24(1, 0, 0));
Point2 p2(-400, -250, RGB24(0, 1, 0));
Point2 p3(400, -250, RGB24(0, 0, 1));
Triangle trangile(p1, p2, p3);
trangile.Fill(hdc);
}
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"CAG";
WNDCLASS wc = { };
wc.style = CS_HREDRAW | CS_VREDRAW; // 重新绘制整个工作区
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
WINDOW_TEXT, // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}