Spherical Harmonics Math

DirectXMath (aka XNAMath version 3) provides almost all the functionality of the original D3DXMath library with two exceptions. The first is the 'matrix stack' helper and the second is the 'spherical harmonics' math functions. The matrix stack is fairly easy to implement, but the SH math functions are another story. The original code for SH math in the D3DX utility library was written by John Snyder (MSR) and Peter-Pike Sloan (former Microsoft now at NVIDIA), and was the basis for a number of DirectX SDK samples and graphics publications as well as several ATI/AMD demos. With the removal of the SH math functionality from D3DX11 and the retirement of the D3DX library generally (see Where is the DirectX SDK?), this post provides a replacement for this functionality.

Note this post does not include the Precomputed Radiance Transfer (PRT) simulator that was shipped in D3DX9. You can continue to make use of that library for offline computations, and then use this post's SH math routines for runtime usage in a modern Direct3D 11 application without any legacy D3DX dependencies.


The DirectXSH.h/.cpp file pair provides the SH math functions. It is based on DirectXMath and requires Visual Studio 2012 or VS 2010 with the Windows 8.0 SDK.

Note it is fairly easy to modify these files if you must use XNAMath for compatibility with Xbox 360 or VS 2008 by removing namespaces and changing from SAL2 to VS-style SAL, but that is left as an exercise to the reader.

XMSHEvalDirection Evaluates the Spherical Harmonic basis functions. Equivalent to D3DXSHEvalDirection function.
XMSHRotate Rotates SH vector by a rotation matrix. Equivalent to the D3DXSHRotate function.
XMSHRotateZ Rotates the SH vector in the Z axis by an angle. Equivalent to the D3DXSHRotateZ function.
XMSHAdd Adds two SH vectors. Equivalent to the D3DXSHAdd function.
XMSHScale Scales a SH vector. Equivalent to the D3DXSHScale function.
XMSHDot Computes the dot product of two SH vectors. Equivalent to the D3DXSHDot function.
Computes the product of two functions represented using SH. Equivalent to D3DXSHMultiply2, D3DXSHMultiply3, D3DXSHMultiply4, D3DXSHMultiply5, and D3DXSHMultiply6.
XMSHEvalDirectionalLight Evaluates a directional light and returns spectral SH data. Equivalent to the D3DXSHEvalDirectionalLight function.
XMSHEvalSphericalLight Evaluates a spherical light and returns spectral SH data. Equivalent to the D3DXEvalSphericalLight function.
XMSHEvalConeLight Evaluates a light that is a cone of constant intensity and returns spectral SH data. Equivalent to the D3DXSHEvalConeLight function.
XMSHEvalHemisphereLight Evaluates a light that is a linear interpolant between two colors over the sphere. Equivalent to the D3DXSHEvalHemisphereLight function.


The DirectXSHD3D11.cpp module provides the function for computing the SH projection of a cubemap.

SHProjectCubeMap Projects a function represented in a cube map into spherical harmonics. Equivalent to the D3DX11SHProjectCubeMap function.


Update: The source code for this project is now available on GitHub under the MIT license.

Further Reading

Green, Robin. Spherical Harmonic Lighting: The Gritty Details. Game Developers' Conference, San Jose, CA, March 2003. PDF

Kautz, Jan. Peter-Pike Sloan, Jaakko Lehtinen. Precomputed Radiance Transfer: Theory and Practice. SIGGRAPH 2005 Course. Website.

Oat, Chris Oat and Natalya Tatarchuk. Irradiance Volumes for Games. Game Developers' Conference 2005, ATI Technologies, GDC 2005. PDF

Sloan, Peter-Pike. Stupid Spherical Harmonics (SH) Tricks. Game Developer Conference 2008 PDF PPTX

Sloan, Peter-Pike, Jan Kautz, and John Snyder. Precomputed Radiance Transfer for Real-Time Rendering in Dynamic, Low-Frequency Lighting Environments. ACM Transactions on Graphics (TOG), Proceedings of the 29th Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH), pp. 527-536. New York, NY: ACM Press, 2002. PDF

Sloan, Peter-Pike, Jesse Hall, John Hart, and John Snyder. Clustered Principal Components for Precomputed Radiance Transfer. ACM Transactions on Graphics (TOG), Vol. 22, Issue 3 (SIGGRAPH), pp. 382-391. New York, NY: ACM Press, July 2003. PDF

Sloan, Peter-Pike, Ben Luna and John Snyder. Local, Deformable Precomputed Radiance Transfer. ACM Transaction on Graphics 24(3) [Proceedings of SIGGRAPH], 2005. PDF PPT

Sloan, Peter-Pike. Normal Mapping for Precomputed Radiance Transfer. ACM Symposium on Interactive 3D Graphics and Games 2006. March, 2006. PDF PPT

Xinguo Liu, Peter-Pike Sloan, Heung-Yeung Shum and John Snyder. All-Frequency Precomputed Radiance Transfer for Glossy Objects. Eurographics Symposium on Rendering 2004, June, 2004. PDF

Comments (4)

  1. Or sed away all the patented SAL cruft completely, so it's usable for compilers that refuse to touch SAL due to fear of patents.

  2. I SAL annotate headers and code I write to support the /analyze feature of Visual C++ which is included in all editions with VS 2012.

    All Windows headers have SAL annotation, and all Windows 8.0 SDK headers have SAL2 annotations, so alternative compilers will have to cope with these annotations to consume existing SDKs.

    It's pretty easy to make some macros that remove SAL2 should it be a problem for some alternative or older compiler:

    #if !defined(_In_reads_)
    #define _Analysis_assume_(exp)
    #define _In_reads_(exp)
    #define _Out_writes_(exp)
    #define _Out_writes_ops_(exp)
    #define _In_reads_bytes_(exp)
    #define _Out_writes_bytes_(exp)

    #ifndef _Use_decl_annotations_
    #define _Use_decl_annotations_

    In fact, alternative compilers don't have to do anything with SAL annotations. The sal.h header in the Windows SDK already maps these to empty #defines unless the __PREFAST__ compiler symbol is active.

  3. gainexec says:

    Its outstanding

  4. Alakanu says:

    Hello there,
    I’ve noticed that in your SHProjectCubeMap, the weight calculation for each texel is done in the Peter Pyke Sloan’s fashion(calculate fDiffSolid at each step and then normalize at the end). I’ve been using the same code as yours for projection but one thing bugs me. My 0 order coefficients of R, G and B are all in the range [1,2], but shouldn’t they be the average value of R,G and B (which belongs to [0,1]) over the unit sphere? I am writing this because I checked my code a milion times and I was wondering whether it is a common result or there is still something I cannot figure out.

Skip to main content