Interview with a Material Artist

General / 24 May 2022

This year I have been almost 10 years in the games industry and wanted to share my ideas and answer questions that I have gotten over the years but didn't get to answer. Hopefully my blogpost will inspire you and help you find your way within the industry or maybe even help you find your niche?

How did you get into such a cool IP as Horizon? What sort of brought you in this direction?
Back in 2012 I was already an intern at the studio, a couple of years before I rejoined in 2014, however during my internship I did see some early concept art for this new IP now called Horizon. When I was about to join the team I didn't know for sure which project I'd be working on but, as you can imagine, I had my suspicions. Back then I was mostly working on assets or environment-art but was also looking into creating shaders and material expressions. This technical interest landed me the shader/texture artist position and started delving deeper into this area of expertise over the last couple of years.


What was your general approach to assets in this production? You’ve had quite a tricky task, building all those amazing materials. How did you decide to tackle this?
During the concept phase there were already a whole lot of reference images available (collected by our talented Concept-Artist and Directors) but also my Art-Director had specifications of what he was looking for. The target was to blend this look from the proposed Concept Art and the requirements of the Environment-team/Art-Director(s) and of course I had my own input. From these reference images I have created a huge reference sheet with everything I found interesting per image and from there we picked and chose which characteristics we liked and added callouts to highlight what we felt was necessary to sell the idea of the materials. This really helps to get everyone on board with the exact look we were going for.For any artist I'd suggest; always try to collect images to build your own material library, this can be Pinterest or snapshots on holiday. I do this and then after one or two years, I delete everything and refresh my entire collection.

Ref images

Ref images


You were using Photoshop and ZBrush to craft all those amazing textures. Could you talk in more detail how it all worked?
During the development of previous projects we worked with high poly sculpts in Zbrush to generate detailed heightmap information from those. But when we started implementing Substance with a few textures to get a feel of the program and its workflow. For example with a gravel texture, we generated tiny pebbles and added multiple stacks with offsets and a variety of scaling to make it look more interesting and finalize it with some photo overlays and color correction in Photoshop. 

No matter which program or tool we used, we always focused on getting the height information correct first, before diving into the Color and Roughness values too much. For some textures it felt more comfortable to generate the content in Zbrush as it gave me complete control per brick (or had to match with pre-existing assets/models), I was able to put each brick at an angle or give it height differences to give it some nice parallaxing effect. The downside was: it’s very time consuming. For texturing the albedo/diffuse we tried several approaches, for example: polypainting the bricks in zbrush but we had to keep such a high polycount that Zbrush became unworkable and too little poly density would result in a lack of detail. Then we used Photoshop but now that Substance expanded their libraries a lot is possible now, that wasn't before. I would've picked up a hybrid approach, generated high poly mesh and generated the diffuse and roughness in Substance.


You’ve mentioned that you choose Photoshop because of more control over the subtleties in color/height variation. Why was this more important to you? I mean you could have gotten very similar results Procedurally.
In hindsight I probably could have pulled off a similar result. As the height information was the most important to me, it really sold the textural details and state of the bricks and ultimately sold the believability of the material. In the reference images that were collected, it showed me the importance of all the states of decay that were having subtle tonal variety and height values.

Timelapse of focusing on the height information first.Before adding diffuse/roughness.


How did you make these materials tile in such a beautiful manner? Did you use some other tools to scatter the rocks here and other little things?

With a bit of planning and proper mesh setup, you can easily offset your subtools and align them so it’s tiling perfectly (especially now with Substance Designer in our arsenal). Getting the scale right versus the right amount of detail and uniqueness is tricky. Each brick was placed as a unique subtool, so it could easily get warped and moved around. We iterated many times on the brick layout to get the right feel before we proceeded with the Diffuse/Albedo/Roughness maps.

The scattering of rocks was a combination with custom Maya scripts where I could scatter kitbashed rocks or in Substance Designer. Scattering rocks with photo scanned data was interesting to familiarize yourself with generating procedural content and also match it with pre-existing photoreal content.


You’ve done some absolutely stunning work with the brick wall. It’s like the most favorite subject of every texture artist, but your material is something else. Can you tell us, how did you manage to build it in such a way that the brick wall actually has information about 3 types of bricks: old, worn down and new. 

