OpenGL concise tutorial shaders

3.1 start

Shader is a small program running on GPU. In the previous article, we learned how to use shader to change vertex array into the figure we want triangle. In this article, we will make other figures, such as rectangle; the configuration method of shader, the configuration of figure color, the realization of some other colors and so on.

3.2 rectangle

3.2.1 add vertex

If you only use the knowledge in the previous article to draw a rectangle, you can think of drawing two connected triangles. In this way, as long as two triangles - six vertex data are passed into the shader program, the shader program does not need to be changed.

GLfloat vertices[] = {
    // First triangle
    0.5f, 0.5f, 0.0f,   // Upper right corner
    0.5f, -0.5f, 0.0f,  // Lower right corner
    -0.5f, 0.5f, 0.0f,  // Top left corner
    // Second triangle
    0.5f, -0.5f, 0.0f,  // Lower right corner
    -0.5f, -0.5f, 0.0f, // Lower left quarter
    -0.5f, 0.5f, 0.0f   // Top left corner
};

As long as the drawing function in the main loop is changed a little, a rectangle can be drawn.

// Change the original 3 to 6, and draw a triangle with 6 vertices
glDrawArrays(GL_TRIANGLES, 0, 6);
3.2.2 index method

It is wasteful to use six vertices to draw a rectangle. In fact, there are only four vertices in the rectangle. OpenGL provides index drawing method, which only gives the vertex and drawing order (index).

GLfloat vertices[] = {
    0.5f, 0.5f, 0.0f,   // Upper right corner
    0.5f, -0.5f, 0.0f,  // Lower right corner
    -0.5f, -0.5f, 0.0f, // Lower left quarter
    -0.5f, 0.5f, 0.0f   // Top left corner
};
GLuint indices[] = { // Note that the index starts from 0! 
    0, 1, 3, // First triangle
    1, 2, 3  // Second triangle
};

To draw with index mode, in addition to VBO and VAO, another kind of buffer object, Element Buffer Object (EBO), must be used

// Create code for VBO and VAO (before unbinding)
// Create an EBO object
GLuint EBO;
glGenBuffers(1, &EBO);
// Bind the newly created buffer to the GL? Element? Array? Buffer target in a different way than VBO and VAO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// Copy index data to buffer, similar to VBO vertex input
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); 
// Untying
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

When drawing in the main loop, you need to change the original glDrawArrays() function to glDrawElements()

// The number of vertices to be drawn is 6, and the index type is int, that is, GL? Unsigned? Int
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);


Exercise: draw 2 triangles of different colors. Source code: https://www.cnblogs.com/darkmorn/p/12258911.html

3.3 GLSL

GLSL (OpenGL Shading Language) is a kind of C language. GLSL focuses on computer graphics and has a good performance on vector and matrix graphics data. GLSL is used to configure shaders, and the work of shaders is to convert one input to another output. The in and out keywords of GLSL correspond to the input data and output data. main() is the program's entry function, similar to C language.

3.3.1 data type

Similar to C language, the basic data types in GLSL are: int, float, double, uint (object) and bool.
GLSL has two types of containers: Vector and Matrix.

Vector type Meaning
vecn The default vector containing n float components, such as vec1 vec2 vec3 vec4
bvecn Vector containing n bool components
ivecn A vector containing n int components
uvecn A vector containing n unsigned int components
dvecn A vector containing n double components

A vector can contain 1-4 components, corresponding to xyzw. Each component is a basic data type. Vector is a kind of flexible data type, which can be obtained by vector.x or freely combined by vector.xyx.

vec2 vectorA = vec2(0.3f, 0.5f);
// vector.x is used to get the first component, xyzw represents the first to fourth components respectively, and it can also be flexibly combined into a new vector
vec3 vectorB = vec3(vectorA.x, vectorA.y, vectorA.x,);
vec3 vectorC = vectorA.xyx;         // The results of vectorB and vectorC are the same: (0.3f, 0.5f, 0.3f)
vec4 vectorD = vec4(vectorC, 0.0f); // vectorD: (0.3f, 0.5f, 0.3f, 0.0f)
// It can even be easily calculated
vec3 vectorE = vectorA.xxy + vectorC.xyz;// vectorE: (0.6f, 0.8f, 0.8f)
3.3.2 syntax

GLSL always starts with version number. Its syntax is basically the same as C language. A standard shader source program is like the following:

#version version_number
// Input data
in type in_variable_name0;
in type in_variable_name1;
// output data
out type out_variable_name;
// Global data
uniform type uniform_name;

