[OpenGL & C]Modelldarstellung mit Fehlern (OBJ-Loader)



  • Screenshot: https://i.imgur.com/CaDAA1J.png

    Egal welches Modell ich lade, es kommt immer mit Fehldarstellungen (s. Screenshot). Ich vermute, dass es an glBufferData, glVertexAttribPointer oder glDrawElements liegt, weil evtl. die Bytegrößen nicht richtig gesetzt worden sind.

    Ich verwende eine einfach verkette Listen beim Parsen des Objekts (ist sehr langsam). Zudem verwende ich benutzdefinierte Array-Objekte/Struktur (die noch zusätzlich zum Array die Größeninformation inne hat). Das wäre einmal FloatArray und UIntArray. FloatArray ist ähnlich wie UIntArray mittels typedef definiert. Der Unterschied ist, dass das UIntArray ein uint32_t Array beinhaltet. Hier ist die sind die typedefs dazu:

    #ifndef ARRAYS_H_
    #define ARRAYS_H_
    
    #include <stddef.h>
    #include <inttypes.h>
    
    // uint array object
    typedef struct {
      uint32_t* elements;
      size_t length;
    } UIntArray;
    
    // float array object
    typedef struct {
      float* elements;
      size_t length;
    } FloatArray;
    
    // Dumps an primitive uint array on the heap with its size info
    UIntArray* newUIntArray(uint32_t* dump, const size_t length);
    // Dumps an primitive uint array on the heap with its size info
    UIntArray* newUIntArrayNoDump(const size_t length);
    // Creates a float array object with the specified size
    FloatArray* newFloatArrayNoDump(const size_t length);
    // Dumps an primitive float array on the heap with its size info
    FloatArray* newFloatArray(float* dump, const size_t length);
    // Free up used heap memory by the uint array object
    void delUIntArray(UIntArray* p);
    // Free up used heap memory by the float array object
    void delFloatArray(FloatArray* p);
    
    #endif // ARRAYS_H_
    

    Details

    Das ist die engine.c mit der Funktion run, die die Rendering-Loop enthält:

    #include "engine.h"
    #include "shader.h"
    #include "mesh.h"
    #include "linalg.h"
    #include <math.h>
    #include "arrays.h"
    
    // TODO define a function instead of a macro
    #define TO_RAD(d) ((float) (d * M_PI) / 180.0f)
    
    void run() {
      // Window creation
      WNDHND wh = createWindow(WIN_WIDTH, WIN_HEIGHT, WIN_TITLE);
      // Compile and link the shaders
      SHADERID progID = setupShaders("vs1.glsl", "fs1.glsl");
      // Dump vertex and index data into the array objects from an OBJ file
      FloatArray* vertexDump;
      UIntArray* indexDump;
      loadOBJ("cow.smf", &vertexDump, &indexDump);
      // Send the vertex and index data to the graphics driver
      OBJECTID vao1 = setupBuffers(vertexDump, indexDump);
      // Model matrix definition (col.-major)
      const float Tx = +0.0f;
      const float Ty = +0.0f;
      const float Tz = +1.0f;
      Matrix16f M = {
        +1.0f, +0.0f, +0.0f, +Tx,
        +0.0f, +1.0f, +0.0f, +Ty,
        +0.0f, +0.0f, +1.0f, +Tz,
        +0.0f, +0.0f, +0.0f, +1.0f,
      };
      // View matrix definition (col.-major)
      const float Rx = 1.0f;
      const float Ry = 0.0f;
      const float Rz = 0.0f;
      const float Ux = 0.0f;
      const float Uy = 1.0f;
      const float Uz = 0.0f;
      const float Px = 0.0f;
      const float Py = 0.0f;
      const float Pz = 0.0f;
      const float Fx = 0.0f;
      const float Fy = 0.0f;
      const float Fz = 1.0f;
      Matrix16f V = {
        +Rx, +Ry, +Rz, -Px,
        +Ux, +Uy, -Uz, -Py,
        -Fx, -Fy, -Fz, -Pz,
        +0.0f, +0.0f, +0.0f, +1.0f,
      };
     // Projection matrix (col.-major)
     const float near = 0.1f;
     const float far = 100.0f;
     const float aspect = (float) WIN_WIDTH / WIN_HEIGHT;
     const float fov = 75.0f;
     const float range = tan(TO_RAD(fov) / 2.0f) * near;
     const float Sx = (float) (2.0f * near) / (float) (range * aspect + range * aspect);
     const float Sy = (float) near / (float) range;
     const float Sz = (float) -(far + near) / (float) (far - near);
     const float PPz = (float) -(2 * far * near) / (float) (far - near);
     Matrix16f P = {
       +Sx, +0.0f, +0.0f, +0.0f,
       +0.0f, +Sy, +0.0f, +0.0f,
       +0.0f, +0.0f, +Sz, +PPz,
       +0.0f, +0.0f, -1.0f, +0.0f,
      };
      // Construct MVP matrix
      Matrix16f MVP = {0};
      multiplyMatrices(&MVP, M, V);
      multiplyMatrices(&MVP, MVP, P);
      // Send the constructed MVP matrix to the vertex shader
      sendMVPMatrix(progID, MVP.entries);
      // Set the clear color
      const float r = 0.3f;
      const float g = 0.3f;
      const float b = 0.3f;
      const float a = 1.0f;
      glClearColor(r, g, b, a);
      // Enable depth
      glEnable(GL_DEPTH_TEST);
      glDepthFunc(GL_LESS);
      // Culling options
      glDisable(GL_CULL_FACE);
      glFrontFace(GL_CCW);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      // Render loop
      while(!windowShouldClose(wh)) {
        // Clear color buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        // Resize frame buffer if the window dimensions are changing
        maintainAspectRatio(wh);
        // Use shader program
        glUseProgram(progID);
        // Activate the VAO
        glBindVertexArray(vao1);
        // Enable the position attribute in the vertex shader at location 0
        glEnableVertexAttribArray(0);
        // Draw
        const uint32_t faceCount = indexDump->length / 3;
        glDrawElements(GL_TRIANGLES, faceCount, GL_UNSIGNED_INT, 0);
        // Swap buffers
        swapBuffers(wh);
        // Listen for any events
        pollEvents();
      }
      destroyWindow(wh);
    }
    

    Das ist mesh.c mit den Funktionen setupBuffers und loadOBJ:

    #include "mesh.h"
    #include "shader.h"
    #include <string.h>
    
    BUFFERID setupBuffers(FloatArray* verts, UIntArray* inds) {
      // Generate VAO
      uint32_t vao;
      glGenVertexArrays(1, &vao);
      // Bind the VAO
      glBindVertexArray(vao);
      // Generate VBO
      uint32_t vbo;
      glGenBuffers(1, &vbo);
      // Bind VBO
      glBindBuffer(GL_ARRAY_BUFFER, vbo);
      // Feed VBO with data
      const uint32_t arrayBufferSize = sizeof(float) * verts->length;
      glBufferData(GL_ARRAY_BUFFER, arrayBufferSize, verts->elements, GL_STATIC_DRAW);
      // TODO remove debug output
      // DEBUG vertices
      fprintf(stdout, "DEBUG verts->length in function setupBuffers: %d\n",
      verts->length);
      for (int i = 0; i < verts->length; ++i) {
        fprintf(stdout, "DEBUG verts->elements[%d] in function setupBuffers: %f\n",
        i,  verts->elements[i]);
      }
      // Link the position attribute in the vertex shader with the data at
      // attribute location 0.
      const uint32_t componentCountPos = 3;
      const uint32_t stride = sizeof(float) * componentCountPos;
      const uint32_t offsetPos = 0;
      glVertexAttribPointer(0, componentCountPos, GL_FLOAT, GL_FALSE, stride,
        (void*) offsetPos);
      glEnableVertexAttribArray(0);
      // Generate element buffer object
      uint32_t ebo;
      glGenBuffers(1, &ebo);
      // Bind the EBO
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
      // Feed with index data
      const uint32_t elementArrayBufferSize = sizeof(uint32_t) * inds->length;
      glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementArrayBufferSize, inds->elements, GL_STATIC_DRAW);
      // TODO remove debug output
      // DEBUG indices
      fprintf(stdout, "DEBUG inds->length in function setupBuffers: %d\n",
      inds->length);
      for (int i = 0; i < inds->length; ++i) {
        fprintf(stdout, "DEBUG inds->elements[%d] in function setupBuffers: %d\n",
        i,  inds->elements[i]);
      }
      // Return back the ID to the VAO
      return vao;
    }
    
    // TODO complete implementation: read UVs, normals too
    void loadOBJ(const uint8_t* path, FloatArray** verts, UIntArray** inds) {
      // Open OBJ file
      FILE* fis = fopen(path, "rb");
      if (!fis) {
        perror("Error: failed to open OBJ file.\n");
        exit(1);
      }
      // Parse vertex data
      FloatNode* fn = NULL;
      UIntNode* uin = NULL;
      uint8_t lineHeader[128];
      float vX = 0.0f;
      float vY = 0.0f;
      float vZ = 0.0f;
      uint32_t i1 = 0;
      uint32_t i2 = 0;
      uint32_t i3 = 0;
      while (fscanf(fis, "%s", lineHeader) != EOF) {
        if (strcmp(lineHeader, "v") == 0) {
          if (fscanf(fis, "%f %f %f\n", &vX, &vY, &vZ)) {
            fn = addFloatNodeLast(fn, vX);
            fn = addFloatNodeLast(fn, vY);
            fn = addFloatNodeLast(fn, vZ);
        }
        } else if (strcmp(lineHeader, "f") == 0) {
            uint32_t junk;
            if (fscanf(fis, "%d %d %d %d/%d/%d %d/%d/%d\n",
            &i1, &i2, &i3, &junk,
            &junk, &junk, &junk, &junk, &junk)) {
              uin = addUIntNodeLast(uin, i1);
              uin = addUIntNodeLast(uin, i2);
              uin = addUIntNodeLast(uin, i3);
          }
        }
      }
      // Copy float list to float array object
      const uint64_t flLength = getFloatListLength(fn);
      FloatNode* fnIt = fn;
      (*verts) = newFloatArrayNoDump(flLength);
      for (uint64_t i = 0; i < flLength; ++i) {
        (*verts)->elements[i] = fnIt->data;
        if (fnIt) {
          fnIt = fnIt->next;
        }
      }
      // Copy uint32_t list to uint32_t array object
      const uint64_t uilLength = getUIntListLength(uin);
      UIntNode* uinIt = uin;
      (*inds) = newUIntArrayNoDump(uilLength);
      for (uint64_t i = 0; i < (*inds)->length; ++i) {
        (*inds)->elements[i] = uinIt->data;
        if (uinIt) {
          uinIt = uinIt->next;
        }
      }
      // Close file stream
      fclose(fis);
    } // end of loadOBJ
    

    Das ist cow.smf (das Modell, das geladen wird): https://pastebin.com/Fg3hMrQH



  • Edit: Ansonsten werden die Vertex- und Indexdaten schon korrekt geparst.