最近在研究webGL,觉得soul app的星球挺有意思的,于是就实现了一下,中间涉及的细节和知识点挺多的,写篇博客分享一下
因为要解决万向锁的问题,所以不能使用rotateX
、rotateY
、rotateZ
来旋转,应当使用四元数THREE.Quaternion
这里通过内部放置了一个半透明的黑色小球来实现
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,
THREE.Sprite
创建小球标签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)
贴图采样位移
来实现跑马灯效果射线拾取
来实现点击交互</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()