zoukankan      html  css  js  c++  java
  • 【转】three.js 实现露珠滴落动画

    演示地址:

    https://codepen.io/alphardex/pen/BaQYXvy

    原文:

    https://mp.weixin.qq.com/s/bS8IXkdDyiO2I3o0UNRUBQ

    html

    <div class="relative w-screen h-screen">
      <div class="ray-marching w-full h-full bg-black"></div>
    </div>
    

    css

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      margin: 0;
      background: hsl(240, 56%, 98%);
      overflow: hidden;
    }
    

    js

    import * as THREE from "https://cdn.skypack.dev/three@0.124.0";
    import ky from "https://cdn.skypack.dev/kyouka@1.2.2";
    import { OrbitControls } from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/controls/OrbitControls";
    import { GLTFLoader } from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/loaders/GLTFLoader";
    import { FBXLoader } from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/loaders/FBXLoader";
    import { EffectComposer } from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/postprocessing/EffectComposer";
    import Stats from "https://cdn.skypack.dev/three@0.124.0/examples/jsm/libs/stats.module";
    import * as dat from "https://cdn.skypack.dev/dat.gui@0.7.7";
    
    const calcAspect = (el: HTMLElement) => el.clientWidth / el.clientHeight;
    
    const getNormalizedMousePos = (e: MouseEvent | Touch) => {
      return {
        x: (e.clientX / window.innerWidth) * 2 - 1,
        y: -(e.clientY / window.innerHeight) * 2 + 1
      };
    };
    
    const matcapTextureUrl = "https://i.loli.net/2021/02/27/7zhBySIYxEqUFW3.png";
    
    const rayMarchingVertexShader = `
    varying vec2 vUv;
    
    void main(){
        vec4 modelPosition=modelMatrix*vec4(position,1.);
        vec4 viewPosition=viewMatrix*modelPosition;
        vec4 projectedPosition=projectionMatrix*viewPosition;
        gl_Position=projectedPosition;
        
        vUv=uv;
    }
    `;
    
    const rayMarchingFragmentShader = `
    uniform float uTime;
    uniform vec2 uMouse;
    uniform vec2 uResolution;
    uniform float uVelocityBox;
    uniform float uProgress;
    uniform float uAngle;
    uniform float uDistance;
    uniform float uVelocitySphere;
    uniform sampler2D uTexture;
    
    varying vec2 vUv;
    
    const float EPSILON=.0001;
    const float PI=3.14159265359;
    
    // https://gist.github.com/yiwenl/3f804e80d0930e34a0b33359259b556c
    mat4 rotationMatrix(vec3 axis,float angle){
        axis=normalize(axis);
        float s=sin(angle);
        float c=cos(angle);
        float oc=1.-c;
        
        return mat4(oc*axis.x*axis.x+c,oc*axis.x*axis.y-axis.z*s,oc*axis.z*axis.x+axis.y*s,0.,
            oc*axis.x*axis.y+axis.z*s,oc*axis.y*axis.y+c,oc*axis.y*axis.z-axis.x*s,0.,
            oc*axis.z*axis.x-axis.y*s,oc*axis.y*axis.z+axis.x*s,oc*axis.z*axis.z+c,0.,
        0.,0.,0.,1.);
    }
    
    vec3 rotate(vec3 v,vec3 axis,float angle){
        mat4 m=rotationMatrix(axis,angle);
        return(m*vec4(v,1.)).xyz;
    }
    
    vec3 background(vec2 uv){
        float dist=length(uv-vec2(.5));
        vec3 bg=mix(vec3(.3),vec3(.0),dist);
        return bg;
    }
    
    // https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
    float sdSphere(vec3 p,float r)
    {
        return length(p)-r;
    }
    
    float sdBox(vec3 p,vec3 b)
    {
        vec3 q=abs(p)-b;
        return length(max(q,0.))+min(max(q.x,max(q.y,q.z)),0.);
    }
    
    // https://www.iquilezles.org/www/articles/smin/smin.htm
    float smin(float a,float b,float k)
    {
        float h=clamp(.5+.5*(b-a)/k,0.,1.);
        return mix(b,a,h)-k*h*(1.-h);
    }
    
    float movingSphere(vec3 p,float shape){
        float rad=uAngle*PI;
        vec3 pos=vec3(cos(rad),sin(rad),0.)*uDistance;
        vec3 displacement=pos*fract(uTime*uVelocitySphere);
        float gotoCenter=sdSphere(p-displacement,.1);
        return smin(shape,gotoCenter,.3);
    }
    
    float sdf(vec3 p){
        vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
        float box=sdBox(p1,vec3(.3));
        float sphere=sdSphere(p,.3);
        float sBox=smin(box,sphere,.3);
        float mixedBox=mix(sBox,box,uProgress);
        mixedBox=movingSphere(p,mixedBox);
        float aspect=uResolution.x/uResolution.y;
        vec2 mousePos=uMouse;
        mousePos.x*=aspect;
        float mouseSphere=sdSphere(p-vec3(mousePos,0.),.15);
        return smin(mixedBox,mouseSphere,.1);
    }
    
    // http://jamie-wong.com/2016/07/15/ray-marching-signed-distance-functions/
    // https://gist.github.com/sephirot47/f942b8c252eb7d1b7311
    float rayMarch(vec3 eye,vec3 ray,float end,int maxIter){
        float depth=0.;
        for(int i=0;i<maxIter;i++){
            vec3 pos=eye+depth*ray;
            float dist=sdf(pos);
            depth+=dist;
            if(dist<EPSILON||dist>=end){
                break;
            }
        }
        return depth;
    }
    
    vec2 centerUv(vec2 uv){
        uv=2.*uv-1.;
        float aspect=uResolution.x/uResolution.y;
        uv.x*=aspect;
        return uv;
    }
    
    // https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm
    vec3 calcNormal(in vec3 p)
    {
        const float eps=.0001;
        const vec2 h=vec2(eps,0);
        return normalize(vec3(sdf(p+h.xyy)-sdf(p-h.xyy),
        sdf(p+h.yxy)-sdf(p-h.yxy),
        sdf(p+h.yyx)-sdf(p-h.yyx)));
    }
    
    // https://github.com/hughsk/matcap/blob/master/matcap.glsl
    vec2 matcap(vec3 eye,vec3 normal){
        vec3 reflected=reflect(eye,normal);
        float m=2.8284271247461903*sqrt(reflected.z+1.);
        return reflected.xy/m+.5;
    }
    
    // https://www.shadertoy.com/view/4scSW4
    float fresnel(float bias,float scale,float power,vec3 I,vec3 N)
    {
        return bias+scale*pow(1.+dot(I,N),power);
    }
    
    void main(){
        vec2 cUv=centerUv(vUv);
        vec3 eye=vec3(0.,0.,2.5);
        vec3 ray=normalize(vec3(cUv,-eye.z));
        vec3 bg=background(vUv);
        vec3 color=bg;
        float end=5.;
        int maxIter=256;
        float depth=rayMarch(eye,ray,end,maxIter);
        if(depth<end){
            vec3 pos=eye+depth*ray;
            vec3 normal=calcNormal(pos);
            vec2 matcapUv=matcap(ray,normal);
            color=texture2D(uTexture,matcapUv).rgb;
            float F=fresnel(0.,.4,3.2,ray,normal);
            color=mix(color,bg,F);
        }
        gl_FragColor=vec4(color,1.);
    }
    `;
    
    class Base {
      debug: boolean;
      container: HTMLElement | null;
      scene!: THREE.Scene;
      camera!: THREE.PerspectiveCamera | THREE.OrthographicCamera;
      rendererParams!: Record<string, any>;
      perspectiveCameraParams!: Record<string, any>;
      orthographicCameraParams!: Record<string, any>;
      cameraPosition!: THREE.Vector3;
      lookAtPosition!: THREE.Vector3;
      renderer!: THREE.WebGLRenderer;
      controls!: OrbitControls;
      mousePos!: THREE.Vector2;
      raycaster!: THREE.Raycaster;
      sound!: THREE.Audio;
      stats!: Stats;
      composer!: EffectComposer;
      shaderMaterial!: THREE.ShaderMaterial;
      mouseSpeed!: number;
      constructor(sel: string, debug = false) {
        this.debug = debug;
        this.container = document.querySelector(sel);
        this.perspectiveCameraParams = {
          fov: 75,
          near: 0.1,
          far: 100
        };
        this.orthographicCameraParams = {
          zoom: 2,
          near: -100,
          far: 1000
        };
        this.cameraPosition = new THREE.Vector3(0, 3, 10);
        this.lookAtPosition = new THREE.Vector3(0, 0, 0);
        this.rendererParams = {
          outputEncoding: THREE.LinearEncoding,
          config: {
            alpha: true,
            antialias: true
          }
        };
        this.mousePos = new THREE.Vector2(0, 0);
        this.mouseSpeed = 0;
      }
      // 初始化
      init() {
        this.createScene();
        this.createPerspectiveCamera();
        this.createRenderer();
        this.createMesh({});
        this.createLight();
        this.createOrbitControls();
        this.addListeners();
        this.setLoop();
      }
      // 创建场景
      createScene() {
        const scene = new THREE.Scene();
        if (this.debug) {
          scene.add(new THREE.AxesHelper());
          const stats = Stats();
          this.container!.appendChild(stats.dom);
          this.stats = stats;
        }
        this.scene = scene;
      }
      // 创建透视相机
      createPerspectiveCamera() {
        const { perspectiveCameraParams, cameraPosition, lookAtPosition } = this;
        const { fov, near, far } = perspectiveCameraParams;
        const aspect = calcAspect(this.container!);
        const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
        camera.position.copy(cameraPosition);
        camera.lookAt(lookAtPosition);
        this.camera = camera;
      }
      // 创建正交相机
      createOrthographicCamera() {
        const { orthographicCameraParams, cameraPosition, lookAtPosition } = this;
        const { left, right, top, bottom, near, far } = orthographicCameraParams;
        const camera = new THREE.OrthographicCamera(
          left,
          right,
          top,
          bottom,
          near,
          far
        );
        camera.position.copy(cameraPosition);
        camera.lookAt(lookAtPosition);
        this.camera = camera;
      }
      // 更新正交相机参数
      updateOrthographicCameraParams() {
        const { container } = this;
        const { zoom, near, far } = this.orthographicCameraParams;
        const aspect = calcAspect(container!);
        this.orthographicCameraParams = {
          left: -zoom * aspect,
          right: zoom * aspect,
          top: zoom,
          bottom: -zoom,
          near,
          far,
          zoom
        };
      }
      // 创建渲染
      createRenderer(useWebGL1 = false) {
        const { rendererParams } = this;
        const { outputEncoding, config } = rendererParams;
        const renderer = !useWebGL1
          ? new THREE.WebGLRenderer(config)
          : new THREE.WebGL1Renderer(config);
        renderer.setSize(this.container!.clientWidth, this.container!.clientHeight);
        renderer.outputEncoding = outputEncoding;
        this.resizeRendererToDisplaySize();
        this.container?.appendChild(renderer.domElement);
        this.renderer = renderer;
        this.renderer.setClearColor(0x000000, 0);
      }
      // 允许投影
      enableShadow() {
        this.renderer.shadowMap.enabled = true;
      }
      // 调整渲染器尺寸
      resizeRendererToDisplaySize() {
        const { renderer } = this;
        if (!renderer) {
          return;
        }
        const canvas = renderer.domElement;
        const pixelRatio = window.devicePixelRatio;
        const { clientWidth, clientHeight } = canvas;
        const width = (clientWidth * pixelRatio) | 0;
        const height = (clientHeight * pixelRatio) | 0;
        const isResizeNeeded = canvas.width !== width || canvas.height !== height;
        if (isResizeNeeded) {
          renderer.setSize(width, height, false);
        }
        return isResizeNeeded;
      }
      // 创建网格
      createMesh(
        meshObject: MeshObject,
        container: THREE.Scene | THREE.Mesh = this.scene
      ) {
        const {
          geometry = new THREE.BoxGeometry(1, 1, 1),
          material = new THREE.MeshStandardMaterial({
            color: new THREE.Color("#d9dfc8")
          }),
          position = new THREE.Vector3(0, 0, 0)
        } = meshObject;
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.copy(position);
        container.add(mesh);
        return mesh;
      }
      // 创建光源
      createLight() {
        const dirLight = new THREE.DirectionalLight(
          new THREE.Color("#ffffff"),
          0.5
        );
        dirLight.position.set(0, 50, 0);
        this.scene.add(dirLight);
        const ambiLight = new THREE.AmbientLight(new THREE.Color("#ffffff"), 0.4);
        this.scene.add(ambiLight);
      }
      // 创建轨道控制
      createOrbitControls() {
        const controls = new OrbitControls(this.camera, this.renderer.domElement);
        const { lookAtPosition } = this;
        controls.target.copy(lookAtPosition);
        controls.update();
        this.controls = controls;
      }
      // 监听事件
      addListeners() {
        this.onResize();
      }
      // 监听画面缩放
      onResize() {
        window.addEventListener("resize", (e) => {
          if (this.shaderMaterial) {
            this.shaderMaterial.uniforms.uResolution.value.x = window.innerWidth;
            this.shaderMaterial.uniforms.uResolution.value.y = window.innerHeight;
            this.renderer.setSize(window.innerWidth, window.innerHeight);
          } else {
            if (this.camera instanceof THREE.PerspectiveCamera) {
              const aspect = calcAspect(this.container!);
              const camera = this.camera as THREE.PerspectiveCamera;
              camera.aspect = aspect;
              camera.updateProjectionMatrix();
            } else if (this.camera instanceof THREE.OrthographicCamera) {
              this.updateOrthographicCameraParams();
              const camera = this.camera as THREE.OrthographicCamera;
              const {
                left,
                right,
                top,
                bottom,
                near,
                far
              } = this.orthographicCameraParams;
              camera.left = left;
              camera.right = right;
              camera.top = top;
              camera.bottom = bottom;
              camera.near = near;
              camera.far = far;
              camera.updateProjectionMatrix();
            }
            this.renderer.setSize(
              this.container!.clientWidth,
              this.container!.clientHeight
            );
          }
        });
      }
      // 动画
      update() {
        console.log("animation");
      }
      // 渲染
      setLoop() {
        this.renderer.setAnimationLoop(() => {
          this.resizeRendererToDisplaySize();
          this.update();
          if (this.controls) {
            this.controls.update();
          }
          if (this.stats) {
            this.stats.update();
          }
          if (this.composer) {
            this.composer.render();
          } else {
            this.renderer.render(this.scene, this.camera);
          }
        });
      }
      // 创建文本
      createText(
        text = "",
        config: THREE.TextGeometryParameters,
        material: THREE.Material = new THREE.MeshStandardMaterial({
          color: "#ffffff"
        })
      ) {
        const geo = new THREE.TextGeometry(text, config);
        const mesh = new THREE.Mesh(geo, material);
        return mesh;
      }
      // 创建音效源
      createAudioSource() {
        const listener = new THREE.AudioListener();
        this.camera.add(listener);
        const sound = new THREE.Audio(listener);
        this.sound = sound;
      }
      // 加载音效
      loadAudio(url: string): Promise<AudioBuffer> {
        const loader = new THREE.AudioLoader();
        return new Promise((resolve) => {
          loader.load(url, (buffer) => {
            this.sound.setBuffer(buffer);
            resolve(buffer);
          });
        });
      }
      // 加载模型
      loadModel(url: string): Promise<THREE.Object3D> {
        const loader = new GLTFLoader();
        return new Promise((resolve, reject) => {
          loader.load(
            url,
            (gltf) => {
              const model = gltf.scene;
              console.log(model);
              resolve(model);
            },
            undefined,
            (err) => {
              console.log(err);
              reject();
            }
          );
        });
      }
      // 加载FBX模型
      loadFBXModel(url: string): Promise<THREE.Object3D> {
        const loader = new FBXLoader();
        return new Promise((resolve, reject) => {
          loader.load(
            url,
            (obj) => {
              resolve(obj);
            },
            undefined,
            (err) => {
              console.log(err);
              reject();
            }
          );
        });
      }
      // 加载字体
      loadFont(url: string): Promise<THREE.Font> {
        const loader = new THREE.FontLoader();
        return new Promise((resolve) => {
          loader.load(url, (font) => {
            resolve(font);
          });
        });
      }
      // 创建点选模型
      createRaycaster() {
        this.raycaster = new THREE.Raycaster();
        this.trackMousePos();
      }
      // 追踪鼠标位置
      trackMousePos() {
        window.addEventListener("mousemove", (e) => {
          this.setMousePos(e);
        });
        window.addEventListener(
          "touchstart",
          (e: TouchEvent) => {
            this.setMousePos(e.touches[0]);
          },
          { passive: false }
        );
        window.addEventListener("touchmove", (e: TouchEvent) => {
          this.setMousePos(e.touches[0]);
        });
      }
      // 设置鼠标位置
      setMousePos(e: MouseEvent | Touch) {
        const { x, y } = getNormalizedMousePos(e);
        this.mousePos.x = x;
        this.mousePos.y = y;
      }
      // 获取点击物
      getInterSects(): THREE.Intersection[] {
        this.raycaster.setFromCamera(this.mousePos, this.camera);
        const intersects = this.raycaster.intersectObjects(
          this.scene.children,
          true
        );
        return intersects;
      }
      // 选中点击物时
      onChooseIntersect(target: THREE.Object3D) {
        const intersects = this.getInterSects();
        const intersect = intersects[0];
        if (!intersect || !intersect.face) {
          return null;
        }
        const { object } = intersect;
        return target === object ? intersect : null;
      }
      // 获取跟屏幕同像素的fov角度
      getScreenFov() {
        return ky.rad2deg(
          2 * Math.atan(window.innerHeight / 2 / this.cameraPosition.z)
        );
      }
      // 获取重心坐标系
      getBaryCoord(bufferGeometry: THREE.BufferGeometry) {
        // https://gist.github.com/mattdesl/e399418558b2b52b58f5edeafea3c16c
        const length = bufferGeometry.attributes.position.array.length;
        const count = length / 3;
        const bary = [];
        for (let i = 0; i < count; i++) {
          bary.push(0, 0, 1, 0, 1, 0, 1, 0, 0);
        }
        const aCenter = new Float32Array(bary);
        bufferGeometry.setAttribute(
          "aCenter",
          new THREE.BufferAttribute(aCenter, 3)
        );
      }
      // 追踪鼠标速度
      trackMouseSpeed() {
        // https://stackoverflow.com/questions/6417036/track-mouse-speed-with-js
        let lastMouseX = -1;
        let lastMouseY = -1;
        let mouseSpeed = 0;
        window.addEventListener("mousemove", (e) => {
          const mousex = e.pageX;
          const mousey = e.pageY;
          if (lastMouseX > -1) {
            mouseSpeed = Math.max(
              Math.abs(mousex - lastMouseX),
              Math.abs(mousey - lastMouseY)
            );
            this.mouseSpeed = mouseSpeed / 100;
          }
          lastMouseX = mousex;
          lastMouseY = mousey;
        });
        document.addEventListener("mouseleave", () => {
          this.mouseSpeed = 0;
        });
      }
      // 使用PCFSoft阴影
      usePCFSoftShadowMap() {
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      }
      // 使用VSM阴影
      useVSMShadowMap() {
        this.renderer.shadowMap.type = THREE.VSMShadowMap;
      }
      // 将相机的方向设为z轴
      setCameraUpZ() {
        this.camera.up.set(0, 0, 1);
      }
    }
    
    class RayMarching extends Base {
      clock!: THREE.Clock;
      rayMarchingMaterial!: THREE.ShaderMaterial;
      constructor(sel: string, debug: boolean) {
        super(sel, debug);
        this.clock = new THREE.Clock();
        this.cameraPosition = new THREE.Vector3(0, 0, 0);
      }
      // 初始化
      init() {
        this.createScene();
        this.createOrthographicCamera();
        this.createRenderer();
        this.createRayMarchingMaterial();
        this.createPlane();
        this.createLight();
        this.trackMousePos();
        this.addListeners();
        // this.createDebugPanel();
        this.setLoop();
      }
      // 创建光线追踪材质
      createRayMarchingMaterial() {
        const loader = new THREE.TextureLoader();
        const texture = loader.load(matcapTextureUrl);
        const rayMarchingMaterial = new THREE.ShaderMaterial({
          vertexShader: rayMarchingVertexShader,
          fragmentShader: rayMarchingFragmentShader,
          side: THREE.DoubleSide,
          uniforms: {
            uTime: {
              value: 0
            },
            uMouse: {
              value: new THREE.Vector2(0, 0)
            },
            uResolution: {
              value: new THREE.Vector2(window.innerWidth, window.innerHeight)
            },
            uTexture: {
              value: texture
            },
            uProgress: {
              value: 1
            },
            uVelocityBox: {
              value: 0.25
            },
            uVelocitySphere: {
              value: 0.5
            },
            uAngle: {
              value: 1.5
            },
            uDistance: {
              value: 1.2
            }
          }
        });
        this.rayMarchingMaterial = rayMarchingMaterial;
        this.shaderMaterial = rayMarchingMaterial;
      }
      // 创建平面
      createPlane() {
        const geometry = new THREE.PlaneBufferGeometry(2, 2, 100, 100);
        const material = this.rayMarchingMaterial;
        this.createMesh({
          geometry,
          material
        });
      }
      // 动画
      update() {
        const elapsedTime = this.clock.getElapsedTime();
        const mousePos = this.mousePos;
        if (this.rayMarchingMaterial) {
          this.rayMarchingMaterial.uniforms.uTime.value = elapsedTime;
          this.rayMarchingMaterial.uniforms.uMouse.value = mousePos;
        }
      }
      // 创建调试面板
      createDebugPanel() {
        const { rayMarchingMaterial } = this;
        const gui = new dat.GUI({  300 });
        gui
          .add(rayMarchingMaterial.uniforms.uProgress, "value")
          .min(0)
          .max(1)
          .step(0.01)
          .name("progress");
        gui
          .add(rayMarchingMaterial.uniforms.uVelocityBox, "value")
          .min(0)
          .max(1)
          .step(0.01)
          .name("velocityBox");
        gui
          .add(rayMarchingMaterial.uniforms.uVelocitySphere, "value")
          .min(0)
          .max(1)
          .step(0.01)
          .name("velocitySphere");
        gui
          .add(rayMarchingMaterial.uniforms.uAngle, "value")
          .min(0)
          .max(2)
          .step(0.01)
          .name("angle");
        gui
          .add(rayMarchingMaterial.uniforms.uDistance, "value")
          .min(0)
          .max(2)
          .step(0.01)
          .name("distance");
      }
    }
    
    const start = () => {
      const rayMarching = new RayMarching(".ray-marching", false);
      rayMarching.init();
    };
    
    start();
    

      

  • 相关阅读:
    mysql 备份脚本
    (四)监控cpu
    (三)修改图形乱码问题
    (二)centos7安装zabbix agentd端
    (一)Centos7安装zabbix3.4 server端
    (五)条件测试
    (四)mysql数据类型
    (三)mysql数据库基本操作
    (二)mysql忘记root密码
    (五)cobbler自定义系统安装
  • 原文地址:https://www.cnblogs.com/guxingzhe/p/14704610.html
Copyright © 2011-2022 走看看