3D scenes
Write 3D in TSX with Bevy's names: a camera, lights, meshes and materials, moved by the frame, with 2D titles over them in the same composition.
You write 3D the way you write the rest of a composition: components whose props are computed from the frame. raea turns them into Bevy entities (meshes, lights, cameras, glTF models, post-processing) and draws them under the page, so titles, captions and a score sit over the 3D in the same file. You never write Rust or a render loop. A render and a live play of the same file draw the same thing, so what you check in a render is what plays.
A turntable
// check: frames 0,45,89
import { AbsoluteFill, Composition, Easing, interpolate, registerRoot, useCurrentFrame, useVideoConfig } from "raea";
import { Cylinder, Entity } from "raea/3d";
import { StandardMaterial } from "raea/pbr";
import { Cuboid, Mesh3d } from "raea/mesh";
import { AmbientLight, DirectionalLight } from "raea/light";
import { Quat, Transform } from "raea/transform";
import { Camera3d } from "raea/camera";
function Turntable() {
const f = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
const turn = (f / fps) * (Math.PI / 3); // a sixth of a turn a second
const z = interpolate(f, [0, durationInFrames - 1], [7, 5], { easing: Easing.out(Easing.cubic), extrapolateLeft: "clamp", extrapolateRight: "clamp" });
return (
<AbsoluteFill style={{ background: "#14161c" }}>
<Camera3d transform={Transform.fromXyz(0, 1.6, z).lookingAt([0, 0.5, 0])} projection={{ Perspective: { fov: (40 * Math.PI) / 180 } }} />
<AmbientLight brightness={160} />
<DirectionalLight transform={Transform.fromXyz(3, 5, 4).lookingAt([0, 0, 0])} illuminance={11200} shadow_maps_enabled />
<Entity transform={Transform.IDENTITY.withRotation(Quat.fromEuler("XYZ", 0, turn, 0))}>
<Mesh3d transform={Transform.fromXyz(0, -0.1, 0)}>
<Cylinder radius={1.4} half_height={0.1} />
<StandardMaterial base_color="#2b2f3a" perceptual_roughness={0.6} />
</Mesh3d>
<Mesh3d transform={Transform.fromXyz(0, 0.6, 0)}>
<Cuboid half_size={[0.45, 0.6, 0.45]} />
<StandardMaterial base_color="#e8b04a" perceptual_roughness={0.35} metallic={0.2} />
</Mesh3d>
</Entity>
<div style={{ position: "absolute", top: 80, width: "100%", textAlign: "center", fontFamily: "Inter", fontWeight: 800, fontSize: 72, color: "#ffffff" }}>
Made to be seen
</div>
</AbsoluteFill>
);
}
registerRoot(() => <Composition id="turntable" component={Turntable} width={1080} height={1080} fps={30} durationInFrames={90} />);raea render turntable.tsx --at 0,45,89 -o frames/
raea render turntable.tsx -o turntable.mp4What each part does:
Camera3dis the camera: where it is (transform) and its lens (projection, with the field of view in radians). The first camera in the tree is the one that renders.- Lights:
AmbientLightfills the shadows,DirectionalLightis a sun (its brightness in lux, here 11,200),shadow_maps_enabledturns its shadows on. Entitygroups parts so they move together. Turning the group turns the plinth and the box on it.Mesh3dis a visible thing: a shape (Cylinder,Cuboid,Sphere,Torusand others fromraea/mesh) and a material (StandardMaterialfromraea/pbr, withbase_color,perceptual_roughnessandmetallic).Transformplaces things:Transform.fromXyz(x, y, z),.lookingAt([x, y, z]), and rotations fromQuat.fromEuler. Y is up and units are metres, as in Bevy.- The
divafter the 3D is ordinary 2D layout, drawn over the scene.
The rules for 3D
- Every value comes from the frame. A spin is
(frame / fps) * speed, not a value kept between frames. That is what keeps a render and a live play the same. - Rotations are in radians.
Quat.fromEuler("XYZ", 0, Math.PI / 2, 0)is a quarter turn about Y. - Colours are hex strings. CSS variables don't reach the 3D.
- Name parts that come and go (
<Mesh3d name="lid">), so raea matches them across frames. - A part's shape is fixed for its life. Animate its transform, colour, opacity, emission, roughness or metalness; to change its shape, swap in another named part.
Checking a scene
raea scene lint turntable.tsx # parts floating, sunk into the floor, or clipping each other
raea scene light turntable.tsx # how the scene is lit, and whether the subject is exposed well
raea render turntable.tsx --review review/ # fixed cameras (hero, front, right, top, iso, low) in colour and clay--review writes the same scene from six fixed cameras, in colour and as untextured clay, so you can see the shapes
and the light without the materials getting in the way.
The 3D guides
The 3D guides go topic by topic, each with a complete composition to start from. Read one or two at a time:
raea guide bevy # the map of 3D topics
raea guide bevy/lights # one topic| Topic | Covers |
|---|---|
bevy/scene | The camera, camera moves, groups, 2D under and over 3D |
bevy/lights | Sun, bulbs, spots, ambient light, shadows, brightness |
bevy/materials | Colour, metal, glass, glow, clearcoat, textures, toon shading and outlines, decals |
bevy/meshes | Primitives, modelled solids, blended organic shapes, trees, terrain, rocks, 3D text |
bevy/animation | Spins, springs, reveals, model animation clips |
bevy/characters | Bodies posed by goals, motion capture, walking to a spot |
bevy/rooms | Interiors, furniture along walls, windows, daylight, sound placed in the scene |
bevy/games | 3D games: state, input, many entities, collisions, state machines |
bevy/assets | glTF models, pictures in space, where files go |
bevy/blender | Fixing an asset in Blender first: scale, pivot, heavy meshes, FBX to glTF |
bevy/splats | Gaussian splats trained from footage, and animating them |
bevy/liquids | Liquids and goo, simulated and baked |
bevy/post | Bloom, fog, tone mapping, exposure, lens effects, motion blur |
bevy/limits | How much a scene can hold, and what isn't there yet |
The same topics ship in the raea skill as 3d/*.md.