A software rasterizer using HTML5 Canvas2D with a THREE.js-compatible scene graph API. Every pixel is drawn by the CPU using a painter's-algorithm scanline rasterizer - no WebGL, no GPU.
# npm
npm install @xsyetopz/easel
# yarn
yarn install @xsyetopz/easel
# pnpm
pnpm install @xsyetopz/easel
# bun
bun install @xsyetopz/easel
# deno
deno add npm:@xsyetopz/easelJSR support: npx jsr add @xsyetopz/easel (npm), bunx jsr add @xsyetopz/easel (bun), deno add jsr:@xsyetopz/easel (deno)
import * as EASEL from "@xsyetopz/easel";
const [WIDTH, HEIGHT] = [800, 600];
const renderer = new EASEL.Renderer({
canvas: document.querySelector("canvas"),
width: WIDTH,
height: HEIGHT,
});
const scene = new EASEL.Scene();
const camera = new EASEL.PerspectiveCamera({
fov: 45,
aspect: WIDTH / HEIGHT,
near: 0.1,
far: 100,
});
camera.position.set(0, 2, 5);
scene.add(new EASEL.AmbientLight(0xffffff, 0.4));
const sun = new EASEL.DirectionalLight(0xffffff, 0.8);
sun.position.set(3, 5, 4);
scene.add(sun);
const box = new EASEL.Mesh(
new EASEL.BoxGeometry(1, 1, 1),
new EASEL.LambertMaterial({ color: 0xff4444 }),
);
scene.add(box);
function animate() {
requestAnimationFrame(animate);
box.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();EASEL.js mirrors the THREE.js API wherever it makes sense. If you know THREE.js, you already know the basics.
| Category | Classes |
|---|---|
| Core | Scene, Node, Group, Mesh, Raycaster, Clock |
| Cameras | PerspectiveCamera, OrthographicCamera |
| Lights | AmbientLight, DirectionalLight, PointLight, SpotLight, HemisphereLight |
| Materials | BasicMaterial, LambertMaterial, ToonMaterial, LineMaterial |
| Geometry | BoxGeometry, SphereGeometry, PlaneGeometry, CylinderGeometry, TorusGeometry, + 15 more |
| Textures | Texture, CanvasTexture, DataTexture, VideoTexture |
| Helpers | BoxHelper, GridHelper, AxesHelper, SpotLightHelper |
| Controls | OrbitControls |
| Animation | Animator, AnimationClip, Track |
| Math | Vector3, Matrix4, Quaternion, Color, Ray, MathUtils |
| THREE.js | EASEL.js | Reason |
|---|---|---|
Object3D |
Node |
Scene graph node |
BufferGeometry |
Geometry |
No GPU buffers |
WebGLRenderer |
Renderer |
Single renderer |
MeshBasicMaterial |
BasicMaterial |
"Mesh" prefix redundant |
MeshLambertMaterial |
LambertMaterial |
Same |
MeshToonMaterial |
ToonMaterial |
Same |
AnimationMixer |
Animator |
Plays clips |
KeyframeTrack |
Track |
All tracks are keyframe |
graph LR
ST["SceneTraversal"]
FC["FogCuller"]
PS["PainterSort"]
LB["LightBaker"]
R["Rasterizer"]
FB["Framebuffer"]
ST --> FC --> PS --> LB --> R --> FB
- Painter's algorithm - back-to-front sort by tile distance, Uint16 depth buffer for residual overlap
- Flat & Gouraud shading - per-face and per-vertex lighting, no per-pixel
- Affine UV mapping - no perspective correction (visible warping on large quads)
- Linear fog - per-vertex depth fog with configurable color, near, and far
- Integer screen coords -
(x + 0.5) | 0on projected vertices - 128x128 max texture - nearest-neighbor, no mipmaps
- 9-step opacity - discrete 0-8, not continuous alpha
bun run dev # Vite dev server + playground
bun run build # Production build
bun run test:run # Vitest (single run)
bun run typecheck # tsc --noEmit
bun run biome:check # Biome lint + formatContributions welcome. See CONTRIBUTING.md for workflow, coding guidelines, and the PR checklist.
All contributors are expected to follow the Code of Conduct.