Start here · Chapter 0
How to read this
What the parts of every chapter are for, how to read a figure, a stamp and a piece of code, and how to run ShadyTracer and its tests yourself, so that you can check what the chapters say against the real thing.
1What this tutorial is
ShadyTracer is a renderer: a program that makes pictures of 3D scenes, in a web browser, on the graphics card (the GPU). This tutorial explains how it works inside, from the first ray to every trick that makes it fast, for someone who wants to understand its code, build a renderer of their own, or improve this one. It sits beside the program and is not part of it.
It is not a manual. How to use ShadyTracer (build a scene, light it, render it) is in the program's own Help: press F1, or open Help › Contents.
Every chapter after this one has the same parts, in the same order. On a first reading, the maths, the code and what went wrong before can all wait: the idea and its figure carry the chapter.
- The question, in a line and a picture.
- The idea, in plain words beside a live figure. Paragraphs with a dot beside them move the figure when you scroll to them, or click them.
- The maths, folded away in a box. Every chapter reads without it: open it when you want the real equation, with every letter named.
- How ShadyTracer does it: pieces of its real code.
- Tricks and trade-offs, each stamped with how sure it is (section 5).
- What went wrong before: the bugs the code's own comments tell, and why the code looks the way it does because of them.
- Try it: things to do in the figures, and in ShadyTracer.
- Words from this chapter, and boxes with where to read more: the books and articles the ideas come from.
The mathsWhat a fold-out looks like
Like this: the equation, then each part of it named. For example, the brightness a lamp gives a surface straight in front of it, r away, in 3D (at a slant it gives less: chapter 1):
E = Ir2
- E
- the light arriving on each bit of the surface (its irradiance)
- I
- how strongly the lamp shines (its intensity)
- r
- the distance; why it is squared is section 3
2Reading a figure
The figures are drawn and computed in your browser while you read: each one is a small renderer of its own, written to follow ShadyTracer's rules, and where ShadyTracer has a piece of JavaScript that does the job (the formula for a shape, the way two shapes melt together, its random numbers, its lens: each explained in its chapter), the figure runs that very code.
The figures show the renderer at work, so here it is in brief (chapter 1 builds it). The picture is a grid of pixels. For each pixel the renderer sends a ray, a line with a start and a direction, out from the eye through that pixel: a camera ray. That is backwards to the way light goes, into the eye, and it is done on purpose: only the light that reaches the eye makes the picture, so the renderer starts where that light ends. It finds the first surface the ray hits, and from there sends a shadow ray towards each lamp: if nothing is in the way, the lamp lights that point. Then the ray bounces on, as light does, and the light it meets on the way is added up into the pixel.
The menu at the top right of every page chooses a style, the look of the pages and their figures. Within a style, the same colour and line mean the same thing in every figure. This is the key, drawn in the style you are reading in. Most of its entries you will meet in the next few chapters (a step of the march is a circle for a reason chapter 2 gives); come back to it when a figure puzzles you. A circled cross (⊕) is a handle: drag it with the mouse or a finger, or click it and use the arrow keys. A figure that is three-dimensional turns when you drag its background.
Below each figure: its controls (sliders, switches), and a line of text, its readout, that says in numbers what the figure is showing now. When a paragraph says "the readout", it means that line.
3The flat world
Most figures show a slice of a scene, seen from the side: a flat world. A slice can be drawn whole, and its shapes dragged. The ideas are the same as in ShadyTracer's 3D scenes, but a few numbers are not, and this figure shows why. A lamp sends its light out in every direction. In the flat world it spreads around a circle; in 3D, over a sphere.
The lamp's rays are drawn 10° apart, round the circle in the flat world and each way over the sphere in 3D. At a distance r = 1 the piece of surface catches 4 of the flat world's 36, and the square patch in 3D catches 4 × 4 = 16 (the dots where they cross it).
Drag the handle out to twice the distance. The circle round the lamp is 2πr long, twice as long now, so the same piece of surface catches half the rays, 2: the light falls as 1/r. A sphere's area is 4πr², four times as large at twice the distance, so in 3D the same patch catches half as many rays each way, 2 × 2 = 4, a quarter: 1/r². The flat world's light weakens more slowly than the real world's. (In between, the counts jump: a piece catches whole rays. The laws describe the light, which is spread evenly; the drawn rays only sample it.)
The same happens to other laws. A lamp shines every way, but a surface sends light out to one side only, its front: the light leaving a surface spreads over a half-circle in the flat world and over a half-sphere in 3D, so the numbers in its formula differ too (chapter 8 shows one). Wherever a formula changes between flat and 3D, the page says so, and the figures that need the real laws are 3D.
4Reading the code
Code is never copied into a page. Each piece is named (a file and a function, or a line it starts with), and your browser reads it from ShadyTracer's own files when the page opens, so it is the code as it is today. Above it: the file and its lines (a link to them on GitHub), the language, and a line of plain words saying what to look for. Here is a short one, the formula chapter 3 uses to melt two shapes together. You are not meant to follow it yet: it is here to show what a piece of code looks like on a page, and the line above it is what to read. Chapter 3 explains it.
ShadyTracer's code is in two languages. The parts that run on the main processor are JavaScript. The parts the graphics card runs are kernels written in WGSL, which sit inside the JavaScript files as long pieces of text. A label above each piece says which it is.
A short reference to a line, such as (where the program's version number is set), is a file name and a line number: follow it to see that line. Each piece of code was checked against the words around it when the page was written. If the code has changed since, the page says so above the piece ("changed since this page was checked"), and the words may no longer fit.
The comments in ShadyTracer's code are long, and they matter: they say why the code is written as it is, often by telling what went wrong before. The chapters quote them, and say so when a comment has gone out of date.
5How sure is each claim?
The people who write ShadyTracer hold every statement to one question: do I know this, or do I think it? Wherever it matters, the chapters say which, with a stamp. Point at a stamp to see its card.
Measured: someone ran it and wrote the number down. A speed, or anything about the picture the renderer makes, is measured on a real graphics card, and the page says whose and which card (chapter 14's cost of shadow rays is one). A count or a calculation can be measured by a program on any computer (in a program that imitates the GPU's own arithmetic, with its 32-bit numbers, say: chapter 4), and the page names that program. Either way it says the number and where it is recorded.
Read in code: the code does this, and the page links the line. It says what the code does, never what it costs: how long something takes is only ever measured.
Inferred: a reasonable guess, not yet tested, written as a guess. The page says what would test it.
Why not measure speed on any computer? Because ShadyTracer's speed is the graphics card's, and a computer without one can only imitate it, slowly and differently. So every number about speed comes from someone's own card; when a page says the user, as the project's own notes do, it means ShadyTracer's developer, the person who builds it and measures it on their own graphics cards. There are two, both made by NVIDIA, of the generations it calls Pascal (older) and Ampere (newer). They open ShadyTracer, choose View › Tests and press a test's button; a table of results appears with a Copy button, and that table is pasted back into the project's notes. The tests sit in folders by what they check, each with a Run all, and every row of a table ends in a verdict: PASS, CHECK or FAIL against the answer it knows in advance (chapter 29). Its first line names the exact code it ran, then the picture's size and the graphics card (). The code is named by its version and its revision, the exact saved state of the files, as the program's version file describes:
The tests themselves are rows in one file, lib/pathtracer/tests.js, so a new measurement is a new
row, not a new program (docs/TESTING.md
says why).
6Running ShadyTracer
The quickest way is the published copy: open shadytracer.com in a browser with WebGPU and press Open ShadyTracer. To run your own copy, and read and change its code, you need two common programs installed first: git, which fetches the code, and Python 3, whose one-line web server serves it (any web server will do). Then open a terminal, the window you type commands into (Terminal on a Mac, PowerShell on Windows, any terminal on Linux), and type these three commands, one at a time, pressing Enter after each:
git clone --recursive https://github.com/lampmaker/ShadyTracer.git cd ShadyTracer python3 -m http.server 8000
The first fetches the code into a new folder, the second goes into it, and the third starts a web server there,
which keeps running until you close the terminal. Then open http://localhost:8000/app.html in the browser
(http://localhost:8000/ is the site's front page, with a button to it): localhost is
your own computer, and 8000 the number the server listens on, so the address asks your own computer for the files
the server is serving. What it needs:
- A browser with WebGPU: Chrome or Edge 113 or later, Firefox 141 or later on Windows, or Safari 26 or later. Not sure? Open the published copy: its front page says, under its button, whether your browser can run it.
- Real graphics hardware, not a software imitation of it. The results in these chapters were measured on NVIDIA cards, which is where the numbers come from, not a demand of the program: ShadyTracer's README asks only for a real graphics card.
- A web server, even the one-line one above: opened straight from the disk
(
file://), the page does not work. ShadyTracer's README says why: WebGPU is only given to a page the browser counts as a "secure context", and one served fromlocalhostis. This tutorial is served the same way:http://localhost:8000/tutorial/. --recursive: the code is kept in a repository, a folder git keeps the history of, and two of the libraries it needs are repositories of their own inside it. In a copy made without--recursive,git submodule update --initfetches them.
Nothing of ShadyTracer's own is built or installed: the files in the repository are the program. If a scene you made stops the program
from starting, add ?safe to the address: it starts without compiling your scene, that is, without
turning it into code for the graphics card (and does so by itself after a start that never finished).
7Running the tests
This section is for when you change the code. ShadyTracer checks itself with small programs, run with
node, a program that runs JavaScript outside a browser (install it too). They check everything that
can be checked without a graphics card, and come in three kinds, by the end of their names:
*.test.mjs(any name ending in.test.mjs: the * stands for the rest of the name): plain checks, no graphics card. GitHub runs all of them on every proposed change (a pull request), and so should you before you propose one: the list is inCLAUDE.md, the repository's notes for whoever changes the code, and the check on GitHub reads it from there ().*.gpu.mjs: run the real kernels on a graphics card simulated in software (they need thewebgpupackage for node, installed withnpm i -g webgpu, and lavapipe, a graphics card done in software; each test says so when one is missing). Slow, and a check of correctness, never of speed.*.check.mjs: open the pages in a browser that a program drives, with no window (they need Playwright, a tool for that).
And one more, to run before every change: node lib/pathtracer/core/check-shaders.mjs. Most kernels' code
depends on the scene they draw (chapter 24), so it puts every kernel together with every example scene, as the
page would, and checks what a browser would otherwise only find when the page fails to
start. Its first comment says why it exists:
This tutorial has its own checks: node tutorial/test/tutorial.test.mjs (run on GitHub with the
others) holds every page, link, glossary word and piece of code to today's files and tests the figures' renderers
against answers worked out by hand; node tutorial/test/pages.check.mjs opens every page in every
style. And node tutorial/review/affected.mjs lists the chapters a change to ShadyTracer touches, so
the tutorial is read again whenever the code it explains changes.
8Try it
- In the flat world figure, set the distance to 1.0 and then to 2.0 (the handle or the slider). Count the bold rays that reach the piece each time (the readout counts them too).
- Open ShadyTracer, press F1, and find the page on signed distance functions (shapes described by how far each point is from them). Then read chapter 2 here: the Help says how to use them, the chapter how they work.
- In your own copy, run
node lib/pathtracer/core/check-shaders.mjs. Then openlib/pathtracer/core/lens.js, find the comment that starts/* a point of the lens's disc(inside the kernel text, afterLENS_WGSL), put a backtick in it, run the check again, and see what it says. Take the backtick out.