> 文章列表 > OpenGL入门教程之 变化颜色的三角形

OpenGL入门教程之 变化颜色的三角形

OpenGL入门教程之 变化颜色的三角形

一、 知识点

(1)着色器

着色器(Shader)是运行在GPU上的小程序这些小程序为图形渲染管线的某个特定部分而运行从基本意义上来说,着色器只是一种把输入转化为输出的程序着色器也是一种非常独立的程序,因为它们之间不能相互通信它们之间唯一的沟通只有通过输入和输出
着色器是使用一种叫GLSL的类C语言写成的GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性

着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中
 一个典型的着色器有下面的结构:

#version version_numberin type in_variable_name;
in type in_variable_name;out type out_variable_name;uniform type uniform_name;int main()
{// 处理输入并进行一些图形操作...// 输出处理过的结果到输出变量out_variable_name = weird_stuff_we_processed;
}


当我们特别谈论到顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:

GLint nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

 通常情况下它至少会返回16个,大部分情况下是够用了。

(2)向量

 了解GLSL的向量,以及向量的重组
OpenGL入门教程之 变化颜色的三角形

(3)输入和输出

 了解着色器输入和输出的定义。注意如果两个相邻着色器定义了相同类型和名称的输入和输出(上一个定义输入,下一个定义输出),则上一个着色器的输出会赋值给下一个着色器的对应输入。顶点着色器可以给片段着色器传递输入
OpenGL入门教程之 变化颜色的三角形
 顶点着色器:

#version 330 core
layout (location = 0) in vec3 position; // position变量的属性位置值为0out vec4 vertexColor; // 为片段着色器指定一个颜色输出void main()
{gl_Position = vec4(position, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f); // 把输出变量设置为暗红色
}

 片段着色器:

#version 330 core
in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同)out vec4 color; // 片段着色器输出的变量名可以任意命名,类型必须是vec4void main()
{color = vertexColor;
}

(4)Uniform

了解Unifrom,使用Uniform从内存向显存发送数据
OpenGL入门教程之 变化颜色的三角形

#version 330 core
out vec4 color;uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量void main()
{color = ourColor;
}  

OpenGL入门教程之 变化颜色的三角形

GLfloat timeValue = glfwGetTime();
GLfloat greenValue = (sin(timeValue) / 2) + 0.5;
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

OpenGL入门教程之 变化颜色的三角形

二、变化颜色的三角形

(1)核心代码:

while(!glfwWindowShouldClose(window))
{// 检测并调用事件glfwPollEvents();// 渲染// 清空颜色缓冲glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 记得激活着色器glUseProgram(shaderProgram);// 更新uniform颜色GLfloat timeValue = glfwGetTime();GLfloat greenValue = (sin(timeValue) / 2) + 0.5;GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);// 绘制三角形glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);glBindVertexArray(0);
}

(2)完整代码

#include <iostream>
#include <cmath>// 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""layout (location = 1) in vec3 color;\\n""out vec3 ourColor;\\n""void main()\\n""{\\n""gl_Position = vec4(position, 1.0);\\n""ourColor = color;\\n""}\\0";
const GLchar* fragmentShaderSource = "#version 330 core\\n""out vec4 color;\\n""uniform vec4 ourColor;\\n""void main()\\n""{\\n""color = ourColor;\\n""}\\n\\0";// The MAIN function, from here we start the application and run the game loop
int main()
{// Init GLFWglfwInit();// Set all the required options for GLFWglfwWindowHint(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 functionsGLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);glfwMakeContextCurrent(window);// Set the required callback functionsglfwSetKeyCallback(window, key_callback);// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensionsglewExperimental = GL_TRUE;// Initialize GLEW to setup the OpenGL Function pointersglewInit();// Define the viewport dimensionsglViewport(0, 0, WIDTH, HEIGHT);// Build and compile our shader program// Vertex shaderGLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// Check for compile time errorsGLint 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 shaderGLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// Check for compile time errorsglGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\\n" << infoLog << std::endl;}// Link shadersGLuint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// Check for linking errorsglGetProgramiv(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 pointersGLfloat vertices[] = {// Positions        0.5f, -0.5f, 0.0f,  // Bottom Right-0.5f, -0.5f, 0.0f,  // Bottom Left0.0f,  0.5f, 0.0f   // Top };GLuint VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// 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);// Position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);glBindVertexArray(0); // Unbind VAO// Game loopwhile (!glfwWindowShouldClose(window)){// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functionsglfwPollEvents();// Render// Clear the colorbufferglClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// Be sure to activate the shaderglUseProgram(shaderProgram);// Update the uniform colorGLfloat timeValue = glfwGetTime();GLfloat greenValue = (sin(timeValue) / 2) + 0.5;GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);// Draw the triangleglBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);glBindVertexArray(0);// Swap the screen buffersglfwSwapBuffers(window);}// Properly de-allocate all resources once they've outlived their purposeglDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);// Terminate GLFW, clearing any resources allocated by GLFW.glfwTerminate();return 0;
}

三、调色板三角形

效果展示及分析

 这个图片可能不是你所期望的那种,因为我们只提供了3个颜色,而不是我们现在看到的大调色板。这是在片段着色器中进行的所谓片段插值(Fragment Interpolation)的结果当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合;更精确地说就是30%蓝 + 70%绿。
 这正是在这个三角形中发生了什么。我们有3个顶点,和相应的3个颜色,从这个三角形的像素来看它可能包含50000左右的片段,片段着色器为这些像素进行插值颜色。如果你仔细看这些颜色就应该能明白了:红首先变成到紫再变为蓝色。片段插值会被应用到片段着色器的所有输入属性上
OpenGL入门教程之 变化颜色的三角形

完整代码

#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""layout (location = 1) in vec3 color;\\n""out vec3 ourColor;\\n""void main()\\n""{\\n""gl_Position = vec4(position, 1.0);\\n""ourColor = color;\\n""}\\0";
const GLchar* fragmentShaderSource = "#version 330 core\\n""in vec3 ourColor;\\n""out vec4 color;\\n""void main()\\n""{\\n""color = vec4(ourColor, 1.0f);\\n""}\\n\\0";// The MAIN function, from here we start the application and run the game loop
int main()
{// Init GLFWglfwInit();// Set all the required options for GLFWglfwWindowHint(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 functionsGLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);glfwMakeContextCurrent(window);// Set the required callback functionsglfwSetKeyCallback(window, key_callback);// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensionsglewExperimental = GL_TRUE;// Initialize GLEW to setup the OpenGL Function pointersglewInit();// Define the viewport dimensionsglViewport(0, 0, WIDTH, HEIGHT);// Build and compile our shader program// Vertex shaderGLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// Check for compile time errorsGLint 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 shaderGLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// Check for compile time errorsglGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\\n" << infoLog << std::endl;}// Link shadersGLuint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// Check for linking errorsglGetProgramiv(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 pointersGLfloat vertices[] = {// Positions         // Colors0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  // Bottom Right-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  // Bottom Left0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f   // Top };GLuint VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// 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);// Position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);// Color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));glEnableVertexAttribArray(1);glBindVertexArray(0); // Unbind VAO// Game loopwhile (!glfwWindowShouldClose(window)){// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functionsglfwPollEvents();// Render// Clear the colorbufferglClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// Draw the triangleglUseProgram(shaderProgram);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);glBindVertexArray(0);// Swap the screen buffersglfwSwapBuffers(window);}// Properly de-allocate all resources once they've outlived their purposeglDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);// 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);
}