“Cheap” Diamond Rendering
I was surfing the web and found this wonderful ShaderToy Diamond. I’d immediately wanted to create something for conventional meshes and not analytical one. This “cheap” solution is still a bit heavy, up to 7 cubemap samples per pixel and yet it should be faster than raymarching/raytracing considering that we should calculate diamond SDF on each step and then 4 more SDFs to get a normal on hit. BTW, my method is not a physically correct rendering, but with a help of the Local Cubemaps it can be though.
What is the deal with diamonds and where all this sparkles and shininess come from? Reflection, Refraction and Dispersion — the very important part is a diamond cut, with the right cut a ray of light can be trapped inside and make multiple bounces to create complex visual patterns (good video on the perfect cut). However, if the cut is bad it’s still possible to save situation by artificially decreasing the critical angle of the diamond. In the real world, critical angle is about 24 degrees. To trap the ray inside the diamond we can make it 3 to 10 degrees. This was the case with my first diamond which I found on a 3D stock for free (thanks to my wife, she created the right diamond for me later). After the ray enters the diamond it splits into many rays by color and this is where the color shifting comes from. There is still a small portion of the Fresnel effect which gives a nice bright outline.
Here is my implementation:
- To calculate the internal bounces I created a Normal Cubemap captured from inside of the object
- There is a nice script in Unity Doc to capture cubemaps https://docs.unity3d.com/ScriptReference/Camera.RenderToCubemap.html
- All calculations are done in object space because we will be dealing with the internal object normals multiple times
- We are going to need some consts for the fragment shader. Different refraction indices and spreads for different color channels. Max bounces — basically the max amount of internal cubemap samples. The cosine of 24 degrees critical angle.
- At first, we calculate the Fresnel factor and reflection with an environment cubemap
- The biggest part where most of the magic happens.
- In a perfect scenario we should split colors on the first entry and calculate RGB rays independently, but it’s too expensive so I decided to fake it on the exit.
- I also tried Local Cubemaps to make rendering a bit more realistic, but it didn’t give enough improvements to justify costs. I basically was not able to spot any difference.
In theory, it’s possible to make a physically correct simulation with this method. One need to bake not only the normals to cubemap but also a distance from the centre of a diamond into the alpha channel. This way it’s possible to calculate precise location of each bounce.
Thanks for all your likes on the initial Twitter post, it motivates to create and share.