acroscene
acroscene

Reputation: 1067

Port shader with multiple buffers from shadertoy

Im trying to understand how to implement multiple buffers in three.js by porting a shader from shadertoy with help of this thread. https://discourse.threejs.org/t/help-porting-shadertoy-to-threejs/

I tried to rewrite it for js but it doesnt compile. Here is the code: https://codepen.io/haangglide/pen/BaKXmLX

It is based on this one: https://www.shadertoy.com/view/4sG3WV

My understanding of using buffers is:

  1. create a bufferscene

bufferAscene = new THREE.Scene();

  1. create a texture
textureA = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
     minFilter: THREE.LinearFilter,
     magFilter: THREE.NearestFilter
    });
  1. create a shadermaterial where you define the uniforms for passing to the shader
    bufferA = new THREE.ShaderMaterial({
        uniforms: {
            iFrame: { value: 0 },
            iResolution: { value: resolution },
            iMouse: { value: mousePosition },
            iChannel0: { value: textureA.texture },
            iChannel1: { value: textureB.texture }
        },
        vertexShader: VERTEX_SHADER,
        fragmentShader: BUFFER_A_FRAG,
    });
  1. create a PlaneBufferGeometry and create a mesh from the geometry and buffermaterial new THREE.Mesh(planeA, bufferA)

  2. add it to the Scene bufferAscene.add(new THREE.Mesh(planeA, bufferA));

In the render:

  1. update the uniforms bufferA.uniforms.iChannel0.value = textureA

I dont really understand the swap though.

If anyone can help me to get the application to compile it would be very much apreciated!

Upvotes: 5

Views: 2049

Answers (1)

Mugen87
Mugen87

Reputation: 31036

Here is a live example that ported the original TS code to JavaScript.

// Port from Shadertoy to THREE.js: https://www.shadertoy.com/view/4sG3WV

const VERTEX_SHADER = `
    varying vec2 vUv;
    
    void main() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
    }
`;

const BUFFER_A_FRAG = `
    uniform vec4 iMouse;
    uniform sampler2D iChannel0;
    uniform sampler2D iChannel1;
    uniform vec2 iResolution;
    uniform float iFrame;
    varying vec2 vUv;
    
    #define mousedata(a,b) texture2D( iChannel1, (0.5+vec2(a,b)) / iResolution.xy, -0.0 )
    #define backbuffer(uv) texture2D( iChannel0, uv ).xy
    
    float lineDist(vec2 p, vec2 start, vec2 end, float width) {
        vec2 dir = start - end;
        float lngth = length(dir);
        dir /= lngth;
        vec2 proj = max(0.0, min(lngth, dot((start - p), dir))) * dir;
        return length( (start - p) - proj ) - (width / 2.0);
    }
    
    void main() {
        vec2 uv = vUv;
        vec2 col = uv;
        if (iFrame > 2.) {
            col = texture2D(iChannel0,uv).xy;
            vec2 mouse = iMouse.xy/iResolution.xy;
            vec2 p_mouse = mousedata(2.,0.).xy;
            if (mousedata(4.,0.).x > 0.) {
                col = backbuffer(uv+((p_mouse-mouse)*clamp(1.-(lineDist(uv,mouse,p_mouse,0.)*20.),0.,1.)*.7));
            }
        }
        gl_FragColor = vec4(col,0.0,1.0);
    }

`;

const BUFFER_B_FRAG = `
    uniform vec4 iMouse;
    uniform sampler2D iChannel0;
    uniform vec3 iResolution;
    varying vec2 vUv;
    
    bool pixelAt(vec2 coord, float a, float b) {
        return (floor(coord.x) == a && floor(coord.y) == b);
    }
    
    vec4 backbuffer(float a,float b) {
      return texture2D( iChannel0, (0.5+vec2(a,b)) / iResolution.xy, -100.0 );
    }
    
    void main( ) {
    
        vec2 uv = vUv;// / iResolution.xy;
        vec4 color = texture2D(iChannel0,uv);
    
        if (pixelAt(gl_FragCoord.xy,0.,0.)) { //Surface position
            gl_FragColor = vec4(backbuffer(0.,0.).rg+(backbuffer(4.,0.).r*(backbuffer(2.,0.).rg-backbuffer(1.,0.).rg)),0.,1.);
        } else if (pixelAt(gl_FragCoord.xy,1.,0.)) { //New mouse position
            gl_FragColor = vec4(iMouse.xy/iResolution.xy,0.,1.);
        } else if (pixelAt(gl_FragCoord.xy,2.,0.)) { //Old mouse position
            gl_FragColor = vec4(backbuffer(1.,0.).rg,0.,1.);
        } else if (pixelAt(gl_FragCoord.xy,3.,0.)) { //New mouse holded
            gl_FragColor = vec4(clamp(iMouse.z,0.,1.),0.,0.,1.);
        } else if (pixelAt(gl_FragCoord.xy,4.,0.)) { //Old mouse holded
            gl_FragColor = vec4(backbuffer(3.,0.).r,0.,0.,1.);
        } else {
            gl_FragColor = vec4(0.,0.,0.,1.);
        }
    
    }
`;

