<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.163/build/three.module.js", 
      "three/addons/": "https://unpkg.com/three@0.163/examples/jsm/",
      "three/nodes": "https://unpkg.com/three@0.163/examples/jsm/nodes/Nodes.js",
       "GUI": "https://unpkg.com/lil-gui@0.19.2/"
    }
  }
</script>
import {color, MeshPhongNodeMaterial} from "https://esm.sh/three/nodes";
import {OrbitControls} from "https://esm.sh/three/addons/controls/OrbitControls.js";
import * as THREE from "https://esm.sh/three";
import { GUI } from "https://esm.sh/three/addons/libs/lil-gui.module.min.js";
      import { ImprovedNoise } from "https://esm.sh/three/addons/math/ImprovedNoise.js";

        let positionX = -.25;
        let direction = 1;
let renderer, scene, camera, innerSphere;
let mesh;
var counter = 0;

      init();
      animate();

      function init() {

        renderer = new THREE.WebGLRenderer();
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );

        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 100 );
        camera.position.set( 0, 0, 1.5 );

        new OrbitControls( camera, renderer.domElement );

        // Sky

        const canvas = document.createElement( 'canvas' );
        canvas.width = 1;
        canvas.height = 32;

        const context = canvas.getContext( '2d' );
        const gradient = context.createLinearGradient( 0, 0, 0, 32 );
        gradient.addColorStop( 0.0, '#014a84' );
        gradient.addColorStop( 0.5, '#0561a0' );
        gradient.addColorStop( 1.0, '#437ab6' );
        context.fillStyle = gradient;
        context.fillRect( 0, 0, 1, 32 );

        const skyMap = new THREE.CanvasTexture( canvas );
        skyMap.colorSpace = THREE.SRGBColorSpace;

        const sky = new THREE.Mesh(
          new THREE.SphereGeometry( 10 ),
          new THREE.MeshBasicMaterial( { map: skyMap, side: THREE.BackSide } )
        );
        scene.add( sky );

        // Texture

        const size = 128;
        const data = new Uint8Array( size * size * size );

        let i = 0;
        const scale = 0.05;
        const perlin = new ImprovedNoise();
        const vector = new THREE.Vector3();

        for ( let z = 0; z < size; z ++ ) {

          for ( let y = 0; y < size; y ++ ) {

            for ( let x = 0; x < size; x ++ ) {

              const d = 1.0 - vector.set( x, y, z ).subScalar( size / 2 ).divideScalar( size ).length();
              data[ i ] = ( 128 + 128 * perlin.noise( x * scale / 1.5, y * scale, z * scale / 1.5 ) ) * d * d;
              i ++;

            }

          }

        }

        const texture = new THREE.Data3DTexture( data, size, size, size );
        texture.format = THREE.RedFormat;
        texture.minFilter = THREE.LinearFilter;
        texture.magFilter = THREE.LinearFilter;
        texture.unpackAlignment = 1;
        texture.needsUpdate = true;

        // Material


        const geometry1 = new THREE.SphereGeometry( .05, 32, 16 ); 
        const material1 = new THREE.MeshBasicMaterial( { color: 'green' } ); 
        innerSphere = new THREE.Mesh( geometry1, material1 ); 
        scene.add( innerSphere );

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

          varying vec3 vOrigin;
          varying vec3 vDirection;

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

            vOrigin = vec3( inverse( modelMatrix ) * vec4( cameraPos, 1.0 ) ).xyz;
            vDirection = position - vOrigin;



            gl_Position = projectionMatrix * mvPosition;
          }
        `;

        const fragmentShader = /* glsl */`
          precision highp float;
          precision highp sampler3D;

          varying vec3 vOrigin;
          varying vec3 vDirection;


          uniform vec3 base;
          uniform sampler3D map;

          uniform float threshold;
          uniform float range;
          uniform float opacity;
          uniform float steps;
          uniform float frame;

          uniform float camNear;
          uniform float camFar;

          #include <common>
          #include <packing>

          uint wang_hash(uint seed)
          {
              seed = (seed ^ 61u) ^ (seed >> 16u);
              seed *= 9u;
              seed = seed ^ (seed >> 4u);
              seed *= 0x27d4eb2du;
              seed = seed ^ (seed >> 15u);
              return seed;
          }

          float randomFloat(inout uint seed)
          {
              return float(wang_hash(seed)) / 4294967296.;
          }

          vec2 hitBox( vec3 orig, vec3 dir ) {
            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 ) {
            return texture( map, p ).r;
          }

          float shading( vec3 coord ) {
            float step = 0.01;
            return sample1( coord + vec3( - step ) ) - sample1( coord + vec3( step ) );
          }

          vec4 linearToSRGB( in vec4 value ) {
            return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
          }

          void main(){
            vec3 rayDir = normalize( vDirection );
            vec2 bounds = hitBox( vOrigin, rayDir );

            if ( bounds.x > bounds.y ) discard;

            bounds.x = max( bounds.x, 0.0 );
            float distance = camNear;

            vec3 p = vOrigin + bounds.x * rayDir;
            vec3 inc = 1.0 / abs( rayDir );
            float delta = min( inc.x, min( inc.y, inc.z ) );
            delta /= steps;

            // Jitter

            // Nice little seed from
            // https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/
            uint seed = uint( gl_FragCoord.x ) * uint( 1973 ) + uint( gl_FragCoord.y ) * uint( 9277 ) + uint( frame ) * uint( 26699 );
            vec3 size = vec3( textureSize( map, 0 ) );
            float randNum = randomFloat( seed ) * 2.0 - 1.0;
            p += rayDir * randNum * ( 1.0 / size );


            vec4 ac = vec4( base, 0.0 );

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

              float d = sample1( p + 0.5 );

              d = smoothstep( threshold - range, threshold + range, d ) * opacity;

              ac.rgba += ( 1.0 - ac.a ) * d ;

              p += rayDir * delta;

              distance += d;
              
              if ( ac.a >= 0.95 ) break;

            }

            vec4 viewPosition = viewMatrix * vec4(p, 1);
            gl_FragDepth = viewZToPerspectiveDepth(viewPosition.z, camNear, camFar);
            if (gl_FragDepth >=  0.99999) discard;

            gl_FragColor = linearToSRGB( ac );
            if ( gl_FragColor.a == 0.0 ) discard;

          }
        `;

        const geometry = new THREE.BoxGeometry( 1, 1, 1 );
        const material = new THREE.ShaderMaterial( {
          uniforms: {
            base: { value: new THREE.Color( 0x798aa0 ) },
            map: { value: texture },
            cameraPos: { value: new THREE.Vector3() },
            threshold: { value: 0.25 },
            opacity: { value: 0.25 },
            range: { value: 0.1 },
            steps: { value: 100 },
                        camNear: { value: camera.near },
            camFar: { value: camera.far },
            frame: { value: 0 }
          },
          vertexShader,
          fragmentShader,
          side: THREE.BackSide,
          transparent: true
        } );

        mesh = new THREE.Mesh( geometry, material );
        scene.add( mesh );

        //
        const parameters = {
          threshold: 0.25,
          opacity: 0.25,
          range: 0.1,
          steps: 100
        };

        function update() {
          material.uniforms.threshold.value = parameters.threshold;
          material.uniforms.opacity.value = parameters.opacity;
          material.uniforms.range.value = parameters.range;
          material.uniforms.steps.value = parameters.steps;

        }

        const gui = new GUI();
        gui.add( parameters, 'threshold', 0, 1, 0.01 ).onChange( update );
        gui.add( parameters, 'opacity', 0, 1, 0.01 ).onChange( update );
        gui.add( parameters, 'range', 0, 1, 0.01 ).onChange( update );
        gui.add( parameters, 'steps', 0, 200, 1 ).onChange( update );
        window.addEventListener( 'resize', onWindowResize );
      }

      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize( window.innerWidth, window.innerHeight );
      }

      function animate() {
        requestAnimationFrame( animate );
        mesh.material.uniforms.cameraPos.value.copy( camera.position );
        mesh.material.uniforms.camNear.value=camera.near 
        mesh.material.uniforms.camFar.value=camera.far 

        innerSphere.position.x = positionX;
        positionX += 0.01 * direction; 

        if (positionX > 1 || positionX < -1) {
          direction *= -1;
        }
        
        mesh.material.uniforms.frame.value ++;
        renderer.render( scene, camera );
      }

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.