xiaohongshu
xiaohongshu
composable-svelte-graphics
3D graphics and WebGPU/WebGL rendering with Composable Svelte. Use when building 3D scenes, working with cameras, lights, meshes, materials, or implementing WebGPU/WebGL graphics. Covers Scene, Camera, Light, Mesh components, geometry types (box, sphere, cylinder, torus, plane), material properti...
Full skill instructions
State-driven 3D graphics for Composable Svelte using WebGPU/WebGL with Babylon.js.
Package: @composable-svelte/graphics
Purpose: Declarative 3D graphics with automatic WebGPU/WebGL renderer selection.
Technology Stack:
Renderer Selection:
Core Components:
Scene - Root container, manages renderer lifecycleCamera - Viewpoint and projectionLight - Illumination (ambient, directional, point, spot)Mesh - 3D objects with geometry and materialsState Management:
graphicsReducer - Pure reducer for all graphics statecreateInitialGraphicsState() - Initial state factoryimport { createStore } from '@composable-svelte/core';
import {
Scene,
Camera,
Light,
Mesh,
graphicsReducer,
createInitialGraphicsState
} from '@composable-svelte/graphics';
// Create graphics store
const store = createStore({
initialState: createInitialGraphicsState({
backgroundColor: '#1a1a2e'
}),
reducer: graphicsReducer,
dependencies: {}
});
// Track rotation for animation
let rotation = $state(0);
function rotateShapes() {
rotation += Math.PI / 4;
}
// Render 3D scene
<Scene {store} height="500px">
<Camera {store} position={[0, 4, 12]} lookAt={[0, 0, 0]} fov={45} />
<Light {store} type="ambient" intensity={0.4} />
<Light {store} type="directional" position={[5, 10, 7.5]} intensity={1.2} />
<Mesh
{store}
id="rotating-box"
geometry={{ type: 'box', size: 1.5 }}
material={{ color: '#ff6b6b', metallic: 0.7, roughness: 0.3 }}
position={[0, 1.5, 0]}
rotation={[0, rotation, 0]}
/>
</Scene>
<button onclick={rotateShapes}>Rotate 45°</button>
Purpose: Root container for 3D rendering. Manages Babylon.js engine lifecycle and syncs store state to the renderer.
Props:
store: Store<GraphicsState, GraphicsAction> - Graphics store (required)width: string | number - Canvas width (default: '100%')height: string | number - Canvas height (default: '600px')children: Snippet - Child components (Camera, Light, Mesh)Behavior:
rendererInitialized action with capabilitiesUsage:
<Scene {store} height="500px">
<!-- Children render here -->
</Scene>
State Synchronization: Scene uses manual subscription (like MapPrimitive) to avoid infinite loops:
Renderer Info: Access renderer info from store:
$store.renderer.activeRenderer // 'webgpu' | 'webgl'
$store.renderer.isInitialized // boolean
$store.renderer.capabilities // { maxTextureSize, ... }
$store.renderer.error // string | null
Purpose: Defines the viewpoint and projection for the scene.
Props:
store: Store<GraphicsState, GraphicsAction> - Graphics store (required)type: 'perspective' | 'orthographic' - Camera type (default: 'perspective')position: [number, number, number] - Camera position (required)lookAt: [number, number, number] - Target point to look at (required)fov: number - Field of view in degrees (default: 45, perspective only)near: number - Near clipping plane (optional)far: number - Far clipping plane (optional)Behavior:
updateCamera action on mountUsage:
<!-- Perspective camera (default) -->
<Camera
{store}
position={[0, 4, 12]}
lookAt={[0, 0, 0]}
fov={45}
/>
<!-- Orthographic camera -->
<Camera
{store}
type="orthographic"
position={[0, 10, 0]}
lookAt={[0, 0, 0]}
/>
Common Camera Positions:
position={[0, 0, 10]}, lookAt={[0, 0, 0]}position={[0, 10, 0]}, lookAt={[0, 0, 0]}position={[5, 5, 5]}, lookAt={[0, 0, 0]}position={[0, 2, 5]}, lookAt={[0, 1, 0]}Purpose: Add illumination to the scene. Supports multiple light types.
Light Types:
Uniform light from all directions (no position/direction).
Props:
type: 'ambient'intensity: number - Light intensity (0-1 typical, can exceed)color: string - Light color (hex or CSS color, default: '#ffffff')Usage:
<Light {store} type="ambient" intensity={0.4} color="#ffffff" />
Parallel rays from a specific direction (like sunlight).
Props:
type: 'directional'position: [number, number, number] - Light position (defines direction)intensity: number - Light intensitycolor: string - Light color (optional)Usage:
<Light {store} type="directional" position={[5, 10, 7.5]} intensity={1.2} />
Emits light in all directions from a point (like a light bulb).
Props:
type: 'point'position: [number, number, number] - Light positionintensity: number - Light intensityradius: number - Light radius/range (optional)color: string - Light color (optional)Usage:
<Light {store} type="point" position={[0, 3, 0]} intensity={1.5} radius={10} />
Cone-shaped light (like a flashlight).
Props:
type: 'spot'position: [number, number, number] - Light positiondirection: [number, number, number] - Light direction vectorangle: number - Cone angle in radians (default: Math.PI / 4)intensity: number - Light intensitycolor: string - Light color (optional)Usage:
<Light
{store}
type="spot"
position={[0, 5, 0]}
direction={[0, -1, 0]}
angle={Math.PI / 6}
intensity={2.0}
/>
Common Lighting Setups:
<!-- Three-point lighting (photography standard) -->
<Light {store} type="ambient" intensity={0.3} />
<Light {store} type="directional" position={[5, 5, 5]} intensity={1.0} /> <!-- Key -->
<Light {store} type="directional" position={[-3, 3, -3]} intensity={0.5} /> <!-- Fill -->
<Light {store} type="directional" position={[0, 2, -5]} intensity={0.3} /> <!-- Back -->
<!-- Outdoor scene (sun + ambient) -->
<Light {store} type="ambient" intensity={0.4} color="#87ceeb" />
<Light {store} type="directional" position={[10, 20, 10]} intensity={1.5} color="#fff8dc" />
<!-- Indoor scene (ambient + point lights) -->
<Light {store} type="ambient" intensity={0.2} />
<Light {store} type="point" position={[0, 3, 0]} intensity={1.0} radius={5} />
<Light {store} type="point" position={[5, 2, 5]} intensity={0.8} radius={4} />
Purpose: Render 3D objects with geometry and materials.
Props:
store: Store<GraphicsState, GraphicsAction> - Graphics store (required)id: string - Unique identifier (required)geometry: GeometryConfig - Geometry configuration (required)material: MaterialConfig - Material configuration (required)position: [number, number, number] - Position (required)rotation: [number, number, number] - Rotation in radians (default: [0, 0, 0])scale: [number, number, number] - Scale (default: [1, 1, 1])visible: boolean - Visibility (default: true)Lifecycle:
onMount: Dispatches addMesh actionupdateMesh actiononDestroy: Dispatches removeMesh actionUsage:
<Mesh
{store}
id="my-cube"
geometry={{ type: 'box', size: 1.5 }}
material={{ color: '#ff6b6b', metallic: 0.7, roughness: 0.3 }}
position={[0, 1, 0]}
rotation={[0, Math.PI / 4, 0]}
scale={[1, 1, 1]}
/>
Rectangular prism.
Config:
{ type: 'box'; size: number }
Example:
<Mesh
{store}
id="cube"
geometry={{ type: 'box', size: 1.5 }}
material={{ color: '#ff6b6b' }}
position={[0, 1, 0]}
/>
Spherical geometry.
Config:
{
type: 'sphere';
radius: number;
segments?: number; // Default: 32 (higher = smoother)
}
Example:
<Mesh
{store}
id="ball"
geometry={{ type: 'sphere', radius: 0.8, segments: 32 }}
material={{ color: '#4ecdc4', metallic: 0.8, roughness: 0.2 }}
position={[0, 1, 0]}
/>
Segments: Higher values create smoother spheres but increase draw calls.
Cylindrical geometry.
Config:
{
type: 'cylinder';
height: number;
diameter: number;
}
Example:
<Mesh
{store}
id="pillar"
geometry={{ type: 'cylinder', height: 2, diameter: 1 }}
material={{ color: '#95e1d3' }}
position={[0, 1, 0]}
/>
Donut-shaped geometry.
Config:
{
type: 'torus';
diameter: number; // Outer diameter
thickness: number; // Tube thickness
segments?: number; // Default: 32
}
Example:
<Mesh
{store}
id="ring"
geometry={{ type: 'torus', diameter: 1.5, thickness: 0.3, segments: 32 }}
material={{ color: '#f38181', metallic: 0.9, roughness: 0.1 }}
position={[0, 1, 0]}
/>
Flat rectangular surface.
Config:
{
type: 'plane';
width: number;
height: number;
}
Example:
<!-- Ground plane (rotated to horizontal) -->
<Mesh
{store}
id="ground"
geometry={{ type: 'plane', width: 12, height: 12 }}
material={{ color: '#aa96da', metallic: 0.3, roughness: 0.7 }}
position={[0, 0, 0]}
rotation={[Math.PI / 2, 0, 0]}
/>
Note: Planes are initially vertical (facing Z-axis). Rotate by [Math.PI / 2, 0, 0] to make horizontal (ground).
MaterialConfig Interface:
interface MaterialConfig {
color: string; // Hex or CSS color
metallic?: number; // 0-1 (default: 0.5)
roughness?: number; // 0-1 (default: 0.5)
emissive?: string; // Emissive color (optional)
alpha?: number; // 0-1 transparency (optional)
wireframe?: boolean; // Wireframe mode (default: false)
}
PBR Workflow: Materials use Physically Based Rendering (PBR) with metallic/roughness workflow.
Controls how metal-like the surface appears.
0.0: Non-metallic (plastic, rubber, wood, stone)0.5: Semi-metallic (painted metal, worn surfaces)1.0: Fully metallic (polished metal, chrome)Examples:
// Plastic
{ color: '#ff0000', metallic: 0.0, roughness: 0.5 }
// Painted metal
{ color: '#4ecdc4', metallic: 0.5, roughness: 0.4 }
// Polished chrome
{ color: '#ffffff', metallic: 1.0, roughness: 0.1 }
Controls surface smoothness/reflectivity.
0.0: Mirror-smooth (glossy, high reflections)0.5: Semi-rough (satin finish)1.0: Very rough (matte, diffuse)Examples:
// Glass/mirror
{ color: '#ffffff', metallic: 0.0, roughness: 0.0 }
// Satin finish
{ color: '#ff6b6b', metallic: 0.3, roughness: 0.5 }
// Matte rubber
{ color: '#333333', metallic: 0.0, roughness: 1.0 }
// Polished gold
const gold = { color: '#ffd700', metallic: 1.0, roughness: 0.2 };
// Brushed aluminum
const aluminum = { color: '#c0c0c0', metallic: 1.0, roughness: 0.4 };
// Copper
const copper = { color: '#b87333', metallic: 1.0, roughness: 0.3 };
// Wood
const wood = { color: '#8b4513', metallic: 0.0, roughness: 0.8 };
// Plastic
const plastic = { color: '#ff6b6b', metallic: 0.0, roughness: 0.4 };
// Stone
const stone = { color: '#808080', metallic: 0.0, roughness: 0.9 };
// Rubber
const rubber = { color: '#1a1a1a', metallic: 0.0, roughness: 1.0 };
Full scene with all geometry types:
<script lang="ts">
import { createStore } from '@composable-svelte/core';
import {
Scene,
Camera,
Light,
Mesh,
graphicsReducer,
createInitialGraphicsState
} from '@composable-svelte/graphics';
// Create graphics store
const store = createStore({
initialState: createInitialGraphicsState({
backgroundColor: '#1a1a2e'
}),
reducer: graphicsReducer,
dependencies: {}
});
// Track rotation for animation
let rotation = $state(0);
function rotateShapes() {
rotation += Math.PI / 4;
}
</script>
<!-- Renderer info -->
<div>
{#if $store.renderer.isInitialized}
<span>Renderer: {$store.renderer.activeRenderer?.toUpperCase()}</span>
<span>Max Texture: {$store.renderer.capabilities.maxTextureSize}px</span>
{:else if $store.renderer.error}
<span>Error: {$store.renderer.error}</span>
{:else}
<span>Initializing...</span>
{/if}
</div>
<!-- 3D Scene -->
<Scene {store} height="500px">
<Camera {store} position={[0, 4, 12]} lookAt={[0, 0, 0]} fov={45} />
<Light {store} type="ambient" intensity={0.4} color="#ffffff" />
<Light {store} type="directional" position={[5, 10, 7.5]} intensity={1.2} color="#ffffff" />
<!-- Row 1: Box, Sphere, Cylinder -->
<Mesh
{store}
id="box"
geometry={{ type: 'box', size: 1.5 }}
material={{ color: '#ff6b6b', metallic: 0.7, roughness: 0.3 }}
position={[-4, 1.5, 0]}
rotation={[0, rotation, 0]}
/>
<Mesh
{store}
id="sphere"
geometry={{ type: 'sphere', radius: 0.8, segments: 32 }}
material={{ color: '#4ecdc4', metallic: 0.8, roughness: 0.2 }}
position={[-1.5, 1.5, 0]}
rotation={[0, rotation, 0]}
/>
<Mesh
{store}
id="cylinder"
geometry={{ type: 'cylinder', height: 2, diameter: 1 }}
material={{ color: '#95e1d3', metallic: 0.6, roughness: 0.4 }}
position={[1, 1.5, 0]}
rotation={[0, rotation, 0]}
/>
<!-- Row 2: Torus, Plane -->
<Mesh
{store}
id="torus"
geometry={{ type: 'torus', diameter: 1.5, thickness: 0.3, segments: 32 }}
material={{ color: '#f38181', metallic: 0.9, roughness: 0.1 }}
position={[3.5, 1.5, 0]}
rotation={[0, rotation, 0]}
/>
<!-- Ground plane -->
<Mesh
{store}
id="plane"
geometry={{ type: 'plane', width: 12, height: 12 }}
material={{ color: '#aa96da', metallic: 0.3, roughness: 0.7 }}
position={[0, -0.5, 0]}
rotation={[Math.PI / 2, 0, 0]}
/>
</Scene>
<button onclick={rotateShapes}>Rotate All Shapes 45°</button>
interface GraphicsState {
// Renderer
renderer: {
activeRenderer: 'webgpu' | 'webgl' | null;
isInitialized: boolean;
capabilities: {
supportsWebGPU: boolean;
supportsWebGL: boolean;
maxTextureSize: number;
maxVertexAttributes: number;
};
error: string | null;
};
// Scene
scene: SceneNode;
backgroundColor: string;
// Camera
camera: CameraConfig;
// Lights
lights: LightConfig[];
// Meshes
meshes: MeshConfig[];
// Animations (future)
animations: AnimationState[];
// Loading
isLoading: boolean;
loadingProgress: number; // 0-1
}
type GraphicsAction =
// Renderer
| { type: 'rendererInitialized'; renderer: 'webgpu' | 'webgl'; capabilities: RendererCapabilities }
| { type: 'rendererError'; error: string }
// Camera
| { type: 'updateCamera'; camera: Partial<CameraConfig> }
| { type: 'setCameraPosition'; position: Vector3 }
| { type: 'setCameraLookAt'; lookAt: Vector3 }
// Mesh
| { type: 'addMesh'; mesh: MeshConfig }
| { type: 'removeMesh'; id: string }
| { type: 'updateMesh'; id: string; updates: Partial<MeshConfig> }
| { type: 'setMeshPosition'; id: string; position: Vector3 }
| { type: 'setMeshRotation'; id: string; rotation: Vector3 }
| { type: 'setMeshScale'; id: string; scale: Vector3 }
| { type: 'toggleMeshVisibility'; id: string }
// Light
| { type: 'addLight'; light: LightConfig }
| { type: 'removeLight'; index: number }
| { type: 'updateLight'; index: number; light: Partial<LightConfig> }
// Scene
| { type: 'setBackgroundColor'; color: string }
| { type: 'clearScene' };
Graphics reducer is pure and testable:
import { graphicsReducer } from '@composable-svelte/graphics';
import { TestStore } from '@composable-svelte/core';
const store = new TestStore({
initialState: createInitialGraphicsState(),
reducer: graphicsReducer,
dependencies: {}
});
// Add mesh
await store.send({
type: 'addMesh',
mesh: {
id: 'test-cube',
geometry: { type: 'box', size: 1 },
material: { color: '#ff0000' },
position: [0, 0, 0]
}
}, (state) => {
expect(state.meshes.length).toBe(1);
expect(state.meshes[0].id).toBe('test-cube');
});
// Update position
await store.send({
type: 'setMeshPosition',
id: 'test-cube',
position: [1, 2, 3]
}, (state) => {
expect(state.meshes[0].position).toEqual([1, 2, 3]);
});
Segments: Higher segment counts create smoother geometry but increase draw calls.
// Low poly (fast, retro look)
geometry={{ type: 'sphere', radius: 1, segments: 16 }}
// Default (good balance)
geometry={{ type: 'sphere', radius: 1, segments: 32 }}
// High poly (slow, smooth)
geometry={{ type: 'sphere', radius: 1, segments: 64 }}
Each mesh = 1 draw call. Minimize meshes for better performance.
Good:
// 3 meshes = 3 draw calls
<Mesh id="obj1" ... />
<Mesh id="obj2" ... />
<Mesh id="obj3" ... />
Bad:
// 1000 meshes = 1000 draw calls (very slow!)
{#each items as item}
<Mesh id={item.id} ... />
{/each}
Solution: Use instancing for many similar objects (future feature).
Scene sync uses deep comparison (JSON.stringify). Avoid updating mesh props every frame.
Good:
// Update rotation only when button clicked
let rotation = $state(0);
function rotate() { rotation += Math.PI / 4; }
<Mesh rotation={[0, rotation, 0]} ... />
Bad:
// Updates every frame (60 FPS) - expensive!
let time = $state(0);
setInterval(() => { time += 0.01; }, 16);
<Mesh rotation={[0, time, 0]} ... />
Solution: Use animation system (future feature) or throttle updates.
let rotation = $state(0);
function rotateObject() {
rotation += Math.PI / 4; // 45 degrees
}
<Mesh rotation={[0, rotation, 0]} ... />
<button onclick={rotateObject}>Rotate 45°</button>
let cameraDistance = $state(12);
function zoomIn() {
cameraDistance = Math.max(5, cameraDistance - 2);
}
function zoomOut() {
cameraDistance = Math.min(20, cameraDistance + 2);
}
<Camera position={[0, 4, cameraDistance]} lookAt={[0, 0, 0]} />
<button onclick={zoomIn}>Zoom In</button>
<button onclick={zoomOut}>Zoom Out</button>
let lightIntensity = $state(1.0);
function adjustBrightness(delta: number) {
lightIntensity = Math.max(0, Math.min(2, lightIntensity + delta));
}
<Light type="directional" position={[5, 10, 7.5]} intensity={lightIntensity} />
<button onclick={() => adjustBrightness(0.2)}>Brighter</button>
<button onclick={() => adjustBrightness(-0.2)}>Dimmer</button>
let showObject = $state(true);
// Option 1: Conditional rendering
{#if showObject}
<Mesh id="object" ... />
{/if}
// Option 2: Visible prop
<Mesh id="object" visible={showObject} ... />
<button onclick={() => showObject = !showObject}>
{showObject ? 'Hide' : 'Show'}
</button>
These features are planned but not yet implemented:
// Future API
<Mesh
id="custom"
geometry={{ type: 'sphere', radius: 1 }}
material={{
type: 'custom',
vertexShader: '...',
fragmentShader: '...',
uniforms: { time: 0.0 }
}}
position={[0, 0, 0]}
/>
// Future API
<Mesh
id="textured"
geometry={{ type: 'box', size: 1 }}
material={{
color: '#ffffff',
albedoTexture: '/textures/wood.jpg',
normalMap: '/textures/wood_normal.jpg'
}}
position={[0, 0, 0]}
/>
// Future API
<Mesh
id="animated"
geometry={{ type: 'box', size: 1 }}
material={{ color: '#ff6b6b' }}
position={[0, 0, 0]}
animation={{
property: 'rotation',
from: [0, 0, 0],
to: [0, Math.PI * 2, 0],
duration: 2000,
loop: true,
easing: 'linear'
}}
/>
// Future API
<Scene {store} postProcessing={{
bloom: { enabled: true, intensity: 0.5 },
ssao: { enabled: true, radius: 2 },
fxaa: true
}}>
...
</Scene>
Related Skills:
When to Use Each Package:
Scene not rendering:
graphicsReducer$store.renderer.errorObjects not visible:
lookAt prop)visible propPoor performance:
TypeScript errors:
@composable-svelte/graphics is installedbox requires size, not radius)[x, y, z]xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience