CD-ROM Shader: Diffraction Grating – Part 2

This post completes the series on how to create a shader for CD-ROMs.

You can find the complete series here:

A link to download the Unity project used in this series is also provided at the end of the page.

Introduction

In the first part of this tutorial, we have created a first approximation for the iridescent reflections that CD-ROMs exhibit. It’s important to remember that this shader is physically based. To correctly simulate the reflection we want, we need to make sure that the tracks on our CD-ROM are arranged in a circular way. This will ensure a radial reflection.

Slit Orientation

The grating equation that we have derived in The Mathematics of Diffraction Grating has a big limitation: it assumes that the slits are all aligned in the same direction. While this is often the case with insects’ exoskeletons, the bands on the surface of a CD-ROM are arranged in a circular pattern. If we naively implement the solution presented in the section above, we will obtain a rather disappointing reflection (below, right).

To correct this issue, we need to take into account the local orientation of the slits on a CD-ROM. Using the normal vector will not help here since all the slits share the same normal direction, which points away from the surface of the disk. What correctly captures the local orientation of a slit is its tangent vector (above, left).

 

In the diagram above, the normal direction N is in blue and the tangent direction T in red. The angles the light source and viewer make with the normal direction N are called \theta_L and \theta_V, respectively. The analogous angles with respect to T are \Theta_L and \Theta_V. As stated before, using \theta_L and \theta_V for our calculations will result in a “flat” reflection, since all slits share the same N. We need to find a to use \Theta_L and \Theta_V instead since they correctly capture the local orientation.

So far, we know that:

    \[N \cdot L = \cos{\theta_L}\; \; \; N \cdot V = \cos{\theta_V}\]

    \[T \cdot L = \cos{\Theta_L}\; \; \;  T \cdot V = \cos{\Theta_V}\]

Since T and N are orthogonal, the following property holds:

    \[T \cdot L = \cos{\Theta_L} = \sin{\theta_L}\]

    \[T \cdot V = \cos{\Theta_V} = \sin{\theta_V}\]

That is very convenient also because Cg offers a native implementation of the dot product. What’s left now is to calculate T.

❓ Where does the cosine come from?

Calculating the Tangent Vector

To complete our shader, we need to calculate the tangent vector T. Normally, this could be provided directly in the mesh vertices. However, given how simple the surface of a CD-ROM is, we can calculate it directly. Please, keep in mind that the approach presented in this tutorial is rather simple and will only work under the assumption that the surface of your CD-ROM mesh is correctly UV-mapped.

The diagram above shows how the tangent directions are calculated. The underlying assumption is that the surface of the disk is UV mapped like a quad, with coordinates ranging from (0,0) to (1,1). Once that is known, each point on the CD-ROM surface is remapped onto (-1,-1) to (+1,+1). With this change of the frame of reference, we have that the new coordinate of a point also corresponds with its direction away from the centre (green arrow). We can rotate that direction 90 degrees to find a vector that is tangent to concentric tracks of the CD-ROM (in red).

These operations need to be done in the surf function of the shader since the UV coordinates are not available in the lighting function LightingDiffraction.

// IN.uv_MainTex: [ 0, +1]
// uv:            [-1, +1]
fixed2 uv = IN.uv_MainTex * 2 -1;
fixed2 uv_orthogonal = normalize(uv);
fixed3 uv_tangent = fixed3(-uv_orthogonal.y, 0, uv_orthogonal.x);

What’s left now is to convert the calculated tangent from object space to world space. The conversion will take into account the object translation, rotation and scale.

worldTangent = normalize( mul(unity_ObjectToWorld, float4(uv_tangent, 0)) );

❓ How to pass the tangent direction to the lighting function?

❓ How to switch between coordinate spaces?

Putting All Together…

We now have all we need to calculate the colour contribution of the iridescence reflection:

inline fixed4 LightingDiffraction(SurfaceOutputStandard s, fixed3 viewDir, UnityGI gi)
{
	// Original colour
	fixed4 pbr = LightingStandard(s, viewDir, gi);
			
	// --- Diffraction grating effect ---
	float3 L = gi.light.dir;
	float3 V = viewDir;
	float3 T = worldTangent;

	float d = _Distance;
	float cos_ThetaL = dot(L, T);
	float cos_ThetaV = dot(V, T);
	float u = abs(cos_ThetaL - cos_ThetaV);

	if (u == 0)
		return pbr;

	// Reflection colour
	fixed3 color = 0;
	for (int n = 1; n <= 8; n++)
	{
		float wavelength = u * d / n;
		color += spectral_zucconi6(wavelength);
	}
	color = saturate(color);

	// Adds the refelection to the material colour
	pbr.rgb += color;
	return pbr;
}

❓ How this relates to the rainbow?

Conclusion

You can find the complete series here:

Become a Patron!
You can download the Unity package for the CD-ROM Shader effect on Patreon.

Comments

4 responses to “CD-ROM Shader: Diffraction Grating – Part 2”

  1. […] CD-ROM Shader: Diffraction Grating – Part 2 – Alan Zucconi August 28, 2022 […]

  2. Hi, Alan,

    Why the 1st equation N dot V is also cosQ(L), I thought N dot V should be cosQ(V)?

    1. Thank you, I’ve corrected it!

  3. […] 7. CD-ROM Shader: Diffraction Grating (Part […]

Leave a Reply

Your email address will not be published. Required fields are marked *