diff --git a/src/pages/manual/lights/index.html b/src/pages/manual/lights/index.html
new file mode 100644
index 0000000..f18a86e
--- /dev/null
+++ b/src/pages/manual/lights/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Lights
+
+
+
+
+
diff --git a/src/pages/manual/lights/lights.ts b/src/pages/manual/lights/lights.ts
new file mode 100644
index 0000000..6964ff7
--- /dev/null
+++ b/src/pages/manual/lights/lights.ts
@@ -0,0 +1,223 @@
+import { initial } from '@/lib/initial'
+import { useMenu } from '@/lib/menu'
+import GUI from 'lil-gui'
+import * as Three from 'three'
+import {
+ OrbitControls,
+ RectAreaLightHelper,
+ RectAreaLightUniformsLib
+} from 'three/examples/jsm/Addons.js'
+import { ColorGUIHelper, DegRadHelper } from '@/lib/helper'
+import { PhysicalLightingModel } from 'three/webgpu'
+
+// Business
+useMenu()
+const world = initial()
+const { renderer, camera, scene } = world
+renderer.setClearColor(0x000000)
+camera.fov = 45
+camera.far = 100
+camera.position.set(0, 10, 20)
+
+const controls = new OrbitControls(camera, renderer.domElement)
+controls.target.set(0, 5, 0)
+controls.update()
+
+RectAreaLightUniformsLib.init()
+
+// Plane
+const planeSize = 40
+const loader = new Three.TextureLoader()
+const texture = loader.load('/images/checker.png')
+texture.wrapS = Three.RepeatWrapping
+texture.wrapT = Three.RepeatWrapping
+texture.magFilter = Three.NearestFilter
+texture.colorSpace = Three.SRGBColorSpace
+const repeats = planeSize / 2
+texture.repeat.set(repeats, repeats)
+
+const planeGeo = new Three.PlaneGeometry(planeSize, planeSize)
+const planeMat = new Three.MeshPhongMaterial({
+ map: texture,
+ side: Three.DoubleSide
+})
+const mesh = new Three.Mesh(planeGeo, planeMat)
+mesh.rotation.x = Math.PI * -0.5
+scene.add(mesh)
+
+// Geometries
+// Box
+{
+ const cubeSize = 4
+ const cubeGeo = new Three.BoxGeometry(cubeSize, cubeSize, cubeSize)
+ const cubeMat = new Three.MeshPhongMaterial({ color: '#8AC' })
+ const mesh = new Three.Mesh(cubeGeo, cubeMat)
+ mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
+ scene.add(mesh)
+}
+// Sphere
+{
+ const sphereRadius = 3
+ const sphereWidthDivisions = 32
+ const sphereHeightDivisions = 16
+ const sphereGeo = new Three.SphereGeometry(
+ sphereRadius,
+ sphereWidthDivisions,
+ sphereHeightDivisions
+ )
+ const sphereMat = new Three.MeshPhongMaterial({ color: '#CA8' })
+ const mesh = new Three.Mesh(sphereGeo, sphereMat)
+ mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
+ scene.add(mesh)
+}
+// Cone
+{
+ const geo = new Three.ConeGeometry(4, 5, 3)
+ const mat = new Three.MeshStandardMaterial({
+ roughness: 0.5,
+ metalness: 0.5
+ })
+ const cone = new Three.Mesh(geo, mat)
+ cone.position.set(0, 2.5, -6)
+ scene.add(cone)
+}
+// Torus
+{
+ const geo = new Three.TorusGeometry(1, 0.5)
+ const material = new Three.MeshToonMaterial({ color: 0xffaaaa })
+ const torus = new Three.Mesh(geo, material)
+ torus.position.set(6, 0.5, -6.5)
+ torus.rotation.x = 0.5 * Math.PI
+
+ scene.add(torus)
+}
+// Cylinder
+{
+ const geo = new Three.CylinderGeometry(1, 1, 10)
+ const material = new Three.MeshPhysicalMaterial({
+ color: 0xffffff,
+ metalness: 0.5,
+ roughness: 0
+ })
+ const cyliner = new Three.Mesh(geo, material)
+ cyliner.position.set(0, 1, 5)
+ cyliner.rotation.z = Three.MathUtils.degToRad(90)
+
+ scene.add(cyliner)
+}
+
+// Ambient Light
+const gui = new GUI()
+{
+ // const light = new Three.AmbientLight(0xffffff, 1)
+ // scene.add(light)
+ // gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color')
+ // gui.add(light, 'intensity', 0, 2, 0.01)
+}
+
+// Hemisphere Light
+{
+ const light = new Three.HemisphereLight(0xb1e1ff, 0xff9ea3, 1)
+ scene.add(light)
+
+ gui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color')
+ gui.addColor(new ColorGUIHelper(light, 'groundColor'), 'value').name('groundColor')
+ gui.add(light, 'intensity', 0, 2, 0.01)
+}
+
+function makeVector3GUI(gui: GUI, vector: Three.Vector3, name: string, onChangeFn: Function) {
+ const folder = gui.addFolder(name)
+ folder.add(vector, 'x', -10, 10).onChange(onChangeFn)
+ folder.add(vector, 'y', 0, 10).onChange(onChangeFn)
+ folder.add(vector, 'z', -10, 10).onChange(onChangeFn)
+ folder.open()
+}
+// Directional Light
+{
+ const light = new Three.DirectionalLight(0xffffff, 1)
+ light.position.set(0, 10, 0)
+ light.target.position.set(-5, 0, 0)
+ scene.add(light)
+ scene.add(light.target)
+
+ const lightHelper = new Three.DirectionalLightHelper(light)
+ scene.add(lightHelper)
+
+ const lightGui = gui.addFolder('Directional Light')
+
+ const updateLight = () => {
+ light.target.updateMatrixWorld()
+ lightHelper.update()
+ }
+
+ makeVector3GUI(lightGui, light.position, 'position', updateLight)
+ makeVector3GUI(lightGui, light.target.position, 'target', updateLight)
+}
+
+// Point Light
+{
+ const light = new Three.PointLight(0xffffff, 15, 5)
+ light.position.set(0, 1.5, 0)
+ scene.add(light)
+
+ const lightHelper = new Three.PointLightHelper(light)
+ scene.add(lightHelper)
+
+ const lightGui = gui.addFolder('Point Light')
+
+ const updateLight = () => {
+ lightHelper.update()
+ }
+
+ lightGui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color')
+ lightGui.add(light, 'intensity', 0, 250, 0.01)
+ lightGui.add(light, 'distance', 0, 40).onChange(updateLight)
+
+ makeVector3GUI(lightGui, light.position, 'position', updateLight)
+}
+
+// Spot Light
+{
+ const light = new Three.SpotLight(0xffffff, 250, 40, (12 / 180) * Math.PI, 0)
+ light.position.set(0, 10, 0)
+ light.target.position.set(6, 0, -6.5)
+ scene.add(light)
+ scene.add(light.target)
+
+ const helper = new Three.SpotLightHelper(light)
+ scene.add(helper)
+
+ const updateLight = () => {
+ light.target.updateMatrixWorld()
+ helper.update()
+ }
+
+ const lightGui = gui.addFolder('Spot Light')
+ lightGui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color')
+ lightGui.add(light, 'intensity', 0, 250)
+ lightGui.add(light, 'distance', 0, 40).onChange(updateLight)
+ lightGui.add(light, 'penumbra', 0, 1)
+ lightGui.add(new DegRadHelper(light, 'angle'), 'value', 0, 90).name('angle').onChange(updateLight)
+ makeVector3GUI(lightGui, light.position, 'position', updateLight)
+ makeVector3GUI(lightGui, light.target.position, 'target', updateLight)
+}
+
+// Rectangle Area Light
+{
+ const light = new Three.RectAreaLight(0xffffff, 5, 12, 4)
+ light.position.set(0, 10, 5)
+ light.rotation.x = Three.MathUtils.degToRad(-90)
+ scene.add(light)
+
+ const helper = new RectAreaLightHelper(light)
+ light.add(helper)
+
+ const lightGui = gui.addFolder('Rectangle Area Light')
+ lightGui.addColor(new ColorGUIHelper(light, 'color'), 'value').name('color')
+ lightGui.add(light, 'intensity', 0, 10, 0.01)
+ lightGui.add(light, 'width', 0, 20)
+ lightGui.add(light, 'height', 0, 20)
+ lightGui.add(new DegRadHelper(light.rotation, 'x'), 'value', -180, 180).name('x rotation')
+ lightGui.add(new DegRadHelper(light.rotation, 'y'), 'value', -180, 180).name('y rotation')
+ lightGui.add(new DegRadHelper(light.rotation, 'z'), 'value', -180, 180).name('z rotation')
+}
diff --git a/src/public/images/checker.png b/src/public/images/checker.png
new file mode 100644
index 0000000..92d2c0e
Binary files /dev/null and b/src/public/images/checker.png differ