<!-- Credits:-->
<!-- Outside of Society hairy creature:
http://oos.moxiecode.com/js_webgl/hair/
The code is from 2014, using three.js version 58, I changed it so it works with the newest version:D -->

<!-- "hair" shader -->
<script type="x-shader/x-vertex" id="vs-lines">

uniform float globalTime;
uniform vec3 gravity;
uniform vec3 gravity2;
uniform float spacing;

attribute vec3 customColor;
attribute float seed;
attribute float seed2;
attribute float draw;
attribute float index2;
attribute vec3 norm;

varying vec3 vColor;
varying float vDraw;
varying vec3 vNormal;

void main() {

vDraw = draw;
vColor = customColor;

vec3 displacement = vec3(0.0,0.0,0.0);
vec3 forceDirection = vec3(0.0,0.0,0.0);

float displacementFactor = pow(index2, 1.5);
float displacementFactor2 = pow(index2, 3.5);
float displacementFactor3 = pow(1.0-index2, 1.0);
  
// "gravity"
vec3 g = gravity;
g.x *= displacementFactor2 * seed2;

// "wind"
forceDirection.x = sin(globalTime*0.01+seed2*0.75+index2*0.5) * 0.1*displacementFactor;
forceDirection.y = cos(globalTime*0.1+seed2*1.0+index2*1.0) * 0.1*displacementFactor;
forceDirection.z = sin(globalTime*0.1+seed2*0.5+index2*1.0) * 0.1*displacementFactor;

displacement = g + forceDirection + ((1.0-index2)*gravity2) * seed;

vec3 aNormal = norm;
aNormal.xyz += displacement*displacementFactor;

vNormal = norm*(1.0-index2);
vNormal += (gravity2-gravity)*0.05;

vec3 animated = position;

// curl it slightly
animated.x += aNormal.x*index2*20.0*displacementFactor3;

animated += aNormal*index2*(spacing*seed);

if (animated.y < -150.0+seed2*20.0) {
  animated.y = -150.0+seed2*20.0;
  vDraw = 0.0;
}

vec4 mvPosition = modelViewMatrix * vec4( animated, 1.0 );
gl_Position = projectionMatrix * mvPosition;

}

</script>

<script type="x-shader/x-fragment" id="fs-lines">

uniform vec3 color;

varying vec3 vColor;
varying float vDraw;
varying vec3 vNormal;

void main() {

if (vDraw == 0.0) {
  discard;
}

float depth = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = smoothstep( 450.0, 300.0, depth );		

vec3 light = vec3(1.0,1.0,1.0);
float d = pow(max(0.3,dot(vNormal.xyz, light))*2.0, 1.5);
gl_FragColor = vec4( (color * vColor) * d * fogFactor, 1.0 );

}

</script>

<!-- matcap shader -->

<script type="x-shader/x-vertex" id="vs-matcap">

	varying vec2 vN;

	void main() {

		vec3 e = normalize( vec3( modelViewMatrix * vec4( position, 1.0 ) ) );
		vec3 n = normalize( normalMatrix * normal );

		vec3 r = reflect( e, n );
		float m = 2. * sqrt( pow( r.x, 2. ) + pow( r.y, 2. ) + pow( r.z + 1., 2. ) );
		vN = r.xy / m + .5;

		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1. );

	}

</script>

<script type="x-shader/x-vertex" id="fs-matcap">
	
	uniform sampler2D tMatCap;

	varying vec2 vN;

	void main() {
		
		vec3 base = texture2D( tMatCap, vN ).rgb;
		gl_FragColor = vec4( base, 0.44 );

	}
	
</script>

<script id="vs-halo" type="x-shader/x-vertex">
  
  varying vec3 vNormal; 
  
  void main() { 
    
    vNormal = normalize( normalMatrix * normal ); 
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 
  
  }
  
</script>
<script id="fs-halo" type="x-shader/x-vertex">
  
    varying vec3 vNormal; 
  
  void main() { 
    
      float intensity = pow( 0.5 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 0.5 ); 
      gl_FragColor = vec4( 0.96, 0.96, 0.86, 0.6 ) * intensity; 
      
  }
  
</script>
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  background: rgb(238,238,238);
  background: radial-gradient(circle, rgba(238,238,238,1) 0%, rgba(199,166,152,1) 53%, rgba(201,87,87,1) 100%);
  background: rgb(207,203,203);
  background: radial-gradient(circle, rgba(207,203,203,1) 0%, rgba(179,117,107,1) 63%, rgba(117,20,20,1) 100%);
  cursor: pointer;
}
let container, camera, scene, renderer;
let hair, lanternSphere, lanternRings;
let flower, flower2;
let goldMat;

