Skip to content

Instantly share code, notes, and snippets.

@Tjhost
Last active November 14, 2023 20:31
Show Gist options
  • Save Tjhost/a787d49d51c17cb9d8e46ef2c30fa80b to your computer and use it in GitHub Desktop.
Save Tjhost/a787d49d51c17cb9d8e46ef2c30fa80b to your computer and use it in GitHub Desktop.
Displacement Sphere - ThreeJS
<canvas id="scene"></canvas>
<a class="logo" href="https://codyb.co" target="_blank" rel="noreferrer noopener">
<img src="https://codyb.co/icon-256.png" />
</a>
<div class="info">
<h2>Displacement Sphere for <a href="https://codyb.co" target="_blank" rel="noreferrer noopener">my portfolio</a>.</h2>
</div>
// Displacement Sphere
// by Cody Bennett
// https://codyb.co
// Setup
const start = Date.now();
const canvas = document.getElementById('scene');
//Shaders
const sphereFragShader = `
#define PHONG
uniform vec3 diffuse;
uniform vec3 emissive;
uniform vec3 specular;
uniform float shininess;
uniform float opacity;
uniform float time;
varying vec2 vUv;
varying vec3 newPosition;
varying float noise;
#include <common>
#include <packing>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <emissivemap_pars_fragment>
#include <envmap_pars_fragment>
#include <gradientmap_pars_fragment>
#include <fog_pars_fragment>
#include <bsdfs>
#include <lights_pars_begin>
#include <envmap_physical_pars_fragment>
#include <lights_phong_pars_fragment>
#include <shadowmap_pars_fragment>
#include <bumpmap_pars_fragment>
#include <normalmap_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec3 color = vec3(vUv * (0.2 - 2.0 * noise), 1.0);
vec3 finalColors = vec3(color.b * 1.5, color.r, color.r);
vec4 diffuseColor = vec4(cos(finalColors * noise * 3.0), 1.0);
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
vec3 totalEmissiveRadiance = emissive;
#include <logdepthbuf_fragment>
#include <map_fragment>
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <specularmap_fragment>
#include <normal_fragment_begin>
#include <normal_fragment_maps>
#include <emissivemap_fragment>
#include <lights_phong_fragment>
#include <lights_fragment_begin>
#include <lights_fragment_maps>
#include <lights_fragment_end>
#include <aomap_fragment>
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;
#include <envmap_fragment>
#include <premultiplied_alpha_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
gl_FragColor = vec4(outgoingLight, diffuseColor.a);
}
`;
const sphereVertShader = `
vec3 mod289(vec3 x)
{
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x)
{
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x)
{
return mod289(((x * 34.0) + 1.0) * x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
vec3 fade(vec3 t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
// Classic Perlin noise
float cnoise(vec3 P)
{
vec3 Pi0 = floor(P); // Integer part for indexing
vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
Pi0 = mod289(Pi0);
Pi1 = mod289(Pi1);
vec3 Pf0 = fract(P); // Fractional part for interpolation
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = Pi0.zzzz;
vec4 iz1 = Pi1.zzzz;
vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);
vec4 gx0 = ixy0 * (1.0 / 7.0);
vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
gx0 = fract(gx0);
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
vec4 sz0 = step(gz0, vec4(0.0));
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
vec4 gx1 = ixy1 * (1.0 / 7.0);
vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
gx1 = fract(gx1);
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
vec4 sz1 = step(gz1, vec4(0.0));
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
g000 *= norm0.x;
g010 *= norm0.y;
g100 *= norm0.z;
g110 *= norm0.w;
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
g001 *= norm1.x;
g011 *= norm1.y;
g101 *= norm1.z;
g111 *= norm1.w;
float n000 = dot(g000, Pf0);
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
float n111 = dot(g111, Pf1);
vec3 fade_xyz = fade(Pf0);
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
return 2.2 * n_xyz;
}
// Classic Perlin noise, periodic variant
float pnoise(vec3 P, vec3 rep)
{
vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period
vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period
Pi0 = mod289(Pi0);
Pi1 = mod289(Pi1);
vec3 Pf0 = fract(P); // Fractional part for interpolation
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = Pi0.zzzz;
vec4 iz1 = Pi1.zzzz;
vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);
vec4 gx0 = ixy0 * (1.0 / 7.0);
vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
gx0 = fract(gx0);
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
vec4 sz0 = step(gz0, vec4(0.0));
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
vec4 gx1 = ixy1 * (1.0 / 7.0);
vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
gx1 = fract(gx1);
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
vec4 sz1 = step(gz1, vec4(0.0));
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
g000 *= norm0.x;
g010 *= norm0.y;
g100 *= norm0.z;
g110 *= norm0.w;
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
g001 *= norm1.x;
g011 *= norm1.y;
g101 *= norm1.z;
g111 *= norm1.w;
float n000 = dot(g000, Pf0);
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
float n111 = dot(g111, Pf1);
vec3 fade_xyz = fade(Pf0);
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
return 2.2 * n_xyz;
}
float turbulence(vec3 p) {
float w = 100.0;
float t = -.5;
for (float f = 1.0 ; f <= 10.0 ; f++) {
float power = pow(2.0, f);
t += abs(pnoise(vec3(power * p), vec3(10.0, 10.0, 10.0)) / power);
}
return t;
}
// START
uniform float time;
varying vec2 vUv;
varying vec3 vNormal;
varying float noise;
#define PHONG
varying vec3 vViewPosition;
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
vNormal = normalize(transformedNormal);
#endif
#include <begin_vertex>
#include <displacementmap_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
vViewPosition = - mvPosition.xyz;
#include <worldpos_vertex>
#include <envmap_vertex>
#include <shadowmap_vertex>
#include <fog_vertex>
vUv = uv;
noise = turbulence(0.01 * position + normal + time * 0.8);
vec3 displacement = vec3((position.x) * noise, position.y * noise, position.z * noise);
gl_Position = projectionMatrix * modelViewMatrix * vec4((position + normal) + displacement, 1.0);
}
`;
// Init
const rand = Math.random();
const renderer = new THREE.WebGLRenderer({ canvas, powerPreference: 'high-performance' });
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 200);
const scene = new THREE.Scene();
const uniforms = THREE.UniformsUtils.merge([
THREE.UniformsLib['ambient'],
THREE.UniformsLib['lights'],
THREE.ShaderLib.phong.uniforms,
{ time: { type: 'f', value: 0 } },
]);
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: sphereVertShader,
fragmentShader: sphereFragShader,
lights: true,
});
const geometry = new THREE.SphereBufferGeometry(32, 128, 128);
const sphere = new THREE.Mesh(geometry, material);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.position.z = 52;
scene.add(sphere);
sphere.position.z = 0;
sphere.modifier = rand;
// Light
scene.background = new THREE.Color(0x111111);
const light = new THREE.DirectionalLight(0xffffff, 0.6);
light.position.z = 200;
light.position.x = 100;
light.position.y = 100;
scene.add(light);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
scene.add(ambientLight);
// Handle Resize
const handleResize = () => {
const canvasHeight = iosInnerHeight();
const windowWidth = window.innerWidth;
const fullHeight = canvasHeight + canvasHeight * 0.3;
canvas.style.height = fullHeight;
renderer.setSize(windowWidth, fullHeight);
camera.aspect = windowWidth / fullHeight;
camera.updateProjectionMatrix();
if (windowWidth <= 696) {
sphere.position.x = 14;
sphere.position.y = 10;
} else if (windowWidth <= 1024) {
sphere.position.x = 18;
sphere.position.y = 14;
} else {
sphere.position.x = 22;
sphere.position.y = 16;
}
};
window.addEventListener('resize', handleResize);
handleResize();
// Interaction
let ticking = false;
let animationFrame = null;
const onMouseMove = (event) => {
const animate = () => {
const position = {
x: event.clientX / window.innerWidth,
y: event.clientY / window.innerHeight,
};
const tween = new TWEEN.Tween(sphere.rotation)
.to({ x: position.y / 2, y: position.x / 2 }, 2000)
.easing(TWEEN.Easing.Quartic.Out)
.start();
ticking = false;
};
if (!ticking) {
animationFrame = requestAnimationFrame(animate);
ticking = true;
}
};
window.addEventListener('mousemove', onMouseMove);
// Animate
const animate = () => {
const animation = requestAnimationFrame(animate);
uniforms.time.value = 0.00005 * (Date.now() - start);
sphere.rotation.z += 0.001;
renderer.render(scene, camera);
TWEEN.update();
};
animate();
<script src="https://unpkg.com/es6-tween@5.5.10/bundled/Tween.min.js"></script>
<script src="https://unpkg.com/three@0.113.2/build/three.min.js"></script>
<script src="https://unpkg.com/ios-inner-height@1.1.1/dist/ios-inner-height.js"></script>
body {
margin: 0;
overflow: hidden;
background: #111;
}
.logo {
position: absolute;
left: 20px;
top: 20px;
}
.logo img {
height: 48px;
width: 48px;
}
.info {
position: absolute;
right: 18px;
bottom: 3px;
}
.info h2 {
color: #fff;
font-size: 22px;
font-weight: 400;
line-height: 1.4;
}
.info a {
display: inline;
text-decoration: none;
color: rgb(0, 38, 255);
background: linear-gradient(rgb(0, 229, 255), rgb(0, 229, 255)) no-repeat scroll 100% 100% / 0px 2px, rgba(0, 0, 0, 0) linear-gradient(rgba(0, 229, 255, 0.3), rgba(0, 229, 255, 0.3)) no-repeat scroll 0px 100% / 100% 2px;
transition: background-size 0.4s cubic-bezier(0.4, 0, 0.2, 1) 0s;
padding-bottom: 2px;
}
.info a:hover,
.infoa:focus {
background: linear-gradient(rgb(0, 229, 255), rgb(0, 229, 255)) no-repeat 0 100% / 100% 2px, linear-gradient(rgba(0, 229, 255, 0.3), rgba(0, 229, 255, 0.3)) no-repeat 0 100% / 100% 2px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto&amp;display=swap" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment