Day 41 - 🛠️ Behind the process - Writing an Art Style Guide

General / 26 June 2026

A style guide is for the decisions that happen when you’re not in the room. Its job is to make the same call get made the same way, regardless of who’s making it.


Why a style guide exists

Clarity. That’s really it. Not as a buzzword - as the actual problem a style guide solves. Without it, every artist on the team is making judgment calls based on their own interpretation of the brief, their own mental image of what the art direction means. Multiply that across ten artists, add external vendors, and by the time you’re mid-production you have a visual language that’s drifted in ten slightly different directions.

Art direction and a style guide aren’t the same thing. Art direction is the ongoing conversation with reviews, feedback, context shared in the room. A style guide is the written artifact of that conversation. It’s what works when the art director isn’t available, when a vendor joins three months in, when someone needs to make a call at 11pm and can’t ask anyone. Getting it down in writing isn’t about bureaucracy; it’s about making the team functional without requiring a bottleneck.


What actually goes in it

The most valuable thing a style guide can do is remove ambiguity through imagery. Words like "stylized" or "hyperreal" or "gritty sci-fi" mean something different to every person who reads them. If I say H.R. Giger, you immediately have an image in your head, the tone, the shape language, the level of surface detail, the palette. That’s the level of specificity a style guide should aim for.

In practice, the sections worth having are:

Intent and pillars. Two or three sentences on what the overall look is trying to communicate. Not a mood board but a decision. "This world should feel utilitarian and improvised, not designed." That’s a rule an artist can apply.

Source: Riot Games League of Legends VFX Style Guide (public).

Reference and counter-reference. The "do" is only half of it. Showing what the style is not, is equally strong imagery that sits in the wrong direction. This is often more useful than the positive reference alone. A counter-reference removes a whole class of wrong decisions in one image.

Do / Don’t examples from your own work. The moment you can show a correct asset next to an incorrect one from your own project, the guide becomes concrete. Developers understand this immediately in a way that external reference never quite achieves.

Source: Riot Games League of Legends VFX Style Guide (public).

Material and lighting conventions. What’s the roughness range for hero props? Are metals warm or cool? Does foliage get a detail normal? These decisions get made during look-dev and forgotten six months later unless they’re written down.

Source: Riot Games League of Legends VFX Style Guide (public).

Technical constraints. Texel density targets, polygon budgets per category, texture resolution rules. These belong in the style guide, not just in a separate tech doc nobody reads alongside it. More on this below.

The principle throughout: stick to the decisions, not decoration. A style guide stuffed with inspirational images and no rules is just a mood board. What teams need is a document that tells them what to do, again, clarity.


How I’d structure it

I’ll be upfront: I’m not an art director, so take this with that context in mind. But from the Tech-art and Material side, the structure that makes the most sense to me is macro to micro: start from the broadest strokes and get progressively more specific.

Start with intent (one page), move into overall shape language and silhouette, then materials and surface treatment, then lighting conventions, then the technical floor. That order mirrors how an artist actually approaches an asset: they establish the big read before honing in on the detail.

Visual-first throughout. Prose explains; images decide. For every rule you can state in words, find the image that makes it obvious. The "show the wrong version next to the right one" technique is underused, but it’s the fastest way to communicate where the line is.


The technical-art angle

This is where a TA can add something an art director often can’t: grounding the guide in what the engine actually enforces.

"Keep assets feeling grounded" is an art direction note. "All props target 5.12 px/cm texel density at the camera’s closest LOD distance" is a pipeline rule. Both matter, but only the second one is scriptable which means it’s the only one the validation pass can check automatically. The more of the style guide’s rules you can express as measurable constraints, the more of the guide you can enforce without relying on human review.

The goal is to make the style guide and the pipeline agree with each other. If the guide says one thing and the export validation flags something different, artists will learn to ignore one of them and it’s usually the guide. When they point in the same direction, consistency becomes structural rather than aspirational.

This connects directly to what I wrote about in Day 31 - 🛠️ Behind the process - How I Structure a Pipeline and Day 34 - 🛠️ Behind the process - Documenting Tools for Others: the pipeline and the documentation need to be a single coherent system, not two separate artifacts maintained independently.


Keeping it alive

