WebGL动画与交互

367 阅读6分钟

目录


WebGL 动画与交互通常涉及到JavaScript的定时器、矩阵变换、以及用户输入事件的处理。

动画

WebGL中的动画通常是通过不断更新模型的变换矩阵并重新渲染场景来实现的。这通常涉及到使用requestAnimationFrame函数来创建动画循环:

function animate() {
    requestAnimationFrame(animate); // 创建动画循环

    // 更新模型的变换矩阵,例如旋转
    var rotation = getRotation(); // 返回当前的旋转角度
    modelMatrix = rotate(modelMatrix, rotation, [0, 1, 0]); // 旋转矩阵

    // 渲染场景
    renderScene(modelMatrix);
}

// 开始动画
animate();

getRotation()函数可能基于时间或者用户输入返回旋转角度,rotate()函数是一个矩阵旋转操作,renderScene()是渲染整个场景的函数。

交互

WebGL的交互通常需要处理浏览器的DOM事件,例如鼠标点击和移动事件。这些事件通常需要转换成3D空间中的坐标,以便在场景中进行交互:

canvas.addEventListener('mousemove', function(event) {
    var rect = canvas.getBoundingClientRect();
    var x = event.clientX - rect.left;
    var y = event.clientY - rect.top;

    // 将屏幕坐标转换为归一化设备坐标
    var ndcX = (2.0 * x) / canvas.width - 1.0;
    var ndcY = 1.0 - (2.0 * y) / canvas.height;

    // 将归一化设备坐标转换为视口空间坐标
    var viewport = [0, 0, canvas.width, canvas.height];
    var viewSpacePoint = unproject(viewport, ndcX, ndcY, 0.0, 1.0);

    // 使用视口空间坐标与场景交互
    handleInteraction(viewSpacePoint);
});

unproject()函数将屏幕坐标转换为3D空间中的点,handleInteraction()则根据这个点进行交互,例如选择对象或改变物体的位置。

在Three.js等库中,交互和动画通常更易于实现,因为它们提供了现成的事件处理和动画系统。例如,在Three.js中,你可以使用Raycaster来检测鼠标点击的3D对象,以及Object3DrotateOnAxis()方法来执行旋转动画。

拖放

在WebGL中实现拖放功能,通常需要监听mousedownmousemovemouseup事件,以及相应的3D坐标转换。

let isDragging = false;
let dragStartPos = null;

canvas.addEventListener('mousedown', function(event) {
    dragStartPos = get3DPointFromEvent(event);
    isDragging = true;
});

canvas.addEventListener('mousemove', function(event) {
    if (isDragging) {
        var currentPos = get3DPointFromEvent(event);
        var delta = subtract(currentPos, dragStartPos);
        // 更新被拖动对象的位置
        updateObjectPosition(delta);
    }
});

canvas.addEventListener('mouseup', function(event) {
    isDragging = false;
});

function get3DPointFromEvent(event) {
    // 类似之前屏幕坐标到3D坐标的转换
}

function updateObjectPosition(delta) {
    // 根据delta更新对象的位置
}

触摸事件

对于触摸设备,可以使用touchstarttouchmovetouchend事件来处理触摸交互。

let touchStartPos = null;

canvas.addEventListener('touchstart', function(event) {
    event.preventDefault();
    touchStartPos = get3DPointFromTouchEvent(event.touches[0]);
});

canvas.addEventListener('touchmove', function(event) {
    if (touchStartPos) {
        var currentPos = get3DPointFromTouchEvent(event.touches[0]);
        var delta = subtract(currentPos, touchStartPos);
        // 更新被拖动对象的位置
        updateObjectPosition(delta);
    }
});

canvas.addEventListener('touchend', function(event) {
    touchStartPos = null;
});

function get3DPointFromTouchEvent(touch) {
    // 类似之前屏幕坐标到3D坐标的转换,但使用触摸事件的坐标
}

多点触控

对于支持多点触控的设备,可以监听touchstarttouchmovetouchend事件的touches数组,处理多个触摸点。

let touchPoints = [];

canvas.addEventListener('touchstart', function(event) {
    event.preventDefault();
    for (let i = 0; i < event.changedTouches.length; i++) {
        let touch = event.changedTouches[i];
        touchPoints.push({ id: touch.identifier, pos: get3DPointFromTouchEvent(touch) });
    }
});

canvas.addEventListener('touchmove', function(event) {
    for (let i = 0; i < event.changedTouches.length; i++) {
        let touch = event.changedTouches[i];
        let touchIndex = findTouchIndexById(touch.identifier);
        if (touchIndex !== -1) {
            let oldPos = touchPoints[touchIndex].pos;
            let newPos = get3DPointFromTouchEvent(touch);
            let delta = subtract(newPos, oldPos);
            // 更新对应触摸点的对象位置
            updateObjectPosition(delta, touchIndex);
        }
    }
});

canvas.addEventListener('touchend', function(event) {
    for (let i = 0; i < event.changedTouches.length; i++) {
        let touch = event.changedTouches[i];
        let touchIndex = findTouchIndexById(touch.identifier);
        if (touchIndex !== -1) {
            touchPoints.splice(touchIndex, 1);
        }
    }
});

function findTouchIndexById(id) {
    for (let i = 0; i < touchPoints.length; i++) {
        if (touchPoints[i].id === id) {
            return i;
        }
    }
    return -1;
}

function updateObjectPosition(delta, touchIndex) {
    // 更新对应触摸点的对象位置
}

手势识别

手势识别通常需要分析连续的触摸点变化,以识别特定的手势,如旋转、缩放和滑动。以下是一个简化的手势识别框架:

