gpt4 book ai didi

c++ - 如何改善我的文件写入方法以减小Wavefront Object文件的大小?

转载 作者:行者123 更新时间:2023-12-02 10:19:30 25 4
gpt4 key购买 nike

我正在尝试将模型的voxelization写成Wavefront Object File

我的方法很简单,并且可以在合理的时间内运行。问题是-它产生大小可笑的OBJ文件。我尝试将1 GB的文件加载到具有SSD的非常受人尊敬的计算机上的3D Viewer中,但是在某些情况下,尝试移动相机时延迟了几秒钟,而在另一些情况下,它根本不执行任何操作并有效地软锁定了。

我到目前为止所做的:

  • 我不会写出模型内部的任何面-即两个都将被写入文件的体素之间的面。没有意义,因为没有人可以看到它们。
  • 因为OBJ没有广泛支持的二进制格式(据我所知),所以我发现可以通过修剪文件顶点位置的尾随零来节省一些空间。

  • 我不知道该如何节省空间:
  • 不写出重复的顶点。总共,文件中的顶点大约比应有的多8倍。但是,修复此问题非常棘手,因为Wavefront对象文件中的对象不使用每个对象,而是使用全局顶点。通过每次写出所有8个顶点,我总是知道组成下一个体素的8个顶点。如果我没有全部写出8,该如何跟踪全局列表中的哪个位置(如果有的话)。

  • 较难但可能有用的大空间节省:
  • 如果我可以更抽象地工作,则可以采用一种方法将体素组合成更少的对象,或者将沿同一平面放置的面组合成较大的面。 IE,如果两个体素的正面都处于 Activity 状态,则将其变成一个更大的矩形,两倍大。

  • 因为这是必需的,所以下面的代码大致显示了正在发生的事情。这不是实际使用的代码。我不能发布它,它依赖于许多用户定义的类型,并且有很多代码来处理极端情况或额外的功能,因此无论如何都要花很多时间来弄乱它。

    对该问题唯一重要的是我的方法-逐个体素,写出所有8个顶点,然后写出6个面中的任意一个不与 Activity 体素相邻。您将不得不相信我,尽管它确实会产生大文件,但它确实可以工作。

    我的问题是我可以使用哪种方法或方法进一步减小尺寸。例如,如何不写出任何重复的顶点?

    假设:
  • Point只是一个大小为3的数组,带有.x()的 setter/getter
  • Vector3Dstd::vector的3D包装器,使用.at(x,y,z)方法
  • 哪些体素处于 Activity 状态是任意的,并且不遵循模式,但是在调用writeObj之前已知。如果体素在任何位置都处于 Activity 状态,则可以快速获取。
  • //Left, right, bottom, top, front, rear
    static const std::vector<std::vector<uint8_t>> quads = {
    {3, 0, 4, 7}, {1, 2, 6, 5}, {3, 2, 1, 0},
    {4, 5, 6, 7}, {0, 1, 5, 4}, {2, 3, 7, 6}};

    void writeOBJ(
    std::string folder,
    const std::string& filename,
    const Vector3D<Voxel>& voxels,
    const Point<unsigned> gridDim,
    const Point<unsigned>& voxelCenterMinpoint,
    const float voxelWidth)
    {
    unsigned numTris = 0;
    std::ofstream filestream;
    std::string filepath;
    std::string extension;
    ulong numVerticesWritten = 0;

    // Make sure the folder ends with a '/'
    if (folder.back() != '/')
    {
    folder.append("/");
    }

    filepath = folder + filename + ".obj";

    filestream.open(filepath, std::ios::out);

    // Remove the voxelization file if it already exists
    std::remove(filepath.c_str());

    Point<unsigned> voxelPos;

    for (voxelPos[0] = 0; voxelPos[0] < gridDim.x(); voxelPos[0]++)
    {
    for (voxelPos[1] = 0; voxelPos[1] < gridDim.y(); voxelPos[1]++)
    {
    for (voxelPos[2] = 0; voxelPos[2] < gridDim.z(); voxelPos[2]++)
    {
    if (voxels.at(voxelPos))
    {
    writeVoxelToOBJ(
    filestream, voxels, voxelPos, voxelCenterMinpoint, voxelWidth,
    numVerticesWritten);
    }
    }
    }
    }

    filestream.close();
    }

    void writeVoxelToOBJ(
    std::ofstream& filestream,
    const Vector3D<Voxel>& voxels,
    const Point<unsigned>& voxelPos,
    const Point<unsigned>& voxelCenterMinpoint,
    const float voxelWidth,
    ulong& numVerticesWritten)
    {
    std::vector<bool> neighborDrawable(6);
    std::vector<Vecutils::Point<float>> corners(8);
    unsigned numNeighborsDrawable = 0;

    // Determine which neighbors are active and what the 8 corners of the
    // voxel are
    writeVoxelAux(
    voxelPos, voxelCenterMinpoint, voxelWidth, neighborDrawable,
    numNeighborsDrawable, corners);

    // Normally, if all neighbors are active, there is no reason to write out this
    // voxel. (All its faces are internal) If inverted, the opposite is true.
    if (numNeighborsDrawable == 6)
    {
    return;
    }

    // Write out the vertices
    for (const Vecutils::Point<float>& corner : corners)
    {
    std::string x = std::to_string(corner.x());
    std::string y = std::to_string(corner.y());
    std::string z = std::to_string(corner.z());

    // Strip trailing zeros, they serve no prupose and bloat filesize
    x.erase(x.find_last_not_of('0') + 1, std::string::npos);
    y.erase(y.find_last_not_of('0') + 1, std::string::npos);
    z.erase(z.find_last_not_of('0') + 1, std::string::npos);

    filestream << "v " << x << " " << y << " " << z << "\n";
    }

    numVerticesWritten += 8;

    // The 6 sides of the voxel
    for (uint8_t i = 0; i < 6; i++)
    {
    // We only write them out if the neighbor in that direction
    // is inactive
    if (!neighborDrawable[i])
    {
    // The indices of the quad making up this face
    const std::vector<uint8_t>& quad0 = quads[i];

    ulong q0p0 = numVerticesWritten - 8 + quad0[0] + 1;
    ulong q0p1 = numVerticesWritten - 8 + quad0[1] + 1;
    ulong q0p2 = numVerticesWritten - 8 + quad0[2] + 1;
    ulong q0p3 = numVerticesWritten - 8 + quad0[3] + 1;

    // Wavefront object files are 1-indexed with regards to vertices
    filestream << "f " << std::to_string(q0p0) << " "
    << std::to_string(q0p1) << " " << std::to_string(q0p2)
    << " " << std::to_string(q0p3) << "\n";
    }
    }
    }

    void writeVoxelAux(
    const Point<unsigned>& voxelPos,
    const Point<unsigned>& voxelCenterMinpoint,
    const float voxelWidth,
    std::vector<bool>& neighborsDrawable,
    unsigned& numNeighborsDrawable,
    std::vector<Point<float>>& corners)
    {
    // Which of the 6 immediate neighbors of the voxel are active?
    for (ulong i = 0; i < 6; i++)
    {
    neighborsDrawable[i] = isNeighborDrawable(voxelPos.cast<int>() + off[i]);

    numNeighborsDrawable += neighborsDrawable[i];
    }

    // Coordinates of the center of the voxel
    Vecutils::Point<float> center =
    voxelCenterMinpoint + (voxelPos.cast<float>() * voxelWidth);

    // From this center, we can get the 8 corners of the triangle
    for (ushort i = 0; i < 8; i++)
    {
    corners[i] = center + (crnoff[i] * (voxelWidth / 2));
    }
    }

    附录:

    尽管我最终还是做了@Tau所建议的操作,但有一个关键区别-比较运算符。

    对于由3个浮点数表示的点, <==是不够的。即使在两者上都使用公差,它也不能始终如一地工作,并且在我的调试和 Release模式之间存在差异。

    我有一种新方法,我会在可能的时候在这里发布,即使它不是100%可靠的。

    最佳答案

    如果您定义这样的自定义比较器:

    struct PointCompare
    {
    bool operator() (const Point<float>& lhs, const Point<float>& rhs) const
    {
    if (lhs.x() < rhs.x()) // x position is most significant (arbitrary)
    return true;
    else if (lhs.x() == rhs.x()) {
    if (lhs.y() < rhs.y())
    return true;
    else if (lhs.y() == lhs.y())
    return lhs.z() < rhs.z();
    }
    }
    };


    然后,您可以在 vector 中创建从点到其索引的映射,并且每当您在面上使用顶点时,请检查其是否已存在:

    std::vector<Point> vertices;
    std::map<Point, unsigned, PointCompare> indices;

    unsigned getVertexIndex(Point<float>& p) {
    auto it = indices.find(p);
    if (it != indices.end()) // known vertex
    return it->second;
    else { // new vertex, store in list
    unsigned pos = vertices.size();
    vertices.push_back(p);
    indices[p] = pos;
    return pos;
    }
    }

    使用此方法计算所有面孔,然后将 vertices写入文件,然后写入面孔。

    最佳地组合体素的面孔确实比这要复杂一些,但是如果您想尝试一下,可以使用 check this out

    另外,如果您总体上只处理几个网格,则可能希望省去优化代码的麻烦,并使用免费的 MeshLab,它可以删除重复的顶点,合并面并使用以下命令导出为多种(更有效的)格式:只需点击几下。

    顺便说一句:只有将体素真正稀疏存储在列表中才有效。在大多数情况下,使用 bool[][][]会更有效,并且确实可以简化您的算法(例如,用于查找邻居)。

    关于c++ - 如何改善我的文件写入方法以减小Wavefront Object文件的大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60857532/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com