The team owns it. Not one person, not the art director alone but the team. For that to work, everyone needs to see it as a shared resource rather than a top-down document handed to them. The practical way to get there is to build it collaboratively during look-dev, not write it after the fact and present it as law.

A style guide is a living document. When production reality diverges from it, and it will, the guide needs to catch up. Ignoring the gap is how you end up with a document that reflects the project as it was planned, not as it was built. That’s the version nobody trusts, and eventually nobody reads.

When the guide and production diverge significantly, the cost shows up as rework: assets that need to be redone because the rules shifted but weren’t communicated. Keeping the guide updated isn’t overhead - it’s how you prevent that cost from compounding.


Closing thought

A guide nobody reads is worth nothing. Write it to be used, not filed. That means keeping it short enough to actually reference, visual enough to be understood at a glance, and maintained well enough to still be accurate six months later. If it fails any of those three, it becomes wallpaper.

The best style guides I’ve seen are the ones that feel like they were written by people who had to use them.


© 2026 Stefan Groenewoud. All views are my own, not those of my employer.Day 34 - 🛠️ Behind the process - Documenting Tools for OthersDay 34 - 🛠️ Behind the process - Documenting Tools for Others:

Day 40 - ⚡️ Quick - Texture Compression BC7

General / 26 June 2026

BC7 is not lossless. Knowing what it compresses well and what it destroys is the difference between a clean pipeline and a subtle quality regression nobody can trace.


What BC7 Is

Every texture on your GPU has to live somewhere in memory, and uncompressed textures are expensive. A 1024² albedo at 8 bits per channel across four channels is 4 MB. Multiply that across every asset in a scene, and you're burning through VRAM and memory bandwidth fast.

Block compression fixes this by letting the GPU decode textures on the fly. Instead of storing raw pixel data, the texture is divided into 4×4 texel blocks, each encoded independently. The GPU decompresses each block in hardware at essentially zero cost.

source: reedbeta


BC7 is the highest-quality format in the BC family for standard 8-bit LDR textures. It stores 8 bits per pixel, same footprint as BC3, but delivers substantially better quality. The key difference is flexibility: BC7 has eight distinct encoding modes, and per block, the encoder picks whichever fits best. Whether that means subdividing the block into sub-regions, adjusting how many bits go to endpoints versus indices, or swapping the alpha channel with one of the RGB channels. That flexibility is what lets BC7 handle difficult blocks cleanly, sharp color transitions, complex gradients, without the banding you'd get from BC1 or BC3.

The trade-off is encoding time. BC7 can take minutes per texture at high quality settings. That's an offline cost, not a runtime one, so BC7 belongs in a precomputed asset pipeline, not anywhere that regenerates textures frequently.


What It Handles Well and What It Doesn't

Good candidates: albedo maps, color textures with smooth gradients, anything RGBA where quality matters. BC7 handles blocks with multiple color directions far better than older formats. BC1 and BC3 force all colors in a block onto a single line in RGB space. BC7 supports multiple lines per block, so sharp edges between very different hues that produce obvious artifacts in BC1 compress cleanly here.

Use something else for:

  • Single-channel data (roughness, AO, masks): BC4 is half the size and purpose-built for it.
  • Normal maps: BC5 stores two independent R8 channels for XY and derives Z in the shader. BC7 can store normals but you're wasting bits on a channel you don't need.
  • HDR textures: BC7 is LDR only. BC6H handles 16-bit float channels.

Compression is lossy throughout the BC family. The block encoding approximates the original image and you can't recover it exactly. For color textures this is rarely visible. For data textures carrying precise float-range values, it can matter more.


Practical Takeaway

For albedo and RGBA color maps, BC7 is the right default on DX11+/OpenGL 4.2+ hardware. Run it offline at max quality.

If encoding time is a bottleneck during development, BC3 works as a fast stand-in and you swap to BC7 for shipping builds. The quality difference is real but only matters in the final product.

Quick reference: BC4 for single-channel data, BC5 for normal maps (XY only), BC6H for HDR, BC7 where quality color compression or precision matters.


© 2026 Stefan Groenewoud — All views are my own, not those of my employer.

Day 39 - 🔬 Deep dive - Mesh Instancing

General / 24 June 2026

If you're submitting one draw call per rock, per tree, or per blade of grass - you're leaving a lot of performance on the table.


The Draw Call Problem

