WebGL 2D相机缩放到鼠标点 [英] WebGL 2D camera zoom to mouse point

查看:109
本文介绍了WebGL 2D相机缩放到鼠标点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在WebGL中构建2D绘图应用程序.我想实现缩放以指向鼠标指针,类似于此处中的示例.但是我无法弄清楚在我的情况下如何从该答案中应用解决方案.

I'm currently building a 2D drawing app in WebGL. I want to implement zoom to point under mouse cursor similar to example in here. But I can't figure out how to apply the solution from that answer in my case.

我已经通过缩放相机矩阵来完成基本变焦.但是它缩放到画布的左上角,因为那是投影设置的原点(0,0)(据我所知).

I have done basic zoom by scaling camera matrix. But it zooms to the top-left corner of the canvas, due to that being the origin (0,0) set by the projection (as far as I understand).

基本锅&缩放已实现:

Basic pan & zoom implemented:

var projection = null;
var view = null;
var viewProjection = null;

function draw(gl, camera, sceneTree){
  // projection matrix
  projection = new Float32Array(9);
  mat3.projection(projection, gl.canvas.clientWidth, gl.canvas.clientHeight);

  // camera matrix
  view = new Float32Array(9);
  mat3.fromTranslation(view, camera.translation);
  mat3.rotate(view, view, toRadians(camera.rotation));
  mat3.scale(view, view, camera.scale);
  // view matrix
  mat3.invert(view, view)

  // VP matrix
  viewProjection = new Float32Array(9);
  mat3.multiply(viewProjection, projection, view);

  // go through scene tree:
  //  - build final matrix for each object
  //      e.g: u_matrix = VP x Model (translate x rotate x scale) 

  // draw each object in scene tree
  // ... 
}

顶点着色器:

attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}

缩放功能:


function screenToWorld(screenPos){
  // normalized screen position 
  let nsp = [
     2.0 * screenPos[0] / this.gl.canvas.width - 1,
     - 2.0 * screenPos[1] / this.gl.canvas.height + 1
  ];

  let inverseVP = new Float32Array(9);
  mat3.invert(inverseVP, viewProjection);

  let worldPos = [0, 0];
  return vec2.transformMat3(worldPos, nsp, inverseVP);
}

var zoomRange = [0.01, 2];

canvas.addEventListener('wheel', (e) => {
  let oldZoom = camera.scale[0];
  let zoom = Math.min(Math.max(oldZoom + e.deltaX / 100, zoomRange[0]), zoomRange[1]);

  camera.scale = [zoom, zoom];

  let zoomPoint = screenToWorld([e.clientX, e.clientY]);
  // totally breaks if enable this line 
  //vec2.copy(camera.translation, zoomPoint);

  // call draw function again
  draw();

}, false); 

如果我将zoomPoint应用于摄影机平移,则每次缩放事件(如果我放大或缩小都没有限制)以及绘制的对象进入时,zoomPoint(以及摄影机位置)的值就会开始不受控制地升高.场景立即消失了.

If I apply zoomPoint to camera translation, the values of zoomPoint (and the camera position accordingly) start to raise up uncontrollably with every zoom event (no mater if I zoom in or out) and the objects drawn in the scene go immediately out of view.

非常感谢您对我在这里做错的任何见解或建议.谢谢.

Would greatly appreciate any insights or suggestions about what am I doing wrong here. Thanks.

推荐答案

由于您没有在问题本身中发布可重复的最小示例,因此我无法使用您的数学库进行测试.尽管我可以像这样使用自己的镜头进行缩放

Since you didn't post a minimal reproducible example in the question itself I couldn't test with your math library. Using my own though I was able to zoom like this

  const [clipX, clipY] = getClipSpaceMousePosition(e);

  // position before zooming
  const [preZoomX, preZoomY] = m3.transformPoint(
      m3.inverse(viewProjectionMat), 
      [clipX, clipY]);

  // multiply the wheel movement by the current zoom level
  // so we zoom less when zoomed in and more when zoomed out
  const newZoom = camera.zoom * Math.pow(2, e.deltaY * -0.01);
  camera.zoom = Math.max(0.02, Math.min(100, newZoom));

  updateViewProjection();

  // position after zooming
  const [postZoomX, postZoomY] = m3.transformPoint(
      m3.inverse(viewProjectionMat), 
      [clipX, clipY]);

  // camera needs to be moved the difference of before and after
  camera.x += preZoomX - postZoomX;
  camera.y += preZoomY - postZoomY;  

请注意,缩放与比例相反.如果zoom = 2,那么我希望所有内容都显示大2倍.为此,需要缩小相机空间,因此我们将该空间缩放1/缩放

Note that zoom is the opposite of scale. If zoom = 2 then I want everything to appear 2x larger. To do that requires shrinking the camera space so we scale that space by 1 / zoom

