使用 d3-zoom 与 WebGL 交互
Using d3-zoom to interact with WebGL
我正在尝试整理一个小示例,该示例使用 d3-zoom 为使用 WebGL 呈现的 canvas 元素提供简单的交互性。我想做的就是提供 panning/zooming,使用 4x4 转换矩阵非常简单。
我遇到的问题是缩放(缩放)。如果您查看一些 d3-zoom 示例,您会发现缩放焦点始终位于鼠标的位置。
如果您直接使用来自缩放变换的 k
、tx
和 ty
值,则平移有效,但缩放偏移了宽度和高度的一半canvas,见
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 使用 [=44= 内的鼠标坐标] 元素,标准化后可以在 0 到 1 的范围内。
如果将鼠标放在 canvas 的最左上角(canvas 坐标中的 (0,0))并尝试缩放,您会发现情况就是如此。它将缩放,就好像鼠标位于 canvas 的中心(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 端处理?
我刚刚移动了你的顶点以匹配你的矩阵
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 的 "space" 是什么。我想虽然它只是传递给你一个偏移量和一个比例。在这种情况下
// 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>
我正在尝试整理一个小示例,该示例使用 d3-zoom 为使用 WebGL 呈现的 canvas 元素提供简单的交互性。我想做的就是提供 panning/zooming,使用 4x4 转换矩阵非常简单。
我遇到的问题是缩放(缩放)。如果您查看一些 d3-zoom 示例,您会发现缩放焦点始终位于鼠标的位置。
如果您直接使用来自缩放变换的 k
、tx
和 ty
值,则平移有效,但缩放偏移了宽度和高度的一半canvas,见
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 使用 [=44= 内的鼠标坐标] 元素,标准化后可以在 0 到 1 的范围内。
如果将鼠标放在 canvas 的最左上角(canvas 坐标中的 (0,0))并尝试缩放,您会发现情况就是如此。它将缩放,就好像鼠标位于 canvas 的中心(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 端处理?
我刚刚移动了你的顶点以匹配你的矩阵
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 的 "space" 是什么。我想虽然它只是传递给你一个偏移量和一个比例。在这种情况下
// 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>