DEV Community

Cover image for WebGL month. Day 21. Rendering a minecraft terrain
Andrei Lesnitsky
Andrei Lesnitsky

Posted on

WebGL month. Day 21. Rendering a minecraft terrain

This is a series of blog posts related to WebGL. New post will be available every day

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo


Hey πŸ‘‹

Welcome to WebGL month.

Yesterday we rendered a single minecraft dirt cube, let's render a terrain today!

We'll need to store each block position in separate transform matrix

πŸ“„ src/3d-textured.js


  gl.viewport(0, 0, canvas.width, canvas.height);

+ const matrices = [];
+ 
  function frame() {
      mat4.rotateY(cube.modelMatrix, cube.modelMatrix, Math.PI / 180);


Enter fullscreen mode Exit fullscreen mode

Now let's create 10k blocks iteration over x and z axis from -50 to 50

πŸ“„ src/3d-textured.js


  const matrices = [];

+ for (let i = -50; i < 50; i++) {
+     for (let j = -50; j < 50; j++) {
+         const matrix = mat4.create();
+     }
+ }
+ 
  function frame() {
      mat4.rotateY(cube.modelMatrix, cube.modelMatrix, Math.PI / 180);


Enter fullscreen mode Exit fullscreen mode

Each block is a size of 2 (vertex coordinates are in [-1..1] range) so positions should be divisible by two

πŸ“„ src/3d-textured.js

  for (let i = -50; i < 50; i++) {
      for (let j = -50; j < 50; j++) {
          const matrix = mat4.create();
+ 
+         const position = [i * 2, (Math.floor(Math.random() * 2) - 1) * 2, j * 2];
      }
  }


Enter fullscreen mode Exit fullscreen mode

Now we need to create a transform matrix. Let's use ma4.fromTranslation

πŸ“„ src/3d-textured.js

          const matrix = mat4.create();

          const position = [i * 2, (Math.floor(Math.random() * 2) - 1) * 2, j * 2];
+         mat4.fromTranslation(matrix, position);
      }
  }


Enter fullscreen mode Exit fullscreen mode

Let's also rotate each block around Y axis to make terrain look more random

πŸ“„ src/3d-textured.js

  gl.viewport(0, 0, canvas.width, canvas.height);

  const matrices = [];
+ const rotationMatrix = mat4.create();

  for (let i = -50; i < 50; i++) {
      for (let j = -50; j < 50; j++) {

          const position = [i * 2, (Math.floor(Math.random() * 2) - 1) * 2, j * 2];
          mat4.fromTranslation(matrix, position);
+ 
+         mat4.fromRotation(rotationMatrix, Math.PI * Math.round(Math.random() * 4), [0, 1, 0]);
+         mat4.multiply(matrix, matrix, rotationMatrix);
      }
  }


Enter fullscreen mode Exit fullscreen mode

and finally push matrix of each block to matrices collection

πŸ“„ src/3d-textured.js


          mat4.fromRotation(rotationMatrix, Math.PI * Math.round(Math.random() * 4), [0, 1, 0]);
          mat4.multiply(matrix, matrix, rotationMatrix);
+ 
+         matrices.push(matrix);
      }
  }


Enter fullscreen mode Exit fullscreen mode

Since our blocks are static, we don't need a rotation transform in each frame

πŸ“„ src/3d-textured.js

  }

  function frame() {
-     mat4.rotateY(cube.modelMatrix, cube.modelMatrix, Math.PI / 180);
- 
      gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, cube.modelMatrix);
      gl.uniformMatrix4fv(programInfo.uniformLocations.normalMatrix, false, cube.normalMatrix);


Enter fullscreen mode Exit fullscreen mode

Now we'll need to iterate over matrices collection and issue a draw call for each cube with its transform matrix passed to uniform

πŸ“„ src/3d-textured.js

  }

  function frame() {
-     gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, cube.modelMatrix);
-     gl.uniformMatrix4fv(programInfo.uniformLocations.normalMatrix, false, cube.normalMatrix);
+     matrices.forEach((matrix) => {
+         gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, matrix);
+         gl.uniformMatrix4fv(programInfo.uniformLocations.normalMatrix, false, cube.normalMatrix);

-     gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.data.length / 3);
+         gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.data.length / 3);
+     });

      requestAnimationFrame(frame);
  }

Enter fullscreen mode Exit fullscreen mode

Now let's create an animation of rotating camera. Camera has a position and a point where it is pointed. So to implement this, we need to rotate focus point around camera position. Let's first get rid of static view matrix

πŸ“„ src/3d-textured.js

  const viewMatrix = mat4.create();
  const projectionMatrix = mat4.create();

- mat4.lookAt(viewMatrix, [0, 4, -7], [0, 0, 0], [0, 1, 0]);
- 
  mat4.perspective(projectionMatrix, (Math.PI / 360) * 90, canvas.width / canvas.height, 0.01, 100);

  gl.uniformMatrix4fv(programInfo.uniformLocations.viewMatrix, false, viewMatrix);

Enter fullscreen mode Exit fullscreen mode

Define camera position, camera focus point vector and focus point transform matrix

πŸ“„ src/3d-textured.js

- import { mat4 } from 'gl-matrix';
+ import { mat4, vec3 } from 'gl-matrix';

  import vShaderSource from './shaders/3d-textured.v.glsl';
  import fShaderSource from './shaders/3d-textured.f.glsl';
      }
  }

+ const cameraPosition = [0, 10, 0];
+ const cameraFocusPoint = vec3.fromValues(30, 0, 0);
+ const cameraFocusPointMatrix = mat4.create();
+ 
+ mat4.fromTranslation(cameraFocusPointMatrix, cameraFocusPoint);
+ 
  function frame() {
      matrices.forEach((matrix) => {
          gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, matrix);

Enter fullscreen mode Exit fullscreen mode

Our camera is located in 0.0.0, so we need to translate camera focus point to 0.0.0, rotate it, and translate back to original position

πŸ“„ src/3d-textured.js

  mat4.fromTranslation(cameraFocusPointMatrix, cameraFocusPoint);

  function frame() {
+     mat4.translate(cameraFocusPointMatrix, cameraFocusPointMatrix, [-30, 0, 0]);
+     mat4.rotateY(cameraFocusPointMatrix, cameraFocusPointMatrix, Math.PI / 360);
+     mat4.translate(cameraFocusPointMatrix, cameraFocusPointMatrix, [30, 0, 0]);
+ 
      matrices.forEach((matrix) => {
          gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, matrix);
          gl.uniformMatrix4fv(programInfo.uniformLocations.normalMatrix, false, cube.normalMatrix);

Enter fullscreen mode Exit fullscreen mode

Final step – update view matrix uniform

πŸ“„ src/3d-textured.js

      mat4.rotateY(cameraFocusPointMatrix, cameraFocusPointMatrix, Math.PI / 360);
      mat4.translate(cameraFocusPointMatrix, cameraFocusPointMatrix, [30, 0, 0]);

+     mat4.getTranslation(cameraFocusPoint, cameraFocusPointMatrix);
+ 
+     mat4.lookAt(viewMatrix, cameraPosition, cameraFocusPoint, [0, 1, 0]);
+     gl.uniformMatrix4fv(programInfo.uniformLocations.viewMatrix, false, viewMatrix);
+ 
      matrices.forEach((matrix) => {
          gl.uniformMatrix4fv(programInfo.uniformLocations.modelMatrix, false, matrix);
-         gl.uniformMatrix4fv(programInfo.uniformLocations.normalMatrix, false, cube.normalMatrix);

          gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.data.length / 3);
      });

Enter fullscreen mode Exit fullscreen mode

That's it!

This approach is not very performant though, as we're issuing 2 gl calls for each object, so it is a 20k of gl calls each frame. GL calls are expensive, so we'll need to reduce this number. We'll learn a great technique tomorrow!


GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo

Top comments (0)