vidar.games

Anime crystal shader

2026-11-12 ยท Discuss on X

These gems have no transparency and no real refraction. The sparkle is two layers of triangle noise, looked up through a bent view vector and lit by the light behind the face.

The problem

A physically based glass gem in a dark scene shows very little, because it mostly shows what sits behind it. Anime gems are drawn the other way: flat colour facets, bright triangles that flash as the gem turns, a lighter rim. The goal is that drawn look on a 3D mesh, and the shader makes no attempt to be physically correct.

How it works

The surface itself is black with a glossy specular, so the environment still reflects off the facets. Everything else is emission.
The view vector is taken into tangent space and refracted against (0, 0, 1) with 1 / IOR. Its xy divided by z becomes a UV offset that shifts as the view angle changes, like parallax.
That offset feeds two Delaunay triangulation noises. Each one jitters a 3 x 3 block of grid points, picks the diagonal of each quad with a parabolic lifting test, and returns the centre of the triangle the pixel falls in. A random value seeded by that centre gives every triangle its own brightness.
The two layers are multiplied, a Fresnel term is added, and the result scales the ambient light from behind the face (baked GI sampled with the negated normal) and the crystal colour.

What I hit

The graph's View Direction and Normal nodes are set to tangent space, so the normal going into the refract is always (0, 0, 1). The "refraction" never enters the gem. It only slides the noise across each face.
My glass comparison first rendered almost black against the dark backdrop, which is the honest result for transmission with nothing behind it. Both sides now sit in front of the same blurred HDRI.
My first gem had flipped faces on the crown that rendered as a black band. Its flat top face also got stretched UVs from the cylindrical unwrap and turned into noise. The README warns the shader needs low-poly meshes with clean UVs, and the top face now gets a planar UV.

My build

The Shader Graph and the noise HLSL are ported to GLSL inside a three.js standard material. Smoothness 0.71, albedo 0. Tangents come from screen-space derivatives because my meshes have none. Unity's baked GI is replaced by 9 spherical harmonics coefficients projected from the HDRI.
Colours, cell densities, angle offsets and strengths come from the source's green, orange and white materials, IOR 1.75. The gems are 24 to 46 triangles each.

Trade-offs

It costs two noise lookups per pixel and no extra pass. Nothing behind the gem shows through, and the facets inside are painted on, so a close turning shot can read as a texture sliding over the faces. It needs low-poly meshes with sensible UVs.

When to use it

Pickups, magic crystals and jewellery in a stylised or toon game. For a gem that has to show the scene behind it, use a transmission material.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Technique source: URP Anime Crystal Shader by CJT-Jackton, https://github.com/CJT-Jackton/URP-Anime-Crystal-Shader, CC0-1.0 (code), commit 8cfd533b45bd819db2b3782b4fd8790064216e2f. Ported to GLSL from its Crystal shader graph (tangent-space refracted UV offset, two Delaunay noise layers, Random Range, Fresnel, Baked GI with the negated normal) and its DelaunayTriangulationNoise HLSL. Parameter values come from its Crystal_Green, Crystal_Orange and Crystal_White materials.

Environment light: Adams Place Bridge HDRI by Andreas Mischok, Poly Haven, CC0.

Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, r170. Used for drawing, the glass comparison material and the environment reflections. It is not the source of the crystal technique.

My own code: the procedural gem meshes and their UVs, the spherical harmonics stand-in for baked GI, the scene, the debug views, camera moves, labels and video.