Rendering in Retrospect · #11 of 12

What Years of Rendering Taught Me: Five Principles

Retrospective — written in 2026, looking back.

A Series, Compressed

This series started as a look back at what rendering work had actually taught me, as opposed to what I assumed it would. Across posts on physically based shading, shadows, lighting models, textures and memory, transparency, and — most recently — post-processing and building a custom engine for a personal autobattler project, the same lessons kept resurfacing in different costumes. This post distills them into five principles. They are not rules I read somewhere. They are the mistakes I have made often enough that the correction finally stuck.

Principle One: Measure First

Every performance story in this series began the same way: I was sure I knew what was slow, and I was wrong. The reflection probe update that was obviously the problem, until a capture showed the shadow pass dominating. The shader that was obviously too expensive, until the real cost turned out to be a state change forcing a pipeline stall.

Measure first means more than "use a profiler." It means forming the habit of attaching a number to a suspicion before acting on it, and of measuring on the hardware and scene that matter, not the convenient one. GPU timing is slippery — clocks boost and throttle, captures perturb the thing they observe, and an average frame time hides the hitches a player actually feels. So I look at distributions and worst frames, not just means, and I keep a small set of fixed test scenes so measurements across weeks are comparable.

// Illustrative pattern: scoped GPU/CPU timing regions, recorded per frame
// and summarized as percentiles — not just an average.
struct FrameTimings {
    void Record(const char* region, double ms) {
        samples[region].push_back(ms);
    }
    // Report p50 / p95 / max per region; a spike in p95 with a calm p50
    // is a hitch, and hitches are what people perceive.
};

The deeper point is epistemic: intuition about rendering performance is trained on the last bug, not the current one. Measurement is how I argue with myself productively.

Principle Two: Data Flow Before Shaders

When I wrote about textures, streaming, and material systems earlier in this series, the recurring insight was that most rendering problems are data problems wearing a shader costume. A beautiful BRDF fed by a normal map in the wrong space, a roughness texture authored against a different response curve, or albedo values baked with lighting already in them will look wrong no matter how correct the shader math is.

So I now start any feature by tracing the data: where is it authored, in what space and range, how is it compressed, what does compression do to its extremes, who transforms it, and what does the shader finally assume? Half the bugs I used to hunt for days in shader code are visible in ten minutes once that chain is written down. The companion mistake is designing the shader first and discovering that the data it wants is ruinously expensive to produce or store. Bandwidth and memory are the budget the shader spends; design the flow, then write the code that consumes it.

Principle Three: Budgets, Not Features

The posts on shadows and on many-unit rendering in my personal autobattler work both taught the same lesson from opposite ends: a feature without a budget is a liability. "We have cascaded shadow maps" is a feature statement. "Shadows get this much time and this much memory on our target scene, and here is what degrades first when we exceed it" is an engineering statement.

Budgets force the useful conversations early. What is the unit count the battle must hold? What resolution does the shadow map get at that count, and what do we trade — distance, cascade count, update rate — when it isn't enough? Budgets also make quality scalable instead of binary: a system designed with a budget usually has a knob; a system designed as a feature usually has a cliff. In personal projects the budget is simpler but no less real: my time. Every subsystem I described in the engine post has an explicit non-goal list, which is just a budget written in words instead of milliseconds.

Principle Four: Models Break at Edges — Know Where

Every model in this series is a useful lie with a boundary. The microfacet models behind modern PBR assume things about surfaces that thin films, fur, skin, and clear coats violate in different ways. Shadow mapping assumes a single depth from the light is enough, and acne and peter-panning live exactly where that assumption thins out. Tonemapping curves assume a display and a viewing environment. Even the fixed-step, deterministic simulation I built for the autobattler assumes I have enumerated every source of nondeterminism — an assumption a single unordered iteration order can quietly falsify.

Knowing a model means knowing its edges. I keep, for each major system, a short list of "this is where it breaks": grazing angles, very dark inputs, extreme emissive values, unit counts beyond the tested range, near-plane geometry. Most production bugs — and most personal-project bugs too — live on those lists. The fix is rarely a better model everywhere. It is usually a targeted correction at the edge, or an explicit decision that the edge case is out of scope and a clamp to keep it from contaminating everything else.

// Edge discipline in practice: guard the math where the model thins out.
// (Illustrative — avoiding a divide-by-near-zero at grazing angles.)
float NoV = max(dot(normal, viewDir), 1e-4);
float denom = 4.0 * NoV * max(dot(normal, lightDir), 1e-4) + 1e-4;

Small guards like these are not inelegance. They are the written record of where you know the model ends.

Principle Five: Taste Is a Technical Skill

The post-processing post made this argument at length, so here it is compressed: taste is trainable, testable, and technical. It is the ability to look at two nearly identical images and identify which difference matters and why — the highlight rolloff, not the overall brightness; the bloom radius, not the bloom strength. That ability improves with the same deliberate practice as any other skill in this series: side-by-side comparisons, reference study, revisiting decisions the next day with fresh eyes, and keeping the versions you rejected so you can articulate what was wrong with them.

Treating taste as a technical skill also means treating it as discussable. "It looks off" starts a conversation; "the grade is crushing the bottom stop and the skin tones are going green in the shadows" finishes one. In personal projects there is no art director to supply this judgment, which is exactly why building it is not optional.

How the Five Fit Together

These principles are not independent. Measurement is how budgets get enforced honestly. Data flow thinking is how you find a model's edges before your users or your future self do. Taste is what decides how a budget gets spent — which effect gets the milliseconds, which edge case matters visually, which measurement is worth acting on. If this series has a single thesis, it is that rendering is not a bag of techniques but a discipline of trade-offs made visible: you can see, in the final image, every decision you made and every one you avoided. The next post turns from looking back to looking forward — to the study plan I am setting myself next, in an area where I am very much a beginner again.