The Forest - three.js/WebGL versions


I made a demonstration of part of The Forest using the 3-dimensional library three.js which is able to use faster hardware (GPU = Graphics Processing Unit) that is now present in most devices, even phones.

The demonstration can be seen here. It may take a while to load because of getting the three.js library. I have made it so that the scene and map are both displayed. You may have to zoom down your browser display (Ctrl-) to see them side by side.

I don't consider this to be the way forward for The Forest. That is mainly because there is a bottleneck in feeding new terrain data to the GPU whenever the observer moves. And this is why there is only a fixed area available in the demonstration. Performance is also really very little different from the plain Javascript version, even though I have spent some time studying how the three.js library works most efficiently.

There may be a better way in future by using the GPU not for graphics at all but for making the terrain generator process multiple ground points in parallel...

 Update 23/12/23 - WebGL or vanilla Javascript?

I have recently been experimenting with using WebGL to implement my terrain generator. That means using the GPU for non-graphical calculations.

I am making progress and my demonstrator does basically work. There have been several hurdles to overcome but I like a challenge. I will list some of the puzzles I have solved aong the way. None of the various documents I have studied to try to learn WebGL were at all clear about these points. In fact most did not even mention them.

 Results: comparing WebGL times with plain Javascript

I timed 1,000 loops of the WebGL code and then the same for a plain JS version (as used in The Forest). The timings were only to the point where height values are stored in an array that can be accessed from JS. I moved the ground position between loops so I got an average performance. After each time measurement I displayed the results just in order to see progress.

On my laptop the average times (to get the whole 800x600 array of heights) were 36.5ms for WebGL and 32.5ms for JS. This is a Samsung Galaxy 360 with 13th gen Intel core i7 1360-P and Intel Iris GPU. I was using Firefox browser.

I uploaded the code to my web site so I could repeat the test on my android phone. The results were really surprising: 30.8ms for WebGL, 12.1ms for JS. The phone is Samsung Galaxy S22. I was using Samsung's own browser and this result shows it to be significantly more efficient than Firefox. My phone is much faster than my laptop for processing vanilla Javascript!

My conclusion is that it is not worth trying to use WebGL for my terrain generator. I expect non-graphical processing to be more effective in WebGPU but I will wait until that is available in most of the browsers listed as WebGPU-capable on MDN.

 Reference documents

I found these and a few others online. All of them are of course very much about 3D graphics and do not describe using the GPU for non-graphical computations.

 My shaders

For programmers, here are the shaders I wrote for this experiment. If you can see where they might be made more efficient I'd be glad to know.

precision highp float;
precision mediump int;
uniform vec2 uCentre;
uniform vec2 uHalfSize;// of canvas
uniform sampler2D uTexture;
attribute vec2 aPosition;
varying vec4 vTerrain;
void main() 
  float AH [5];
  float BH [5];
  AH [0] = 0.0;  AH [1] = 13.0; AH [2] = 21.0; AH [3] = 22.0; AH [4] = 29.0; 
  BH [0] = 27.0; BH [1] = 26.0; BH [2] = 21.0; BH [3] = 11.0; BH [4] = 1.0;
  float x = aPosition.x;
  float y = aPosition.y;
  float xg = x + uCentre.x;
  float yg = y + uCentre.y;
  float recip128 = 1.0 / 128.0;
  float ht = 0.0;
  for (int i = 0; i < 5; i ++)
    float j = (AH [i] * xg + BH [i] * yg) * recip128;
    float k = mod (j, 256.0) / 256.0;
    vec4 profK = texture2D(uTexture, vec2(k, 1.0));//rgba
    ht += profK.r;
  float lsb = mod (ht, 256.0);
  float msb = floor (ht / 256.0);
  vTerrain = vec4(msb, lsb, 0.0, 255.0); // for now - .b will be veg type
  gl_Position = vec4(x / uHalfSize.x - 1.0, y / uHalfSize.y - 1.0, 0.0, 1.0);
  gl_PointSize = 1.0;

precision highp float;
varying vec4 vTerrain;
void main()
  gl_FragColor = vTerrain;
Next page