<main id="container"></main>
body, html{
	margin: 0;
	padding: 0;
}
const PI2 = Math.PI * 2.0;
const vertexShader = `
attribute vec3 position;
attribute vec2 uv;

uniform vec3 uEuler;

varying vec2 vUv;
varying vec3 normX;
varying vec3 normY;
varying vec3 normZ;

void main() {
	vUv = uv;
	gl_Position = vec4(position, 1.0);
	// Caculate Normal
	vec3 elrsn = sin(uEuler);
 	vec3 elrcs = cos(uEuler);
 	mat3 rotx = mat3(
		1.0, 0.0, 0.0,
      0.0, elrcs.x, elrsn.x,
      0.0, -elrsn.x, elrcs.x
 	);
	mat3 roty = mat3(
      elrcs.y, 0.0, -elrsn.y,
      0.0, 1.0, 0.0,
      elrsn.y, 0.0, elrcs.y
	);
	mat3 rotz = mat3(
      elrcs.z, elrsn.z, 0.0, 
      -elrsn.z, elrcs.z, 0.0,
      0.0, 0.0, 1.0
	);
	mat3 rotmat = rotx * roty * rotz;

	mat3 trrotm = mat3(
      rotmat[0][0], rotmat[1][0], rotmat[2][0],
      rotmat[0][1], rotmat[1][1], rotmat[2][1],
      rotmat[0][2], rotmat[1][2], rotmat[2][2]
 	);
 	normX = trrotm[0];
 	normY = trrotm[1];
 	normZ = trrotm[2];
}
`;
const fragmentShader = `
precision highp float;

uniform float uTime;

varying vec2 vUv;
varying vec3 normX;
varying vec3 normY;
varying vec3 normZ;

float diffuse = 0.8;
float specular = 0.5;
float rstop = 0.1;
float palpha = 1.;

// pos orign radius
float ellipse(vec2 p, vec2 o, vec2 r) { 
    vec2 lp = (p - o) / r;
    return length(lp) - 1.0;
}
void main() {
	vec2 st = vUv; // [0,1]
	// To 3D
	vec3 p = vec3(st - vec2(0.5, 0.5), 0.0) * 2.;// [-1,1]
	vec3 d = vec3(0.0, 0.0, -1.0);
	float nd = normZ.z; //dot(-normZ, d);
	if(abs(nd) < 0.0001) discard;

	float np = dot(normZ, p);
   vec3 tp = p + d * np / nd;//dot(-normZ, d) / dot(-normZ, p);
	// Back 2D 
 	vec2 coord = vec2(dot(normX, tp), dot(normY, tp));

	// angle = 15 degree
	const float flwrsn = 0.258819045102521;
   const float flwrcs = 0.965925826289068;
   mat2 flwrm = mat2(flwrcs, -flwrsn, flwrsn, flwrcs);
   // abs => double [0,1]
   vec2 flwrp = vec2(abs(coord.x), coord.y) * flwrm;
   float r;
   if(flwrp.x < 0.0) {
   	// middle part(double)
      r = ellipse(flwrp, vec2(0.065, 0.024) * 0.5, vec2(0.36, 0.96) * 0.5);
   } else {
		// out part(double)
      r = ellipse(flwrp, vec2(0.065, 0.024) * 0.5, vec2(0.58, 0.96) * 0.5);
    }
	 if(r > rstop) discard;
    // r = ellipse(flwrp, vec2(0.250,0.440), vec2(0.140,0.190));
    vec3 col = mix(vec3(1.0, 0.8, 0.75), vec3(1.0, 0.9, 0.87), r);
    
   float grady = mix(0.0, 1.0, pow(coord.y * 0.5 + 0.5, 0.35));
  	col *= vec3(1.0, grady, grady);
   col *= mix(0.8, 1.0, pow(abs(coord.x), 0.3));
  	col = col * diffuse + specular;
    
	float alpha = (0.5 - r / (rstop * 1.0));
   alpha = smoothstep(0.0, 1.0, alpha) * palpha;
    
	//gl_FragColor = vec4(vec3(alpha), 1.);
   gl_FragColor = vec4(col*0.5, alpha);
}
`;

