logo80lv
Articlesclick_arrow
Talentsclick_arrow
Events
Workshops
Aboutclick_arrow
profile_login
Log in
1
Save
Copy Link
Share

Breakdown: How to Create an Optimized and Realistic Crystal Material

Anastasia Gorban discussed the workflow behind the Crystal Realms project, detailing the material's structure step-by-step, like how to achieve the look of the inside of the crystal using layers, how to get the iridescence in some stones, and how to texture them, adding fine scratches.

Introduction

My name is Anastaisa Gorban. I'm an Unreal Engine Artist currently working as a Technical Lighting Artist. Some readers might remember me from a previous article here — since then I've kept working across level and lighting, but I also like digging into the more technical side of the engine and trying to recreate something beautiful from the real life inside it. That's exactly why I took on this project. I wanted to try building a more complex shader from scratch, outside my usual comfort zone, and see how far I could push it.

Crystal Realms

Initially, this project started as a test task during my job search — I had to create an image of a realistic crystal similar to alexandrite, duochrome on the inside, iridescence on the outside. That first material was much simpler and focused purely on nailing the alexandrite look. But once I finished the task in under three days, I decided to turn it into a master material that could cover different crystal variations and make it as optimized as possible while looking believable.

I kept it opaque on purpose — it's cheaper than translucency and can still look believable. As for Substrate, I had less experience with it, but it's not exactly a new feature anymore, so I decided this was a good chance to fill that gap. And Lumen, because I enjoy exploring what it's capable of. My goal was to get a real-time result close to what artists achieve with offline rendering in other engines, but without any rendering at all. Gemstones were a natural subject; something is mesmerizing about their play of light, and every stone is unique.

Master Material Setup

Substrate was new territory for me, and I'll be upfront: this particular material doesn't strictly need it, because everything here is built with ordinary shader math feeding a single slab, and a similar opaque result could be reproduced on a standard surface material, for less cost. I wanted the iridescence to live on the crystal's outer surface, driven independently of the interior. Iridescence is a thin film tinting the reflection at grazing angles, and Substrate lets me have it without influencing the diffuse interior, while the basic shading model doesn't allow that.

The whole thing grew iteratively rather than being planned upfront. The very first version I made for the test task didn't have parallax, just two internal color layers offset by a fixed distance, no bump/POM, no normal detail on the edges, no inner glow, and no masking to separate the crystal's veins from its base color. It looked okay from a distance, but every pass after that added more layers of physical believability, and the graph grew alongside it.

In general, the final master material has the following structure:

  • POM vs. Bump Offset — depth stage
  • Crystal Insides — the duochrome interior, built by calling two shared functions per layer — MF_InnerLayerUV and MF_ColorLerp
  • Iridescence — view-angle color shift on the specular response
  • Outer Normal — surface scratches + edge wear
  • Outer Roughness — controlled polish/frost range
  • Internal Glow — emissive pass tied to the interior
  • Final assembly — every block converging on one Substrate Slab BSDF

Below is what each block does and why, with the parameters it exposes.

Step 1. Depth stage (POM vs. Bump Offset)

Before any color gets sampled, the material needs a UV offset that fakes depth. I built two ways to get it: real Parallax Occlusion Mapping (accurate, driven by a dedicated Height map) and a cheaper Bump Offset node. An Allow POM? switch decides which one actually drives everything downstream. This replaced my initial approach, which was just a flat distance value per layer with no real view-angle behaviour.

POM is the more physically correct one, but it's also the pricier option (step count, shadow steps, etc.), while Bump Offset is the one that scales better once you're layering multiple internal masks on top of each other. Both branches share the same height source and the same Top Texture Tiling. The height source is the top layer's Top Diffuse Mask texture that is used further down the graph for the top layer.

Using the same texture through the same tiling parameter across both blocks allows any change to the top layer to apply to the height used for the parallax. This way fake depth always lands exactly on the colour it's displacing, and the two can never drift apart. POM Height Ratio (default 0.1) controls how deep the effect reads. Pushing it higher deepens the illusion but risks swimming at grazing angles.

The important detail is that I take POM's Offset Only output and add it on top of the tiled UV, which is built separately in the Crystal Insides block. Using the full Parallax UVs output would add the parallax tiling into it and scale everything twice.

A note specific to POM: for the effect to work at full strength, the top layer wants to sit as close to the surface as possible, which means Top Distance should be set lower than usual.

Bump Offset is the cheaper fallback. Its depth is set by Bump Depth (default 0.1). Since it samples the diffuse texture itself rather than a dedicated height map, I convert it to luminance first (a weighted dot product of RGB) and push that through a Power node to raise the contrast, which deepens the offset. 

Step 2. Crystal Insides

This is the block that carries most of the illusion. I started with two internal surfaces, Top and Bottom. Two layers rather than one is a deliberate choice.

For a duochrome stone like alexandrite, the interior has to show genuinely different colors at different depths, and a single layer with a color lerp can't fake that convincingly — the colors have to sit on separate surfaces so parallax reveals one behind the other as the view moves.

For simpler stones, one layer might be more than enough, and a single layer also doubles as the cheaper path when the crystal is far from the camera and doesn't need the extra depth. 

