繁体   English   中英

OpenGL:朗伯着色导入的 OBJ 会导致伪影和奇怪的剔除

[英]OpenGL: Lambert shading imported OBJs results in artifacts and strange culling

我决定发布此内容,因为我现在认为问题不仅仅是源于着色器程序,而且很可能是 OBJ 导入和网格初始化过程。 我想写一个快速的 Lambert 着色器,最终让屏幕上出现的东西。 最终结果充满了有趣的工件和可见性问题:

可怜的兔子 - 展览 A 可怜的兔子 - 展览 B 看起来好像顶点位置编码正确,但是法线或索引完全搞砸了。

顶点着色器

#version 330

// MeshVertex
in layout(location=0) vec3 a_Position;
in layout(location=1) vec3 a_Normal;
in layout(location=2) vec2 a_UV;
in layout(location=3) vec3 a_Tangent;
in layout(location=4) vec3 a_BiTangent;

uniform mat4 View;
uniform mat4 Projection;
uniform mat4 Model;

out VS_out
{
    vec3 fragNormal;
    vec3 fragPos;
} vs_out;

void main()
{
    mat3 normalMatrix   = mat3(transpose(inverse(Model)));
    vec4 position       = vec4(a_Position, 1.f);

    vs_out.fragPos      = (Model * position).xyz;
    vs_out.fragNormal   = normalMatrix * a_Normal;

    gl_Position = Projection * View * Model * position;
}

我最初以为我错误地将顶点法线传递给片段着色器。 我已经看到一些示例将顶点 position 乘以 ModelView 矩阵。 这对我来说听起来不直观,我的灯光位于世界空间中,所以我需要顶点的世界空间坐标,因此只需要乘以 Model 矩阵。 如果在这个思考过程中没有危险信号,这里是片段着色器:

#version 330

struct LightSource
{
    vec3 position;
    vec3 intensity;
};
uniform LightSource light;

in VS_out
{
    vec3 fragNormal;
    vec3 fragPos;
} fs_in;

struct Material
{
    vec4 color;
    vec3 ambient;
};

uniform Material material;

void main()
{
    // just playing around with some values for now, dont worry, removing this still does not fix the issue
    vec3 ambient = normalize(vec3(69, 111, 124));

    vec3 norm       = normalize(fs_in.fragNormal);
    vec3 pos        = fs_in.fragPos;
    vec3 lightDir   = normalize(light.position - pos);

    float lambert       = max(dot(norm, lightDir), 0.0);
    vec3 illumination   = (lambert * light.intensity) + ambient;

    gl_FragColor = vec4(illumination * material.color.xyz, 1.f);
}

现在主要的怀疑是如何解释 OBJ。 我为此使用tinyOBJ导入器。 我主要复制了他们在 GitHub 页面上的示例代码,并使用该数据初始化了我的本机顶点类型。

OBJ 导入代码

bool Model::Load(const void* rawBinary, size_t bytes)
{
    tinyobj::ObjReader reader;
    if(reader.ParseFromString((const char*)rawBinary, ""))
    {
        // Fetch meshes
        std::vector<MeshVertex> vertices;
        std::vector<Triangle> triangles;

        const tinyobj::attrib_t& attrib = reader.GetAttrib();
        const std::vector<tinyobj::shape_t>& shapes = reader.GetShapes();

        m_Meshes.resize(shapes.size());
        m_Materials.resize(shapes.size());

        // Loop over shapes; in our case, each shape corresponds to a mesh object
        for(size_t s = 0; s < shapes.size(); s++) 
        {
            // Loop over faces(polygon)
            size_t index_offset = 0;
            for(size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) 
            {
                // Num of face vertices for face f
                int fv = shapes[s].mesh.num_face_vertices[f];
                ASSERT(fv == 3, "Only supporting triangles for now");

                Triangle tri;

                // Loop over vertices in the face.
                for(size_t v = 0; v < fv; v++) {
                    // access to vertex
                    tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];

                    tinyobj::real_t vx = 0.f;
                    tinyobj::real_t vy = 0.f;
                    tinyobj::real_t vz = 0.f;
                    tinyobj::real_t nx = 0.f;
                    tinyobj::real_t ny = 0.f;
                    tinyobj::real_t nz = 0.f;
                    tinyobj::real_t tx = 0.f;
                    tinyobj::real_t ty = 0.f;

                    vx = attrib.vertices[3 * idx.vertex_index + 0];
                    vy = attrib.vertices[3 * idx.vertex_index + 1];
                    vz = attrib.vertices[3 * idx.vertex_index + 2];

                    if(attrib.normals.size())
                    {
                        nx = attrib.normals[3 * idx.normal_index + 0];
                        ny = attrib.normals[3 * idx.normal_index + 1];
                        nz = attrib.normals[3 * idx.normal_index + 2];
                    }

                    if(attrib.texcoords.size())
                    {
                        tx = attrib.texcoords[2 * idx.texcoord_index + 0];
                        ty = attrib.texcoords[2 * idx.texcoord_index + 1];
                    }
                    // Populate our native vertex type
                    MeshVertex meshVertex;
                    meshVertex.Position = glm::vec3(vx, vy, vz);
                    meshVertex.Normal = glm::vec3(nx, ny, nz);
                    meshVertex.UV = glm::vec2(tx, ty);
                    meshVertex.BiTangent = glm::vec3(0.f);
                    meshVertex.Tangent = glm::vec3(0.f);

                    vertices.push_back(meshVertex);
                    tri.Idx[v] = index_offset + v;
                }
                triangles.emplace_back(tri);
                index_offset += fv;

                // per-face material
                //shapes[s].mesh.material_ids[f];
            }

            // Adding meshes should occur here!
            m_Meshes[s] = std::make_unique<StaticMesh>(vertices, triangles);
            // m_Materials[s] = ....
        }
    }

    return true;
}