var delta, time, oldTime;

var uniforms;

var gravity = new THREE.Vector3(0, 0, 0);
var gravity2 = new THREE.Vector3(0, 0, 0);

var mouse = new THREE.Vector2(0, 0);

var mouseObj = {

    x: 0,
    y: 0,
    vx: 0,
    vy: 0

};

var projector = new THREE.Projector();
var raycaster = new THREE.Raycaster();
var collisionMesh;

const gltfURL = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/flower-fiesta-2.glb';

window.onload = () => {
  
  init();
  animate();
  
};

function init() {
  
    container = document.createElement('div');
    document.body.appendChild(container);

    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xC95757);
    createCamera();
    createLights();
    loadGLTFModel(gltfURL);
    createLantern();
    createCollisionMesh();
    createHair();
    
    createRenderer();

    window.addEventListener('resize', onWindowResize, false);
    document.addEventListener('mousemove', onMouseMove, false);
    document.addEventListener('touchmove', onTouchMove, false);

}

function createCamera() {

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);

    camera.position.set(0, 0, 400);
    camera.lookAt(scene.position);
    scene.add(camera);

}

function createLights() {

    const hemisphereLight = new THREE.HemisphereLight(
        0xddeeff, 
        0x202020, 
        3.5
    );

    const mainLight = new THREE.DirectionalLight(0xffffff, 5);
    mainLight.position.set(10, 10, 10);

    scene.add(hemisphereLight, mainLight);

}

function createLantern() {

    var scaleX = 1.25;
    var radius = 90;
  
    var sphereGeometry = new THREE.SphereBufferGeometry(radius + 1.5, 32, 32);
    var torusGeometry = new THREE.TorusBufferGeometry(radius, 1.5, 4, 80);
    var cylinderGeometry = new THREE.CylinderBufferGeometry( radius / 2.5, radius / 2.5, 20, 32 );
    var handleGeometry = new THREE.TorusBufferGeometry(radius / 2.5, 1.5, 4, 60);

    var sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000});
    // var sphereMaterial = new THREE.MeshLambertMaterial({
    //     color: 0xc40101,
    //     emissive: 0xff0000,
    //     emissiveIntensity: .75,
    //     side: THREE.DoubleSide
    // });
  
    const textureLoader = new THREE.TextureLoader();
    var matcap = textureLoader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/matcap_gold.jpg');
  
    goldMat = new THREE.ShaderMaterial({
        transparent: false,
        side: THREE.DoubleSide,
		    uniforms: { 
			      tMatCap: { type: 't', value: matcap } 
		    },
		    vertexShader: document.getElementById( 'vs-matcap' ).textContent,
		    fragmentShader: document.getElementById( 'fs-matcap' ).textContent,
		    flatShading: false
	  });
    
    var haloMat = new THREE.ShaderMaterial({
      
        uniforms: {},
        vertexShader: document.getElementById('vs-halo').textContent,
        fragmentShader: document.getElementById('fs-halo').textContent,
        side: THREE.BackSide,
        transparent: true
      
    });
  
    lanternSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
    lanternSphere.scale.x = scaleX;
  
    var ringTop = new THREE.Mesh(cylinderGeometry, goldMat);
    ringTop.position.y = radius;  
  
    var handleTop = new THREE.Mesh(handleGeometry, goldMat);
    handleTop.position.y = radius * 1.15;  
  
    // var ringBottom = new THREE.Mesh(cylinderGeometry, goldMat);
    // ringBottom.position.y = -radius * 0.9; 
    // ringBottom.scale.set(1.5, 0.75, 1.35);
  
    var torusMaterial = goldMat;
    var torus = new THREE.Mesh(torusGeometry, torusMaterial);
    torus.scale.x = scaleX;
    
    lanternRings = new THREE.Group();
  
    var torusNum = 9;
  
    for (i = 1; i < torusNum; i++) {
      
      var torusCopy = torus.clone();
      torusCopy.rotation.y = 2 * Math.PI * i / torusNum;
      lanternRings.add(torusCopy);
      
    }
  
    lanternRings.add(ringTop, handleTop);
  
    var ratio = window.innerWidth / window.innerHeight;
    if (ratio > 2) ratio = 4
    else ratio = 2.75;
    
    var haloMesh = new THREE.Mesh(sphereGeometry, haloMat);
    haloMesh.scale.set(ratio * 1.05, ratio, ratio);
    
    scene.add(lanternSphere, lanternRings, haloMesh);

}

