vibe

产品简介(Web 端)

使用 WebGL 进行图形渲染,并通过 WebSocket 进行实时通讯。这款应用的核心功能是允许用户在虚拟画板上绘制、添加文本、图片、PDF、磁力贴等元素,并支持实时的多用户协作。

官网地址:Vibe

线上地址:Vibe Web App

技术栈:React | Redux | WebGL | bulma 等

技术细节(部分)

应用结构

应用使用 React 框架建立用户界面,Redux 管理应用状态。

通过 WebSocket 连接后端服务器,实现实时数据同步。

使用 WebGL 渲染画板上的图形和文本。

核心组件

Whiteboard、GLCanvas,GLRenderer,GLProgram,GLAnimation;

Whiteboard 是应用程序的最顶层容器,负责管理用户界面和应用程序的状态,它不直接处理绘图逻辑,而是负责处理用户输入(如鼠标操作、键盘快捷键)、显示上下文菜单、处理文件上传(图片、PDF 等)、以及与后端的通信(WebSocket)。Whiteboard 组件是其他组件(如 GLCanvas)的父组件,它将用户操作传递给子组件,并基于子组件的反馈更新应用状态。

GLCanvas 负责创建和管理 <canvas> 元素,提供 WebGL 上下文给 GLRenderer 使用。它是 Whiteboard 和 GLRenderer 之间的桥梁,接收来自 Whiteboard 的指令(如磁贴操作指令)、绘图指令等,并将这些操作的结果反馈回 Whiteboard。此外,GLCanvas 还处理与 Canvas 相关的用户交互,如缩放、拖动等。

GLRenderer 是一个用于封装 WebGL 绘图逻辑的 JavaScript 类,负责实际的绘制工作,包括绘制图形、处理图形变换(缩放、旋转等)以及管理绘图深度。GLRenderer 直接操作 WebGL 上下文,执行具体的绘制命令。它由 GLCanvas 初始化和管理,为 GLCanvas 提供绘图能力。

GLProgram 是一个帮助类,用于封装 WebGL 程序(shader 程序)的创建和使用逻辑。每个 GLProgram 实例代表一组特定的顶点着色器和片元着色器的组合,负责具体的图形渲染效果。GLRenderer 利用 GLProgram 来实现不同的渲染效果,如绘制纯色图形、贴图、处理图形边缘等。通过将 shader 程序的管理抽象为 GLProgram,GLRenderer 的实现更加模块化和易于管理。

GLAnimation 也是一个帮助类,它定义了在 GLCanvas 中执行动画的通用框架。每个 GLAnimation 实例代表一个动画过程,通过更新动画状态并重新绘制 GLCanvas 来实现动画效果。GLRenderer 可以使用 GLAnimation 来实现复杂的动画效果,如平滑过渡、缩放动画等。

远程实时协作

实现:WebSocket;

核心组件:RemoteAction、RemoteActionRenderer、RemoteActionAnimation

概述:发送绘制命令:当用户在设备 A 上绘制时,将这些绘制动作(如起点、终点、颜色、粗细等信息)通过 WebSocket 发送到服务器。接收绘制命令:设备 B 监听来自服务器的消息,当接收到绘制命令时,解析这些命令,并用 WebGL 在本地重现这些绘制动作。

RemoteAction 类:当远程用户在白板上执行操作时(绘制路径),RemoteAction 实例被创建出来代表这个操作,它包含这个操作相关的所有必要信息,比如操作类型、关联的用户和头像图标纹理、以及操作的空间位置。它还会提供方法,用来更新操作的变换矩阵,管理远程操作的数据和资源(如纹理和缓冲区对象 - 存储顶点数据)。它还负责管理操作的起始和结束时间戳(动画控制)。

// 变换矩阵方法
_updateTransform(modalMatrix: Mat4, x: number, y: number) {
  const resultV = vec2.create();
  vec2.transformMat4(resultV, vec2.fromValues(x, y), modalMatrix);
  const transform = mat4.create();
  mat4.identity(transform);
  transform[12] = resultV[0] - BG_WIDTH / 2;
  transform[13] = resultV[1] - BG_HEIGHT + 30;
  this._transform = transform;
}

RemoteActionAnimation 类:继承自 GLAnimation 接口,用于控制远程操作的动画,包括操作的出现、存在和消失三个阶段。

// 动画帧更新
onDrawFrame(time: DOMHighResTimeStamp): boolean {
    const { _drawPath, _magnet } = this;
    if (_drawPath) {
        return this._handleDrawPathAnimation(time, _drawPath);
    } else if (_magnet) {
        return this._handleMagnetAnimation(time, _magnet);
    } else {
        return this._handleAnimation(time);
    }
}
// 根据当前时间参数(time),调用不同的处理函数

RemoteActionRenderer 类:负责将 RemoteAction 的可视化部分渲染到 WebGL 上下文中,使用 RemoteAction 提供的纹理、缓冲区对象和变换矩阵,以及 RemoteActionAnimation 计算出的动画参数,来绘制操作的头像、背景和图标。

// 渲染到 WebGL 上下文
_renderRemoteAvatarBufferObject(
    bo: GLBufferObject,
    mMvpLoc: WebGLUniformLocation,
    vPositionLoc: number,
    aTexCoordLoc: number,
    uTexLoc: WebGLUniformLocation,
    uBorderColorLoc: WebGLUniformLocation,
    texture: WebGLTexture
) {
    const { _gl: gl } = this;

    gl.uniformMatrix4fv(mMvpLoc, false, this._mMvp);
    gl.activeTexture(gl.TEXTURE0);
    gl.uniform4fv(uBorderColorLoc, TRANSPARENT_COLOR);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.uniform1i(uTexLoc, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, bo.vertexBuffer);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bo.indexBuffer);

    gl.vertexAttribPointer(
        vPositionLoc,
        COORDS_PER_VERTEX,
        gl.FLOAT,
        false,
        bo.vertexStride,
        0
    );
    gl.enableVertexAttribArray(vPositionLoc);

    gl.vertexAttribPointer(
        aTexCoordLoc,
        TEX_COORDS_PER_VERTEX,
        gl.FLOAT,
        false,
        bo.vertexStride,
        COORDS_PER_VERTEX * BYTES_PER_FLOAT
    );
    gl.enableVertexAttribArray(aTexCoordLoc);

    gl.drawElements(bo.mode, bo.indexCount, gl.UNSIGNED_SHORT, 0);

    gl.disableVertexAttribArray(vPositionLoc);
    gl.disableVertexAttribArray(aTexCoordLoc);
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}