Planning was very essential for this to succeed. First we started blocking out the intact version of the bricks and tested the look and feel of the layout in-game. We checked for scale, height variation, repeating elements - even a flat color in the albedo with some curvature and ambient occlusion information can help a lot visually to give a feel of the surface and readability over distance.

I then reworked the high poly sculpt and baked out maps for the first pass - I grab all the baked maps, e.g. Position to World Space Normals, custom mat caps in Zbrush. This gives me a wide variety of masking methods I can pick and choose from to create the tonal variety. Blending the Curvature map with the Position map and a random (brick) variation mask, created interesting variations. Next step is to apply more colors by adding photos, mask out bricks based on height or manually select them, add tonal gradients with the HSL slider/node for per brick subtle variations.

For the second material we used the exact same layout in Zbrush and started to replace bricks of the same size or used the well known Dam standard brush or Orb Crack brush combined with a custom alpha mask to split up the bricks or use the TrimSmoothBorder brush to soften the edges (as worn brick does over time). On certain bricks we would add some alpha stamps to make the brick look more damaged. Or by moving some bricks even lower and skewed which emphasized the aging process even more.


How did they help you to nail that beautiful hard surface stuff?

Maarten (Art Director) and I were looking for a way to speed up the texturing process but also maintain the quality that was pushed throughout the game. The two of us decided to delve deeper into the Substance packages and set up custom nodes and materials which also extended our internal Substance library. During this iteration process of creating nodes and testing them, we created a smart material that we could apply to almost all the assets. In 90% of the cases it would get us there and in some cases there were some tweaks needed but it sped up the art creation process quite a lot. Between the two of us we managed to export 45-ish component sets within two days with all the latest smart materials updated and correct masking for detail maps.


How did you work on those wonderful rusty elements in the production? How were these set up? What were the challenges in these assets?

The rusty element was an iterative process of creating custom Substance nodes. First, we started making generic materials with some light wear, tear and discoloration. In the second iteration, we started adding things like dust, dirt and rust. To get the realism we were looking for, we worked on custom mask generators, e.g. rust got stored into its own user-channel, which took Ambient Occlusion and Curvature in mind. With an additional custom node, we can generate streaks based on the rust mask user-channel, this gives us the drips and very long streaks.


Over all, to finalize, how did these materials help to tell the story in the environment? Why do you think they are even important for these humongous productions?

Material expressions are supposed to give the player the idea that they are in a believable world, that it becomes almost tangible. If a material looks ‘off’ it will break that illusion and snap the player right out of the immersion. The materials will tell the story the world is being lived in, it shows age and beauty. But also the interaction between materials, how water affects wood or metal for example or what erosion does to rocks or bricks. No matter how large the production environment is, you can do this kind of environmental storytelling in all sorts of ways.



Day 60 - 📖 Learning - Final Reflection

General / 30 July 2026

Sixty posts. Eight weeks. Shadow proxies to GGX. Here's what I can say now that I couldn't say on Day 1.


What 60 days produced

The output: 60 posts across four blocks. Pipelines & Industry, Rendering & Lighting, Tools & Pipelines, and Math, Code & Algorithms. Plus a closing synthesis week. The series moved from practical and accessible toward increasingly technical. That arc wasn't entirely planned. It followed what I was genuinely trying to understand.

The less obvious output is what it did to how I think. Daily posting creates a specific kind of pressure, not deadline pressure, but a quieter ongoing one. The decision to post every day becomes the default, and the question shifts from "should I write today" to "what am I writing today". That distinction matters.


What changed

Showing your knowledge publicly, including where it limits, is very daunting. I went in not trying to put pressure on hitting the widest possible audience. That was the right call for me. It kept me writing toward depth rather than reach, and the posts that went deep tended to find the right readers anyway.

The other shift was more internal. Writing to explain something forces a kind of precision that just doing the work doesn't. You find out quickly which parts of your understanding are solid and which were always a bit assumed. That's an uncomfortable thing to discover in public, and also the most useful part of the exercise.


What's next

I'm not sure yet and that is okay. One thing I want to do is use this series to figure out what I actually want to do next in my career, in what I build toward.

