在 OpenGL 中,使用 glDrawElements 绘制旋转的地球仪有奇怪的伪影似乎是边缘错误?我该如何调试它?

问题描述 投票:0回答:1

我编写了一个演示,使用三角形带进行索引绘制来绘制纹理球体。 指数看起来是正确的。每行周围有 30 个点:

0, 30, 1, 31, 2, 32, ... 29, 59, 0, 30

然后每行的末尾都有退化三角形。坐标和索引对我来说很合适。 据我计算,如果在纬度线上围绕地球的每个圆周围都有 lonRes 点(在演示中,lonRes= 30),那么每行应该有 lonRes+4,因为每行都连接回起点,然后两个退化索引继续到下一行。不知怎的,计算关闭了,indexSize=2503,实际大小是2432。我将indexSize设置为实际使用和渲染的索引数。球体的大部分看起来都不错,但缺少一列,您希望在其中连接回起点。 地球旋转(在下图中,非洲正在向右移动),但是每行边缘的间隙向后旋转(它向左移动并覆盖了它。无论我的绘制命令有什么问题,我都会我预计它会一起旋转。我不明白这个机制,而且它是一个单一的绘制调用,这使得调试对我来说是一个黑匣子。

我当然会接受答案,但我想学习一些如何进行调试的策略。我已经打印了坐标和索引。他们看起来是正确的。

我包含了代码、着色器(尽管我相当确定它们不是问题)以及显示错误的屏幕截图。

代码是使用以下内容构建的:

g++  -g -std=c++20 -c common/common.cc
g++  -g -std=c++20 06b_sphere3.cc Shape.cc -o bin/06b_sphere3 common.o -lglfw -L/usr/lib64 -lGLEW -lGL -lX11 -lGLU -lwebp

06b_sphere3.cpp:

/*
    Textured Sphere demo
    Load a webp cylindrical projection of earth and map to the sphere
    Tilt earth axis to 23.5 degrees and rotate
*/
#include <GL/glew.h>
#include "common/common.hh"
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include <numbers>
#include <iostream>
#include <iomanip>
#include <cstdint>
#include <string>
using namespace std;
using namespace glm;
using namespace std::numbers;
constexpr double PI = numbers::pi;

class Sphere {
private:
    uint32_t progid; // handle to the shader code
    uint32_t vao; // array object container for vbo and indices
    uint32_t vbo; // handle to the point data on the graphics card
    uint32_t lbo; // handle to buffer of indices for lines for wireframe sphere
    uint32_t latRes, lonRes;
    uint32_t resolution;
    uint32_t indexSize;
public:
    /**
     * @brief Construct a sphere
     *
     * @param r radius of the sphere
     * @param latRes resolution of the grid in latitude
     * @param lonRes resolution of the grid in latitude
     * @param texturePath path to the texture image
     */
    Sphere(double r, uint32_t latRes, uint32_t lonRes);
    ~Sphere() { cleanup(); }
    void render(mat4& trans, GLuint textureID);
    void cleanup();
};

Sphere::Sphere(double r, uint32_t latRes, uint32_t lonRes) : latRes(latRes), lonRes(lonRes),
    resolution((2*latRes-1)*lonRes + 2) {
    progid = loadShaders("06b_texturepoints.vert", "06b_textures.frag");
//    progid = loadShaders("03gouraud.vert", "03gouraud.frag");
    double dlon = 2.0*PI / lonRes, dlat = PI / (2*latRes);
    double z;
    double lat = -PI/2 + dlat; // latitude in radians
    double rcircle;
    float vert[resolution*5]; // x,y,z,u,v
    uint32_t c = 0;
    for (uint32_t j = 0; j < 2*latRes-1; j++, lat += dlat) {
        //what is the radius of hte circle at that height?
        rcircle = r* cos(lat); // size of the circle at this latitude
        z = r * sin(lat); // height of each circle
    
        cout << "rcircle=" << rcircle << ", z=" << z << endl;
        double t = 0;
        for (uint32_t i = 0; i < lonRes; i++, t += dlon) {
            vert[c++] = rcircle * cos(t),
            vert[c++] = rcircle * sin(t);
                cout << '(' << vert[c-2] << ", " << vert[c-1] << ")  ";
            vert[c++] = z;
            vert[c++] = t / (2.0 * PI); // Correct u mapping
            vert[c++] = (lat + PI / 2.0) / PI; // Correct v mapping
        }
        cout << endl;
    }
    // south pole
    vert[c++] = 0;
    vert[c++] = 0;
    vert[c++] = -r;
    vert[c++] = 0.5;
    vert[c++] = 0;

    // north pole
    vert[c++] = 0;
    vert[c++] = 0;
    vert[c++] = r;
    vert[c++] = 0.5;
    vert[c++] = 1;

    cout << "resolution: " << resolution << endl;
    cout << "predicted num vert components: " << resolution*5 << endl;  
    cout << "actual num vert components: " << c << endl;

    indexSize = resolution * 2 + (2*4*latRes-1); 
    //TODO: North and South Poles aren't used
    uint32_t indices[indexSize]; // connect every point in circles or latitude and longitude
    c = 0;
    for (uint32_t j = 0; j < 2*latRes - 2; j++) {
        uint32_t startrow = j*lonRes;
        for (uint32_t i = 0; i < lonRes; i++) {
            indices[c++] = startrow + i;
            indices[c++] = startrow + lonRes + i;
        }
        indices[c++] = startrow;
        indices[c++] = startrow + lonRes;
        // Add degenerate triangles to connect strips
        indices[c++] = (j + 1) * lonRes;
        indices[c++] = (j + 1) * lonRes;
    }
    cout << "indexSize: " << indexSize << endl;
    cout << "actual grid indices: " << c << endl;

    indexSize = c; // not sure why the computaiton is off...
    // Print index data
    cout << "Index data: " ;
    for (size_t i = 0; i < indexSize; i += 2) {
        cout << '(' << indices[i] << ", " << indices[i+1] << ") ";
    }

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, resolution*5*sizeof(float), vert, GL_STATIC_DRAW);
    glGenBuffers(1, &lbo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lbo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize*sizeof(uint32_t), indices, GL_STATIC_DRAW);
    glBindVertexArray(0);
}