Every time the CPU tells the GPU to render something, that instruction is a draw call. The GPU itself is extremely fast at processing geometry. The bottleneck is usually the CPU-to-GPU communication: setting shader state, binding buffers, uploading constants, issuing the call. Do that ten thousand times per frame and you've burned your frame budget on overhead before the GPU has drawn a single interesting pixel.

This shows up hard with repeated geometry. A forest of a thousand identical trees, scatter rocks across a terrain, bolts on a piece of machinery. Individually cheap, collectively expensive if each one is its own draw call.


What Instancing Actually Does

GPU instancing lets you submit one draw call that renders N copies of the same mesh. Instead of re-issuing the draw command for each copy, you pass the GPU a buffer of per-instance data: transforms, colors, material parameters, and the GPU handles the repetition internally, in parallel.

The mesh data (vertex buffer, index buffer) is uploaded once. The instance buffer holds everything that differs between copies. The vertex shader receives both, reading the shared geometry and the per-instance transform to position each copy correctly.

Standard approach:
  for each tree:
    set transform
    issue draw call          <- N draw calls, N state changes

Instanced approach:
  upload instance buffer     <- N transforms, colors, etc.
  issue one draw call        <- GPU iterates internally

The GPU is built for this. Running the same shader across thousands of instances in parallel is exactly what it was designed to do.


Per-Instance Data

The instance buffer is more flexible than it first appears. Each instance can carry:

  • World transform - position, rotation, scale (typically a 4x4 matrix or a packed 3x4)
  • Color tint - subtle variation that breaks visual repetition without unique materials
  • Custom floats - wind phase offset, wetness, damage state, anything you want to vary per copy
  • LOD index - some renderers pack the LOD selection directly into the instance data

This is important because the common objection to instancing "but all my rocks look the same" is solved here. You don't need unique meshes or unique materials to get visual variation. You need per-instance parameters that drive variation inside a shared shader.


The Hard Constraints

Instancing has a few rules that are worth internalizing early:

Same mesh. All instances in a single draw call must share the same vertex and index buffer. If you have three rock variants, that's three instanced draw calls, not one; still a big win over thousands of individual calls, but not a single call.

Same material and shader. All instances must use the same shader pipeline state. Different blend modes, different textures bound to different slots: those break instancing and require a separate batch.

Depth sorting. Transparent or alpha-blended geometry needs to be sorted back-to-front before rendering, which complicates instancing. You can still instance transparent geometry, but you'll either need to sort the instance buffer CPU-side each frame, or accept artifacts. Opaque geometry has no such problem.

Culling. A naive instanced draw call submits all N instances regardless of visibility. For large counts, you want GPU-driven culling: a compute pass that reads the instance buffer, tests each instance against the frustum and occlusion data, and writes only visible instances to an indirect draw buffer. The GPU then executes the draw against that filtered list.


How This Sits in the Pipeline

Instancing changes what the CPU submits but doesn't change the GPU pipeline itself: the same vertex shader, rasterizer, pixel shader, and output merger stages all run as normal, just across many instances in parallel.

Where it interacts with earlier topics: a depth prepass works naturally with instanced geometry. You run the opaque instanced meshes through the Z-prepass first, same draw call, different render state; then the main pass benefits from Early-Z rejection across all instances. The combination is very effective for dense foliage or scatter geometry where overdraw would otherwise be significant.


In Practice

The decisions that matter in production:

  • Group by mesh and material first. Before reaching for instancing, make sure your asset pipeline isn't creating unnecessary material or mesh variants. Every unique combination is a separate batch.
  • Use color tint and parameter variation aggressively. A well-parameterized shader with per-instance floats for tint, scale noise, and phase offsets can make a thousand identical meshes read as varied without breaking the batch.
  • Lean on engine support. Unreal's Instanced Static Mesh (ISM) and Hierarchical Instanced Static Mesh (HISM) components handle the CPU-side batching, LOD selection, and culling for you. HISM adds a spatial tree for efficient frustum culling on very large counts. Unity's GPU instancing flag on materials does the same job on that side of the fence.
  • Watch for the batch-breaker. Dynamic lights, decals, or per-object material overrides are common culprits that silently break instancing and push assets back to individual draw calls. Profile and verify rather than assume.


Practical Takeaway