Sixty posts across this range of topics has been a kind of audit: what I know deeply, where I'm still working things out, and what I find genuinely interesting versus what I think I'm supposed to find interesting. That's a useful insight. I want to sit with it before committing to a direction.

The industry has changed too. When I graduated, companies flew candidates out to interview. I did an art test for a studio abroad early in my career, they loved it, offered to fly me in, then gave the role to an internal hire two days before I was supposed to travel. That kind of thing stings differently when you're just starting out and haven't built up the scar tissue yet.

What's interesting is that version of events barely exists anymore. Everything is digital. That's easier for everyone, practically. But it also means a studio can run a hundred candidates in parallel with no real commitment to any of them. The friction on their side has dropped to zero. The cost on the candidate's side while the art tests, the preparation, the emotional investment hasn't changed. Worth keeping in mind when deciding how much energy to put into a process that hasn't committed to anything.

Whatever comes next will be slower. Sixty posts in sixty days was sustainable but not comfortable. The right cadence is probably fewer posts, more time per post, and a tighter scope.

But that is a future problem. For now: sixty posts, eight weeks, done.


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

Day 59 - 💬 Take - Advice to Myself on Day 1

General / 29 July 2026

Three things. One I knew going in. One I only understood after it bit me. One I wish someone had said at the start.


The advice

Ship over polish. And commit to it. I wrote this rule before Day 1. Then I spent 61 workdays feeling like I was failing every time a post went out late. Posting too late in the day meant it would land technically on the next calendar day for readers overseas. That felt like a failure on my part. In hindsight it wasn't. Knowing the principle and feeling okay about it are different things. The posts I spent the most time on didn't land better than the ones I shipped fast. The gap between two hours and five hours is rarely visible to a reader.

Plan the visual first, not last. If a post needs a diagram, that shapes the writing, what you explain, how long it needs to be, what the image handles so the text doesn't have to. I kept treating visuals as something to find or create after the writing was done. That's the wrong order. The diagram is part of the thinking, not decoration added at the end. Instead of adding more content, I could have spent time on generating the visual examples. But that is sometimes a bit hard to balance with a full-time job.

Be easy on yourself. Don't let the pressure get to you. I skipped one day across 61 workdays, for a holiday. One day. I still beat myself up over it. The series didn't collapse. As a result i was more forgiving to myself when taking off a week when I was traveling. And to my knowledge nobody noticed. The standard you hold yourself to in a challenge like this can become its own obstacle.

That extends further than missing a post. I still feel awkward around calling myself a technical artist. Maybe as insecure, and as much like an imposter, as I was years ago. I thought putting 60 posts out publicly would settle something. It didn't. I'm not sure that's the kind of thing output fixes. But it hasn't stopped the work.


What I got right

The consistency held. In total, 61 workdays straight, one missed for a holiday, otherwise unbroken. That was the commitment and it delivered.

The format rotation also worked. Cycling between deep dives, quick posts, experiments, and reflections gave each topic the right amount of space and stopped the series from feeling repetitive.


What I'd tell anyone else starting

The hardest stretch is days 10 to 20. The novelty is gone and the habit isn't formed yet. Push through that struggle and it becomes mechanical in the best sense.

Write the post you wish had existed three years ago. That's the clearest filter for whether something is worth writing.

Most importantly, be easy on yourself.


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

Day 58 - 🔬 Deep dive - The One Concept I Understand Better Now

General / 28 July 2026

Energy conservation. I could follow the rules. I couldn't explain why breaking them breaks everything downstream.


Where I stood on Day 1

Lighting has never been my strongest area. I know how materials respond to it: how roughness, metalness, and F0 determine what gets reflected where. But I hadn't dived into the rendering side properly in a long time. I wrote custom viewport shaders, specific conversion nodes, etc. I'd dabbled with concepts like energy conservation, Fresnel, masking-shadowing. I hadn't gotten my hands into the actual math for over 3-5 years now, and only slightly understood why the rules are the rules.

That gap is part of why the rendering block of this series was the hardest to write. It's also where I learned the most.


What the concept actually connects

The principle: a surface cannot reflect more light than it receives. Diffuse and specular are complementary. As specular rises, diffuse falls. That constraint isn't arbitrary. It's the condition under which every downstream system produces predictable results.