void Sphere::render(mat4& trans, GLuint textureID) {
    glUseProgram(progid);           // Use the shader
    uint32_t matrixID = glGetUniformLocation(progid, "transform");
    glUniformMatrix4fv(matrixID, 1, GL_FALSE, &trans[0][0]);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureID);
    glUniform1i(glGetUniformLocation(progid, "textureSampler"), 0);

    glBindVertexArray(vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); // Position
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); // Texture coordinates
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glDrawElements(GL_TRIANGLE_STRIP, indexSize, GL_UNSIGNED_INT, 0);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glBindVertexArray(0);
}

void Sphere::cleanup() {
    glDeleteBuffers(1, &vbo);   // remove vbo memory from graphics card
    glDeleteBuffers(1, &lbo);   // remove lbo (line indices)
    glDeleteVertexArrays(1, &vao); // remove vao from graphics card
    glDeleteProgram(progid);
}

using namespace std;
using namespace numbers;

void glmain() {
    win = createWindow(800, 800, "Sphere demo");

    glClearColor(0.0f, 0.0f, 0.4f, 0.0f);   // Dark blue background
    GLuint textureID = loadWebPTexture("earth.webp"); // Load the texture
    Sphere sphere(1.0, 20, 30);
    float rotAngle = 0, dRotAngle = 0.0052;
    mat4 northup = rotate(mat4(1.0f), float(PI/2), vec3(1, 0, 0));

//    mat4 northup = mat4(1.0f);

    vec3 up(0, 1, 0);    // normal OpenGL coordinates, x positive to right, y is up (z positive out of screen)
    do {
        mat trans = rotate(northup, radians(23.5f), vec3(0, 1, 0)); // tilt axis
        trans = rotate(trans, rotAngle, vec3(0, 0, 1)); // spin on axis
        rotAngle += dRotAngle;

        glClear(GL_COLOR_BUFFER_BIT);   // Clear the screen
        glDisable(GL_DEPTH_TEST);
        sphere.render(trans, textureID);

        glfwSwapBuffers(win);             // double buffer
        glfwPollEvents();
    } while (glfwGetKey(win, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
             glfwWindowShouldClose(win) == 0);
    glDeleteTextures(1, &textureID); // Clean up the texture
}

常见.hh

#include <GL/glew.h>    // OpenGL API
#include <GLFW/glfw3.h> // Window API
#include <glm/glm.hpp>  // Matrix and vector math for OpenGL
#include <glm/ext.hpp>

// all demos use a window, declared globally in common.cc
extern GLFWwindow* win;
GLFWwindow* createWindow(uint32_t w, uint32_t h, const char title[]);
GLuint loadShaders(const char vertexPath[], const char * fragmentPath);

void glmain();

/*
  provide a standardized main, because it's always the same
  It catches exceptions and quits if there is a problem
    you write glmain instead
*/
int main(int argc, char* argv[]);
void dump(glm::mat4& mat);
void transpt(glm::mat4& m, double x, double y, double z);

GLuint loadWebPTexture(const char* filePath);
GLuint build_prog(const char vertex_shader[], const char fragment_shader[]);
const uint32_t INVALID_UNIFORM_LOCATION = 0xFFFFFFFF;

// TODO: eventually move all hardcoded, prebuilt shaders into strings
/*
  generically render a textured object composed of a VAO, containing a vertex buffer, an index buffer,
   a texture, and a texture unit
*/
void render_textured_indexed(GLuint program, GLuint vao, GLuint vert, GLuint index, GLuint texture);

/*
  generically render a textured object composed of a VAO, containing a vertex buffer with a color per vertex,
   an index buffer,
*/
void render_indexed_colored(GLuint program, GLuint vao, GLuint vert, GLuint index);

/*
    generically render a surface composed of a VAO, containing a vertex buffer with a value per vertex looking up in a 1D-texture   
*/
void render_indexed_heatmap(GLuint program, GLuint vao, GLuint vert, GLuint index, GLuint texture);

通用.cpp

#include <stdio.h>
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>
#include <sstream>
#include <webp/decode.h>
using namespace std;

#include <stdlib.h>
#include <string.h>

#include <GL/glew.h>

#include "common.hh"

GLFWwindow* win = nullptr;


void check_status( GLuint obj, bool isShader ) {
    GLint status = GL_FALSE, log[ 1 << 11 ] = { 0 };
    ( isShader ? glGetShaderiv : glGetProgramiv )( obj, isShader ? GL_COMPILE_STATUS : GL_LINK_STATUS, &status );
    if( status == GL_TRUE ) return;
    ( isShader ? glGetShaderInfoLog : glGetProgramInfoLog )( obj, sizeof( log ), nullptr, (GLchar*)log );
    std::cerr << (GLchar*)log << "\n";
    std::exit( EXIT_FAILURE );
}

void attach_shader( GLuint program, GLenum type, const char* src ) {
    GLuint shader = glCreateShader( type );
    glShaderSource( shader, 1, &src, NULL );
    glCompileShader( shader );
    check_status( shader, true );
    glAttachShader( program, shader );
    glDeleteShader( shader );
}

// build a vertex and fragment shader program from constants in source code
// this hardcoded version is provided for all the standard shaders that you want for common graphics
// you can always load from files but I will build a number of the basic ones in here
GLuint build_prog(const char vertex_shader[], const char fragment_shader[]) {
    GLuint prog = glCreateProgram();
    attach_shader( prog, GL_VERTEX_SHADER, vertex_shader );
    attach_shader( prog, GL_FRAGMENT_SHADER, fragment_shader );
    glLinkProgram( prog );
    check_status( prog, false );
    return prog;
}

GLuint loadShaders(const char vertexPath[], const char * fragmentPath) {
    // Create the shaders
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

    // Read the Vertex Shader code from the file
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertexPath, std::ios::in);
    if(VertexShaderStream.is_open()){
        std::stringstream sstr;
        sstr << VertexShaderStream.rdbuf();
        VertexShaderCode = sstr.str();
        VertexShaderStream.close();
    }else{
        printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertexPath);
        getchar();
        return 0;
    }

    // Read the Fragment Shader code from the file
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragmentPath, std::ios::in);
    if(FragmentShaderStream.is_open()){
        std::stringstream sstr;
        sstr << FragmentShaderStream.rdbuf();
        FragmentShaderCode = sstr.str();
        FragmentShaderStream.close();
    }

    GLint Result = GL_FALSE;
    int InfoLogLength;


    // Compile Vertex Shader
    printf("Compiling shader : %s\n", vertexPath);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
    glCompileShader(VertexShaderID);

    // Check Vertex Shader
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
        printf("%s\n", &VertexShaderErrorMessage[0]);
    }

    // Compile Fragment Shader
    printf("Compiling shader : %s\n", fragmentPath);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
    glCompileShader(FragmentShaderID);

    // Check Fragment Shader
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
        printf("%s\n", &FragmentShaderErrorMessage[0]);
    }

    // Link the program
    printf("Linking program\n");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);

    // Check the program
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> ProgramErrorMessage(InfoLogLength+1);
        glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
        printf("%s\n", &ProgramErrorMessage[0]);
    }

    
    glDetachShader(ProgramID, VertexShaderID);
    glDetachShader(ProgramID, FragmentShaderID);
    
    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);

    return ProgramID;
}

