Three.js Updating DataTextures with a renderTarget's image data

久未见 提交于 2019-12-11 23:49:46

问题


I am working on a motion detection program in Three.js which uses the difference between the current and previous frame. For now, before the subtraction, the current and the previous frame are both blurred using a Three.EffectComposer each.

The main problem is: Instead of having to blur the previous frame again, I want to use the previously blurred "current" frame as the texture in the subtraction process.

The closest I have managed to do is by using the function below to update the image.data of the Three.DataTexture. It was used in the render()-function after the Blurring composer is rendered, but before the subtraction is rendered. Both of them were rendered to the screen with Three.CopyShader.

function getData(image) {
    var canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;

    var context = canvas.getContext('2d');
    context.drawImage(image, 0, 0);
    return  new Uint8Array(context.getImageData(0, 0, image.width, image.height).data);
}

Where the “image” is the renderer.domElement. This method feels quite inefficient and I needed to render the Blur-pass to the screen, which caused the result to flicker.

Edit 1 The current code is shown below, it blurs the current and previous images and then calculates the difference. The animate()-function is the point of interest.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Three.js Webcam Test</title>
    <meta charset="utf-8">
   <!-- <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
    <script src="lib/three.min.js"></script>

    <!-- Effect composer scripts -->
    <script src = "postprocessing/EffectComposer.js"></script>
    <script src = "postprocessing/MaskPass.js"></script>
    <script src = "postprocessing/RenderPass.js"></script>
    <script src = "postprocessing/TexturePass.js"></script>
    <script src = "postprocessing/ShaderPass.js"></script>
    <script src = "postprocessing/SavePass.js"></script>

    <script src = "shaders/CopyShader.js"></script>
    <script src = "shaders/ColorifyShader.js"></script>
    <script src = "shaders/DilationShader.js"></script>
    <script src = "shaders/ErosionShader.js"></script>
    <script src = "shaders/HorizontalBlurShader.js"></script>
    <script src = "shaders/VerticalBlurShader.js"></script>
    <script src = "shaders/BlendShader.js"></script>
    <script src = "shaders/passThroughShader.js"></script>
    <script src = "shaders/framediffShader.js"></script>
    <script src = "shaders/PawaskarPostShader.js"></script>
    <!-- ----------------------- -->

    <script src="lib/Projector.js"></script>
    <script src="lib/CanvasRenderer.js"></script>
    <script src="lib/webcam.js"></script>
    <script src="lib/perspective.js"></script>
    <script src="lib/stats.min.js"></script>
    <script src="lib/rStats.js"></script>
    <script src="lib/rStats.extras.js"></script>
    <script type="text/javascript" src="lib/dat.gui.min.js"></script>

    <link href="css/style.css" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="http://bootswatch.com/lumen/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap-theme.min.css">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>

</head>
<body>

    <h1>Motion Detection (in progress)</h1>
        <p>Press P to print the current frame</p>
<div id="WebGL-output">
</div>

<div id="camera" class="camera">
    <div class="label"></div>
    <video id="theVideo" autoplay width="640" height="480" class="webcam"></video>
    <canvas id="theCanvas"  width="640" height="480" class="hidden"></canvas>
</div>

