Raymarching - Correct gl_FragDepth

Hello everyone.

I’m desperately trying to calculate the depth of my fragment in a shader using raymarching to create a cloudy sky.

I tried many things, such as several formulas to calculate the gl_FragDepth, or even using a depthTexture of my scene.
The objective is to have a correct rendering of the depth of the clouds according to the scene, and therefore to normally manage the different postprocessing passes and above all to correctly manage the intersection of the clouds with other elements.

Thank you in advance for your help !

const vertexShader = /* glsl */ `
      uniform vec3 cameraPos;

      out vec3 vOrigin;
      out vec3 vDirection;
      out vec3 vWorldPosition;

      #include <common>
			#include <fog_pars_vertex>
      // #include <shadowmap_pars_vertex>

      void main() {
        vec4 mvPosition = vec4( position, 1.0 );
        vec4 mvSavePosition = instanceMatrix * mvPosition;
        mvPosition = modelViewMatrix * mvSavePosition;

        vOrigin = vec3( inverse( modelMatrix * instanceMatrix) * vec4( cameraPos, 1.0 ) ).xyz;
        vDirection = position - vOrigin;
        vec4 worldPosition = mvSavePosition;
        worldPosition = modelMatrix * worldPosition;
        vWorldPosition = worldPosition.xyz;
        gl_Position = projectionMatrix * mvPosition;
        #include <fog_vertex>
      }
    `;

    const fragmentShader = /* glsl */ `
      precision highp float;
      precision highp sampler3D;
      precision highp float;
      precision highp int;
      #include <common>
			#include <packing>

			#include <fog_pars_fragment>

      uniform mat4 modelViewMatrix;
      uniform mat4 instanceMatrix;
      uniform mat4 modelMatrix;
      uniform mat4 projectionMatrix;
      uniform mat4 cameraMatrixWorldInverse;

      in vec3 vOrigin;
      in vec3 vDirection;
      in vec3 vWorldPosition;

      uniform vec3 base;
      uniform sampler2D blueNoise;
      uniform sampler2D tDepth;
      uniform sampler3D map;
      uniform sampler3D hdMap;

      uniform float radius;
      uniform vec2 iResolution;
      uniform vec3 center;
      uniform float threshold;
      uniform float detailsFactor;
      uniform vec3 cameraPos;
      uniform vec3 vScale;
      uniform float range;
      uniform float cameraNear;
      uniform float cameraFar;
      uniform float opacity;
      uniform float steps;
      uniform float frame;
      uniform vec3 lightColor;
      uniform vec3 lightPosition;
      uniform vec3 lightDirection;
      uniform float lightIntensity;

      vec2 hitBox( vec3 orig, vec3 dir, vec3 size ) {
        const vec3 box_min = vec3( -0.5 );
        const vec3 box_max = vec3( 0.5 );
        vec3 inv_dir = 1.0 / dir;
        vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
        vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
        vec3 tmin = min( tmin_tmp, tmax_tmp );
        vec3 tmax = max( tmin_tmp, tmax_tmp );
        float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
        float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
        return vec2( t0, t1 );
      }

      float sample1( vec3 p ) {
        // Ajustement des coordonnées de texture pour empêcher les nuages de toucher les bords de la texture
        vec3 texCoord = mod(p + vec3(frame, 0.0, 0.0), 1.0);
        
        float cloud = texture( map, texCoord + 0.5 ).r;
        return cloud;
    }
    
    float sample2( vec3 p ) {
        // Ajustement des coordonnées de texture pour empêcher les nuages de toucher les bords de la texture
        vec3 texCoord = mod(p + vec3(frame * 2.0, 0.0, 0.0), 1.0);

        float cloud = texture( hdMap, texCoord + 0.5 ).r;
        return cloud;

    }

      float HenyeyGreenstein(float g, float mu) {
          float gg = g * g;
          return (1.0 / (4.0 * PI)) * (1.0 - gg) / pow(1.0 + gg - 2.0 * g * mu, 1.5);
      }

      const float DUAL_LOBE_WEIGHT = 0.8;
      const vec3 EXTINCTION_MULT = vec3(0.7, 0.7, 1.0);

      float DualHenyeyGreenstein(float g, float costh) {
          return mix(HenyeyGreenstein(-g, costh), HenyeyGreenstein(g, costh), DUAL_LOBE_WEIGHT);
      }
      
      float PhaseFunction(float g, float costh) {
          return DualHenyeyGreenstein(g, costh);
      }

      vec3 MultipleOctaveScattering(float density, float mu) {
          float attenuation = 0.2;
          float contribution = 0.2;
          float phaseAttenuation = 0.5;
      
          float a = 1.0;
          float b = 1.0;
          float c = 1.0;
          float g = 0.85;
          float scatteringOctaves = 4.0;
          
          vec3 luminance = vec3(0.0);
      
          for (float i = 0.0; i < scatteringOctaves; i++) {
              float phaseFunction = PhaseFunction(0.35 * c, mu);
              vec3 beers = exp(-density * EXTINCTION_MULT * a);
      
              luminance += b * phaseFunction * beers;
      
              a *= attenuation;
              b *= contribution;
              c *= (1.0 - phaseAttenuation);
          }
          return luminance;
      }

      float inverseLerp(float minValue, float maxValue, float v) {
          return (v - minValue) / (maxValue - minValue);
      }

      float remap(float v, float inMin, float inMax, float outMin, float outMax) {
          float t = inverseLerp(inMin, inMax, v);
          return mix(outMin, outMax, t);
      }

      vec3 CalculateLightEnergy(vec3 rayPos, vec3 rayDirOrigin, float mu, float density, float timeInfluence) {
        float stepLength = 1. / 12.0;
        float lightRayDensity = 0.0;
        float distAccumulated = 0.0;

        for(float j = 0.0; j < 12.0; j++) {
          vec3 lightSamplePos = rayPos + lightDirection * distAccumulated;

          float cloudSDF = sample1(lightSamplePos);

          float distToSample = distance(lightSamplePos, vOrigin);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * (detailsFactor / 2.);
          float detailSDF = sample2(lightSamplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);
          

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          lightRayDensity += d * stepLength;
          distAccumulated += stepLength;
        }
        
        vec3 beersLaw = MultipleOctaveScattering(lightRayDensity, mu);
        vec3 powder = 1.0 - exp(-lightRayDensity * 2.0 * EXTINCTION_MULT);
        return beersLaw * mix(powder * 2.0, vec3(1.0), remap(mu, -1.0, 1.0, 0.0, 1.0));
      }

      float blue_noise() {
        ivec2 size = textureSize(blueNoise, 0);

        vec2 interleaved_pos = (mod(floor(gl_FragCoord.xy), float(size.x))) + vec2(frame * 0.5, frame * 0.5);
        vec2 tex_coord 	     = interleaved_pos / float(size.x) + vec2(0.5f / float(size.x), 0.5f / float(size.x));
        
        float result = texture(blueNoise, tex_coord).r * 2.0f - 1.0f;
        return result;
      }

      float easeInOutCubic(float x) {
          return x < 0.5 ? 4. * x * x * x : 1. - pow(-2. * x + 2., 3.) / 2.;
      }

      void main(){
				#ifdef USE_FOG
          float targetDensity = fogAtmosphereDensity * fogImpact;
					float fogFactor = 1.0 - exp( -targetDensity * targetDensity * vFogDepth * vFogDepth );
					float fogGlobalHeightFactor = 1.0 - ((vFogWorldPosition.y / fogMaxHeight) * exp(-cameraPosition.y * fogAtmosphereDensity));
					fogFactor = saturate(mix(fogFactor * fogGlobalHeightFactor, fogFactor, fogFactor));

					if (fogFactor >= 1.0) {
						discard;
					}
				#endif

        vec3 rayDir = normalize( vDirection );
        vec2 bounds = hitBox( vOrigin, rayDir, vScale );
        if ( bounds.x > bounds.y ) discard;

        bounds.x = max( bounds.x, 0.0 );

        float rng = blue_noise();
        vec3 inc = 1.0 / abs( rayDir );
        float delta = min( inc.x, min( inc.y, inc.z ) );

        vec3 size = vec3( textureSize( map, 0 ) );
        vec3 p = vOrigin + bounds.x * rayDir;
        p += rayDir * rng * (1.0 / size);

        // Get a random number that we'll use to jitter our ray.
        float min_steps = (steps * 0.5f) + (rng * 2.0f);
        // The number of ray march steps is determined depending on how steep the viewing angle is.
        float num_steps = mix(steps, min_steps, abs(rayDir.y));
        delta /= num_steps;

        float timeInfluence = clamp(cos(frame * 0.5), 0.1, 1.0);

        float a = 0.0;
        vec3 scattering = vec3(0.0);
        vec3 transmittanceResult = vec3(1.0);

        vec3 ambientColor = lightColor * 0.1;
        vec3 lightTargetColor = lightColor * lightIntensity;
        
        
        float dist = 0.0;
        for ( float t = bounds.x; t < bounds.y; t += delta ) {
          // Influencer la position en fonction de la position dans le monde
          vec3 samplePos = p;
          float cloudSDF = sample1(samplePos);

          float distToSample = distance(vOrigin, p);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * detailsFactor;
          float detailSDF = sample2(samplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);

          // Appliquer les autres calculs comme auparavant
          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          float extinction = d;
          if (extinction > 0.01) {
            float mu = dot(-(p), lightDirection);
            // Calculer la distance par rapport aux bords de la texture
            a += (1.0 - a) * d;

            vec3 luminance = ambientColor + lightTargetColor * CalculateLightEnergy( -(p), rayDir, mu, d * delta, timeInfluence );
            vec3 transmittance = exp(-extinction * EXTINCTION_MULT);
            vec3 integScatt = extinction * (luminance - luminance * transmittance) / extinction;


            scattering += integScatt * transmittanceResult;
            transmittanceResult *= transmittance;

            if (length(transmittanceResult) <= 0.01) {
              transmittanceResult = vec3(0.0);
              break;
            }
          }
          p += rayDir * delta;

          dist += delta;
        }
        
        vec4 color = vec4(0.0);
        color.rgb = transmittanceResult + scattering;
        #ifdef USE_FOG
          color *= skyExposure;
        #endif
        color.a = a;
        gl_FragColor = color;

				#include <fog_fragment>
				#include <tonemapping_fragment>
				#include <colorspace_fragment>
      }
    `;
    
