Skip to main content

How to add materials and textures to a GLB file

About Us
Published by JET BI
20 January 2025
245

How to add materials and textures to a GLB file: a step-by-step guide

 

Introduction

The GLB format (or its text analog glTF) is widely used for 3D models due to its lightweight nature and support for physically based materials (PBR — Physically Based Rendering). It allows developers and designers to store complex 3D models, complete with geometry, textures, materials, animations, and scenes, in a single binary file. This makes GLB an ideal format for applications in gaming, AR/VR, and web-based 3D content. High-quality materials and textures are critical for achieving realistic and visually appealing 3D models. They not only add depth and detail to the model but also enhance the overall user experience. In this guide, we will look at how to programmatically add materials and textures to a GLB file, enhancing its visual quality and realism. We will also provide tips on creating high-quality materials to ensure your 3D models are ready for any platform or use case.

 

PBR Basics and GLB Structure
 

A GLB file consists of:

Meshes: the geometry of the model.

Materials: a description of the visual properties (color, metallicity, roughness, etc.).

Textures: images used to add details (albedo, normals, metallicity, etc.).


PBR materials use physically correct lighting, which allows you to create realistic scenes. The main parameters of PBR:

Base Color (Albedo): the main color of the surface.

Metallic: The degree of light reflection typical of metals.

Roughness: Adjusts the smoothness of the surface (higher values ​​= more matte surface).

Normal Map: Used to add fine details such as bumps or dents.


 

Initial Step

Launch Blender and Pycharm (or any other IDE, where python is installed).
C:\Users\smykr\Desktop\article_2_materials\1.png


Make an export of this file to glTF format.
C:\Users\smykr\Desktop\article_2_materials\2.jpg

Save it in any directory and name it as you wish.
C:\Users\smykr\Desktop\article_2_materials\3.jpg

 

Adding base colors

To add base colors to the GLB model, you can use the pygltflib library. Install this library with command: 

pip install pygltflib


Launch this code in your IDE.

from pygltflib import GLTF2, Material

# Loading the GLB file
file_path = "path/to/model.glb"
glb = GLTF2().load(file_path)

# Creating a new material with a base color
new_material = Material(
    name="CustomMaterial",
    pbrMetallicRoughness={
        "baseColorFactor": [1.0, 0.0, 0.0, 1.0],  # Red color with full opacity
        "metallicFactor": 0.0,  # Non-metallic
        "roughnessFactor": 0.8  # Matte surface
    }
)

# Adding the material to the model
glb.materials.append(new_material)

# Assigning the new material to the first primitive mesh
for mesh in glb.meshes:
    for primitive in mesh.primitives:
        primitive.material = len(glb.materials) - 1  # Index of the new material

# Saving the updated file
glb.save("model_with_material.glb")

 

Explanations:

baseColorFactor defines the color of the material. In this case, it is red.

MetalFactor controls the metallicity of the material. A value of 0.1 makes the surface slightly metallic.

RoughnessFactor controls the roughness. A value of 0.8 makes the surface matte.

As a result, the mesh models will use red color, low metallicity and high roughness.

Now you have to import this file from your working directory back to Blender.

C:\Users\smykr\Desktop\article_2_materials\4.jpg
C:\Users\smykr\Desktop\article_2_materials\9.jpg

You have to receive this red cube:
C:\Users\smykr\Desktop\article_2_materials\5.png

 

Loading and assigning textures

Textures greatly improve the appearance of the model. Let's consider adding an albedo texture (Base Color).

from pygltflib import GLTF2, Image, Texture, Sampler, Material

# Loading the GLB file
file_path = "path/to/model.glb"
glb = GLTF2().load(file_path)

# Loading the texture image
texture_path = "path/to/texture.png"
with open(texture_path, "rb") as f:
    image_data = f.read()

# Creating an Image object
image = Image(uri="path/to/texture.png")
glb.images.append(image)

# Creating a Sampler object
sampler = Sampler()
glb.samplers.append(sampler)

# Creating a Texture object
texture = Texture(sampler=len(glb.samplers) - 1, source=len(glb.images) - 1)
glb.textures.append(texture)

# Assigning the texture to the material
new_material = Material(
    name="TexturedMaterial",
    pbrMetallicRoughness={
        "baseColorTexture": {"index": len(glb.textures) - 1}
    }
)
glb.materials.append(new_material)

