You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中高效将3D三角形光栅化至均匀体素网格?

3D三角形到均匀体素网格的高效光栅化实现(C++无额外库)

核心思路

把2D扫描线光栅化扩展到3D的核心是按轴分层切片:选择三角形跨度最大的轴作为扫描轴,将3D问题拆解为一系列平行的2D截面光栅化任务。每个截面是三角形与体素网格某一层平面的交线,对这条交线做2D扫描线填充,最终映射到对应的3D体素。这种方法复用成熟的2D扫描线逻辑,同时通过选最长轴最小化切片数量,保证效率。

步骤拆解与代码实现

1. 基础定义与预处理

先定义顶点、体素网格的基础结构,预处理三角形关键参数:

#include <vector>
#include <algorithm>
#include <cmath>

// 3D顶点结构体
struct Vec3 {
    float x, y, z;
    Vec3(float x=0, float y=0, float z=0) : x(x), y(y), z(z) {}
    Vec3 operator-(const Vec3& other) const { return Vec3(x-other.x, y-other.y, z-other.z); }
    Vec3 cross(const Vec3& other) const {
        return Vec3(
            y*other.z - z*other.y,
            z*other.x - x*other.z,
            x*other.y - y*other.x
        );
    }
    float dot(const Vec3& other) const { return x*other.x + y*other.y + z*other.z; }
    Vec3 normalize() const {
        float len = std::sqrt(x*x + y*y + z*z);
        return Vec3(x/len, y/len, z/len);
    }
};

// 3D体素网格,一维数组模拟(索引:x + y*width + z*width*height)
class VoxelGrid {
public:
    int width, height, depth;
    std::vector<float> data; // 存储密度/颜色值

    VoxelGrid(int w, int h, int d) : width(w), height(h), depth(d), data(w*h*d, 0.0f) {}

    float& get(int x, int y, int z) {
        return data[x + y*width + z*width*height];
    }
};

2. 确定扫描轴与分层范围

计算三角形包围盒,选跨度最大的轴作为扫描轴,减少切片数量:

// 获取三角形的包围盒
void getTriangleAABB(const Vec3& v0, const Vec3& v1, const Vec3& v2, 
                     float& minX, float& maxX, float& minY, float& maxY, float& minZ, float& maxZ) {
    minX = std::min({v0.x, v1.x, v2.x});
    maxX = std::max({v0.x, v1.x, v2.x});
    minY = std::min({v0.y, v1.y, v2.y});
    maxY = std::max({v0.y, v1.y, v2.y});
    minZ = std::min({v0.z, v1.z, v2.z});
    maxZ = std::max({v0.z, v1.z, v2.z});
}

// 选择扫描轴(0=X,1=Y,2=Z)
int selectScanAxis(float dx, float dy, float dz) {
    if (dx >= dy && dx >= dz) return 0;
    if (dy >= dx && dy >= dz) return 1;
    return 2;
}

3. 分层计算截面交线

对每个体素层,计算三角形与该层平面的交线(若相交):

// 计算三角形与某平面的交线(planeVal是扫描轴对应的坐标值)
bool getTriangleSlice(const Vec3& v0, const Vec3& v1, const Vec3& v2, int axis, float planeVal,
                      Vec3& p0, Vec3& p1) {
    // 根据轴统一坐标获取逻辑
    auto getCoord = [&](const Vec3& v) {
        switch(axis) {
            case 0: return v.x;
            case 1: return v.y;
            default: return v.z;
        }
    };

    std::vector<std::pair<Vec3, Vec3>> edges = {{v0,v1}, {v1,v2}, {v2,v0}};
    std::vector<Vec3> intersections;

    for (auto& edge : edges) {
        float c0 = getCoord(edge.first);
        float c1 = getCoord(edge.second);
        if ((c0 <= planeVal && c1 >= planeVal) || (c0 >= planeVal && c1 <= planeVal)) {
            // 线性插值计算交点
            float t = (planeVal - c0) / (c1 - c0);
            Vec3 p(
                edge.first.x + t*(edge.second.x - edge.first.x),
                edge.first.y + t*(edge.second.y - edge.first.y),
                edge.first.z + t*(edge.second.z - edge.first.z)
            );
            intersections.push_back(p);
        }
    }

    if (intersections.size() == 2) {
        p0 = intersections[0];
        p1 = intersections[1];
        return true;
    }
    return false;
}

4. 2D扫描线填充截面

将3D交点投影到垂直于扫描轴的2D平面,用扫描线算法填充对应体素,更新3D体素值:

// 2D扫描线填充线段到体素层,更新3D体素
void rasterizeSlice(const Vec3& p0, const Vec3& p1, int axis, int sliceIdx, VoxelGrid& grid, float value, const Vec3& triNormal, const Vec3& triV0) {
    // 投影到2D平面
    float x0, y0, x1, y1;
    switch(axis) {
        case 0: // X轴扫描,投影到Y-Z
            x0 = p0.y; y0 = p0.z;
            x1 = p1.y; y1 = p1.z;
            break;
        case 1: // Y轴扫描,投影到X-Z
            x0 = p0.x; y0 = p0.z;
            x1 = p1.x; y1 = p1.z;
            break;
        default: // Z轴扫描,投影到X-Y
            x0 = p0.x; y0 = p0.y;
            x1 = p1.x; y1 = p1.y;
            break;
    }

    // 保证y0 <= y1
    if (y0 > y1) {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    float yStep = 1.0f; // 体素边长,假设为1
    float xSlope = (y1 - y0) < 1e-6 ? 0 : (x1 - x0) / (y1 - y0);
    float currentX = x0;

    // 遍历每个Y对应的体素行
    for (float y = y0; y <= y1; y += yStep) {
        int yVoxel = static_cast<int>(std::floor(y));
        if (yVoxel < 0 || yVoxel >= (axis==0 ? grid.height : (axis==1 ? grid.depth : grid.height))) continue;

        float xStart = std::min(currentX, currentX + xSlope*yStep);
        float xEnd = std::max(currentX, currentX + xSlope*yStep);
        int xStartVoxel = static_cast<int>(std::floor(xStart));
        int xEndVoxel = static_cast<int>(std::floor(xEnd));

        // 填充X范围内的体素
        for (int x = xStartVoxel; x <= xEndVoxel; x++) {
            if (x < 0 || x >= (axis==0 ? grid.width : (axis==1 ? grid.width : grid.width))) continue;

            // 计算体素中心到三角形的距离,加权更新密度
            Vec3 voxelCenter;
            switch(axis) {
                case 0: voxelCenter = Vec3(sliceIdx+0.5f, x+0.5f, yVoxel+0.5f); break;
                case 1: voxelCenter = Vec3(x+0.5f, sliceIdx+0.5f, yVoxel+0.5f); break;
                default: voxelCenter = Vec3(x+0.5f, yVoxel+0.5f, sliceIdx+0.5f); break;
            }
            float distance = std::abs(triNormal.dot(voxelCenter - triV0));
            float weight = 1.0f / (1.0f + distance); // 距离越近权重越高

            // 更新3D体素值
            switch(axis) {
                case 0: grid.get(x, yVoxel, sliceIdx) += value * weight; break;
                case 1: grid.get(x, sliceIdx, yVoxel) += value * weight; break;
                default: grid.get(x, yVoxel, sliceIdx) += value * weight; break;
            }
        }

        currentX += xSlope*yStep;
    }
}

5. 主光栅化函数

整合所有步骤,完成3D三角形到体素网格的光栅化:

void rasterizeTriangle3D(const Vec3& v0, const Vec3& v1, const Vec3& v2, VoxelGrid& grid, float densityValue) {
    float minX, maxX, minY, maxY, minZ, maxZ;
    getTriangleAABB(v0, v1, v2, minX, maxX, minY, maxY, minZ, maxZ);

    int axis = selectScanAxis(maxX-minX, maxY-minY, maxZ-minZ);

    // 转换为体素索引范围
    int startIdx = static_cast<int>(std::floor(
        axis==0 ? minX : (axis==1 ? minY : minZ)
    ));
    int endIdx = static_cast<int>(std::floor(
        axis==0 ? maxX : (axis==1 ? maxY : maxZ)
    ));

    // 提前计算三角形法向量,用于距离加权
    Vec3 edge1 = v1 - v0;
    Vec3 edge2 = v2 - v0;
    Vec3 triNormal = edge1.cross(edge2).normalize();

    // 遍历每个体素层
    for (int idx = startIdx; idx <= endIdx; idx++) {
        float planeVal = idx + 0.5f; // 体素中心坐标
        Vec3 p0, p1;
        if (getTriangleSlice(v0, v1, v2, axis, planeVal, p0, p1)) {
            rasterizeSlice(p0, p1, axis, idx, grid, densityValue, triNormal, v0);
        }
    }
}

优化技巧

  • 整数运算替代浮点:若体素网格是整数坐标对齐,可将所有浮点坐标乘以体素分辨率转为整数,减少浮点运算开销。
  • 跳过空切片:提前判断三角形包围盒与体素网格的交集,只处理有重叠的层,避免无效循环。
  • 批量连续体素填充:对连续的X范围,直接批量更新数组,减少循环次数,提升缓存命中率。

内容的提问来源于stack exchange,提问作者Spektre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 23:05:30