如何根据 Wavefront (.obj) 文件中给出的纹理索引对纹理位置进行排序?

2023-11-20

我目前正在尝试为 OpenGL 项目制作一个 Wavefront (.obj) 文件加载器。我当前使用的方法是逐行分离向量(std::vectors)中的顶点位置、纹理位置和法线位置,并且我将它们的索引(顶点、纹理和法线索引)存储在三个单独的文件中向量(来自文件的“f”行,对于每个面)。

我无法根据纹理索引对充满纹理坐标的向量进行排序。我能够在正确的位置渲染顶点,因为我的“加载程序”类需要索引,但我无法弄清楚如何以任何方式对纹理坐标进行排序,因此纹理看起来在某些三角形上偏移结果。

具有偏移纹理的立方体图像:

img

纹理图像 (.png),它在每个面上的样子:

img

编辑:这是 .obj 文件和 .mtl 文件的链接。谷歌云端硬盘。

这是我的 OBJLoader.cpp 文件:

    rawObj.open(filePath); // Open file

    while (!rawObj.eof()) {
        getline(rawObj, line); // Read line

        // Read values from each line 
        // starting with a 'v' for 
        // the vertex positions with
        // a custom function (gets the word in a line
        // at position i)

        if (strWord(line, 1) == "v") {   
            for (int i = 2; i <= 4; i++) {
                std::string temp;
                temp = strWord(line, i);
                vertexStrings.push_back(temp);
            }

        // Same for texture positions

        } else if (strWord(line, 1) == "vt") {     
            for (int i = 2; i <= 3; i++) {
                std::string temp;
                temp = strWord(line, i);
                textureStrings.push_back(temp);
            }

        // Same for normal positions

        } else if (strWord(line, 1) == "vn") {     // normals
            for (int i = 2; i <= 4; i++) {
                std::string temp;
                temp = strWord(line, i);
                normalStrings.push_back(temp);
            }

        // Separate each of the three vertices and then separate 
        // each vertex into its vertex index, texture index and
        // normal index

        } else if (strWord(line, 1) == "f") {      // faces (indices)
            std::string temp;

            for (int i = 2; i <= 4; i++) {
                temp = strWord(line, i);
                chunks.push_back(temp);

                k = std::stoi(strFaces(temp, 1));
                vertexIndices.push_back(k-1);

                l = std::stoi(strFaces(temp, 2));
                textureIndices.push_back(l-1);

                m = std::stoi(strFaces(temp, 3));
                normalIndices.push_back(m-1);

            }

        }
    }

    // Convert from string to float

    for (auto &s : vertexStrings) {
        std::stringstream parser(s);
        float x = 0;

        parser >> x;

        vertices.push_back(x);
    }

    for (auto &s : textureStrings) {
        std::stringstream parser(s);
        float x = 0;

        parser >> x;

        texCoords.push_back(x);
    }

    // Y coords are from top left instead of bottom left
    for (int i = 0; i < texCoords.size(); i++) {
        if (i % 2 != 0)
            texCoords[i] = 1 - texCoords[i];
    }

    // Passes vertex positions, vertex indices and texture coordinates 
    // to loader class
    return loader.loadToVao(vertices, vertexIndices, texCoords);
}

我尝试在循环中插入 texCoords[textureIndices[i]] 中的值 (vector.insert),但这不起作用并使输出变得更糟。我尝试了一个简单的:

tempVec[i] = texCoords[textureIndices[i]] 

在 for 循环中,但这也不起作用。

我已经完成了整个项目,并且确定排序是问题的原因,因为当我为立方体插入硬编码值时,它工作得很好,并且纹理根本没有偏移。 (OpenGL 命令/图像加载器正在正常工作。)

最终,是否有另一种方法可以根据textureIndices对texCoords进行排序?


如果顶点坐标和纹理坐标有不同的索引,则顶点位置必须是“重复的”。
顶点坐标及其属性(如纹理坐标)形成一个元组。每个顶点坐标必须有自己的纹理坐标和属性。您可以将 3D 顶点坐标和 2D 纹理坐标视为单个 5D 坐标。
请参见渲染具有多个索引的网格.

假设您有一个.obj像这样的文件:

v -1 -1 -1
v  1 -1 -1
v -1  1 -1
v  1  1 -1
v -1 -1  1
v  1 -1  1
v -1  1  1
v  1  1  1 