# Assigning the material to the mesh
for mesh in glb.meshes:
    for primitive in mesh.primitives:
        primitive.material = len(glb.materials) - 1

# Saving the file
glb.save("model_with_texture.glb")

 

Explanations:

Image: Represents the texture image file (e.g., texture.png) that you want to apply.

Sampler: Controls how the texture is sampled, such as wrapping or filtering.

Texture: Combines the image and sampler for use in the material.

baseColorTexture: Links the texture to the base color of the material.

As a result, the albedo texture (e.g., a detailed color image) is applied to the model, enhancing its appearance.

Import "model_with_texture.glb" as in the upper section.
C:\Users\smykr\Desktop\article_2_materials\6.png

 

Adding normal maps and other textures

To improve quality, you can add normal, metallic and roughness maps. These maps work similarly to albedo:

Load the map image.

Create an Image object, then a Texture.

Assign the texture to the corresponding material parameter (e.g. normalTexture, metallicRoughnessTexture).

Example of adding a normal map:

from pygltflib import GLTF2, Image, Texture, Sampler, Material

# Loading the GLB file
file_path = "path/to/model.glb"
glb = GLTF2().load(file_path)

# Loading the texture image
texture_path = "path/to/texture.png"
with open(texture_path, "rb") as f:
    image_data = f.read()

# Creating an Image object
image = Image(uri="path/to/texture.png")
glb.images.append(image)

# Creating a Sampler object
sampler = Sampler()
glb.samplers.append(sampler)

# Creating a Texture object
texture = Texture(sampler=len(glb.samplers) - 1, source=len(glb.images) - 1)
glb.textures.append(texture)

# Assigning the texture to the material
new_material = Material(
    name="TexturedMaterial",
    pbrMetallicRoughness={
        "baseColorTexture": {"index": len(glb.textures) - 1}
    }
)
glb.materials.append(new_material)

# Assigning the material to the mesh
for mesh in glb.meshes:
    for primitive in mesh.primitives:
        primitive.material = len(glb.materials) - 1

# Loading normal map
normal_map_path = "path/to/normal_map.png"
with open(normal_map_path, "rb") as f:
    normal_data = f.read()

image = Image(uri="path/to/normal_map.png")
glb.images.append(image)
texture = Texture(source=len(glb.images) - 1)
glb.textures.append(texture)

new_material.normalTexture = {"index": len(glb.textures) - 1}

# Saving the file
glb.save("model_with_texture_and_map.glb")

Explanations:

Material Assignment: Creates a new material (TexturedMaterial) and assigns the texture as the base color. Then, this material is linked to the mesh's primitives so it applies to the object.

Loading a Normal Map: Normal maps add depth and detail to the model without changing its geometry by simulating lighting variations.

The normal_map.png file is loaded as an additional Image and linked to a Texture object.

The normalTexture property of the material is set to reference this texture, enabling normal mapping.

Result: The model now includes realistic surface details, such as grooves or ridges, based on the normal map.
C:\Users\smykr\Desktop\article_2_materials\7.png

Tips for Creating High-Quality Materials

  1. Use textures with a resolution that is appropriate for your project. Avoid images that are too large, as this can slow down rendering.
  2. materials look best when using physically based lighting.
  3. Adjust the roughness and metallic parameters to achieve the desired effect.
  4. Test the model in environments with different lighting to ensure that it looks good in all conditions.
     

The Final Result

C:\Users\smykr\Desktop\article_2_materials\8.png


Full Code

from pygltflib import GLTF2, Image, Texture, Sampler, Material

# Loading the GLB file
file_path = "path/to/your/model.glb"
glb = GLTF2().load(file_path)

# Loading the albedo texture image
texture_path = "path/to/your/texture.png"
with open(texture_path, "rb") as f:
    image_data = f.read()

