…so please forgive my lack of knowledge and high ambitions.
First, pay attention to the date of this CodePen, it was created in version R71
. A lot has changed since then. Secondly, from what you say, you're starting with Three.js, I'm not sure if starting with custom light implemented in a custom shader is the best way to start your journey with WebGL
... 😉
Your requirements can be created in a number of ways, simpler and more difficult. The CodePen one you presented is one of the (much) more difficult ones.
Enter "multiple" in examples and you will get a series of results showing how what you expect can be achieved.
https://threejs.org/examples/?q=multiple#webgl_multiple_elements
But back to your points...
① Would like for the light source to be stationary, instead of
following the cursor.
There is no great methodology here, just turn off listening to mouse movement and position the selected light statically.
② Would like to be able to place the background image inside of a
smaller div, to include cases when it doesn't need to cover the whole
page.
Modify the canvas size as you need, place it in another div as you want. If you want the sizes to react dynamically (which is desirable in most cases), create a listener for the screen size change. Check example below how insert texture…
③ Would like to be able to define multiple normal map enabled
background images, lit by shared lighting (for scene continuity).
You can set the light as shared or individualize it to a specific canvas. In the example you have shared.
④ Would like to be able specify that the image textures be tiled.
In the example you have 4 tiles. Of course, you can divide the viewport however you want, but it is worth spending a little more time to take a closer look at performance. The most adequate solution is probably render scissors.
https://threejs.org/docs/#api/en/renderers/WebGLRenderer.setScissor
Generally, if you care about multiplicity, you can have, for example, one canvas and several scenes on it, or you can have a separate canvas for each scene. Everything will come down to the individual project and adapting the relationship between efficiency and the expected effect.
⑤ …In other words, HDR image-based lighting…
Take a look here…
⑥ A subtle tilt of the background image position (or light source
position) in response to movements detected by the mobile device
accelerometer would be cool…
About Sensors and Parallax take look here and here.
Example
body { margin: 0; overflow: hidden; }
.background-div { position: absolute; width: 50vw; height: 50vh; }
#background1 { top: 0; left: 0; }
#background2 { top: 0; left: 50vw; }
#background3 { top: 50vh; left: 0; }
#background4 { top: 50vh; left: 50vw; }
<div class="background-div" id="background1"></div>
<div class="background-div" id="background2"></div>
<div class="background-div" id="background3"></div>
<div class="background-div" id="background4"></div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
function fourPlanes(container) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.z = 20;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.enabled = true;
const light = new THREE.PointLight(0xFFFF00, 777, 100);
light.position.set(40, 0, 10);
scene.add(light);
function createTexturedPlane(textureURL, normalMapURL, position, scene) {
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(textureURL);
const normalMap = textureLoader.load(normalMapURL);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
normalMap.wrapS = normalMap.wrapT = THREE.RepeatWrapping;
texture.repeat.set(4, 4);
normalMap.repeat.set(4, 4);
const material = new THREE.MeshStandardMaterial({
map: texture,
normalMap: normalMap,
});
const geometry = new THREE.PlaneGeometry(10, 10);
const plane = new THREE.Mesh(geometry, material);
plane.position.copy(position);
scene.add(plane);
}
createTexturedPlane('https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/water.jpg', 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/waternormals.jpg', new THREE.Vector3(0, 0, 0), scene);
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
}
const background1 = document.getElementById('background1');
const background2 = document.getElementById('background2');
const background3 = document.getElementById('background3');
const background4 = document.getElementById('background4');
fourPlanes(background1);
fourPlanes(background2);
fourPlanes(background3);
fourPlanes(background4);
</script>