【问题标题】:Drawing pixels in OpenGL在 OpenGL 中绘制像素
【发布时间】:2016-09-10 20:41:45
【问题描述】:

我正在我的大学学习计算机图形学课程。我需要在现代 OpenGL(3.3+)中实现基本的线条绘制算法来在屏幕上绘制图元。这是我想要实现的 Bresenham 的线条绘制算法的功能 -

void bresenham(GLint xStart, GLint yStart, GLint xEnd, GLint yEnd) {
    if (!(xStart < xEnd)) {
    swap(xStart, xEnd);
    swap(yStart, yEnd); 
    }

    GLint dx = xEnd - xStart;
    GLint dy = yEnd - yStart;
    GLint p = 2 * dy - dx;

    GLint x = xStart;
    GLint y = yStart;
    setPixel(x,y);

    while (x < xEnd) {
        x += 1;
        if (p < 0)
            p += (2 * dy);
        else {
            p += (2 * (dy - dx));
            y += 1;
        setPixel(x,y);
        }
    }
}

我对如何实现setPixel() 功能一无所知。我在这里和其他地方找到的大多数答案都使用较旧的 OpenGL 函数 -

void setPixel(int x, int y)
{
    glColor3f(0.0, 0.0, 0.0); //Set pixel to black  
    glBegin(GL_POINTS);
    glVertex2i(x, y); //Set pixel coordinates 
    glEnd();
    glFlush(); //Render pixel
}

在 OpenGL 3.3+ 中执行此操作的等效方法是什么? 假设我可以将“像素”添加到 std::vector 数组中,如何初始化顶点缓冲区数组来存储这些数据?

我在尝试使用GL_POINTS 绘制点时遇到的另一个问题是,由于在转换为标准化设备坐标期间进行了裁剪,任一方向上超出范围 [-1,1] 的点都不会显示在窗口上。

例如,只有前三个点显示在窗口屏幕上。请参阅initialize() 函数 -

#include <stdio.h>
#include <stdlib.h>

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cstdlib>
#include <vector>
#include <iostream>
#include <fstream>

// Read a shader source from a file
// store the shader source in a std::vector<char>
void read_shader_src(const char* fname, std::vector<char> &buffer);

// Compile a shader
GLuint load_and_compile_shader(const char *fname, GLenum shaderType);

// Create a program from two shaders
GLuint create_program(const char *path_vert_shader, const char *path_frag_shader);

// Render scene
void display(GLuint &vao, GLFWwindow* window);

// Initialize the data to be rendered
void initialize(GLuint &vao);


//GLFW Callbacks
static void error_callback(int error, const char* description) {
    fprintf(stderr, "Error: %s\n", description);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
    if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, GL_TRUE);
    }
}

int main() {
    glfwSetErrorCallback(error_callback);
    //Initialize GLFW
    if (!glfwInit()) {
        fprintf(stderr, "Failed to initialize GLFW.\n");
        return -1;
    }

    //Set GLFW window settings and create window
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    GLFWwindow* window = glfwCreateWindow(500, 500, "My window", NULL, NULL);
    if(!window) {
        fprintf(stderr, "Window or context creation failed.\n");
        return -1;
    }

    glfwSetKeyCallback(window, key_callback);
    glfwMakeContextCurrent(window);

    //Initialize GLEW
    glewExperimental = GL_TRUE;
    if(glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to initialize glew");
        glfwTerminate();
        return -1;
    }
    //Create a vertex array object
    GLuint vao;

    //Initialize the data to be rendered
    initialize(vao);

    while (!glfwWindowShouldClose(window)) {
        display(vao, window);
        glfwPollEvents();
    }
    glfwTerminate();
    return 0;
}

//Render scene
void display(GLuint &vao, GLFWwindow* window) {
    //Red background
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f); 
    glClear(GL_COLOR_BUFFER_BIT);

    glBindVertexArray(vao);
    glDrawArrays(GL_POINTS, 0, 12);
    // Swap front and back buffers
    glfwSwapBuffers(window);
}