const baseGeometry = new BoxGeometry(1, 1, 1);
const baseMaterial = new ShaderMaterial({
  uniforms: UniformsUtils.merge([
    ShaderLib.standard.uniforms,
    {
      blueNoise: { value: null },
      base: { value: new Color(0xffffff) },
      lightColor: { value: new Color(0x6b839f) },
      lightPosition: { value: new Vector3(0, 1, 1) },
      lightDirection: { value: new Vector3(0, 1, 1) },
      lightIntensity: { value: 25 },
      detailsFactor: { value: 0.85 },
      vScale: { value: new Vector3() },
      cameraMatrixWorldInverse: { value: new Matrix4() },
      iResolution: { value: new Vector2() },
      tDepth: { value: null },
      center: { value: new Vector3() },
      radius: { value: 50 },
      cameraNear: { value: 0.1 },
      cameraFar: { value: 10000 },
      map: { value: null },
      hdMap: { value: null },
      cameraPos: { value: new Vector3() },
      threshold: { value: 0.5 },
      fogImpact: { value: 0.5 },
      range: { value: 0.1 },
      steps: { value: 32 },
      frame: { value: 0 },
      opacity: { value: 0.5 },
    },
  ]),
  vertexShader,
  fragmentShader,
  side: BackSide,
  fog: true,
  lights: true,
  transparent: true,
  // depthWrite: false,
  // depthTest: false,
});

This and this may be of some help :man_bowing:

2 Likes

Nothing to do, it doesn’t work…
I tried to switch the material to DoubleSide, but apart from doubling the number of calculations, it didn’t change anything… Maybe the problem comes from my depth rendering?

