OpenGL入门学习笔记(四)----绘制第一个三角形

        在上一节当中,我们已经具体的了解了图形渲染管线的工作流程,OpenGL入门学习笔记(三)----图形渲染管道的基本流程-CSDN博客,接下来,就根据上篇所说具体用代码使用VBO,VAO并用我们编写的vertexshader和fragmentshader绘制三角形,由此来进一步体会图形渲染管线的流程和shader的使用。

顶点着色器

        首先我们知道,顶点着色器是图形渲染管线中的第一个可编辑的着色器,为了方便起见,我们输入一组已经是标准化的坐标来表示一个三角形(已经标准化范围-1到1,先前已经提到过了)

GLfloat vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

接下来我们来编写我们的顶点着色器,来处理输入数据和输出,对于这个坐标,我们不需要进行额外的操作,于是有

#version 330 core

layout (location = 0) in vec3 position;

void main()
{
    gl_Position = vec4(position.x, position.y, position.z, 1.0);
}

其中第一行代表了运行的OpenGL版本,第二行我们用layout表示一个输入数据的位置信息(location = 0),在后边我们会用到这个值,另外用in来声明一个输入三维向量(vec3)类型的变量名称为position,gl_Position是一个预定义好的vec4变量,用来表示位置信息,于是我们用原来的输入构建出这个变量,这就是我们在这个顶点着色器所干的所有事情。

        在写好了着色器以后我们需要进行编译,需要注意的是,以上的代码实际上我们使用的一个指针进行承接,所以后续我们需要先创建一个shader进行绑定

GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

在OpenGL一般用GLuint来作为一些特殊对象的标识符,这里我们用一个标识符来代表这个顶点着色器,我们用glCreateShader创建一个着色器并告诉他是顶点着色器传递给这个标识符,同时接下来我们将这个着色器与我们所编写的源代码字符串连接起来,在glShaderSource中进行,第一个函数是我们创建好的着色器,并在第二个参数告诉我们要链接的只有一个,第三个参数则表示我们所写的顶点着色器字符串,他原本应该是这样的

const GLchar* vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 position;\n"
    "void main()\n"
    "{\n"
    "gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
    "}\0";

最后一个参数表示结尾,如果传入NULL默认以\0结尾。之后我们用glCopmileShader把这个shader进行编译。

片段着色器

        同样的,对于可以编辑的片段着色器有以下定义

#version 330 core

out vec4 color;

void main()
{
    color = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

同样定义了版本之外,这个程序中片段着色器只需要一个输出变量由out声明的vec4类型的color变量,由于这个输出是最终的颜色供计算输出。和顶点着色器一样,我们也需要对他进行绑定和编译

GLuint fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, null);
glCompileShader(fragmentShader);

至此,为了完成三角形,我们的着色器还剩最后一步。

着色器程序

着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。要使用刚才的着色器我们需要把他们链接成一个程序对象,然后再渲染使用的时候激活这个程序对象。创建一个程序的过程和着色器的过程类似

GLuint shaderProgram;
shaderProgram = glCreateProgram();

这里不再进行解释。然后我们需要把之前编译的shader附加到程序上

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

最后我们使用glLinkProgram函数对这个程序进行链接,链接好之后可以用glUseProgram()传入这个程序进行使用。另外再添加好shader之后,记得删除他们,我们不再需要他们

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

现在我们已经做好了我们的着色器程序,但是我们还没有把数据发送给他,并告诉GPU应该如何接受解释和使用这些数据。我们需要告诉OpenGL接下来该怎么做。

顶点数据和顶点属性

        顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。

        之前我们知道,我们主要通过VBO(顶点数组对象)管理顶点数据的内存,并通过将VBO储存到VAO当中顶点数据所保持的状态,再使用时直接调用VAO进行复现

