Spherical Harmonics Shader

by Matt

HTML

<base href="https://rawcdn.githack.com/mrdoob/three.js/r152/examples/" />
<script async src="https://cdn.jsdelivr.net/npm/[email protected]/dist/es-module-shims.js"></script>
<script type="importmap">
	{
		"imports": {
			"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
			"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
			"lil-gui": "https://cdn.jsdelivr.net/npm/[email protected]/dist/lil-gui.esm.min.js"
		}
	}
</script>

<template id="vertex-shader">

varying vec3 vPosition;

void main() {
  vPosition = position;
  
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</template>

<template id="fragment-shader">

#define WHITE vec3(1., 1., 1.)


// Reference: https://www.shadertoy.com/view/lsfXWH

#define k01 0.2820947918 // sqrt(  1/PI)/2
#define k02 0.4886025119 // sqrt(  3/PI)/2
#define k03 1.0925484306 // sqrt( 15/PI)/2
#define k04 0.3153915652 // sqrt(  5/PI)/4
#define k05 0.5462742153 // sqrt( 15/PI)/4
#define k06 0.5900435860 // sqrt( 70/PI)/8
#define k07 2.8906114210 // sqrt(105/PI)/2
#define k08 0.4570214810 // sqrt( 42/PI)/8
#define k09 0.3731763300 // sqrt(  7/PI)/4
#define k10 1.4453057110 // sqrt(105/PI)/4

float SH_0_0( in vec3 s ) { vec3 n = s.zxy; return  k01; }
float SH_1_0( in vec3 s ) { vec3 n = s.zxy; return -k02*n.y; }
float SH_1_1( in vec3 s ) { vec3 n = s.zxy; return  k02*n.z; }
float SH_1_2( in vec3 s ) { vec3 n = s.zxy; return -k02*n.x; }
float SH_2_0( in vec3 s ) { vec3 n = s.zxy; return  k03*n.x*n.y; }
float SH_2_1( in vec3 s ) { vec3 n = s.zxy; return -k03*n.y*n.z; }
float SH_2_2( in vec3 s ) { vec3 n = s.zxy; return  k04*(3.0*n.z*n.z-1.0); }
float SH_2_3( in vec3 s ) { vec3 n = s.zxy; return -k03*n.x*n.z; }
float SH_2_4( in vec3 s ) { vec3 n = s.zxy; return  k05*(n.x*n.x-n.y*n.y); }
float SH_3_0( in vec3 s ) { vec3 n = s.zxy; return -k06*n.y*(3.0*n.x*n.x-n.y*n.y); }
float SH_3_1( in vec3 s ) { vec3 n = s.zxy; return  k07*n.z*n.y*n.x; }
float SH_3_2( in...

CSS

canvas {
	position: fixed;
	inset: 0;
}

JavaScript

/**
 * Reference: https://www.shadertoy.com/view/lsfXWH
 */

import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GUI } from 'lil-gui'

let scene, camera, renderer
let mesh, orbitControls, clock
let params

init().then(animate)

async function init() {

	params = {
		colorPos: "#ff0000",
		colorNeg: "#0000ff",
    l0: [0],
    l1: [0,0,0],
    l2: [0,0,0,0,0],
	}
	
	scene = new THREE.Scene()
	scene.background = new THREE.Color('black')

	camera = new THREE.PerspectiveCamera(75, undefined, 0.1, 1000)
	camera.position.set(1, 2, 3)
	
	clock = new THREE.Clock()

	const directionalLight = new THREE.DirectionalLight('white')
	directionalLight.position.set(2, 3, 4)
	scene.add(directionalLight)

	const ambientLight = new THREE.AmbientLight('white', 0.3)
	scene.add(ambientLight)

	renderer = new THREE.WebGLRenderer({ antialias: true })
	document.body.appendChild(renderer.domElement)

	orbitControls = new OrbitControls(camera, renderer.domElement)

	const grid = new THREE.GridHelper()
	grid.position.y = -1
	scene.add(grid)
  
  const axes = new THREE.AxesHelper(2)
  scene.add(axes)
  
  const vertexShader = document.querySelector("#vertex-shader").content.textContent
  const fragmentShader = document.querySelector("#fragment-shader").content.textContent
	const uniforms = {
  	colorPos: { value: new THREE.Color(params.colorPos) },
  	colorNeg: { value: new THREE.Color(params.colorNeg) },
    f0_0: { value: params.l0[0] },
    f1_0: { value: params.l1[0] },
    f1_1: { value: params.l1[1] },
    f1_2: { value: params.l1[2] },
    f2_0: { value: params.l2[0] },
    f2_1: { value: params.l2[1] },
    f2_2: { value: params.l2[2] },
    f2_3: { value: params.l2[3] },
    f2_4: { value: params.l2[4] },
  }
  
	const geometry = new THREE.SphereGeometry(1, 128, 64)
	const material = new THREE.ShaderMaterial({
  	uniforms,
  	vertexShader,
    fragmentShader,
  })
  
	mesh = new THREE.Mesh(geometry,...