Documentation · 3D Guide
1 · A scene from nothing
This chapter assumes you have never written 3D code. By the end of it you will know what a scene is made of, why it is made of that, and you will have put a lit object on the screen in about fifteen lines. Every term introduced here is in the glossary as well.
Everything is triangles
A graphics card can draw exactly one shape: a triangle. Not a sphere, not a curve, not a cylinder — a triangle, with three corners. Everything you have ever seen rendered in real time was made of them.
That sounds like a crippling limitation and is the opposite. Three points are always flat, always convex, and always have a well-defined inside; four points can be none of those. So the hardware does one thing, does it in parallel by the million, and every curved surface is an approximation made of flat pieces. A sphere with 8 segments is a lumpy ball. The same sphere with 64 is smooth, because the pieces are now smaller than a pixel where it counts.
var coarse = Primitives.Sphere(radius: 0.5f, segments: 8, rings: 4); // 64 triangles, visibly faceted
var smooth = Primitives.Sphere(radius: 0.5f, segments: 64, rings: 32); // 4,096 triangles, round
Neither is "correct". A ball bearing in the middle of the screen wants the second; a hundred of them in the distance want the first, and the reader who cares about that is looking for chapter 8.
A mesh is two lists
A Mesh is where those triangles live, and it is two arrays rather than a list of triangles.
Positions— every corner in the object, each an x, y, z point. A corner is called a vertex.Indices— the triangles, as whole numbers that point into that array. Three indices make one triangle.
Corner 0 and corner 2 are each used by both triangles — stored once, named twice. That is the whole reason indices exist.
Why the indirection? Because corners are shared. A cube has eight of them, but six faces of two triangles each is thirty-six corner slots. Storing the position thirty-six times would waste memory and, worse, would mean moving one corner required finding and fixing all its copies. Instead, eight positions and thirty-six indices.
Building one by hand looks like this, and this is a real, complete mesh:
var triangle = new Mesh
{
Positions = [new(-0.5f, -0.5f, 0f), new(0.5f, -0.5f, 0f), new(0f, 0.5f, 0f)],
Indices = [0, 1, 2]
}.WithGeneratedNormals();
WithGeneratedNormals() works out which way the surface faces, which is what lighting needs;
chapter 5 is about that. You will rarely type positions out. The
shapes you would otherwise write from scratch are in
Primitives — box, sphere, cylinder, torus, plane, disc — and anything
complicated comes out of a modelling tool through GltfLoader.
The round ones take an arc as well as a size, so they are not only whole shapes: a sweep and a start angle
turn a cylinder into a pipe section, a sphere into a dome, a disc into a pie slice. Both are in degrees,
both are measured the same way about the same axis, and a partial shape is left open rather than capped,
because a lid is a Disc away and a hole cannot be cut back out of one.
Winding matters. The order of a triangle's three indices decides which side is the
front: counter-clockwise seen from outside, which is the glTF and OpenGL convention and the one every
shape in Primitives follows. It only becomes visible when you turn on
culling — and then a mesh wound the other way turns inside out.
Where things are
Positions are three numbers, and the axes point like this: +X is right, +Y is up, and +Z comes toward you out of the screen. So a model faces −Z, away from the viewer — which is worth remembering now, because chapter 4 aims things and −Z is the direction it aims.
The unit is whatever you decide. Nothing here is metres. What matters is that you are consistent, and that the ratio between your largest and smallest visible object stays sane — chapter 3 explains what "sane" means and what breaks when it is not. For scale: the demo's Contact film holds a 250-unit fighter, a 24,576-unit planet and a sun 900,000 units away in the same frame, and it works because the near plane was chosen deliberately.
Your first scene
Three objects and one assignment. A Mesh is the shape, a Material is what it is made of, a MeshNode puts the two together somewhere in the world, and a Scene is the tree of nodes the control draws.
- Put the control in a window.
- Build a scene and add one node to it.
- Hand the scene to the control. There is no step 4.
<Window xmlns="https://github.com/avaloniaui"
xmlns:a3d="using:Ava3D">
<a3d:Ava3DView x:Name="View" />
</Window>
using Ava3D;
using System.Numerics;
var scene = new Scene();
scene.Children.Add(new MeshNode(
Primitives.Sphere(0.5f),
new Material
{
BaseColor = new Vector4(1f, 0.77f, 0.34f, 1f), // r, g, b, a — 0 to 1, not 0 to 255
Metallic = 1f,
Roughness = 0.25f
}));
View.Scene = scene;
That is a lit, orbitable gold sphere. Nobody set up a camera, a light or a renderer: the control frames whatever it is given, starts with one directional light and an ambient environment, and picks the best renderer the platform will give it. All three are yours to take over when you want them — chapter 3 takes the camera, chapter 5 the lights, and How the renderer is chosen explains the third.
What just happened
You built a tree of objects on the UI thread and something else drew it on another thread. Between the two is a snapshot: once a frame, the control walks your scene and produces a flat, immutable draw list, and that is what crosses over. It is why you can add a hundred nodes in a loop with no lock and never see half a frame.
It is also why some things are the way they are — why a Mesh's arrays are init-only, why there
is no GPU device anywhere in the API, why the 3D clips and fades like any other control instead of
floating on top of your UI. How a frame happens is the whole story
and is worth reading once, though not yet.
Run it
The demo's first scene is this one, in one file of about forty lines. Every scene named in these chapters can be opened by name, and each one does a single thing.
AVA3D_SCENE="Hello cube" dotnet run --project samples/Ava3D.Demo.Desktop
First chapter 2 · Moving things about →