以我理解 OBJ 的方式,OpenGL 索引的概念并不等同于 OBJ 中的 Face 元素。 这是因为每个面元素在 position、正常和 texcoord arrays 中都有不同的索引。 因此,我只是将 face 元素索引的顶点属性复制到我的原生 MeshVertex 结构中——这代表了我的网格的一个顶点; 对应的面部元素 ID 就是我的索引缓冲区 object 的对应索引。 在我的例子中,我使用了一个三角形结构,但它实际上是一回事。

感兴趣的三角形结构:

    struct Triangle
{
    uint32_t Idx[3];

    Triangle(uint32_t v1, uint32_t v2, uint32_t v3)
    {
        Idx[0] = v1;
        Idx[1] = v2;
        Idx[2] = v3;
    }

    Triangle(const Triangle& Other)
    {
        Idx[0] = Other.Idx[0];
        Idx[1] = Other.Idx[1];
        Idx[2] = Other.Idx[2];

    }

    Triangle()
    {
    }
};

除此之外,我不知道是什么导致了这个问题,我愿意听到新的想法; 也许有经验的人明白这些文物意味着什么。 如果您想更深入地了解,我也可以发布网格初始化代码。

编辑:所以我尝试导入 FBX 格式,我遇到了一个非常相似的问题。 我现在正在考虑我的 OpenGL 代码中的愚蠢错误来初始化网格。

这会根据任意顶点数据初始化 OpenGL 缓冲区,以及要索引的三角形

void Mesh::InitBuffers(const void* vertexData, size_t size, const std::vector<Triangle>& triangles)
{
    glGenVertexArrays(1, &m_vao);
    glBindVertexArray(m_vao);

    // Interleaved Vertex Buffer
    glGenBuffers(1, &m_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
    glBufferData(GL_ARRAY_BUFFER, size, vertexData, GL_STATIC_DRAW);

    // Index Buffer
    glGenBuffers(1, &m_ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Triangle) * triangles.size(), triangles.data(), GL_STATIC_DRAW);

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

然后我使用 BufferLayout 结构设置顶点缓冲区的布局,该结构指定我们想要的属性。

void Mesh::SetBufferLayout(const BufferLayout& layout)
{
    glBindVertexArray(m_vao);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

    uint32_t stride = layout.GetStride();
    int i = 0;
    for(const BufferElement& element : layout)
    {
        glEnableVertexAttribArray(i);
        glVertexAttribPointer(i++, element.GetElementCount(), GLType(element.Type), element.Normalized, stride, (void*)(element.Offset));
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);
}

所以在我们的例子中,BufferLayout 对应于我填充的 MeshVertex,包含 Position(float3)、Normal(float3)、UV(float2)、Tangent(float3)、BiTangent(float3)。 我可以通过调试确认步幅和偏移量以及来自 BufferElement 的其他值正是我所期望的; 所以我担心我正在拨打的 OpenGL 电话的性质。

好吧,让我们都忘记这件事已经发生了。 这很尴尬,毕竟一切都很好。 我只是“忘记”在渲染之前调用以下内容:

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

可以理解的是,各种形状都以完全随机的方式被渲染和剔除。 (为什么默认不启用?)

快乐兔

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM