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

未使用Assimp加载OBJ文件时的法线加载Bug求助

问题:自定义OBJ加载器加载法线后模型变形

我正在开发不依赖Assimp的OBJ文件加载程序,遇到如下问题:不加载法线时,模型在世界空间显示正常;加载法线后,模型形状发生扭曲变形。尝试过新增法线索引缓冲区、定义Face结构体等方法,均未解决(曾出现LNK2019链接错误),求帮助。

显示效果对比

  • 带法线:模型几何结构扭曲,形状错误
  • 不带法线:立方体模型显示正常,几何结构正确

顶点数据加载代码

myFile.open("3dModels/Box.obj");
struct  Vertex
{
    XMFLOAT4 Pos;
    XMFLOAT3 Normal;
    //XMFLOAT2 Texture;
};  
std::vector<Vertex> vertices(14);

std::vector<unsigned int> indices;
std::vector<UINT> normalIndice;

std::string Space_delimeter = " ";
int index=0;
int index1 = 0;
int index2= 0;
while (std::getline(myFile, line)) {
    std::istringstream lineStream(line);
    std::string lineType;
    lineStream >> lineType;
    if (line.starts_with("v "))
    {
        
        Vertex vertex;
        lineStream >> vertex.Pos.x >> vertex.Pos.y >> vertex.Pos.z;
        vertices[index1].Pos.x = vertex.Pos.x;
        vertices[index1].Pos.y = vertex.Pos.y;
        vertices[index1].Pos.z = vertex.Pos.z;
        vertices[index1].Pos.w = 1.0f;
        index1++;
    }
    else if (lineType == "vn") {
        // vertex normal line
        
        float nx, ny, nz;
        lineStream >> nx >> ny >> nz;
        vertices[index].Normal.x = nx;
        vertices[index].Normal.y = ny;
        vertices[index].Normal.z = nz;
        index++;

    }
    else if (lineType == "vt") {
        //CURENTLY NOT USING TEXTURES
        // vertex texture line
        float u, v;
        lineStream >> u >> v;
        //vertices[index2].Texture.x = u;
        //vertices[index2].Texture.y = v;
        index2++;
    }
    else if (line.starts_with("f "))
    {
        std::vector<std::string> values{};
        size_t pos = 0;
        while ((pos = line.find(Space_delimeter)) != string::npos) {
            pos = line.find(Space_delimeter);
            values.push_back(line.substr(0, pos));
            line.erase(0, pos + Space_delimeter.length());
        }
        values.push_back(line.substr(0, pos));
        
        for (int i=1;i<values.size();i++)
        {
            std::string Slash_delimeter = "/";
            std::string linePart = values[i];

            size_t pos1 = linePart.find(Slash_delimeter);
            std::string valueStr = linePart.substr(0, pos1);
            //Converting from string to int (vertices)
            unsigned int indice = 0;
            for (int j = 0; j < valueStr.length(); j++) {
                indice = indice * 10 + (valueStr[j] - '0');
            }
            indices.push_back(indice - 1);
            //Setting normals indices
            
            linePart= linePart.erase(0, linePart.find(Slash_delimeter) + Slash_delimeter.length());
            linePart = linePart.erase(0, linePart.find(Slash_delimeter) + Slash_delimeter.length());
            linePart = linePart.erase(0, linePart.find(Slash_delimeter) + Slash_delimeter.length());
            //Converting from string to int (normals)
            unsigned int indice1=0;
            for (int j = 0; j < linePart.length(); j++) {
                indice1 = indice1 * 10 + (linePart[j] - '0');
            }
            normalIndice.push_back(indice1-1);
        }
    }

}

AddStaticBind(std::make_unique<PlaneVertexBuffer>(gfx, vertices));

auto pvs = std::make_unique<VertexShader>(gfx, L"VertexShader.cso");
auto pvsbc = pvs->GetBytecode();
AddStaticBind(std::move(pvs));

AddStaticBind(std::make_unique<PixelShader>(gfx, L"PixelShader.cso"));

        
AddStaticIndexBuffer(std::make_unique<IndexBuffer>(gfx, indices,normalIndice));

//auto type =IndexBuffer::IndiceType::NormalIndice;
//AddStaticNormalIndexBuffer(std::make_unique<IndexBuffer>(gfx, normalIndice,type));

