首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何改进我的文件写入方法以减少Wavefront对象文件的大小?

如何改进我的文件写入方法以减少Wavefront对象文件的大小?
EN

Stack Overflow用户
提问于 2020-03-25 21:17:53
回答 2查看 317关注 0票数 1

我试图把模型的体素化写成波前目标文件

我的方法简单,在合理的时间内运行。问题是-它产生的OBJ文件在大小上是可笑的。我试图用SSD在一台非常受人尊敬的机器上将一个1GB文件加载到三维查看器中,但在某些情况下,当试图移动相机时延迟数秒钟,而在另一些情况下,它完全拒绝执行任何操作,而且实际上是软锁定的。

到目前为止我所做的:

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

显而易见的节省空间我不知道该怎么做:

  • 而不是写出重复的顶点。总之,文件中的顶点比应该多出大约8倍。但是,修复这个问题非常棘手,因为Wavefront对象文件中的对象不是使用每个对象,而是使用全局顶点。通过每次写出所有8个顶点,我总是知道是哪8个顶点构成了下一个体素。如果我不把所有的8个都写出来,我如何跟踪在全局列表中的位置,我可以找到这8个(如果有的话)。

更难,但可能有用的大空间节省:

  • 如果我能更抽象地工作,也许有一种方法可以将体素组合成更少的物体,或者把沿着同一平面的脸组合成更大的脸。如果两个体素都有他们的正面活动,把它变成一个更大的长方形两倍大。

因为这是必需的,这里有一些代码粗略地显示了正在发生的事情。这不是实际使用的代码。我不能发布这一点,而且它依赖于许多用户定义的类型,并且有大量的代码来处理边缘情况或额外的功能,所以不管怎么说,这里的设置都会非常混乱和冗长。

对这个问题来说唯一重要的是我的方法--逐体素,写出所有的8个顶点,然后写出6个边中任何一个不是相邻的主动体素。您只需相信我,它可以工作,虽然它确实产生大文件。

我的问题是,我可以用什么方法或方法来进一步缩小规模。例如,我怎么能不写出任何重复的顶点呢?

Assumptions:

  • Point只是一个大小为3的数组,带有像.x()这样的getter
  • Vector3Dstd::vector的3D包装器,带有.at(x,y,z)方法
  • 哪个体素是活动的,是任意的,不遵循模式,但是在调用writeObj之前就知道了。如果一个体素在任何位置都是活动的,那么提取是可能的和快速的。
代码语言:javascript
复制
//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));
    }
}

增编:

虽然我最终还是做了类似@τ建议的事情,但有一个关键的区别--比较操作符。

对于由3个浮点数表示的点,<==是不够的。即使在这两种情况下都使用公差,它也不能一致地工作,并且在我的调试和发布模式之间也有差异。

我有一个新的方法,我会张贴在这里,当我可以,尽管它不是100%万无一失。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-03-26 11:06:13

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

代码语言:javascript
复制
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();
    }
  }
};

然后,您可以在向量中从点到其索引绘制一张地图,每当在脸上使用顶点时,检查它是否已经存在:

代码语言:javascript
复制
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;
  }
}

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

优化地组合体素面确实比这要复杂一些,但是如果您想要尝试,看看这个

或者,如果您只处理几个网格,那么您可能希望为自己省去优化代码的麻烦,并使用免费的MeshLab,它可以删除重复的顶点,合并面并导出到各种(更有效)格式,只需点击几下。

顺便说一句:只有当体素非常稀疏时,才能有效地将体素存储在列表中;在大多数情况下,使用bool[][][]将更有效,并真正简化了算法(例如,查找邻居)。

票数 1
EN

Stack Overflow用户

发布于 2020-03-26 11:08:51

显而易见的节省空间我不知道该怎么做:

  • 而不是写出重复的顶点。总之,文件中的顶点比应该多出大约8倍。但是,修复这个问题非常棘手,因为Wavefront对象文件中的对象不是使用每个对象,而是使用全局顶点。通过每次写出所有8个顶点,我总是知道是哪8个顶点构成了下一个体素。如果我不把所有的8个都写出来,我如何跟踪在全局列表中的位置,我可以找到这8个(如果有的话)。

这是我过去为OpenGL缓冲区准备网格(在加载的几何图形之外)时所做的事情。为此,我打算删除顶点中的重复项,因为索引缓冲区已经计划好了。

这就是我所做的:在一个std::set中插入所有顶点,这将消除重复。

为了降低内存消耗,我使用了一个索引类型的std::set (例如,size_tunsigned)和一个自定义谓词,该谓词对索引坐标进行比较。

