A melting room with vertex shader and GLTF or planes?

I’m trying to make a room with melting/bubbly walls. For this I’m now testing both a GLTF model and a bunch of PlaneGeometries (and applying a vertex and fragment shader to them). I’m having problems with both approaches and that is probably due to my lack of understanding of normals and shaders in general. Code is at the end of the post.

1)GLTF in Blender:

2)GLTF in Three.js using basicVertexShader and fragmentShader2:

3)GLTF in Three.js using explosionVertexShader and fragmentShader2:

As can be seen here, the explosionVertexShader makes the GLTF crazy, not wavy and noisy like it should.

I also tried to create a similar room with planes. Here the vertex shader works better, but it looks much more natural with fragmentShader than fragmentShader2(latter):

The sphere in the center uses explosionVertexShader as well. It breaks on the edges regardless of which fragment shader is in use.

So I have two questions:

  1. What am I missing in uniforms, normals, in applying the vertex shader in general?

  2. Is there a better way to create a view inside a box that has melting walls? I’m trying to change the walls based on data and thought shaderMaterial is a good idea.

Let me know about any mistakes in question, it is my first post here :slight_smile:

<!DOCTYPE ht<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">

</head>

<body>
  <div id="container"></div>
  <script src="js/three.js"></script>
  <script src="js/GLTFLoader.js"></script>

  <script id="basicVertexShader" type="x-shader/x-vertex">

    varying vec2 vUv;

    void main()
    {
    vUv = uv;
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
    }

  </script>

  <script id="explosionVertexShader" type="x-shader/x-vertex">

    vec3 mod289(vec3 x)
    {
      return x - floor(x * (1.0 / 289.0)) * 289.0;
    }

    vec4 mod289(vec4 x)
    {
      return x - floor(x * (1.0 / 289.0)) * 289.0;
    }

    vec4 permute(vec4 x)
    {
      return mod289(((x*34.0)+1.0)*x);
    }

    vec4 taylorInvSqrt(vec4 r)
    {
      return 1.79284291400159 - 0.85373472095314 * r;
    }

    vec3 fade(vec3 t) {
      return t*t*t*(t*(t*6.0-15.0)+10.0);
    }

    // Classic Perlin noise
    float cnoise(vec3 P)
    {
      vec3 Pi0 = floor(P); // Integer part for indexing
      vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
      Pi0 = mod289(Pi0);
      Pi1 = mod289(Pi1);
      vec3 Pf0 = fract(P); // Fractional part for interpolation
      vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
      vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
      vec4 iy = vec4(Pi0.yy, Pi1.yy);
      vec4 iz0 = Pi0.zzzz;
      vec4 iz1 = Pi1.zzzz;

      vec4 ixy = permute(permute(ix) + iy);
      vec4 ixy0 = permute(ixy + iz0);
      vec4 ixy1 = permute(ixy + iz1);

      vec4 gx0 = ixy0 * (1.0 / 7.0);
      vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
      gx0 = fract(gx0);
      vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
      vec4 sz0 = step(gz0, vec4(0.0));
      gx0 -= sz0 * (step(0.0, gx0) - 0.5);
      gy0 -= sz0 * (step(0.0, gy0) - 0.5);

      vec4 gx1 = ixy1 * (1.0 / 7.0);
      vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
      gx1 = fract(gx1);
      vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
      vec4 sz1 = step(gz1, vec4(0.0));
      gx1 -= sz1 * (step(0.0, gx1) - 0.5);
      gy1 -= sz1 * (step(0.0, gy1) - 0.5);

      vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
      vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
      vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
      vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
      vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
      vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
      vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
      vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);

      vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
      g000 *= norm0.x;
      g010 *= norm0.y;
      g100 *= norm0.z;
      g110 *= norm0.w;
      vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
      g001 *= norm1.x;
      g011 *= norm1.y;
      g101 *= norm1.z;
      g111 *= norm1.w;

      float n000 = dot(g000, Pf0);
      float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
      float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
      float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
      float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
      float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
      float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
      float n111 = dot(g111, Pf1);

      vec3 fade_xyz = fade(Pf0);
      vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
      vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
      float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
      return 2.2 * n_xyz;
    }

    // Classic Perlin noise, periodic variant
    float pnoise(vec3 P, vec3 rep)
    {
      vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period
      vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period
      Pi0 = mod289(Pi0);
      Pi1 = mod289(Pi1);
      vec3 Pf0 = fract(P); // Fractional part for interpolation
      vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
      vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
      vec4 iy = vec4(Pi0.yy, Pi1.yy);
      vec4 iz0 = Pi0.zzzz;
      vec4 iz1 = Pi1.zzzz;

      vec4 ixy = permute(permute(ix) + iy);
      vec4 ixy0 = permute(ixy + iz0);
      vec4 ixy1 = permute(ixy + iz1);

      vec4 gx0 = ixy0 * (1.0 / 7.0);
      vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
      gx0 = fract(gx0);
      vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
      vec4 sz0 = step(gz0, vec4(0.0));
      gx0 -= sz0 * (step(0.0, gx0) - 0.5);
      gy0 -= sz0 * (step(0.0, gy0) - 0.5);

      vec4 gx1 = ixy1 * (1.0 / 7.0);
      vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
      gx1 = fract(gx1);
      vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
      vec4 sz1 = step(gz1, vec4(0.0));
      gx1 -= sz1 * (step(0.0, gx1) - 0.5);
      gy1 -= sz1 * (step(0.0, gy1) - 0.5);

      vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
      vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
      vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
      vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
      vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
      vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
      vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
      vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);

      vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
      g000 *= norm0.x;
      g010 *= norm0.y;
      g100 *= norm0.z;
      g110 *= norm0.w;
      vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
      g001 *= norm1.x;
      g011 *= norm1.y;
      g101 *= norm1.z;
      g111 *= norm1.w;

      float n000 = dot(g000, Pf0);
      float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
      float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
      float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
      float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
      float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
      float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
      float n111 = dot(g111, Pf1);

      vec3 fade_xyz = fade(Pf0);
      vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
      vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
      float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
      return 2.2 * n_xyz;
    }

    // Include the Ashima code here!

    varying vec2 vUv;
    varying float uNoise;
    uniform float uTime;

    float turbulence( vec3 p ) {
      float w = 100.0;
      float t = -.5;
      for (float f = 1.0 ; f <= 10.0 ; f++ ){
        float power = pow( 2.0, f );
        t += abs( pnoise( vec3( power * p ), vec3( 10.0, 10.0, 10.0 ) ) / power );
      }
      return t;
    }

    void main() {

      vUv = uv;

      uNoise = 10.0 *  -.10 * turbulence( .5 * normal + uTime );
      float b = 5.0 * pnoise( 7. * vec3(vUv, 1.0) + vec3( 0.1 * uTime ), vec3( 100.0 ) );
      //float displacement = - 10. * uNoise + b;
      //float displacement = 10.*sin(position.x + position.y +uTime);
      float displacement = b;
      vec3 newPosition = position + normal * displacement;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );

    }

	</script>



  <script id="fragmentShader" type="x-shader/x-fragment">

    uniform float uTime;

    varying vec2 vUv;
    //
   void main( void ) {
    //
    vec2 position = - 1.0 + 2.0 * vUv;
    //
    float red = abs( sin( position.x * position.y + uTime / 5.0 ) );
    float green = abs( sin( position.x * position.y + uTime / 4.0 ) );
     float blue = abs( sin( position.x * position.y + uTime / 3.0 ) );
    gl_FragColor = vec4( red, green, blue, 1.0 );

     }

	</script>

  <script id="fragmentShader2" type="x-shader/x-fragment">

    uniform float uTime;

			varying vec2 vUv;

			void main( void ) {

				vec2 position = vUv;

				float color = 0.0;
				color += sin( position.x * cos( uTime / 15.0 ) * 80.0 ) + cos( position.y * cos( uTime / 15.0 ) * 10.0 );
				color += sin( position.y * sin( uTime / 10.0 ) * 40.0 ) + cos( position.x * sin( uTime / 25.0 ) * 40.0 );
				color += sin( position.x * sin( uTime / 5.0 ) * 10.0 ) + sin( position.y * sin( uTime / 35.0 ) * 80.0 );
				color *= sin( uTime / 10.0 ) * 0.5;

				gl_FragColor = vec4( vec3( color, color * 0.5, sin( color + uTime / 3.0 ) * 0.75 ), 1.0 );

			}

		</script>


  <script id="explosionFragmentShader" type="x-shader/x-fragment">

    varying vec2 vUv;
      varying float noise;
      uniform sampler2D tExplosion;

      float random( vec3 scale, float seed ){
        return fract( sin( dot( gl_FragCoord.xyz + seed, scale ) ) * 43758.5453 + seed ) ;
      }

      void main() {

        float r = .01 * random( vec3( 12.9898, 78.233, 151.7182 ), 0.0 );
        vec2 tPos = vec2( 0, 1.3 * noise + r );
        vec4 color = texture2D( tExplosion, tPos );
        gl_FragColor = vec4( color.rgb, 1.0 );

      }
    </script>



  <script>
    let camera, scene, renderer, uniforms, uniformsBackground, uniformsTop, uniformsBottom, uniformsSides, uniformsExplosion, mouseX, mouseY;
    let ballGeometry, explosionGeometry;
    mouseX = 0;
    mouseY = 0;
    const loader = new THREE.GLTFLoader();


    init();
    animate();


    function init() {

      const container = document.getElementById('container');

      // renderer
      renderer = new THREE.WebGLRenderer({
        antialias: true
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      container.appendChild(renderer.domElement);
      renderer.outputEncoding = THREE.sRGBEncoding;


      let geometry, material;


      uniformsBackground = {
        "uTime": {
          value: 10000.0
        }
      };

      uniformsTop = {
        "uTime": {
          value: 21000.0
        }
      };

      uniformsBottom = {
        "uTime": {
          value: 21000.0
        }
      };

      uniformsSides = {
        "uTime": {
          value: 33000.0
        }
      };

      uniformsExplosion = {
        "uTime": {
          value: 10000.0
        }
      };



      // scene
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);

      camera.position.set(0, 75, 160);



      const planeGeo = new THREE.PlaneGeometry(125.1, 125.1, 100, 100);

      const planeBackground = new THREE.Mesh(planeGeo, new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader2').textContent,
        uniforms: uniformsBackground
      }));
      planeBackground.position.y = 70;
      planeBackground.position.z = -50;
      //planeBackground.rotateX( - Math.PI / 4 );
      scene.add(planeBackground);

      // walls
      const planeTop = new THREE.Mesh(planeGeo, new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader2').textContent,
        uniforms: uniformsTop
      }));
      planeTop.position.y = 120;
      planeTop.rotateX(Math.PI / 2);
      scene.add(planeTop);

      const planeBottom = new THREE.Mesh(planeGeo, new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader2').textContent,
        uniforms: uniformsBottom
      }));
      planeBottom.rotateX(-Math.PI / 2);
      planeBottom.position.y = 20;
      scene.add(planeBottom);



      const planeRight = new THREE.Mesh(planeGeo, new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader2').textContent,
        uniforms: uniformsSides
      }));
      planeRight.position.x = 50;
      planeRight.position.y = 70;
      planeRight.rotateY(-Math.PI / 2);
      scene.add(planeRight);

      const planeLeft = new THREE.Mesh(planeGeo, new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader2').textContent,
        uniforms: uniformsSides
      }));
      planeLeft.position.x = -50;
      planeLeft.position.y = 70;
      planeLeft.rotateY(Math.PI / 2);
      scene.add(planeLeft);


      ballGeometry = new THREE.SphereGeometry(20, 32, 32);
      console.log(ballGeometry);
      const ballMaterial = new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent,
        uniforms: uniformsBackground
      });
      const sphere = new THREE.Mesh(ballGeometry, ballMaterial);
      sphere.position.y = 70;
      sphere.position.x = 0;
      sphere.position.z = 0;
      scene.add(sphere);


      const modelMaterial = new THREE.ShaderMaterial({
        vertexShader: document.getElementById('explosionVertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent,
        uniforms: uniformsBackground
      });

      const modelMaterial2 = new THREE.MeshPhongMaterial({
        color: 0xffffff
      });


      loader.load('boxTemplate4.gltf', function(gltf) {

          const model = gltf.scene;
          model.traverse(function(child) {

            if (child.isMesh) {

              //uniformsExplosion.uTime.value = child.material.map; was testing


              child.material = modelMaterial;

            }

          });

          model.position.y = 72;
          model.position.x = 0;
          model.position.z = 20;
          model.scale.set(40, 40, 40); // was 42 for basic template
          //scene.add( model );


        },
        // called while loading is progressing
        function(xhr) {

          console.log((xhr.loaded / xhr.total * 100) + '% loaded');

        },
        // called when loading has errors
        function(error) {

          console.log('An error happened');

        }
      );


      // lights
      const mainLight = new THREE.PointLight(0xcccccc, 1.5, 250);
      mainLight.position.y = 60;
      scene.add(mainLight);

      const greenLight = new THREE.PointLight(0x00ff00, 0.25, 1000);
      greenLight.position.set(550, 50, 0);
      scene.add(greenLight);

      const redLight = new THREE.PointLight(0xff0000, 0.25, 1000);
      redLight.position.set(-550, 50, 0);
      scene.add(redLight);

      const blueLight = new THREE.PointLight(0x7f7fff, 0.25, 1000);
      blueLight.position.set(0, 50, 550);
      scene.add(blueLight);

      window.addEventListener('resize', onWindowResize, false);
      window.addEventListener("mousemove", onWindowMouseMove)

    }


    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();

      renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function onWindowMouseMove(event) {
      mouseX = event.clientX / window.innerWidth;
      mouseY = event.clientY / window.innerHeight;
    }


    function animate() {

      requestAnimationFrame(animate);

      const timer = performance.now() * 0.01;

      uniformsBackground.uTime.value = timer;
      uniformsTop.uTime.value = timer;
      uniformsBottom.uTime.value = timer;
      uniformsSides.uTime.value = timer;
      uniformsExplosion.uTime.value = timer;

      renderer.render(scene, camera);

    }
  </script>


</body>

</html>