Instancing is one of the highest-leverage optimizations available for scatter geometry, foliage, and props. The constraint is sameness (same mesh, same material) but per-instance data gives you more variation than it looks like on the surface. The real work is upstream: keeping your asset pipeline clean enough that instancing can actually happen.

This is my interpretation based on what I've read in books and online; things do change as technology evolves.

© 2026 Stefan Groenewoud - All views are my own, not those of my employer.

Day 38 - ⚡️ Quick - Texel Density - Targets and Validation

General / 23 June 2026

Part 2: what targets to aim for, how LODs change the picture, and how to automate the check.


In Practice

There are no universal targets, but the numbers that come up consistently across the industry follow camera distance:

Each step roughly halves as the camera gets further from the asset - which tracks with how mip levels work. A first-person weapon can fill 40% of the screen; a crate in a third-person game rarely does.

These are per-category baselines, not absolutes. A AAA first-person game might push hero weapons to 20+ px/cm while background architecture sits at 5. An open-world title might cap everything at 5 purely for memory budget, regardless of perspective. The right number is the one that fits your project's constraints - these are just a starting point for setting your validation range.


Validation

The formula from Part 1 is most useful when you run it across an entire asset set rather than checking meshes one at a time. The goal is a pipeline pass that computes TD for every asset and flags anything outside your target range.

In practice this means defining a minimum and maximum threshold per asset category - for example, props in a third-person game might target 4.5-6.0 px/cm, with anything below flagged as too low-resolution and anything above flagged as wasting memory budget. The script compares each result against those bounds and outputs a report or fails the build step.

This catches two common problems: artists who unwrap efficiently but use the wrong texture resolution, and assets that were authored for a different project or camera distance and pulled in without a rescale. A single number per asset makes both of those immediately visible.


Level of Detail

TD requirements relax as LOD level increases, because the asset is further from the camera and occupies fewer screen pixels. The same logic that sets your L0 target also tells you how aggressively you can drop at each subsequent level - if your L0 target is 5.12 px/cm and each LOD step roughly doubles the draw distance, you can halve the TD at each step. L1 at 2.56 px/cm, L2 at 1.28 px/cm, and so on, until the geometry is simple enough that the texture is no longer the limiting factor.

In practice this means your validation thresholds should be per-LOD, not per-asset. Flagging an L2 mesh for low texel density against an L0 threshold will produce false positives on every asset. Set a target range for each LOD level separately.

For very distant LODs, a reprojection or distant-LOD bake is often more practical than trying to maintain a coherent UV layout - at that distance the asset is gestural anyway, and a flat projection onto a small atlas tile is faster to author and cheaper to render than a properly unwrapped mesh.

© 2026 Stefan Groenewoud - All views are my own, not those of my employer.

Day 37 - ⚡️ Quick - Texel Density Calculations

General / 22 June 2026

How to measure whether your assets are consistent - and catch the ones that aren't before they ship.


What Is Texel Density?

Texel density measures how many texels map to one unit of real-world surface area. It's the consistency metric that ensures assets read correctly relative to each other at the same distance - if a wall has 10 texels per centimeter and a crate next to it has 2, the crate will look blurry by comparison even at the same resolution.


Why does it matter?

Higher texel density doesn't automatically mean better quality. There's a ceiling set by how many pixels the asset actually occupies on screen. A cup in a third-person game might only cover a small region of the viewport regardless of how dense its texture is - forcing a higher mip doesn't recover detail, it just introduces aliasing. The texture is already resolving finer than the screen can show.

The budget argument compounds this. Standardizing at an unnecessarily high texel density bloats texture memory and file sizes across every asset in the project. Large textures that stay resident in VRAM have a direct cost on GPU performance. Getting texel density right is about matching the resolution of your textures to what the game can actually use - not maximizing it.

source: renderhub.com


Formula

def CalculateTexelDensity(inMesh, inWidth, inHeight):
    return sqrt(
        sum(GetUVArea(inMesh) * (inHeight * inWidth)) /
        sum(GetSurfaceAreas(inMesh)))


Where:

  • GetUVArea - UV area of the mesh in UV space (0-1 range)
  • inWidth / inHeight - texture resolution
  • GetSurfaceAreas - world-space surface area of the mesh


© 2026 Stefan Groenewoud - All views are my own, not those of my employer.

Day 36 - 🔬 Deep dive - LOD Pipelines - The Math Behind the Decisions