A material that violates energy conservation injects extra energy into every bounce. Indirect light brightens in ways that are hard to trace. The error doesn't stay in the material slot. It propagates through the whole lighting chain.

This is why the albedo range exists. Why metalness is binary. Why F0 for dielectrics sits around 0.04. The rules are load-bearing. They're the conditions under which the lighting math works correctly, from the material outward through global illumination.


Where it clicked

Deeply investigating IBL and Energy Conservation made this concrete. The split-sum approximation precomputes the specular response under a fixed environment, and that derivation is built on the same energy conservation and Fresnel assumptions that PBR materials follow. The two systems are designed together.

A non-compliant material fed into a physically correct IBL produces incorrect output even if the environment capture was perfect. Writing Day 21 - 🔬 Deep dive - PBR Compliant Work and Day 22 - 🔬 Deep dive - How IBL Works in sequence made that connection visible. Trace the energy from the material through the BRDF through the precomputed LUT to the final pixel and you can see how each step depends on the previous one being correct.


Practical takeaway

Knowing how light and materials interact at this level isn't just academic. It explains why certain lighting conditions work better for specific material types, how the BRDF constrains what's artistically possible and where limitations come from. Understanding the rules tells you which ones can be bent and which ones break the whole chain when you push past them.


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

Day 57 - 🛠️ Behind the process - What I Learned from 60 Days of Writing

General / 27 July 2026

The topics are easy to list. What actually changed is harder to name precisely.


What the habit changed

Putting your thoughts and knowledge out there is daunting, especially as an introvert. In an office environment, sharing ideas with colleagues feels natural. Doing it in public is a different kind of exposure and pressure.

The biggest shift was discovering where my understanding was thinner than I thought. IBL is the clearest example. I'd implemented it, debugged it, used it in production. Then I tried to write a post explaining how it actually works, from cubemap capture through prefiltering to the split-sum approximation, and got stuck. The explanation revealed the gaps.

Some of these systems have also evolved since I first properly read about them. What I understood back then was correct for that moment. Writing forced me to dig deeper and verify, because the last thing I wanted was to put incorrect information out into the world.


What surprised me

Which posts landed and which didn't wasn't what I expected. The technically dense ones (IBL, specular aliasing, tone mapping) got more engagement than the lighter takes. I'd assumed depth would narrow the audience. It seemed to have the opposite effect, at least on ArtStation.

Posts with visual examples consistently held up better than text-heavy ones. That wasn't surprising in hindsight, but I kept underestimating how much one diagram changes the read.


What I'd do differently

Two things.

Visuals first, not last. I kept treating images as something to find or create after the writing was done. That's backwards. If a post needs a diagram, planning it upfront shapes the writing. Several posts that ran long could have been tighter if the diagram had done the explanatory work instead.

A narrower scope. Going from LOD pipelines to BRDF math across eight weeks is a lot of context-switching for a reader. The range made sense to me. It maps to how I actually think about technical art. But I'm not sure it mapped equally well to an audience trying to follow a series. A tighter theme would have built a cleaner thread.


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

Day 56 - 📖 Learning - Week 8 Reflection

General / 23 July 2026

Writing the BRDF end-to-end showed me how much I'd remembered. Writing the metalness showed me what I was still missing.


What clicked

The BRDF series was eight years in the making, in a sense. I'd implemented something similar back then but never stopped to understand each term properly. Writing it from scratch this time, term by term, was different. The moment the full shader started producing expected output, the individual pieces (diffuse, specular, ambient, roughness) made sense as a system rather than a checklist.

That was the specific shift: not "here is what GGX does" but why each term exists and what breaks when you remove it. Seeing it work end-to-end in code made that real in a way that reading about it never quite did.


What flopped

The metalness experiments. Frustrating and instructive in equal measure. The math was working. The problem was the environment: the shader previewer doesn't support texture sampling, so proper specular reflection was off the table entirely. Knowing that it's an environment constraint and not a math problem is a useful thing to understand. It still wasn't the result I wanted.


Into the final stretch

Block 5 is less about technical depth and more about closing the arc. What the last few posts need to do is say something about what 60 days of this actually felt like, not just what was learned. The technical body of work is there. The final posts are for the other part.


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

