“使用 Three.js 的 WebGL 小实验。古怪的艺术画廊。”
HTML:
<div id="scene-container"
aria-label="A quirky 3d scene consisting of animated geometric shapes: spheres, boxes, cones, vases and sunny side up eggs."
role="img">
</div>
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #b89287;
}
#scene-container {
position: absolute;
width: 100vw;
height: 100vh;
}
JAVASCRIPT:
import * as THREE from "https://unpkg.com/three@0.123.0/build/three.module.js";
import { OrbitControls } from "https://unpkg.com/three@0.123.0/examples/jsm/controls/OrbitControls";
import { SVGLoader } from "https://unpkg.com/three@0.123.0/examples/jsm/loaders/SVGLoader.js";
import * as TWEEN from "https://cdnjs.cloudflare.com/ajax/libs/tween.js/18.6.4/tween.esm.js";
;
let container, camera, controls, scene, renderer;
// GLOBAL MESHES
let ball, hoopBig, hoopSmall, hoopSmaller, hoopSmallest;
let artwork;
// ASSETS
let lettersURL = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/good-morning-new.svg';
// MOUSE
let mouseX = 0;
let mouseY = 0;
function init() {
container = document.querySelector('#scene-container');
scene = new THREE.Scene();
createCamera();
createControls();
createLights();
createRenderer();
createGeometries();
createMaterials();
createSVG(lettersURL);
createMeshes();
initTweens();
document.addEventListener("mousemove", onDocumentMouseMove);
window.addEventListener("resize", onWindowResize);
renderer.setAnimationLoop(() => {
render();
update();
animate();
});
}
function createCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(-7, 5, 12);
}
function createControls() {
controls = new OrbitControls(camera, container);
}
function createLights() {
const ambientLight = new THREE.HemisphereLight(0xddeeff, 0x202020, 5);
const mainLight = new THREE.DirectionalLight( 0xffffff, 5 );
mainLight.position.set(10, 10, 10);
scene.add(ambientLight, mainLight);
}
function createGeometries() {
const box = new THREE.BoxBufferGeometry( 1, 1, 1 );
const ball = new THREE.SphereBufferGeometry( 0.5, 32, 32 );
const cylinderTall = new THREE.CylinderBufferGeometry( 0.5, 0.5, 3, 64 );
const cone = new THREE.ConeBufferGeometry( 1, 4, 64 );
const coneSmall = new THREE.ConeBufferGeometry( 0.5, 1.5, 64 );
const disc = new THREE.CylinderBufferGeometry( 0.65, 0.5, 0.25, 64 );
const hoopBig = new THREE.TorusBufferGeometry( 1.25, 0.15, 32, 100 );
const hoopSmall = new THREE.TorusBufferGeometry( 0.5, 0.025, 32, 100 );
const path = new CustomSinCurve( 0.5 );
const tube = new THREE.TubeGeometry( path, 256, 0.05, 64, false );
const lathe = createLathe();
return {
box,
ball,
cylinderTall,
cone,
coneSmall,
disc,
hoopBig,
hoopSmall,
tube,
lathe
};
}
function createMaterials() {
const black = new THREE.MeshStandardMaterial({
color: 0x000000,
roughness: 0.8,
metalness: 0.6,
flatShading: true
});
black.color.convertSRGBToLinear();
const seagreen = new THREE.MeshStandardMaterial({
color: 0x00B5CC,
roughness: 0.1,
metalness: 0.6,
flatShading: true
});
seagreen.color.convertSRGBToLinear();
const brick = new THREE.MeshStandardMaterial({
color: 0xc0392b,
roughness: 0.1,
metalness: 0.6,
flatShading: true
});
brick.color.convertSRGBToLinear();
const lemon = new THREE.MeshStandardMaterial({
color: 0xe87e04,
roughness: 0.1,
metalness: 0.6,
flatShading: true
});
lemon.color.convertSRGBToLinear();
const green = new THREE.MeshStandardMaterial({
color: 0xf7d63,
roughness: 0.1,
metalness: 0.6,
flatShading: true
});
green.color.convertSRGBToLinear();
const bordeaux = new THREE.MeshStandardMaterial({
color: 0x55091d,
roughness: 0.9,
metalness: 0.6,
flatShading: true
});
bordeaux.color.convertSRGBToLinear();
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load( 'https://assets.codepen.io/911157/textureWine_512_optimized.jpg' );
texture.encoding = THREE.sRGBEncoding;
texture.anisotropy = 16;
const marbled = new THREE.MeshStandardMaterial({
map: texture
});
return {
lemon,
brick,
marbled,
bordeaux,
seagreen,
green,
black
};
}
function CustomSinCurve( scale ) {
THREE.Curve.call( this );
this.scale = ( scale === undefined ) ? 1 : scale;
}
CustomSinCurve.prototype = Object.create( THREE.Curve.prototype );
CustomSinCurve.prototype.constructor = CustomSinCurve;
CustomSinCurve.prototype.getPoint = function ( t ) {
var tx = t * 2 - 1.5;
var ty = 0.35 * Math.sin( 5.9 * Math.PI * t);
var tz = 0;
return new THREE.Vector3( tx, ty, tz ).multiplyScalar( this.scale );
};
function createSVG( url ) {
const loader = new SVGLoader();
loader.load( url, function ( data ) {
let paths = data.paths;
let group = new THREE.Group();
group.scale.multiplyScalar( 0.011 );
group.position.x = -9;
group.rotation.x = Math.PI;
group.position.y = 5;
group.position.z = -3;
for ( let i = 0; i < paths.length; i ++ ) {
let path = paths[ i ];
let material = new THREE.MeshBasicMaterial( {
color: path.color,
side: THREE.DoubleSide,
depthWrite: false
} );
let shapes = path.toShapes( true );
for ( let j = 0; j < shapes.length; j ++ ) {
let shape = shapes[ j ];
let geometry = new THREE.ShapeBufferGeometry( shape );
let mesh = new THREE.Mesh( geometry, material );
group.add( mesh );
}
}
artwork.add( group );
},
function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
function ( error ) {
console.log( 'An error happened' );
}
);
} // createSVG
function createLathe() {
const points = [];
let pointsNum = 20;
for ( let i = 0; i < pointsNum; i ++ ) {
points.push( new THREE.Vector2( Math.sin( 0.7 * i ) * 3 + 4, ( i - 5 ) * 2 ));
}
const lathe = new THREE.LatheBufferGeometry( points, 32 );
return lathe;
}
function createMeshes() {
artwork = new THREE.Group();
scene.add( artwork );
const geometries = createGeometries();
const materials = createMaterials();
// CYLINDER + TORUS SCULPTURE
const cylinderTall = new THREE.Mesh( geometries.cylinderTall, materials.bordeaux );
cylinderTall.position.x = -2;
cylinderTall.rotation.x = Math.PI;
hoopBig = new THREE.Mesh( geometries.hoopBig, materials.black );
hoopBig.position.x = -2;
hoopBig.position.y = 1.15;
const disc = new THREE.Mesh( geometries.disc, materials.black );
disc.position.x = -2;
disc.position.y = 1.15;
const cylinderGroup = new THREE.Group();
cylinderGroup.add( cylinderTall, hoopBig, disc );
// SHELL + ROTATING HOOPS
const shell = new THREE.Mesh( geometries.ball, materials.marbled );
shell.position.set( -5, -0.1, 0 );
shell.scale.set( 2.5, 2.0, 1 );
shell.rotation.y = Math.PI;
const stand = new THREE.Mesh( geometries.box, materials.black );
stand.position.set( -5, -1, 0 );
hoopSmall = new THREE.Mesh( geometries.hoopSmall, materials.black );
hoopSmall.position.set( -5, 1.75, 0 );
hoopSmaller = hoopSmall.clone();
hoopSmaller.scale.set( 1.5, 1.5, 1 );
hoopSmallest = hoopSmall.clone();
hoopSmallest.scale.set( 0.75, 0.75, 0.75 );
const hoopGroup = new THREE.Group();
hoopGroup.add( hoopSmall, hoopSmaller, hoopSmallest );
const shellGroup = new THREE.Group();
shellGroup.add( shell, stand, hoopGroup );
// BALL + BOXES GROUP
const ball2 = new THREE.Mesh( geometries.ball, materials.seagreen );
ball2.position.set( -3.5, -0.65, 1 );
ball2.scale.multiplyScalar( 0.85 );
const box = new THREE.Mesh( geometries.box, materials.black );
box.position.set( -3.5, -1.5, 1 );
const box2 = box.clone();
box2.position.y = -2.25;
box2.scale.y = 0.5;
box2.material = materials.green;
const boxGroup = new THREE.Group();
boxGroup.add( ball2, box, box2 );
// BOXES GROUP 2
const box3 = new THREE.Mesh( geometries.box, materials.lemon );
box3.position.y = -2;
const box4 = box3.clone();
box4.material = materials.bordeaux;
box4.position.y = box3.position.y - 0.7;
box4.scale.y = 0.35;
const box5 = box4.clone();
box5.material = materials.black;
box5.position.y = box3.position.y - 0.9;
box5.scale.set( 1.25, 0.15, 1.25 );
const boxGroup2 = new THREE.Group();
boxGroup.add( box3, box4, box5 );
boxGroup.position.x = -0.5;
boxGroup.position.z = 1;
// EYE + EYELASHES GROUP
const eye = new THREE.Mesh( geometries.ball, materials.brick );
eye.position.set( 0.5, 1.15, -1);
const hoopExtra = new THREE.Mesh( geometries.hoopSmall, materials.black );
hoopExtra.position.set( 0.5, 1.15, -1 );
hoopExtra.scale.set( 2, 1.5, 1 );
const tube = new THREE.Mesh( geometries.tube, materials.green );
tube.position.set( 0.5, 2.9, -1.05 );
tube.rotation.z = 0.5 * Math.PI;
tube.scale.x = 1.5;
const tube2 = tube.clone();
tube2.position.set( -0.2, 2, -1.05 );
tube2.rotation.z = -0.25 * Math.PI;
const tube3 = tube.clone();
tube3.position.set( 1.5, 2.65, -1.05 );
tube3.rotation.z = 0.35 * Math.PI;
const eyeGroup = new THREE.Group();
eyeGroup.add( eye, hoopExtra, tube, tube2, tube3 );
// CONE BIG
const cone = new THREE.Mesh( geometries.cone, materials.black );
cone.position.set( 2, 0, 0 );
// HOURGLASS + FALLING BALL
const coneSmall = new THREE.Mesh( geometries.coneSmall, materials.seagreen );
coneSmall.position.y = -1;
const coneSmallUp = coneSmall.clone();
coneSmallUp.position.y = 0.45;
coneSmallUp.rotation.x = Math.PI;
ball = new THREE.Mesh( geometries.ball, materials.green );
ball.position.y = 3;
const hourglassGroup = new THREE.Group();
hourglassGroup.position.x = 4;
hourglassGroup.add( coneSmall, coneSmallUp, ball );
// VASE
const vase = new THREE.Mesh( geometries.lathe, materials.marbled );
vase.scale.set( 0.14, 0.08, 0.14 );
vase.position.set( 4.5, -1.5, 1.75 );
vase.rotation.y = 0.75 * Math.PI;
vase.material.side = THREE.DoubleSide;
// EGGS
const egg = new THREE.Mesh( geometries.ball, materials.lemon );
egg.position.set( -5.25, 3.6, -3);
egg.scale.set( 1, 1, 0.45 );
const egg2 = egg.clone();
egg2.position.set( -3, 3.6, -3);
const egg3 = egg.clone();
egg3.position.set( 3.5, 3.8, -3);
artwork.add(
shellGroup,
boxGroup,
boxGroup2,
cylinderGroup,
eyeGroup,
cone,
hourglassGroup,
vase,
egg,
egg2,
egg3
);
}
function initTweens() {
let tween = new TWEEN.Tween(ball.position)
.to({ y: 1.5 }, 3000)
.easing(TWEEN.Easing.Bounce.Out)
.yoyo(false)
.start()
;
let tween2 = new TWEEN.Tween(hoopBig.rotation)
.to({ x: Math.PI / 2 }, 6000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.repeat(3)
.yoyo(true)
.start()
;
let tween3 = new TWEEN.Tween(hoopSmaller.rotation)
.to({ y: 2 * Math.PI }, 5000)
.easing(TWEEN.Easing.Linear.None)
.repeat(Infinity)
.yoyo(false)
.start()
;
let tween4 = new TWEEN.Tween(hoopSmallest.rotation)
.to({ y: -2 * Math.PI }, 3000)
.easing(TWEEN.Easing.Linear.None)
.repeat(Infinity)
.yoyo(false)
.start()
;
let tween5 = new TWEEN.Tween(hoopSmall.rotation)
.to({ y: -2 * Math.PI }, 6000)
.easing(TWEEN.Easing.Linear.None)
.repeat(Infinity)
.yoyo(false)
.start()
;
}
function createRenderer() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.gammaFactor = 2.2;
renderer.outputEncoding = THREE.GammaEncoding;
renderer.physicallyCorrectLights = true;
renderer.shadowMap.autoUpdate = false;
container.appendChild( renderer.domElement );
}
function animate( time ) {
TWEEN.update(time);
}
function update() {
let factorX = .001;
let factorY = .001;
if ( artwork ) {
artwork.rotation.y += 0.1 * ( factorX * mouseX - artwork.rotation.y );
artwork.rotation.x += 0.05 * ( factorY * mouseY - artwork.rotation.x );
}
}
function render() {
renderer.render( scene, camera );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove( event ) {
mouseX = (event.clientX - (window.innerWidth / 2));
mouseY = (event.clientY - (window.innerWidth / 2));
}
init();
源码:
https://codepen.io/ScavengerFrontend/pen/xxKPLrL
体验:
https://codepen.io/ScavengerFrontend/full/xxKPLrL