OpenGL: Textures

This blog post explains OpenGL texture maps

1. Texture

Texture: Texture, in most cases is a 2D image (there are also 1D and 3D textures)

It can be imagined that the texture is a piece of paper with bricks painted on it, pasted on a 3D house, so that the house looks like it has a brick wall appearance

The following 512x512 brick wall image is a texture:

Next, learn how to paste this texture on the previously drawn triangle

First, you need to define a set of texture coordinates, specifying which part of the texture each vertex of the triangle corresponds to

2. Texture coordinates

The texture coordinates of the 2D texture image are on the x and y axes, the range is (0,1), the origin is the coordinates of the lower left corner (0, 0), and the coordinates of the upper right corner are (1, 1)

Using texture coordinates to obtain texture colors is called sampling (collecting fragment colors)

The following figure shows the mapping of texture coordinates and triangles:

We just pass the vertex shader the three texture coordinates that map to the triangle,

They are then passed to the fragment shader, which interpolates the texture coordinates for each fragment

Texture coordinates:

float texCoords[] = {     0.0f, 0.0f, // bottom left corner     1.0f, 0.0f, // bottom right corner     0.5f, 1.0f // top center };



After setting the texture coordinates,

Also tell OpenGL how to do texture sampling

This is the texture surround mode and texture filtering method that needs to be configured below

3. Texture Surrounding

As mentioned in the previous section, the range of texture coordinates is from (0,0) to (1,1)

Beyond this range , OpenGL will repeat the texture image by default

In fact, OpenGL is chosen by others, but we need to configure it:

Surround

describe

GL_REPEAT

Default: repeat texture image

GL_MIRRORED_REPEAT

Texture Image Mirror Repeat

GL_CLAMP_TO_EDGE

Texture Image Edge Repeat

GL_CLAMP_TO_BORDER

User-specified edge color fill

These options can be set for individual axes using the glTexParameter() function

2D texture coordinate axes: s, t

3D texture coordinate axes: s, t, r

Example:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

Parameter one: texture target

Parameter 2: texture coordinate axis

Parameter three: surround mode

If it is the GL_CLAMP_TO_BORDER option, you also need to call the glTexParameterfv() function to specify an edge color:

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };

glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

4. Texture filtering

Texture filtering can be understood as a way for OpenGL to sample texture pixels when using textures for mapping

There are many options for texture filtering, the most important are two: GL_NEAREST and GL_LINEAR

GL_NEAREST : Nearest Neighbor Filtering, the default texture filtering method of OpenGL

OpenGL will select the pixel whose center point is closest to the texture coordinates as the sampling color

There are four pixels in the picture below, the plus sign represents the texture coordinates, the center of the texture pixel in the upper left corner is closest to the texture coordinates, so its pixel color will be sampled:

GL_LINEAR: Linear Filtering

Based on the texels near the texture coordinates, OpenGL calculates an approximate color between those texels.

The closer the center of a texel is to the texture coordinates, the greater the contribution of the texel's color to the final sample color.

In the image below you can see that the returned color is a blend of neighboring pixels:

Applying a low-resolution texture to a large object, what are the different effects of these two texture filtering methods,

Look at the picture below:

GL_NEAREST : Sharpened grainy patterns, allowing the pixels that make up the texture to be clearly seen

GL_LINEAR : smooth blurred patterns, it is difficult to see individual texels

Proximity filtering is used when textures are minified

Use linear filtering when textures are upscaled

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Five. Multi-level gradual distance texture

When texture mapping multiple objects with different distances, there is no need for distant objects to use normal resolution textures for mapping

This not only wastes memory, but also affects OpenGL rendering performance, and using large resolution textures on small objects will also produce unrealistic

For this, OpenGL uses a method called multi-level gradient texture (Mipmap)

If this concept is based on the interpretation of official documents, it is a bit difficult to understand thoroughly.

In explaining the multi-level gradient texture, first introduce a very common term in the field of graphics and images: image pyramid

It is an image of the original size that is stepwise downsampled with a scalar coefficient to create images of different scalar levels, which is called an image pyramid.

In the OpenGL multi-level gradual distance texture mode, the scalar is 1/2

After creating a texture, call the glGenerateMipmaps () function and pass in the OpenGL texture type bound to the texture ID to enable the multi-level fading mode of the texture:

unsigned int texture;

glGenTextures(1, &texture);

glBindTexture(GL_TEXTURE_2D, texture);

...

glGenerateMipmap(GL_TEXTURE_2D);

OpenGL will select a texture with the most suitable resolution for the object from the image pyramid of this texture according to the distance of the object.

OpenGL multi-level gradual distance texture example:

We don't need to care about the details of image pyramid creation and selection, OpenGL will automatically handle it for us, all we need to do is call glGenerateMipmaps()

When switching the multi-level gradual distance texture level (Level) in rendering, OpenGL will produce an unreal hard border between two different levels of multi-level gradual distance texture layers.

Just like normal texture filtering, you can also use NEAREST and LINEAR filtering between two different fading texture levels when switching fading texture levels.

In order to specify the filtering method between different multi-level gradient texture levels, you can use one of the following four options instead of the original filtering method: 

filter method describe
GL_NEAREST_MIPMAP_NEAREST Use nearest-neighbor multi-level gradient textures to match pixel size, and use proximity interpolation for texture sampling
GL_LINEAR_MIPMAP_NEAREST Use the nearest multi-level fader texture level and sample using linear interpolation
GL_LINEAR_MIPMAP_NEAREST Use the nearest multi-level fader texture level and sample using linear interpolation
GL_LINEAR_MIPMAP_LINEAR Use linear interpolation between two adjacent multi-level gradient textures, and use linear interpolation to sample

Multi-level gradient textures are only used when the texture is scaled down

