In the last article, we drew a multi-colored triangle based on vertex data. This time, we started to work on the image.

Let’s take a look at the final effect we want to achieve. Take a certain color of each image and mix it into the final texture data.

texture

We can add color to each vertex to add graphic detail to create interesting images. However, if we want the graph to look realistic, we must have enough vertices to specify enough colors. This incurs a lot of extra overhead because each model requires more vertices, and each vertex requires a color attribute.

Artists and programmers prefer to use textures. A texture is a 2D image (there are even 1D and 3D textures) that can be used to add detail to an object; You can imagine a texture being a piece of paper painted with bricks that folds seamlessly into your 3D house so that your house looks like it has a brick facade. Because we can insert so much detail into a single image, we can make things very fine without specifying extra vertices.

polygon

Remember, all of OpenGL’s graphics are made of triangles, so the question is, what about other shapes? A rectangle? The two triangles together, that is 6 points, if more complex, for example if you need to thin body, and so on the character images in operation, the need to split into more triangles, then the vertex is n * 3, obviously it is not acceptable, on second thought, because many points are repeated, such as rectangular 2 triangles, but actually only 4 points, Is there a way to record only the index, that way, you don’t have to define duplicate data vertices, and opengL obviously takes that into account, and EBO index buffers are for that.

EBO (Index buffer object)

This is exactly how index buffer objects work. Like the vertex buffer object, EBO is a buffer that stores indexes. OpenGL calls the indexes of these vertices to determine which vertex to draw. Indexed Drawing is the solution to our problem. First, we need to define (non-repeating) vertices and the indexes needed to draw the rectangle. Example code is shown below.

Float are [] = {0.5 f, f 0.5, 0.0, f / 0.5 f/right corner, 0.5 f, 0.0 f, / / the bottom right hand corner - 0.5 f to 0.5 f to 0.0 f, / / the bottom left corner - 0.5 f, 0.5 f, 0.0f // top left}; Unsigned int indices[] = { 0, 1, 3, // First triangle 1, 2, 3 // second triangle}; // Generate eBO buffer object unsigned int eBO; glGenBuffers(1, &EBO); // Bind buffer object data glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // Finally call glDrawElements glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);Copy the code

The glDrawElements function retrieves the index from the EBO currently bound to the GL_ELEMENT_ARRAY_BUFFER target. This means that we have to bind the appropriate EBO each time we want to render an object with an index, which is still a bit of a hassle. But the vertex array object can also hold the binding state of the index buffer object. The index buffer object being bound at VAO binding time is saved as the element buffer object of VAO. The BINDING of VAO also automatically binds EBO.

In short, VAO automatically binds to EBO, and if there is a VAO, the final code should look like this

/ /.. :: Initialization code ::.. // 1. Bind the vertexArray object glBindVertexArray(VAO); // 2. Copy our array of vertices into a vertex buffer for OpenGL to use glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 3. Copy our index array into an index buffer for OpenGL to use 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(float), (void*)0); glEnableVertexAttribArray(0); [...]. / /.. :: Draw code (in render loop) ::.. glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0) glBindVertexArray(0);Copy the code

Just call glBindVertexArray(VAO), VBO bound to VAO,EBO will be automatically bound to it, combined with EBO, based on the previous article, you can try to draw a rectangle.

Texture wrap

As mentioned earlier, texture coordinates usually range from (0, 0) to (1, 1), but what happens if the setting goes beyond that? OpenGL’s default behavior is to repeat the texture image (we basically ignore the integer part of the floating-point texture coordinates), but OpenGL offers more options:

Around the way describe
GL_REPEAT Default behavior for textures. Repeat texture image.
GL_MIRRORED_REPEAT Same as GL_REPEAT, but each repeat image is mirrored.
GL_CLAMP_TO_EDGE The texture coordinates are constrained between 0 and 1, and the excess portion repeats the edges of the texture coordinates, creating the effect of the edges being stretched.
GL_CLAMP_TO_BORDER The coordinate beyond is the user-specified edge color.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
Copy the code

Load and create textures

In OpengL, there is no concept of an image, everything is considered a texture data

Stb_image.h is Sean Barrett’s very popular single-header image loading library. It loads most popular file formats and is easy to integrate into your project. Stb_image.h can be downloaded here. Download the header file, add it to your project as stb_image.h, and create a new C++ file by typing the following code:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
Copy the code

By defining STB_IMAGE_IMPLEMENTATION, the preprocessor changes the header file to include only the source code for the associated function definitions, effectively turning the header into a.cpp file. Now just include stb_image.h in your program and compile it.

To generate the texture

As with the generated OpenGL object, the texture is referenced by ID. Let’s create one, roughly as follows

unsigned int texture; GlGenTextures (1, &texture); GlBindTexture (GL_TEXTURE_2D, texture); GlTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data) glGenerateMipmap(GL_TEXTURE_2D);Copy the code

glTexImage2D

  • The first parameter specifies the texture Target. Setting it to GL_TEXTURE_2D means that textures will be generated on the same target as the currently bound texture object (any textures bound to GL_TEXTURE_1D and GL_TEXTURE_3D will not be affected).
  • The second parameter specifies the level of the multi-level fade texture for the texture
  • The third parameter tells OpenGL what format we want to store the texture in. Our image is onlyRGBValue, so we also store the texture asRGBvalue
  • The fourth and fifth parameters set the width and height of the final texture. We stored them when we loaded the images earlier, so we use the corresponding variables.
  • The next argument should always be set to0(Problems left over from history)
  • The seventh and eighth parameters define the format and data type of the source diagram. We load the images with RGB values and store them aschar(byte) array, we will pass in the corresponding values.
  • The last parameter is the actual image data.