GLuint VBO, VAO;
glCreateBuffers(1, &VBO);
glCreateVertexArrays(1, &VAO);
glBindVertexArray(VAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(GLfloat),(void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);

这段代码中,第一部分我们类似shader一样有一个声明标识符并绑定一个实际对象的过程,值得一提的是,在创建VAO和VBO时,我们第一个参数传递的是创建的数量,也就是说,我们可以一下子创建一个数组承接多个创建的VBO或者VAO对象。在第一部分的最后一行,我们将绑定VAO确定了当前的数组对象,并对当前数组对象进行操作。

        接下来就是刻画这个模板,用glBindBuffer进行绑定,OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,告诉OpenGL我们当前正在使用这个缓冲流的缓冲对象的是我们绑定的VBO,接下来的顶点数据和属性都将通过GL_ARRAY_BUFFER这个缓冲流交给VBO进行管理,接下来我们传入数据。在glBufferDate函数当中,第一个参数代表当前的缓冲流类型,第二个代表了传入数据的大小,然后是我们要传入的数据,是提前已经定义好的三角形顶点数据,然后是我们传入的模式

它有三种形式:

GL_STATIC_DRAW :数据不会或几乎不会改变。

GL_DYNAMIC_DRAW:数据会被改变很多。

GL_STREAM_DRAW :数据每次绘制时都会改变。

glVertexAttribArray则告诉了OpenGL所传入的顶点数据的属性,第一个参数表示了这个数据的位置(或者是标识符),它与先前的location相对应,第二个参数代表了顶点属性的大小,我们用三个浮点数代表一个位置,所以是3,第三个是数据类型,GLSL中vec默认都是float,第四个则告诉了数据是否需要归一化处理,我们传入的数据是在-1--1之间的标准化坐标,所以不需要,第五个表示步长,可以理解为第一个x到第二个x坐标在数据中的距离,最后一个表示数据的偏移量,由于我们的数据的起始位置在数据的开头,所以是0,参数类型是GLvoid*,所以进行了这个奇怪的强制类型转换。

最后我们启用顶点属性(为了确保不出问题,这个默认是关闭的)。并解除当前VAO的绑定,以防止后续的操作对当前VAO可能带来的影响。

绘制部分

        最后就是令人期待的三角形了,我们在Gameloop中进行我们的绘制指令

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0); 

,OpenGL给我们提供了glDrawArrays函数,它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元。在此之前,我们需要使用程序,并激活要用的VAO。在绘制函数当中,第一个定义了绘制的图形,第二个代表起始索引,是0,第三个是要绘制的顶点。

关于VEO

        在之前的环节我们也同样了解到可以用VEO通过索引的方式提高效率。这个也可以通过保存在VAO中的状态进行。创建一个VEO对象和VBO的过程大抵相同。需要注意的是VEO的缓冲类型是GL_ELEMENT_ARRAY_BUFFER,而不是GL_ARRAY_BUFFER类型。同时通过glBufferFData将索引数据传入VEO,并将对应的顶点数据传入VBO。

 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

不同于VBO,最后用这个函数进行绘制,第三个代表索引数据的类型,最后一个参数里我们可以指定EBO中的偏移量(或者传递一个索引数组,但是这是当你不在使用索引缓冲对象的时候),但是我们会在这里填写0。

代码

接下来分别上传两个类型的完整代码

VBO

#include<iostream>

#define GLEW_STATIC
#include<GL/glew.h>

#include<GLFW/glfw3.h>
const GLchar* vertexShaderSource = "#version 330 core\n"
							  "layout (location = 0) in vec3 position;\n"
							  "void main(){\n"
							  "gl_Position = vec4(position.x, position.y, position.z, 1.0);"
							  "}";
const GLchar* fragmentShaderSource = "#version 330 core\n"
							  "out vec4 color;\n"
							  "void main(){\n"
							  "color = vec4(0,0,1.0f,1.0f);"
	"}";
