This post will guide you through the creation of a shader that reproduces the rainbow reflections that can be seen on CD-ROMs and DVDs. This tutorial is part of a longer series on physically based iridescence.
You can find the complete series here:
- Part 1. The Nature of Light
- Part 2. Improving the Rainbow (Part 1)
- Part 3. Improving the Rainbow (Part 2)
- Part 4. Understanding Diffraction Grating
- Part 5. The Mathematics of Diffraction Grating
- Part 6. CD-ROM Shader: Diffraction Grating (Part 1)
- Part 7. CD-ROM Shader: Diffraction Grating (Part 2)
- Part 8. Iridescence on Mobile
- Part 9. The Mathematics of Thin-Film Interference
- Part 10. Car Paint Shader: Thin-Film Interference
A link to download the Unity project used in this series is also provided at the end of the page.
Introduction
In a previous tutorial, The Mathematics of Diffraction Grating, we have derived the equations that capture the very nature of the iridescent reflections that certain surfaces exhibit. Iridescence occurs on materials featuring a repeating surface pattern which size is comparable to the wavelength of the light they reflect.
The optical effects we are interested in reproducing ultimately depends on three factors: the angle of the light source with the surface normal (light direction), the angle of the viewer (view direction) and the distance between the repeating gaps.
We want our shader to add iridescent reflections on top of the normal effects that the Standard material usually comes with. For this reason, we will extend the lighting function of a Standard Surface shader. If you are unfamiliar with the procedure, Physically Based Rendering and Lighting Models provides a good introduction.
Creating a Surface Shader
The first step is to create a new shader.Since we want to extend a shader that already supports physically based lighting, we will start with a Standard Surface Shader.
The newly created CD-ROM shader needs a new property: the distance used in the diffraction grating equation. Let’s add it to the Properties
block, which should now look like this:
Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 _Distance ("Grating distance", Range(0,10000)) = 1600 // nm }
This will create a new slider in the Material Inspector. The _Distance
property, however, still needs to be coupled with a variable in the CGPROGRAM
section:
float _Distance;
We are now ready to start.
Customising the Lighting Function
The first step we need to take is to replace the lighting function of the CD-ROM shader with a custom one. We can do this by altering the #pragma
directive from:
#pragma surface surf Standard fullforwardshadows
to:
#pragma surface surf Diffraction fullforwardshadows
This forces Unity to delegate the lighting calculation to a function called LightingDiffraction
. It is important to understand that we want to extend the behaviour of this Surface shader, not override it. For this reason, out new lighting function will start by simply calling Unity’s Standard PBR lighting function:
#include "UnityPBSLighting.cginc" inline fixed4 LightingDiffraction(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi) { // Original colour fixed4 pbr = LightingStandard(s, viewDir, gi); // <diffraction grating code here> return pbr; }
As you can see from the snippet above, the new LightingDiffraction
simply calls LightingStandard
and returns its value. If we compile the shader now, we will see no difference in the way it renders materials.
Before continuing, however, we need to create an additional function to handle the Global Illumination. Since we are not interested in changing that behaviour, our new global illumination function will once be a proxy for Unity’s Standard PBR function:
void LightingDiffraction_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi) { LightingStandard_GI(s, data, gi); }
Lastly, please note that since we are using LightingStandard
and LightingDiffraction_GI
directly, we will need to include UnityPBSLighting.cginc
our shader.
⭐ Recommended Unity Assets
Unity is free, but you can upgrade to Unity Pro or Unity Plus subscription plans to get more functionalities and training resources for your games.
Implementing the Diffraction Grating
This is the core of our shader. We are finally ready to implement the diffraction grating equations seen in The Mathematics of Diffraction Grating. In that post, we concluded that the viewer sees an iridescent reflection which is a sum of all the wavelengths which satisfy the grating equation:
with being an integer number greater than .
Given a certain pixel, the values for (given by the light direction), (given by the view direction) and (the gap distance) are known. The unknown variables are and . The easiest thing to do is to loop over certain values of , to see which wavelengths satisfy the grating equation.
When we know which wavelengths contribute to the final iridescent reflection, we calculate their associated colours and add them together. The Improving the Rainbow discussed several approached to convert wavelengths from the visible spectrum into colours. for this tutorial, we will use spectral_zucconi6
as it provides the best approximation with the cheapest computational cost.
Let’s see a possible implementation below:
inline fixed4 LightingDiffraction(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi) { // Original colour fixed4 pbr = LightingStandard(s, viewDir, gi); // Calculates the reflection color fixed3 color = 0; for (int n = 1; n <= 8; n++) { float wavelength = abs(sin_thetaL - sin_thetaV) * d / n; color += spectral_zucconi6(wavelength); } color = saturate(color); // Adds the refelection to the material colour pbr.rgb += color; return pbr; }
In the snippet above we use values of up to 8. For better results, you can go higher, although this should already account for a significant part of the iridescent reflection.
We now have one last thing left to do. Calculating sin_thetaL
and sin_thetaV
. That requires to introduce yet another concept: the tangent vector. We will see how to calculate that in the next part of this tutorial.
Conclusion
You can find the complete series here:
- Part 1. The Nature of Light
- Part 2. Improving the Rainbow (Part 1)
- Part 3. Improving the Rainbow (Part 2)
- Part 4. Understanding Diffraction Grating
- Part 5. The Mathematics of Diffraction Grating
- Part 6. CD-ROM Shader: Diffraction Grating (Part 1)
- Part 7. CD-ROM Shader: Diffraction Grating (Part 2)
- Part 8. Iridescence on Mobile
- Part 9. The Mathematics of Thin-Film Interference
- Part 10. Car Paint Shader: Thin-Film Interference
Become a Patron!
You can download the Unity package for the CD-ROM Shader effect on Patreon.
Leave a Reply