Generate a texture data that looks something like this

unsigned int texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); 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); // Load and generate textures int width, height, nrChannels; unsigned char *data = stbi_load("resource/first.jpg", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cout << "Failed to load texture" << std::endl; } stbi_image_free(data);Copy the code

Let’s tweak the fragment shader code

#version 330 core
out vec4 FragColor;
​
in vec3 ourColor;
in vec2 TexCoord;
​
uniform sampler2D ourTexture;
​
void main()
{
    FragColor = texture(ourTexture, TexCoord);
}
Copy the code

As you can see, our out FragColor is the texture data of the used image, which is sent to the next pipeline for processing. Once processed correctly, an image will be displayed.

But careful you will find, eh? The ourTexture does not see where the value is assigned to it. Where does that value come from? If you’re new to OpengL, a lot of times you’ll have this problem, sometimes it looks like it’s not assigned, but it will always show the correct result, and often that’s because there’s a lot of default bindings

The position value of a Texture is usually called a Texture Unit. The default texture unit for a texture is 0, which is the default active texture unit, so we didn’t assign a location value earlier in the tutorial.

The main purpose of the texture unit is to allow us to use more than one texture in the shader. By assigning texture units to the sampler, we can bind multiple textures at once, as long as we activate the corresponding texture unit first. Just like glBindTexture, we can use glActiveTexture to activate the texture unit, passing in the texture unit we need to use:

glActiveTexture(GL_TEXTURE0); GlBindTexture (GL_TEXTURE_2D, texture) is activated before binding the texture;Copy the code

The texture unit GL_TEXTURE0 is always activated by default, so we didn’t need to activate any texture units in the previous example when we used glBindTexture.

We add an image to display, and at the same time, we dilute this image and overlay it on top of the first image, which is actually in the fragment shader, and adjust the color algorithm.

#version 330 core ... uniform sampler2D texture1; uniform sampler2D texture2; Void main() {FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2); }Copy the code

Use mix to blend the two textures. 0.4 returns 60% of the first input color and 40% of the second.

The complete code is as follows for reference

#include "TranslateSample.h";
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <filesystem>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION

#include "stb_image.h"
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos; \n"
"layout (location = 1) in vec2 aTexCoord; \n"

"uniform mat4 transform ; \n"

"out vec2 TexCoord; \n"
"void main()\n"
"{\n"
"Gl_Position = vec4(apos. x, apos. y, apos. z, 1.0); \n"
" TexCoord = aTexCoord; \n"
"} \ 0";
const char* fragmentShaderSource = "#version 330 core\n"
"in vec2 TexCoord;; \n"
"out vec4 FragColor; \n"
"uniform sampler2D texture1; \n"
"uniform sampler2D texture2; \n"
"void main()\n"
"{\n"
"FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.4); \n"
"} \ 0";

TranslateSample::TranslateSample()
{
}

TranslateSample::~TranslateSample()
{
}


void TranslateSample::init(a) {
    // glfw: initialize and configure
   // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL".NULL.NULL);
    if (window == NULL)
    {
        std: :cout << "Failed to create GLFW window" << std: :endl;
        glfwTerminate();
        return;
    }
    glfwMakeContextCurrent(window);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if(! gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std: :cout << "Failed to initialize GLAD" << std: :endl;
        return;
    }

    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char 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
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile 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
    unsigned int 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 configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
        // ---- location ---- - texture coordinates -
             0.5 f.0.5 f.0.0 f.1.0 f.1.0 f./ / right
             0.5 f.0.5 f.0.0 f.1.0 f.0.0 f./ / right
            0.5 f.0.5 f.0.0 f.0.0 f.0.0 f./ / lower left
            0.5 f.0.5 f.0.0 f.0.0 f.1.0 f    / / left
    };
    unsigned int indices[] = {
          0.1.3.// The first triangle
          1.2.3  // The second triangle
    };
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

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

    glVertexAttribPointer(0.3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1.2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void(*)3 * sizeof(float)));
    glEnableVertexAttribArray(1);


    unsigned int EBO;
    glGenBuffers(1, &EBO);

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


    unsigned int texture1;
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // Flip the Y-axis. OpenGL requires the Y-axis to be at the bottom of the image, but the Y-axis is usually at the top
    stbi_set_flip_vertically_on_load(true);
    int width, height, channel;
    unsigned char* data = stbi_load("resource/container.jpg", &width, &height, &channel, 0);
    if (data)
    {
        std: :cout << "success\n" << width << height << std: :endl;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std: :cout << "stbi_load_FAILED\n" << std: :endl;
    }

    unsigned int texture2;
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    int width2, height2, channel2;
    unsigned char* data1 = stbi_load("resource/awesomeface.png", &width2, &height2, &channel2, 0);
    if (data1)
    {
        std: :cout << "success\n" << width2 << height2 << std: :endl;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width2, height2, 0, GL_RGBA, GL_UNSIGNED_BYTE, data1);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std: :cout << "stbi_load_FAILED\n" << std: :endl;
    }
    stbi_image_free(data);
    stbi_image_free(data1);


    glUseProgram(shaderProgram);
    glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);
    glUniform1i(glGetUniformLocation(shaderProgram, "texture2"), 1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glBindVertexArray(0);

    // render loop
    while(! glfwWindowShouldClose(window)) {// input
        // -----
        glViewport(0.0, SCR_WIDTH, SCR_HEIGHT);

        // Link to each cell

        // render
        glClearColor(0.2 f.0.3 f.0.3 f.1.0 f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Bind the image texture
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);

        // draw our first triangle
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return;

}



Copy the code