Textures

Texture is an image which is used to draw the colour of the pixels of a certain model.

Texture coordinates specify the point in the texture image that will correspond to the vertex you are specifying them for. Thus (0,0) is top left corner of image and (1,1) is bottom right corner.

In OpenGL, coordinates are called S and T. You can also meet U and V names.

Texel or texture pixel specifies color of textured surface.

In complex models, one vertex can be involved in rendering different textures. For example, each vertex of cube used to render 3 faces. So, if you want to render a textured cube you must specify 24 vertices. One vertex 3 times: same coordinates, but different texture coordinates. You can download full sources of textured cube on GitHub.

load image

Before creating the texture, you need to load the texture image. This task dependent from platform and library.

LWJGL 3 uses the STBImage library. Unlike PNGDecoder library, it allows to load big image into ByteBuffer without memory problem. With PNGDecoder i could load maximum 64x64 image, with STBImage 1024x1024. Also STBImage supports other image formats.

On Android a bitmap data is limited to half the memory that the application can use. For example, if an app can use 32 MB, then all images cannot exceed 16 MB.

On WebGL images are loaded from network. This is asynchronous task. While image is loading you can use a one-pixel temporary texture.

On WebGL a COARSE rule will be applied to images. If you want try a demo on local machine, you must start own web server, for example with node.js

Load image example

There are sites where you can download free textures for testing, for example:

create texture