function createCollisionMesh() {

    collisionMesh = new THREE.Mesh(
        new THREE.PlaneBufferGeometry(800, 700),
        new THREE.MeshBasicMaterial({
            opacity: 0,
            transparent: true
        })
    );
    collisionMesh.renderDepth = -200;
    scene.add(collisionMesh);

}

function createHair() {
  
// Credits: Outside of Society hairy creature: oos.moxiecode.com/js_webgl/hair/

    var attributes = {

        draw:        { type: 'f', value: [] },
        seed:        { type: 'f', value: [] },
        seed2:       { type: 'f', value: [] },
        customColor: { type: 'c', value: [] },
        index2:       { type: 'f', value: [] },
        norm:        { type: 'v3', value: [] }

    };

    uniforms = {

        color:       { type: "c", value: new THREE.Color(0xffcb15) },
        globalTime:  { type: "f", value: 0.0 },
        gravity:     { type: "v3", value: gravity },
        gravity2:    { type: "v3", value: gravity },
        spacing:     { type: "f", value: 35.0 }

    };

    var shaderMaterial = new THREE.ShaderMaterial({

        uniforms: uniforms,
        vertexShader: document.getElementById('vs-lines').textContent,
        fragmentShader: document.getElementById('fs-lines').textContent,

    });

    shaderMaterial.linewidth = 1;

    var lineGeo = new THREE.Geometry();
    var radius = 25;
    var num = 50;
    var baseGeo = new THREE.SphereGeometry(radius, num, num, undefined, undefined, 0.2, Math.PI * 0.5);

    for (var i = 0; i < baseGeo.vertices.length; i++) {

        baseGeo.vertices[i].x += Math.random() * 4 - 2;
        baseGeo.vertices[i].y += Math.random() * 4 - 2;
        baseGeo.vertices[i].z += Math.random();

    }

    var seedArray = [];
    var seedArray2 = [];
    var colorArray = [];
    var drawArray = [];
    var index2Array = [];
    var normArray = [];

    for (var i = 0; i < baseGeo.vertices.length; i++) {

        var num = 30;

        var base = baseGeo.vertices[i];
        var seed = 1 + Math.random() * 0.5;
        var seed2 = Math.random();

        var norm = new THREE.Vector3().copy(base).normalize();
        norm = norm.normalize();

        var black = 0.85 + Math.random() * 0.95;

        for (var j = 0; j < num; j++) {

            var vertex = new THREE.Vector3().copy(base);
            var color = new THREE.Color(0xffffff);
            color.setRGB(1.0 * black, 1.0 * black, 1.0 * black);
            // color.setRGB(1.0 * black, 1.0 * black, 1.0 * black);

            lineGeo.vertices.push(vertex);
            colorArray.push(color);
            seedArray.push(seed);
            seedArray2.push(seed2);
            index2Array.push(j / num);
            normArray.push(norm);

            if (j == num - 1 || j == 0) {

                drawArray.push(0);

            } else {

                drawArray.push(1);

            }

        }

    }

    var vertices = lineGeo.vertices;
    var values_color = attributes.customColor.value;
    var values_seed = attributes.seed.value;
    var values_seed2 = attributes.seed2.value;
    var values_draw = attributes.draw.value;
    var values_index2 = attributes.index2.value;
    var values_norm = attributes.norm.value;

    for (var v = 0; v < vertices.length; v++) {

        values_seed[v] = seedArray[v];
        values_seed2[v] = seedArray2[v];
        values_draw[v] = drawArray[v];
        values_color[v] = colorArray[v];
        values_index2[v] = index2Array[v];
        values_norm[v] = normArray[v];

    }
    
    var bufferLineGeo = new THREE.BufferGeometry();
    var positions = new Float32Array(vertices.length * 3);
    bufferLineGeo.setAttribute('position', new THREE.BufferAttribute( positions, 3).copyVector3sArray(vertices));
    
    var values_bColor = new Float32Array(values_color.length * 3);
    var values_bNorm  = new Float32Array(values_norm.length  * 3);
  
    bufferLineGeo.setAttribute('draw', new THREE.BufferAttribute(new Float32Array(values_draw),  1));
    bufferLineGeo.setAttribute('seed', new THREE.BufferAttribute(new Float32Array(values_seed),  1));
    bufferLineGeo.setAttribute('seed2', new THREE.BufferAttribute(new Float32Array(values_seed2), 1));
    bufferLineGeo.setAttribute('customColor', new THREE.BufferAttribute(values_bColor, 3).copyColorsArray(  values_color));
    bufferLineGeo.setAttribute('index2', new THREE.BufferAttribute(new Float32Array(values_index2), 1));
    bufferLineGeo.setAttribute('norm', new THREE.BufferAttribute(values_bNorm,  3).copyVector3sArray(values_norm));

    hair = new THREE.Line(bufferLineGeo, shaderMaterial, THREE.LineStrip);
    hair.position.y = -5;
    hair.scale.set(0.75, 0.8, 0.75);
    scene.add(hair);
  
}

