【问题标题】:Using d3-zoom to interact with WebGL使用 d3-zoom 与 WebGL 交互
【发布时间】:2017-07-13 09:27:48
【问题描述】:

我正在尝试收集一个小示例,该示例使用 d3-zoom 为使用 WebGL 呈现的画布元素提供简单的交互性。我想做的就是提供平移/缩放,使用 4x4 转换矩阵相当简单。

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

如果您使用 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>

我的直觉是,这与 WebGL 中的视口 x 和 y 坐标各自从 -1 到 1 的事实有关,而 d3-zoom 使用画布元素内的鼠标坐标,当归一化时可以在 0 到 1 的范围内。

如果您将鼠标放在画布的最左上角(画布坐标中的 (0,0))并尝试缩放,您会看到这种情况。它会像鼠标在画布的中心一样缩放(WebGL 坐标中的 (0,0))。

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

    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;

        }

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

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

【问题讨论】:

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


    【解决方案1】:

    我刚刚移动了你的顶点以匹配你的矩阵

        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 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;
    
            }
    
        }
    <script src="https://d3js.org/d3.v4.min.js"></script>

    但老实说,我可能会使用数学库并使用一些转换。这样我更容易理解代码。我不确定 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]);
    

    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;
    
            }
    
        }
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

    【讨论】:

    • 非常好,感谢您的回复。真的在挖twgl。我现在唯一的问题是图像被垂直翻转,以便在正确的位置进行缩放(在你的最后一个例子中,红色应该是右上角)。这可以通过在创建矩阵时切换0gl.canvas.height 来解决,但随后交互会垂直翻转。这是可以通过矩阵运算解决的问题,还是我们需要更改 d3 获取其 y 位置的位置(即canvas.clientHeight - y 而不仅仅是y)?
    • 你可以用矩阵运算来修复它,只需在底部按 [1, -1, 1] 缩放(或者只需更改顶点,您就不需要缩放)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-03
    • 2020-11-16
    • 1970-01-01
    • 2014-08-27
    相关资源
    最近更新 更多