preface

Hello, this is CSS magic – Alphardex.

In this paper, we will use three.js to draw a dazzling liquid crystal sphere. The following is the effect picture of the final implementation

Let’s get started!

The preparatory work

The author’s three.js template can be copied by clicking on fork in the lower right corner

To modularize shaders, glslify is needed

The following NPM packages will also be required: GLSL-Noise, GLSL-Constants

positive

Scenario building

Create a sphere

class LiquidCrystal extends Base {
  constructor(sel: string, debug: boolean) {
    super(sel, debug);
    this.clock = new THREE.Clock();
    this.cameraPosition = new THREE.Vector3(0.0.25);
    this.params = {
      timeScale: 0.1.iriBoost: 8}; }/ / initialization
  init() {
    this.createScene();
    this.createPerspectiveCamera();
    this.createRenderer();
    this.createLiquidCrystalMaterial();
    this.createSphere();
    this.trackMousePos();
    this.createOrbitControls();
    this.addListeners();
    this.setLoop();
  }
  // Create a liquid crystal material
  createLiquidCrystalMaterial() {
    const liquidCrystalMaterial = new THREE.ShaderMaterial({
      vertexShader: liquidCrystalVertexShader,
      fragmentShader: liquidCrystalFragmentShader,
      side: THREE.DoubleSide,
      uniforms: {
        uTime: {
          value: 0,},uResolution: {
          value: new THREE.Vector2(window.innerWidth, window.innerHeight),
        },
        uMouse: {
          value: new THREE.Vector2(0.0),},// Comment out the following lines and restore them when writing the chip shader
        // uIriMap: {
          // value: new ThinFilmFresnelMap(1000, 1.2, 3.2, 64),
        // },
        // uIriBoost: {
          // value: this.params.iriBoost,
        // },}});this.liquidCrystalMaterial = liquidCrystalMaterial;
  }
  // Create sphere
  createSphere() {
    const geometry = new THREE.SphereBufferGeometry(10.64.64);
    const material = this.liquidCrystalMaterial;
    this.createMesh({
      geometry,
      material,
    });
  }
  / / animation
  update() {
    const elapsedTime = this.clock.getElapsedTime();
    const time = elapsedTime * this.params.timeScale;
    const mousePos = this.mousePos;
    if (this.liquidCrystalMaterial) {
      this.liquidCrystalMaterial.uniforms.uTime.value = time;
      this.liquidCrystalMaterial.uniforms.uMouse.value = mousePos; }}}Copy the code

Vertex shader

Use Simplex Noise to create a distortion effect. It’s a bit more free. You can do whatever you want, as long as it looks good

There is a point to note: after the distortion of position to correct the normal line, otherwise it will display error, foreign forumhas a better solution, directly used

#pragma glslify:snoise=require(glsl-noise/simplex/3d)
#pragma glslify:PI=require(glsl-constants/PI)
#pragma glslify:getWorldNormal=require(.. /modules/getWorldNormal)

uniform float uTime;
uniform vec2 uMouse;

varying vec2 vUv;
varying vec3 vWorldNormal;

vec3 distort(vec3 p){
    vec3 pointDirection=normalize(p);
    vec3 mousePoint=vec3(uMouse,1.);
    vec3 mouseDirection=normalize(mousePoint);
    float mousePointAngle=dot(pointDirection,mouseDirection);
    
    float freq=1.5;
    float t=uTime*100.;
    
    float f=PI*freq;
    float fc=mousePointAngle*f;
    
    vec3 n11=pointDirection*1.5;
    vec3 n12=vec3(uTime)*4.;
    float dist=smoothstep(4..1.,mousePointAngle);
    float n1a=dist*2.;
    float noise1=snoise(n11+n12)*n1a;
    
    vec3 n21=pointDirection*1.5;
    vec3 n22=vec3(0..0.,uTime)*2.;
    vec3 n23=vec3(uMouse,0.) *2.;
    float n2a=8.;
    float noise2=snoise(n21+n22+n23)*n2a;
    
    float mouseN1=sin(fc+PI+t);
    float mouseN2=smoothstep(f,f*2.,fc+t);
    float mouseN3=smoothstep(f*2.,f,fc+t);
    float mouseNa=4.;
    float mouseNoise=mouseN1*mouseN2*mouseN3*mouseNa;
    
    float noise=noise1+noise2+mouseNoise;
    vec3 distortion=pointDirection*(noise+length(p));
    return distortion;
}

#pragma glslify:fixNormal=require(.. /modules/fixNormal,map=distort)

void main(){
    vec3 pos=position;
    pos=distort(pos);
    vec4 modelPosition=modelMatrix*vec4(pos,1.);
    vec4 viewPosition=viewMatrix*modelPosition;
    vec4 projectedPosition=projectionMatrix*viewPosition;
    gl_Position=projectedPosition;
    
    vec3 distortedNormal=fixNormal(position,pos,normal);
    
    vUv=uv;
    vWorldNormal=getWorldNormal(modelMatrix,distortedNormal).xyz;
}
Copy the code

Fixed the normal function fixnormal.glsl

#pragma glslify:orthogonal=require(./orthogonal)

vec3 fixNormal(vec3 position,vec3 distortedPosition,vec3 normal){
    vec3 tangent=orthogonal(normal);
    vec3 bitangent=normalize(cross(normal,tangent));
    float offset=1.;
    vec3 neighbour1=position+tangent*offset;
    vec3 neighbour2=position+bitangent*offset;
    vec3 displacedNeighbour1=map(neighbour1);
    vec3 displacedNeighbour2=map(neighbour2);
    vec3 displacedTangent=displacedNeighbour1-distortedPosition;
    vec3 displacedBitangent=displacedNeighbour2-distortedPosition;
    vec3 displacedNormal=normalize(cross(displacedTangent,displacedBitangent));
    return displacedNormal;
}

#pragma glslify:export(fixNormal)
Copy the code

Orthogonal functions orthogonal. GLSL

vec3 orthogonal(vec3 v){
    return normalize(abs(v.x)>abs(v.z)?vec3(-v.y,v.x,0.)
    :vec3(0.,-v.z,v.y));
}
#pragma glslify:export(orthogonal);
Copy the code

Get the world normal function getWorldNormal.glsl

vec4 getWorldNormal(mat4 modelMat,vec3 normal){
    vec4 worldNormal=normalize((modelMat*vec4(normal,0.)));
    return worldNormal;
}

#pragma glslify:export(getWorldNormal)
Copy the code

Chip shader

Use PBR to generate lighting, plus a colorful material

Dazzlingly textures directly drag thinFilmFresnelmap.js into the LiquidCrystal class.

#pragma glslify:snoise=require(glsl-noise/simplex/3d)
#pragma glslify:invert=require(.. /modules/invert)

uniform float uTime;
uniform vec2 uMouse;
uniform vec2 uResolution;
uniform sampler2D uIriMap;
uniform float uIriBoost;

varying vec2 vUv;
varying vec3 vWorldNormal;

void main(){
    vec2 newUv=vUv;
    
    // pbr
    float noise=snoise(vWorldNormal*5.) *3.;
    vec3 N=normalize(vWorldNormal+vec3(noise));
    vec3 V=normalize(cameraPosition);
    float NdotV=max(dot(N,V),0.);
    float colorStrength=smoothstep(0..8.,NdotV);
    vec3 color=invert(vec3(colorStrength));
    
    // iri
    vec3 airy=texture2D(uIriMap,vec2(NdotV*99..0.)).rgb;
    airy*=airy;
    vec3 specularLight=vWorldNormal*airy*uIriBoost;
    
    float mixStrength=smoothstep(3..6.,NdotV);
    vec3 finalColor=mix(specularLight,color,mixStrength);
    
    gl_FragColor=vec4(finalColor,0.);
}
Copy the code

Invert function invert. GLSL

float invert(float n){
    return 1.-n;
}

vec3 invert(vec3 n){
    return 1.-n;
}

#pragma glslify:export(invert)
Copy the code

The end result is as follows

The project address

Liquid Crystal