本文介绍了一种制作 3D 收款码的方法。通过本文介绍的 C++ 程序,可以将收款码(本质上是一串二进制数据)转换为 3D 模型(STL 文件),然后就可以用 3D 打印机打印出来了。
当然,要真正拿到属于你的 3D 收款码的实物,你首先需要一台 3D 打印机,不过本文重点是其中的几何算法、程序设计思路。
我已经把这个 C++ 程序开源,有兴趣的可以试试使用这个工具。Github 地址
1、3D 模型概述
三维物体由 3D 模型描述。 3D 模型是一系列相互连接的外包络三角形面片。也就是说,任意一个模型,其基本组成都是三维空间的三角形,这些三角形串接在一起,形成了三维物体的表面。

把 3D 模型放大看,其表面上有:
- 一系列的点
- 一系列的棱,每个棱有两个端点
- 一系列的三角形面片,每个面片有三个棱、三个顶点
- 每个棱,连接两个面片
因此,只要给定一个顶点坐标的数组 [(x,y,z)],一个三角形面片的数组 [(a,b,c)],就可以描述一个 3D 模型。这里 a、b、c 是整数,是指向顶点数组的索引。
2、STL 格式介绍
有很多中文件格式,可以原来保存 3D 模型数据到文件中。STL 是工业界比较常用的一种。
STL 除了保存顶点坐标、三角形面片,也保存一些元数据(比如:作者、标题)。
STL 有一个特别的地方,它没有独立保存顶点坐标数组,而是在三角形面片中直接使用顶点坐标。下面是一个三角形面片的定义:
facet normal 0.0 0.0 0.0
outer loop
vertex -23 -23 0
vertex 23 -23 0
vertex 23 -23 4
endloop
endfacet
3、3D 收款码设计
收款码是一种二维码,而二维码是一个大正方形包含了一系列黑白方块。将二维码 3D 化,通常是将黑白方块赋予不同的高度。
下面是一个黑色块比较矮的凹型 3D 收款码:

也可以用凸型表示:

上面的模型都是附加了一个方块宽度的外边框。
4、二维码模型
下面就 3D 收款码实现的关键思路做一些描述。
4.1、制作二维码(ZXing)
首先要生成二维码,通常用 ZXing 这个开源库来做。ZXing 支持多种条形码、二维码,其中二维码有下列 5 种:
- Aztec
- DataMatrix
- MaxiCode
- PDF417
- QRCode(常用)
为了支持多种二维码格式,我们采用 MultiFormatWriter 来生成二维码。
std::vector<std::vector<unsigned char>> bytes;
auto writer = MultiFormatWriter(format).setMargin(0).setEncoding(encoding).setEccLevel(eccLevel);
auto bits = writer.encode(TextUtfEncoding::FromUtf8(text), 0, 0, configs);
for (int i = 0; i < bits.height(); ++i) {
auto row = bits.row(i);
bytes.push_back(std::vector<unsigned char>(row.begin(), row.end()));
}
4.2、二维码 3D 化
二维码 3D 化是比较关键的一步,3D 化就是将描述 3D 模型的所有顶点、三角形给出来。
这里所有的表面都是长方形(正方形也是长方形),每一个长方形可以用两个三角形表示。
考察三维模型,有下列长方形:
- 底面大正方形
- 侧面长方形(4个)
- 顶面:
- 白色块(包括外边框),每个方块一个正方形
- 黑色块,每个方块一个正方形
- 黑色块的侧面(仅与白色块相邻的)
点的唯一性
考察三维模型的顶点,有下列顶点:
- 底面顶点(4个)
- 顶面:
- 每个白色方块(4个)
- 每个黑色方块(8个,其中4个与白色面等高)
这里很多顶点是重复的,比如下面四个方块共用一个顶点 A。

所以,一个方块的左上角,可以是其左上角相邻方块的右下角,或者上边相邻方块的左下角、左边相邻方块的右上角。
完整的顶点重叠关系如下:
| 方块的顶点 | 左上角相邻方块 | 上边相邻方块 | 左边相邻方块 |
| 左上角 | 右下角 | 左下角 | 右上角 |
| 右上角 | 右下角 | ||
| 左下角 | 右下角 | ||
| 右下角 |
用代码实现为:
size_t QRCodeModel::blockPoint(size_t row, size_t col, size_t index)
{
size_t& p = blocks[row][col].points[index];
if (p == 0) {
auto n = index & 3;
if (n == 0 && row > 0 && col > 0)
p = blockPoint(row - 1, col - 1, index + 2);
else if (n == 0 && row > 0)
p = blockPoint(row - 1, col, index + 3);
else if (n == 0 && col > 0)
p = blockPoint(row, col - 1, index + 1);
else if (n == 1 && (row > 0 || !++col))
p = blockPoint(row - 1, col, index + 1);
else if (n == 3 && (col > 0 || !++row))
p = blockPoint(row, col - 1, index - 1);
else if (n != 2 || (++row && ++col))
p = mesh.addVerticals({ { col * blockSize - radiusX, row * blockSize - radiusY, (index >= 4) == concave ? thickness : thickness / 2 } });
}
return p;
}
这里 index 取值范围是 0~7,白色块的等高面的顶点是 4~7,黑色块的等高面的顶点是 0~3。对于凹凸型,黑白色块的高度是不一样的。
黑白方块的面片拆分
黑色方块比白色方块多了侧面,所以要复杂一些,代码实现如下:
void QRCodeModel::generateBlackBlock(size_t row, size_t col)
{
bool l = blocks[row][col - 1].group == 1; // if left neighbour is black
bool r = blocks[row][col + 1].group == 1;
bool t = blocks[row - 1][col].group == 1;
bool b = blocks[row + 1][col].group == 1;
size_t points[] = {
blockPoint(row, col, 0), blockPoint(row, col, 1), blockPoint(row, col, 2), blockPoint(row, col, 3),
(l && t) ? 0 : blockPoint(row, col, 4),
(r && t) ? 0 : blockPoint(row, col, 5),
(r && b) ? 0 : blockPoint(row, col, 6),
(l && b) ? 0 : blockPoint(row, col, 7),
};
mesh.addTriangles({
{points[0], points[2], points[3]},
{points[0], points[1], points[2]},
});
if (!l)
mesh.addTriangles({
{points[0], points[3], points[7]},
{points[0], points[7], points[4]},
});
if (!t)
mesh.addTriangles({
{points[0], points[5], points[1]},
{points[0], points[4], points[5]},
});
if (!r)
mesh.addTriangles({
{points[1], points[5], points[6]},
{points[1], points[6], points[2]},
});
if (!b)
mesh.addTriangles({
{points[3], points[2], points[6]},
{points[3], points[6], points[7]},
});
}

本文介绍了如何使用C++程序将二维码转化为3D模型,并以STL文件格式保存,适合3D打印。通过ZXing库生成二维码,然后将其3D化,黑色和白色方块具有不同高度,形成凹凸效果。最终,代码实现将每个方块的顶点和三角形面片详细描述,创建出3D收款码模型。
477

被折叠的 条评论
为什么被折叠?