function createRenderer() {

    renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: false
    });

    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.autoClear = false;
    renderer.setClearColor(0xC95757, 1.0);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.gammaFactor = 2.2;
    renderer.gammaOutput = true;
    renderer.physicallyCorrectLights = true;
  
    container.appendChild(renderer.domElement);
  
}

function onWindowResize(event) {

    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

}

function onMouseMove(event) {

    event.preventDefault();

    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

}

function onTouchMove(event) {

    event.preventDefault();

    mouse.x = (event.touches[0].clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.touches[0].clientY / window.innerHeight) * 2 + 1;

}


function animate() {

  requestAnimationFrame(animate);
  render();

}

function render() {
  
    if (flower && flower2) {

      flower.rotation.x += 0.025 * mouse.y;
      flower.rotation.z += 0.04 * mouse.x;
      flower2.rotation.x += 0.025 * mouse.y;
      flower2.rotation.z += 0.04 * mouse.x;
      
    }
  
    time = new Date().getTime();
    delta = time - oldTime;
    oldTime = time;

    if (isNaN(delta) || delta > 1000 || delta == 0) {

        delta = 1000 / 60;

    }

    uniforms.globalTime.value += delta * 0.005;

    var optimalDivider = delta / 16;
    var smoothing = Math.max(4, (20 / optimalDivider));

    // fake some gravity according to mouse movement
    mouseObj.vx *= 0.85;
    mouseObj.vy *= 0.005;
    mouseObj.x += mouseObj.vx;
    mouseObj.y += mouseObj.vy;
  
    gravity.x += (-(mouse.x - mouseObj.x) * 10 - gravity.x) / smoothing;
    gravity2.x += (-(mouse.x - mouseObj.x) * 10 - gravity2.x) / smoothing * 4;

    if (gravity.x < -5) gravity.x = -5;
    if (gravity2.x < -5) gravity2.x = -5;

    if (gravity.x > 5) gravity.x = 5;
    if (gravity2.x > 5) gravity2.x = 5;

    var dif = (mouse.x - mouseObj.x) * 300;
    var toy = (-5.0 + (Math.abs(dif) / 100) - (mouse.y - mouseObj.y) * 10);
    gravity.y += (toy - gravity.y) / smoothing;
    gravity2.y += (toy - gravity2.y) / smoothing;

    // intersection
    var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
    vector.unproject(camera);

    raycaster.set(camera.position, vector.sub(camera.position).normalize());

    var intersects = raycaster.intersectObject(collisionMesh);

    if (intersects.length > 0) {

        var inter = intersects[0];

        hair.position.x += 0.7*(inter.point.x - hair.position.x) / smoothing;
        hair.position.y += 0.7*(inter.point.y - hair.position.y) / smoothing;
        lanternSphere.position.x += 0.7*(inter.point.x - hair.position.x) / smoothing;
        lanternSphere.position.y += 0.7*(inter.point.y - hair.position.y) / smoothing;
        lanternRings.position.x += 0.7*(inter.point.x - hair.position.x) / smoothing;
        lanternRings.position.y += 0.7*(inter.point.y - hair.position.y) / smoothing;
      


    }

    renderer.render(scene, camera);

}

// load flowers
function loadGLTFModel(url) {
  
  var loader = new THREE.GLTFLoader();
  loader.load( url, 
  function ( gltf ) {
      gltf.scene.traverse( function ( child ) {
          if ( child.isMesh ) {
              child.geometry.center();
              child.material = goldMat;
          }
      });
    
      gltf.scene.scale.set(3.25, 3.25, 3.25);
      flower = gltf.scene;
      flower.rotation.set(Math.PI / 3, 0, 0);
      flower2 = flower.clone();
      flower.position.set(90, 0, 200);
      flower2.position.set(-120, 0, 200);
      scene.add( flower, flower2 );
    
  }, (xhr) => xhr, ( err ) => console.error( e ));
  
}
Run Pen

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/911157/THREEProjector.js
  3. https://s3-us-west-2.amazonaws.com/s.cdpn.io/911157/GLTFLoader.js