General / 19 June 2026

One measurement decides when a mesh swaps to a cheaper version. Get it wrong and assets pop or waste budget. Here's how to size an object from every angle and turn that into a draw distance.

Welcome back to Part 2 of our discussion on level of detail meshes. This one builds directly on the first post, so it's a little shorter. In Part 1 we calculated the switch distance from a single number: the diagonal of the object. That works for roughly cubic assets, but it falls apart the moment something is thin or flat, because a player in a game sees the mesh from every direction, not just along its longest line.


Why one angle isn't enough

The diagonal is dominated by the largest dimensions. For a flat asset, a wall panel, a sign, a leaf, the diagonal makes the object look bigger than it ever appears side-on, and the LOD lingers at full detail longer than it should. Measure only the thin face and you get the opposite problem: the asset drops to a cheaper mesh while it's still filling a chunk of the screen.

The fix is to stop trusting one number. I measure the object from a few representative directions, full frontal, full side, full top, plus the 3D diagonal, and feed an aggregate of those into the distance calculation. Thin and flat objects get a fairer reading because the orientation the artist happens to place them in no longer dominates the result.


Measuring from every side

Each face measurement is just a 2D diagonal of two dimensions; the overall diagonal is the 3D version. Pull the width, height and depth from the asset's bounding box, and wrap each one in Abs() so a bounding-box corner with negative coordinates can't flip a sign on you (thanks to the reader who flagged that on Part 1).

Correction to Parts 1 and 2: the earlier posts computed these with a cube root and reported the diagonal as 0.387 m. That was my mistake. A diagonal is the square root of the sum of squares, no matter how many dimensions you combine, that's just the Pythagorean theorem extended to 3D. Cube-rooting squared metres doesn't even return a length (the units come out as m^⅔). The numbers below are the corrected versions.

pseudo code
diagonal = √(0.075² + 0.157² + 0.167²) = 0.241 m
front    = √(0.075² + 0.157²)          = 0.174 m
side     = √(0.157² + 0.167²)          = 0.229 m
top      = √(0.075² + 0.167²)          = 0.183 m
mean = (0.241 + 0.174 + 0.229 + 0.183) / 4 = 0.207 m


Mean or max?

Taking the mean is the obvious first move, and it's what I shipped originally. But in hindsight I'm not sure it's the right aggregate. Averaging pulls the input toward the smaller faces, so for an object whose dimensions vary a lot, the mean sits well below the diagonal and the LOD fades out sooner than it should.

If the spread between the smallest and largest measurement is wide, the maximum is probably the safer choice: it keeps the higher-detail mesh on screen until the object is genuinely small from every angle, which is usually what you want visually. The mean is the more aggressive, more memory-friendly option. Neither is universally correct, it's a quality-versus-cost dial, and the right setting depends on how forgiving your asset class is about popping. I'd expose it rather than hard-code it.


From size to distance

Once you have a size, the distance is trigonometry. The function below answers a single question: how far from the camera does an object of this size occupy a given percentage of the screen?

CalculateDistance(inSize, inScreenSpacePercentage=100, inFovDegrees=45.0, inVerticalResolution=1080):
   ratio = inVerticalResolution / 1080 
   # distance at which the asset fills the screen top to bottom
   distance_to_object     = (inSize * 0.5) / tan(radians(inFovDegrees * 0.5))
   distance_by_screensize = distance_to_object * 100 / inScreenSpacePercentage
   max_distance           = distance_by_screensize * ratio
   return max_distance


Walking through it: distance_to_object is the distance at which the asset exactly fills the vertical field of view, half the object's height over the tangent of half the FOV, the standard "fit a height into an angle" relation. The inScreenSpacePercentage term then scales that out; if you only want the asset to occupy 50% of screen height before switching, it needs to sit twice as far away, hence 100 / percentage. Finally the resolution ratio nudges the distance for higher-density displays, on the assumption your screen-space target was tuned at 1080p.

One implementation gotcha worth calling out, because it bit me: tan() in most math libraries expects radians, not degrees. Feed it 45 * 0.5 directly and every distance comes out roughly 35% wrong. Convert the FOV first, that's the radians() call above.

