Source file: /examples/lights/spot.js
import {
MeshMaterial3D,
WebGLRenderer,
TextureLoader,
PerspectiveProjection,
Camera,
WebGLRenderDevice,
PlaneMeshBuilder,
OrbitCameraControls,
MeshMaterialPlugin,
LightPlugin,
AmbientLight,
TextureType,
SkyBox,
UVSphereMeshBuilder,
LambertMaterial,
BasicMaterial,
Color,
PhongMaterial,
StandardMaterial,
SpotLight,
Vector3,
CuboidMeshBuilder,
Affine3,
CanvasTarget,
SkyboxPlugin,
ShadowPlugin,
SpotLightShadow,
CameraPlugin
} from "webgllis"
import { GUI } from "dat.gui"
const canvas = document.createElement('canvas')
const renderTarget = new CanvasTarget(canvas)
const renderDevice = new WebGLRenderDevice(canvas,{
depth:true
})
const renderer = new WebGLRenderer({
plugins: [
new ShadowPlugin(),
new LightPlugin(),
new SkyboxPlugin(),
new MeshMaterialPlugin(),
new CameraPlugin()
]
})
// loaders
const textureLoader = new TextureLoader()
// assets
const environmentTexture = textureLoader.load({
paths: [
"/assets/images/skybox/miramar_right.png",
"/assets/images/skybox/miramar_left.png",
"/assets/images/skybox/miramar_top.png",
"/assets/images/skybox/miramar_bottom.png",
"/assets/images/skybox/miramar_back.png",
"/assets/images/skybox/miramar_front.png",
],
type: TextureType.TextureCubeMap,
})
const texture = textureLoader.load({
paths: ["/assets/images/uv.jpg"],
})
/**@type {[LambertMaterial, PhongMaterial, StandardMaterial]} */
const materials = [
new LambertMaterial({
mainTexture: texture
}),
new PhongMaterial({
mainTexture: texture
}),
new StandardMaterial({
mainTexture: texture,
roughness: 0.4,
metallic: 0
})
]
const meshBuilder = new PlaneMeshBuilder()
const lightMeshBuilder = new CuboidMeshBuilder()
lightMeshBuilder.height = 0.01
lightMeshBuilder.width = 0.01
lightMeshBuilder.depth = 0.5
meshBuilder.width = 10
meshBuilder.height = 10
// objects
const camera = new Camera(renderTarget)
const cameraControls = new OrbitCameraControls(camera, canvas)
const ambientLight = new AmbientLight()
const shadow = new SpotLightShadow()
const light = new SpotLight()
const skyBox = new SkyBox({
day: environmentTexture
})
const ground = new MeshMaterial3D(meshBuilder.build(), materials[0])
const objects = createObjects()
const lightHelper = new MeshMaterial3D(lightMeshBuilder.build(), new BasicMaterial({
color: new Color(1, 0, 0)
}))
ambientLight.intensity = 0.15
skyBox.transform.orientation.rotateY(Math.PI)
light.intensity = 1
light.add(lightHelper)
light.transform.position.y = 1
light.range = 10
light.shadow = shadow
ground.transform.orientation.rotateX(-Math.PI / 2)
//set up the camera
cameraControls.distance = 2
if (camera.projection instanceof PerspectiveProjection) {
camera.projection.fov = Math.PI / 180 * 75
camera.projection.aspect = innerWidth / innerHeight
}
document.body.append(canvas)
addEventListener("resize", updateView)
updateView()
requestAnimationFrame(update)
function createObjects() {
const results = []
const meshBuilder2 = new UVSphereMeshBuilder()
meshBuilder2.radius = 0.25
const sphereMesh = meshBuilder2.build()
for (let x = -5; x < 5; x++) {
for (let y = -5; y < 5; y++) {
const object = new MeshMaterial3D(sphereMesh, materials[0])
object.transform.position.x = x
object.transform.position.y = 0.5
object.transform.position.z = y
results.push(object)
}
}
return results
}
function update() {
cameraControls.update()
renderer.render([ground, ...objects, light, ambientLight, skyBox, camera], renderDevice)
requestAnimationFrame(update)
}
function updateView() {
canvas.style.width = innerWidth + "px"
canvas.style.height = innerHeight + "px"
canvas.width = innerWidth * devicePixelRatio
canvas.height = innerHeight * devicePixelRatio
if (camera.projection instanceof PerspectiveProjection) {
camera.projection.aspect = innerWidth / innerHeight
}
}
// gui controls
const options = [
'LAMBERT',
'PHONG',
'STANDARD'
]
const settings = {
position: new Vector3(),
color: {
r: 0,
g: 0,
b: 0
},
material: options[0],
shadow: true
}
const controls = new GUI()
const lightFolder = controls.addFolder("Light")
const shadowFolder = controls.addFolder("Shadow")
lightFolder
.add(light.transform.position, 'x', -10, 10)
.name("Translate X")
lightFolder
.add(light.transform.position, 'y', 0, 2)
.name("Translate Y")
lightFolder
.add(light.transform.position, 'z', -10, 10)
.name("Translate Z")
lightFolder
.add(settings.position, 'x', -1, 1)
.name("Direction X")
.onChange(setOrientation)
lightFolder
.add(settings.position, 'y', -1, 1)
.name("Direction Y")
.onChange(setOrientation)
lightFolder
.add(settings.position, 'z', -1, 1)
.name("Direction Z")
.onChange(setOrientation)
lightFolder
.add(light, 'intensity', 0, 100)
.name("Intensity")
lightFolder
.add(light, 'innerAngle', 0, Math.PI / 2)
.name("Inner Angle")
lightFolder
.add(light, 'outerAngle', 0, Math.PI / 2)
.name("Outer Angle")
lightFolder
.add(light, 'range', 0, 10)
.name("Range")
lightFolder
.add(light, 'decay', 0, 100)
.name("Distance Decay")
lightFolder
.add(settings, 'material', options)
.name("Material")
.onChange(changeMaterial)
lightFolder
.addColor(settings, 'color')
.name('Color')
.onChange((value) => {
light.color.set(
value.r / 255,
value.g / 255,
value.b / 255
)
})
shadowFolder
.add(settings, 'shadow')
.name("Enable Shadow")
.onChange(toggleShadows)
shadowFolder
.add(shadow, 'near', 0.1, 1)
.name('Near')
shadowFolder
.add(shadow, 'bias', 0, 0.01)
.name('Bias')
shadowFolder
.add(shadow, 'normalBias', 0, 0.005)
.name('Normal Bias')
lightFolder.open()
shadowFolder.open()
/**
* @param {string} value
*/
function changeMaterial(value) {
switch (value) {
case options[0]:
objects.forEach((o) => o.material = materials[0])
ground.material = materials[0]
break;
case options[1]:
objects.forEach((o) => o.material = materials[1])
ground.material = materials[1]
break;
case options[2]:
objects.forEach((o) => o.material = materials[2])
ground.material = materials[2]
break;
default:
break;
}
}
function setOrientation() {
const transform = Affine3.lookAt(settings.position.normalize(), Vector3.Zero, Vector3.Y)
const [_, orientation] = transform.decompose()
light.transform.orientation.copy(orientation)
}
/**
* @param {boolean} value
*/
function toggleShadows(value) {
if (value) {
light.shadow = shadow
} else {
light.shadow = undefined
}
}