使用d3-zoom与WebGL交互

时间:2017-02-23 03:42:59

标签: javascript d3.js html5-canvas webgl zoom

我试图让一个小例子一起使用d3-zoom为使用WebGL渲染的canvas元素提供简单的交互性。我想做的就是提供平移/缩放,使用4x4转换矩阵非常简单。

我遇到的问题是缩放(缩放)。如果您查看一些d3缩放示例,您会看到缩放焦点始终位于鼠标的位置。

如果直接使用缩放变换中的ktxty值,则平移有效,但缩放会偏移画布宽度和高度的一半,见



var width = 300,
        height = 150;

    var zoom = d3.zoom()
        .on( 'zoom', zoomed );

    var canvas = d3.select( 'body' )
        .append( 'canvas' )
        .attr( 'width', width )
        .attr( 'height', height )
        .call( zoom );

    var gl = canvas.node().getContext( 'webgl' );
    var shader = basic_shader(gl);

    initialize_gl();
    set_transform( 1, 0, 0 );

    function zoomed () {
        var t = d3.event.transform;
        set_transform( t.k, t.x, t.y );
    }

    function initialize_gl () {

        var sb = d3.color('steelblue');
        gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        var vertices = [
            0.5, 0.5, 0.0, 1.0,
            -0.5, 0.5, 0.0, 1.0,
            0.5, -0.5, 0.0, 1.0,
            -0.5, -0.5, 0.0, 1.0
        ];

        var colors = [
            1.0, 1.0, 1.0, 1.0,    // white
            1.0, 0.0, 0.0, 1.0,    // red
            0.0, 1.0, 0.0, 1.0,    // green
            0.0, 0.0, 1.0, 1.0     // blue
        ];

        var vertex_buffer = gl.createBuffer();
        var color_buffer = gl.createBuffer();

        gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);

    }

    function set_transform ( k, tx, ty ) {

        var matrix = new Float32Array([
            k, 0, 0, 0,
            0, k, 0, 0,
            0, 0, 1, 0,
            2*tx/width, -2*ty/height, 0, 1
        ]);

        gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
        gl.clear( gl.COLOR_BUFFER_BIT );
        gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );

    }

    function basic_vertex () {

        return [
            'attribute vec4 vertex_position;',
            'attribute vec4 vertex_color;',
            'varying lowp vec4 vert_color;',
            'uniform mat4 matrix;',
            'void main( void ) {',
            '   gl_Position = matrix * vertex_position;',
            '   vert_color = vertex_color;',
            '}'
        ].join('\n');

    }

    function basic_fragment () {

        return [
            'varying lowp vec4 vert_color;',
            'void main( void ) {',
            '   gl_FragColor = vert_color;',
            '}'
        ].join('\n');

    }

    function basic_shader ( gl ) {

        var program = gl_program( gl, basic_vertex(), basic_fragment() );

        gl.useProgram( program );
        program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
        program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
        program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
        program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
        program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
        gl.enableVertexAttribArray( program.vertex_attrib );
        gl.enableVertexAttribArray( program.color_attrib );

        return program;

    }

    function gl_shader ( gl, type, code ) {

        var shader = gl.createShader( type );
        gl.shaderSource( shader, code );
        gl.compileShader( shader );
        return shader;

    }

    function gl_program ( gl, vertex_source, fragment_source ) {

        var shader_program = gl.createProgram();
        var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
        var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );

        if ( shader_program && vertex_shader && fragment_shader ) {

            gl.attachShader( shader_program, vertex_shader );
            gl.attachShader( shader_program, fragment_shader );
            gl.linkProgram( shader_program );

            gl.deleteShader( vertex_shader );
            gl.deleteShader( fragment_shader );

            return shader_program;

        }

    }

<script src="https://d3js.org/d3.v4.min.js"></script>
&#13;
&#13;
&#13;

我的预感是,这与以下事实有关:在WebGL中,视口x和y坐标各自从-1到1,而d3-zoom使用canvas元素中的鼠标坐标,当标准化时可以在0到1的范围内。

