2023年8月

WEBGL中使用shader实现旋转的圆环

1.实现圆环绘制

// fragmentshader
void main() {
  float radius = 0.5;
  vec2 st = gl_PointCoord.xy;
  float dist = distance(st,vec2(0.5,0.5));

  float alpha = smoothstep(radius-0.12,radius,dist) - smoothstep(radius-0.1,radius,dist);
  gl_FragColor = vec4( 1.0,0.0,0.0, alpha);
}

2.让圆环存在一个1/4的缺口(具体大小可以自己调整)

// fragmentshader
#define PI 3.14159265359
void main() {
  float radius = 0.5;
  vec2 st = gl_PointCoord.xy;
  float dist = distance(st,vec2(0.5,0.5));

  float alpha = smoothstep(radius-0.12,radius,dist) - smoothstep(radius-0.1,radius,dist);
  float theta = mod( atan(st.y - 0.5,st.x - 0.5) + PI , PI*2.0);
  float a = step( PI/2.0, theta );
  gl_FragColor = vec4( 1.0,0.0,0.0, alpha*a);
}

完整html的demo:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <script type="x-shader/x-vertex" id="vertexshader">
attribute float aSize;
attribute float aStartAngle;

varying float vStartAngle;

void main() {
  vStartAngle = aStartAngle;
  gl_PointSize = aSize;

  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
    </script>

    <script type="x-shader/x-fragment" id="fragmentshader">
uniform float uTheta;
varying float vStartAngle;
void main() {
  float radius = 0.5;
  vec2 st = gl_PointCoord.xy;
  float dist = distance(st,vec2(0.5,0.5));

  float alpha = smoothstep(radius-0.15,radius,dist) - smoothstep(radius-0.05,radius,dist);
  float theta = mod( atan(st.y - 0.5,st.x - 0.5) + PI + vStartAngle + uTheta , PI*2.0);
  float a = step( PI/2.0, theta );
  gl_FragColor = vec4( 1.0,0.0,0.0, alpha*a);
}
    </script>
    <script type="module">
      import * as THREE from "https://unpkg.com/three@0.155.0/build/three.module.js";
      // import * as THREE from './three.module.js'

      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );

      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      const geometry = new THREE.BufferGeometry();
      const positions = [];
      const sizes = [];
      const startAngles = [];
      for (let i = 0; i < 10; i++) {
        // position
        for (let s = 0; s < 3; s++) {
          positions.push(Math.random() * 5 - 2.5);
        }

        // size
        sizes.push(20 + Math.random() * 30);

        // startAngle 让每个圆环不要看起来是同步的
        startAngles.push(Math.random() * Math.PI * 2);
      }

      geometry.setAttribute(
        "position",
        new THREE.Float32BufferAttribute(positions, 3)
      );
      geometry.setAttribute(
        "aSize",
        new THREE.Float32BufferAttribute(sizes, 1)
      );
      geometry.setAttribute(
        "aStartAngle",
        new THREE.Float32BufferAttribute(startAngles, 1)
      );

      const material = new THREE.ShaderMaterial({
        vertexShader: document.getElementById("vertexshader").textContent,
        fragmentShader: document.getElementById("fragmentshader").textContent,
        uniforms: {
          uTheta: { value: (Date.now() * 0.01) % (Math.PI * 2) },
        },
        defines: {
          PI: Math.PI,
        },
        transparent: true,
      });

      const points = new THREE.Points(geometry, material);

      scene.add(points);

      camera.position.z = 5;

      function animate() {
        requestAnimationFrame(animate);

        material.uniforms.uTheta.value = (Date.now() * 0.01) % (Math.PI * 2);

        renderer.render(scene, camera);
      }

      animate();
    </script>
  </body>
</html>

https://pic1.zhimg.com/v2-4d47e7de914f256f2035482338aa4244_b.webp