A-Frame Adventures 02 - Fixing Fog
There are many reasons to add fog to a scene. It gives a scene more depth and can be used to create various aesthetics, like a spooky swamp or a morning fog. It also conveniently covers up the far edges of the scene. Luckily A-Frame has built-in support for fog. However there is something wrong with it… In this post we’ll look into the problem and create a fix for it.
How fog works in A-Frame (and Three.js)
Adding fog to a scene in A-Frame is simple. Just add the fog
component to the <a-scene>
element:
<a-scene fog="type: linear; color: #AAA; near: 10; far: 40"></a-scene>
There are two types of fog: linear
and exponential
. The linear fog ramps up the density of the fog between a near
and far
distance. In the above example there won’t be any fog on objects closer than 10 meters, things get foggy between 10 and 40 meters and beyond 40 meters only the fog color can be seen. The exponential fog type doesn’t use a near
and far
value, but instead uses a density
property. The principle remains the same, the further away something is the fogger it gets.
Here’s an example of linear fog in A-Frame:
The Problem
At first glance everything seems fine, but once you check it out in VR (try it yourself) something odd happens. As you rotate your head, the fog ‘moves’ along as if it’s a plane that’s constantly facing you. This effect is less pronounced with exponential fog or if the fog is further away, but it’s still there. Once you notice it, it’s a very distracting effect. So what gives?
The problem has to do with the way distance is being calculated. Fog is handled using shaders. Here is the shader code responsible for determining the depth for the fog calculation:
#ifdef USE_FOG
vFogDepth = - mvPosition.z;
#endif
The depth is based on the Z coordinate of the mvPosition
. The mvPosition
is the position of the vertex after the model and view matrices have been applied. This position is in camera space. In camera space the camera is at the center (0, 0, 0) and is looking along the Z-axis in the negative direction. That’s also why there is a minus sign in the shader code.
Here’s a visualization of the camera space, along with the depth according to the above calculation and the true depth:
As can be seen, the further away something is from the center of the view, the less accurate the depth becomes. In 3D this means that the depth is underestimated in the corners, meaning not enough fog is applied.
The Fix
Now that the cause of the problem is known, the fix is actually quite trivial. The shader needs to compute the depth more accurately. This can be done by making a few small changes to the ShaderChunks of Three.js that are used for fog. Firstly, in the vertex shader, let’s keep track of the position in world space:
#ifdef USE_FOG
- vFogDepth = - mvPosition.z;
+ vFogPosition = worldPosition.xyz;
#endif`;
And, secondly, let’s use this position to compute the depth more accurate in the fragment shader:
#ifdef USE_FOG
+ float fogDepth = distance( cameraPosition, vFogPosition );
#ifdef FOG_EXP2
- float fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );
+ float fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );
#else
- float fogFactor = smoothstep( fogNear, fogFar, vFogDepth );
+ float fogFactor = smoothstep( fogNear, fogFar, fogDepth );
#endif
gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );
#endif
And that’s it! Try the updated example for yourself and notice how the fog behaves correctly. It’s no longer a plane that moves as you rotate your head, but it’s all around you.
Conclusion
A small change, but it makes quite the difference. If you want to experiment with it yourself, or include it in one of your projects, checkout the code on GitHub (link below). The fix-fog.js
file contains all you need to get going.
There’s more that can be done with the fog shader. Some very nice effects can be created, but that’s for another time. Stay tuned!