Inside that pipeline, two things build each layer's UV: a distance offset (how far the layer is pushed inside) and a distortion (how irregular that push is, imitating the refraction inside the crystal) are applied. These processes are packed into one function, MF_InnerLayerUV, with Distance, Noise Texture, Texture Tiling, and Distortion Scale as its four inputs instead of leaving three near-identical copies sitting in the graph.

1— A reflection vector is built from the camera vector in tangent space — this is the view-dependent direction the whole offset is based on, so the offset direction actually behaves like something bouncing inside the gem.

2— Distortion - the reflection vector's in-plane (X/Y) component gets perturbed by a Noise Texture param, scaled by a Distortion Scale param. This is what makes it read as irregular, closer to how light breaks up inside a real stone, so each layer reads as an organic fracture, imitating the refraction inside the stone. Each layer gets its own noise texture, since refraction breaks up differently depending on the layer's internal geometry, and this parameter can be pushed further to imitate stones like carnelian or amber.

3— That perturbed direction is divided by the reflection vector's depth (Z) component, which keeps the offset stable instead of blowing up at grazing angles.

4— Distance — at this point, the result gets scaled by the layer's Distance value — Top Distance uses 200, Bottom Distance uses 400. Distance is what sets how far each layer's UV moves as the camera rotates, and the gap between the two values is what separates Top and Bottom visually instead of collapsing into a flat blend. For stones that don't need much depth, the Distance values can be lowered so the top layer sits closer to the surface, which is also the recommended setup for stones relying on POM.

5— Lastly, the scaled offset is added onto the layer's own tiled UV, multiplied by that layer's Texture Tiling input. This way, Top and Bottom layers receive their own tiling, for example, for cases when a tighter crystalline lattice is needed inside of the crystal. As was mentioned before, Top Texture Tiling introduced in the depth stage is the same value that scales the top layer's diffuse, so the two can't drift apart.

Each layer's MF_InnerLayerUV output is then added to the UV offset from Step 1 (POM vs. Bump Offset), combining the per-layer depth offset and distortion with the base parallax stage into one final UV. That UV is what samples the layer's Diffuse Mask texture inside the MF_ColorLerp function.

Color-wise, the material undergoes the following pipeline. The diffuse mask's red channel is what gets split out and fed into MF_ColorLerp, which lerps between Main Color and Veins Color across it.

The darker the Main Color, the stronger the translucency illusion, since a near-black diffuse contributes almost nothing and reads like empty volume. In a two-layer setup, the Top layer is best kept darker so the Bottom colour shows through it. If the whole stone should lean toward translucency, both layers want to be dark. The lighter the colour, the more milky and opaque the layer becomes. 

I gave Bottom a second color pass on top of this because color inside a real crystal, as well as texture, doesn't stay uniform with depth — zoning, pleochroism, and dispersion all make a stone shift color depending on where you're looking into it. A single Main/Veins pair per layer couldn't fully reproduce that, so Bottom got a third color region with its own Veins Color 2 and Diffuse Mask 2, blended in through a low-exponent Fresnel term that's near zero when looking straight into the surface and rises toward the silhouette.

The Main Color stays shared with the first pass. Blending the body color would be pointless here, since it's the veins that actually carry the interior's color; the fill is just the medium they sit in. That concentrates the extra color at the centre of the stone and fades it out toward the edges instead of spreading it evenly across the layer.

The two layers stay separate up to this point. The Top layer's result is its output, Top Layer. Adding Top and Bottom together produces the Two Layers output. The Allow Second Layer? switch then picks between them.

A note on the textures themselves: most of the Diffuse maps and masks I used here were generated with Nano Banana. Good free crystal textures are hard to come by, and generating them gave me exactly the patterns I needed, with full control over how they read, instead of settling for whatever happened to be available.

Step 3. Iridescence

My alexandrite needed an iridescent coat because that play of color is a distinctive feature of the stone, but it can be switched off for the ones that don't have it. Transparent single-hue stones like quartz, ruby, or sapphire show no surface iridescence, while stones like opal, labradorite, moonstone, and ammolite do — so making this an optional layer keeps the master material applicable across crystal types.

Once the interior worked, I built a separate view-angle-driven color shift for the outside, feeding into the specular response (F0), so it reads as a thin-film coating.  The camera vector is transformed to tangent space and dotted with the pixel normal, giving a standard view-dependence term — 1 when looking straight at the surface, falling to 0 at grazing angles — saturated and raised to a power (Iridescence Blend Intensity) to shape the falloff.

That's multiplied by a Wave Scale (default 6.28 ≈ 2π) and pushed through Sine: thin-film interference is periodic in viewing angle, so Sine stands in for that periodicity and turns a smooth ramp into repeating bands, with a higher Wave Scale giving more of them. The sine is remapped to [0,1] and used as the alpha of a lerp between two Iridescent Color params, scaled by an overall Iridescence Intensity.

Iridescence in this setup is gated behind its own switch: Allow Iridescence?.

Step 4. Outer Normal and Outer Roughness

