【问题标题】:threejs fragment shader using recycled frame buffers使用回收帧缓冲区的threejs片段着色器
【发布时间】:2013-11-21 06:11:20
【问题描述】:

我正在尝试制作一个模拟长时间曝光摄影的应用。这个想法是我从网络摄像头中抓取当前帧并将其合成到画布上。随着时间的推移,照片会“曝光”,变得越来越亮。 (见http://www.chromeexperiments.com/detail/light-paint-live-mercury/?f=)

我有一个完美运行的着色器。这就像 Photoshop 中的“添加”混合模式。问题是我无法让它回收前一帧。

我认为它会像 renderer.autoClear = false; 这样简单,但在这种情况下,该选项似乎没有任何作用。

这是使用 THREE.EffectComposer 应用着色器的代码。

        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();
        }

这里是着色器。没什么特别的。

        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")

【问题讨论】:

  • This discussion 似乎表明您应该像这样创建 WebGLRenderer:new WebGLRenderer({ preserveDrawingBuffer: true }),并将 renderer.autoClearColor 设置为 false。
  • 这将用于在几何体上创建效果(如运动模糊),但它对几何体上的实际纹理没有影响。这就是我要瞄准的目标。

标签: javascript three.js webgl fragment-shader


【解决方案1】:

这在本质上等同于 posit labs 的答案,但我已经通过更简化的解决方案取得了成功 - 我创建了一个只包含我想要回收的 ShaderPass 的 EffectComposer,然后在每次渲染时为该 Composer 交换 renderTargets。

初始化:

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);

渲染:

composer.render();
composer.swapTargets();

然后,辅助 EffectComposer 可以获取两个 renderTargets 之一并将其推送到屏幕或进一步转换它。

还请注意,我在初始化 ShaderPass 时将“frameTexture”声明为 textureID。这让 ShaderPass 知道用上一个 Pass 的结果更新 frameTexture 制服。

【讨论】:

【解决方案2】:

要实现这种反馈效果,您必须交替写入WebGLRenderTarget 的单独实例。否则,帧缓冲区将被覆盖。不完全确定为什么会发生这种情况......但这是解决方案。

初始化:

    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 });

渲染:

    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;

【讨论】:

    【解决方案3】:

    试试这个:

    this.renderer = new THREE.WebGLRenderer( { preserveDrawingBuffer: true } );
    

    【讨论】:

    • 感谢您的回复,doob。不过,恐怕这对我不起作用。我作为答案发布的渲染到纹理技术效果很好......但我仍然不确定它为什么会这样。
    猜你喜欢
    • 2011-05-21
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多