自定义减谓词:

代码语言:javascript
复制
// functor for less predicate comparing indexed values
template <typename VALUE, typename INDEX>
struct LessValueT {
  VALUE *values;
  LessValueT(std::vector<VALUE> &values): values(values.data()) { }
  bool operator()(INDEX i1, INDEX i2) const { return values[i1] < values[i2]; }
};

和带有此谓词的std::set

代码语言:javascript
复制
// an index table (sorting indices by indexed values)
template <typename VALUE, typename INDEX>
using LookUpTableT = std::set<INDEX, LessValueT<VALUE, INDEX>>;

使用上述坐标(或法线),这些坐标(或法线)存储如下

代码语言:javascript
复制
template <typename VALUE>
struct Vec3T { VALUE x, y, z; };

因此,有必要使较少的操作符超载,这是我以最幼稚的方式对此示例所做的:

代码语言:javascript
复制
template <typename VALUE>
bool operator<(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
  return vec1.x < vec2.x ? true : vec1.x > vec2.x ? false
    : vec1.y < vec2.y ? true : vec1.y > vec2.y ? false
    : vec1.z < vec2.z;
}

因此,没有必要考虑这个谓词所产生的顺序的意义或非意义。它必须符合std::set的要求,以区分和排序具有不同组件的向量值。

为了演示这一点,我使用了一个tetrix海绵

使用不同数量的三角形(取决于细分级别)构建起来很容易,并且非常类似于IMHO --我对OPs数据所做的假设:

  • 相当数量的共享顶点
  • 少量不同的法线。

完整的示例代码testCollectVtcs.cc

代码语言:javascript
复制
#include <cassert>
#include <cmath>
#include <chrono>
#include <fstream>
#include <functional>
#include <iostream>
#include <numeric>
#include <set>
#include <string>
#include <vector>

namespace Compress {

// functor for less predicate comparing indexed values
template <typename VALUE, typename INDEX>
struct LessValueT {
  VALUE *values;
  LessValueT(std::vector<VALUE> &values): values(values.data()) { }
  bool operator()(INDEX i1, INDEX i2) const { return values[i1] < values[i2]; }
};

// an index table (sorting indices by indexed values)
template <typename VALUE, typename INDEX>
using LookUpTableT = std::set<INDEX, LessValueT<VALUE, INDEX>>;

} // namespace Compress

// the compress function - modifies the values vector
template <typename VALUE, typename INDEX = size_t>
std::vector<INDEX> compress(std::vector<VALUE> &values)
{
  typedef Compress::LessValueT<VALUE, INDEX> LessValue;
  typedef Compress::LookUpTableT<VALUE, INDEX> LookUpTable;
  // collect indices and remove duplicate values
  std::vector<INDEX> idcs; idcs.reserve(values.size());
  LookUpTable lookUp((LessValue(values)));
  INDEX iIn = 0, nOut = 0;
  for (const INDEX n = values.size(); iIn < n; ++iIn) {
    values[nOut] = values[iIn];
    std::pair<LookUpTable::iterator, bool> ret = lookUp.insert(nOut);
    if (ret.second) { // new index added?
      ++nOut; // remark value as stored
    }
    idcs.push_back(*ret.first); // store index
  }
  // discard all obsolete values
  values.resize(nOut);
  // done
  return idcs;
}

// instrumentation to take times

typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::microseconds USecs;
typedef decltype(std::chrono::duration_cast<USecs>(Clock::now() - Clock::now())) Time;

Time duration(const Clock::time_point &t0)
{
  return std::chrono::duration_cast<USecs>(Clock::now() - t0);
}

Time stopWatch(std::function<void()> func)
{
  const Clock::time_point t0 = Clock::now();
  func();
  return duration(t0);
}

// a minimal linear algebra tool set

template <typename VALUE>
struct Vec3T { VALUE x, y, z; };

template <typename VALUE>
Vec3T<VALUE> operator*(const Vec3T<VALUE> &vec, VALUE s) { return { vec.x * s, vec.y * s, vec.z * s }; }

template <typename VALUE>
Vec3T<VALUE> operator*(VALUE s, const Vec3T<VALUE> &vec) { return { s * vec.x, s * vec.y, s * vec.z }; }

template <typename VALUE>
Vec3T<VALUE> operator+(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
  return { vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z };
}

template <typename VALUE>
Vec3T<VALUE> operator-(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
  return { vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z };
}