These came in alongside iridescence to make the surface read as real with the fine scuffs and scratches a polished stone picks up. Two normal sources are blended: Outer Normal Texture (scaled by Outer Normal Intensity) carrying fine scratches across the whole surface, and an Edge Normal Texture (with Edge Normal Strength) concentrated only where the facets meet, for a crisper highlight along the edges.  

For the edge mask, I reused the emissive map texture that shipped with the free crystal asset from Fab — its emissive is laid out exactly along the geometric edges, so I pulled its green channel and used it as a Geometry Edge Mask directly. It's essentially a free curvature map for this particular mesh; the tradeoff is that it's baked to this asset and wouldn't transfer to arbitrary geometry (a real curvature/cavity bake, or a runtime curvature term from the normal's derivatives, would be the general-purpose version).

Roughness is a single Outer Roughness Texture remapped into an artist-controlled Min/Max Roughness range.

Step 5. Internal Glow

Crystals scatter light, transmit it through their body, and refract it internally, so light seems to gather and bounce inside the stone rather than simply reflecting off it. In an opaque material, there's no real transmission to lean on, so an emissive pass stands in for that internal light, creating the illusion of light being refracted and pooling inside the crystal.

It's masked by a Fresnel term using Edge Shadow exponent as the interior's edge-darkening, so the glow concentrates inside the crystal. That mask multiplies a Glow Color and an Emissive Intensity.

The Allow Inner Glow? switch selects where the glow originates:

  • On, the light comes from the bottom layer, reading as light refracted up from within;
  • Off, it comes from the top layer only, a purely surface glow.

Allow Second Layer? The description above is independent; a crystal can carry two layers with no inner glow, while Allow Inner Glow? is not — internal glow originates from the bottom layer, so enabling it inherently requires that layer to exist. Turning the inner glow on effectively pulls the second layer into the result even if Allow Second Layer? isn't checked, because there's nothing for the light to come from without it.

Step 6. Final assembly

All of it converges on one Substrate Slab BSDF - Simple node: Diffuse Albedo comes from the Crystal Insides result (single- or dual-layer, depending on the switch), F0 comes from a flat Specular value or Iridescence, Roughness and Normal come straight from their respective blocks, and Emissive Color comes from Glow. Everything else on the BSDF stays off. The material is opaque, faking interior depth through the layered diffuse rather than paying for real translucency and subsurface scattering. 

In the current setup, toggling features aren't really an optimization tool in the strict sense. They do trim some parts of the instructions, but their main purpose is to keep the parameter set in order and avoid chaos in the Material Instance.

This allows us to strip away the controls a given crystal type simply doesn't need, so an artist configuring a pink quartz isn't wading through iridescence and inner-glow parameters that don't apply to it. 

Performance and Optimization

The Substrate material runs between roughly 445 instructions (all switches off) and 517 (everything on). An empty Substrate Slab BSDF already costs -326 instructions on its own. I rebuilt the same setup on a standard non-Substrate material, and it came out to just 291–369 instructions. So if the goal were purely shipping this exact opaque crystal as cheaply as possible, the standard material would be the right call.

The one thing you lose is iridescence in its pure form, because the standard shading model doesn't let you colour a dielectric's specular reflection directly, so the thin-film effect that drove F0 in Substrate has nowhere to go. You can approximate it by mixing the iridescent colour into Base Color instead, but it's a tinted surface rather than a true coloured highlight on the reflection. Tweaking parameters like Specular and Metallic would also help to achieve a similar effect.

Conclusion

The whole process took me about two weeks, including three days for the original test task that laid the foundation. It would have been faster if I'd had a clear plan from the start, but I was inventing new layers of logic on the fly. A fair amount of time also went into lighting. To get this look, I needed several light sources with controlled light channels and gobo effects, and I had to modify the default Megascans rock materials to add sparkle and displacement tessellation.

There was also a small PCG setup to scatter the crystals across the surface of another rock. The main challenge was simply not getting lost in the stack of logic blocks, and figuring out which fake would get me closest to reality. A material like this is a pile of cheats standing in for real optics, and the hard part is choosing which cheat is worth adding and keeping them all readable as they pile up.

My advice would be to start with a plan of what you want to see in the material, then a rough idea of how to build it, but stay open to unexpected approaches, because those are often the ones that actually get you the look you're after. Always keep different use cases in mind and don't cling to a single variant.

At the same time, do build that first concrete version properly, because it gives you a foundation to grow from. Test constantly and question your graph, verify a node is doing what you think before building on top of it, and don't be afraid to delete work that isn't contributing.

And that's about it. Huge thanks to 80 Level for the chance to walk through how this material works — it's genuinely rewarding to break down something that started as only three nodes in the graph. Good luck to everyone with your projects!

Anastasia Gorban, Level & Lighting Artist

Interview conducted by Gloria Levine

Built for the Game & Digital Art Industry
Get Our Media Kit

Comments

1

arrow
Type your comment here
  • Anonymous user
    an hour ago
    0
Leave Comment
Built for the Game & Digital Art Industry
Get Our Media Kit

We need your consent

We use cookies on this website to make your browsing experience better. By using the site you agree to our use of cookies.Learn more