const BUFFER_FINAL_FRAG = `
    uniform sampler2D iChannel0;
    uniform sampler2D iChannel1;
    varying vec2 vUv;
    
    void main() {
        vec2 uv = vUv;
        vec2 a = texture2D(iChannel1,uv).xy;
        gl_FragColor = vec4(texture2D(iChannel0,a).rgb,1.0);
    }
`;

class App {

  constructor() {

    this.width = 1024;
    this.height = 512;

    this.renderer = new THREE.WebGLRenderer();
    this.loader = new THREE.TextureLoader();
    this.mousePosition = new THREE.Vector4();
    this.orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    this.counter = 0;

    this.renderer.setSize(this.width, this.height);
    document.body.appendChild(this.renderer.domElement);

    this.renderer.domElement.addEventListener('mousedown', () => {
      this.mousePosition.setZ(1);
      this.counter = 0;
    });

    this.renderer.domElement.addEventListener('mouseup', () => {
      this.mousePosition.setZ(0);
    });

    this.renderer.domElement.addEventListener('mousemove', event => {
      this.mousePosition.setX(event.clientX);
      this.mousePosition.setY(this.height - event.clientY);
    });

    this.targetA = new BufferManager(this.renderer, {
      width: this.width,
      height: this.height
    });
    this.targetB = new BufferManager(this.renderer, {
      width: this.width,
      height: this.height
    });
    this.targetC = new BufferManager(this.renderer, {
      width: this.width,
      height: this.height
    });

  }


  start() {

    const resolution = new THREE.Vector3(this.width, this.height, window.devicePixelRatio);
    const channel0 = this.loader.load('https://res.cloudinary.com/di4jisedp/image/upload/v1523722553/wallpaper.jpg');
    this.loader.setCrossOrigin('');

    this.bufferA = new BufferShader(BUFFER_A_FRAG, {
      iFrame: {
        value: 0
      },
      iResolution: {
        value: resolution
      },
      iMouse: {
        value: this.mousePosition
      },
      iChannel0: {
        value: null
      },
      iChannel1: {
        value: null
      }
    });

    this.bufferB = new BufferShader(BUFFER_B_FRAG, {
      iFrame: {
        value: 0
      },
      iResolution: {
        value: resolution
      },
      iMouse: {
        value: this.mousePosition
      },
      iChannel0: {
        value: null
      }
    });

    this.bufferImage = new BufferShader(BUFFER_FINAL_FRAG, {
      iResolution: {
        value: resolution
      },
      iMouse: {
        value: this.mousePosition
      },
      iChannel0: {
        value: channel0
      },
      iChannel1: {
        value: null
      }
    });

    this.animate();

  }

  animate() {
    requestAnimationFrame(() => {

      this.bufferA.uniforms['iFrame'].value = this.counter++;

      this.bufferA.uniforms['iChannel0'].value = this.targetA.readBuffer.texture;
      this.bufferA.uniforms['iChannel1'].value = this.targetB.readBuffer.texture;
      this.targetA.render(this.bufferA.scene, this.orthoCamera);

      this.bufferB.uniforms['iChannel0'].value = this.targetB.readBuffer.texture;
      this.targetB.render(this.bufferB.scene, this.orthoCamera);

      this.bufferImage.uniforms['iChannel1'].value = this.targetA.readBuffer.texture;
      this.targetC.render(this.bufferImage.scene, this.orthoCamera, true);

      this.animate();

    });

  }

}

class BufferShader {

  constructor(fragmentShader, uniforms = {}) {

    this.uniforms = uniforms;
    this.material = new THREE.ShaderMaterial({
      fragmentShader: fragmentShader,
      vertexShader: VERTEX_SHADER,
      uniforms: uniforms
    });
    this.scene = new THREE.Scene();
    this.scene.add(
      new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material)
    );
  }

}

class BufferManager {


  constructor(renderer, size) {

    this.renderer = renderer;

    this.readBuffer = new THREE.WebGLRenderTarget(size.width, size.height, {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
      format: THREE.RGBAFormat,
      type: THREE.FloatType,
      stencilBuffer: false
    });

    this.writeBuffer = this.readBuffer.clone();

  }

  swap() {
    const temp = this.readBuffer;
    this.readBuffer = this.writeBuffer;
    this.writeBuffer = temp;
  }

  render(scene, camera, toScreen = false) {
    if (toScreen) {
      this.renderer.render(scene, camera);
    } else {
      this.renderer.setRenderTarget(this.writeBuffer);
      this.renderer.clear();
      this.renderer.render(scene, camera)
      this.renderer.setRenderTarget(null);
    }
    this.swap();
  }

}

document.addEventListener('DOMContentLoaded', () => {
  (new App()).start();
});
body {
      margin: 0;
}

canvas {
    display: block;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.js"></script>

Upvotes: 13

Related Questions