如果将鼠标放在画布的左上角(画布坐标中的(0,0))并尝试缩放,则可以看到这种情况。它将缩放,就好像鼠标位于画布的中心(WebGL坐标中的(0,0))。

为了解决这个问题,你可以从x平移中减1(即坐标系宽度的一半[-1,1])并加1(即坐标系高度的一半[-1,1] ])到y翻译,如图所示

&#13;
&#13;
    var width = 300,
        height = 150;

    var zoom = d3.zoom()
        .on( 'zoom', zoomed );

    var canvas = d3.select( 'body' )
        .append( 'canvas' )
        .attr( 'width', width )
        .attr( 'height', height )
        .call( zoom );

    var gl = canvas.node().getContext( 'webgl' );
    var shader = basic_shader(gl);

    initialize_gl();
    set_transform( 1, 0, 0 );

    function zoomed () {
        var t = d3.event.transform;
        set_transform( t.k, t.x, t.y );
    }

    function initialize_gl () {

        var sb = d3.color('steelblue');
        gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        var vertices = [
            0.5, 0.5, 0.0, 1.0,
            -0.5, 0.5, 0.0, 1.0,
            0.5, -0.5, 0.0, 1.0,
            -0.5, -0.5, 0.0, 1.0
        ];

        var colors = [
            1.0, 1.0, 1.0, 1.0,    // white
            1.0, 0.0, 0.0, 1.0,    // red
            0.0, 1.0, 0.0, 1.0,    // green
            0.0, 0.0, 1.0, 1.0     // blue
        ];

        var vertex_buffer = gl.createBuffer();
        var color_buffer = gl.createBuffer();

        gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);

    }

    function set_transform ( k, tx, ty ) {

        var matrix = new Float32Array([
            k, 0, 0, 0,
            0, k, 0, 0,
            0, 0, 1, 0,
            2*tx/width-1.0, -2*ty/height+1.0, 0, 1
        ]);

        gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
        gl.clear( gl.COLOR_BUFFER_BIT );
        gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );

    }

    function basic_vertex () {

        return [
            'attribute vec4 vertex_position;',
            'attribute vec4 vertex_color;',
            'varying lowp vec4 vert_color;',
            'uniform mat4 matrix;',
            'void main( void ) {',
            '   gl_Position = matrix * vertex_position;',
            '   vert_color = vertex_color;',
            '}'
        ].join('\n');

    }

    function basic_fragment () {

        return [
            'varying lowp vec4 vert_color;',
            'void main( void ) {',
            '   gl_FragColor = vert_color;',
            '}'
        ].join('\n');

    }

    function basic_shader ( gl ) {

        var program = gl_program( gl, basic_vertex(), basic_fragment() );

        gl.useProgram( program );
        program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
        program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
        program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
        program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
        program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
        gl.enableVertexAttribArray( program.vertex_attrib );
        gl.enableVertexAttribArray( program.color_attrib );

        return program;

    }

    function gl_shader ( gl, type, code ) {

        var shader = gl.createShader( type );
        gl.shaderSource( shader, code );
        gl.compileShader( shader );
        return shader;

    }

    function gl_program ( gl, vertex_source, fragment_source ) {

        var shader_program = gl.createProgram();
        var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
        var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );

        if ( shader_program && vertex_shader && fragment_shader ) {

            gl.attachShader( shader_program, vertex_shader );
            gl.attachShader( shader_program, fragment_shader );
            gl.linkProgram( shader_program );

            gl.deleteShader( vertex_shader );
            gl.deleteShader( fragment_shader );

            return shader_program;

        }

    }
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
&#13;
&#13;
&#13;

然而,通过执行偏移,您的场景最初被翻译,这不是完全理想的。所以我的问题是,处理这个问题的最佳方法是什么?最好由d3方面还是WebGL方面处理?

1 个答案:

答案 0 :(得分:3)

我只是移动了顶点以匹配矩阵

    var vertices = [
         .5,  -.5, 0.0, 1.0,
        1.5,  -.5, 0.0, 1.0,
         .5, -1.5, 0.0, 1.0,
        1.5, -1.5, 0.0, 1.0
    ];      

