Game Development 23 min read

How Crab’s WebGL Engine Powers Game‑Level Animations on the Web

This article explores the design and implementation of the Crab rendering engine, a WebGL‑based solution that combines high expressiveness, interactivity, and cross‑device compatibility to deliver game‑like animations for large‑scale activities, detailing its architecture, rendering pipeline, practical applications, and performance optimizations.

Kuaishou Frontend Engineering
Kuaishou Frontend Engineering
Kuaishou Frontend Engineering
How Crab’s WebGL Engine Powers Game‑Level Animations on the Web

Project Background

In large‑scale activities on Kuaishou, animation plays a crucial role in attracting users, increasing engagement, and improving retention, leading to increasingly complex effects.

Two complex animation cases are shown, where prominent KV sections use continuous animations that must run smoothly on many devices.

The three key characteristics of such animations are high expressiveness, high interactivity, and high compatibility, collectively referred to as “gameified animations”.

Limitations of Conventional Solutions

Typical animation implementations fall into two categories: keyframe‑based (CSS, Lottie) and frame‑by‑frame (sprite sheets, APNG, video). Keyframe solutions are simple but only support basic transforms or vector changes, while frame‑by‑frame solutions offer high visual fidelity but lack interactivity.

Consequently, conventional methods cannot simultaneously satisfy expressiveness, interactivity, and compatibility for gameified animations.

Why WebGL?

WebGL provides low‑level 3D graphics capabilities that allow fine‑grained control over each animation element, down to the pixel level, enabling both high expressiveness and interactivity.

<code>function createShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  return shader;
}

function createProgram(gl, vertexShader, fragmentShader) {
  const program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  return program;
}

let fragStr = `#version 300 es
precision mediump float;

in vec2 position;
out vec4 outColor;

void main() {
  outColor = vec4(1., 0., 0., 1.);
}`;

let vertStr = `#version 300 es
in vec2 a_position;
uniform vec2 u_resolution;
out vec2 position;

void main() {
  position = a_position;
  gl_Position = vec4(a_position * 2. - 1., 0., 1.);
}`;

let positions = new Float32Array([.5, 1., 1., 0., 0., 0.]);
let canvas = document.querySelector('#can');
canvas.width = 640;
canvas.height = 320;
let gl = canvas.getContext('webgl2');
let program = createProgram(gl, createShader(gl, gl.VERTEX_SHADER, vertStr), createShader(gl, gl.FRAGMENT_SHADER, fragStr));
gl.useProgram(program);
gl.viewport(0, 0, canvas.width, canvas.height);

const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
const size = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
gl.bindVertexArray(vao);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const primitiveType = gl.TRIANGLES;
const count = 3;
gl.drawArrays(primitiveType, 0, count);
</code>

Rendering a simple triangle with WebGL requires dozens of lines of code, highlighting the need for a higher‑level engine.

Why Crab?

Four selection principles guided the choice of an engine:

Lightweight implementation: only import required engine modules to keep bundle size small.

Extensibility: allow easy addition of rendering and interaction features without tightly coupling to core capabilities.

Ease of consolidation: provide standard interfaces for reusable features across projects.

Co‑location of gameified animations and business code: reduce collaboration cost and simplify debugging.

Existing solutions failed to meet all criteria, so a custom engine named Crab was developed.

Crab Overview

Crab follows a layered architecture consisting of an access layer, asset abstraction layer, extension layer, runtime layer, and a functional layer (third‑party or first‑party packages).

The architecture enables lightweight core packages while allowing optional feature packages to be added as needed.

Tick Processing

Each tick contains multiple hook stages where users can register custom logic. The render processor (Render Processor) executes rendering operations and exposes rendering hooks.

Rendering Pipeline

Crab’s rendering pipeline consists of render stages and render pipelines. Four types of render stages connect FrameBuffers and the screen, offering high extensibility similar to LEGO bricks.

Render pipelines are the smallest rendering unit, encapsulating shader programs, uniforms, and draw‑call settings. They can be attached to Renderable components such as Unlit, Blinn‑Phong, particle, or Spine components.

Applications and Practices

2D Animation Solutions

Spine provides 2.5D skeletal animation, offering a balance between visual quality and lightweight implementation. Crab wraps Spine playback, skin swapping, and attachment points, and supplies a loader to hide Spine internals.

Displacement maps, an AE technique, achieve similar effects with lower production cost by using a color map, a displacement‑factor map, and an animation clip.

3D Animation Solutions

Crab supports 3D models, particle systems, and various material types. For high‑quality rendering, Physically Based Rendering (PBR) is ideal but costly; instead, Matcap materials use pre‑computed texture maps to achieve near‑offline quality with low performance overhead.

Matcap rendering involves an index map, a Matcap texture, and optionally a color map, allowing efficient shading even on devices that only support WebGL1.

Animation Transitions and Vertex Deformation

To avoid abrupt animation switches, Crab implements transition and blending capabilities. For fine‑grained facial animation, vertex deformation stores multiple mesh snapshots and blends them during playback.

On WebGL1 devices, all snapshots are packed into a single 2D texture with a custom lookup function, enabling many snapshots without exceeding texture limits.

Particle System

Crab’s particle system mirrors Unity’s design, consisting of a core component (lifetime, emission) and optional components (shape, velocity over lifetime, etc.).

Shader Effects

For special effects, custom shaders can be added to render stages and pipelines. Example code adds several stages to the render process:

<code>this.scene.renderProcess
  .addStage(advectVelPass)
  .addStage(disturbVelPass)
  .addStage(advergencePass)
  .addStage(iteraGroup)
  .addStage(applyForcePass)
  .addStage(disturbDyePass)
  .addStage(advectDyePass)
  .addStage(bloomGroup)
  .addStage(displayPass);
</code>

Delivery Challenges and Editor Solution

Multiple asset types (3D models, Lottie, sprite sheets, etc.) create challenges in mixed playback, material production, and format conversion. Crab’s dedicated editor provides import/export, format conversion, editing, and preview, allowing designers to produce assets that can be directly used at runtime.

Performance Metrics and Optimization

Key performance indicators include frame rate, draw‑call count, triangle count, memory usage, and jank rate. Optimizations involve locking engine frame rate, frustum culling, instanced rendering, reducing triangle count, using compressed textures, and simplifying shader logic.

Frame rate: limit logic and render cycles per second.

Draw calls: reduce via frustum culling or instancing.

Triangles: keep low by using simple geometry.

Memory: lower texture resolution or use compressed formats.

Jank: avoid dynamic loops and conditionals in shaders.

Crab also performs internal optimizations such as timely GPU resource release and minimizing WebGL state changes.

Conclusion

The article presented the self‑developed Crab animation engine, its architecture, rendering capabilities, practical use cases, delivery workflow, and performance tuning, aiming to provide useful insights for developers building game‑like animations on the web.

frontendRenderingGame developmentWebGLanimation engine
Kuaishou Frontend Engineering
Written by

Kuaishou Frontend Engineering

Explore the cutting‑edge tech behind Kuaishou's front‑end ecosystem

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.