template <typename VALUE>
VALUE length(const Vec3T<VALUE> &vec)
{
  return std::sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
}

template <typename VALUE>
VALUE dot(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
  return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
}

template <typename VALUE>
Vec3T<VALUE> cross(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
  return {
    vec1.y * vec2.z - vec1.z * vec2.y,
    vec1.z * vec2.x - vec1.x * vec2.z,
    vec1.x * vec2.y - vec1.y * vec2.x
  };
}

template <typename VALUE>
Vec3T<VALUE> normalize(const Vec3T<VALUE> &vec) { return (VALUE)1 / length(vec) * vec; }

// build sample - a tetraeder sponge

template <typename VALUE>
using StoreTriFuncT = std::function<void(const Vec3T<VALUE>&, const Vec3T<VALUE>&, const Vec3T<VALUE>&)>;

namespace TetraSponge {

template <typename VALUE>
void makeTetrix(
  const Vec3T<VALUE> &p0, const Vec3T<VALUE> &p1,
  const Vec3T<VALUE> &p2, const Vec3T<VALUE> &p3,
  StoreTriFuncT<VALUE> &storeTri)
{
  storeTri(p0, p1, p2);
  storeTri(p0, p2, p3);
  storeTri(p0, p3, p1);
  storeTri(p1, p3, p2);
}

template <typename VALUE>
void subDivide(
  unsigned depth,
  const Vec3T<VALUE> &p0, const Vec3T<VALUE> &p1,
  const Vec3T<VALUE> &p2, const Vec3T<VALUE> &p3,
  StoreTriFuncT<VALUE> &storeTri)
{
  if (!depth) { // build the 4 triangles
    makeTetrix(p0, p1, p2, p3, storeTri);
  } else {
    --depth;
    auto middle = [](const Vec3T<VALUE> &p0, const Vec3T<VALUE> &p1)
    {
      return 0.5f * p0 + 0.5f * p1;
    };
    const Vec3T<VALUE> p01 = middle(p0, p1);
    const Vec3T<VALUE> p02 = middle(p0, p2);
    const Vec3T<VALUE> p03 = middle(p0, p3);
    const Vec3T<VALUE> p12 = middle(p1, p2);
    const Vec3T<VALUE> p13 = middle(p1, p3);
    const Vec3T<VALUE> p23 = middle(p2, p3);
    subDivide(depth, p0, p01, p02, p03, storeTri);
    subDivide(depth, p01, p1, p12, p13, storeTri);
    subDivide(depth, p02, p12, p2, p23, storeTri);
    subDivide(depth, p03, p13, p23, p3, storeTri);
  }
}

} // namespace TetraSponge

template <typename VALUE>
void makeTetraSponge(
  unsigned depth, // recursion depth (values 0 ... 9 recommended)
  StoreTriFuncT<VALUE> &storeTri)
{
  TetraSponge::subDivide(depth,
    { -1, -1, -1 },
    { +1, +1, -1 },
    { +1, -1, +1 },
    { -1, +1, +1 },
    storeTri);
}

// minimal obj file writer

template <typename VALUE, typename INDEX>
void writeObjFile(
  std::ostream &out,
  const std::vector<Vec3T<VALUE>> &coords, const std::vector<INDEX> &idcsCoords,
  const std::vector<Vec3T<VALUE>> &normals, const std::vector<INDEX> &idcsNormals)
{
  assert(idcsCoords.size() == idcsNormals.size());
  out
    << "# Wavefront OBJ file\n"
    << "\n"
    << "# " << coords.size() << " coordinates\n";
  for (const Vec3 &coord : coords) {
    out << "v " << coord.x << " " << coord.y << " " << coord.z << '\n';
  }
  out
    << "# " << normals.size() << " normals\n";
  for (const Vec3 &normal : normals) {
    out << "vn " << normal.x << " " << normal.y << " " << normal.z << '\n';
  }
  out
    << "\n"
    << "g faces\n"
    << "# " << idcsCoords.size() / 3 << " triangles\n";
  for (size_t i = 0, n = idcsCoords.size(); i < n; i += 3) {
    out << "f "
      << idcsCoords[i + 0] + 1 << "//" << idcsNormals[i + 0] + 1 << ' '
      << idcsCoords[i + 1] + 1 << "//" << idcsNormals[i + 1] + 1 << ' '
      << idcsCoords[i + 2] + 1 << "//" << idcsNormals[i + 2] + 1 << '\n';
  }
}

