javascript - shaders - toy shader
sombreador de fragmentos threejs utilizando memorias intermedias de marcos reciclados (3)
Intento crear una aplicación que simule una fotografía de larga exposición. La idea es que agarre el marco actual de la cámara web y lo componga en un lienzo. Con el tiempo, la foto se ''expondrá'', haciéndose más y más brillante. (ver http://www.chromeexperiments.com/detail/light-paint-live-mercury/?f= )
Tengo un sombreador que funciona a la perfección. Es como el modo de combinación ''agregar'' en photoshop. El problema es que no puedo conseguir que recicle el cuadro anterior.
Pensé que sería algo simple como renderer.autoClear = false;
pero esa opción parece no hacer nada en este contexto.
Aquí está el código que usa THREE.EffectComposer para aplicar el sombreador.
onWebcamInit: function () {
var $stream = $("#user-stream"),
width = $stream.width(),
height = $stream.height(),
near = .1,
far = 10000;
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
this.renderer.autoClear = false;
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, near, far);
this.scene.add(this.camera);
this.$el.append(this.renderer.domElement);
this.frameTexture = new THREE.Texture(document.querySelector("#webcam"));
this.compositeTexture = new THREE.Texture(this.renderer.domElement);
this.composer = new THREE.EffectComposer(this.renderer);
// same effect with or without this line
// this.composer.addPass(new THREE.RenderPass(this.scene, this.camera));
var addEffect = new THREE.ShaderPass(addShader);
addEffect.uniforms[ ''exposure'' ].value = .5;
addEffect.uniforms[ ''frameTexture'' ].value = this.frameTexture;
addEffect.renderToScreen = true;
this.composer.addPass(addEffect);
this.plane = new THREE.Mesh(new THREE.PlaneGeometry(width, height, 1, 1), new THREE.MeshBasicMaterial({map: this.compositeTexture}));
this.scene.add(this.plane);
this.frameTexture.needsUpdate = true;
this.compositeTexture.needsUpdate = true;
new FrameImpulse(this.renderFrame);
},
renderFrame: function () {
this.frameTexture.needsUpdate = true;
this.compositeTexture.needsUpdate = true;
this.composer.render();
}
Aquí está el sombreador. Nada sofisticado.
uniforms: {
"tDiffuse": { type: "t", value: null },
"frameTexture": { type: "t", value: null },
"exposure": { type: "f", value: 1.0 }
},
vertexShader: [
"varying vec2 vUv;",
"void main() {",
"vUv = uv;",
"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("/n"),
fragmentShader: [
"uniform sampler2D frameTexture;",
"uniform sampler2D tDiffuse;",
"uniform float exposure;",
"varying vec2 vUv;",
"void main() {",
"vec4 n = texture2D(frameTexture, vUv);",
"vec4 o = texture2D(tDiffuse, vUv);",
"vec3 sum = n.rgb + o.rgb;",
"gl_FragColor = vec4(mix(o.rgb, sum.rgb, exposure), 1.0);",
"}"
].join("/n")
Esto es, en esencia, equivalente a la respuesta de posit labs, pero he tenido éxito con una solución más simplificada: creo un EffectComposer solo con ShaderPass que quiero reciclar, luego cambio renderTargets para ese compositor con cada renderizado.
Inicialización:
THREE.EffectComposer.prototype.swapTargets = function() {
var tmp = this.renderTarget2;
this.renderTarget2 = this.renderTarget1;
this.renderTarget1 = tmp;
};
...
composer = new THREE.EffectComposer(renderer,
new THREE.WebGLRenderTarget(512, 512, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat })
);
var addEffect = new THREE.ShaderPass(addShader, ''frameTexture'');
addEffect.renderToScreen = true;
this.composer.addPass(addEffect);
hacer:
composer.render();
composer.swapTargets();
Un EffectComposer secundario puede tomar uno de los dos renderTargets y empujarlo a la pantalla o transformarlo más.
También tenga en cuenta que declaro "frameTexture" como textureID al inicializar ShaderPass. Esto le permite a ShaderPass saber actualizar la textura frameTexture con los resultados del pase anterior.
Para lograr este tipo de efecto de retroalimentación, debe alternar la escritura en instancias separadas de WebGLRenderTarget
. De lo contrario, el búfer de cuadros se sobrescribe. No estoy totalmente seguro de por qué sucede esto ... pero aquí está la solución.
en eso:
this.rt1 = new THREE.WebGLRenderTarget(512, 512, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat });
this.rt2 = new THREE.WebGLRenderTarget(512, 512, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat });
hacer:
this.renderer.render(this.scene, this.camera);
this.renderer.render(this.scene, this.camera, this.rt1, false);
// swap buffers
var a = this.rt2;
this.rt2 = this.rt1;
this.rt1 = a;
this.shaders.add.uniforms.tDiffuse.value = this.rt2;
Prueba con esto:
this.renderer = new THREE.WebGLRenderer( { preserveDrawingBuffer: true } );