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)