<script src="https://unpkg.com/[email protected]"></script>
body {
background-color: #000;
margin: 0px;
overflow: hidden;
}
const renderer = new THREE.WebGLRenderer( {
alpha: false
} );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild( renderer.domElement );
const scene = new THREE.Scene;
const camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 10000 );
camera.position.set( 8, 6, 0 );
camera.lookAt( new THREE.Vector3( 0, 0, 0 ) );
const light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 30, 30, 30 );
light.castShadow = true;
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
light.shadow.camera.top = 24;
light.shadow.camera.right = 24;
light.shadow.camera.bottom = -24;
light.shadow.camera.left = -24;
light.shadow.camera.far = 64;
scene.add( light );
const floor = new THREE.Mesh( new THREE.PlaneBufferGeometry( 12, 12, 1, 1 ), new THREE.MeshStandardMaterial( { color: new THREE.Color( 'grey' ) } ) );
floor.rotation.x = -90 * 0.01745327;
floor.receiveShadow = true;
scene.add( floor );
scene.background = new THREE.Color( 'white' );
const uTime = {
shared: true,
mixed: true,
value: 0
};
const loader = new THREE.CubeTextureLoader();
loader.setCrossOrigin( "" );
loader.setPath( 'https://threejs.org/examples/textures/cube/pisa/' );
const cubeTexture = loader.load( [
'px.png', 'nx.png',
'py.png', 'ny.png',
'pz.png', 'nz.png'
] );
// Creating 2 new materials, each based on THREE.MeshStandardMaterial adding new features
let testMaterial;
const bouncingMaterial = THREE.extendMaterial( THREE.MeshPhongMaterial, {
class: THREE.CustomMaterial,
// In this case ShaderMaterial would be fine too, just for some features such as envMap this is required
vertexHeader: 'uniform float offsetScale;',
vertex: {
transformEnd: 'transformed += normal * offsetScale;'
},
uniforms: {
envMap: cubeTexture,
offsetScale: {
mixed: true, // Uniform will be passed to a derivative material (MeshDepthMaterial below)
linked: true, // Similar as shared, but only for derivative materials, so wavingMaterial will have it's own, but share with it's shadow material
value: 0
}
}
} );
const wavingMaterial = THREE.extendMaterial( bouncingMaterial, {
class: THREE.CustomMaterial,
vertexHeader: 'uniform float uTime;',
vertex: {
transformEnd: 'transformed.z += sin( position.y * 4.0 + uTime ) * 0.25;'
},
uniforms: {
envMap: cubeTexture,
uTime
}
} );
testMaterial = wavingMaterial;
// Meshes are patched so you can store the material variant on the original material instead having to assign it to every new instance ( see next: clone )
testMaterial.customDepthMaterial = THREE.extendMaterial( THREE.MeshDepthMaterial, {
template: testMaterial
} );
const mesh = new THREE.Mesh( new THREE.TorusKnotBufferGeometry( .7, .1, 100, 16 ), testMaterial );
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.position.z = -3;
mesh.position.y = 2;
const clone = mesh.clone();
clone.position.z = 3;
// We want a individual offsetScale parameter for mesh2 one so we clone the material, the depth material automatically gets cloned and linked to the new material as well
const mesh2 = mesh.clone();
mesh2.position.x = 2;
mesh2.position.z = 0;
// Tip: if you want to avoid creating new materials just for individual uniforms you can also have a look at <link>
// this let's you share materials and store the individual "parameters" for uniforms on each mesh instead.
scene.add( mesh, clone, mesh2 );
let t = 0.0;
function render() {
t += 0.05;
uTime.value = t;
mesh.material.uniforms.offsetScale.value = Math.abs( Math.sin( t ) ) * .15;
mesh2.material.uniforms.offsetScale.value = Math.abs( Math.sin( t * 2.0 ) ) * .15;
requestAnimationFrame( render );
renderer.render( scene, camera );
}
render();
View Compiled
This Pen doesn't use any external CSS resources.