Plug the mean (0.207 m) into a 90% screen target at a 45° FOV and you get a switch distance of about 0.28 m, the point where this small asset stops being rendered at L0. From there the rest of the chain follows a rule of thumb from Part 1: each LOD step aims for a 50% triangle reduction, so doubling the distance per step mostly holds (L1 around 0.56 m, and so on). It breaks down once the poly count is already low enough that further reduction wrecks the silhouette or the normals, at which point the math stops being the limiting factor and your eyes take over.


Why bother doing this by hand

Simplygon or Unreal will generate LODs for you, so why work it out yourself? Because knowing the math is what lets you debug the tool when its output looks wrong, tune the thresholds to a specific project instead of accepting generic defaults, and stay unblocked when licensing or budget takes the automated option off the table. The tool is faster; understanding why it picks a distance is what makes you useful when it doesn't.

Are there flaws here? Almost certainly, the resolution scaling in particular is a simplification, and the mean-versus-max question is still open. If you've solved either more cleanly, I'd genuinely like to hear it.


© 2026 Stefan Groenewoud - All views are my own, not those of my employer.

Day 35 - 📖 Learning - Week 5 Reflection

General / 19 June 2026

Block 3, first part: tools and pipelines.

Writing about pipelines this week forced me to articulate things I'd been doing on instinct. That's uncomfortable in a useful way. When you have to explain why you structure something a certain way, you quickly find out whether you actually have a reason or whether you've just been doing it out of habit.

I'm still working out what makes my approach to pipelines distinctly mine. Part of it comes down to how I weigh speed against quality: they're not always at odds, but when they are, which one gives first? That's not a question I had a clean answer to before this week. I'm not sure I have a clean answer now either, but I at least know it's a question worth being deliberate about.

The reflection on past decisions was probably the most useful part. Looking back at choices I made on previous projects and tracing why they played out the way they did is more grounding than any amount of forward planning. It's also humbling. There are a few things I'd clearly do differently. Noting that down matters.

One thing I want to keep pushing on is involving external input earlier. I have my own intuitions about what a pipeline needs, but those intuitions are shaped by my own context. End-users think differently. Knowing why something doesn't land for them is often more valuable than knowing why it does land for me.


What clicked

Thinking out loud about pipeline design, on the blog and in the posts, helped me surface assumptions I didn't know I was making. The "How I Structure a Pipeline" and "Documenting Tools for Others" posts in particular felt like they were doing real work: not just explaining a process, but actually refining how I think about it.


What flopped

No images. The NDA makes it impossible to show actual documentation examples or real pipeline screenshots from recent work, and most companies don't publish that kind of material either. It's kept close. So a lot of the posts this week were more abstract than I wanted them to be. I could describe the approach, but I couldn't show it. That's a meaningful limitation for this type of content.


Into next week

Block 3's second part shifts toward the more technical side of pipelines: LOD systems, texel density, mesh instancing, texture compression. More math, more numbers to justify the decisions. Looking forward to it.

© 2026 Stefan Groenewoud. All views are my own, not those of my employer.

Day 34 - 🛠️ Behind the process - Documenting Tools for Others

General / 18 June 2026

Good documentation isn't about explaining your tool. It's about making sure someone else can own it.

Imagine this: someone set up a tool before you arrived. No README, no comments, no context. It works, until it doesn't. And when it breaks, you're reverse-engineering someone else's decisions without knowing what constraints they were working under, what they tried first, or what the tool was never meant to do.

That's the cost of skipping documentation. Not a missed readme. Actual time and money, compounded every time someone new touches an existing system.

I wrote about this briefly on ArtStation a while back - going more into the practicalities and implications of doing documentation for others. This is the expanded version, with more practical detail on what I've actually seen go wrong and how I approach it now.


What Documentation Is Actually For

The default instinct is to write documentation for the tool. That's the wrong approach.

Documentation is for the person who comes after you or the one that has to use it daily. Whether that's a vendor picking up your texturing pipeline, a developer joining mid-project, or you eight months from now when you've forgotten how something works.

That means three things have to be true:

It has to be easily accessible. Information scattered across Confluence pages, Slack threads, and a half-cooked wiki is effectively lost. If someone has to ask where the docs are, they're not going to use them.

Who is your target audience. A developer picking up a tool for the first time needs different depth than someone who worked on the same engine for three years. Writing one document for both audiences means it's useful to neither.