class Shaping {
	constructor() {
		this.uniforms = {
			uTime: {
				type: 'f',
				value: 0
			},
			uEuler:{
				type: 'v3',
				value: new THREE.Vector3(
					Math.random() * Math.PI * 2.0,
					Math.random() * Math.PI * 2.0,
					Math.random() * Math.PI * 2.0
				)
			}
		};
		this.obj = this.createObj();
	}
	symmetryrand(){
		return (Math.random() * 2.0 - 1.0);
	}
	createObj() {
		let screenGeometry = new THREE.BufferGeometry();
		let posVertices = new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]);
		let uvVertices = new Float32Array([0, 0, 2, 0, 0, 2]);
		this.rotationArr = new THREE.Vector3(
			this.symmetryrand() * PI2 * 0.5,
            this.symmetryrand() * PI2 * 0.5,
            this.symmetryrand() * PI2 * 0.5
		)
		screenGeometry.addAttribute('position', new THREE.BufferAttribute(posVertices, 3));
		screenGeometry.addAttribute('uv', new THREE.BufferAttribute(uvVertices, 2));
		return new THREE.Mesh(
			screenGeometry,
			new THREE.RawShaderMaterial({
				uniforms: this.uniforms,
				vertexShader,
				fragmentShader,
			})
		);
	}
	repeatEuler(eulerVec, time) {
		let keyArr = ['x','y','z'];
		keyArr.forEach(key => {
			let element = eulerVec[`${key}`];
			element += this.rotationArr[`${key}`] * time;
			element = element % PI2;
			if (element < 0.0) {
				element += PI2;
			}
			eulerVec[`${key}`] = element;
		});
	}
	render(time) {
		let eulerVec = this.uniforms.uEuler.value;
		this.repeatEuler(eulerVec, time);
		this.uniforms.uTime.value += time;
	}
}

class MainScene {
	constructor(container, callback) {
		this.container = container;
		this.width = window.innerWidth;
		this.height = window.innerHeight;
		this.clock = new THREE.Clock();

		// Camera
		const camera = (this.camera = new THREE.PerspectiveCamera(
			45,
			document.innerWidth / window.innerHeight,
			1,
			1000
		));
		camera.position.set(0, 0, 400);

		// Renderer
		const renderer = (this.renderer = new THREE.WebGLRenderer({
			antialias: false
		}));
		renderer.domElement.id = 'canvasWebGL';
		renderer.setPixelRatio(window.devicePixelRatio);
		renderer.setSize(this.width, this.height);
		renderer.gammaFactor = 2.2;
		renderer.setClearColor(0x111111, 1.0);
		container.appendChild(renderer.domElement);
		// Scene
		this.initScene(callback);
		// Events
		this.initEvents();
	}
	initScene(callback) {
		const scene = (this.scene = new THREE.Scene());
		const shapingObject = (this.shapingObject = new Shaping());
		scene.add(shapingObject.obj);
		this.animete();
		typeof callback === 'function' && callback();
	}
	initEvents() {
		window.addEventListener('resize', this.onWindowResize.bind(this), false);
	}
	onWindowResize() {
		this.width = getEleWidth(this.container);
		this.height = getEleHeight(this.container);
		this.camera.aspect = this.width / this.height;
		this.camera.updateProjectionMatrix();
		this.renderer.setPixelRatio(window.devicePixelRatio);
		this.renderer.setSize(this.width, this.height);
	}
	animete() {
		const time = this.clock.getDelta();
		this.shapingObject.render(time);
		this.renderer.render(this.scene, this.camera);
		requestAnimationFrame(this.animete.bind(this));
	}
}

window.onload = ()=> {
	let containerEle = document.querySelector("#container");
	new MainScene(containerEle);
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js