GLFWwindow* createWindow(uint32_t w, uint32_t h, const char title[]) {
    // Initialise GLFW
    if( !glfwInit() )   {
        throw "Failed to initialize GLFW";
    }

    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Open a window and create its OpenGL context
    GLFWwindow* win = glfwCreateWindow(w, h, title, nullptr, nullptr);
    if (win == nullptr) {
        glfwTerminate();
        throw "Failed to open GLFW window";
    }
    glfwMakeContextCurrent(win); // create OpenGL context

    // Initialize GLEW
    glewExperimental = true; // Needed for core profile
    if (glewInit() != GLEW_OK) {
        throw "Failed to initialize GLEW";
    }

    // Ensure we can capture the escape key to quit
    glfwSetInputMode(win, GLFW_STICKY_KEYS, GL_TRUE);

    return win;
}

/*
    standardized main to catch errors.
    In this simplified version each error is just reported as a string
    It would be better to also track which file and line number the error
    happened in, but that would take an exception object.
    For now, keeping it simple
 */
int main(int argc, char* argv[]) {
    try {
        glmain();
        glfwTerminate();        // Close OpenGL window and terminate GLFW
    } catch (const char* msg) {
        cerr << msg << '\n';
        exit(-1);
    }
    return 0;
}


void dump(glm::mat4& mat) {
    // TODO: I suspect we are printing the matrix transposed
    const float* m = &mat[0][0];
    cerr << setprecision(7);
    for (int i = 0, c = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++, c++)
        cerr << setw(14) << m[c];
      cerr << '\n';
    }
  }