let gestureState = {
    mode: 'none',
    startPositions: [],
    currentPositions: []
};

canvas.addEventListener('touchstart', function(event) {
    event.preventDefault();
    for (let i = 0; i < event.changedTouches.length; i++) {
        gestureState.startPositions.push(get2DPointFromTouchEvent(event.changedTouches[i]));
    }
    gestureState.currentPositions = gestureState.startPositions.slice();
});

canvas.addEventListener('touchmove', function(event) {
    event.preventDefault();
    gestureState.currentPositions = [];
    for (let i = 0; i < event.changedTouches.length; i++) {
        gestureState.currentPositions.push(get2DPointFromTouchEvent(event.changedTouches[i]));
    }
    recognizeGesture();
});

canvas.addEventListener('touchend', function(event) {
    gestureState.startPositions = [];
    gestureState.currentPositions = [];
});

function recognizeGesture() {
    switch (gestureState.currentPositions.length) {
        case 1:
            // 单指滑动
            break;
        case 2:
            // 双指缩放或旋转
            if (isScaling()) {
                gestureState.mode = 'scale';
                // 缩放操作
            } else if (isRotating()) {
                gestureState.mode = 'rotate';
                // 旋转操作
            }
            break;
        default:
            // 更复杂的手势
            break;
    }

    if (gestureState.mode === 'scale') {
        // 处理缩放
    } else if (gestureState.mode === 'rotate') {
        // 处理旋转
    }
}

function isScaling() {
    // 计算两个手指之间的距离变化
}

function isRotating() {
    // 计算两个手指之间的角度变化
}

function get2DPointFromTouchEvent(touch) {
    // 将触摸事件坐标转换为2D屏幕坐标
}

滑动手势

滑动手势通常用于导航或切换场景。以下是一个基本的滑动手势检测:

let swipeStartPos = null;

canvas.addEventListener('mousedown', function(event) {
    swipeStartPos = get2DPointFromEvent(event);
});

canvas.addEventListener('mousemove', function(event) {
    if (swipeStartPos) {
        var currentPos = get2DPointFromEvent(event);
        var distance = length(subtract(currentPos, swipeStartPos));
        if (distance > SWIPE_THRESHOLD) {
            // 触发滑动手势
            triggerSwipe(currentPos);
            swipeStartPos = null;
        }
    }
});

function triggerSwipe(endPos) {
    // 处理滑动手势,例如切换场景或滚动
}

缩放和平移

缩放和平移通常与多点触控相关,例如双指缩放和单指平移。以下是一个简化的实现:

let scaleCenter = null;
let translateStartPos = null;

canvas.addEventListener('touchstart', function(event) {
    if (event.touches.length === 2) {
        scaleCenter = get2DPointFromEvent(event.touches[0], event.touches[1]);
    } else if (event.touches.length === 1) {
        translateStartPos = get2DPointFromEvent(event.touches[0]);
    }
});

canvas.addEventListener('touchmove', function(event) {
    if (event.touches.length === 2) {
        var currentCenter = get2DPointFromEvent(event.touches[0], event.touches[1]);
        var scaleDelta = distance(currentCenter, scaleCenter);
        // 更新缩放
        updateScale(scaleDelta);
    } else if (event.touches.length === 1 && translateStartPos) {
        var currentPos = get2DPointFromEvent(event.touches[0]);
        var translateDelta = subtract(currentPos, translateStartPos);
        // 更新平移
        updateTranslation(translateDelta);
    }
});

function updateScale(delta) {
    // 根据delta更新场景的缩放
}

function updateTranslation(delta) {
    // 根据delta更新场景的平移
}

键盘控制

键盘控制通常用于游戏或交互式应用,允许用户通过按键来控制场景中的对象。以下是一个简单的键盘事件处理示例:

let keyStates = {};

document.addEventListener('keydown', function(event) {
    keyStates[event.key] = true;
});

document.addEventListener('keyup', function(event) {
    keyStates[event.key] = false;
});

function update() {
    if (keyStates['ArrowUp']) {
        // 控制对象向上移动
    } else if (keyStates['ArrowDown']) {
        // 控制对象向下移动
    }
    // 其他键位处理...
}

游戏控制器

游戏控制器(如游戏手柄)可以提供更丰富的交互方式,尤其是对于游戏应用。以下是一个使用gamepad API的基本示例:

javascript
let gamepad;

function update() {
    if (navigator.getGamepads) {
        var pads = navigator.getGamepads();
        for (var i = 0; i < pads.length; i++) {
            if (pads[i]) {
                gamepad = pads[i];
                break;
            }
        }
    }

    if (gamepad) {
        if (gamepad.buttons[0].pressed) {
            // A按钮按下,执行动作
        }
        if (gamepad.axes[1] < -0.5) {
            // 左摇杆向左,控制对象向左移动
        }
        // 其他轴和按钮处理...
    }
}

虚拟现实(VR)和增强现实(AR)交互

WebGL可以与WebVR或WebXR API结合,实现虚拟现实和增强现实的交互。以下是一个简单的VR交互示例:

let vrDisplay;

function initVR() {
    if (navigator.getVRDisplays) {
        navigator.getVRDisplays().then(displays => {
            if (displays.length > 0) {
                vrDisplay = displays[0];
                vrDisplay.requestAnimationFrame(render);
            }
        });
    }
}

function render() {
    if (vrDisplay) {
        vrDisplay.getFrameData(frameData);
        // 使用frameData更新场景和视角
        vrDisplay.requestAnimationFrame(render);
    } else {
        // 无VR设备时的普通渲染
    }
}

在增强现实方面,WebGL可以与AR库(如AR.js或A-Frame)结合,利用摄像头和标记来实现AR交互。