OpenGL:奇怪的法线渲染

OpenGL: Strange normal rendering

我对 OpenGL 法线有疑问。 我正在渲染龙模型,但我有一些奇怪的正常模式。

这是我渲染的截图: render_screen

这是我的缓冲区创建方法:

glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    if (has_position) {
        glGenBuffers(1, &vertex_buffer);
        glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
        glBufferData(GL_ARRAY_BUFFER, vertices.size() * 3 * sizeof(float), vertices.data(), GL_STATIC_DRAW);
        glEnableVertexAttribArray(kVertexArray);
        glVertexAttribPointer(kVertexArray, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    }

    if (has_normal) {
        glGenBuffers(1, &normal_buffer);
        glBindBuffer(GL_ARRAY_BUFFER, normal_buffer);
        glBufferData(GL_ARRAY_BUFFER, normals.size() * 3 * sizeof(float), normals.data(), GL_STATIC_DRAW);
        glEnableVertexAttribArray(kNormalArray);
        glVertexAttribPointer(kNormalArray, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    }

    if (has_tex_coord) {
        glGenBuffers(1, &tex_coord_buffer);
        glBindBuffer(GL_ARRAY_BUFFER, tex_coord_buffer);
        glBufferData(GL_ARRAY_BUFFER, tex_coords.size() * 3 * sizeof(float), tex_coords.data(), GL_STATIC_DRAW);
        glEnableVertexAttribArray(kTexCoordArray);
        glVertexAttribPointer(kTexCoordArray, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    }

    if (has_index) {
        glGenBuffers(1, &index_buffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * 3 * sizeof(unsigned short), indices.data(), GL_STATIC_DRAW);
        glBindVertexArray(0);
    }

并绘制: glDrawElements(GL_TRIANGLES, indices.size() * 3, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));

Wavefront 对象加载器方法:

bool Loadfs() {
    CVector3<float> vertex;
    CVector3<unsigned short> vindex, tindex, nindex;

    while(file_stream->offset_ < file_stream->size_) {
        if (file_stream->buffer_[file_stream->offset_] == '#') {
            char comment[512] = {0};
            file_stream->ReadLine(comment); // check return code -> "its local variable return address"
            file_stream->SkipLine();
        }
        else if (file_stream->buffer_[file_stream->offset_] == 'v') {
            file_stream->offset_++;

            if (file_stream->buffer_[file_stream->offset_] == ' ') {
                has_position = true;
                file_stream->offset_++;
                file_stream->SkipWhiteSpace();

                vertex.x = file_stream->ReadFloat();
                file_stream->SkipWhiteSpace();

                vertex.y = file_stream->ReadFloat();
                file_stream->SkipWhiteSpace();

                vertex.z = file_stream->ReadFloat();
                file_stream->SkipLine();

                vertices.push_back(vertex);
            }
            else if (file_stream->buffer_[file_stream->offset_] == 'n') {
                has_normal = true;
                file_stream->offset_++;
                file_stream->SkipWhiteSpace();

                vertex.x = file_stream->ReadFloat();
                file_stream->SkipWhiteSpace();

                vertex.y = file_stream->ReadFloat();
                file_stream->SkipWhiteSpace();

                vertex.z = file_stream->ReadFloat();
                file_stream->SkipLine();

                normals.push_back(vertex);
            }
            else if (file_stream->buffer_[file_stream->offset_] == 't') {
                has_tex_coord = true;
                file_stream->offset_++;
                file_stream->SkipWhiteSpace();

                vertex.x = file_stream->ReadFloat();
                file_stream->SkipWhiteSpace();

                vertex.y = file_stream->ReadFloat();
                file_stream->SkipWhiteSpace();

                vertex.z = file_stream->ReadFloat();
                file_stream->SkipLine();

                tex_coords.push_back(vertex);
            }
        }
        else if (file_stream->buffer_[file_stream->offset_] == 'f') {
            has_index = true;
            file_stream->offset_++;
            file_stream->SkipWhiteSpace();

            //
            if (has_position) {
                vindex.x = file_stream->ReadShort();
            }
            if (has_tex_coord) {
                file_stream->offset_++;
                tindex.x = file_stream->ReadShort();
            }
            if (has_normal) {
                file_stream->offset_++;
                nindex.x = file_stream->ReadShort();
            }
            file_stream->SkipWhile(' ');

            //
            if (has_position) {
                vindex.y = file_stream->ReadShort();
            }
            if (has_tex_coord) {
                file_stream->offset_++;
                tindex.y = file_stream->ReadShort();
            }
            if (has_normal) {
                file_stream->offset_++;
                nindex.y = file_stream->ReadShort();
            }
            file_stream->SkipWhile(' ');

            //
            if (has_position) {
                vindex.z = file_stream->ReadShort();
            }
            if (has_tex_coord) {
                file_stream->offset_++;
                tindex.z = file_stream->ReadShort();
            }
            if (has_normal) {
                file_stream->offset_++;
                nindex.z = file_stream->ReadShort();
            }
            vi.push_back(--vindex);
            ti.push_back(--tindex);
            ni.push_back(--nindex);
            //indices.push_back(--vindex);
        }
        else file_stream->SkipLine();
    }


    indices.insert(indices.end(), vi.begin(), vi.end());
    return true;
}

此处vs:glsl主要方法:

position_ = MV * vec4(vVertex, 1.0);
normal_ = normalize(N * vNormal);
texture_ = vTexture;
//shadow_ = S * M * vec4(vVertex, 1.0);

gl_Position = MVP * vec4(vVertex, 1.0);

和fs.glsl主要方法:

vec3 rgb = vec3(1.0, 1.0, 1.0);
rgb = PhongShade(g_light, g_material, position_, normal_);
_frag_color = vec4(rgb, 1.0);

//_frag_color = texel * vec4(ambient + diffuse + specular, 1.0);

有人有什么想法吗?

问题在于 OpenGL 使用单个索引缓冲区来索引顶点位置、纹理坐标和法线缓冲区,每个缓冲区都使用相同的索引(三角形有 3 个索引),而 Wavefront obj 格式允许每个面分别为顶点位置、纹理坐标和法线指定单独的索引(一个三角形总共有 9 个索引)。

从你的代码中看不清楚,但很可能你实际上并没有使用你正在阅读的 tini 索引数组,而是从 vi,因此顶点索引被用于索引 normal_buffertex_coord_buffer,给出了奇怪的结果。

要解决此问题,您需要创建顶点坐标、tex 坐标和法线坐标的缓冲区,并为面定义中使用的顶点位置索引、tex 坐标索引和法线索引的每个唯一组合创建一个条目obj 文件,然后将索引缓冲区索引到这些数组中,以便特定的面顶点对每个顶点具有相同的索引。理想情况下,这将作为预处理步骤而不是在加载时完成。

或者,放弃使用 glDrawElements 的索引渲染并改用 glDrawArrays。根据位置、tex 坐标和法线的面顶点索引,为每个面顶点写出一个条目到每个数组,然后渲染它。然而,这效率较低。

另一种解决方案(如果可能的话)是以每个面顶点使用相同索引的位置、tex 坐标和法线的方式导出 obj 文件。