Create texture example
fun createTexture(idTex: Int, imageData: ByteBuffer,
    width: Int, height: Int): Int {

    val idTexture = if (idTex == 0) glGenTextures() else idTex

    glBindTexture(GL_TEXTURE_2D, idTexture)

    glTexImage2D(
        GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
        GL_RGBA, GL_UNSIGNED_BYTE, imageData
    )

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    return idTexture
}
public static int createTexture(int idTex, 
              ByteBuffer imageData,
              int width, int height) {
    
    int idTexture = (idTex == 0) ? glGenTextures() : idTex;

    glBindTexture(GL_TEXTURE_2D, idTexture);

    glTexImage2D(
         GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
         GL_RGBA, GL_UNSIGNED_BYTE, imageData
    );

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    return idTexture;
}
GlUtils.createTexture = function(gl, idTexture, image) {
    idTexture = (idTexture == 0) ? gl.createTexture() : idTexture;
    gl.bindTexture(gl.TEXTURE_2D, idTexture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    return idTexture;
}
fun createTexture(idTex: Int, imageData: Bitmap): Int {

    val idTexture = if (idTex == 0) {
         glGenTextures(1, ID_ARRAY, 0)
         ID_ARRAY[0]
    } else {
        idTex
    }

    glBindTexture(GL_TEXTURE_2D, idTexture)

    GLUtils.texImage2D(GL_TEXTURE_2D, 0, imageData, 0);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    return idTexture
}

shader with texture

Let's see our default shaders.

#version 300 es
precision mediump float;
layout(location=0) in vec4 vCoord;
layout(location=1) in vec4 vColor ;
layout(location=2) in vec3 vNormal;
layout(location=3) in vec2 vTexCoord ;
out vec4 exColor;
out vec2 exTexCoord;
uniform mat4 matrix;
uniform vec4 uColor;
void main() {
     gl_Position = matrix  * vCoord;
     if(uColor.w != -1.0) { exColor=uColor; } else {exColor=vColor;} 
     exTexCoord = vTexCoord;
}
#version 300 es
precision mediump float;
in vec4 exColor;
in vec2 exTexCoord;
out vec4 fragColor;
uniform int withTexture;
uniform sampler2D texSampler;

void main() {
    if(withTexture==1) {fragColor = texture(texSampler, exTexCoord);}
    else {fragColor = exColor;}
}

As you see a vertex shader must accept texture coordinates and pass them to the fragment shader.

Fragment shader must have variable of the sampler2D type. It will contain index of texture unit. WebGL provides a minimum of 8 texture units (named as TEXTURE0, TEXTURE1,...).

Also, the fragment shader must render the resulting color using the texture (the texture() function).

vertex data

To render a textured quad, we need the vertex data, for example as shown below

val data = floatArrayOf( 
  // coords         texture coords
  -1.0f, -1.0f, /* */ 0.0f, 1.0f, // first triangle
  1.0f, -1.0f,  /* */ 1.0f, 1.0f,
  1.0f, 1.0f,   /* */ 1.0f, 0.0f,
  
  1.0f, 1.0f,   /* */ 1.0f, 0.0f, // second triangle
  -1.0f, 1.0f,  /* */ 0.0f, 0.0f,
  -1.0f, -1.0f, /* */ 0.0f, 1.0f
)
float[] data = new float[]{
  // coords         texture coords
  -1.0f, -1.0f, /* */ 0.0f, 1.0f, // first triangle
  1.0f, -1.0f,  /* */ 1.0f, 1.0f,
  1.0f, 1.0f,   /* */ 1.0f, 0.0f,

  1.0f, 1.0f,   /* */ 1.0f, 0.0f, // second triangle
  -1.0f, 1.0f,  /* */ 0.0f, 0.0f,
  -1.0f, -1.0f, /* */ 0.0f, 1.0f
}
const vData = [
  // coords         texture coords
  -1.0, -1.0, /**/ 0.0, 1.0, // first triangle
  1.0, -1.0,  /**/ 1.0, 1.0,
  1.0, 1.0,   /**/ 1.0, 0.0,
  
  1.0, 1.0,   /**/ 1.0, 0.0, // second triangle
  -1.0, 1.0,  /**/ 0.0, 0.0,
  -1.0, -1.0, /**/ 0.0, 1.0
];

render texture

Draw textured quad example
// use specified shader program
glUseProgram(prog.idProgram) 
 
// find location of sampler 
val samplerLocation = glGetUniformLocation(prog.idProgram, "texSampler")
 
// set active texture unit (0 in our case)
glActiveTexture(GL_TEXTURE0)
 
// bind specified texture to the texture unit 0
glBindTexture(GL_TEXTURE_2D, idTexture)
 
// bind specified sampler to the texture unit 0
glUniform1i(samplerLocation, 0)

// do other stuff ...
       
// if you want apply texture to the one side of quad
// glEnable(GL_CULL_FACE);
 
// draw vertices as usually
glDrawArrays(GL_TRIANGLES, 0, 6) 
// use specified shader program
glUseProgram(prog.idProgram);
  
// find location of sampler   
int samplerLocation = glGetUniformLocation(prog.idProgram, "texSampler");

// set active texture unit (0 in our case)
glActiveTexture(GL_TEXTURE0);

// bind specified texture to the texture unit 0
glBindTexture(GL_TEXTURE_2D, idTexture);

// bind specified sampler to the texture unit 0
glUniform1i(samplerLocation, 0);

// do other stuff ...

// if you want apply texture to the one side of quad
// glEnable(GL_CULL_FACE);

// draw vertices as usually
glDrawArrays(GL_TRIANGLES, 0, 6); 
// use specified shader program
gl.useProgram(prog.idProgram);

// set active texture unit (0 in our case)
gl.activeTexture(gl.TEXTURE0);

// bind the texture to texture unit 0
gl.bindTexture(gl.TEXTURE_2D, this.idTexture);

// find location of sampler  
const sampleLocation = gl.getUniformLocation(prog.idProgram, 
                            'texSampler');

// bind specified sampler to the texture unit 0
gl.uniform1i(sampleLocation, 0);

// if you want apply texture to the one side of quad
//  gl.enable(gl.CULL_FACE);

gl.drawArrays(gl.TRIANGLES, 0, 6);
// same as kotlin (LWJGL 3)
// use specified shader program
glUseProgram(prog.idProgram) 
 
// find location of sampler 
val samplerLocation = glGetUniformLocation(prog.idProgram, "texSampler")
 
// set active texture unit (0 in our case)
glActiveTexture(GL_TEXTURE0)
 
// bind specified texture to the texture unit 0
glBindTexture(GL_TEXTURE_2D, idTexture)
 
// bind specified sampler to the texture unit 0
glUniform1i(samplerLocation, 0)

// do other stuff ...
       
// if you want apply texture to the one side of quad
// glEnable(GL_CULL_FACE);
 
// draw vertices as usually
glDrawArrays(GL_TRIANGLES, 0, 6) 

texture properties

The glTexParameter() sets main properties of texture.

GL_TEXTURE_MIN_FILTER

The texture minifying function is used whenever the pixel being textured maps to an area greater than one texture element. There are six defined minifying functions. Two of them use the nearest one or nearest four texture elements to compute the texture value. The other four use mipmaps.

GL_TEXTURE_MAG_FILTER

The texture magnification function is used when the pixel being textured maps to an area less than or equal to one texture element. It sets the texture magnification function to either GL_NEAREST or GL_LINEAR (default). GL_NEAREST is generally faster than GL_LINEAR, but it can produce textured images with sharper edges because the transition between texture elements is not as smooth.

GL_TEXTURE_WRAP_S

Sets the wrap parameter for texture coordinate S to either GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT or GL_REPEAT (default).

GL_CLAMP_TO_EDGE causes S coordinates to be clamped to the range [1/(2N), 1−1/(2N)] , where N is the size of the texture in the direction of clamping.

GL_REPEAT causes the integer part of the S coordinate to be ignored. The GL uses only the fractional part, thereby creating a repeating pattern.

GL_MIRRORED_REPEAT causes the s coordinate to be set to the fractional part of the texture coordinate if the integer part of S is even; if the integer part of S is odd, then the s texture coordinate is set to 1−frac(s) , where frac(s) represents the fractional part of S.

GL_TEXTURE_WRAP_T

Same as GL_TEXTURE_WRAP_S for T coordinate.

You can download full sources of textured quad on GitHub.