Day 55 - 💬 Take - Why Technical Artists Should Learn Math

General / 22 July 2026

I hated math at school. I just hadn't found a problem worth caring about yet.

Most technical artists I know are self-taught in math. Some never felt the gap. Others hit it mid-career when the work got more complex: shader debugging, LOD budgets, performance tradeoffs that needed real justification. I had a good mentor who reignited the interest in me, because I genuinely hated it in high school.


Where it actually pays off

For me it showed up in three places: writing shaders, optimization, and building custom scripts and nodes for image processing and automated tools. All three involve parameters with mathematical shapes. Knowing whether you're working with a linear, squared, or exponential relationship changes how you approach the problem entirely.

Debugging a shader is different when you can reason about the equation rather than tweak values until it looks right. If the BRDF output looks wrong at grazing angles, knowing which term is responsible tells you exactly where to look. Without the math, you're guessing.

The most consistent payoff: when something breaks and you know why, you fix it faster and explain it clearly to whoever needs to hear it.


Where it doesn't

You don't need a degree. Most of what comes up in technical art is linear algebra and some calculus fundamentals: vectors, dot products, matrices, derivatives.

The value of deeper math also depends on the role. Rigging-heavy work is different from shaders-heavy work. Pipeline work leans more on scripting logic than graphics theory. It compounds fastest if you're working close to rendering.

If math feels like a wall: don't start with a textbook. Start with a problem you're curious about. The math sticks faster when there's something concrete to attach it to.


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

Day 54 - 🧪 Experiment - Writing a BRDF from Scratch (Part 4)

General / 20 July 2026

The BRDF from Parts 1 through 3 covered opaque, non-transmissive materials. This post adds a translucency term to simulate light passing through the surface. Not full subsurface scattering, but a cheap approximation that reads well at a distance.


The Code

The translucency term bends the light direction slightly toward the surface normal, then raises the result to a power to control spread. A distance-based mask keeps it from affecting geometry too far from the light origin.

float translucencyPower = 175.0;
vec3 LTlight            = l + n * 0.003;
float LTDot             = pow(clamp(dot(e, -LTlight), 0.0, 1.0), translucencyPower) * 1.0;
float t                 = clamp(0.4 * length(fWorldPos - vec3(0.0, 0.3, 0.0)), 0.0, 1.0);
vec3 translucent_light  = light_color * (vec3(LTDot) + ambient) * t * light_strength;


Result

The diffuse contribution is softened by interpolating between the base color and the translucency-adjusted color. Applying a curve to the underlying albedo to lift saturation gives a more convincing read at the rim.

Before


After

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

Day 53 - 🧪 Experiment - Writing a BRDF from Scratch (Part 3)

General / 20 July 2026

Sometimes the constraint is the environment, not the math.

Parts 1 and 2 built a dielectric BRDF: Burley diffuse, GGX specular, and hemisphere ambient. This part extends it to metallic materials and runs into a hard limit in the shader previewer.


Metallic

Metals behave differently from dielectrics. A metallic material absorbs diffuse entirely, so with metalness at 1.0 the diffuse contribution drops to zero. At low roughness, that leaves a mostly dark surface with a sharp specular highlight as the only visible term.


Albedo and Ambient

Adding the diffuse ambient term back in brightens the material noticeably. Without it, the metal reads too dark outside of the specular contribution. The ambient gives enough indirect light for the surface to read correctly.


Specular Ambient

This is where it breaks down. Proper specular ambient requires sampling a cubemap or prefiltered environment texture to capture reflected light. The shader previewer does not support texture sampling, so there is no way to evaluate the reflected environment term correctly.


Result

The shader ends up incomplete, but the failure mode is worth noting. The missing piece is not a math problem. It is an environment problem. A real specular ambient term needs an IBL pipeline: a prefiltered environment map and a BRDF lookup table at minimum. That is out of scope for a single-file GLSL previewer, and a good reason to move this into a proper engine next.


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

Day 52 - 🧪 Experiment - Writing a BRDF from Scratch (Part 2)

General / 18 July 2026

Specular is where the BRDF starts to earn its name.

