This is the 12th day of my participation in the August Text Challenge.More challenges in August

Title: WebGL Lesson 26: Texturing Code in Action | More challenges in AugustCopy the code

Helpful hints

This article is the 26th in the WebGL Curriculum column, and it is highly recommended to start with the previous one. Because we spent a lot of time talking about vectors and matrix operations. These basics will influence your thinking.

The code of this lesson can be directly jumped to:Lesson 26 Code

primers

We need to prepare concepts:

  • What we pass into WebGL is vertex by vertex information, including (coordinates, colors, UV, etc.)
  • forDraw triangle patternFor example, after WebGL draws three points every time, interpolation is carried out inside the three points to calculate the coordinates, colors and UV inside the three points
  • Then according to the calculated interpolation, the interior of the three points is filled with color, or the image is sampled according to the UV

The above content was explained in the last class, if you are not clear about it, it is better to go to the last class first.

Create a directory

We are going to do texture this time, so we are going to create a directory

images
Copy the code

My image is called funny-cat.jpeg and it’s a picture of a cat:

images/funny-cat.jpeg
Copy the code

Front-end JS pulls images

This is not difficult, but the main thing to notice is that pulling an image is an asynchronous process.

We must pass the data to WebGL after pulling the image, otherwise the image will not come out and an error will be reported.

The code is as follows:

var image = new Image();
image.src = "images/funny-cat.jpeg"; // Change this to your own image
image.addEventListener('load'.function () {
    // This function is called after the load is complete
});
Copy the code

Pass image data to WebGL

There’s nothing to talk about here, the basic process:

    1. Create a map store in WebGL.
    1. Switch the WebGL state machine to this map store.
    1. Pass the image data just pulled to WebGL.

The code is as follows:

    Create a texture in WebGL
    let texture = gl.createTexture();
    // Switch the state machine to the current texture
    gl.bindTexture(gl.TEXTURE_2D, texture);
    //
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
Copy the code

Combine the two processes

Since the image was pulled successfully, it is given in the form of an asynchronous callback function, so the code is as follows:

var images_loaing_progress = 0; // Create a variable to record whether the image has been loaded
function CreateTextureAndLoadImage(gl) {
    Create a texture in WebGL
    let texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // Asynchronously load an image into the newly created texture
    var image = new Image();
    image.src = "images/funny-cat.jpeg";
    image.addEventListener('load'.function () {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        images_loaing_progress = 100;
    });
    return texture;
}

Copy the code

If you write this way, it is very likely that you will not see the picture, and you will report an error.

Why is that?

WebGL requires the width height of images to be an integer power of 2. Let’s say 2, 4, 8, 16, et cetera.

In order to make all images usable, our final code is as follows:

var images_loaing_progress = 0;

// Check if it is 2 to the integer power
function isPowerOf2(value) {
    return (value & (value - 1= = =))0;
}

function CreateTextureAndLoadImage(gl) {
    Create a texture in WebGL
    let texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // Asynchronously load an image into the newly created texture
    var image = new Image();
    image.src = "images/funny-cat.jpeg";
    image.addEventListener('load'.function () {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            console.log("Not two to the power of an integer.");
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }
        images_loaing_progress = 100;
    });
    return texture;
}

Copy the code

Okay, maybe you found it

gl.generateMipmap(gl.TEXTURE_2D);
Copy the code

Mipmap (Mipmap, Mipmap, Mipmap) I’ll just write it this way.

Generates vertex coordinates and vertex UV

Whatever we’re showing, we have to have vertices, and in order to draw a triangle, we have at least three vertices.

Vertex information, here are two:

    1. coordinates
    1. UV

Let’s take three points at random:

  • A coordinates :(-1, -1) UV: (0, 0) lower left corner of image

  • B coordinates :(1, -1) UV: (1, 0) lower right corner of image

  • C coordinates :(0, 1) UV: (0.5, 1) image top center

Let’s express the above data as a flat array:

var data = [-1, -1.0.0.1, -1.1.0.0.1.0.5.1
];
var dataArr = new Float32Array(data);
var pointCount = 3;
Copy the code

Notice that the coordinates of the three points must be counterclockwise.

Vertex_shader receives UV information

We know that vertex_shader can be used to receive information from buffer.

Coordinates of vertices:

attribute vec2 a_PointVertex; // Vertex coordinatesCopy the code

UV information is the same:

attribute vec2 a_PointUV; / / vertex UVCopy the code

Extremely simple.

Because of the final color, and what the image is, it’s all done in fragment_shader. So the UV message needs to be passed to fragment_shader.

So we must declare a variable VARYING:

    varying vec2 uv;