&#13;
&#13;
var width = 300,
        height = 150;

    var zoom = d3.zoom()
        .on( 'zoom', zoomed );

    var canvas = d3.select( 'body' )
        .append( 'canvas' )
        .attr( 'width', width )
        .attr( 'height', height )
        .call( zoom );

    var gl = canvas.node().getContext( 'webgl' );
    var shader = basic_shader(gl);

    initialize_gl();
    set_transform( 1, 0, 0 );

    function zoomed () {
        var t = d3.event.transform;
        set_transform( t.k, t.x, t.y );
    }

    function initialize_gl () {

        var sb = d3.color('steelblue');
        gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        var vertices = [
             .5,  -.5, 0.0, 1.0,
            1.5,  -.5, 0.0, 1.0,
             .5, -1.5, 0.0, 1.0,
            1.5, -1.5, 0.0, 1.0
        ];        

        var colors = [
            1.0, 1.0, 1.0, 1.0,    // white
            1.0, 0.0, 0.0, 1.0,    // red
            0.0, 1.0, 0.0, 1.0,    // green
            0.0, 0.0, 1.0, 1.0     // blue
        ];

        var vertex_buffer = gl.createBuffer();
        var color_buffer = gl.createBuffer();

        gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);

    }

    function set_transform ( k, tx, ty ) {

        var matrix = new Float32Array([
            k, 0, 0, 0,
            0, k, 0, 0,
            0, 0, 1, 0,
            2*tx/width-1.0, -2*ty/height+1.0, 0, 1
        ]);

        gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
        gl.clear( gl.COLOR_BUFFER_BIT );
        gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );

    }

    function basic_vertex () {

        return [
            'attribute vec4 vertex_position;',
            'attribute vec4 vertex_color;',
            'varying lowp vec4 vert_color;',
            'uniform mat4 matrix;',
            'void main( void ) {',
            '   gl_Position = matrix * vertex_position;',
            '   vert_color = vertex_color;',
            '}'
        ].join('\n');

    }

    function basic_fragment () {

        return [
            'varying lowp vec4 vert_color;',
            'void main( void ) {',
            '   gl_FragColor = vert_color;',
            '}'
        ].join('\n');

    }

    function basic_shader ( gl ) {

        var program = gl_program( gl, basic_vertex(), basic_fragment() );

        gl.useProgram( program );
        program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
        program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
        program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
        program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
        program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
        gl.enableVertexAttribArray( program.vertex_attrib );
        gl.enableVertexAttribArray( program.color_attrib );

        return program;

    }

    function gl_shader ( gl, type, code ) {

        var shader = gl.createShader( type );
        gl.shaderSource( shader, code );
        gl.compileShader( shader );
        return shader;

    }

    function gl_program ( gl, vertex_source, fragment_source ) {

        var shader_program = gl.createProgram();
        var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
        var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );

        if ( shader_program && vertex_shader && fragment_shader ) {

            gl.attachShader( shader_program, vertex_shader );
            gl.attachShader( shader_program, fragment_shader );
            gl.linkProgram( shader_program );

            gl.deleteShader( vertex_shader );
            gl.deleteShader( fragment_shader );

            return shader_program;

        }

    }
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
&#13;
&#13;
&#13;

但说实话,我可能会使用数学库并使用一些变换。我更容易理解代码。我不确定&#34;空间&#34; D3。我想虽然它只是传递了一个偏移和一个比例。在这种情况下

        // change the space to be pixels with 0,0 in top left
        var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);

        // apply the d3 translate and zoom
        matrix = m4.translate(matrix, [tx, ty, 0]);
        matrix = m4.scale(matrix, [k, k, 1]);

        // translate the unit quad to the center 
        matrix = m4.translate(matrix, [width / 2, height / 2, 0]);

        // make the unit quad be half the size of the canvas
        matrix = m4.scale(matrix, [width / 2, height / 2 , 1]);