void key_callback(GLFWwindow* window, int key, int scancode, int action, int maode);
int main() {
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);

	GLFWwindow* window;
	window = glfwCreateWindow(1920,1080, "Triangle Exercise", glfwGetPrimaryMonitor(), nullptr);
	if (window == nullptr) {
		std::cout << "Failed to create window." << std::endl;
		glfwTerminate();
		return -1;
	}

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

	glewExperimental = GL_TRUE;
	glewInit();
	//不要忘了窗口对象绑定视口
	int width, height;
	glfwGetFramebufferSize(window, &width, &height);
	glViewport(0,0,width,height);

	//创建、绑定、编译、验证shader
	GLuint vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
	glCompileShader(vertexShader);

	GLint success;
	GLchar Log[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(vertexShader,512,NULL,Log);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << Log << std::endl;
	}

	GLuint fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
	glCompileShader(fragmentShader);

	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragmentShader,512,NULL,Log);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << Log << std::endl;
	}

	GLuint program;
	program = glCreateProgram();
	glAttachShader(program, vertexShader);
	glAttachShader(program, fragmentShader);
	glLinkProgram(program);

	glGetProgramiv(program, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(program, 512, NULL, Log);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << Log << std::endl;
	}
	//绑定后删除原有的shader
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//顶点数据,不用VEO的
	GLfloat vertices[] = {
		// First triangle
		-0.9f, -0.5f, 0.0f,  // Left 
		-0.0f, -0.5f, 0.0f,  // Right
		-0.45f, 0.5f, 0.0f,  // Top 
		// Second triangle
		 0.0f, -0.5f, 0.0f,  // Left
		 0.9f, -0.5f, 0.0f,  // Right
		 0.45f, 0.5f, 0.0f   // Top 
	};

	GLuint VBO, VAO;
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	glBindBuffer(GL_ARRAY_BUFFER,VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(GLfloat),(void*)0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
	//GameLoop
	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents();//用于处理在队列中的响应事件,比如设置的键盘回调函数,需要用这个函数处理相应并调用对应回调
		glClearColor(0.2f, 0.3f, 0.3f, 0.6f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(program);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES,0,6);
		glBindVertexArray(0);
		glfwSwapBuffers(window);
	}
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glfwTerminate();
	return 0;
}

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

VEO

#include <iostream>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>


// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);

// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;

// Shaders
const GLchar* vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 position;\n"
    "void main()\n"
    "{\n"
    "gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
    "}\0";
const GLchar* fragmentShaderSource = "#version 330 core\n"
    "out vec4 color;\n"
    "void main()\n"
    "{\n"
    "color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n\0";

// The MAIN function, from here we start the application and run the game loop
int main()
{
    std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
    // Init GLFW
    glfwInit();
    // Set all the required options for GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    // Create a GLFWwindow object that we can use for GLFW's functions
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);

    // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
    glewExperimental = GL_TRUE;
    // Initialize GLEW to setup the OpenGL Function pointers
    glewInit();

    // Define the viewport dimensions
    int width, height;
    glfwGetFramebufferSize(window, &width, &height);  
    glViewport(0, 0, width, height);


    // Build and compile our shader program
    // Vertex shader
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // Check for compile time errors
    GLint success;
    GLchar infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // Fragment shader
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // Check for compile time errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // Link shaders
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // Check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    // Set up vertex data (and buffer(s)) and attribute pointers
    //GLfloat vertices[] = {
    //  // First triangle
    //   0.5f,  0.5f,  // Top Right
    //   0.5f, -0.5f,  // Bottom Right
    //  -0.5f,  0.5f,  // Top Left 
    //  // Second triangle
    //   0.5f, -0.5f,  // Bottom Right
    //  -0.5f, -0.5f,  // Bottom Left
    //  -0.5f,  0.5f   // Top Left
    //}; 
    GLfloat vertices[] = {
         0.5f,  0.5f, 0.0f,  // Top Right
         0.5f, -0.5f, 0.0f,  // Bottom Right
        -0.5f, -0.5f, 0.0f,  // Bottom Left
        -0.5f,  0.5f, 0.0f   // Top Left 
    };
    GLuint indices[] = {  // Note that we start from 0!
        0, 1, 3,  // First Triangle
        1, 2, 3   // Second Triangle
    };
    GLuint VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);
    // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, 0); // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind

    glBindVertexArray(0); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs), remember: do NOT unbind the EBO, keep it bound to this VAO


    // Uncommenting this call will result in wireframe polygons.
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // Game loop
    while (!glfwWindowShouldClose(window))
    {
        // Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
        glfwPollEvents();

        // Render
        // Clear the colorbuffer
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // Draw our first triangle
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        //glDrawArrays(GL_TRIANGLES, 0, 6);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);

        // Swap the screen buffers
        glfwSwapBuffers(window);
    }
    // Properly de-allocate all resources once they've outlived their purpose
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    // Terminate GLFW, clearing any resources allocated by GLFW.
    glfwTerminate();
    return 0;
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

  • 33
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值