DEV Community

loading...
Cover image for WebGL Month. Day 29. Fog

WebGL Month. Day 29. Fog

lesnitsky profile image Andrei Lesnitsky ・3 min read

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

Today we're going to improve our 3D minecraft terrain scene with fog

Basically we need to "lighten" the color of far cubes (calculate distance between camera and cube vertex)

To calculate relative distance between camera position and some point, we need to multiply position by view and model matrices. Since we also need the same resulting matrix together with projection matrix, let's just extract it to a variable

📄 src/shaders/3d-textured.v.glsl

  }

  void main() {
-     gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
+     mat4 modelView = viewMatrix * modelMatrix;
+ 
+     gl_Position = projectionMatrix * modelView * vec4(position, 1.0);

      vTexCoord = texCoord;
      vColor = encodeObject(index);

Since our camera looks in a negative direction of Z axis, we need to get z coordinate of resulting vertex position

📄 src/shaders/3d-textured.v.glsl


      gl_Position = projectionMatrix * modelView * vec4(position, 1.0);

+     float depth = (modelView * vec4(position, 1.0)).z;
+ 
      vTexCoord = texCoord;
      vColor = encodeObject(index);


But this value will be negative, while we need a positive value, so let's just negate it

📄 src/shaders/3d-textured.v.glsl


      gl_Position = projectionMatrix * modelView * vec4(position, 1.0);

-     float depth = (modelView * vec4(position, 1.0)).z;
+     float depth = -(modelView * vec4(position, 1.0)).z;

      vTexCoord = texCoord;
      vColor = encodeObject(index);

We can't use depth directly, since we need a value in [0..1] range. Also it'd be nice to have a smooth "gradient" like fog. We can apply glsl smoothstep function to calcuate the final amount of fog. This function interpolates a value in range of lowerBound and upperBound. Max depth of our camera is 142

mat4.perspective(
    projectionMatrix,
    (Math.PI / 360) * 90,
    canvas.width / canvas.height,
    0.01,
    142 // <- zFar
);

So the max value of depth should be < 142 in order to see any fog at all (object farther than 142 won't be visible at all). Let's use 60..100 range.

One more thing to take into account is that we don't want to see the object completely white, so let's multiply the final amount by 0.9

We'll need the final value of fogAmount in fragment shader, so this should be a varying

📄 src/shaders/3d-textured.v.glsl

  varying vec2 vTexCoord;
  varying vec3 vColor;
  varying vec4 vColorMultiplier;
+ varying float vFogAmount;

  vec3 encodeObject(float id) {
      int b = int(mod(id, 255.0));
      gl_Position = projectionMatrix * modelView * vec4(position, 1.0);

      float depth = -(modelView * vec4(position, 1.0)).z;
+     vFogAmount = smoothstep(60.0, 100.0, depth) * 0.9;

      vTexCoord = texCoord;
      vColor = encodeObject(index);

Let's define this varying in fragment shader

📄 src/shaders/3d-textured.f.glsl


  uniform float renderIndices;
  varying vec4 vColorMultiplier;
+ varying float vFogAmount;

  void main() {
      gl_FragColor = texture2D(texture, vTexCoord * vec2(1, -1) + vec2(0, 1)) * vColorMultiplier;

Now let's define a color of the fog (white). We can also pass this color to a uniform, but let's keep things simple

📄 src/shaders/3d-textured.f.glsl

  void main() {
      gl_FragColor = texture2D(texture, vTexCoord * vec2(1, -1) + vec2(0, 1)) * vColorMultiplier;

+     vec3 fogColor = vec3(1.0, 1.0, 1.0);
+ 
      if (renderIndices == 1.0) {
          gl_FragColor.rgb = vColor;
      }

and finally we need to mix original color of the pixel with the fog. We can use glsl mix

📄 src/shaders/3d-textured.f.glsl

      gl_FragColor = texture2D(texture, vTexCoord * vec2(1, -1) + vec2(0, 1)) * vColorMultiplier;

      vec3 fogColor = vec3(1.0, 1.0, 1.0);
+     gl_FragColor.rgb = mix(gl_FragColor.rgb, fogColor, vFogAmount);

      if (renderIndices == 1.0) {
          gl_FragColor.rgb = vColor;

That's it, our scene is now "foggy". To implement the same effect, but "at night", we just need to change fog color to black.

Thanks for reading!

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo

Discussion (0)

pic
Editor guide