您的当前位置:首页正文

WebGL实现soul星球效果

2024-11-01 来源:个人技术集锦

WebGL实现soul星球效果

最近在研究webGL,觉得soul app的星球挺有意思的,于是就实现了一下,中间涉及的细节和知识点挺多的,写篇博客分享一下

soul原版

WebGL实现的

主要技术要点

1.自由转动

因为要解决万向锁的问题,所以不能使用rotateXrotateYrotateZ来旋转,应当使用四元数THREE.Quaternion

2.背面小球变暗

这里通过内部放置了一个半透明的黑色小球来实现


js

代码解读

复制代码

// 创建半透明球体 const sphereGeometry = new THREE.SphereGeometry(4.85, 16, 16)

为了使小球从正面转动的背面的过程中可以平滑的变暗,这里还需要把半透明小球的边沿处理成高斯模糊,具体实现就是使用GLSL的插值函数smoothstep


js

代码解读

复制代码

fragmentShader: ` uniform vec3 color; uniform float opacity; varying vec3 vNormal; void main() { float alpha = opacity * smoothstep(0.5, 1.0, vNormal.z); gl_FragColor = vec4(color, alpha); }

但是需要注意的是需要关闭小球的深度测试,否则会遮挡小球


js

代码解读

复制代码

side: THREE.FrontSide, depthWrite: false,

3.使用THREE.Sprite创建小球标签
4.标签位置计算

js

代码解读

复制代码

for (let i = 0; i < numPoints; i++) { const y = 1 - (i / (numPoints - 1)) * 2 const radiusAtY = Math.sqrt(1 - y * y) const theta = (2 * Math.PI * i) / goldenRatio const x = Math.cos(theta) * radiusAtY const z = Math.sin(theta) * radiusAtY const smallBallMaterial = new THREE.MeshBasicMaterial({ color: getRandomBrightColor(), depthWrite: true, depthTest: true, side: THREE.FrontSide, }) const smallBall = new THREE.Mesh(smallBallGeometry, smallBallMaterial) smallBall.position.set(x * radius, y * radius, z * radius)

5.超出长度的标签采用贴图采样位移来实现跑马灯效果
6.滚动阻尼,鼠标转动球体之后速度能衰减到转动旋转的速率
7.自动旋转需要保持上一次滚动的方向
8.使用射线拾取来实现点击交互

完整代码

  </head>

  <body>

    <script type="module">

      import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.169.0/build/three.module.js'

      // 创建场景

      const scene = new THREE.Scene()

      // 创建相机

      const camera = new THREE.PerspectiveCamera(

        75,

        window.innerWidth / window.innerHeight,

        0.1,

        1000

      )

      camera.position.set(0, 0, 14)

      camera.lookAt(0, 0, 0)

      // 创建渲染器

      const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })

      renderer.setSize(window.innerWidth, window.innerHeight)

      renderer.setPixelRatio(window.devicePixelRatio)

      renderer.setClearColor(0x000000, 0)

      document.body.appendChild(renderer.domElement)

      // 创建半透明球体

      const sphereGeometry = new THREE.SphereGeometry(4.85, 16, 16)

      const sphereMaterial = new THREE.ShaderMaterial({

        uniforms: {

          color: { value: new THREE.Color(0x000000) },

          opacity: { value: 0.8 },

        },

        vertexShader: `

          varying vec3 vNormal;

          void main() {

              vNormal = normalize(normalMatrix * normal);

              gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);

          }

        `,

        fragmentShader: `

          uniform vec3 color;

          uniform float opacity;

          varying vec3 vNormal;

          void main() {

              float alpha = opacity * smoothstep(0.5, 1.0, vNormal.z);

              gl_FragColor = vec4(color, alpha);

          }

        `,

        transparent: true,

        side: THREE.FrontSide,

        depthWrite: false,

      })

      const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)

      scene.add(sphere)

      // 创建小球体和标签数组

      const smallBallGeometry = new THREE.SphereGeometry(0.15, 16, 16)

      const smallBalls = []

      const labelSprites = []

      const radius = 5

      const numPoints = 88

      const goldenRatio = (1 + Math.sqrt(5)) / 2

      const maxWidth = 160

      const textSpeed = 0.002

      // 创建射线投射器

      const raycaster = new THREE.Raycaster()

Top