Texture box for WebGL learning

We’ve looked at 2d textures beforegl.TEXTURE_2D, but also used it to achieve various effects. But there’s also a cube texturegl.TEXTURE_CUBE_MAPIt contains six textures representing the six faces of the cube. Unlike regular texture coordinates that have 2 latitudes, cube textures use normal vectors, in other words three-dimensional directions. See the demo implemented in this sectionThe sky box

One of the six faces of the cube is selected according to the orientation of the normal vector, and the pixels of this face are used to sample and generate colors. The six faces are referenced by their orientation relative to the center of the cube. They are respectively

gl.TEXTURE_CUBE_MAP_POSITIVE_X/ / right
gl.TEXTURE_CUBE_MAP_NEGATIVE_X/ / left
gl.TEXTURE_CUBE_MAP_POSITIVE_Y/ /
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y/ /
gl.TEXTURE_CUBE_MAP_POSITIVE_Z/ / after
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z/ / before
Copy the code

Environment map

Texture boxes are not used to texture cubes. The standard way to texture cubes is to use 2d maps. What does texture boxes do? The most common use of texture boxes is for environment mapping. One example of an environment mapping application is 3D Street View in Baidu and Google Maps.

texture

Here are 6 red canyon images

Fill each side of the cube with the above 512×512 images. Here is how to create and load the texture

// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
 
const faceInfos = [
  {
    target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, 
    url: '/img/sorbin_rt.jpg'}, {target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 
    url: '/img/sorbin_lf.jpg'}, {target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 
    url: '/img/sorbin_up.jpg'}, {target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 
    url: '/img/sorbin_dn.jpg'}, {target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 
    url: '/img/sorbin_bk.jpg'}, {target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 
    url: '/img/sorbin_ft.jpg',},]; faceInfos.forEach((faceInfo) = > {
  const {target, url} = faceInfo;
  // Upload the canvas to each face of the cube map
  const level = 0;
  const format = gl.RGBA;
  const width = 512;
  const height = 512;
  const type = gl.UNSIGNED_BYTE;
  // Set each face so that it is immediately renderable
  gl.texImage2D(target, level, format, width, height, 0, format, type, null);
 
  // load images asynchronously
  const image = new Image();
  image.src = url;
  image.onload = function() {
    // When the image is loaded, copy it to the texture
    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
    gl.texImage2D(target, level, internalFormat, format, type, image);
    gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
  };
});
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
Copy the code

The normal vector

The difference between standard cube normal vector and texture box normal vector

A huge advantage of using texture boxes for 3D cubes is that you don’t need to specify texture coordinates. As long as the box is placed at the origin of the world coordinate system, the box’s own coordinates can be used as texture coordinates, because in the 3D world position itself is a vector representing a direction, and that’s the direction we want.

So vertex shaders are very simple

attribute vec4 a_position;
uniform mat4 u_vpMatrix;
varying vec3 v_normal;

void main(a) {
    gl_Position = u_vpMatrix * a_position;
    // Since the position is based on the geometric center, we can use vertex coordinates as normal vectors
    v_normal = normalize(a_position.xyz);
} 
Copy the code

In the fragment shader we need samplerCube instead of sampler2D and textureCube instead of texture2D. TextureCube requires vectors of type VEC3. The normal vector passed from the vertex shader is interpolated and needs to be reunitalized.

precision mediump float; // from the vertex shader.
varying vec3 v_normal; / / texture.
uniform samplerCube u_texture; 

void main(a) {   
  gl_FragColor = textureCube(u_texture, normalize(v_normal));
}
Copy the code

implementation

When you run it, you get something like this, which is clearly a cube, not the 360-degree surround we were looking for.

All we need to do is position the camera at the origin (0,0,0) and lookAt one of the faces. But there’s a problem at the origin, what if I want to rotate to see the scene? We can rotate the position of the camera, which is essentially a cube rotation, and we don’t need the matrix displacement information, just the orientation information. You can also disable writing to the depth cache to create the illusion that the background is far away and make the effect more realistic.

const viewPosition = new Vector3([0.0.1]);// Camera position
const lookAt = [0.0.0];/ / the origin

// The camera rotates around the y axis
cameraMatrix.rotate(0.2.0.1.0);
viewPoint = cameraMatrix.multiplyVector3(viewPosition);
vpMatrix.setPerspective( 30, canvas.width / canvas.height, 0.1.5); vpMatrix.lookAt(... viewPoint.elements, ... lookAt,0.1.0);

// Reset the displacement
vpMatrix.elements[12] = 0;
vpMatrix.elements[13] = 0;
vpMatrix.elements[14] = 0;

// Disallow writing to the depth cache, creating the illusion that the background is far away
gl.depthMask(false);
Copy the code

Environment texture mapping

Ambient maps are more commonly known as skyboxes. Then we also want to achieve a very cool effect, in the sky box 3D scene, the objects in it reflect the shading around the scene. This operation is called Environment mapping.

reflection

If the surface of the object is like a smooth mirror, then we can see that the object reflects the sky and the surrounding landscape. The principle of reflection is very simple, that is, the reflection formula is used to map the corresponding texture box elements:

The camera position (observation point) and the vertex position of the object, which contains the normal information, can easily calculate the reflection vector R through the reflect function of GLSL, and then determine which surface shading is seen.

implementation

We will add a mirror cube under the sky box, which will need to add a pair of shaders, first vertex shader need to add normal line, MVP matrix

attribute vec4 a_position;
attribute vec4 a_normal;
uniform mat4 u_vpMatrix;
uniform mat4 u_modelMatrix;
varying vec3 v_position;
varying vec3 v_normal;

void main(a) {
    v_position = (u_modelMatrix * a_position).xyz;
    v_normal = vec3(u_modelMatrix * a_normal);
    gl_Position = u_vpMatrix * u_modelMatrix * a_position;
}
Copy the code

The slice shader needs to add camera positions, textures, and normals and vertex positions passed by the vertex shader

precision highp float;
varying vec3 v_position;
varying vec3 v_normal;
uniform samplerCube u_texture;
uniform vec3 u_viewPosition;

void main(a) {
    vec3 normal = normalize(v_normal);
    vec3 eyeToSurfaceDir = normalize(v_position - u_viewPosition);
    vec3 direction = reflect(eyeToSurfaceDir,normal);
    gl_FragColor = textureCube(u_texture, direction);
}
Copy the code

So we have to take turns switching shader program as we draw

function draw(){
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    / / the sky boxes
    gl.useProgram(program.program);
		// Draw the skybox
  	/ /...

    / / cube
    gl.useProgram(cProgram.program);
		// Draw the cube
  	/ /...

    requestAnimationFrame(draw);
}
Copy the code

Finally achieve the following effect, demo situationThe sky box

Afterword.

In addition to environment mapping, texture boxes can also be combined with light and shadow mapping to create a lot of cool effects.