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.)
- for
Draw triangle pattern
For 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:
-
- Create a map store in WebGL.
-
- Switch the WebGL state machine to this map store.
-
- 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:
-
- coordinates
-
- 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…