It has to be maintained. Documentation that was accurate six months ago and hasn't been touched since is worse than no documentation. If a workflow changes and the docs don't, you're actively misleading the next person.

None of this is complicated in principle. The failure is usually in assuming that the document you wrote at launch is done.


What Goes Wrong

Most documentation failures fall into a small number of patterns, and they compound.

Ambiguous scope. The document doesn't say what it covers or who it's for. Is this the full material pipeline or just the blend workflow? Is it targeting artists or TAs? Readers spend time in the wrong section or miss the section they needed.

Inconsistent terminology. If the document says "master material" in one place and "base material" in another, and those mean the same thing, readers slow down to verify. If they mean different things and it's not explained, readers make assumptions, and those assumptions produce errors downstream.

Redundancy that creates skimming. When the same information appears in multiple places, readers learn to skim. Once skimming is the default reading mode, they start missing things that only appear once.

Complexity without hierarchy. Dumping advanced workflow steps alongside basic setup instructions treats all information as equally important. It isn't. A new team member trying to set up a material needs to get through basic setup before they can use height blending. Structure the content in the order someone would actually encounter it.

Not accounting for language. Depending on your vendor relationships, the people reading your documentation may not be native English speakers. Passive voice, idiom-heavy writing, and long compound sentences create unnecessary friction. This is easy to fix and almost always overlooked.


How I Structure It

The structure that's worked best for me is a two-axis hierarchy: topic and complexity.

Texturing & Materials/
├── Overview                        ← what this section covers, who it's for
├── Basic Workflow/
│   ├── Simple Material Blend
│   └── Bespoke Texturing
└── Advanced Workflow/
    └── Height Blend


Each section starts with a one-paragraph summary: what this covers, what you need to know first, and who it's relevant to. That alone eliminates most of the "I read the wrong section for 20 minutes" problem.

The complexity split is deliberate. Not every artist needs the advanced workflow. Separating them means the basic workflow stays clean, and the advanced section can go deeper without worrying about overwhelming someone who's just getting started.

For tooling documentation specifically, I add a fourth section: known limitations and open questions. If a tool has edge cases that produce bad output, or if there's a workflow that's undocumented because it wasn't finished, that belongs in the docs. The next person is going to hit those limits. Better they know upfront.


The Iteration Loop

Writing documentation once isn't enough. The loop that actually works:

1. Walk through it yourself, step by step. Follow every documented instruction exactly as written, as if you'd never used the tool. This catches gaps immediately: steps that seem obvious in your head but aren't on the page, missing prerequisites, wrong button labels.

2. Buddy check. Have someone else follow the same steps independently. Not someone who already knows the tool; someone who has the target knowledge level but not the tool-specific knowledge. Watch where they hesitate or ask questions. Those are the gaps.

3. Update when workflows change. Tie documentation updates to tool or pipeline changes, not to a separate review cycle. If a change ships without updated docs, the docs are already wrong.

4. Evaluate blockers in shared assets. If artists are running into issues with shared shaders or the material library, that's documentation signal as much as it's a tooling issue. Recurring questions usually mean the answer isn't where it needs to be.

The goal isn't a perfect document; it's a document that reduces confusion faster than confusion accumulates.


What I'd Do Differently

A few things I'd change if starting from scratch on a documentation project:

Proof of concept before writing. Before writing full documentation, show a one-page layout of the proposed structure and get sign-off. It's much faster to argue about organization at that stage than after ten pages are written.

Visuals from the start, not as an afterthought. Screenshots, annotated diagrams, short video walkthroughs: these reduce confusion significantly, especially for visual learners and non-native readers. Adding them retroactively is tedious. Building them in from the beginning is a better habit.

Delegate by department early. TAs shouldn't be writing the artist-facing workflow documentation and the developer-facing API documentation. Get the right people owning the right sections from the start. Coordination takes effort, but the output is documentation that actually reflects how each team uses the tool.

Don't get attached to what you wrote. Good documentation requires being willing to cut sections, restructure, and rewrite. If a section isn't working, the answer isn't to add more explanation; it's to reconsider the structure. Version control makes this low-risk.


The Real Cost of Getting It Right

Documentation feels like overhead until you're the one debugging a tool someone else built without any. Then it feels like the most valuable thing that could have existed.

