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

如何编写OBJ解析器实现为每个顶点匹配对应法向量的功能

实现方案

核心问题

OBJ格式的面(f)索引是三组独立的索引,分别对应顶点位置、纹理坐标、法向量,三者没有绑定关系。你当前的代码只提取了顶点位置索引,直接用该索引去法向量数组取值,自然会出现数量不匹配、对应关系错误的问题。

具体实现步骤

  • 首先完善面解析逻辑:修改ParseFace函数的返回结构,除了顶点位置索引VertexIndices,新增纹理坐标索引TexIndices、法向量索引NormalIndices,把f v/vt/vn格式里的三组索引全部解析出来,注意OBJ索引默认从1开始,解析后要减1适配C++数组从0开始的规则。
  • 定义渲染顶点结构:将「位置索引+纹理索引+法向量索引」三个值打包为一个Key,用来判断是否是同一个可渲染顶点,也可以直接把三个属性值打包作为Key。
  • 构建索引映射表:用std::map或者std::unordered_map存储Key到最终顶点索引的映射,遍历所有面的所有顶点组合:
    • 如果当前Key不存在于映射表中:新建一个顶点,从已缓存的位置数组、UV数组、法向量数组中分别取出对应索引的属性,存入Mesh的最终顶点数组、UV数组、法向量数组,将新生成的索引存入映射表,同时把该索引加入Mesh的Indices数组。
    • 如果当前Key已经存在:直接取映射表中对应的索引加入Indices数组即可。
  • 边界适配:如果解析的OBJ文件没有法向量/UV数据,可以选择自动计算面法向量赋值给对应顶点,或者默认填充默认值即可。

代码修改示例

// 先定义用来做去重的Key结构
struct VertexKey {
    int v_idx;
    int vt_idx;
    int vn_idx;
    // 重载==用于比较
    bool operator==(const VertexKey& other) const {
        return v_idx == other.v_idx && vt_idx == other.vt_idx && vn_idx == other.vn_idx;
    }
};

// 给Key定义哈希函数,用于unordered_map
namespace std {
    template<> struct hash<VertexKey> {
        size_t operator()(const VertexKey& k) const {
            return ((hash<int>()(k.v_idx) ^ (hash<int>()(k.vt_idx) << 1)) >> 1) ^ (hash<int>()(k.vn_idx) << 1);
        }
    };
}

// 修改后的LoadMeshData逻辑
void LoadMeshData(mesh_t* Mesh, char* FileName)
{
    FILE* file = NULL;
    fopen_s(&file, FileName, "r");
    
    // 临时存储从OBJ读取的原始属性数组,不要直接存入Mesh
    std::vector<v3> temp_vertices;
    std::vector<tex2d> temp_uvs;
    std::vector<v3> temp_normals;
    std::unordered_map<VertexKey, uint32_t> key_to_idx;

    char Line[256];
    while(fgets(Line, 256, (FILE*)file) != NULL)
    {
        int32_t CharIdx = 0;
        char PrevChar = 0;
        char Char = Line[CharIdx];

        while(Char)
        {
            Char = Line[CharIdx++];
            if(Char == '#' || Char == 'm' || Char == 'o' && Char == 'u' || Char == 's') break;
            if((PrevChar == 'v' && Char == 't')) 
            {
                v3 Coords = ParseVertex(Line);
                tex2d UVCoords = V3ToUVs(Coords);
                temp_uvs.push_back(UVCoords);
                break;
            }
            if((PrevChar == 'v' && Char == 'n'))
            {
                v3 Normals = ParseVertex(Line);
                temp_normals.push_back(Normals);
                break;
            }

            if((PrevChar == 'v' && Char == ' '))
            {
                v3 Vert = ParseVertex(Line);
                temp_vertices.push_back(Vert);
                break;
            } 
            if((PrevChar == 'f' && Char == ' '))
            {
                parsed_obj Obj = ParseFace(Line); // 需提前修改ParseFace返回三组索引
                // 遍历面的三个顶点
                for(int i=0; i<3; i++) {
                    VertexKey key = {
                        Obj.VertexIndices[i]-1, 
                        Obj.TexIndices[i]-1, 
                        Obj.NormalIndices[i]-1
                    };
                    // 不存在则新增顶点
                    if(key_to_idx.find(key) == key_to_idx.end()) {
                        Mesh->Vertices.push_back(temp_vertices[key.v_idx]);
                        Mesh->TextureCoords.push_back(temp_uvs[key.vt_idx]);
                        Mesh->Normals.push_back(temp_normals[key.vn_idx]);
                        key_to_idx[key] = Mesh->Vertices.size() - 1;
                    }
                    // 添加索引
                    Mesh->Indices.push_back(key_to_idx[key]);
                }
                break;
            }
            PrevChar = Char;
        }
    }
    fclose(file);
}

按照上述逻辑处理后,Mesh的Vertices、Normals、TextureCoords数组长度会完全一致,每个索引值对应的三个属性完全匹配,即可实现每个顶点对应唯一法向量的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 20:36:10