void initialize(GLuint &vao) {
    glEnable(GL_PROGRAM_POINT_SIZE);
    // Use a Vertex Array Object
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    //Store verex positions in an array
    GLfloat vertices[24] = {
        0.0, 0.0, // Only these
        0.5, 0.5, //three points show up
        1.0, 1.0, //on the window screen

        4.0, 4.0,
        5.0, 5.0,
        6.0, 6.0,

        7.0, 7.0,
        8.0, 8.0,
        9.0, 9.0,

        10.0, 10.0,
        11.0, 11.0,
        12.0, 12.0,
    };

    //Create a vertex buffer object to store the vertex data
    GLuint vbo;
    //Generates 1 buffer object name and stores it in vbo
    glGenBuffers(1, &vbo);
    //Bind the buffer object to the buffer binding target
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    //Creates and initializes the buffer object's data store(with data from vertices)
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    GLuint shaderProgram = create_program("/Users/.../vert.shader", "/Users/.../frag.shader"); //path to shader files

    // Get the location of the attributes that enters in the vertex shader
    GLint posAttrib = glGetAttribLocation(shaderProgram, "position");

    // Specify how the data for position can be accessed
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);

    //Enable the attribute
    glEnableVertexAttribArray(posAttrib);
}

// Read a shader source from a file
// store the shader source in a std::vector<char>
void read_shader_src(const char *fname, std::vector<char> &buffer) {
    std::ifstream in;
    in.open(fname, std::ios::binary);

    if(in.is_open()) {
        // Get the number of bytes stored in this file
        in.seekg(0, std::ios::end);
        size_t length = (size_t)in.tellg();

        // Go to start of the file
        in.seekg(0, std::ios::beg);

        // Read the content of the file in a buffer
        buffer.resize(length + 1);
        in.read(&buffer[0], length);
        in.close();
        // Add a valid C - string end
        buffer[length] = '\0';
    }
    else {
        std::cerr << "Unable to open " << fname << " I'm out!" << std::endl;
        exit(-1);
    }
}

//Compile a shader
GLuint load_and_compile_shader(const char* fname, GLenum shaderType) {
    //Load a shader from an external file
    std::vector<char> buffer;
    read_shader_src(fname, buffer);
    const char *src = &buffer[0];

    //Create and compile the shader
    GLuint shader = glCreateShader(shaderType);
    glShaderSource(shader, 1, &src, NULL);
    glCompileShader(shader);

    GLint shader_compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_compiled);
    if(!shader_compiled) {
        GLchar message[1024];
        glGetShaderInfoLog(shader, 1024, NULL, message);
        std::cerr << "Shader compilation failed.";
        std::cerr << "Log: " << &message << std::endl;
        glfwTerminate();
        exit(-1);
    }
    return shader;
}

// Create a program from two shaders
GLuint create_program(const char *path_vert_shader, const char *path_frag_shader) {
    // Load and compile the vertex and fragment shaders
    GLuint vertexShader = load_and_compile_shader(path_vert_shader, GL_VERTEX_SHADER);
    GLuint fragmentShader = load_and_compile_shader(path_frag_shader, GL_FRAGMENT_SHADER);

    // Attach the above shader to a program
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);

    // Flag the shaders for deletion
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // Link and use the program
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);

    return shaderProgram;
}

我做了哪些修改以便绘制其余的点?

顶点着色器 -

#version 150 core

in vec4 position;

void main() {
    gl_Position = position;
    gl_PointSize = 10.0;
}

片段着色器 -

#version 150 core

out vec4 out_color;

void main() {
    out_color = vec4(1.0, 1.0, 1.0, 1.0);
}

