废话不说,代码如下// 绘制带数字的圆点 import { Scene, BufferGeometry, Points, PerspectiveCamera, Vector3, WebGLRenderer, CanvasTexture, ShaderMaterial, Color, BufferAttribute, } from "three"; let renderer, scene, camera; const width = window.innerWidth; const height = window.innerHeight; function init() { const container = document.getElementById("app"); scene = new Scene(); camera = new PerspectiveCamera(45, width / height, 1, 10000); camera.position.set(0, 0, 18); camera.lookAt(scene.position); camera.updateMatrix(); const points = createPoint(); scene.add(points); // const helper = new AxesHelper(5); // scene.add(helper); renderer = new WebGLRenderer(); renderer.setSize(width, height); container.appendChild(renderer.domElement); } function createPoint() { const geometry = new BufferGeometry().setFromPoints([ new Vector3(-3, 3, 0), new Vector3(0, 3, 0), new Vector3(3, 3, 0), new Vector3(-3, 0, 0), new Vector3(0, 0, 0), new Vector3(3, 0, 0), new Vector3(-3, -3, 0), new Vector3(0, -3, 0), new Vector3(3, -3, 0), new Vector3(0, 6, 0), ]); const numbers = [1, 2, 3, 4, 56789, 6, 7, 768, 19, 0]; geometry.setAttribute( "a_number", new BufferAttribute(new Float32Array(numbers), 1) ); const material = new createMaterial(); const points = new Points(geometry, material); return points; } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } init(); animate(); // testAppendCanvas(); function createCanvasSpirit() { const SIZE = 64; const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = SIZE * 10; canvas.height = SIZE; ctx.font = SIZE + "px serif"; ctx.fillStyle = "#000"; // 居中 ctx.textAlign = "center"; ctx.textBaseline = "middle"; for (let i = 0; i < 10; i++) { ctx.fillText(i, SIZE * i + SIZE / 2, SIZE / 2); } return canvas; } function testAppendCanvas() { const canvas = createCanvasSpirit(); canvas.style.border = "1px solid #000"; canvas.style.marginTop = "10px"; canvas.style.marginLeft = "10px"; const container = document.getElementById("app"); container.appendChild(canvas); } function createCanvasTexture() { const canvas = createCanvasSpirit(); const texture = new CanvasTexture(canvas); return texture; } function createMaterial() { const vertexShader = ` attribute float a_number; uniform float u_size; varying float v_number; void main(){ v_number = a_number; gl_PointSize = u_size; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `; const fragmentShader = ` uniform vec3 u_color; uniform vec3 u_front_color; uniform sampler2D u_number_spirit; varying float v_number; float numDigits(float num) { float digits = 1.0; while (num >= 10.0) { num = floor( num / 10.0 ); digits += 1.0; } return floor(digits); } float getNumByDigits(float num, float digits) { float digit = floor( mod(num / pow(10.0, digits ), 10.0) ); return digit; } void main() { float dist = distance( gl_PointCoord, vec2(0.5,0.5) ); float discard_opacity = 1.0 - smoothstep( 0.48, 0.5, dist ); if( discard_opacity == 0.0 ) discard; vec2 uv = vec2( gl_PointCoord.x,1.0- gl_PointCoord.y); float index = mod(v_number, pow(10.0,MAX_DIGITS) ); float num_length = numDigits(index); // 数字长度 uv = clamp((uv - 0.5) * (0.8 + num_length * 0.2) + 0.5,0.0,1.0); float num_step = 1.0 / num_length; // 每位数字占的宽度 float current_digits = floor( uv.x / num_step ); // 当前uv渲染的是第几个数字 float value = getNumByDigits(index, num_length - 1.0 - current_digits); // 需要渲染的对应数字 float x = mod(uv.x,num_step)/num_step * 0.1; if(num_length > 1.0){ x = x/2.0 + value * 0.1 + 0.025; }else{ x += value * 0.1; } float y = uv.y; float opacity = texture2D( u_number_spirit, vec2(x,y)).a; opacity = step(0.1,opacity) * opacity; vec3 color = mix(u_color, u_front_color, opacity); vec4 diffuseColor = vec4( color, 1.0 * discard_opacity ); gl_FragColor = diffuseColor; } `; const texture = createCanvasTexture(); const size = 64 * window.devicePixelRatio; const material = new ShaderMaterial({ uniforms: { u_size: { value: size }, // 点的大小 u_color: { value: new Color(0x00ff00) }, // 背景填充色 u_front_color: { value: new Color(0xff0000) }, // 文字颜色 u_number_spirit: { value: texture }, // 数字纹理 }, defines: { MAX_DIGITS: "3.0", // 最大支持3位数 }, vertexShader, fragmentShader, transparent: true, }); return material; }