# Creating an Image object for the albedo texture
image = Image(uri="path/to/your/texture.png"
glb.images.append(image)

# Creating a Sampler object
sampler = Sampler()
glb.samplers.append(sampler)

# Creating a Texture object for the albedo texture
texture = Texture(sampler=len(glb.samplers) - 1, source=len(glb.images) - 1)
glb.textures.append(texture)

# Creating a new material with a base color texture
new_material = Material(
    name="TexturedMaterial",
    pbrMetallicRoughness={
        "baseColorTexture": {"index": len(glb.textures) - 1},
        "metallicFactor": 0.0,  # Non-metallic
        "roughnessFactor": 0.8  # Matte surface
    }
)
glb.materials.append(new_material)

# Assigning the material to the first primitive mesh
for mesh in glb.meshes:
    for primitive in mesh.primitives:
        primitive.material = len(glb.materials) - 1  # Index of the new material

# Loading the normal map texture
normal_map_path = "path/to/your/normal_map.png"
with open(normal_map_path, "rb") as f:
    normal_data = f.read()

# Creating an Image object for the normal map
normal_image = Image(uri="path/to/your/normal_map.png")
glb.images.append(normal_image)

# Creating a Texture object for the normal map
normal_texture = Texture(source=len(glb.images) - 1)
glb.textures.append(normal_texture)

# Assigning the normal map texture to the material
new_material.normalTexture = {"index": len(glb.textures) - 1}

# Saving the updated GLB file
glb.save("full_model.glb")

 

Conclusion

Adding materials and textures to GLB files with Python opens up endless possibilities for automating workflows and creating realistic 3D models. By programmatically applying materials, textures, and maps, you can ensure consistency across projects and maintain precision in your 3D assets. Experiment with different texture maps, such as normal or roughness maps, to achieve the desired level of realism and visual quality. Always test your models after making changes to ensure compatibility with your target platform, whether it's a game engine, AR application, or web viewer. The combination of creativity and scripting opens up opportunities to bring your ideas to life efficiently. Let’s bring more stunning models to the virtual world together.


Roman Smyk
Python Developer/Data Analyst
image
Expertise
Question to the expert
image

We have available resources to start working on your project within 5 business days

1 UX Designer

image

1 Admin

image

2 QA engineers

image

1 Consultant

image
Related Articles
All articles
image
Is Salesforce Winning the Public Sector Race?
An analysis of Salesforce's rapid expansion into the U.S. public sector, tracing its path from cautious early government licensing deals in the 2010s through the launch of Government Cloud in 2012, its pivotal role in COVID-19 vaccine rollouts, and its 2025–2026 push into military and intelligence work via Agentforce and Missionforce. The piece covers major 2026 contracts — including a $5.6 billion Army deal, a $1.6 billion VA agreement, and Pentagon Impact Level 5 authorization — alongside real-world case studies like California's REAL ID processing and the UK's NHS back-office operations. It also examines the structural obstacles still facing Salesforce and other vendors in government tech: legacy IT systems decades old, outdated federal procurement rules, budget constraints, and organizational caution around AI adoption, plus the competitive pressure from Palantir, Microsoft, and Oracle in the race for public sector AI spending.
28 August 2026
image
Why Your Salesforce Flows Are Agentforce's Biggest Problem
This article argues that the most underestimated risk in Agentforce deployments isn't data quality — it's the automation layer: years of overlapping Flows, Process Builder processes, Apex triggers, and managed package logic that no one has reviewed end-to-end. It explains why AI agents inherit automation complexity without the tribal knowledge human admins carry, why technical debt only becomes visible after an agent hits it in production, and why a clean demo is no indicator of production readiness. The article closes with a concrete, tool-by-tool inventory approach using Flow Trigger Explorer, Salesforce Optimizer, Setup Audit Trail, Apex Debug Logs, Agent Builder, and Health Check — scoped to the specific processes the agent will actually use rather than the whole org.
23 July 2026
image
How to Wire Multiple Salesforce Projects in One Org Without Breaking Everything
This article maps the real integration patterns that emerge when multiple Salesforce projects — both managed packages and unpackaged code — share a single org. It covers four concrete patterns: attaching custom triggers to package-owned objects, calling global members exposed by managed packages, writing directly into another project's objects, and runtime-guarded reads of package data. It then addresses access control for authenticated and guest users, including the Master-Detail wall and the without sharing elevation pattern. The piece closes with eight concrete risks (compile-time dependencies that block uninstall, upgrade coupling, silent cascade failures, access invisible to admins) and six actionable recommendations for keeping cross-project coupling manageable.
08 July 2026