const vertexShader = /* glsl */ `
      uniform vec3 cameraPos;

      out vec3 vOrigin;
      out vec3 vDirection;
      out vec3 vWorldPosition;
      out mat4 vModelMatrix;
      out mat4 vInstanceMatrix;

      #include <common>
			#include <fog_pars_vertex>
      // #include <shadowmap_pars_vertex>

      void main() {
        vec4 mvPosition = vec4( position, 1.0 );
        vec4 mvSavePosition = instanceMatrix * mvPosition;
        mvPosition = modelViewMatrix * mvSavePosition;

        vOrigin = vec3( inverse( modelMatrix * instanceMatrix) * vec4( cameraPos, 1.0 ) ).xyz;
        vDirection = position - vOrigin;
        vModelMatrix = modelMatrix;
        vInstanceMatrix = instanceMatrix;
        vec4 worldPosition = mvSavePosition;
        worldPosition = modelMatrix * worldPosition;
        vWorldPosition = worldPosition.xyz;
        gl_Position = projectionMatrix * mvPosition;
        #include <fog_vertex>
      }
    `;

    const fragmentShader = /* glsl */ `
      precision highp float;
      precision highp sampler3D;
      precision highp float;
      precision highp int;
      #include <common>
			#include <packing>

			#include <fog_pars_fragment>

      uniform mat4 cameraMatrixWorldInverse;

      in vec3 vOrigin;
      in vec3 vDirection;
      in mat4 vModelMatrix;
      in mat4 vInstanceMatrix;
      in vec3 vWorldPosition;

      uniform vec3 base;
      uniform sampler2D blueNoise;
      uniform sampler2D tDepth;
      uniform sampler3D map;
      uniform sampler3D hdMap;

      uniform float radius;
      uniform vec2 iResolution;
      uniform vec3 center;
      uniform float threshold;
      uniform float detailsFactor;
      uniform vec3 cameraPos;
      uniform vec3 vScale;
      uniform float range;
      uniform float cameraNear;
      uniform float cameraFar;
      uniform float opacity;
      uniform float steps;
      uniform float frame;
      uniform vec3 lightColor;
      uniform vec3 lightPosition;
      uniform vec3 lightDirection;
      uniform float lightIntensity;

      vec2 hitBox( vec3 orig, vec3 dir, vec3 size ) {
        const vec3 box_min = vec3( -0.5 );
        const vec3 box_max = vec3( 0.5 );
        vec3 inv_dir = 1.0 / dir;
        vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
        vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
        vec3 tmin = min( tmin_tmp, tmax_tmp );
        vec3 tmax = max( tmin_tmp, tmax_tmp );
        float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
        float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
        return vec2( t0, t1 );
      }

      float sample1( vec3 p ) {
        // Ajustement des coordonnées de texture pour empêcher les nuages de toucher les bords de la texture
        vec3 texCoord = mod(p + vec3(frame, 0.0, 0.0), 1.0);
        
        float cloud = texture( map, texCoord + 0.5 ).r;
        return cloud;
    }
    
    float sample2( vec3 p ) {
        // Ajustement des coordonnées de texture pour empêcher les nuages de toucher les bords de la texture
        vec3 texCoord = mod(p + vec3(frame * 2.0, 0.0, 0.0), 1.0);

        float cloud = texture( hdMap, texCoord + 0.5 ).r;
        return cloud;

    }

      float HenyeyGreenstein(float g, float mu) {
          float gg = g * g;
          return (1.0 / (4.0 * PI)) * (1.0 - gg) / pow(1.0 + gg - 2.0 * g * mu, 1.5);
      }

      const float DUAL_LOBE_WEIGHT = 0.8;
      const vec3 EXTINCTION_MULT = vec3(0.7, 0.7, 1.0);

      float DualHenyeyGreenstein(float g, float costh) {
          return mix(HenyeyGreenstein(-g, costh), HenyeyGreenstein(g, costh), DUAL_LOBE_WEIGHT);
      }
      
      float PhaseFunction(float g, float costh) {
          return DualHenyeyGreenstein(g, costh);
      }

      vec3 MultipleOctaveScattering(float density, float mu) {
          float attenuation = 0.2;
          float contribution = 0.2;
          float phaseAttenuation = 0.5;
      
          float a = 1.0;
          float b = 1.0;
          float c = 1.0;
          float g = 0.85;
          float scatteringOctaves = 4.0;
          
          vec3 luminance = vec3(0.0);
      
          for (float i = 0.0; i < scatteringOctaves; i++) {
              float phaseFunction = PhaseFunction(0.35 * c, mu);
              vec3 beers = exp(-density * EXTINCTION_MULT * a);
      
              luminance += b * phaseFunction * beers;
      
              a *= attenuation;
              b *= contribution;
              c *= (1.0 - phaseAttenuation);
          }
          return luminance;
      }

      float inverseLerp(float minValue, float maxValue, float v) {
          return (v - minValue) / (maxValue - minValue);
      }

      float remap(float v, float inMin, float inMax, float outMin, float outMax) {
          float t = inverseLerp(inMin, inMax, v);
          return mix(outMin, outMax, t);
      }

      vec3 CalculateLightEnergy(vec3 rayPos, vec3 rayDirOrigin, float mu, float density, float timeInfluence) {
        float stepLength = 1. / 12.0;
        float lightRayDensity = 0.0;
        float distAccumulated = 0.0;

        for(float j = 0.0; j < 12.0; j++) {
          vec3 lightSamplePos = rayPos + lightDirection * distAccumulated;

          float cloudSDF = sample1(lightSamplePos);

          float distToSample = distance(lightSamplePos, vOrigin);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * (detailsFactor / 2.);
          float detailSDF = sample2(lightSamplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);
          

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          lightRayDensity += d * stepLength;
          distAccumulated += stepLength;
        }
        
        vec3 beersLaw = MultipleOctaveScattering(lightRayDensity, mu);
        vec3 powder = 1.0 - exp(-lightRayDensity * 2.0 * EXTINCTION_MULT);
        return beersLaw * mix(powder * 2.0, vec3(1.0), remap(mu, -1.0, 1.0, 0.0, 1.0));
      }

      float blue_noise() {
        ivec2 size = textureSize(blueNoise, 0);

        vec2 interleaved_pos = (mod(floor(gl_FragCoord.xy), float(size.x))) + vec2(frame * 0.5, frame * 0.5);
        vec2 tex_coord 	     = interleaved_pos / float(size.x) + vec2(0.5f / float(size.x), 0.5f / float(size.x));
        
        float result = texture(blueNoise, tex_coord).r * 2.0f - 1.0f;
        return result;
      }

      float easeInOutCubic(float x) {
          return x < 0.5 ? 4. * x * x * x : 1. - pow(-2. * x + 2., 3.) / 2.;
      }

      void main(){
				#ifdef USE_FOG
          float targetDensity = fogAtmosphereDensity * fogImpact;
					float fogFactor = 1.0 - exp( -targetDensity * targetDensity * vFogDepth * vFogDepth );
					float fogGlobalHeightFactor = 1.0 - ((vFogWorldPosition.y / fogMaxHeight) * exp(-cameraPosition.y * fogAtmosphereDensity));
					fogFactor = saturate(mix(fogFactor * fogGlobalHeightFactor, fogFactor, fogFactor));

					if (fogFactor >= 1.0) {
						discard;
					}
				#endif

        vec3 rayDir = normalize( vDirection );
        vec2 bounds = hitBox( vOrigin, rayDir, vScale );
        if ( bounds.x > bounds.y ) discard;

        bounds.x = max( bounds.x, 0.0 );

        float rng = blue_noise();
        vec3 inc = 1.0 / abs( rayDir );
        float delta = min( inc.x, min( inc.y, inc.z ) );

        vec3 size = vec3( textureSize( map, 0 ) );
        vec3 p = vOrigin + bounds.x * rayDir;
        p += rayDir * rng * (1.0 / size);

        // Get a random number that we'll use to jitter our ray.
        float min_steps = (steps * 0.5f) + (rng * 2.0f);
        // The number of ray march steps is determined depending on how steep the viewing angle is.
        float num_steps = mix(steps, min_steps, abs(rayDir.y));
        delta /= num_steps;

        float timeInfluence = clamp(cos(frame * 0.5), 0.1, 1.0);

        float a = 0.0;
        vec3 scattering = vec3(0.0);
        vec3 transmittanceResult = vec3(1.0);

        vec3 ambientColor = lightColor * 0.1;
        vec3 lightTargetColor = lightColor * lightIntensity;
        
        vec2 screenUV = gl_FragCoord.xy / iResolution.xy;
        float worldDepth = unpackRGBAToDepth(texture2D(tDepth, screenUV));

        float dist = 0.0;
        float depth = 0.0;
        vec3 vProjPoint;

        for ( float t = bounds.x; t < bounds.y; t += delta ) {
          vec3 samplePos = p;
          float cloudSDF = sample1(samplePos);

          float distToSample = distance(vOrigin, p);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * detailsFactor;
          float detailSDF = sample2(samplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          if (d >= 1.0) {
            break;
          }

          float extinction = d;
          if (extinction > 0.01) {
            float mu = dot(-p, lightDirection);
            a += (1.0 - a) * d;

            vec3 luminance = ambientColor + lightTargetColor * CalculateLightEnergy( -(p), rayDir, mu, d * delta, timeInfluence );
            vec3 transmittance = exp(-extinction * EXTINCTION_MULT);
            vec3 integScatt = extinction * (luminance - luminance * transmittance) / extinction;

            scattering += integScatt * transmittanceResult;
            transmittanceResult *= transmittance;

            if (length(transmittanceResult) <= 0.01) {
              transmittanceResult = vec3(0.0);
              break;
            }
          }

          vProjPoint = (vModelMatrix * vInstanceMatrix * vec4(p, 1.0)).xyz;
          depth = length(vProjPoint - cameraPos);
          depth = (depth - cameraNear) / (cameraFar - cameraNear);
          depth = saturate(depth);

          if (worldDepth < depth) {
            break;
          }

          p += rayDir * delta;
          dist += delta;
        }

        gl_FragDepth = depth;
        
        vec4 color = vec4(0.0);
        color.rgb = transmittanceResult + scattering;
        #ifdef USE_FOG
          color *= skyExposure;
        #endif
        color.a = a;
        gl_FragColor = color;

				#include <fog_fragment>
				#include <tonemapping_fragment>
				#include <colorspace_fragment>
      }
    `;
    