Using a multi-level fader texture when the texture is enlarged will have no effect and will generate a GL_INVALID_ENUM error code

6. Loading and creating textures

1.stb_image.h

Texture maps need to serialize images in different formats (png, jpg, bmp....), you can use the open source stb_image.h library

It is not so much a library, it is actually a .h file, and the function declarations and definitions for binary parsing of pictures in various formats are all in this .h file

We just need to download it and put it in inc, and then define and include it in the code

Download address: stb/stb_image.h at master nothings/stb GitHub

Code loading:

(1) Header file loading

#define STB_IMAGE_IMPLEMENTATION

#include "stb_image.h"

#define STB_IMAGE_IMPLEMENTATION: The preprocessor will modify the header file so that it only contains the relevant function definition source code, which is equivalent to turning this header file into a .cpp file. The #define sentence in the code should be #include "stb_image.h " before, otherwise the following error will be reported:

(2) Load pictures in the code:

int width, height, nrChannels;

unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);

stbi_load() function:

Parameter 1: image path

Parameters 2 and 3: image width and height

Parameter four: the number of color channels

2. Generate texture

(1) Create texture ID:

unsigned int texture;

glGenTextures(1, &texture);

(2) Bind the texture ID to OpenGL:

glBindTexture(GL_TEXTURE_2D, texture);

(3) glTexImage2D() generates texture:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

glGenerateMipmap(GL_TEXTURE_2D);

glTexImage2D() function:

Parameter one: the 2D texture currently bound to OpenGL

Parameter 2: Multi-level gradual distance texture level, if you set it manually, fill in 0

Parameter three: the format in which the texture is stored

Parameters four and five: texture width and height

Parameter six: set to 0, don't ask why, OpenGL historical issues

Parameters seven and eight: source image format and data type (we load this image using RGB values ​​and store them as char (byte) arrays)

Parameter nine: real image data

After calling glTexImage2D(), the texture object currently bound to OpenGL will be attached with the texture image

If you do not set the multi-level gradient texture, OpenGL will only load the original texture image

The second parameter allows us to manually set the multi-level gradual distance texture level (that is, the image pyramid level), or we can call glGenerateMipmap() externally to set it separately and let OpenGL configure the level by itself

(4) Release image memory:

stbi_image_free(data);

(5) The overall process of generating a texture:

//创建、绑定纹理
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);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.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);

3. Draw the texture:

(1). Define the vertex data with added texture coordinates:

float vertices[] = {

        // ---- position ---- ---- color ---- - texture coordinates -

        0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right

        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right

        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left

        -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left

};

(2). Configure vertex attributes

New vertex format:

Vertex attribute configuration code:

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));

glEnableVertexAttribArray(2);

(3) Modify the shader code

Modify the vertex shader code to increase the texture coordinates

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

Modify the fragment shader code to use the output variable TexCoord of the texture coordinates in the vertex shader as the input variable

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

The texture object should also be accessible in the fragment shader

Sample2D: GLSL's built-in data type SampleXD, also known as a sampler , can be used to add texture objects to shaders

texture(): GLSL built-in function, the first the texture sampler, and the second parameter is the texture coordinate

The texture function will use the previously set texture parameters to sample the corresponding color value

The output of this fragment shader is the color after the texture interpolation filter

(4) draw

Bind texture, glDrawElements() draw

glBindTexture(GL_TEXTURE_2D, texture);

glBindVertexArray(VAO);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

Compile and run:

Modify the fragment shader to mix the texture color with the vertex color:

FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

7. Blend textures

1. Texture unit

The wooden box texture map effect has been realized in the above process

One of the details is that in the fragment shader we define a uniform variable of type sampler2D for the texture object: ourTexture

As mentioned in the previous blog post, the uniform global variable in the shader can be assigned in the code using the glUniformX() type function

Then, we can define multiple textures in the fragment shader

The position values ​​of these textures in the fragment shader (also can be understood as index values) are called texture units

Texture objects need to be activated by calling glActiveTexture() before binding

glActiveTexture(GL_TEXTURE0); // Activate the texture unit before binding the texture

glBindTexture(GL_TEXTURE_2D, texture);

OpenGL activates GL_TEXTURE0 by default

Because there is only one texture in the previous code, we did not call glActiveTexture() to activate the texture specifically

OpenGL guarantees at least 16 texture units , which means it can be activated from GL_TEXTURE0 to GL_TEXTRUE15

They are all defined in order, so we can also get GL_TEXTURE8 through GL_TEXTURE0 + 8

This is useful when we need to loop over some texture units

2. Mix rendering two textures

Two texture mixed maps, first modify the fragment shader code:

#version 330 core
...

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

mix(): GLSL built-in function , using the third parameter weighted for linear interpolation

            Output: parameter 1*(1-parameter 3) + parameter 2*parameter 3

After adding a texture, in addition to the fragment shader code needs to be modified, the rendering process code also needs to be changed accordingly

This is reflected in two places:

(1). Before the texture object is bound to OpenGL, it must be activated

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

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

(2). Set the texture unit of the texture sampler to OpenGL

Ensure that each uniform sampler corresponds to the correct texture unit

// 不要忘记在设置uniform变量之前激活着色器程序!
ourShader.use(); 
// 手动设置
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); 
// 或者使用我们前文中封装好的着色器类的api设置
ourShader.setInt("texture2", 1); 
while(...)
{
[...]
}

Another thing to note is that

OpenGL requires that the y-axis 0.0 coordinate is at the bottom of the picture, but the y-axis 0.0 coordinate of the picture is usually at the top

So the code also needs to flip the y-axis of the image through the api of stb_image.h:

stbi_set_flip_vertically_on_load(true);

3. Compile and run 

Guess you like

Origin blog.csdn.net/geyichongchujianghu/article/details/129909235