Part 1 covered albedo, Lambertian diffuse, and hemisphere ambient. This part adds specular using GGX, then swaps the Lambertian model for Burley diffuse, which folds roughness into the diffuse response too.

From Lambertian to Burley

Instead of writing the diffuse result directly to out_color, I store it separately. With specular terms arriving, keeping them isolated makes the combining step cleaner to reason about.

Lambertian treats diffuse as constant regardless of viewing angle. Burley adds a Fresnel-based correction: it softens the diffuse response at grazing angles and adds a slight retroreflection on rougher surfaces. The difference is subtle, but since roughness was already going into specular, it made sense to have the diffuse side respond to it too.

vec3 Lambertian(float inNoL, vec3 inAlbedo, vec3 inLightColor, float inLightIntensity)
{
  return (inAlbedo / PI) * inLightColor * inLightIntensity * inNoL;
}


float F_Schlick(float inU, float inf0, float inf90)
{
  float Fc = exp2((-5.55473 * inU - 6.98316) * inU);
  return inf0 + (inf90 - inf0) * Fc;
}


// Disney Burley diffuse
float Burley(float NoV, float NoL, float LoH, float roughness) {
    float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
    float lightScatter = F_Schlick(NoL, 1.0, f90);
    float viewScatter = F_Schlick(NoV, 1.0, f90);
    return lightScatter * viewScatter * (1.0 / PI);
}
// ...
vec3 diffuse = albedo * Burley(NoE, NoL, LoH, roughness) * light_color * light_strength * NoL;


GGX Specular

The specular term is the product of three functions: D, V, and F. D is the Normal Distribution Function, which controls the shape of the specular highlight. V is the geometric visibility term, accounting for microfacets that shadow or occlude each other at grazing angles. F is Fresnel, which drives the grazing-angle brightness rise.

V_SmithGGX is included for reference. I used HCV instead, the height-correlated Smith-GGX formulation and a closer match to the Disney model.

float GGX(float inNoH, float inAlpha)
{
  float temp = inAlpha / max(1e-8, (inNoH * inNoH * (inAlpha * inAlpha - 1.0) + 1.0));
  return temp * temp / PI;
}

float F_Schlick(float inU, float inf0, float inf90)
{
  float Fc = exp2((-5.55473 * inU - 6.98316) * inU);
  return inf0 + (inf90 - inf0) * Fc;
}

float GFGL(float inNoX, float inAlpha)
{
  return -0.5 + 0.5 * sqrt(1.0 + inAlpha * inAlpha * (1.0 / (inNoX * inNoX) - 1.0));
}

float HCV(float inNoL, float inNoE, float inAlpha)
{
  inNoL = max(inNoL, 1e-3);
  inNoE = max(inNoE, 1e-3);
  float geo = 1.0 / (1.0 + GFGL(inNoE, inAlpha) + GFGL(inNoL, inAlpha));
  float normalization = 1.0 / (4.0 * inNoL * inNoE);
  return geo * normalization;
}

float V_SmithGGX(float NoV, float NoL, float a) {
    float a2 = a * a;
    float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
    float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
    return 0.5 / (GGXV + GGXL);
}

void main()
{
  vec3 light_color  = vec3(1.0, 1.0, 1.0);
  vec3 albedo       = vec3(0.8, 0.25, 0.0);
  float pRoughness  = 0.5;
  vec3 specColor    = vec3(0.2, 0.2, 0.2);

  float roughness = pRoughness * pRoughness; // perceptual roughness remapping

  float D = GGX(NoH, roughness);
  float V = HCV(NoL, NoE, spec_alpha);
  float F = F_Schlick_Fast(LoH, max(max(specColor.x, specColor.y), specColor.z));

  vec3 specular       = vec3(D * V * F) * specColor;
  vec3 specular_light = specular * light_color;

  vec3 diffuse = albedo * Burley(NoE, NoL, LoH, roughness) * light_color * light_strength * NoL;

  vec3 out_color = diffuse;
  out_color += specular_light;
  out_color += (1.0 - NoL) * albedo * ambient;
}


Result

With roughness driving both the Burley diffuse and the GGX specular, the material responds consistently across the full parameter range. The clip below cycles through: black albedo, specular only, albedo plus specular, then with ambient added. Light position and roughness are both animated to verify behavior across conditions.

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