&#13;
&#13;
var m4 = twgl.m4;
    var width = 300,
        height = 150;

    var zoom = d3.zoom()
        .on( 'zoom', zoomed );

    var canvas = d3.select( 'body' )
        .append( 'canvas' )
        .attr( 'width', width )
        .attr( 'height', height )
        .call( zoom );

    var gl = canvas.node().getContext( 'webgl' );
    var shader = basic_shader(gl);

    initialize_gl();
    set_transform( 1, 0, 0 );

    function zoomed () {
        var t = d3.event.transform;
        set_transform( t.k, t.x, t.y );
    }

    function initialize_gl () {

        var sb = d3.color('steelblue');
        gl.clearColor(sb.r / 255, sb.g / 255, sb.b / 255, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);

        var vertices = [
            -.5,   .5, 0.0, 1.0,
             .5,   .5, 0.0, 1.0,
            -.5,  -.5, 0.0, 1.0,
             .5,  -.5, 0.0, 1.0
        ];        

        var colors = [
            1.0, 1.0, 1.0, 1.0,    // white
            1.0, 0.0, 0.0, 1.0,    // red
            0.0, 1.0, 0.0, 1.0,    // green
            0.0, 0.0, 1.0, 1.0     // blue
        ];

        var vertex_buffer = gl.createBuffer();
        var color_buffer = gl.createBuffer();

        gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.color_attrib, 4, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.vertex_attrib, 4, gl.FLOAT, false, 0, 0);

    }

    function set_transform ( k, tx, ty ) {

        // change the space to be pixels with 0,0 in top left
        var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
        // apply the d3 translate and zoom
        matrix = m4.translate(matrix, [tx, ty, 0]);
        matrix = m4.scale(matrix, [k, k, 1]);
        // translate the unit quad to the center 
        matrix = m4.translate(matrix, [width / 2, height / 2, 0]);
        // make the unit quad be half the size of the canvas
        matrix = m4.scale(matrix, [width / 2, height / 2 , 1]);

        gl.uniformMatrix4fv( shader.matrix_uniform, false, matrix );
        gl.clear( gl.COLOR_BUFFER_BIT );
        gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 );

    }

    function basic_vertex () {

        return [
            'attribute vec4 vertex_position;',
            'attribute vec4 vertex_color;',
            'varying lowp vec4 vert_color;',
            'uniform mat4 matrix;',
            'void main( void ) {',
            '   gl_Position = matrix * vertex_position;',
            '   vert_color = vertex_color;',
            '}'
        ].join('\n');

    }

    function basic_fragment () {

        return [
            'varying lowp vec4 vert_color;',
            'void main( void ) {',
            '   gl_FragColor = vert_color;',
            '}'
        ].join('\n');

    }

    function basic_shader ( gl ) {

        var program = gl_program( gl, basic_vertex(), basic_fragment() );

        gl.useProgram( program );
        program.vertex_attrib = gl.getAttribLocation( program, 'vertex_position' );
        program.color_attrib = gl.getAttribLocation( program, 'vertex_color' );
        program.matrix_uniform = gl.getUniformLocation( program, 'matrix' );
        program.translate_uniform = gl.getUniformLocation( program, 'translate_matrix' );
        program.scale_uniform = gl.getUniformLocation( program, 'scale_matrix' );
        gl.enableVertexAttribArray( program.vertex_attrib );
        gl.enableVertexAttribArray( program.color_attrib );

        return program;

    }

    function gl_shader ( gl, type, code ) {

        var shader = gl.createShader( type );
        gl.shaderSource( shader, code );
        gl.compileShader( shader );
        return shader;

    }

    function gl_program ( gl, vertex_source, fragment_source ) {

        var shader_program = gl.createProgram();
        var vertex_shader = gl_shader( gl, gl.VERTEX_SHADER, vertex_source );
        var fragment_shader = gl_shader( gl, gl.FRAGMENT_SHADER, fragment_source );

        if ( shader_program && vertex_shader && fragment_shader ) {

            gl.attachShader( shader_program, vertex_shader );
            gl.attachShader( shader_program, fragment_shader );
            gl.linkProgram( shader_program );

            gl.deleteShader( vertex_shader );
            gl.deleteShader( fragment_shader );

            return shader_program;

        }

    }
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
&#13;
&#13;
&#13;