【问题讨论】:

  • 我知道这只是为了练习,但是使用 OpenGL 设置单独的像素来绘制图元有点否定 GL 的目的......
  • 你只需要画线还是必须使用Bresenham的算法?
  • @pleluron 我必须使用“光栅化”算法。我知道我可以使用 GL_LINES 否则。
  • 只需将 x 和 y 依次存储在一个数组中,并使用 glDrawArrays() 和 GL_POINTS 来渲染线条。根据 -1,1 创建一个 2D 投影矩阵,也许使用 glm 并将其发送到您的着色器并简单地将点乘以它。
  • 这可能有点愚蠢,但是您可以使用 Bresenham 算法将线条绘制到 CPU 端的纹理上,然后正常将该四边形绘制到屏幕上。如果您有兴趣,我可以写一个更详细的答案

标签: c++ opengl graphics


【解决方案1】:

从 cmets 看来,您似乎正在尝试使用 OpenGL 来代替类所需的非常旧的图形库。由于计算机图形已经发生了如此大的变化,以至于您在现代 OpenGL 中尝试做的事情是不合理的,您应该尝试对当前作业和以后的作业执行此操作:

免责声明:这不是在现代 OpenGL 中画线的合理方式

  1. 创建一个任意大小的二维数组,大到可以在其上绘制整条线。
  2. 使用原始函数画线,创建一些 setPixel 函数来更改该数组中的元素
  3. 一旦你画完线(或做任何其他未来的任务会让你做的事情),从该数组创建一个 OpenGL 纹理。此处提供了出色的指南:https://open.gl/textures

一些粗略的伪代码:

GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_FLOAT, pixels);
glBindTexture(GL_TEXTURE_2D, 0);

然后您将创建一个四边形(实际上只有两个三角形),将这个纹理绘制到屏幕上。遵循 open.gl 指南应该可以很好地工作,因为他们已经为他们的纹理这样做了。从他们提供的代码中提取(这做了一些额外的事情,所有正确并遵循规范):

GLfloat vertices[] = {
//  Position      Color             Texcoords
    -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // Top-left
     0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // Top-right
     0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // Bottom-right
    -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f  // Bottom-left
};

GLuint elements[] = {
    0, 1, 2,
    2, 3, 0
};

// Set up buffer objects, create shaders, initialize GL, etc.

//drawing
//bind buffers, enable attrib arrays, etc
glBindTexture(GL_TEXTURE_2D, tex);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

【讨论】:

  • 如果你想变得更漂亮,你可以给自己一个持久映射的像素缓冲区对象。这样,您的像素推送操作将(逻辑上)直接发生在图形内存上,并且完成 glTexSubImage 只会更新纹理对象。在该行中,您甚至可以走前沿并使用 Vulkan API,其中“纹理”实际上只是元数据,用于将任意可访问缓冲区中的数据解释为图像数据。
  • 您能详细说明第 1 点和第 2 点吗?更具体地说,我想知道如何声明和初始化传递给glTexImage2Dpixel 变量。本教程仅展示如何从预先存在的图像加载纹理。
  • @datenwolf 根据您在此处的回答(stackoverflow.com/a/18369213/5306573):“但是,纹理可能也不是直接更新屏幕上单个像素的最有效方式。不过,首先是一个好主意绘制像素缓冲区的像素,将其加载到纹理中,然后将其绘制到完整的视口四边形上。”有关如何执行此操作的任何提示(在像素缓冲区上绘制像素);我认为可以通过调用glTexImage2D?
  • @ByteMan2021 可以像float pixels[w * h * 3]; 一样简单,setPixel 的实现就像pixels[(y * w * 3) + (x * 3)] = 1; pixels[(y * w * 3) + (x * 3) + 1] = 1; pixels[(y * w * 3) + (x * 3) + 2] = 1; 一样简单,在这种情况下,三个指的是纹理 I 的 R、G 和 B 通道如上所述。您可以随时更改glTexImage2D 的其他参数以将其简化为灰度纹理。
  • 工作就像一个魅力!非常感谢。我认为唯一的问题是wh 必须相当大,以便可以绘制最右上角的点而不会在setPixel 函数中出现索引越界错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-13
  • 2023-04-11
相关资源
最近更新 更多