template <typename VALUE, typename INDEX = size_t>
void writeObjFile(
  std::ostream &out,
  const std::vector<Vec3T<VALUE>> &coords, const std::vector<Vec3T<VALUE>> &normals)
{
  assert(coords.size() == normals.size());
  std::vector<INDEX> idcsCoords(coords.size());
  std::iota(idcsCoords.begin(), idcsCoords.end(), 0);
  std::vector<INDEX> idcsNormals(normals.size());
  std::iota(idcsNormals.begin(), idcsNormals.end(), 0);
  writeObjFile(out, coords, idcsCoords, normals, idcsNormals);
}

// main program (experiment)

template <typename VALUE>
bool operator<(const Vec3T<VALUE> &vec1, const Vec3T<VALUE> &vec2)
{
  return vec1.x < vec2.x ? true : vec1.x > vec2.x ? false
    : vec1.y < vec2.y ? true : vec1.y > vec2.y ? false
    : vec1.z < vec2.z;
}

using Vec3 = Vec3T<float>;
using StoreTriFunc = StoreTriFuncT<float>;

int main(int argc, char **argv)
{
  // read command line options
  if (argc <= 2) {
    std::cerr
      << "Usage:\n"
      << "> testCollectVtcs DEPTH FILE\n";
    return 1;
  }
  const unsigned depth = std::stoi(argv[1]);
  const std::string file = argv[2];
  std::cout << "Build sample...\n";
  std::vector<Vec3> coords, normals;
  { const Time t = stopWatch([&]() {
      StoreTriFunc storeTri = [&](const Vec3 &p0, const Vec3 &p1, const Vec3 &p2) {
        coords.push_back(p0); coords.push_back(p1); coords.push_back(p2);
        const Vec3 n = normalize(cross(p0 - p2, p1 - p2));
        normals.push_back(n); normals.push_back(n); normals.push_back(n);
      };
      makeTetraSponge(depth, storeTri);
    });
    std::cout << "Done after " << t.count() << " us.\n";
  }
  std::cout << "coords: " << coords.size() << ", normals: " << normals.size() << '\n';
  const std::string fileUncompr = file + ".uncompressed.obj";
  std::cout << "Write uncompressed OBJ file '" << fileUncompr << "'...\n";
  { const Time t = stopWatch([&]() {
      std::ofstream fOut(fileUncompr.c_str(), std::ios::binary);
      /* std::ios::binary -> force Unix line-endings on Windows
       * to win some extra bytes
       */
      writeObjFile(fOut, coords, normals);
      fOut.close();
      if (!fOut.good()) {
        std::cerr << "Writing of '" << fileUncompr << "' failed!\n";
        throw std::ios::failure("Failed to complete writing of file!");
      }
    });
    std::cout << "Done after " << t.count() << " us.\n";
  }
  std::cout << "Compress coordinates and normals...\n";
  std::vector<size_t> idcsCoords, idcsNormals;
  { const Time t = stopWatch([&]() {
      idcsCoords = compress(coords);
      idcsNormals = compress(normals);
    });
    std::cout << "Done after " << t.count() << " us.\n";
  }
  std::cout
    << "coords: " << coords.size() << ", normals: " << normals.size() << '\n'
    << "coord idcs: " << idcsCoords.size() << ", normals: " << normals.size() << '\n';
  const std::string fileCompr = file + ".compressed.obj";
  std::cout << "Write compressed OBJ file'" << fileCompr << "'...\n";
  { const Time t = stopWatch([&]() {
      std::ofstream fOut(fileCompr.c_str(), std::ios::binary);
      /* std::ios::binary -> force Unix line-endings on Windows
       * to win some extra bytes
       */
      writeObjFile(fOut, coords, idcsCoords, normals, idcsNormals);
      fOut.close();
      if (!fOut.good()) {
        std::cerr << "Writing of '" << fileCompr << "' failed!\n";
        throw std::ios::failure("Failed to complete writing of file!");
      }
    });
    std::cout << "Done after " << t.count() << " us.\n";
  }
  std::cout << "Done.\n";
}

第一次检查:

代码语言:javascript
复制
> testCollectVtcs
Usage:
> testCollectVtcs DEPTH FILE

> testCollectVtcs 1 test1
Build sample...
Done after 34 us.
coords: 48, normals: 48
Write uncompressed OBJ file 'test1.uncompressed.obj'...
Done after 1432 us.
Compress coordinates and normals...
Done after 12 us.
coords: 10, normals: 4
coord idcs: 48, normals: 4
Write compressed OBJ file'test1.compressed.obj'...
Done after 1033 us.
Done.

这产生了两个文件:

代码语言:javascript
复制
$ ls test1.*.obj
-rw-r--r-- 1 Scheff 1049089  553 Mar 26 11:46 test1.compressed.obj
-rw-r--r-- 1 Scheff 1049089 2214 Mar 26 11:46 test1.uncompressed.obj

$
代码语言:javascript
复制
$ cat test1.uncompressed.obj
# Wavefront OBJ file

# 48 coordinates
v -1 -1 -1
v 0 0 -1
v 0 -1 0
v -1 -1 -1
v 0 -1 0
v -1 0 0
v -1 -1 -1
v -1 0 0
v 0 0 -1
v 0 0 -1
v -1 0 0
v 0 -1 0
v 0 0 -1
v 1 1 -1
v 1 0 0
v 0 0 -1
v 1 0 0
v 0 1 0
v 0 0 -1
v 0 1 0
v 1 1 -1
v 1 1 -1
v 0 1 0
v 1 0 0
v 0 -1 0
v 1 0 0
v 1 -1 1
v 0 -1 0
v 1 -1 1
v 0 0 1
v 0 -1 0
v 0 0 1
v 1 0 0
v 1 0 0
v 0 0 1
v 1 -1 1
v -1 0 0
v 0 1 0
v 0 0 1
v -1 0 0
v 0 0 1
v -1 1 1
v -1 0 0
v -1 1 1
v 0 1 0
v 0 1 0
v -1 1 1
v 0 0 1
# 48 normals
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735
vn 0.57735 0.57735 0.57735

g faces
# 16 triangles
f 1//1 2//2 3//3
f 4//4 5//5 6//6
f 7//7 8//8 9//9
f 10//10 11//11 12//12
f 13//13 14//14 15//15
f 16//16 17//17 18//18
f 19//19 20//20 21//21
f 22//22 23//23 24//24
f 25//25 26//26 27//27
f 28//28 29//29 30//30
f 31//31 32//32 33//33
f 34//34 35//35 36//36
f 37//37 38//38 39//39
f 40//40 41//41 42//42
f 43//43 44//44 45//45
f 46//46 47//47 48//48

$
代码语言:javascript
复制
$ cat test1.compressed.obj
# Wavefront OBJ file

# 10 coordinates
v -1 -1 -1
v 0 0 -1
v 0 -1 0
v -1 0 0
v 1 1 -1
v 1 0 0
v 0 1 0
v 1 -1 1
v 0 0 1
v -1 1 1
# 4 normals
vn 0.57735 -0.57735 -0.57735
vn -0.57735 -0.57735 0.57735
vn -0.57735 0.57735 -0.57735
vn 0.57735 0.57735 0.57735

g faces
# 16 triangles
f 1//1 2//1 3//1
f 1//2 3//2 4//2
f 1//3 4//3 2//3
f 2//4 4//4 3//4
f 2//1 5//1 6//1
f 2//2 6//2 7//2
f 2//3 7//3 5//3
f 5//4 7//4 6//4
f 3//1 6//1 8//1
f 3//2 8//2 9//2
f 3//3 9//3 6//3
f 6//4 9//4 8//4
f 4//1 7//1 9//1
f 4//2 9//2 10//2
f 4//3 10//3 7//3
f 7//4 10//4 9//4

$

所以,这就是我说出来的

  • 48坐标对10坐标
  • 正常人48人,正常人4人。

这是,这个样子:

(我看不出test1.compressed.obj有什么视觉差异。)

关于停止观察的时间,我不会太相信他们。因此,样本太小了。

因此,另一个具有更多几何学(更多)的测试:

代码语言:javascript
复制
> testCollectVtcs 8 test8
Build sample...
Done after 40298 us.
coords: 786432, normals: 786432
Write uncompressed OBJ file 'test8.uncompressed.obj'...
Done after 6200571 us.
Compress coordinates and normals...
Done after 115817 us.
coords: 131074, normals: 4
coord idcs: 786432, normals: 4
Write compressed OBJ file'test8.compressed.obj'...
Done after 1513216 us.
Done.

>

这两个档案:

代码语言:javascript
复制
$ ls -l test8.*.obj
-rw-r--r-- 1 ds32737 1049089 11540967 Mar 26 11:56 test8.compressed.obj
-rw-r--r-- 1 ds32737 1049089 57424470 Mar 26 11:56 test8.uncompressed.obj

$

概括地说:

  • 11 MBytes对56 MBytes。
  • 压缩和写入:0.12s+ 1.51 s= 1.63 s
  • 与编写未压缩的: 6.2

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60857532

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档