2D 缩放以指向 webgl [英] 2D zoom to point in webgl

查看:28
本文介绍了2D 缩放以指向 webgl的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 WebGL(更具体地说是 regl)创建 2D 图形可视化.通过我当前的实现,我已经可以看到应用于每个节点的力布局,这很好.当我尝试相对于当前鼠标位置进行缩放时,问题就出现了.根据我的研究,要实现此行为,必须按以下顺序应用矩阵变换:

translate(nodePosition, mousePosition)规模(比例因子)翻译(节点位置,-mousePosition)

因此,每次触发 wheel 事件时,都会重新计算鼠标位置,并使用新的鼠标位置信息更新变换矩阵.当前的行为很奇怪,我似乎无法理解出了什么问题.

显然,如果我用固定在初始位置的鼠标放大和缩小,一切正常.但是,如果我移动鼠标并尝试将焦点放在另一个节点上,则会失败.

获取鼠标位置的函数是:

const getMousePosition = (event) =>{var canvas = event.currentTargetvar rect = canvas.getBoundingClientRect()var x = event.clientX - rect.leftvar y = event.clientY - rect.topvar 投影 = mat3.create()var pos = vec2.fromValues(x,y)//这会将鼠标坐标从//像素空间到 WebGL 剪辑空间mat3.projection(投影,canvas.clientWidth,canvas.clientHeight)vec2.transformMat3(位置,位置,投影)返回(位置)}

wheel 事件监听回调:

var zoomFactor = 1.0var 鼠标 = vec2.fromValues(0.0, 0.0)options.canvas.addEventListener("wheel", (event) => {event.preventDefault()鼠标 = getMousePosition(事件)var 方向 = event.deltaY <0 ?1:-1zoomFactor = 1 + 方向 * 0.1更新转换()})

以及更新变换的函数:

var transform = mat3.create()函数更新转换(){varnegativeMouse = vec2.create()vec2.negate(negativeMouse,鼠标)mat3.translate(变换,变换,鼠标)mat3.scale(transform, transform, [zoomFactor, zoomFactor])mat3.translate(变换,变换,负鼠标)}

这个 transform 矩阵在顶点着色器中作为一个统一的可用:

 精度高浮点数;属性 vec2 位置;均匀 mat3 变换;统一浮动舞台宽度;统一浮动舞台高度;vec2 normalizeCoords(vec2 位置) {float x = (position[0]+ (stageWidth/2.0));float y = (position[1]+ (stageHeight/2.0));返回 vec2(2.0 * ((x/stageWidth ) - 0.5),-(2.0 * ((y/stageHeight) - 0.5)));}无效主(){gl_PointSize = 7.0;vec3 final = 变换 * vec3(normalizeCoords(position), 1);gl_Position = vec4(final.xy, 0, 1);}

其中,position 是保存节点位置的属性.

到目前为止我尝试过的:

  • 我已经尝试过更改转换顺序.结果更奇怪.
  • 当我独立应用平移或缩放时,一切看起来都很好.

这是我第一次与不常见的 SVG/canvas 内容进行交互.解决方案可能很明显,但我真的不知道去哪里找了.我做错了什么?

更新 06/11/2018

我遵循@Johan 的建议并在

I'm trying to create a 2D graph visualization using WebGL (regl, to be more specific). With my current implementation I can already see the force layout being applied to each node, which is good. The problem comes when I try to zoom with respect to the current mouse position. According to my research, to achieve this behavior, it is necessary to apply matrix transformations in the following order:

translate(nodePosition, mousePosition)
scale(scaleFactor)
translate(nodePosition, -mousePosition)

So, every time the wheel event is fired, the mouse position is recalculated and the transform matrix is updated with the new mouse position information. The current behavior is weird and I can't seem to understand what is wrong. Here is a live example.

Apparently, if I zoom in and out with the mouse fixed at the initial position, everything works just fine. However, if I move the mouse and try to focus on another node, then it fails.

The function for retrieving the mouse position is:

const getMousePosition = (event) => {
    var canvas = event.currentTarget
    var rect = canvas.getBoundingClientRect()
    var x = event.clientX - rect.left
    var y = event.clientY - rect.top
    var projection = mat3.create()
    var pos = vec2.fromValues(x,y)
    // this converts the mouse coordinates from 
    // pixel space to WebGL clipspace
    mat3.projection(projection, canvas.clientWidth, canvas.clientHeight)
    vec2.transformMat3(pos, pos, projection)
    return(pos)
}

The wheel event listener callback:

var zoomFactor = 1.0
var mouse = vec2.fromValues(0.0, 0.0)
options.canvas.addEventListener("wheel", (event) => {
    event.preventDefault()
    mouse = getMousePosition(event)
    var direction = event.deltaY < 0 ? 1 : -1
    zoomFactor = 1 + direction * 0.1
    updateTransform()
})

And the function that updates the transform:

var transform = mat3.create()
function updateTransform() {
    var negativeMouse = vec2.create()
    vec2.negate(negativeMouse, mouse)
    mat3.translate(transform, transform, mouse)
    mat3.scale(transform, transform, [zoomFactor, zoomFactor])
    mat3.translate(transform, transform, negativeMouse)
}

This transform matrix is made available as an uniform in the vertex shader:

  precision highp float;
  attribute vec2 position;

  uniform mat3 transform;

  uniform float stageWidth;
  uniform float stageHeight;

  vec2 normalizeCoords(vec2 position) {
    float x = (position[0]+ (stageWidth  / 2.0));
    float y = (position[1]+ (stageHeight / 2.0));

    return vec2(
        2.0 * ((x / stageWidth ) - 0.5),
      -(2.0 * ((y / stageHeight) - 0.5))
    );
  }

  void main () {
    gl_PointSize = 7.0;
    vec3 final = transform * vec3(normalizeCoords(position), 1);
    gl_Position = vec4(final.xy, 0, 1);
  }

where, position is the attribute holding the node position.

What I've tried, so far:

  • I already tried changing the order of the transformations. The result is even weirder.
  • When I apply either translation or scaling independently, everything looks ok.

This is my first interaction with something that is not the usual SVG/canvas stuff. The solution is probably obvious, but I really don't know where to look anymore. What am I doing wrong?

Update 06/11/2018

I followed @Johan's suggestions and implemented it on the live demo. Although the explanation was rather convincing, the result is not quite what I was expecting. The idea of inverting the transform to get the mouse position in the model space makes sense to me, but my intuition (which is probably wrong) says that applying the transform directly on the screen space should also work. Why can't I project both the nodes and the mouse in the screen space and apply the transform directly there?

Update 07/11/2018

After struggling a little, I decided to take a different approach and adapt the solution from this answer for my use case. Although things are working as expected for the zoom (with the addition of panning as well), I still believe there are solutions that do not depend on d3-zoom at all. Maybe isolating the view matrix and controlling it independently to achieve the expected behavior, as suggested in the comments. To see my current solution, check my answer bellow.

解决方案

Alright, after failing with the original approach, I managed to make this solution work for my use case.

The updateTransform function is now:

var transform = mat3.create();
function updateTransform(x, y, scale) {
    mat3.projection(transform, options.canvas.width, options.canvas.height);
    mat3.translate(transform, transform, [x,y]);
    mat3.scale(transform, transform, [scale,scale]);
    mat3.translate(transform, transform, [
      options.canvas.width / 2,
      options.canvas.height / 2
    ]);
    mat3.scale(transform, transform, [
      options.canvas.width / 2,
      options.canvas.height / 2
    ]);
    mat3.scale(transform, transform, [1, -1]);
}

And is called by d3-zoom:

import { zoom as d3Zoom } from "d3-zoom";
import { select } from "d3-selection";

var zoom = d3Zoom();

d3Event = () => require("d3-selection").event;

select(options.canvas)
      .call(zoom.on("zoom", () => {
          var t = d3Event().transform
          updateTransform(t.x, t.y, t.k)
       }));

Here is the live demonstration with this solution.

这篇关于2D 缩放以指向 webgl的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