Modular Material System
Color Space
Jonathen Wilks
A Brief Intro
Color Space uses a dynamic and modular material system to represent all gameplay color states. The system combines hand-drawn texture work, material functions, and data-driven logic to ensure consistency, scalability, and flexibility across the project.
Hand-drawn Textures
Each material uses a surface texture I drew in Adobe Fresco to add visual detail and a tactile quality that felt more interesting than a flat procedural look.I started with the darkest version, then adjusted contrast and brightness to create the variants. This gave me a consistent yet flexible base to work with.
Material Function
These textures are blended in the MF_BaseColor material function using simple lerps and masks. The function outputs the BaseColor, roughness, ambient occlusion, and specular values, reducing redundant logic and allowing for centralized updating in the future for similar materials. This gave every material a shared surface feel while still allowing distinct coloration.
The core material, M_ColorMatch, uses this function and exposes a BaseColor vector parameter. I then created dozens of material instances (like MI_CM_R4, MI_CM_G2, MI_CM_Y3, etc.), each with a specific RGB value tied to gameplay color logic.
Why a Material Function?
Earlier in development, I explored the idea of using unique surface patterns to communicate different object interactions, such as collision based mechanics for passing through or walking over puzzle objects.
Originally, the M_ColorMatch material housed everything, including the custom logic and textures. But as I began designing multiple materials for different interaction types, I realized I needed centralized control. So I restructured the material to rely entirely on a single input: the BaseColor vector. The material function handled everything else—meaning I could globally update visual logic across all materials with one change.
This modular design made it easy to prototype new material types without duplicating effort. For instance, I created a test material (M_CM_2xStripe) using a simple UV math setup (TexCoord, Frac, Clamp, Step) to generate customizable stripes. These were meant to visually distinguish different color logic types. While I ultimately scrapped the idea for gameplay clarity, the system made that experimentation quick and painless—and it remains flexible enough to revisit later if needed.
Conclusion
Unreal doesn’t currently make it easy to pull vector parameters directly from Material Instances at runtime. To work around this, I created a custom Data Table where each Material Instance was manually assigned a name and corresponding RGB vector.
A name and matching RGB vector value was manually entered for each material instance in the table. This ensured that my Blueprint logic could reference the exact same color information used in the visuals.
I explore that system more deeply in a separate case study, but this material pipeline was built specifically to support that data-driven structure.