Copy the code

The final vertex_shader:

<script id="vertex_shader" type="myshader"> // Vertex Shader precision mediump int; precision mediump float; uniform mat3 u_all; // attribute vec2 a_PointVertex; // Attribute vec2 a_PointUV; // Vertex VARYING VEC2 UV; Void main() {vec3 coord = u_all * vec3(a_PointVertex, 1.0); Gl_Position = vec4(coord.x, coord.y, 0.0, 1.0); uv = a_PointUV; } </script>Copy the code

Since we use a buffer to store both coordinates and UV information, we must tell vertex_shader how to use this buffer:

var a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
var a_PointUV = gl.getAttribLocation(program, 'a_PointUV');
gl.vertexAttribPointer(a_PointVertex, 2, gl.FLOAT, false.16.0); // Each vertex has 16 bytes, starting from the 0th byte
gl.enableVertexAttribArray(a_PointVertex);
gl.vertexAttribPointer(a_PointUV, 2, gl.FLOAT, false.16.8); // Each vertex is 16 bytes, UV starts at the 8th byte
gl.enableVertexAttribArray(a_PointUV);
Copy the code

Fragment_shader samples an image using UV information

We know that buffers can be received as attributes in vertex_shader.

So how do we get the Image information, in fragment_shader?

The answer is sampling!Copy the code
      <script id="fragment_shader" type="myshader">
       // Fragment shader
       precision mediump int;
       precision mediump float;

       uniform sampler2D u_funny_cat; // What is the cat

       varying vec2 uv;

       void main() {
         vec4 sample_color = texture2D(u_funny_cat, uv);
         gl_FragColor = vec4(sample_color, 1.0);
       }
   </script>

Copy the code

One step

var u_FunnyCatLocation = gl.getUniformLocation(program, "u_funny_cat");
gl.uniform1i(u_FunnyCatLocation, 0);
Copy the code

‘fragment_shader’ has a uniform variable u_funny_cat.

This variable represents the subscript of the image we saved in WebGL. We only saved one image, so the subscript of the image is 0.

See the effect

Note that we need to drag the slider on the page to get the image, because dragging will call the drawing code.

First of all, the picture is upside down. That’s not a big deal. Because of the coordinate system of the picture itself, and the coordinate system used by UV is not the same.

Here we change the vertex’s UV directly:

var data = [-1, -1.0.1.1, -1.1.1.0.1.0.5.0
        ];
Copy the code

It looks like this (again, you need to drag the slider first) :

Oops, the kitten didn’t show up completely.

Display the complete image by changing the UV of the vertex

Since the UV of the three vertices represents that the three points are anchored to that point in the image, the filling in the middle is done automatically by interpolation.

So, to make the kitten complete, let’s change point C (the top point) to -1 and try:

var data = [-1, -1.0.1.1, -1.1.1.0.1.0.5, -1
];
Copy the code

Break it down:

According to the UV of the three points, the UV of the red point in our diagram is roughly (0.5, 0) according to the interpolation.

The point (0.5, 0) is just the top center of the original image. (Note that the original Y is inverted, Y = 0, which is the top).

And then the other points, according to this logic, correspond to the original graph, and this is called interpolation.

We put the

  • The point UV on the left is pulled to the left
  • The point UV on the right is pulled to the right

Code:

var data = [-1, -1, -0.5.1.1, -1.1.5.1.0.1.0.5, -1
        ];
Copy the code

The results are as follows:

So let’s calculate the approximate UV of this box based on the interpolation and the UV of the three points, and see if it’s more or less the four corners of a graph.

Okay, so that’s why, in this box, you can see the whole kitten.

The Y axis of UV is reversed, which is very unpleasant

Because of this, we often have trouble imagining UV messages outside.

So when we’re not building outside, we do this correction.

We fixed it in vertex_shader:

uv = a_PointUV; Y = 1.0-uv. y; // Just this sentence...Copy the code

See the effect:

The kitten turned upside down again, because the UV of our data has just been corrected, we used to retrieve the original data:

var data = [-1, -1.0.0.1, -1.1.0.0.1.0.5.1
];
Copy the code

Effect:

The display is incomplete again, I will not show how to display the complete code, friends to work out.




With the end of the text, here is the q&ACopy the code

Little melon melon said: if I fill in the UV information, what will happen?

  • A: Nothing happens, except that the UV information of the three points is messy, and then the intermediate interpolation is messy, and the displayed image is messy:
var data = [-1, -1.Math.random(), Math.random(),
          1, -1.Math.random(), Math.random(),
          0.1.Math.random(), Math.random()
      ];
Copy the code

You can try writing UV…