<!--
check out the playlist here:
https://open.spotify.com/playlist/64lTO7UojBC6xoDljFdrVu?si=i6Shy45qSkWm34HgPsl4Xg
-->
<script id="vertexShader" type="x-shader/x-vertex">
uniform float mRefractionRatio;
uniform float mFresnelBias;
uniform float mFresnelScale;
uniform float mFresnelPower;

varying vec3 vReflect;
varying vec3 vRefract[3];
varying float vReflectionFactor;

varying vec2 f_uv;
varying vec3 f_normal;
varying vec3 f_position;
void main() {
  f_uv = uv;
  f_normal = normalize( normalMatrix * normal );
  f_position = position;
  
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
  vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
  
  vec3 worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );
  
  vec3 I = worldPosition.xyz - cameraPosition;
  
  vReflect = reflect( I, worldNormal );
  vRefract[0] = refract( normalize( I ), worldNormal, mRefractionRatio );
  vRefract[1] = refract( normalize( I ), worldNormal, mRefractionRatio * 0.99 );
  vRefract[2] = refract( normalize( I ), worldNormal, mRefractionRatio * 0.98 );
  vReflectionFactor = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( I ), worldNormal ), mFresnelPower );
  
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform samplerCube tCube;

varying vec3 vReflect;
varying vec3 vRefract[3];
varying float vReflectionFactor;
  
uniform sampler2D texture;
uniform int u_useTexture;
uniform vec3 u_albedo;
uniform vec3 u_ambient;
uniform vec3 u_lightPos;
uniform vec3 u_lightCol;
uniform float u_lightIntensity;
uniform vec3 CamPos;

varying vec3 f_position;
varying vec3 f_normal;
varying vec2 f_uv;

void main() {
  vec4 color = vec4(u_albedo, 1.0);
  
  if (u_useTexture == 1) {
    color = texture2D(texture, f_uv);
  }
  float d = clamp(dot(f_normal, normalize(u_lightPos - f_position)), 0.0, 1.0);
  float t = clamp(dot(f_normal, normalize(CamPos - f_position)), 0.0, 1.0);
  
  float red = 0.5 + 0.5*(cos(6.28*(t)));
  float green = 0.5 + 0.5*(cos(6.28*(t+0.33)));
  float blue = 0.5 + 0.5*(cos(6.28*(t+0.67)));
  
  vec3 iridescent_color = vec3(red, green, blue);
  
  vec4 reflectedColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );
  vec4 refractedColor = vec4( 1.0 );
  
  refractedColor.r = mix(textureCube( tCube, vec3( -vRefract[0].x, vRefract[0].yz ) ).r, red, 0.8);
  refractedColor.g = mix(textureCube( tCube, vec3( -vRefract[1].x, vRefract[1].yz ) ).g, green, 0.8);
  refractedColor.b = mix(textureCube( tCube, vec3( -vRefract[2].x, vRefract[2].yz ) ).b, blue, 0.8);
  gl_FragColor = mix( refractedColor, reflectedColor, clamp( vReflectionFactor, 0.0, 1.0 ) );
}
</script>
html, body{overflow:hidden;}
View Compiled
var container;
var camera, scene, renderer, textureCube, object;
var mouseX = 0, mouseY = 0;
var clock = new THREE.Clock();
var path = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/253981/';
var format = ".png";
var order = ["sky", "sky", "sky", "sky", "cov", "sky"];
var urls = [];
order.forEach(side => {urls.push(`${path}${side}${format}`);});
var loaded = false;
init();

function init() {
  renderer = new THREE.WebGLRenderer({antialias:true, alpha: true });
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  container = document.createElement( 'div' );
  document.body.appendChild( container );
  container.appendChild( renderer.domElement );
  var textureCube = new THREE.CubeTextureLoader().load(urls);
  textureCube.format = THREE.RGBFormat;
  
  height = window.innerHeight;
  width = window.innerWidth;
  camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
  camera.position.z =-200;
  scene = new THREE.Scene();
  //renderer.setClearColor( 0x000000, 0 );
  scene.background = new THREE.Color( 0x111111 );
  
  var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.5 );
  scene.add( ambientLight );
  var pointLight = new THREE.PointLight( 0xffffff, 0.9);
  camera.add( pointLight );
  scene.add( camera );
  uniforms = {
    mRefractionRatio: { value: 0.96 },
    mFresnelBias: { value: 0.05 },
    mFresnelPower: { value: 2.0 },
    mFresnelScale: { value: 0.5 },
    tCube: { value: textureCube },
    texture: {type: 't',value: null},
    u_useTexture: {type: 'i',value: true},
    u_albedo: {type: 'v3',value: new THREE.Color(0x000000)},
    u_ambient: {type: 'v3',value: new THREE.Color(0x000000)},
    u_lightPos: {type: 'v3',value: new THREE.Vector3(-600, 0, 400)},
    u_lightCol: {type: 'v3',value: new THREE.Color(0xffffff)},
    u_lightIntensity: {type: 'f',value: .6},
    CamPos: {type: 'v3',value:  new THREE.Vector3(-600, 0, 400)}
  }
  group = new THREE.Group();
  material = new THREE.ShaderMaterial( {
    uniforms: uniforms,
    vertexShader: document.getElementById( 'vertexShader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentShader' ).textContent
  });     
  var manager = new THREE.LoadingManager( function(){
    object.traverse( function ( child ) {
      if ( child.isMesh ) {
        child.material = material;
        child.material.side = THREE.DoubleSide;
      } 
    });
    scale = 90;
    object.scale.set(scale,scale,scale);
    group.position.y = 3;
    //group.rotation.y =Math.PI * -.5;
    object.castShadow = true;
    object.receiveShadow = true;
    group.add(object);
    scene.add( group );
    ;   
  } );
  var loader = new THREE.OBJLoader( manager );
  loader.load( 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/253981/heart10.obj', function ( obj ) {
    object = obj;
    loaded = true;
    animate();
  });

  window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
  requestAnimationFrame( animate );
  render();
}
function render() {
  timer = 0.001 * Date.now();
  if(loaded){
    group.rotation.x = Math.sin(timer * .5  ) * (Math.PI / 4) * -.5;
    group.rotation.y = (Math.PI / 2) + Math.sin(timer * .125  ) * (Math.PI);
    group.rotation.z = Math.sin(timer * .25  ) * (Math.PI / 8);
    group.position.z = Math.sin(timer *.125  ) * -20;
  }
  camera.lookAt( scene.position );
  camera.position.y = -5;
  renderer.render( scene, camera );
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/253981/OBJLoader.js