May I avoid using Sqrt() in this radial fog frag shader?

I have a radial fog shader that works like this:

  • a variable which determines max fog distance (beyond this distance, everything is 100% fogged) (max_fog_dist), hard-coded to 10.0 for now
  • a variable (0.0 to 1.0) which represents when the fog should begin to fade in, starting from the eye/camera (fog_start), hard-coded to 0.5 for now

I’m still a little hazy on all things matrices and I generally follow tutorials for things relating to them, and I’d done so here. The fog shader WORKS, but it uses SQRT in the frag shader. Not a huge deal, but I gather it’s something to be avoided if possible. Mostly, this is for learning, and is not of any urgency.

Vertex shader:

in vec3 vPosition;
in vec4 vColor;
in vec2 vUV;
out vec4 fColor;
out vec2 fUV;
out vec4 fViewSpace;
out float fAlpha;
uniform mat4 uMatrixWorld;
uniform mat4 uMatrixView;
uniform mat4 uMatrixProjection;

void main()
{
   fColor = vColor;

   // save the alpha, we're about to overwrite this
   fAlpha = fColor.a;
   // store vertex z-pos in the alpha
   fColor.a = vPosition.a;

   fUV = vUV;
   fViewSpace = uMatrixView * uMatrixWorld * vec4(vPosition, 1.0);
   gl_Position = uMatrixProjection * fViewSpace;
}

Frag shader:

in vec4 fColor;
in vec2 fUV;
in vec4 fViewSpace;
in float fAlpha;
out vec4 final;
uniform sampler2D uTexture0;
uniform vec3 uFogColor;

void main()
{
   // unrelated fragment shader stuff relating to textures/uvs

   // fog stuff
   float max_fog_dist = 10.0;
   float fog_start = 0.5;

   // fragment distance
   float dist = (fViewSpace.x * fViewSpace.x) + (fViewSpace.y * fViewSpace.y) + (fViewSpace.z * fViewSpace.z);
   float fog_amount = (max_fog_dist - sqrt(dist)) / (max_fog_dist * fog_start);
   fog_amount = clamp(fog_amount, 0.0, 1.0);

   // then, use fog_amount as a multiplier for how much to blend uFogColor
   // and return "final" frag color
}

Really I think this is a logic problem and not a GLSL question.

The problem is that my frag distance I’m working with is squared to begin with (the "dist = " line), and I don’t think I can retrieve that distance without having it be squared in the first place. The guide recommends to “work with all squared numbers” and you can avoid using SQRT that way, but it doesn’t stay correct if you do that. For example, if I square the other number:

// "dist" is already a squared value
float fog_amount = ((max_fog_dist * max_fog_dist) - dist) / (max_fog_dist * max_fog_dist) * fog_start);

This doesn’t stay accurate (linear) because of the subtraction on the left side of the “/”. Using my values of 10 for max_fog_dist and 0.5 for fog_start, the non-squared way, if our fragment distance is at 7.5, which should be half-way fogged (between 5 and 10), results in:
(10 - 7.5) / (10 * 0.5)
2.5 / 5.0
50%

Great, that’s what I want.

Squared version, however, if frag is again at 7.5, (without using sqrt) goes like this:
// “frag” is already squared, so distance of “7.5” in reality is 7.5*7.5=56.25
(100 - 56.25) / (100 * 0.5)
43.75 / 50
87.5%

Gradient is wrong this way, (well, duh, because the distance value is squared). Is there any way to structure or math this problem in a way such that I can still achieve the linear fade without squarerooting in the frag shader, or is it simply unavoidable in my case? Again not a high-priority question but just trying to learn a bit.

There are various alternatives which can be used (e.g. approximation via a polynomial or linear-filtered texture), but there’s no point. One sqrt per fragment is a trivial overhead for modern hardware; the alternatives may well be slower.

If you were just performing a comparison, you’d compare the squared distances. But that won’t work if you’re using the distance to interpolate.

Also: you can just use the length function to get the (unsquared) length of a vector.

“If you were just performing a comparison, you’d compare the squared distances. But that won’t work if you’re using the distance to interpolate.”

Ahhhh, okay, yes that makes sense then. So, squared values are still fine for comparisons but not for linear interp.

Using length(fViewSpace) has turned everything black (fully fogged), but then I assumed it was because of the W value, so I tried length(fViewSpace.xyz) and it works as you said, and now I don’t need to use SQRT. This is more readable, but I assume that the length() function itself is squarerooting to find the distance to begin with. However, as you’re saying this is trivial, regardless, then that has eased my mind and I’ll no longer worry about it : )

Bonus: length(fViewSpace.xy) , with the radial fog code above, results in a flashlight beam effect. Accidents are fun. : )

Thanks again, Clements.