int main(){
  // Process input and perform some graphic operations
  ...
  // Output processed results to output variables
  out_variable_name = weird_stuff_we_processed;
}

Shaders are encapsulated independently, and the communication between them only depends on the input and output. Each stage receives the output of the previous stage as the input, and outputs to the next stage after processing. The data defined in will be matched by the program with the same name and type defined in out in the previous stage. In this way, the serial connection of data is realized.
But for the entrance and exit of the whole rendering pipeline, they are connected with memory data, so some restrictions need to be made. That is to say, the input (vertex data) of vertex shader and the output (color value) of clip shader are special.

  • The vertex data input by the vertex shader should be received by using layout (location = 0) in vec3 position;. Layout (location = 0) saves the received data to the location with the attribute value of 0. At least 16 vertex attributes are defined in OpenGL, each of which can store a vector of up to 4 components.
  • The color value variable name output by the clip shader can be named arbitrarily, but the type must be vec4

In addition to in and out, there is also a data exchange method called uniform, which is a global variable of GLSL, and can be defined on any shader. Once defined, it can be used by any shader. At the same time, this variable can be taken and modified by cpu (user program).

3.3.3 color triangle

The color information is not only processed by segment shaders, but also can be directly passed to vertex shaders, and then output by segment shaders as is, which reduces the workload of segment shaders.

GLfloat vertices[] = {
    // Location / / color
     0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // lower right
    -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // Left lower
     0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // Top
};

We add the color information after the vertex position information, and the shader needs to separate the information. Different vertex attributes, i.e. location, are used to distinguish.

#version 330 core
layout (location = 0) in vec3 position; // The property location value of the location variable is 0 
layout (location = 1) in vec3 color;    // The attribute position value of the color variable is 1

out vec3 ourColor; // Output a color to the clip shader

void main(){
    gl_Position = vec4(position, 1.0);
    ourColor = color; // Set our ourColor to the input color we get from the vertex data
}

Clip shaders simply add the color value to the alpha value (opacity) and output it directly.

#version 330 core
in vec3 ourColor;
out vec4 color;

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

Finally, the glVertexAttribPointer() function used in linking vertices can be modified slightly.

// Location property, the location value is 0, the step size is 6 float s, and the offset from the beginning is 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// Color attribute, the location value is 1, the step size is 6 floats, and the offset from the start is 3 floats
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3* sizeof(GLfloat)));
glEnableVertexAttribArray(1);

The final result is this:

We determine the color of the three corners, and OpenGL will use color interpolation to fill other parts of the clip, and the calculation result is equivalent to average the color around it.

3.3.4 color change

Next, let's do something different. Let's change the color of the triangle.
Vertex shaders add a global uniform vector, which is used to calculate variation for different angles.

#version 330 core

uniform vec3 change;
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
out vec3 ourColor;

void main(){
	gl_Position = vec4(position, 1.0f);
	if(color.x > 0.0f) ourColor = change.xyz;
	if(color.y > 0.0f) ourColor = change.yzx;
	if(color.z > 0.0f) ourColor = change.zxy;
	//ourColor = color;
}

In the main loop, glfwGetTime() is used to get the current running time, and glGetUniformLocation() function is used to get the uniform vector, which alternates the red, green and blue colors, and GLSL is used to achieve the effect of three role colors in the red, green and blue color gradient.

// Changing the color of triangles in real time
// In the main cycle:
glUseProgram(shaderProgram);
GLfloat timeValue = glfwGetTime();
float speedDown = 5;
timeValue = timeValue / speedDown;
GLint intValue = timeValue;
GLfloat xValue = (timeValue - intValue)*3;
GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "change");
GLfloat redValue = xValue < 1.0f ? 1.0f - xValue : (xValue >= 2.0f ? xValue - 2.0f : 0.0f);
GLfloat greenValue = xValue < 2.0f && xValue >= 1.0f ? 2.0f - xValue : (xValue < 1.0f ? xValue : 0.0f);
GLfloat blueValue = xValue >= 2.0f ? 3.0f - xValue : (xValue < 2.0f && xValue >= 1.0f ? xValue - 1.0f : 0.0f);
glUniform3f(vertexColorLocation, redValue, greenValue, blueValue);

Source code: https://www.cnblogs.com/darkmorn/p/12264774.html

Published 9 original articles, won praise 3, visited 961
Private letter follow

Tags: C Attribute

Posted on Wed, 05 Feb 2020 23:46:27 -0500 by lansing