Нарисуйте цветной ЖК-шар с помощью three.js

внешний интерфейс WebGL three.js
Нарисуйте цветной ЖК-шар с помощью three.js

предисловие

Всем привет, это волшебник CSS - alphardex.

В этой статье мы будем использовать three.js для рисования цветного жидкокристаллического шара Ниже приведен окончательный рендеринг.

ball.gif

Давайте начнем!

Готов к работе

авторышаблон three.js: Нажмите вилку в правом нижнем углу, чтобы скопировать копию.

Чтобы модульизовать шейдер, нам нужно использоватьglslify

Вам также необходимо установить следующие пакеты npm:glsl-noise,glsl-constants

положительный

Построение сцены

Просто создайте сферу

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,
    };
  }
  // 初始化
  init() {
    this.createScene();
    this.createPerspectiveCamera();
    this.createRenderer();
    this.createLiquidCrystalMaterial();
    this.createSphere();
    this.trackMousePos();
    this.createOrbitControls();
    this.addListeners();
    this.setLoop();
  }
  // 创建液晶材质
  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),
        },
        // 下面几行先注释掉,等写片元着色器时再恢复
        // uIriMap: {
          // value: new ThinFilmFresnelMap(1000, 1.2, 3.2, 64),
        // },
        // uIriBoost: {
          // value: this.params.iriBoost,
        // },
      },
    });
    this.liquidCrystalMaterial = liquidCrystalMaterial;
  }
  // 创建球体
  createSphere() {
    const geometry = new THREE.SphereBufferGeometry(10, 64, 64);
    const material = this.liquidCrystalMaterial;
    this.createMesh({
      geometry,
      material,
    });
  }
  // 动画
  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;
    }
  }
}

1

вершинный шейдер

Используйте симплексный шум для достижения эффекта искажения, здесь более бесплатно, вы можете искажать как хотите, если это выглядит хорошо

Одно предостережение: скрученное положениеpositionисправить нормали позжеnormal, иначе выдаст ошибку, на зарубежных форумах уже есть лучшее решение, и оно будет использовано напрямую

#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;
}

Фикс нормальной функции 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)

Ортогональная функция 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);

Получить функцию мировой нормали getWorldNormal.glsl

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

#pragma glslify:export(getWorldNormal)

2

фрагментный шейдер

Используйте pbr для генерации освещения, плюскрасочный материал

Красочный материал будет непосредственноThinFilmFresnelMap.jsВытащите его локально и импортируйте в класс LiquidCrystal (то есть просто удалите комментарий выше)

#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.);
}

Функция инвертирования invert.glsl

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

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

#pragma glslify:export(invert)

Окончательный эффект выглядит следующим образом

ball.gif

адрес проекта

Liquid Crystal