const baseGeometry = new BoxGeometry(1, 1, 1);
const baseMaterial = new ShaderMaterial({
  uniforms: UniformsUtils.merge([
    ShaderLib.standard.uniforms,
    {
      blueNoise: { value: null },
      base: { value: new Color(0xffffff) },
      lightColor: { value: new Color(0x6b839f) },
      lightPosition: { value: new Vector3(0, 1, 1) },
      lightDirection: { value: new Vector3(0, 1, 1) },
      lightIntensity: { value: 25 },
      detailsFactor: { value: 0.85 },
      vScale: { value: new Vector3() },
      cameraMatrixWorldInverse: { value: new Matrix4() },
      iResolution: { value: new Vector2() },
      tDepth: { value: null },
      center: { value: new Vector3() },
      radius: { value: 50 },
      cameraNear: { value: 0.1 },
      cameraFar: { value: 10000 },
      map: { value: null },
      hdMap: { value: null },
      cameraPos: { value: new Vector3() },
      threshold: { value: 0.5 },
      fogImpact: { value: 0.5 },
      range: { value: 0.1 },
      steps: { value: 32 },
      frame: { value: 0 },
      opacity: { value: 0.5 },
    },
  ]),
  vertexShader,
  fragmentShader,
  side: BackSide,
  fog: true,
  lights: true,
  transparent: true,
  depthWrite: false,
  // depthTest: false,
});

Postprocessing:

this.cameraMesh.composer = new POSTPROCESSING.EffectComposer(this.renderer, { frameBufferType: HalfFloatType, multisampling: this.postprocessingOptions.msaa?.enabled ? this.postprocessingOptions.msaa.samples : 0 });
    const firstRenderPass = new POSTPROCESSING.RenderPass(this.scene, this.cameraMesh);

    const depthPass = new POSTPROCESSING.DepthPass(this.scene, this.cameraMesh);

    const depthTexture = depthPass.texture;
    depthTexture.wrapS = RepeatWrapping;
    depthTexture.wrapT = RepeatWrapping;
    depthTexture.generateMipmaps = false;
    depthTexture.magFilter = NearestFilter;
    depthTexture.minFilter = NearestFilter;
    this.postprocessing.depthPass = depthPass;
    this.cameraMesh.composer.addPass(depthPass);
    this.cameraMesh.depthPass = depthPass;
    this.cameraMesh.depthTexture = depthTexture;

    this.cameraMesh.composer.addPass(firstRenderPass);


Weird, I tried to use a WebGLRenderTarget while overriding the material of the scene, and I obtain a result, but as soon as I move the camera it’s as if the depthTexture was not updated in my uniforms, although yes…

createDepthRenderer() {
    const depthRenderTarget = new WebGLRenderTarget();
    depthRenderTarget.setSize(window.innerWidth, window.innerHeight);
    depthRenderTarget.texture.minFilter = NearestFilter;
    depthRenderTarget.texture.magFilter = NearestFilter;
    depthRenderTarget.texture.generateMipmaps = false;
    depthRenderTarget.depthTexture = new DepthTexture();

    window.web3.depthTexture = depthRenderTarget.texture;

    this.depthRenderTarget = depthRenderTarget;
  }
render(scene, camera, delta, time) {
    this.renderer.clear();
    this.renderer.setRenderTarget(this.depthRenderTarget);
    scene.overrideMaterial = new MeshDepthMaterial();
    const disabledMeshes = [];
    scene.traverse((child) => {
      if (child.depthWrite === false) {
        disabledMeshes.push(child);
        child.visible = false;
      }
    })
    this.renderer.render(scene, camera);
    this.renderer.setRenderTarget(null);
    scene.overrideMaterial = null;
    disabledMeshes.forEach((child) => {
      child.visible = true;
    })
    if (camera.composer) {
      camera.composer.render(delta);
      
    } else {
      this.renderer.render(scene, camera);
    }
  }

Shader:

vec2 screenUV = gl_FragCoord.xy / iResolution.xy;
        float worldDepth = unpackRGBAToDepth(texture2D(tDepth, screenUV));
        vec3 vDirectionDeltaStep = rayDir * delta;

        float dist = 0.0;
        float depth = 0.0;
        vec3 vProjPoint;
        vec3 lastNonSolidPoint = vec3(p);

        float debug = 0.0;
        for ( float t = bounds.x; t < bounds.y; t += delta ) {
          vec3 samplePos = p;
          float cloudSDF = sample1(samplePos);

          float distToSample = distance(vOrigin, p);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * detailsFactor;
          float detailSDF = sample2(samplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          if (d < 1.) {
            lastNonSolidPoint = p;
          } else if (d >= 1.0) {
            break;
          }

          float extinction = d;
          if (extinction > 0.01) {
            float mu = dot(-p, lightDirection);
            a += (1.0 - a) * d;

            vec3 luminance = ambientColor + lightTargetColor * CalculateLightEnergy( -(p), rayDir, mu, d * delta, timeInfluence );
            vec3 transmittance = exp(-extinction * EXTINCTION_MULT);
            vec3 integScatt = extinction * (luminance - luminance * transmittance) / extinction;

            scattering += integScatt * transmittanceResult;
            transmittanceResult *= transmittance;

            if (length(transmittanceResult) <= 0.01) {
              transmittanceResult = vec3(0.0);
              lastNonSolidPoint = p;
              break;
            }
          }

          vProjPoint = (vModelMatrix * vInstanceMatrix * vec4(p, 1.0)).xyz;
          depth = length(vProjPoint - cameraPos);
          depth = (depth - cameraNear) / (cameraFar - cameraNear);
          depth = saturate(depth);
          
          if (worldDepth < depth) {
            debug = 1.0;
          }

          p += vDirectionDeltaStep;
          dist += delta;
        }

        p = lastNonSolidPoint;
        // vProjPoint = (vModelMatrix * vec4(p, 1.0)).xyz;
        // depth = length(vProjPoint - cameraPos);
        // depth = (depth - cameraNear) / (cameraFar - cameraNear);
        // depth = saturate(depth);

        // gl_FragDepth = depth;
        
        vec4 color = vec4(0.0);
        color.rgb = transmittanceResult + scattering;
        #ifdef USE_FOG
          color *= skyExposure;
        #endif
        color.a = a;
        gl_FragColor = color;
        if (debug > 0.0) {
          gl_FragColor.rgb = vec3(1.0, 0.0, 0.0);
        }

Two different volumetric cloud topics in one day :smile:
Since you have wide landscapes, 10000 is a bit low for camera.far but that’s not the problem. As I see it, you render the depthTexture anew every time.

If I play guessing games it won’t help you. I don’t know how you create your landscape and how the depth values ​​are generated from it. And since you are not sure whether the depthTexture is correct, the cause must be narrowed down.
Have you already created several primitive objects like spheres with a very simple basicMaterial and placed them at different distances? With these you will quickly see whether your depthTexture is correct or not. If something is wrong with simple spheres with a basicMaterial, you know that it is really due to the depthTexture.

Then I’ll also share with you the link to my volumetric cloud repository. Maybe you’ll find something useful for your project.

I don’t have any landscapes in there, but I had some nice results with those too.
Am I missing the size in your hitbox?
It won’t really only go from -0.5 to 0.5?

Here is my hitBox for comparison:

	vec2 hitBox(vec3 orig, vec3 dir) {
		vec3 box_min = vec3( -10000., CLOUDS_BOTTOM_HEIGHT, -10000. );
		vec3 box_max = vec3( 10000., CLOUDS_TOP_HEIGHT, 10000. );
		vec3 inv_dir = 1.0 / dir;
		vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
		vec3 tmax_tmp = ( box_max - orig ) * inv_dir;	
		vec3 tmin = min( tmin_tmp, tmax_tmp );
		vec3 tmax = max( tmin_tmp, tmax_tmp );
		
		float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
		float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
		
		return vec2( t0, t1 );
	}

I reconstruct the world coordinates with the tDepth differently than you. That doesn’t mean you have to do it like me. Because I tend to do things very close to the lowest level, because if something doesn’t work, I know I’m the one to blame :grin:

There you can find my cloud fragmentShader in my repo. This might help you:

resources/shader/cloudsFS.js
1 Like

Concerning the -0.5 / 0.5 box, I think it’s because I calculate the coordinates locally, and not in relation to the world. And I tell myself that this is undoubtedly what poses a problem when I try to calculate the depth…
But I don’t know how to transform these local coordinates into global ones… Maybe it’s something else?

I’m going to try drawing my depth rendering on a simple plane to see if it works, or do you perhaps have a simpler solution to see the result?

Anyway if I don’t do anything, this is what I get

2 Likes

In any case, referring to different coordinate systems is a potential source of error that you can easily avoid.
I reconstruct the original world coordinates in my shader with the inverseProjectionMatrix and the inverseViewMatrix of the camera. Then I have the same coordinates in the shader as in the 3D space in three.js. If a certain point in your scene is 150m away, then it is the same 150m in the shader. In my opinion, that makes things much clearer.

Here from my shader you can see how I do this:

vec3 computeWorldPosition(){ 
	
	float z = texture(tDepth, vUv).x;
	
	vec4 posCLIP = vec4(vec3(vUv, z) * 2.0 - 1.0, 1.0); 		
	vec4 posVS = inverseProjection * posCLIP;
	posVS = vec4(posVS.xyz / posVS.w, 1.0);
	vec4 posWS = inverseView * posVS;	
		
	return posWS.xyz;
}

and in the main:

vec3 posWS = computeWorldPosition();
vec3 cameraDirection = normalize(posWS - cameraPos);

The cameraDirection is then simply the respective world coordinate point minus the camera position, just like in the three.js scene.

Then you wouldn’t need such a complicated vertex shader for your postprocessing. Mine looks like this:

cloudsVS =`
	
	in vec2 uv;
	out vec2 vUv;
 
	void main() { 
		vUv = uv;  
		gl_Position = vec4( (uv - 0.5)*2.0, 0.0, 1.0 ); 
    }
`;

The post-processing world is initially completely flat and consists only of the image of the rendered scene. That’s why I only use the uv coordinates in the vertex shader, because that’s all the post-processing world consists of.
There are no vertices and therefore the vertex shader has nothing to do other than just prepare the uv coordinates for the fragment shader.

And in the fragment shader, where the pixels are then run through, I use my computeWorldPosition() to reconstruct the original world coordinates from the corresponding uv coordinate from the vertex shader together with the depth texture and the two camera matrices so that I can then work normally with the same coordinates in the shader like in the scene.

(postprocessing coordinates) uv → clip → view → world (three.js 3D scene coordinates)

I know coordinate transformations can be a bit confusing. But having the same coordinates in the shader as in the scene is a very pleasant thing. I hope this helps you to understand how you can work with world coordinates.

1 Like

I’m having trouble visualizing how I can modify my shader to adapt to your advice… I’m really a beginner in shaders, but very persevering haha

In any case, thank you very much for your help, it’s really nice! I’ll try to find inspiration from your code, it might take me days haha

I saw that you rendered the clouds in a separate postprocessing pass. This is undoubtedly a very good idea, especially it would allow rendering in half resolution. For my part, the sky is mesh with box geometry placed in the scene.
In reality, it is an instancedmesh which is rendered, because my “engine” analyzes everything in the scene to transform it into an instancedmesh to reduce the number of calls

1 Like

We all started small, I didn’t understand how shaders work at the beginning either.

I was wondering why you were working with the model matrix in postprocessing, because models no longer exist in postprocessing.
Since you don’t know much about shaders yet, you’ve treated yourself to a tough start with clouds :smile:

I recommend that you save your shader and put it aside and rebuild the postprocessing from scratch so that it corresponds to its natural way.
You will then be able to quickly install the clouds into the shader. If the foundation is sturdy, you will have a much easier time finding your way around it.
Postprocessing is called postprocessing because it is carried out after the scene has been rendered and thus postprocesses it.

Here is my main from my real clouds app with landscape. The shader is quite large with 732 lines. The main but very manageable.
In it I read the rendered scene tDiffuse and then overlay it with the postprocessing clouds. I read the tDepth texture directly in the computeWorldPosition() function. I commented out everything except for tDiffuse, the rendered scene that you want to postprocess. As a test, I simply gave these a red tint. If you see your normal scene with this main, only colored red, you know that your postprocessing shader is working. Then you can install the computeWorldPosition and the associated uniforms and test the posWS. If that works, you can quickly install your clouds the way you want.
The foundation is important. If the basic structure is robust, you will always be able to quickly isolate errors.

void main() {

	vec3 diffuse = texture(tDiffuse, vUv).rgb;

	/*				
	vec3 posWS = computeWorldPosition();
	vec3 cameraDirection = normalize(posWS - cameraPos);
	vec3 sunDir = normalize(planetPos - sunPos);
		
	vec4 clouds = scatterClouds(cameraPos, cameraDirection, posWS, sunDir);
	
	vec4 color = vec4(diffuse * (1.- clouds.a) + clouds.rgb, 1.);
	*/

	vec4 color = vec4(diffuse, 1.0) + vec4(0.5, 0.0, 0.0, 1.0);
		
	outColor = color;

	//outColor = vec4(normalize(posWS), 1.);  //just to test the posWS
}`;

I hope you have fun experimenting.

A small addition: In my real app I overlay the cloud postprocessing with an atmosphere postprocessing. The atmosphere has a very strong influence on the appearance of clouds.

I have just managed to generate my depthTexture, I had a problem clearing the rendering which was not happening, so the depth rendering was accumulating on the texture…

I tried to use your code, and go to world coordinates, but without success…
Well now I have to figure out how to correctly locate the depth of the clouds! If you have an idea, I’m interested !

const vertexShader = /* glsl */ `
      uniform vec3 cameraPos;

      out vec3 vOrigin;
      out vec3 vDirection;
      out vec3 vWorldPosition;
      out mat4 vModelMatrix;
      out mat4 vInstanceMatrix;
      out mat4 vModelViewMatrix;
      out mat4 vProjectionMatrix;
      out vec2 vUv;

      #include <common>
			#include <fog_pars_vertex>
      // #include <shadowmap_pars_vertex>

      void main() {
        vec4 mvPosition = vec4( position, 1.0 );
        vec4 mvSavePosition = instanceMatrix * mvPosition;
        mvPosition = modelViewMatrix * mvSavePosition;

        vOrigin = vec3( inverse( modelMatrix * instanceMatrix) * vec4( cameraPos, 1.0 ) ).xyz;
        vDirection = position - vOrigin;
        vModelMatrix = modelMatrix;
        vInstanceMatrix = instanceMatrix;
        vec4 worldPosition = mvSavePosition;
        worldPosition = modelMatrix * worldPosition;
        vWorldPosition = worldPosition.xyz;
        vUv = uv;
        vModelViewMatrix = modelViewMatrix;
        vProjectionMatrix = projectionMatrix;
        gl_Position = projectionMatrix * mvPosition;
        #include <fog_vertex>
      }
    `;

    const fragmentShader = /* glsl */ `
      precision highp float;
      precision highp sampler3D;
      precision highp float;
      precision highp int;
      #include <common>
			#include <packing>

			#include <fog_pars_fragment>

      uniform mat4 inverseProjection;
      uniform mat4 inverseView;

      in vec3 vOrigin;
      in vec2 vUv;
      in vec3 vDirection;
      in mat4 vModelMatrix;
      in mat4 vModelViewMatrix;
      in mat4 vProjectionMatrix;
      in mat4 vInstanceMatrix;
      in vec3 vWorldPosition;

      uniform vec3 base;
      uniform sampler2D blueNoise;
      uniform sampler2D tDepth;
      uniform sampler3D map;
      uniform sampler3D hdMap;

      uniform float radius;
      uniform vec2 iResolution;
      uniform vec3 center;
      uniform float threshold;
      uniform float detailsFactor;
      uniform vec3 cameraPos;
      uniform vec3 vScale;
      uniform float range;
      uniform float cameraNear;
      uniform float cameraFar;
      uniform float opacity;
      uniform float steps;
      uniform float frame;
      uniform vec3 lightColor;
      uniform vec3 lightPosition;
      uniform vec3 lightDirection;
      uniform float lightIntensity;

      vec2 hitBox( vec3 orig, vec3 dir, vec3 size ) {
        vec3 box_min = vec3( -0.5 );
        vec3 box_max = vec3( 0.5 );
        vec3 inv_dir = 1.0 / dir;
        vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
        vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
        vec3 tmin = min( tmin_tmp, tmax_tmp );
        vec3 tmax = max( tmin_tmp, tmax_tmp );
        float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
        float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
        return vec2( t0, t1 );
      }

      float sample1( vec3 p ) {
        vec3 texCoord = mod(p + vec3(frame, 0.0, 0.0), 1.0);
        
        float cloud = texture( map, texCoord + 0.5 ).r;
        return cloud;
      }
    
      float sample2( vec3 p ) {
          vec3 texCoord = mod(p + vec3(frame * 2.0, 0.0, 0.0), 1.0);

          float cloud = texture( hdMap, texCoord + 0.5 ).r;
          return cloud;

      }

      float HenyeyGreenstein(float g, float mu) {
          float gg = g * g;
          return (1.0 / (4.0 * PI)) * (1.0 - gg) / pow(1.0 + gg - 2.0 * g * mu, 1.5);
      }

      const float DUAL_LOBE_WEIGHT = 0.8;
      const vec3 EXTINCTION_MULT = vec3(0.7, 0.7, 1.0);

      float DualHenyeyGreenstein(float g, float costh) {
          return mix(HenyeyGreenstein(-g, costh), HenyeyGreenstein(g, costh), DUAL_LOBE_WEIGHT);
      }
      
      float PhaseFunction(float g, float costh) {
          return DualHenyeyGreenstein(g, costh);
      }

      vec3 MultipleOctaveScattering(float density, float mu) {
          float attenuation = 0.2;
          float contribution = 0.2;
          float phaseAttenuation = 0.5;
      
          float a = 1.0;
          float b = 1.0;
          float c = 1.0;
          float g = 0.85;
          float scatteringOctaves = 4.0;
          
          vec3 luminance = vec3(0.0);
      
          for (float i = 0.0; i < scatteringOctaves; i++) {
              float phaseFunction = PhaseFunction(0.35 * c, mu);
              vec3 beers = exp(-density * EXTINCTION_MULT * a);
      
              luminance += b * phaseFunction * beers;
      
              a *= attenuation;
              b *= contribution;
              c *= (1.0 - phaseAttenuation);
          }
          return luminance;
      }

      float inverseLerp(float minValue, float maxValue, float v) {
          return (v - minValue) / (maxValue - minValue);
      }

      float remap(float v, float inMin, float inMax, float outMin, float outMax) {
          float t = inverseLerp(inMin, inMax, v);
          return mix(outMin, outMax, t);
      }

      vec3 CalculateLightEnergy(vec3 rayPos, vec3 rayDirOrigin, float mu, float density, float timeInfluence) {
        float stepLength = 1. / 12.0;
        float lightRayDensity = 0.0;
        float distAccumulated = 0.0;

        for(float j = 0.0; j < 12.0; j++) {
          vec3 lightSamplePos = rayPos + lightDirection * distAccumulated;

          float cloudSDF = sample1(lightSamplePos);

          float distToSample = distance(lightSamplePos, vOrigin);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * (detailsFactor / 2.);
          float detailSDF = sample2(lightSamplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);
          

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          lightRayDensity += d * stepLength;
          distAccumulated += stepLength;
        }
        
        vec3 beersLaw = MultipleOctaveScattering(lightRayDensity, mu);
        vec3 powder = 1.0 - exp(-lightRayDensity * 2.0 * EXTINCTION_MULT);
        return beersLaw * mix(powder * 2.0, vec3(1.0), remap(mu, -1.0, 1.0, 0.0, 1.0));
      }

      float blue_noise() {
        ivec2 size = textureSize(blueNoise, 0);

        vec2 interleaved_pos = (mod(floor(gl_FragCoord.xy), float(size.x))) + vec2(frame * 0.5, frame * 0.5);
        vec2 tex_coord 	     = interleaved_pos / float(size.x) + vec2(0.5f / float(size.x), 0.5f / float(size.x));
        
        float result = texture(blueNoise, tex_coord).r * 2.0f - 1.0f;
        return result;
      }

      void main(){
				#ifdef USE_FOG
          float targetDensity = fogAtmosphereDensity * fogImpact;
					float fogFactor = 1.0 - exp( -targetDensity * targetDensity * vFogDepth * vFogDepth );
					float fogGlobalHeightFactor = 1.0 - ((vFogWorldPosition.y / fogMaxHeight) * exp(-cameraPosition.y * fogAtmosphereDensity));
					fogFactor = saturate(mix(fogFactor * fogGlobalHeightFactor, fogFactor, fogFactor));

					if (fogFactor >= 1.0) {
						discard;
					}
				#endif

        vec3 rayDir = normalize(vDirection);

        vec2 bounds = hitBox( vOrigin, rayDir, vScale );

        bounds.x = max( bounds.x, 0.0 );
        
        if ( bounds.x > bounds.y ) discard;
        if ( bounds.y <= 0.0 ) discard;

        float rng = blue_noise();
        vec3 inc = 1.0 / abs( rayDir );
        float delta = min( inc.x, min( inc.y, inc.z ) );

        vec3 size = vec3( textureSize( map, 0 ) );
        
        vec3 p = vOrigin + rayDir * bounds.x;
        p += rayDir * rng * (1.0 / size);

        float min_steps = (steps * 0.5f) + (rng * 2.0f);
        float num_steps = mix(steps, min_steps, abs(rayDir.y));
        delta /= num_steps;

        float timeInfluence = clamp(cos(frame * 0.5), 0.1, 1.0);

        float a = 0.0;
        vec3 scattering = vec3(0.0);
        vec3 transmittanceResult = vec3(1.0);

        vec3 ambientColor = lightColor * 0.1;
        vec3 lightTargetColor = lightColor * lightIntensity;
        
        vec3 vDirectionDeltaStep = rayDir * delta;

        float dist = 0.0;
        vec3 vProjPoint;
        vec3 lastNonSolidPoint = vec3(p);
        float debug = 0.0;

        for ( float t = bounds.x; t < bounds.y; t += delta ) {
          dist += delta;
          float cloudSDF = sample1(p);

          float distToSample = distance(vOrigin, p);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * detailsFactor;
          float detailSDF = sample2(p * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          if (d < 1.) {
            lastNonSolidPoint = p;
          } else if (d >= 1.0) {
            break;
          }

          float extinction = d;
          if (extinction > 0.01) {
            a += (1.0 - a) * d;

            if (a < 0.01) {
              continue;
            }

            float mu = dot(-normalize(p), lightDirection);

            vec3 luminance = ambientColor + lightTargetColor * CalculateLightEnergy( -p, rayDir, mu, d * delta, timeInfluence );
            vec3 transmittance = exp(-extinction * EXTINCTION_MULT);
            vec3 integScatt = extinction * (luminance - luminance * transmittance) / extinction;

            scattering += integScatt * transmittanceResult;
            transmittanceResult *= transmittance;

            if (length(transmittanceResult) <= 0.01) {
              transmittanceResult = vec3(0.0);
              lastNonSolidPoint = p;
              break;
            }
          }

          p += vDirectionDeltaStep;
        }

        p = lastNonSolidPoint;

        // Transformer p en coordonnées du monde
        vec4 worldPosition = vec4(p, 1.0);
        worldPosition = vInstanceMatrix * worldPosition;
        worldPosition = vModelMatrix * worldPosition;
        vec3 worldPos = worldPosition.xyz;

        // Depth test
        vec4 mvPosition = vModelViewMatrix * worldPosition;
        vec4 clipPos = vProjectionMatrix * mvPosition;
        vec2 depthUV = (clipPos.xy / clipPos.w) * 0.5 + 0.5;
        float sceneDepth = texture2D(tDepth, depthUV).r;
        float fragDepth = clipPos.z / clipPos.w;
        gl_FragDepth = fragDepth;

        if (fragDepth > sceneDepth + 0.01) {
          discard;
        }
        
        vec4 color = vec4(0.0);
        color.rgb = transmittanceResult + scattering;
        
        #ifdef USE_FOG
          color *= skyExposure;
        #endif
        color.a = a;
        gl_FragColor = color;

        if (debug > 0.0) {
          gl_FragColor.rgb = vec3(1.0, 0.0, 0.0);
        }

				#include <fog_fragment>
				#include <tonemapping_fragment>
				#include <colorspace_fragment>
      }
    `;

Since this thread goes deep into volumetrics, just a general petition - if someone here could end up with a volumetric material based on a density DataTexture3D, or if a few people could team up on a discord channel and compile one, I’d be more than happy to refresh openvdb (I have a cleaner version locally for a longer time, but it wasn’t finished.)

EmberGen VDBs are now compressable to as little as 5MB, so loading them on web is not much of an issue in terms of size, but writing both the volumetric materials and the feature loaders for OpenVDB format is a bit a lot of work :smiling_face_with_tear:

1 Like

In geometry space:


OK:


I finally succeeded !
It was necessary to set the material to depthTest: false and stop the loop when the ray is behind the depth of the scene.
But now, when the camera is in the mesh space (geometry box), there is this weird transparency effect. I think it’s because my raymarching loop stops too early in this scenario… Do you have an idea to correct this?

const vertexShader = /* glsl */ `
      uniform vec3 cameraPos;

      out vec3 vOrigin;
      out vec3 vDirection;
      out mat4 vModelMatrix;
      out mat4 vInstanceMatrix;
      out mat4 vModelViewMatrix;
      out mat4 vProjectionMatrix;

      #include <common>
			#include <fog_pars_vertex>
      // #include <shadowmap_pars_vertex>

      void main() {
        vec4 mvPosition = vec4( position, 1.0 );
        vec4 mvSavePosition = instanceMatrix * mvPosition;
        mvPosition = modelViewMatrix * mvSavePosition;

        vOrigin = vec3( inverse( modelMatrix * instanceMatrix) * vec4( cameraPos, 1.0 ) ).xyz;
        vDirection = position - vOrigin;
        vModelMatrix = modelMatrix;
        vInstanceMatrix = instanceMatrix;
        vec4 worldPosition = mvSavePosition;
        worldPosition = modelMatrix * worldPosition;
        vModelViewMatrix = modelViewMatrix;
        vProjectionMatrix = projectionMatrix;
        gl_Position = projectionMatrix * mvPosition;
        #include <fog_vertex>
      }
    `;

    const fragmentShader = /* glsl */ `
      precision highp float;
      precision highp sampler3D;
      precision highp float;
      precision highp int;
      #include <common>
			#include <packing>

			#include <fog_pars_fragment>

      in vec3 vOrigin;
      in vec3 vDirection;
      in mat4 vModelMatrix;
      in mat4 vModelViewMatrix;
      in mat4 vProjectionMatrix;
      in mat4 vInstanceMatrix;

      uniform vec3 base;
      uniform sampler2D blueNoise;
      uniform sampler2D tDepth;
      uniform sampler3D map;
      uniform sampler3D hdMap;
      uniform vec2 iResolution;
      uniform float threshold;
      uniform float detailsFactor;
      uniform vec3 cameraPos;
      uniform float range;
      uniform float opacity;
      uniform float steps;
      uniform float frame;
      uniform vec3 lightColor;
      uniform vec3 lightPosition;
      uniform vec3 lightDirection;
      uniform float lightIntensity;

      vec2 hitBox( vec3 orig, vec3 dir ) {
        vec3 box_min = vec3( -0.5 );
        vec3 box_max = vec3( 0.5 );
        vec3 inv_dir = 1.0 / dir;
        vec3 tmin_tmp = ( box_min - orig ) * inv_dir;
        vec3 tmax_tmp = ( box_max - orig ) * inv_dir;
        vec3 tmin = min( tmin_tmp, tmax_tmp );
        vec3 tmax = max( tmin_tmp, tmax_tmp );
        float t0 = max( tmin.x, max( tmin.y, tmin.z ) );
        float t1 = min( tmax.x, min( tmax.y, tmax.z ) );
        return vec2( t0, t1 );
      }

      float sample1( vec3 p ) {
        vec3 texCoord = mod(p + vec3(frame, 0.0, 0.0), 1.0);
        
        float cloud = texture( map, texCoord + 0.5 ).r;
        return cloud;
      }
    
      float sample2( vec3 p ) {
          vec3 texCoord = mod(p + vec3(frame * 2.0, 0.0, 0.0), 1.0);

          float cloud = texture( hdMap, texCoord + 0.5 ).r;
          return cloud;

      }

      float HenyeyGreenstein(float g, float mu) {
          float gg = g * g;
          return (1.0 / (4.0 * PI)) * (1.0 - gg) / pow(1.0 + gg - 2.0 * g * mu, 1.5);
      }

      const float DUAL_LOBE_WEIGHT = 0.8;
      const vec3 EXTINCTION_MULT = vec3(0.7, 0.7, 1.0);

      float DualHenyeyGreenstein(float g, float costh) {
          return mix(HenyeyGreenstein(-g, costh), HenyeyGreenstein(g, costh), DUAL_LOBE_WEIGHT);
      }
      
      float PhaseFunction(float g, float costh) {
          return DualHenyeyGreenstein(g, costh);
      }

      vec3 MultipleOctaveScattering(float density, float mu) {
          float attenuation = 0.2;
          float contribution = 0.2;
          float phaseAttenuation = 0.5;
      
          float a = 1.0;
          float b = 1.0;
          float c = 1.0;
          float g = 0.85;
          float scatteringOctaves = 4.0;
          
          vec3 luminance = vec3(0.0);
      
          for (float i = 0.0; i < scatteringOctaves; i++) {
              float phaseFunction = PhaseFunction(0.35 * c, mu);
              vec3 beers = exp(-density * EXTINCTION_MULT * a);
      
              luminance += b * phaseFunction * beers;
      
              a *= attenuation;
              b *= contribution;
              c *= (1.0 - phaseAttenuation);
          }
          return luminance;
      }

      float inverseLerp(float minValue, float maxValue, float v) {
          return (v - minValue) / (maxValue - minValue);
      }

      float remap(float v, float inMin, float inMax, float outMin, float outMax) {
          float t = inverseLerp(inMin, inMax, v);
          return mix(outMin, outMax, t);
      }

      vec3 CalculateLightEnergy(vec3 rayPos, vec3 rayDirOrigin, float mu, float density, float timeInfluence) {
        float stepLength = 1. / 12.0;
        float lightRayDensity = 0.0;
        float distAccumulated = 0.0;

        for(float j = 0.0; j < 12.0; j++) {
          vec3 lightSamplePos = rayPos + lightDirection * distAccumulated;

          float cloudSDF = sample1(lightSamplePos);

          float distToSample = distance(lightSamplePos, vOrigin);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * (detailsFactor / 2.);
          float detailSDF = sample2(lightSamplePos * 2.);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);
          

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          lightRayDensity += d * stepLength;
          distAccumulated += stepLength;
        }
        
        vec3 beersLaw = MultipleOctaveScattering(lightRayDensity, mu);
        vec3 powder = 1.0 - exp(-lightRayDensity * 2.0 * EXTINCTION_MULT);
        return beersLaw * mix(powder * 2.0, vec3(1.0), remap(mu, -1.0, 1.0, 0.0, 1.0));
      }

      float blue_noise() {
        ivec2 size = textureSize(blueNoise, 0);

        vec2 interleaved_pos = (mod(floor(gl_FragCoord.xy), float(size.x))) + vec2(frame * 0.5, frame * 0.5);
        vec2 tex_coord 	     = interleaved_pos / float(size.x) + vec2(0.5f / float(size.x), 0.5f / float(size.x));
        
        float result = texture(blueNoise, tex_coord).r * 2.0f - 1.0f;
        return result;
      }

      void main(){
				#ifdef USE_FOG
          float targetDensity = fogAtmosphereDensity * fogImpact;
					float fogFactor = 1.0 - exp( -targetDensity * targetDensity * vFogDepth * vFogDepth );
					float fogGlobalHeightFactor = 1.0 - ((vFogWorldPosition.y / fogMaxHeight) * exp(-cameraPosition.y * fogAtmosphereDensity));
					fogFactor = saturate(mix(fogFactor * fogGlobalHeightFactor, fogFactor, fogFactor));

					if (fogFactor >= 1.0) {
						discard;
					}
				#endif

        vec3 rayDir = normalize(vDirection);

        vec2 bounds = hitBox( vOrigin, rayDir );

        bounds.x = max( bounds.x, 0.0 );
        
        if ( bounds.x > bounds.y ) discard;
        if ( bounds.y <= 0.0 ) discard;

        float rng = blue_noise();
        vec3 inc = 1.0 / abs( rayDir );
        float delta = min( inc.x, min( inc.y, inc.z ) );

        vec3 size = vec3( textureSize( map, 0 ) );
        
        vec3 p = vOrigin + rayDir * bounds.x;
        p += rayDir * rng * (1.0 / size);

        float min_steps = (steps * 0.5f) + (rng * 2.0f);
        float num_steps = mix(steps, min_steps, abs(rayDir.y));
        delta /= num_steps;

        float timeInfluence = clamp(cos(frame * 0.5), 0.1, 1.0);

        float a = 0.0;
        vec3 scattering = vec3(0.0);
        vec3 transmittanceResult = vec3(1.0);

        vec3 ambientColor = lightColor * 0.1;
        vec3 lightTargetColor = lightColor * lightIntensity;
        
        vec3 vDirectionDeltaStep = rayDir * delta;
        vec3 lastNonSolidPoint = vec3(p);

        vec4 sampledDepth = texture2D(tDepth, gl_FragCoord.xy / iResolution.xy);
        float sceneDepth = unpackRGBAToDepth(sampledDepth);
        float fragDepth = gl_FragCoord.z;

        for ( float t = bounds.x; t < bounds.y; t += delta ) {
          float cloudSDF = sample1(p);

          float distToSample = distance(vOrigin, p);
          float t_detailDropout = smoothstep(1000.0, 800., distToSample);

          float detailStrength = t_detailDropout * detailsFactor;
          float detailSDF = sample2(p * 2.5);
          float cloud = remap(cloudSDF, detailStrength * detailSDF, 1.0, 0.0, 1.0);

          float d = smoothstep(threshold - range, threshold + range, cloud) * opacity;
          d = d * (timeInfluence + 0.5);

          if (d < 1.) {
            lastNonSolidPoint = p;
          } else if (d >= 1.0) {
            break;
          }

          float extinction = d;
          if (extinction > 0.01) {
            a += (1.0 - a) * d;

            if (a < 0.01) {
              continue;
            }

            float mu = dot(-normalize(p), lightDirection);

            vec3 luminance = ambientColor + lightTargetColor * CalculateLightEnergy( -p, rayDir, mu, d * delta, timeInfluence );
            vec3 transmittance = exp(-extinction * EXTINCTION_MULT);
            vec3 integScatt = extinction * (luminance - luminance * transmittance) / extinction;

            scattering += integScatt * transmittanceResult;
            transmittanceResult *= transmittance;

            if (length(transmittanceResult) <= 0.01) {
              transmittanceResult = vec3(0.0);
              lastNonSolidPoint = p;
              break;
            }
          }
          p += vDirectionDeltaStep;

          // Depth test
          vec4 worldPosition = vInstanceMatrix * vec4(p, 1.0);
          worldPosition = vModelMatrix * worldPosition;
          vec4 clipSpace = vProjectionMatrix * vModelViewMatrix * worldPosition;
          float fragDepth = clipSpace.z / clipSpace.w;
          fragDepth = saturate((fragDepth + 1.0) / 2.0);

          if (fragDepth > sceneDepth) {
            break;
          }
        }

        gl_FragDepth = fragDepth;

        p = lastNonSolidPoint;

        vec4 color = vec4(0.0);
        color.rgb = transmittanceResult + scattering;
        
        #ifdef USE_FOG
          color *= skyExposure;
        #endif
        color.a = a;
        gl_FragColor = color;

				#include <fog_fragment>
				#include <tonemapping_fragment>
				#include <colorspace_fragment>
      }
    `;
    
const baseGeometry = new BoxGeometry(1, 1, 1);
const baseMaterial = new ShaderMaterial({
  uniforms: UniformsUtils.merge([
    ShaderLib.standard.uniforms,
    {
      blueNoise: { value: null },
      base: { value: new Color(0xffffff) },
      lightColor: { value: new Color(0x6b839f) },
      lightPosition: { value: new Vector3(0, 1, 1) },
      lightDirection: { value: new Vector3(0, 1, 1) },
      lightIntensity: { value: 25 },
      detailsFactor: { value: 0.85 },
      iResolution: { value: new Vector2() },
      tDepth: { value: null },
      map: { value: null },
      hdMap: { value: null },
      cameraPos: { value: new Vector3() },
      threshold: { value: 0.5 },
      fogImpact: { value: 0.5 },
      range: { value: 0.1 },
      steps: { value: 32 },
      frame: { value: 0 },
      opacity: { value: 0.5 },
    },
  ]),
  vertexShader,
  fragmentShader,
  side: BackSide,
  fog: true,
  lights: true,
  transparent: true,
  depthTest: false,
});
2 Likes