You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Piano.io网站中WebGL地球的卡片显示与脉冲动画效果?

Piano.io地球动画核心实现方案

一、点位标记与卡片显示

实现思路

  1. 标记点批量渲染:用Three.js的InstancedMesh批量处理大量点位标记,兼顾性能;通过GLSL实现广告牌效果,让标记点始终面向相机。
  2. 卡片交互:借助Three.js射线拾取获取选中点位,在React中渲染对应DOM卡片,可通过CSS3DRenderer让卡片贴合3D空间位置,或直接计算屏幕坐标定位DOM元素。

最简GLSL标记点着色器

顶点着色器

attribute vec3 offset; // 每个标记点的3D位置偏移
attribute float size; // 标记点基础大小
uniform float uTime;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;

void main() {
    // 广告牌效果:让标记点始终朝向相机
    vec3 cameraPos = uViewMatrix[3].xyz;
    vec3 lookDir = normalize(cameraPos - offset);
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 right = normalize(cross(lookDir, up));
    up = normalize(cross(right, lookDir));

    vec3 pos = offset + (right * position.x + up * position.y) * size;
    gl_Position = uProjectionMatrix * uViewMatrix * vec4(pos, 1.0);
}

片元着色器

precision mediump float;
uniform vec3 uColor;

void main() {
    // 绘制圆形标记点
    float dist = distance(gl_PointCoord, vec2(0.5));
    if (dist > 0.5) discard;
    gl_FragColor = vec4(uColor, 1.0);
}

React+TypeScript 核心代码片段

import * as THREE from 'three';
import { useRef, useEffect } from 'react';

const EarthMarkers = () => {
    const sceneRef = useRef<THREE.Scene>(new THREE.Scene());
    const instancedMeshRef = useRef<THREE.InstancedMesh | null>(null);
    const cameraRef = useRef<THREE.PerspectiveCamera>(
        new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
    );

    useEffect(() => {
        // 模拟点位3D坐标数据
        const positions = [
            new THREE.Vector3(1, 0, 0),
            new THREE.Vector3(0, 1, 0),
            new THREE.Vector3(0, 0, 1)
        ];

        const geometry = new THREE.PlaneGeometry(1, 1);
        const material = new THREE.ShaderMaterial({
            vertexShader: /* 上述顶点着色器代码 */,
            fragmentShader: /* 上述片元着色器代码 */,
            uniforms: {
                uColor: { value: new THREE.Color(0xff4444) },
                uTime: { value: 0 }
            },
            transparent: true
        });

        // 创建实例化网格
        instancedMeshRef.current = new THREE.InstancedMesh(geometry, material, positions.length);
        sceneRef.current.add(instancedMeshRef.current);

        // 设置每个标记点的位置
        positions.forEach((pos, index) => {
            const matrix = new THREE.Matrix4().setPosition(pos);
            instancedMeshRef.current!.setMatrixAt(index, matrix);
        });

        // 射线拾取监听(简化版)
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();
        window.addEventListener('click', (e) => {
            mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
            raycaster.setFromCamera(mouse, cameraRef.current);
            const intersects = raycaster.intersectObject(instancedMeshRef.current!);
            if (intersects.length > 0) {
                const instanceIndex = intersects[0].instanceId;
                // 触发React状态更新,显示对应卡片
                console.log('选中点位索引:', instanceIndex);
            }
        });

        // 动画循环
        const animate = () => {
            requestAnimationFrame(animate);
            instancedMeshRef.current!.material.uniforms.uTime.value += 0.01;
        };
        animate();
    }, []);

    return <div ref={el => el && new THREE.WebGLRenderer({ antialias: true }).setSize(window.innerWidth, window.innerHeight)} />;
};

export default EarthMarkers;

二、脉冲式动画效果

实现思路

通过GLSL的时间变量,结合三角函数(sin/cos)计算脉冲强度,控制标记点的缩放、透明度或颜色变化,模拟类似地震波的扩散效果。

最简GLSL脉冲动画代码(集成到标记点着色器)

修改后的顶点着色器(添加脉冲缩放)

attribute vec3 offset;
attribute float size;
uniform float uTime;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;

void main() {
    // 脉冲缩放因子:基于时间和点位距离的波动
    float pulse = 1.0 + sin(uTime * 5.0 + length(offset)) * 0.3;
    float scaledSize = size * pulse;

    // 广告牌逻辑
    vec3 cameraPos = uViewMatrix[3].xyz;
    vec3 lookDir = normalize(cameraPos - offset);
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 right = normalize(cross(lookDir, up));
    up = normalize(cross(right, lookDir));

    vec3 pos = offset + (right * position.x + up * position.y) * scaledSize;
    gl_Position = uProjectionMatrix * uViewMatrix * vec4(pos, 1.0);
}

片元着色器(添加脉冲透明度)

precision mediump float;
uniform vec3 uColor;
uniform float uTime;
attribute vec3 offset;
varying float vPulse;

void main() {
    // 脉冲透明度计算
    vPulse = 0.5 + 0.5 * sin(uTime * 5.0 + length(offset));
    
    float dist = distance(gl_PointCoord, vec2(0.5));
    if (dist > 0.5) discard;
    gl_FragColor = vec4(uColor, vPulse);
}

React+TypeScript 动画驱动

在动画循环中持续更新时间变量即可:

const animate = () => {
    requestAnimationFrame(animate);
    const time = performance.now() * 0.001;
    instancedMeshRef.current!.material.uniforms.uTime.value = time;
};

内容的提问来源于stack exchange,提问作者husky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 00:23:19