簡體   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