WebGL month. Day 21. Rendering a minecraft terrain

lesnitsky

Andrei Lesnitsky

Posted on July 21, 2019

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

šŸ’– šŸ’Ŗ šŸ™… šŸš©
lesnitsky
Andrei Lesnitsky

Posted on July 21, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related