three.js dev template - module
HTML
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js",
"three/addons/": "https://unpkg.com/three/examples/jsm/"
}
}
</script>
<script type="x-shader/x-fragment" id="fragment-shader">
uniform sampler2D uTexture[TEXTURE_COUNT];
varying vec2 vUv;
varying vec4 vTextureUvBounds[TEXTURE_COUNT];
varying vec2 vTextureUv[TEXTURE_COUNT];
void main () {
vec2 uv = vUv;
vec4 color = vec4(0.0);
float textureAlpha[TEXTURE_COUNT];
bool outsideUv = uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0;
for (int i = 0; i < TEXTURE_COUNT; i++) {
vec2 textureUv = vTextureUv[i];
bool outsideTextureUvBounds = textureUv.x < vTextureUvBounds[i].x || textureUv.x > vTextureUvBounds[i].y || textureUv.y < vTextureUvBounds[i].z || textureUv.y > vTextureUvBounds[i].w;
if (outsideUv || outsideTextureUvBounds) {
textureAlpha[i] = 0.0;
} else {
textureAlpha[i] = 1.0;
}
}
#include <texture_sampling>
gl_FragColor = color;
}
</script>
<script type="x-shader/x-vertex" id="vertex-shader">
uniform vec2 uTextureRes[TEXTURE_COUNT];
uniform vec2 uRes;
uniform float uTranslateX;
varying vec2 vTextureUv[TEXTURE_COUNT];
varying vec4 vTextureUvBounds[TEXTURE_COUNT];
varying vec2 vUv;
void main () {
vUv = uv;
for (int i = 0; i < TEXTURE_COUNT; i++) {
vTextureUv[i] = vUv;
float textureAspect = uTextureRes[i].x / uTextureRes[i].y;
float meshAspect = uRes.x / uRes.y;
// fit: cover
float scaleX = 1.0;
if (textureAspect < meshAspect) {
// Texture is "taller"; scale y
float scale = textureAspect / meshAspect;
vTextureUv[i].y = vTextureUv[i].y * scale - (scale - 1.0) * 0.5;
// Calculate UV bounds for cropping
vTextureUvBounds[i].xy = vec2(0.0, 1.0); // minX, maxX
...
CSS
body {
margin: 0px;
}
JavaScript
import * as THREE from "three"
import { OrbitControls } from "three/addons/controls/OrbitControls.js"
init()
async function init() {
const sources = [
"https://picsum.photos/id/1/960/540",
"https://picsum.photos/id/10/540/960",
"https://picsum.photos/id/20/960/540",
]
const loader = new THREE.TextureLoader()
const textures = await Promise.all(
sources.map((src) => loader.loadAsync(src)),
)
// renderer
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setAnimationLoop(animate)
document.body.appendChild(renderer.domElement)
// scene
const scene = new THREE.Scene()
// camera
const camera = new THREE.PerspectiveCamera(
40,
window.innerWidth / window.innerHeight,
1,
10000,
)
camera.position.set(20, 20, 20)
// controls
const controls = new OrbitControls(camera, renderer.domElement)
// axes
scene.add(new THREE.AxesHelper(20))
// geometry
const geometry = new THREE.PlaneGeometry(10, 10)
// material
const material = new THREE.ShaderMaterial({
defines: {
TEXTURE_COUNT: textures.length,
},
uniforms: {
uRes: {
value: new THREE.Vector2(
geometry.parameters.width,
geometry.parameters.height,
),
},
uTranslateX: { value: 0 },
uTextureRes: {
value: textures.map(
(t) => new THREE.Vector2(t.image.width, t.image.height),
),
},
uTexture: { value: textures }
},
side: THREE.DoubleSide,
vertexShader: document.getElementById("vertex-shader").textContent,
fragmentShader: document.getElementById("fragment-shader").textContent,
})
material.onBeforeCompile = (shader) => {
let textureSampling = ""
for (let i = 0; i < textures.length; i++) {
textureSampling += `
vec4 textureColor${i} = texture2D(uTexture[${i}], vTextureUv[${i}]);
color...