Sombreador de puertos con múltiples búferes de shadowrtoy

Oct 03 2020

Estoy tratando de entender cómo implementar múltiples búferes en three.js portando un sombreador de shadowrtoy con la ayuda de este hilo. https://discourse.threejs.org/t/help-porting-shadertoy-to-threejs/

Intenté reescribirlo para js pero no se compila. Aquí está el código:https://codepen.io/haangglide/pen/BaKXmLX

Se basa en este: https://www.shadertoy.com/view/4sG3WV

Mi comprensión del uso de búferes es:

  1. crear una escena de búfer

bufferAscene = new THREE.Scene();

  1. crear una textura
textureA = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
     minFilter: THREE.LinearFilter,
     magFilter: THREE.NearestFilter
    });
  1. Cree un material de sombreado donde defina los uniformes para pasar al sombreador
    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. crear un PlaneBufferGeometry y crear una malla a partir de la geometría y el material de amortiguación new THREE.Mesh(planeA, bufferA)

  2. agregarlo a la escena bufferAscene.add(new THREE.Mesh(planeA, bufferA));

En el render:

  1. actualizar los uniformes bufferA.uniforms.iChannel0.value = textureA

Sin embargo, realmente no entiendo el cambio.

Si alguien puede ayudarme a compilar la aplicación, ¡sería muy apreciado!

Respuestas

3 Mugen87 Oct 04 2020 at 11:13

Aquí hay un ejemplo en vivo que transfirió el código TS original a 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>