const std::vector<D3D11_INPUT_ELEMENT_DESC> ied =
{
    {"Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"Normal", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,D3D11_APPEND_ALIGNED_ELEMENT,D3D11_INPUT_PER_VERTEX_DATA, 0}
};
AddStaticBind(std::make_unique<InputLayout>(gfx, ied, pvsbc));

AddStaticBind(std::make_unique<Topology>(gfx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST));

IndexBuffer实现代码

IndexBuffer::IndexBuffer(Graphics& gfx, const std::vector<unsigned int>& indices, const std::vector<unsigned int>& normalIndices)
    :
    count((UINT)indices.size()+normalIndices.size())
{
    
    std::vector<Face> faces(indices.size());
    for (int i = 0; i < indices.size(); i++) {
        faces[i]=(Face(indices[i], normalIndices[i]));
    }
    D3D11_BUFFER_DESC ibd = {};
    ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    ibd.Usage = D3D11_USAGE_DEFAULT;
    ibd.CPUAccessFlags = 0u;
    ibd.MiscFlags = 0u;
    ibd.ByteWidth = UINT(count * sizeof(unsigned int));
    ibd.StructureByteStride = sizeof(unsigned int);
    D3D11_SUBRESOURCE_DATA isd = {};
    isd.pSysMem = faces.data();
    GetDevice(gfx)->CreateBuffer(&ibd, &isd, &pIndexBuffer);
}

VertexShader代码

cbuffer CBuf
{
    matrix transform;
    matrix model;
    matrix modelViewProj;
};

struct VSOut
{
    float4 worldPos : Position;
    float3 normal : Normal;
    float4 position : SV_Position;
};

struct VS_INPUT
{
    float3 position : Position;
    float3 normal : Normal;
};

VSOut main(VS_INPUT input )
{
    VSOut vso;
    vso.position = mul(float4(input.position,1.0f), transform);
    vso.normal = input.normal;
    return vso;
}

问题根源及修复方案

1. OBJ法线索引解析逻辑错误

OBJ的f行格式为v/vt/vn,你在解析法线索引时错误执行了三次erase,导致跳过所有有效内容,解析出的法线索引完全无效。正确解析方式:

std::string linePart = values[i];
// 提取顶点索引
size_t slash1 = linePart.find('/');
unsigned int v_idx = stoi(linePart.substr(0, slash1)) - 1;
linePart.erase(0, slash1 + 1);

// 跳过纹理索引(即使为空)
size_t slash2 = linePart.find('/');
linePart.erase(0, slash2 + 1);

// 提取法线索引
unsigned int vn_idx = stoi(linePart) - 1;
normalIndice.push_back(vn_idx);

2. IndexBuffer实现完全错误

Direct3D索引缓冲区只能存储单一类型的顶点索引,不能存储Face结构体。你将Face数据传入索引缓冲区,导致GPU读取的索引值混乱,这是模型变形的核心原因。

正确逻辑:OBJ中顶点的唯一标识是(v, vt, vn)的组合,同一位置顶点对应不同法线时,需拆分出独立顶点实例:

  • 用std::map<std::tuple<int, int, int>, unsigned int>记录每个组合对应的顶点索引
  • 解析f行时,检查当前(v_idx, vt_idx, vn_idx)是否已存在:
    • 存在:直接使用对应顶点索引加入indices
    • 不存在:从v和vn列表取数据创建新Vertex,加入vertices并记录映射关系,再将新索引加入indices
  • 最终indices仅存储顶点缓冲区的索引,无需单独法线索引缓冲区

修正后的IndexBuffer:

IndexBuffer::IndexBuffer(Graphics& gfx, const std::vector<unsigned int>& indices)
    : count((UINT)indices.size())
{
    D3D11_BUFFER_DESC ibd = {};
    ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    ibd.Usage = D3D11_USAGE_DEFAULT;
    ibd.CPUAccessFlags = 0u;
    ibd.MiscFlags = 0u;
    ibd.ByteWidth = UINT(count * sizeof(unsigned int));
    ibd.StructureByteStride = sizeof(unsigned int);
    D3D11_SUBRESOURCE_DATA isd = {};
    isd.pSysMem = indices.data();
    GetDevice(gfx)->CreateBuffer(&ibd, &isd, &pIndexBuffer);
}

3. 顶点初始化与输入布局不匹配

  • 你固定初始化了14个顶点,OBJ顶点数量可能不符,改为动态添加:std::vector<Vertex> vertices;,解析v时用vertices.push_back(vertex)
  • Vertex结构体中Pos是XMFLOAT4,但输入布局中Position格式为DXGI_FORMAT_R32G32B32_FLOAT,导致数据偏移错误。修正输入布局:
const std::vector<D3D11_INPUT_ELEMENT_DESC> ied =
{
    {"Position", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0,D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"Normal", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,D3D11_APPEND_ALIGNED_ELEMENT,D3D11_INPUT_PER_VERTEX_DATA, 0}
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 09:11:31