Artifacts in Atmospheric Scattering Shader on Large Scales and Distances

Hello Three.js Community,

I’m experiencing a challenge with my atmospheric scattering shader when applied to large scale planets and at significant distances from the scene’s origin. The shader exhibits artifacts like “noise” and concentric rings which intensify as the distance from the scene’s center increases.

Here’s a brief outline of the issue:

  • The shader is designed to render atmospheric effects around 3D planets.
  • Artifacts occur as planets are placed further from the scene’s center, with larger planets displaying more pronounced “noise”.
  • The artifacts are less noticeable for planets closer to the light source and become severe as the distance grows.
  • The artifacts manifest as both concentric rings around the planet and random “noise” in the atmosphere.

Any insights or suggestions to mitigate these artifacts would be greatly appreciated. If more code or context is required, I can provide it.

Thank you for your assistance!

Here are some screenshots demonstrating the problems




Are you using shadow casting lights ?
Are you using logarithmicDepthBuffer ?
Do these effects interact with the depth buffer?
What values are your camera.near and camera.far set to, and what is the world space size of the celestial bodies?

  1. yes and no, in app having Directional light, but it is legacy of start development, when i used default materials, now it doesn’t use for its intended purpose, shadow map is disabled
  2. no
  3. no, depth calculates in shader, using uniforms lightPosition, objectPosition, cameraPosition
  4. it is a good question, camera.near = 0.5 camera.far = 5000000, sizes of every object computes by simple formula - radius * 0.001 (radiuses taken from open sources of physical parameters celestial bodies), vectors scales by the same formula but with astronimical units parameter (vector * au * 0.001), the most large object on scene is 696 units, parameters camera.near and camera.far now are experimental, but changing them up or down did not affect the shader

changing near to something like 1, and far to something like 1000 didn’t increase the precision/change the character of the artifacts?

( I guess if the depth is computed then immediately used in the shader, it wouldn’t go through the depth buffer and lose precision that way… )

Are you setting any precision settings on the shaders?

tried various parameters of camera, 1 and 1000 as well, it didn’t work for this case

i’m using “precision highp float” in vertex and fragment shaders

hmmm ok. I’m running out of ideas. I/we would probably would have to see some code to make any progress.

sure, i can provide shader code

import { Vector3 } from 'three'