<script>
    var SCREEN_HEIGHT = 480;
    var SCREEN_WIDTH = 640;

    var values = {
        detectmotion: true,
        softness: 0.17,
        threshold: 0.11,
        color: "#ffae23",
        usecolor: false,
        postprocess: false,
        postprocessmethod: 0,
        preprocess: true,
        detectedges: false,
        framedifference: false,
        binarydifference: false,
        bufferlength: 1
    };

    var stats, container, video, renderer, currTexture, uniforms, camera, scene, prevTexture, prevTexture2, prevTexture3, prevTextureBuffer = [], bufferLenght,
        videoContext,
        prevTime;
    var rS, glS, tS;
    var postProcessFilters = [];
    var prepScene, prepRenderTarget, prepComposer, prepPrevComposer, hBlur, vBlur, temporalShader, prevTemporalShader, prevBlur;
    var modelScene, modelRenderTarget, modelComposer, passShader;
    var subtractScene, subtractRenderTarget,subtractComposer, subtractShader;
    //GUI variables
    var gui, cPostProcessMethod, doPostProcess = false;
    var frameNumber;
    /** TEST **/
    var BlurSave;

    function init(){

        frameNumber = 0;
        /* INIT */

        scene = new THREE.Scene();

        camera = new THREE.Camera();
        scene.add(camera);

        webcam.updateSources(function(s){
            webcam.start('theVideo',s[0]);
        });

        var size = SCREEN_WIDTH * SCREEN_HEIGHT;

        video = document.getElementById( 'theVideo' );
        videoContext = document.getElementById('theCanvas').getContext('2d');

        //The textures
        currTexture = new THREE.DataTexture([],SCREEN_WIDTH,SCREEN_HEIGHT);
        prevTexture = new THREE.DataTexture([],SCREEN_WIDTH,SCREEN_HEIGHT);
        prevBlur = new THREE.DataTexture([], SCREEN_WIDTH, SCREEN_HEIGHT);
        currTexture.minFilter = prevTexture.minFilter = prevBlur.minFilter=  THREE.LinearFilter;

        prevTime = -1;

        renderer = new THREE.WebGLRenderer();
        renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
        renderer.domElement.width = SCREEN_WIDTH;
        renderer.domElement.height = SCREEN_HEIGHT;
        renderer.autoClear = false;
        document.body.insertBefore(renderer.domElement, document.body.childNodes[0]);

        uniforms = {
            currentTexture:   { type: "t", value: currTexture },
            mirrorImage:       { type: "i", value: 0}
        }

        var geometry = new THREE.PlaneBufferGeometry(1, 1);
        var material = new THREE.ShaderMaterial( {
            uniforms: uniforms,
            vertexShader: THREE.passThroughShader.vertexShader,
            fragmentShader: THREE.passThroughShader.fragmentShader

        } );

        // A plane with the current video context as texture
        var mesh = new THREE.Mesh(geometry,material);
        mesh.material.depthTest = false;
        mesh.material.depthWrite = false;
        scene.add(mesh);

        // COPY SHADER, used to render the current context to the screen
        var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
        effectCopy.renderToScreen = true;

        /** Preprocess stage **/

        prepScene = new THREE.Scene();
        prepScene.add( new THREE.AmbientLight( 0xffffff ) );
        prepScene.add(mesh) // add the current quad

        var renderTargetParameters = {minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false};

        //blur shaders
        hBlur = new THREE.ShaderPass(THREE.HorizontalBlurShader);
        hBlur.uniforms["h"].value = 1 / SCREEN_WIDTH;
        hBlur.enabled = values['preprocess'];

        vBlur = new THREE.ShaderPass(THREE.VerticalBlurShader);
        vBlur.uniforms["v"].value = 1 / SCREEN_HEIGHT;
        vBlur.enabled = values['preprocess'];

        BlurSave = new THREE.SavePass(new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters));

        //preprocess scene render pass
        var renderModelPrep = new THREE.RenderPass(prepScene, camera);
        var prevPassShader1 = new THREE.ShaderPass(THREE.passThroughShader);
        prevPassShader1.uniforms["mirrorImage"].value = 1;

        //Preprocess of the current image
        //It is this prepComposer's rendertarget value I want to use in the next loop
        prepComposer = new THREE.EffectComposer(renderer, new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters));
        prepComposer.addPass(renderModelPrep);
        prepComposer.addPass(prevPassShader1);
        prepComposer.addPass(hBlur);
        prepComposer.addPass(vBlur);
        prepComposer.addPass(BlurSave);

        //
       // subtractComposer.addPass(effectCopy);

        //Preprocess of the previous image
        //Want to skip this stage
        var prevPassShader = new THREE.ShaderPass(THREE.passThroughShader, prevTexture);
        prevPassShader.uniforms["currentTexture"].value = prevTexture;
        prevPassShader.uniforms["mirrorImage"].value = 1;
        var prevBlurSave = new THREE.SavePass(new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters));

        prepPrevComposer = new THREE.EffectComposer(renderer, new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters));
        prepPrevComposer.addPass(renderModelPrep);
        prepPrevComposer.addPass(prevPassShader);
        prepPrevComposer.addPass(hBlur);
        prepPrevComposer.addPass(vBlur);
        prepPrevComposer.addPass(prevBlurSave);

        /**------------------**/

        /**---------------------------**/

        /** Background Subtraction stage **/

        subtractScene = new THREE.Scene();
        subtractScene.add( new THREE.AmbientLight( 0xffffff ) );

        var renderTargetParameters3 = {minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, stencilBuffer: false};

        //Background Subtraction shaders
        subtractShader = new THREE.ShaderPass(THREE.framediffShader);
        subtractShader.uniforms['currentTexture'].value = BlurSave.renderTarget; // from the preprocess
        subtractShader.uniforms['previousTexture'].value = prevBlurSave.renderTarget; //modelled background

        //Background subtraction scene render pass
        var renderSubtract = new THREE.RenderPass(subtractScene, camera);

        //Background subtraction Composer
        subtractComposer = new THREE.EffectComposer(renderer, new THREE.WebGLRenderTarget(SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters3));
        subtractComposer.addPass(renderSubtract);
        subtractComposer.addPass(subtractShader);

        //subtractComposer.addPass(effectCopy2);

        /**------------------------------**/

        /** Postprocessing stage **/

        //Dilation
        var dilationFilter = new THREE.ShaderPass(THREE.DilationShader);
        dilationFilter.enabled = values['postprocess'];
        postProcessFilters.push(dilationFilter);
        //Erosion
        var erosionFilter = new THREE.ShaderPass(THREE.ErosionShader);
        erosionFilter.enabled = values['postprocess'];
        postProcessFilters.push(erosionFilter);

        //Pawaskar's postprocess filter
        var pawaskarFilter = new THREE.ShaderPass(THREE.PawaskarPostShader);
        pawaskarFilter.uniforms['threshold'].value = values['threshold'];
        pawaskarFilter.enabled = values['postprocess'];
        postProcessFilters.push(pawaskarFilter);

        subtractComposer.addPass(pawaskarFilter);

        //Opening
        subtractComposer.addPass(erosionFilter);
        subtractComposer.addPass(dilationFilter);
        //Closing
        subtractComposer.addPass(dilationFilter);
        subtractComposer.addPass(erosionFilter);

        //The final result rendered to the screen
        subtractComposer.addPass(effectCopy);

        /**----------------------**/

        animate();

    }

    function animate()
    {

        if(video.readyState === video.HAVE_ENOUGH_DATA ){
            var time = video.currentTime;
            if(time !== prevTime){
                //Because a firefox bug when drawImage is used, need to catch NS_ERROR_NOT_AVAILABLE
                try {

                    videoContext.drawImage(video, 0, 0,SCREEN_WIDTH,SCREEN_HEIGHT); //update the video

                    if(currTexture.image.data.length){

                        //var imgData = getData(renderer.domElement);
                        //var imgData = renderer.domElement.toDataURL();

                       // var gl = renderer.getContext();
                        //gl.readPixels( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, gl.RGBA, gl.UNSIGNED_BYTE, prevBlur.image.data );

                       //prevBlur.image.data = imgData;
                       // prevBlur.needsUpdate = true;

                        /** I want to update the prevBlur texture with the BlurSave.renderTarget! **/

                        prevTexture.image.data = currTexture.image.data;
                        prevTexture.needsUpdate = true; //updates the previous texture in the shader
                    }

                    currTexture.image.data = new Uint8Array(videoContext.getImageData(0,0,SCREEN_WIDTH, SCREEN_HEIGHT).data);
                    currTexture.needsUpdate = true; //updates the current texture in the shader
                    prevTime = time;

                }catch (e) {
                    if (e.name == "NS_ERROR_NOT_AVAILABLE") {
                        console.error(e);
                    } else {
                        throw e;
                    }
                }
            }
        }


        prepComposer.render(0.05);
        prepPrevComposer.render(0.05);
        subtractComposer.render(0.05);

        frameNumber++;
        requestAnimationFrame(animate);
    }

     function getData(image) {
        var canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;

        var context = canvas.getContext('2d');
        context.drawImage(image, 0, 0);

        return new Uint8Array(context.getImageData(0, 0, image.width, image.height).data);
    }


    function copyCanvas(e) {
        var imgData, imgNode;
        if (e.which !== 80) {
            return;
        } else {
            imgData = renderer.domElement.toDataURL();
        }
        // create a new image and add to the document
        imgNode = document.createElement("img");
        imgNode.src = imgData;
        document.body.appendChild(imgNode);
    }


    window.onload = init;

    window.addEventListener("keyup", copyCanvas);

</script>

</body>
</html>
  • How can I update the prevBlur.image.data with the current image data of the BlurSave.rendertarget?

  • Are there any other way to update a shader's Sampler2D uniform with the value from a WebGLRenderTarget’s image data from a previous time-step?


回答1:


If you want to post-process your scene with a subtraction shader that uses the difference between the current and previous frames, you can do something like the following:

First create two render targets rt1 and rt2. Set currentRT = rt1 and prevRT = rt2.

Then in your render loop, (1) render to currentRT, then (2) pass currentRT and prevRT as uniforms to your subtraction shader and render to the screen, then (3) swap the render targets.

three.js r.70



来源:https://stackoverflow.com/questions/28943424/three-js-updating-datatextures-with-a-rendertargets-image-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!