Your Blender model exports to glTF as a white blob. You probably don't need to bake it.
You build a material in Blender. Noise into the base colour, a Voronoi for the aggregate, a bump for the grain. It looks right in the viewport, right in Cycles, right in EEVEE.
You export it to .glb, open it in three.js or <model-viewer>, and the whole model is white.
Not untextured-grey. Not black. Flat, featureless white — as if the material never existed.
Every answer you find says the same thing: bake your procedural textures to images. That answer is correct, and for a lot of cases it is also more work than you need to do.
What is actually happening
glTF is a transport format. It is deliberately not a shading language. A glTF material stores a handful of values — baseColorFactor, metallicFactor, roughnessFactor — and optionally an image texture for each. That is the whole vocabulary.
A Blender node graph is not in that vocabulary. So when the exporter walks your Principled BSDF and finds a Noise Texture plugged into Base Color, it cannot translate it, and it does the only thing it can: it leaves baseColorFactor out.
Here is the part that surprises people. It does not fall back to the value in the socket. It omits the field, and the glTF specification says an omitted baseColorFactor defaults to [1, 1, 1, 1].
White. Not a bug, not a broken export — the format's default, faithfully applied.
You can see it for yourself without any 3D software. A .glb is a binary header followed by a JSON chunk:
import struct, json
d = open('model.glb', 'rb').read()
n = struct.unpack('<I', d[12:16])[0]
gltf = json.loads(d[20:20 + n])
for m in gltf['materials']:
print(m['name'], m.get('pbrMetallicRoughness', {}).get('baseColorFactor'))
If that prints None for your materials, nothing is wrong with your viewer. The colour was never in the file.
The fix that takes two minutes
If what you need out of the export is a flat colour per material — and for a lot of work, especially review models, prototypes and stylised assets, it is — you do not have to bake anything.
Unlink the procedural inputs just for the duration of the export. With the node disconnected, the exporter reads the socket's own value and writes it into baseColorFactor.
import bpy
# inputs whose node graphs glTF cannot carry
FLATTEN = ('Base Color', 'Roughness', 'Metallic', 'Normal',
'Emission Color', 'Emission Strength', 'Alpha')
def unlink_procedural():
saved = []
for mat in bpy.data.materials:
if not mat.use_nodes or not mat.node_tree:
continue
for node in mat.node_tree.nodes:
if node.type != 'BSDF_PRINCIPLED':
continue
for name in FLATTEN:
inp = node.inputs.get(name)
if not inp or not inp.is_linked:
continue
for link in list(inp.links):
saved.append((mat.node_tree, link.from_socket, inp))
mat.node_tree.links.remove(link)
return saved
def relink(saved):
for tree, from_socket, to_socket in saved:
tree.links.new(from_socket, to_socket)
saved = unlink_procedural()
try:
bpy.ops.export_scene.gltf(filepath='model.glb', export_format='GLB',
use_selection=True, export_apply=True)
finally:
relink(saved)
The try/finally matters. If the export throws, you want your material graph back.
The one line that decides whether this works
This trick only works if the socket has a sensible value in it to begin with.
That sounds obvious. It is not, because Blender does not require you to set one. If you build a material in Python and only ever link a node into Base Color, the socket keeps whatever it was born with, and unlinking gives you that — usually a default grey, sometimes white. You have swapped one wrong colour for another.
So whenever you write a material procedurally, set the value and link the node:
bsdf.inputs['Base Color'].default_value = (*base_rgb, 1) # the fallback
# ...then build the noise/voronoi graph and link it in
One line. It costs nothing in Cycles, because the link overrides it, and it is the entire difference between a correct export and a white one.
We know because we shipped the bug. Our first pass set the fallback on every material and the exports were fine. Then we wrote three new ground materials — asphalt, paving, turf — that built their colour entirely in nodes and never touched default_value. Every render was correct. Every export was white, and we spent a while looking at the viewer before looking at the material.
When you do have to bake
Be honest about the limits. Unlinking gives you one flat colour per material. Bake when:
- The pattern is the point. Brick courses, wood grain, decals, anything
where the texture carries the identity of the surface.
- You need per-pixel roughness or normals. A flat roughness reads as
plastic on anything meant to look worn.
- It is a hero asset. Something a viewer will orbit and inspect deserves
real texture maps.
Do not bake when:
- It is a review or approval model — you are judging form and proportion,
and flat colour is enough.
- It is one of two hundred instances in a scene, thirty pixels tall.
- You are still iterating on the shape, and a bake is a five-minute tax on
every change.
Three smaller traps in the same area
Modifiers do not export unless you ask. export_apply=True evaluates the modifier stack. Without it your bevels, arrays and subdivisions are simply absent, and the model arrives faceted.
Emission needs a strength above 1 to read as emissive. glTF carries it through KHR_materials_emissive_strength, which not every importer honours. Check the extension list in the JSON chunk before assuming the viewer is wrong.
Instances share mesh data; check that the exporter kept it. Objects created with obj.copy() reference one mesh. Blender's exporter deduplicates them, so two hundred trees can be a few megabytes. If your file is enormous, something broke the sharing — usually a modifier applied per-instance.
The short version
- glTF stores values and images. It cannot store a node graph.
- Unsupported input → the field is omitted → the spec default is white.
- Unlink those inputs at export, restore after, and you get the socket value.
- Set
default_valueon every material you build in code, or the trick
gives you white anyway.
- Bake when the pattern matters. Skip it when only the colour does.
Inspect the JSON chunk first, every time. It tells you in five lines whether the colour left Blender at all — which decides whether you are debugging the export or the viewer.