Three.js fundamentals, shadertoy example, how to transform the pattern along with the object?

Viewed 112

Here's an example based on the second live example from https://threejsfundamentals.org/threejs/lessons/threejs-shadertoy.html:

html, body {
  height: 100%;
  margin: 0;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
  
<script type="module">
// Three.js - Shadertoy Basic
// from https://threejsfundamentals.org/threejs/threejs-shadertoy-basic.html


import * as THREE from 'https://unpkg.com/three@0.122.0/build/three.module.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});
  renderer.autoClearColor = false;

  const camera = new THREE.PerspectiveCamera(
    45, 16/9, 0.01, 1000
  );
  camera.position.z = 5
  const scene = new THREE.Scene();
  const plane = new THREE.PlaneBufferGeometry(2, 2);


  const fragmentShader = `
  #include <common>

  uniform vec3 iResolution;
  uniform float iTime;

  // https://www.shadertoy.com/view/MtXSWj

  float alternate(float p, float d){;
    return sign(fract(p*d*.5)*2.-1.);
  }

  vec3 rainbow(float t){
      return sin(t+vec3(0,.33,.66)*6.28)*.5+.5;
  }

  vec3 TwinDragon(vec2 p){
      float time       = fract(iTime*0.05)*20.;
      
      //scaling
      p = (p*2.-iResolution.xy)/iResolution.y*1.5;
      
      //----------the fractal stuff----   ---THIS IS ANIMATIONS----(so remove them if you want)
      p.y += alternate(p.x, 256. )/512. * clamp(time-16.,0.,2.)/2.;
      p.x -= alternate(p.y, 128. )/256. * clamp(time-14.,0.,2.)/2.;
      p.y += alternate(p.x,  64. )/128. * clamp(time-12.,0.,2.)/2.;
      p.x -= alternate(p.y,  32. )/ 64. * clamp(time-10.,0.,2.)/2.;
      p.y += alternate(p.x,  16. )/ 32. * clamp(time- 8.,0.,2.)/2.;
      p.x -= alternate(p.y,   8. )/ 16. * clamp(time- 6.,0.,2.)/2.;
      p.y += alternate(p.x,   4. )/  8. * clamp(time- 4.,0.,2.)/2.;
      p.x -= alternate(p.y,   2. )/  4. * clamp(time- 2.,0.,2.)/2.;

      // prettifying
      vec2  block  = ceil(p+.5);               //index for blocks from which the fractal is shifted
      vec3  color  = rainbow(block.x*4.+block.y);  //rainbow palette using block index as t
      float dis    = length(fract(p+.5)*2.-1.);//distance to middle of block
            color *= .5+dis*.7;                    //using distance within block for some more pretty.
      
      return color;
  }

  void mainImage( out vec4 fragColor, in vec2 fragCoord ){
      
      vec2 d = vec2(.5,0);
      
      //some antialiasing
      vec3 col = (
          TwinDragon(fragCoord+d.xy)+
          TwinDragon(fragCoord-d.xy)+
          TwinDragon(fragCoord+d.yx)+
          TwinDragon(fragCoord-d.yx)
      )*.25;
      
    fragColor = vec4(col,1.);
      
  }

  void main() {
    mainImage(gl_FragColor, gl_FragCoord.xy);
  }
  `;
  const uniforms = {
    iTime: { value: 0 },
    iResolution:  { value: new THREE.Vector3() },
  };
  const material = new THREE.ShaderMaterial({
    fragmentShader,
    uniforms,
    side: THREE.DoubleSide
  });
  const mesh = new THREE.Mesh(plane, material)
  scene.add(mesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;  // convert to seconds



    resizeRendererToDisplaySize(renderer);

    const canvas = renderer.domElement;
    uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
    uniforms.iTime.value = time;

    mesh.rotation.y += 0.01

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();

</script>

But as you see, when I change to PerspectiveCamera, and rotate the plane, the texture does not transform with the object.

What's the simplest way to modify the example to make the shader transform with the plane, so that the graphic appears to be on the surface of the plane instead of the plane appearing to be like a mask?

1 Answers

The answer is to pass the uv coordinates from the vertex shader to the fragment shader using a varying variable.

Basically we can replace the lines

  const fragment = `
  ... clipped ...
  void main() {
    mainImage(gl_FragColor, gl_FragCoord.xy);
  }
  `;
  const uniforms = {
    iTime: { value: 0 },
    iResolution:  { value: new THREE.Vector3() },
  };
  const material = new THREE.ShaderMaterial({
    fragmentShader,

with

  const fragment = `
  ... clipped ...
  varying vec2 vUv;

  void main() {
    mainImage(gl_FragColor, vUv * iResolution.xy);
  }
  `;
  const vertexShader = `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
  `;
  const uniforms = {
    iTime: { value: 0 },
    iResolution:  { value: new THREE.Vector3() },
  };
  const material = new THREE.ShaderMaterial({
    vertexShader,
    fragmentShader,

and we get the following result:

html, body {
  height: 100%;
  margin: 0;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
  
<script type="module">
// Three.js - Shadertoy Basic
// from https://threejsfundamentals.org/threejs/threejs-shadertoy-basic.html


import * as THREE from 'https://unpkg.com/three@0.122.0/build/three.module.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});
  renderer.autoClearColor = false;

  const camera = new THREE.PerspectiveCamera(
    45, 16/9, 0.01, 1000
  );
  camera.position.z = 5
  const scene = new THREE.Scene();
  const plane = new THREE.PlaneBufferGeometry(2, 2);


  const fragmentShader = `
  #include <common>

  uniform vec3 iResolution;
  uniform float iTime;

  // https://www.shadertoy.com/view/MtXSWj

  float alternate(float p, float d){;
    return sign(fract(p*d*.5)*2.-1.);
  }

  vec3 rainbow(float t){
      return sin(t+vec3(0,.33,.66)*6.28)*.5+.5;
  }

  vec3 TwinDragon(vec2 p){
      float time       = fract(iTime*0.05)*20.;
      
      //scaling
      p = (p*2.-iResolution.xy)/iResolution.y*1.5;
      
      //----------the fractal stuff----   ---THIS IS ANIMATIONS----(so remove them if you want)
      p.y += alternate(p.x, 256. )/512. * clamp(time-16.,0.,2.)/2.;
      p.x -= alternate(p.y, 128. )/256. * clamp(time-14.,0.,2.)/2.;
      p.y += alternate(p.x,  64. )/128. * clamp(time-12.,0.,2.)/2.;
      p.x -= alternate(p.y,  32. )/ 64. * clamp(time-10.,0.,2.)/2.;
      p.y += alternate(p.x,  16. )/ 32. * clamp(time- 8.,0.,2.)/2.;
      p.x -= alternate(p.y,   8. )/ 16. * clamp(time- 6.,0.,2.)/2.;
      p.y += alternate(p.x,   4. )/  8. * clamp(time- 4.,0.,2.)/2.;
      p.x -= alternate(p.y,   2. )/  4. * clamp(time- 2.,0.,2.)/2.;

      // prettifying
      vec2  block  = ceil(p+.5);               //index for blocks from which the fractal is shifted
      vec3  color  = rainbow(block.x*4.+block.y);  //rainbow palette using block index as t
      float dis    = length(fract(p+.5)*2.-1.);//distance to middle of block
            color *= .5+dis*.7;                    //using distance within block for some more pretty.
      
      return color;
  }

  void mainImage( out vec4 fragColor, in vec2 fragCoord ){
      
      vec2 d = vec2(.5,0);
      
      //some antialiasing
      vec3 col = (
          TwinDragon(fragCoord+d.xy)+
          TwinDragon(fragCoord-d.xy)+
          TwinDragon(fragCoord+d.yx)+
          TwinDragon(fragCoord-d.yx)
      )*.25;
      
    fragColor = vec4(col,1.);
      
  }

  varying vec2 vUv;

  void main() {
    mainImage(gl_FragColor, vUv * iResolution.xy);
  }
  `;
  const vertexShader = `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
  `;
  const uniforms = {
    iTime: { value: 0 },
    iResolution:  { value: new THREE.Vector3() },
  };
  const material = new THREE.ShaderMaterial({
    vertexShader,
    fragmentShader,
    uniforms,
    side: THREE.DoubleSide
  });
  const mesh = new THREE.Mesh(plane, material)
  scene.add(mesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;  // convert to seconds



    resizeRendererToDisplaySize(renderer);

    const canvas = renderer.domElement;
    uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
    uniforms.iTime.value = time;

    mesh.rotation.y += 0.01

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();

</script>

Related