参考 https://www.bilibili.com/video/BV1y3411S7e1
交叉条
zBuffer消隐
伪深度
重心坐标插值
zBuffer消隐藏
算法设计
完整代码
// 32-zBuffer算法
// 参考 https://www.bilibili.com/video/BV1y3411S7e1
#define UNICODE
#include <Windows.h>
#include <Windowsx.h>
#include <math.h>
#define WINDOW_TEXT L"32-zBuffer算法"
#define ROUND(d) int(floor(d)+0.5) // 四舍五入
#define PI 3.1415926
#define RGB2RGB(c) RGB(c.red*255,c.green*255,c.blue*255)
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 // 二维点
{
double x;
double y;
double w;
Point2() :x(0), y(0), w(1) {}
Point2(double x, double y) :x(x), y(y), w(1) {}
friend Point2 operator + (Point2 pt0, Point2 pt1)
{
return Point2(pt0.x + pt1.x, pt0.y + pt1.y);
}
friend Point2 operator * (Point2 pt, double n)
{
return Point2(pt.x * n, pt.y * n);
}
friend Point2 operator * (double n, Point2 pt)
{
return Point2(pt.x * n, pt.y * n);
}
};
struct Point3 :Point2 // 三维点
{
double z;
RGB24 c;
Point3() :z(0),c(0,0,0) {}
Point3(double x, double y, double z) :Point2(x, y), z(z), c(0, 0, 0) {}
friend Point3 operator + (Point3 pt0, Point3 pt1)
{
return Point3(pt0.x + pt1.x, pt0.y + pt1.y, pt0.z + pt1.z);
}
friend Point3 operator * (double scalar, const Point3& pt)
{
return Point3(pt.x * scalar, pt.y * scalar, pt.z * scalar);
}
friend Point3 operator * (const Point3& pt, double scalar)
{
return Point3(pt.x * scalar, pt.y * scalar, pt.z * scalar);
}
friend Point3 operator / (const Point3& pt, double scalar)
{
return Point3(pt.x / scalar, pt.y / scalar, pt.z / scalar);
}
double DotProduct(const Point3& p0, const Point3& p1) // 向量的点积
{
return(p0.x * p1.x + p0.y * p1.y + p0.z * p1.z);
}
Point3 CrossProduct(const Point3& v0, const Point3& v1) // 向量的叉积
{
return Point3(v0.y * v1.z - v0.z * v1.y,
v0.z * v1.x - v0.x * v1.z,
v0.x * v1.y - v0.y * v1.x);
}
};
class Transform3
{
public:
Point3* _P; // 顶点数组
int _ptNumber; // 顶点数量
double _M[4][4]; // 变换矩阵
public:
Transform3() :_P(NULL), _ptNumber(0)
{
memset(_M, 0, sizeof(_M));
};
~Transform3() {};
void MultiplyMatrix() //矩阵相乘
{
Point3* PTemp = new Point3[_ptNumber];
for (int i = 0; i < _ptNumber; i++)
{
PTemp[i] = _P[i];
}
for (int i = 0; i < _ptNumber; i++)
{
_P[i].x = _M[0][0] * PTemp[i].x + _M[0][1] * PTemp[i].y + _M[0][2] * PTemp[i].z + _M[3][0]; // * PTemp[i].w
_P[i].y = _M[1][0] * PTemp[i].x + _M[1][1] * PTemp[i].y + _M[1][2] * PTemp[i].z + _M[3][1]; // * PTemp[i].w
_P[i].z = _M[2][0] * PTemp[i].x + _M[2][1] * PTemp[i].y + _M[2][2] * PTemp[i].z + _M[3][2]; // * PTemp[i].w
_P[i].w = _M[3][0] * PTemp[i].x + _M[3][1] * PTemp[i].y + _M[3][2] * PTemp[i].z + _M[3][3]; // * PTemp[i].w
}
delete[]PTemp;
}
void SetMatrix(Point3* P, int ptNumber) //顶点数组初始化
{
_P = P;
_ptNumber = ptNumber;
}
void Identity()
{
_M[0][0] = 1.0, _M[0][1] = 0.0, _M[0][2] = 0.0, _M[0][3] = 0.0;
_M[1][0] = 0.0, _M[1][1] = 1.0, _M[1][2] = 0.0, _M[1][3] = 0.0;
_M[2][0] = 0.0, _M[2][1] = 0.0, _M[2][2] = 1.0, _M[2][3] = 0.0;
_M[3][0] = 0.0, _M[3][1] = 0.0, _M[3][2] = 0.0, _M[3][3] = 1.0;
}
void Translate(double tx, double ty, double tz)//平移变换
{
Identity();
_M[3][0] = tx;
_M[3][1] = ty;
_M[3][2] = tz;
MultiplyMatrix();
}
void Scale(double sx, double sy, double sz)//缩放变换
{
Identity();
_M[0][0] = sx;
_M[1][1] = sy;
_M[2][2] = sz;
MultiplyMatrix();
}
void RotateX(double beta)//绕X轴旋转变换
{
Identity();
beta = beta * PI / 180;
_M[1][1] = cos(beta), _M[1][2] = -sin(beta);
_M[2][1] = sin(beta), _M[2][2] = cos(beta);
MultiplyMatrix();
}
void RotateY(double beta)//绕Y轴旋转变换
{
Identity();
beta = beta * PI / 180;
_M[0][0] = cos(beta), _M[0][2] = sin(beta);
_M[2][0] = -sin(beta), _M[2][2] = cos(beta);
MultiplyMatrix();
}
void RotateZ(double beta)//绕Z轴旋转变换
{
Identity();
beta = beta * PI / 180;
_M[0][0] = cos(beta), _M[0][1] = -sin(beta);
_M[1][0] = sin(beta), _M[1][1] = cos(beta);
MultiplyMatrix();
}
};
class Projection // 透视投影
{
public:
Point3 Eye; // 视点
double R; // 视点球坐标
double Phi; //
double Theta; //
double D; //
double K[8]; // 透视常数
double Near; // 近剪切面
double Far; // 远剪切面
public:
Projection()
{
R = 1000;
D = 800;
Phi = 90;
Theta = 0; // 世界坐标z轴正上方 看向原点
Near = D;
Far = 2000;
InitialParameter();
}
~Projection() {}
void InitialParameter() // 初始化参数
{
K[0] = sin(PI * Theta / 180);
K[1] = sin(PI * Phi / 180);
K[2] = cos(PI * Theta / 180);
K[3] = cos(PI * Phi / 180);
K[4] = K[1] * K[2];
K[5] = K[0] * K[1];
K[6] = K[2] * K[3];
K[7] = K[0] * K[3];
Eye = Point3(R * K[5], R * K[5], R * K[4]); // 设置视点
}
void SetEye(double r, double phi, double theta) // 设置视点
{
R = r;
Phi = phi;
Theta = theta;
InitialParameter();
}
Point3 GetEye()
{
return Eye;
}
Point2 OrthographicProjection(Point3 worldPoint) // 正交投影
{
return Point2(worldPoint.x, worldPoint.y);
}
Point2 CavalierProjection(Point3 worldPoint) // 斜等侧投影
{
Point2 screenPoint; // 屏幕坐标
double cota = 1;
double beta = PI / 4;
screenPoint.x = worldPoint.x - worldPoint.z * cota * cos(beta);
screenPoint.y = worldPoint.y - worldPoint.z * cota * cos(beta);
return screenPoint;
}
Point2 CabinetProjection(Point3 worldPoint) // 斜二侧投影
{
Point2 screenPoint; // 屏幕坐标
double cota = 0.5;
double beta = PI / 4;
screenPoint.x = worldPoint.x - worldPoint.z * cota * cos(beta);
screenPoint.y = worldPoint.y - worldPoint.z * cota * cos(beta);
return screenPoint;
}
Point2 PerspectiveProjection2(Point3 worldPoint) // 二维透视投影
{
Point3 viewPoint; // 观察坐标系
viewPoint.x = K[2] * worldPoint.x - K[0] * worldPoint.z;
viewPoint.y = -K[7] * worldPoint.x + K[1] * worldPoint.y - K[6] * worldPoint.z;
viewPoint.z = -K[5] * worldPoint.x - K[3] * worldPoint.y - K[4] * worldPoint.z + R;
Point2 screenPoint; // 屏幕坐标
screenPoint.x = D * viewPoint.x / viewPoint.z;
screenPoint.y = D * viewPoint.y / viewPoint.z;
return screenPoint;
}
Point3 PerspectiveProjection3(Point3 worldPoint) // 三维坐标点
{
Point3 viewPoint; // 观察坐标系
viewPoint.x = K[2] * worldPoint.x - K[0] * worldPoint.z;
viewPoint.y = -K[7] * worldPoint.x + K[1] * worldPoint.y - K[6] * worldPoint.z;
viewPoint.z = -K[5] * worldPoint.x - K[3] * worldPoint.y - K[4] * worldPoint.z + R;
viewPoint.c = worldPoint.c;
Point3 screenPoint; // 屏幕坐标
screenPoint.x = D * viewPoint.x / viewPoint.z;
screenPoint.y = D * viewPoint.y / viewPoint.z;
screenPoint.z = Far * (1 - Near / viewPoint.z) / (Far - Near);
screenPoint.c = viewPoint.c;
return screenPoint;
}
};
class ZBuffer // 消隐
{
Point3 P0, P1, P2; // 三角形顶点坐标
double** _zBuffer; // 深度缓冲区
int _nWidth; // 缓冲区宽
int _nHeight; // 缓冲区高
public:
ZBuffer() {};
~ZBuffer()
{
if (_zBuffer)
{
for (int i = 0; i < _nWidth; i++)
{
delete[] _zBuffer[i] ;
_zBuffer[i] = NULL;
}
delete[] _zBuffer;
_zBuffer = NULL;
}
};
void SetPoint(Point3 *p) // 顶点构造
{
P0 = p[0];
P1 = p[1];
P2 = p[2];
}
void InitialDepthBuffer(int nWidth,int nHeight) // 初始化深度缓存区
{
_nWidth = nWidth;
_nHeight = nHeight;
_zBuffer = new double* [nWidth];
for (int i = 0; i < nWidth; i++)
{
_zBuffer[i] = new double[nHeight];
}
}
void ClearDepthBuffer(double nDepth)
{
// 初始化深度缓冲区
for (int i = 0; i < _nWidth; i++) // 初始化深度缓冲区
{
for (int j = 0; j < _nHeight; j++)
{
_zBuffer[i][j] = nDepth;
}
}
}
void FillTriangle(HDC hdc) // 重心坐标填充三角形
{
int xMin = ROUND(min(min(P0.x, P1.x), P2.x)); // 包围盒左下坐标
int yMin = ROUND(min(min(P0.y, P1.y), P2.y));
int xMax = ROUND(max(max(P0.x, P1.x), P2.x)); // 包围盒右上坐标
int yMax = ROUND(max(max(P0.y, P1.y), P2.y));
for (int y = yMin; y <= yMax; y++)
{
for (int x = xMin; x <= xMax; x++)
{
double area = P0.x * P1.y + P1.x * P2.y + P2.x * P0.y - P2.x * P1.y - P1.x * P0.y - P0.x * P2.y;
double area0 = x * P1.y + P1.x * P2.y + P2.x * y - P2.x * P1.y - P1.x * y - x * P2.y;
double area1 = P0.x * y + x * P2.y + P2.x * P0.y - P2.x * y - x * P0.y - P0.x * P2.y;
double area2 = P0.x * P1.y + P1.x * y + x * P0.y - x * P1.y - P1.x * P0.y - P0.x * y;
// 计算重心坐标
double alpha = area0 / area;
double beta = area1 / area;
double gamma = area2 / area;
if (alpha >= 0 && beta >= 0 && gamma >= 0)
{
RGB24 crColor = alpha * P0.c + beta * P1.c + gamma * P2.c; // 计算三角形内内任一点颜色
double zDepth = alpha * P0.z + beta * P1.z + gamma * P2.z; // 计算三角形内内任一点深度
// 左手坐标系,Z轴指向屏幕内,z越小距离屏幕越近。
// _nWidth / 2 _nHeight / 2 半宽高 消除负数
if (zDepth <= _zBuffer[x + _nWidth / 2][y + _nHeight / 2]) // ZBuffer
{
_zBuffer[x + _nWidth / 2][y + _nHeight / 2] = zDepth;
SetPixelV(hdc, ROUND(x), ROUND(y), RGB2RGB(crColor));
}
}
}
}
}
};
class Face
{
public:
int ptIndex[4]; // 面的顶点索引
public:
Face() {};
~Face() {};
};
class Bar // 长方体
{
public:
Point3 m_V[8]; // 顶点数组
private:
int m_a, m_b, m_c; // 长 宽 高
RGB24 m_ClrLeft; // 左半部分颜色
RGB24 m_ClrRight; // 右半部分颜色
Face m_F[6]; // 6个小面
Projection m_projection; // 投影
ZBuffer m_pZBuffer;
public:
Bar() {};
~Bar() {};
void SetParameter(int nLength,int nWidth,int nHeight, RGB24 clrLeft,RGB24 clrRight) //设置参数
{
m_a = nLength;
m_b = nWidth;
m_c = nHeight;
m_ClrLeft = clrLeft;
m_ClrRight = clrRight;
}
void ReadVertex() // 读入点表
{
m_V[0].x = -m_a / 2.0; m_V[0].y = -m_b / 2.0; m_V[0].z = -m_c / 2.0; m_V[0].c = m_ClrLeft;
m_V[1].x = +m_a / 2.0; m_V[1].y = -m_b / 2.0; m_V[1].z = -m_c / 2.0; m_V[1].c = m_ClrRight;
m_V[2].x = +m_a / 2.0; m_V[2].y = +m_b / 2.0; m_V[2].z = -m_c / 2.0; m_V[2].c = m_ClrRight;
m_V[3].x = -m_a / 2.0; m_V[3].y = +m_b / 2.0; m_V[3].z = -m_c / 2.0; m_V[3].c = m_ClrLeft;
m_V[4].x = -m_a / 2.0; m_V[4].y = -m_b / 2.0; m_V[4].z = +m_c / 2.0; m_V[4].c = m_ClrLeft;
m_V[5].x = +m_a / 2.0; m_V[5].y = -m_b / 2.0; m_V[5].z = +m_c / 2.0; m_V[5].c = m_ClrRight;
m_V[6].x = +m_a / 2.0; m_V[6].y = +m_b / 2.0; m_V[6].z = +m_c / 2.0; m_V[6].c = m_ClrRight;
m_V[7].x = -m_a / 2.0; m_V[7].y = +m_b / 2.0; m_V[7].z = +m_c / 2.0; m_V[7].c = m_ClrLeft;
}
void ReadFacet() // 读入面表
{
m_F[0].ptIndex[0] = 4; m_F[0].ptIndex[1] = 5; m_F[0].ptIndex[2] = 6; m_F[0].ptIndex[3] = 7;
m_F[1].ptIndex[0] = 0; m_F[1].ptIndex[1] = 3; m_F[1].ptIndex[2] = 2; m_F[1].ptIndex[3] = 1;
m_F[2].ptIndex[0] = 0; m_F[2].ptIndex[1] = 4; m_F[2].ptIndex[2] = 7; m_F[2].ptIndex[3] = 3;
m_F[3].ptIndex[0] = 1; m_F[3].ptIndex[1] = 2; m_F[3].ptIndex[2] = 6; m_F[3].ptIndex[3] = 5;
m_F[4].ptIndex[0] = 2; m_F[4].ptIndex[1] = 3; m_F[4].ptIndex[2] = 7; m_F[4].ptIndex[3] = 6;
m_F[4].ptIndex[0] = 2; m_F[4].ptIndex[1] = 3; m_F[4].ptIndex[2] = 7; m_F[4].ptIndex[3] = 6;
m_F[5].ptIndex[0] = 0; m_F[5].ptIndex[1] = 1; m_F[5].ptIndex[2] = 5; m_F[5].ptIndex[3] = 4;
}
void Draw(HDC hdc,ZBuffer* pZbuffer) // 绘图
{
for (int nFace = 0; nFace < 6; nFace++) // 立方体6个面
{
Point3 points[4];
for (int nPoint = 0; nPoint < 4; nPoint++) // 立方体4个顶点
{
points[nPoint] = m_projection.PerspectiveProjection3(m_V[m_F[nFace].ptIndex[nPoint]]);// 投影
}
// 左侧三角形
Point3 RDP[3] = { points[0], points[1], points[2] };
pZbuffer->SetPoint(RDP);
pZbuffer->FillTriangle(hdc);
// 右侧三角形
Point3 LDP[3] = { points[0], points[2], points[3] };
pZbuffer->SetPoint(LDP);
pZbuffer->FillTriangle(hdc);
}
}
};
static bool g_Play = false;
static wchar_t* g_str = L"左键旋转";
int g_NumberObject; // 场景中物体数量
Bar g_bar[4]; // 交插条数量
Transform3 g_transform[4]; // 坐标变化
ZBuffer* m_pZBuffer ;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
static double alpha;
static double beta;
int a = 500; // 长度
int b = 60; // 宽度
int c = 300; // 厚度
int d = 120; // 交叉头
int e = 160; // 收缩宽度
RGB24 red (1.0, 0.0, 0.0);
RGB24 green (0.0, 1.0, 0.0);
RGB24 blue (0.0, 0.0, 1.0);
RGB24 yellow(1.0, 1.0, 0.0);
g_NumberObject = 4;
g_bar[0].SetParameter(a, b, c, green, red);
g_bar[1].SetParameter(a, b, c, red, green);
g_bar[2].SetParameter(a, b, e, blue, yellow);
g_bar[3].SetParameter(a, b, e, yellow, blue);
for (int i = 0; i < g_NumberObject; i++)
{
g_bar[i].ReadVertex();
g_bar[i].ReadFacet();
g_transform[i].SetMatrix(g_bar[i].m_V, 8);
}
g_transform[0].Translate(0, d + b / 2.0, 0); // 顶面上方条位置
g_transform[1].Translate(0, -d - b / 2.0, 0); // 底面上方条位置
g_transform[2].RotateZ(90); // 左侧上方条位置
g_transform[2].Translate(-d - b / 2.0, 0, 0); // 左侧上方条位置
g_transform[3].RotateZ(90); // 右侧上方条位置
g_transform[3].Translate(d + b / 2.0, 0, 0); // 右侧上方条位置
switch (uMsg)
{
case WM_TIMER:
alpha += 0.5;
beta += 0.5;
InvalidateRect(hwnd, NULL, FALSE);
return 0;
case WM_LBUTTONDOWN:
g_Play = !g_Play;
if (g_Play)
{
SetTimer(hwnd, 0, 0, NULL);
}
else
{
KillTimer(hwnd, 0);
}
return 0;
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, rect.right, rect.bottom, NULL);
SetViewportExtEx(hdc, rect.right, -rect.bottom, NULL);
SetViewportOrgEx(hdc, rect.right / 2, rect.bottom / 2, NULL);
// 内存DC
HDC memDC = CreateCompatibleDC(hdc); // 创建兼容DC 画板
HBITMAP newBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); // 创建画布
HGDIOBJ oldBitmap = SelectObject(memDC, newBitmap); // 将画布选入画板
//FillRect(memDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
DrawText(memDC, g_str, wcslen(g_str), &rect, NULL);
SetMapMode(memDC, MM_ANISOTROPIC);
SetWindowExtEx(memDC, rect.right, rect.bottom, NULL);
SetViewportExtEx(memDC, rect.right, -rect.bottom, NULL);
SetViewportOrgEx(memDC, rect.right / 2, rect.bottom / 2, NULL);
// 绘制图形
{
// 清空缓冲区
m_pZBuffer->ClearDepthBuffer(1000);
if (g_Play)
{
for (int i = 0; i < g_NumberObject; i++)
{
g_transform[i].RotateX(alpha);
g_transform[i].RotateY(beta);
}
}
for (int i = 0; i < g_NumberObject; i++)
{
g_bar[i].Draw(memDC, m_pZBuffer);
}
}
// 内存dc复制到设备
BitBlt(hdc, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), rect.right, rect.bottom, memDC, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), SRCCOPY);
SelectObject(memDC, oldBitmap);
DeleteObject(newBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// 创建深度缓冲区
m_pZBuffer = new ZBuffer;
m_pZBuffer->InitialDepthBuffer(1000.0, 1000.0); // 初始化深度缓冲区
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, CLASS_NAME,WINDOW_TEXT,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
delete m_pZBuffer; // 删除 zBuffer
return 0;
}
总结