vt 0 0
vt 0 1
vt 1 0
vt 1 1

vn -1  0  0 
vn  0 -1  0
vn  0  0 -1
vn  1  0  0
vn  0  1  0
vn  0  0  1

f 3/1/1 1/2/1 5/4/1 7/3/1
f 1/1/2 2/2/2 3/4/2 6/3/2
f 3/1/3 4/2/3 2/4/3 1/3/3
f 2/1/4 4/2/4 8/4/4 6/3/4
f 4/1/5 3/2/5 7/4/5 8/3/5
f 5/1/6 6/2/6 8/4/6 7/3/6

由此,您必须找到顶点坐标、纹理纹理坐标和法线向量索引的所有组合,这些组合用于面规范中:

 0 : 3/1/1 
 1 : 1/2/1
 2 : 5/4/1
 3 : 7/3/1
 4 : 1/1/2
 5 : 2/2/2
 6 : 3/4/2
 7 : 6/3/2
 8 : ...

然后你必须创建一个与索引组合数组相对应的顶点坐标、纹理坐标和法线向量数组。 顶点坐标及其属性可以合并到一个数组中以形成数据集,也可以合并到具有相同数量属性的三个数组中:

 index   vx vy vz     u v     nx ny nz
 0 :     -1  1 -1     0 0     -1  0  0
 1 :     -1 -1 -1     0 1     -1  0  0
 2 :     -1 -1  1     1 1     -1  0  0
 3 :     -1  1  1     1 0     -1  0  0
 4 :     -1 -1 -1     0 0      0 -1  0
 5 :      1 -1 -1     0 1      0 -1  0
 6 :     -1  1 -1     1 1      0 -1  0
 7 :      1 -1  1     1 0      0 -1  0
 8 : ...

看一下非常简单的c++函数,它可以读取一个.obj文件,就像您链接到的那样。 该函数读取文件并将数据写入元素向量和属性向量。

注意,该函数可以优化并且不关心性能。 对于小文件(例如立方体3.obj你喜欢的),这并不重要,但对于一个大文件来说, 尤其是索引表中的线性搜索,还需要改进。

我只是想让你知道如何阅读.obj文件以及如何创建元素和属性向量,可以直接使用 OpenGL 来绘制网格。

#include <vector>
#include <array>
#include <string>
#include <fstream>
#include <strstream>
#include <algorithm>

bool load_obj( 
    const std::string          filename, 
    std::vector<unsigned int> &elements,
    std::vector<float>        &attributes )
{
    std::ifstream obj_stream( filename, std::ios::in );
    if( !obj_stream )
        return false;

    // parse the file, line by line
    static const std::string white_space = " \t\n\r";
    std::string token, indices, index;
    float value;
    std::vector<float> v, vt, vn;
    std::vector<std::array<unsigned int, 3>> f;
    for( std::string line; std::getline( obj_stream, line ); )
    {
        // find first non whispce characterr in line
        size_t start = line.find_first_not_of( white_space );
        if ( start == std::string::npos )
            continue;

        // read the first token
        std::istringstream line_stream( line.substr(start) );
        line_stream.exceptions( 0 );
        line_stream >> token;

        // ignore comment lines
        if ( token[0] == '#' )
            continue;

        // read the line
        if ( token == "v" ) // read vertex coordinate
        {
            while ( line_stream >> value )  
                v.push_back( value );
        }
        else if ( token == "vt" ) // read normal_vectors 
        {
            while ( line_stream >> value )
                vt.push_back( value );
        }
        else if ( token == "vn" )  // read normal_vectors 
        {
            while ( line_stream >> value )
                vn.push_back( value );
        }
        else if ( token == "f" )
        {
            // read faces
            while( line_stream >> indices )
            {
                std::array<unsigned int, 3> f3{ 0, 0, 0 };
                // parse indices
                for ( int j=0; j<3; ++ j )
                {
                    auto slash = indices.find( "/" );
                    f3[j] = std::stoi(indices.substr(0, slash), nullptr, 10);
                    if ( slash == std::string::npos )
                        break;
                    indices.erase(0, slash + 1);
                }
            
                // add index
                auto it = std::find( f.begin(), f.end(), f3 );
                elements.push_back( (unsigned int)(it - f.begin()) );
                if ( it == f.end() )
                    f.push_back( f3 );
            }
        }
    }

    // create array of attributes from the face indices
    for ( auto f3 : f )
    {
        if ( f3[0] > 0 )
        {
            auto iv = (f3[0] - 1) * 3;
            attributes.insert( attributes.end(), v.begin() + iv, v.begin() + iv + 3 );
        }

        if ( f3[1] > 0 )
        {
            auto ivt = (f3[1] - 1) * 2;
            attributes.insert( attributes.end(), vt.begin() + ivt, vt.begin() + ivt + 2 );
        }

        if ( f3[2] > 0 )
        {
            auto ivn = (f3[2] - 1) * 3;
            attributes.insert( attributes.end(), vn.begin() + ivn, vn.begin() + ivn + 3 );
        }
    }

    return true;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何根据 Wavefront (.obj) 文件中给出的纹理索引对纹理位置进行排序? 的相关文章

  • 编译时运算符

    有人可以列出 C 中可用的所有编译时运算符吗 C 中有两个运算符 无论操作数如何 它们的结果始终可以在编译时确定 它们是sizeof 1 and 2 当然 其他运算符的许多特殊用途可以在编译时解决 例如标准中列出的那些整数常量表达式 1 与
  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 通过 CMIS (dotCMIS) 连接到 SP2010:异常未经授权

    我正在使用 dotCMIS 并且想要简单连接到我的 SP2010 服务器 我尝试用 C 来做到这一点 如下所示http chemistry apache org dotnet getting started with dotcmis htm
  • “构建”构建我的项目,“构建解决方案”则不构建

    我刚刚开始使用VS2010 我有一个较大的解决方案 已从 VS2008 成功迁移 我已将一个名为 Test 的控制台应用程序项目添加到解决方案中 选择构建 gt 构建解决方案不编译新项目 选择构建 gt 构建测试确实构建了项目 在失败的情况
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new

随机推荐

  • Linux 导航和文件管理

    介绍 导航和操作文件系统中的文件和文件夹是使用大多数计算机的关键部分 云服务器大多使用相同的常见 Linux shell 和常见 Linux 命令来处理文件和文件夹 本终端将介绍使用这些命令的一些基本技能 先决条件和目标 为了遵循本指南 您
  • 如何在 Ubuntu 18.04 上安装 Elasticsearch、Logstash 和 Kibana (Elastic Stack)

    笔者精选互联网档案馆接受捐赠作为为捐款而写程序 介绍 Elastic Stack 以前称为ELK堆栈 是由以下公司制作的开源软件集合Elastic它允许您搜索 分析和可视化从任何来源以任何格式生成的日志 这种做法称为集中记录 当尝试识别服务
  • Log4j2 示例教程 - 配置、级别、Appender

    欢迎使用 Apache Log4j2 示例教程 如果您向专家开发人员询问应用程序中最烦人的事情 答案可能与日志记录有关 如果应用程序中没有合适的日志记录 维护将是一场噩梦 大多数应用程序都会经过开发测试 单元测试 集成测试 但当涉及到生产时
  • 如何在 Debian 8 上安装 Linux、Apache、MySQL、PHP (LAMP) 堆栈

    介绍 LAMP 软件堆栈 包括LLinux操作系统 A阿帕奇网络服务器 MmySQL 数据库 以及PHP 脚本语言是 Web 或应用程序开发的良好基础 安装在一起后 该软件堆栈使您的服务器能够托管动态网站和 Web 应用程序 在本教程中 我
  • 如何在 Linux 上安装 TestDisk 并恢复已删除的文件

    您是否曾经遇到过不小心删除文件的情况 在本教程中 我们将介绍如何在 Linux 中安装 TestDisk 并恢复已删除的文件 在本教程中 我将使用 Ubuntu 服务器进行工作 但即使您使用任何其他发行版 也可以遵循相同的步骤 唯一不同的是
  • 将QChartView插入到ui中

    我想把在同一个 qtchart 上绘制烛台和 5 天平均线 但给出两个 x 轴图将代码写入 UI 加载器 import sys from PyQt5 QtWidgets import QApplication QWidget from Py
  • 如何将 PowerShell 变量返回到 VBScript

    我有一个 vbscript 来调用 PowerShell 脚本 希望将 PowerShell 输出返回到 HTA HTML 应用程序 GUI 现在我只想看看是否可以将 PowerShell 输出返回到 vbscript 中的 MsgBox
  • 如何找到 .bash_profile 并将其添加到 shell 的初始化文件中? [关闭]

    Closed 这个问题是无关 目前不接受答案 我正在尝试使用 rvm 升级 ruby 上务实网站 它说 重要的部分是将以下行添加到 shell 初始化文件 bash profile 的末尾 s HOME rvm scripts rvm so
  • 卸载 RVM 后 Zshell 启动,退出状态为 1

    我刚刚卸载了rvm 我跑了rvm implode并从中删除了rvm PATH in my zshrc 如指定这个堆栈溢出帖子 卸载后 我注意到我的 shell 启动的退出状态为1 我已经使用它检查过echo 加载 shell 后 我总是得到
  • 按位异或两个数字会得到数字的和或差

    当我对任意两个数字进行异或时 我得到的是它们的差值或总和的绝对值 我在谷歌上搜索了很多 试图找到任何相关的公式 但对此没有明显的公式或陈述 Example 10 XOR 2 1010 XOR 10 1000 8 1 XOR 2 01 XOR
  • 使用for循环在画布上绘制线条

    我正在尝试用画布绘制线条 并使用 for 循环更改坐标 这是我的画布元素
  • PostgreSQL 中数组是否全部为 NULL

    如果 PostgreSQL 数组的所有元素都为 NULL 是否有一个表达式返回 TRUE 如果它是 NULL 以外的值 我当然可以使用类似以下内容的值 SELECT 4 ALL ARRAY 4 5 integer 但是我想用一个进行 ALL
  • __printflike__ 修饰符

    printflike 修饰符 到底是什么 这个词是什么意思 据猜测 它告诉编译器您正在使用的函数采用以下形式的参数 anything format 哪里的format 部分看起来像参数printf The printflike 属性允许编译
  • 像 Google 使用的滚动条

    随着 Google 推出的最新更新 所有网站都获得了自定义 JS 滚动条 至少在 Chrome 中 我最喜欢它的一点是它简单且运行完美 到目前为止 我见过的很多 JS 滚动条都不能很好地工作 也就是说 如果你滚动得非常快或者滚动并移动鼠标
  • Jquery 延迟加载与 ajax

    我在我的电子商务网站上使用lazyload 惰性加载 效果很好 我使用这段代码来做到这一点 function img lazy lazyload effect fadeIn 还有一些过滤器 如颜色 价格等 运行 ajax 并显示新结果 当新
  • SwiftUI/Combine:订阅@Binding的值变化

    我有一个带有视图模型的视图 该视图中的操作可以更改视图模型 为了能够将逻辑分解为可重用的部分 我将视图的一部分作为其自己的视图 并对其所需的值使用 Binding 现在 我希望能够根据值更改执行一些逻辑 而不必只是视图更改 我怎样才能做到这
  • 如何使用多个命令启动 cmd.exe /k?

    为什么下面的代码不改变颜色和标题cmd2 怎么做以及做什么 该命令改变颜色cmd1 并将标题设置为cmd2 start cmd exe k TITLE TEST color 02 mode con cols 160 lines 78 sta
  • Android 序列化问题

    我创建了一个类 它有几个成员变量 所有这些变量都是可序列化的 除了一个位图 我尝试扩展位图并实现可序列化 但不认为位图是最终类 我想保存该类 它基本上构成了游戏的当前状态 以便玩家可以拾取并加载游戏 在我看来 我有两个选择 1 寻找另一种保
  • 如何为 java HttpURLConnection 流量启用线路日志记录?

    我用过雅加达公共 HttpClient在另一个项目中 我也想要同样的电线记录输出但使用 标准 HttpUrlConnection 我用过Fiddler作为代理 但我想直接从 java 记录流量 捕获连接输入和输出流的内容是不够的 因为 HT
  • 如何根据 Wavefront (.obj) 文件中给出的纹理索引对纹理位置进行排序?

    我目前正在尝试为 OpenGL 项目制作一个 Wavefront obj 文件加载器 我当前使用的方法是逐行分离向量 std vectors 中的顶点位置 纹理位置和法线位置 并且我将它们的索引 顶点 纹理和法线索引 存储在三个单独的文件中