The industry is built on handoffs: between teams, between studios, between projects, across years. Documentation is what makes those handoffs work. It's not a nice-to-have for polished pipelines. It's how you prevent the next person from starting from zero.

Build the habit early. Revisit it consistently. And write it for the person who wasn't in the room when the decisions were made.

© 2026 Stefan Groenewoud - All views are my own, not those of my employer.


Day 33 - 💬 Take - Technical Artists Need to Think Like Engineers

General / 17 June 2026

Technical artists sit between art and engineering. The bridge only holds if you can speak both languages fluently.

As a technical artist you are the liaison and bridge between the art department and the rendering or programming team. The value you bring comes from understanding both sides: artists want to push fidelity, programmers are keeping a close eye on performance. Being able to mediate between the two is worth its weight in gold. Not many people have the skill to do that.


More Than Tools and Pipeline

There's a common assumption that technical art is mostly about building tools and pipelines. Get the pipeline running, hand it off, done. But that's only part of the job.

You are often the one translating requests for new feature implementations, or exposing engine capabilities to your department, so the team can actually use them. "Automate that part of the pipeline" sounds simple, but getting it done means knowing how to communicate the ask to engineers clearly and effectively - so you actually get what you asked for.


Talking to Engineers

Engineers respond well to specificity. Vague requests get vague results, or worse, results that technically meet the spec but don't produce what you needed.

Knowing enough about how a system works to describe the problem at the right level of abstraction is a real skill. You don't need to write the code, but you do need to be able to say what you need it to do, what the constraints are, and why it matters. That gets you a better outcome faster.


Talking to Artists

The same principle applies in reverse. If you present a pipeline that doesn't map onto how artists actually work, you'll end up with a tool that nobody uses and eventually gets deprecated.

Understanding what kind of tools and solutions artists are actually looking for - before you start building - saves everyone time. The best way to find out is to ask, watch how they work, and iterate early rather than late.


The Broader Point

The technical part of technical art is obvious. The communication part is where a lot of the actual leverage is. Being able to move fluently between an engineering conversation and an art direction conversation, without losing context in either direction, is what makes a technical artist genuinely useful on a production team rather than just technically competent.


© 2026 Stefan Groenewoud - All views are my own, not those of my employer.

Day 32 - ⚡️ Quick - The Problem with Manual Texture Work at Scale

General / 16 June 2026

Manual processes that work fine for ten assets start failing silently at a hundred. Here is where scale exposes the cracks.


When Manual Works

Manual texturing is the right call when the asset count is low and the assets are mostly hero props, a handful of unique sets, anything where the craft is the point and you'd never want a script flattening your artistic intentions. At ten assets you can hold the whole set in your head, hand-fix an outlier, and re-export without it eating your week. The processes that serve you in the simple situations are also the ones that fail at a hundred.


Where It Breaks Down

Reworking textures at large scale is expensive, and not just the first time you author them. The real cost lands every time something upstream changes. If you switch the method you use to generate AO or normals and, for consistency's sake, you now have to revisit every asset, update it, re-export, and re-validate in-game. Same story if you need to alter the art-style. Do that by hand across a hundred assets and you're not spending an afternoon, you're spending months updating and iterating.

It is easy to miss a few assets here and there. A few assets quietly get missed, bake slightly differently, or drift out of spec, and nobody notices until it shows up in-engine in context, usually next to a correct one where the inconsistency is obvious. Manual work doesn't fail loudly at scale; it just gets a little bit wrong in a hundred places.


What Consistent Scale Actually Requires

The fix isn't "automate everything", that's its own trap (more on that in Day 30 - 💬 Take - Automation). It's consistency first: a defined process where every asset is authored, packed, and validated the same way, so a global change becomes one deterministic operation instead of a hundred manual ones. Only once the pipeline is defined and the edge cases are known is the repetitive part worth scripting.

You can try to 'speedrun' the iteration process but it will catch up to you later, because you simply missed catching the edge cases and how to solve them.


Example

On these bunker environment kits, my tech art director and I spent a few days defining the pipeline: getting the mesh bakes right, testing the system on four assets, locking down the edge cases. Once that was done, we processed 40 asset sets in two-weeks. The consistency we got out of it wasn't just faster than doing it by hand: it was more consistent than hand-authoring would have produced at all.

© 2026 Stefan Groenewoud - All views are my own, not those of my employer.