Glow effect disappears when the camera is near

I have created a pound shop planetary atmosphere, and while it looks quite good, it disappears when the camera is very close to it, as you can see in the images below.


I would like the atmosphere to be visible when you are close to the planet, too. Here is the shader code:

    const Shaders = {
      atmosphere: {
        uniforms: {
          lightPosition: { value: new THREE.Vector3() },
          colour: {
            type: "c",
            value: new THREE.Color(this.mass.atmosphere)
          }
        },
        vertexShader: `
        varying vec3 vPosition;
        varying vec3 vNormal;

        void main() {
          vNormal = normalize( normalMatrix * normal );
          gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
          vPosition = gl_Position.xyz;
        }
        `,
        fragmentShader: `
        varying vec3 vNormal;
        varying vec3 vPosition;
        
        uniform vec3 lightPosition;
        uniform vec3 colour;

        void main() {
          vec3 lightDirection = normalize(lightPosition - vPosition);
          float dotNL = clamp(dot(lightDirection, vNormal), 0.0, 1.0);
          float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 8.0 );
          gl_FragColor = vec4( colour, 1.0 ) * intensity * dotNL;
        }
        `
      }
    };

    const atmosphereMaterial = new THREE.ShaderMaterial({
      uniforms: THREE.UniformsUtils.clone(Shaders["atmosphere"].uniforms),
      vertexShader: Shaders["atmosphere"].vertexShader,
      fragmentShader: Shaders["atmosphere"].fragmentShader,
      side: THREE.BackSide,
      transparent: true
    });

I then update the light position, like so:

      const atmosphere = massManifestation.getObjectByName("atmosphere");

      if (atmosphere) {
        atmosphere.material.uniforms.lightPosition.value
          .copy(
            this.manifestationsService.manifestations
              .find(manifestation => manifestation.name === "Sun")
              .getObjectByName("main").position
          )
          .applyMatrix4(this.camera.matrixWorldInverse);
      }

One solution that I have tried is to make the scale of the atmosphere mesh a function of the distance between it and the camera, and while this kind of works, the transition in scale is not smooth. Basically I set the scale of the mesh to be the ratio of the radius of the mesh and its distance to the camera, i.e r / d.

Any chance anyone could fill me in on how this problem could be addressed?

Maybe your atmosphere mesh gets unintentionally frustum culled. Try to disable view frustum culling like so and see if it helps:

atmosphere.frustumCulled = false;
2 Likes

Thanks for chiming in. Unfortunately that didn’t do the trick. This pen does not take the position of the light source into account, but the same issue occurs. If you set the camera z position vector to 1000, the atmosphere looks super, but when you set it to 400, it’s barely visible.

The glow effect here comes from using normalMatrix in shader code

vNormal = normalize( normalMatrix * normal );

that controls the intensity

float intensity = 1.0 - vNormal.z;

if you zoom in/out here using mouse wheel:

you will see that this effect fades out as you get closer to the sphere.

I don’t know what normalMatrix does, but judging by other components of vNormal that you can see by changing .z to .x or .y in this line:

float intensity = 1.0 - vNormal.z

it seems to recalculate all geometry normals relative to the camera position, that also depends on distance from the camera to its target.

Maybe there is a way to compensate for that by mixing this distance into the code?

2 Likes

It dawned upon me that most likely the reason for this effect to change with distance is that you’re using perspective camera, so you see less and less of a sphere as you get close to it, so the glow can not span across the whole sphere radius.

If you use ortho camera you will always see a half of the sphere and the effect will be unchanged regardless of the distance.

To compensate for this you will need to pass camera position as a uniform into the shader and change the intensity but the formula will depend of your camera setup, so it will probably be hard to do.