void transpt(glm::mat4& m, double x, double y, double z) {
    cerr << "orig=(" << x << "," << y << "," << z << ") transformed: (" <<
     (m[0][0] * x + m[1][0] * y + m[2][0] * z + m[3][0]) << "," <<
     (m[0][1] * x + m[1][1] * y + m[2][1] * z + m[3][1]) << "," <<
     (m[0][2] * x + m[1][2] * y + m[2][2] * z + m[3][2]) << ")\t(";

    cerr <<
         (m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]) << "," <<
     (m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]) << "," <<
     (m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]) << ")\n";

  }

GLuint loadWebPTexture(const char* filePath) {
    // Read the file into a buffer
    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        std::cerr << "Failed to open WebP file: " << filePath << std::endl;
        return 0;
    }
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);
    std::vector<char> buffer(size);
    if (!file.read(buffer.data(), size)) {
        std::cerr << "Failed to read WebP file: " << filePath << std::endl;
        return 0;
    }

    // Decode the WebP image
    int width, height;
    uint8_t* data = WebPDecodeRGBA(reinterpret_cast<uint8_t*>(buffer.data()), size, &width, &height);
    if (!data) {
        std::cerr << "Failed to decode WebP image: " << filePath << std::endl;
        return 0;
    }

    // Generate and bind a texture
    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Upload the texture data
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

    // Set texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Free the image data
    WebPFree(data);

    return textureID;
}

void transform_dump(glm::mat4& mat, double x, double y, double z) {
    glm::vec4 point = glm::vec4(x, y, z, 1.0f);
    glm::vec4 tp = mat * point;
    cerr << "Transformed point: (" << tp.x << ", " << tp.y << ", " << tp.z << ")\n";
}

06b_texturepoints.vert

#version 330 core

layout(location = 0) in vec3 pos;       // Position (x, y, z)
layout(location = 1) in vec2 texCoord;  // Texture coordinates (u, v)

uniform mat4 transform;

out vec2 TexCoord;

void main() {
    gl_Position = transform * vec4(pos, 1.0);
    TexCoord = texCoord;
}

06b_textures.frag

#version 330 core

in vec2 TexCoord;
out vec4 FragColor;

uniform sampler2D textureSampler;

void main() {
    FragColor = texture(textureSampler, TexCoord);
} 

这是试图显示问题的屏幕截图:

enter image description here

c++ opengl glfw glm-math texture-mapping
1个回答
1
投票

为了便于讨论,我们假设您将

lonRes
设置为 10。 根据上面的代码,
vert
中的单个水平条带的左侧顶点的
u
设置为 0, 0.1, ..., 0.9。

在索引计算代码中,将每个顶点与其后面的顶点连接,然后将最终顶点与行中的第一个顶点连接,因此 u=0.9 的顶点将连接到 u=0 的顶点。这意味着最终的正方形将包含整个地图的 90% (

1-1/lonRes
),水平翻转,因为你从 u=0.9 变为 u=0。

相反,使经度循环更长,以便在

u=1
处生成顶点。还要调整
indices
上的循环,使其上升到 并包括
lonRes
,并将显式重置降至
startrow

一些不相关的建议:

  • 编写一个
    struct Vertex { float x,y,z,u,v; }
    ,这样您就可以按字段分配值或一次创建整个顶点,而不是一直执行
    vert[c++] = ...
  • 拥抱
    std::vector
    !使用
    emplace_back
    和合理的容量提示与普通数组一样快,无需预先计算顶点数。
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.