150 lines
5.6 KiB
JavaScript
150 lines
5.6 KiB
JavaScript
const SHOW_STATS = false;
|
|
const CANYON_WIDTH = 400;
|
|
const CANYON_LENGTH = 120;
|
|
const CANYON_SEGMENTS_W = 27;
|
|
const CANYON_SEGMENTS_L = 10;
|
|
const CLIFF_BASE = 60;
|
|
const CLIFF_VARY = 15;
|
|
const FLOOR_VARY = 10;
|
|
const CANYON_SPEED = 70;
|
|
const CAMERA_DRIFT_DISTANCE = 15;
|
|
const CAMERA_DRIFT_SPEED = 0.05;
|
|
|
|
let lastUpdate;
|
|
let camera, scene, renderer, composer;
|
|
let cameraBaseX, cameraBaseY;
|
|
let uResolutionScale;
|
|
let uTime;
|
|
let canyonA, canyonB;
|
|
|
|
function init() {
|
|
// stats
|
|
if (SHOW_STATS) {
|
|
const stats = new Stats();
|
|
stats.domElement.classList.add('stats-element');
|
|
document.body.appendChild(stats.domElement);
|
|
requestAnimationFrame(function updateStats(){
|
|
stats.update();
|
|
requestAnimationFrame(updateStats);
|
|
});
|
|
}
|
|
|
|
// basic setup
|
|
const container = document.getElementById('container');
|
|
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 300);
|
|
const cameraDistance = 70;
|
|
const cameraAngle = .05*Math.PI;
|
|
camera.position.z = cameraDistance;
|
|
cameraBaseX = 0;
|
|
cameraBaseY = 0.3 * (CLIFF_BASE + CLIFF_VARY + FLOOR_VARY);
|
|
camera.position.y = cameraBaseY;
|
|
camera.rotation.x = -cameraAngle;
|
|
scene = new THREE.Scene();
|
|
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
|
// shader setup
|
|
lastUpdate = new Date().getTime();
|
|
const vertexShader = document.getElementById( 'vertexShader' ).textContent;
|
|
const fragmentShader = document.getElementById( 'fragmentShader' ).textContent;
|
|
uTime = { type: 'f', value: 1.0 };
|
|
uResolutionScale = { type: 'f', value: 1.0 };
|
|
|
|
// add objects
|
|
const canyonGeometry = new THREE.PlaneGeometry(CANYON_WIDTH, CANYON_LENGTH, CANYON_SEGMENTS_W, CANYON_SEGMENTS_L);
|
|
canyonGeometry.rotateX(-0.5 * Math.PI);
|
|
const reverseGeometry = canyonGeometry.clone();
|
|
const simplexA = new SimplexNoise(Math.floor(0xffff*Math.random()));
|
|
const simplexB = new SimplexNoise(Math.floor(0xffff*Math.random()));
|
|
for (let i = 0, l = canyonGeometry.vertices.length; i < l; i++) {
|
|
const { x, z } = canyonGeometry.vertices[i];
|
|
canyonGeometry.vertices[i].y =
|
|
Math.min(1.0, Math.pow(x/50, 4)) * Math.round(CLIFF_BASE + simplexA.noise2D(x,z) * CLIFF_VARY) + Math.round(simplexB.noise2D(x,z) * FLOOR_VARY);
|
|
reverseGeometry.vertices[i].y =
|
|
Math.min(1.0, Math.pow(x/50, 4)) * Math.round(CLIFF_BASE + simplexA.noise2D(x,-z) * CLIFF_VARY) + Math.round(simplexB.noise2D(x,-z) * FLOOR_VARY);
|
|
}
|
|
const canyonMaterial = new THREE.ShaderMaterial({
|
|
transparent: true,
|
|
side: THREE.DoubleSide,
|
|
uniforms: { uTime, uResolutionScale },
|
|
vertexShader,
|
|
fragmentShader
|
|
});
|
|
canyonMaterial.extensions.derivatives = true;
|
|
canyonA = new THREE.Mesh(geomToBufferGeomWithCenters(canyonGeometry), canyonMaterial);
|
|
scene.add(canyonA);
|
|
canyonB = new THREE.Mesh(geomToBufferGeomWithCenters(reverseGeometry), canyonMaterial);
|
|
canyonB.position.z -= CANYON_LENGTH;
|
|
scene.add(canyonB);
|
|
container.appendChild(renderer.domElement);
|
|
|
|
// effect composition
|
|
const renderScene = new THREE.RenderPass(scene, camera);
|
|
const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.86);
|
|
bloomPass.threshold = 0.3;
|
|
bloomPass.strength = 2.5 * uResolutionScale.value;
|
|
bloomPass.radius = 0.3 * uResolutionScale.value;
|
|
composer = new THREE.EffectComposer(renderer);
|
|
composer.addPass(renderScene);
|
|
composer.addPass(bloomPass);
|
|
|
|
// event listeners
|
|
onWindowResize();
|
|
window.addEventListener( 'resize', onWindowResize, false);
|
|
document.getElementById('resolution').addEventListener('change', onResolutionChange, false);
|
|
}
|
|
|
|
// events
|
|
function onWindowResize(evt) {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
composer.setSize(window.innerWidth, window.innerHeight);
|
|
}
|
|
function onResolutionChange(evt) {
|
|
uResolutionScale.value = parseFloat(evt.target.value);
|
|
bloomPass.strength = 2.5 * uResolutionScale.value;
|
|
bloomPass.radius = 0.3 * uResolutionScale.value;
|
|
renderer.setPixelRatio( window.devicePixelRatio / uResolutionScale.value );
|
|
}
|
|
function animate() {
|
|
const currentTime = new Date().getTime();
|
|
const timeSinceLastUpdate = currentTime - lastUpdate;
|
|
lastUpdate = currentTime;
|
|
const deltaTime = timeSinceLastUpdate / 1000;
|
|
uTime.value += deltaTime;
|
|
// move canyons
|
|
canyonA.position.z += deltaTime * CANYON_SPEED;
|
|
canyonB.position.z += deltaTime * CANYON_SPEED;
|
|
if (canyonA.position.z > CANYON_LENGTH) {
|
|
canyonA.position.z -= 2*CANYON_LENGTH;
|
|
}
|
|
if (canyonB.position.z > CANYON_LENGTH) {
|
|
canyonB.position.z -= 2*CANYON_LENGTH;
|
|
}
|
|
// drift camera (simple lissajous)
|
|
camera.position.x = cameraBaseX + CAMERA_DRIFT_DISTANCE*Math.sin(7*CAMERA_DRIFT_SPEED*Math.PI*uTime.value);
|
|
camera.position.y = cameraBaseY + CAMERA_DRIFT_DISTANCE*Math.sin(5*CAMERA_DRIFT_SPEED*Math.PI*uTime.value);
|
|
// render
|
|
// renderer.render( scene, camera );
|
|
composer.render();
|
|
requestAnimationFrame( animate );
|
|
}
|
|
|
|
// boot
|
|
init();
|
|
animate();
|
|
|
|
// utils
|
|
// adapted from https://github.com/mrdoob/three.js/blob/dev/examples/webgl_materials_wireframe.html for wireframe effect
|
|
function geomToBufferGeomWithCenters(geom) {
|
|
const buffGeom = new THREE.BufferGeometry().fromGeometry(geom);
|
|
const vectors = [new THREE.Vector3(1,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1)];
|
|
const { position } = buffGeom.attributes;
|
|
const centers = new Float32Array(position.count*3);
|
|
for (let i=0, l=position.count; i<l; i++) {
|
|
vectors[i%3].toArray(centers,i*3);
|
|
}
|
|
buffGeom.setAttribute('center', new THREE.BufferAttribute(centers,3));
|
|
return buffGeom;
|
|
} |