<!-- 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 ));
}
This Pen doesn't use any external CSS resources.