<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
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://mevedia.com/share/OrbitControls.js
  2. https://threejs.org/examples/js/controls/TransformControls.js
  3. https://mevedia.com/share/ExtendMaterial.js?latest