export const AtmosphereShader: IShader = {
  uniforms: {
    targetPosition: { value: new Vector3() },
    targetRadius: { value: 0 },
    atmosphereRadius: { value: 0 },
    lightPosition: { value: new Vector3() },
    scatterRGB: { value: new Vector3() },
    densityFalloff: { value: 0 }
  },
  vertexShader: `
    precision highp float;
    
    varying vec3 vPositionW;

    void main() {
        vPositionW = (modelMatrix * vec4(position, 1.0)).xyz;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    precision highp float;
  
    #define SCATTER_POINT_COUNT 4
    #define OPTICAL_DEPTH_POINT_COUNT 4
    
    uniform vec3 targetPosition;
    uniform float targetRadius;
    uniform float atmosphereRadius;
    uniform vec3 lightPosition;
    uniform vec3 scatterRGB;
    uniform float densityFalloff;
    
    varying vec3 vPositionW;
    varying vec3 vPos;
    
    vec2 raySphere(in vec3 rayOrigin, in vec3 rayDirection, in vec3 spherePosition, in float sphereRadius) {
        vec3 offset = rayOrigin - spherePosition;
        float a = 1.0;
        float b = 2.0 * dot(offset, rayDirection);
        float c = dot(offset, offset) - sphereRadius * sphereRadius;
        float d = b * b - 4.0 * a * c;
    
        if (d > 0.0) {
            float s = sqrt(d);
            float dstToSphereNear = max(0.0, (-b - s) / (2.0 * a));
            float dstToSphereFar = (-b + s) / (2.0 * a);
    
            if (dstToSphereFar >= 0.0) {
                return vec2(dstToSphereNear, dstToSphereFar - dstToSphereNear);
            }
        }
    
        return vec2(10000.0, 0.0);
    }
    
    float densityAtPoint(in vec3 densitySamplePoint) {
        float heightAboveSurface = length(densitySamplePoint - targetPosition) - targetRadius;
        float height01 = heightAboveSurface / (atmosphereRadius - targetRadius);
        float localDensity = exp(-height01 * densityFalloff) * (1.0 - height01);
    
        return localDensity;
    }
    
    float opticalDepth(in vec3 rayOrigin, in vec3 rayDirection, in float rayLength) {
        vec3 densitySamplePoint = rayOrigin;
        float stepSize = rayLength / float(OPTICAL_DEPTH_POINT_COUNT - 1);
        float opticalDepth = 0.0;
    
        for (int i = 0; i < OPTICAL_DEPTH_POINT_COUNT; i++) {
            float localDensity = densityAtPoint(densitySamplePoint);
            opticalDepth += localDensity * stepSize;
            densitySamplePoint += rayDirection * stepSize;
        }
    
        return opticalDepth;
    }
    
    vec3 calculateLight(in vec3 rayOrigin, in vec3 rayDirection, in float rayLength, in vec3 originalColor) {
        vec3 dirToSun = normalize(lightPosition - targetPosition);
        vec3 scatterPoint = rayOrigin;
        float stepSize = rayLength / float(SCATTER_POINT_COUNT - 1);
        vec3 scatterLight = vec3(0.0, 0.0, 0.0);
    
        float viewRayOpticalDepth = 0.0;
    
        for (int i = 0; i < SCATTER_POINT_COUNT; i++) {
            float sunRayLength = raySphere(scatterPoint, dirToSun, targetPosition, atmosphereRadius).y;
            float sunRayOpticalDepth = opticalDepth(scatterPoint, dirToSun, sunRayLength);
            viewRayOpticalDepth = opticalDepth(scatterPoint, -rayDirection, stepSize * float(i));
            float localDensity = densityAtPoint(scatterPoint);
            vec3 transmittance = exp(-(sunRayOpticalDepth + viewRayOpticalDepth) * scatterRGB);
    
            scatterLight += localDensity * stepSize * transmittance * scatterRGB;
            scatterPoint += rayDirection * stepSize;
        }
    
        float originalColorTransmittance = exp(-viewRayOpticalDepth);
    
        return originalColorTransmittance * originalColor + scatterLight;
    }
    
    void main() {
        vec3 originalColor = vec3(0.0);
    
        vec3 rayOrigin = cameraPosition;
        vec3 rayDirection = normalize(vPositionW - rayOrigin);
        
        vec2 planetHitInfo = raySphere(rayOrigin, rayDirection, targetPosition, targetRadius);
        vec2 atmosphereHitInfo = raySphere(rayOrigin, rayDirection, targetPosition, atmosphereRadius);
    
        float dstToSurface = planetHitInfo.x;
        float dstToAtmosphere = atmosphereHitInfo.x;
        float dstThroughAtmosphere = min(atmosphereHitInfo.y, dstToSurface - dstToAtmosphere);
    
        if (dstThroughAtmosphere > 0.0) {
            const float epsilon = 0.0001;
            vec3 pointInAtmosphere = rayOrigin + rayDirection * (dstToAtmosphere + epsilon);
            vec3 light = calculateLight(pointInAtmosphere, rayDirection, dstThroughAtmosphere - epsilon * 2.0, originalColor);
    
            gl_FragColor = vec4(light, 1.0);
        } else {
            gl_FragColor = vec4(originalColor, 0.0);
        }
    }
  `
}

I sent you a discord/slack invite… we can perhaps take the conversation offline here and hash it out directly.

@sergowt1975 Hey did you ever figure out these artifacts? oddly enough i’m actually doing atmospheric shading myself but i see these artifacts outside of any kind of ray tracing…

i solved this problem long ago, the solution is translation from world coordinates to local, shader works well now

1 Like

Could you share an example of your final solution?

i think so, find me in discord, nickname _ oxyel _ (without spaces)