示例:

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');

const vs = `
attribute vec2 a_position;
uniform mat3 u_matrix;
void main() {
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
`;

const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
  gl_FragColor = u_color;
}
`;

// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  a_position: {
    numComponents: 2,
    data: [
       0,  0, // 0----1
      40,  0, // |    |
      40, 10, // | 3--2
      10, 10, // | |
      10, 20, // | 4-5
      30, 20, // |   |
      30, 30, // | 7-6
      10, 30, // | |
      10, 50, // 9-8
       0, 50,
    ],
  },
  indices: [
    0, 1, 2,
    0, 2, 3,
    0, 3, 8,
    0, 8, 9,
    4, 5, 6,
    4, 6, 7,
  ],
});

const camera = {
  x: 0,
  y: 0,
  rotation: 0,
  zoom: 1,
};

const scene = [
  { x:  20, y:  20, rotation: 0,       scale: 1,   color: [1,   0, 0, 1], bufferInfo},
  { x: 100, y:  50, rotation: Math.PI, scale: 0.5, color: [0, 0.5, 0, 1], bufferInfo},
  { x: 100, y:  50, rotation: 0,       scale: 2,   color: [0,   0, 1, 1], bufferInfo},
  { x: 200, y: 100, rotation: 0.7,     scale: 1,   color: [1,   0, 1, 1], bufferInfo},
];

let viewProjectionMat;

function makeCameraMatrix() {
  const zoomScale = 1 / camera.zoom;
  let cameraMat = m3.identity();
  cameraMat = m3.translate(cameraMat, camera.x, camera.y);
  cameraMat = m3.rotate(cameraMat, camera.rotation);
  cameraMat = m3.scale(cameraMat, zoomScale, zoomScale);
  return cameraMat;
}

function updateViewProjection() {
  // same as ortho(0, width, height, 0, -1, 1)
  const projectionMat = m3.projection(gl.canvas.width, gl.canvas.height);
  const cameraMat = makeCameraMatrix();
  let viewMat = m3.inverse(cameraMat);
  viewProjectionMat = m3.multiply(projectionMat, viewMat);
}

function draw() {
  gl.clear(gl.COLOR_BUFFER_BIT);

  updateViewProjection();
    
  gl.useProgram(programInfo.program);

  for (const {x, y, rotation, scale, color, bufferInfo} of scene) {
    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    
    let mat = m3.identity();
    mat = m3.translate(mat, x, y);
    mat = m3.rotate(mat, rotation);
    mat = m3.scale(mat, scale, scale);

    // calls gl.uniformXXX
    twgl.setUniforms(programInfo, {
      u_matrix: m3.multiply(viewProjectionMat, mat),
      u_color: color,
    });

    // calls gl.drawArrays or gl.drawElements
    twgl.drawBufferInfo(gl, bufferInfo);
  }
}

draw();

function getClipSpaceMousePosition(e) {
  // get canvas relative css position
  const rect = canvas.getBoundingClientRect();
  const cssX = e.clientX - rect.left;
  const cssY = e.clientY - rect.top;
  
  // get normalized 0 to 1 position across and down canvas
  const normalizedX = cssX / canvas.clientWidth;
  const normalizedY = cssY / canvas.clientHeight;

  // convert to clip space
  const clipX = normalizedX *  2 - 1;
  const clipY = normalizedY * -2 + 1;
  
  return [clipX, clipY];
}

canvas.addEventListener('wheel', (e) => {
  e.preventDefault();  
  const [clipX, clipY] = getClipSpaceMousePosition(e);

  // position before zooming
  const [preZoomX, preZoomY] = m3.transformPoint(
      m3.inverse(viewProjectionMat), 
      [clipX, clipY]);
    
  // multiply the wheel movement by the current zoom level
  // so we zoom less when zoomed in and more when zoomed out
  const newZoom = camera.zoom * Math.pow(2, e.deltaY * -0.01);
  camera.zoom = Math.max(0.02, Math.min(100, newZoom));
  
  updateViewProjection();
  
  // position after zooming
  const [postZoomX, postZoomY] = m3.transformPoint(
      m3.inverse(viewProjectionMat), 
      [clipX, clipY]);

  // camera needs to be moved the difference of before and after
  camera.x += preZoomX - postZoomX;
  camera.y += preZoomY - postZoomY;  
  
  draw();
});

canvas { border: 1px solid black; display: block; }

<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m3.js"></script>

请注意,我包含了camera.rotation只是为了确保旋转后一切正常.他们似乎. 这里有缩放,平移和旋转的功能

note that I included camera.rotation just to make sure things worked if rotated. They seem to. Here's one with zoom, pan, and rotate

这篇关于WebGL 2D相机缩放到鼠标点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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