使用ShaderMaterial的Three.js Sprite无法响应点击等交互事件
问题描述
在Three.js与@react-three/fiber环境中编写了Sprite组件,使用SpriteMaterial时onClick、onPointerOver、onPointerOut等交互事件正常工作,但替换为ShaderMaterial后所有交互事件失效,相关代码如下:
使用SpriteMaterial的正常版本
export function Point({ position = [0, 0, 0] }: { position?: [number, number, number] }) { const spriteRef = useRef<THREE.Sprite>(null) const [hovered, setHovered] = useState(false) useCursor(hovered) useFrame(({ camera, clock }) => { spriteRef.current?.lookAt(camera.position); const scale = 0.1 + (1 + Math.sin(clock.getElapsedTime() * 5)) * 0.01; spriteRef.current!.scale.set(scale, scale, scale) }) const onClick = () => alert('You clicked me!') return ( <sprite ref={spriteRef} onClick={onClick} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)} position={position}> <spriteMaterial attach="material" color={'hotpink'} /> </sprite> ) }
替换为ShaderMaterial后失效的版本
export function Point({ position = [0, 0, 0] }: { position?: [number, number, number] }) { const spriteRef = useRef<THREE.Sprite>(null) const [hovered, setHovered] = useState(false) useCursor(hovered) useFrame(({ camera, clock }) => { spriteRef.current?.lookAt(camera.position); const scale = 0.1 + (1 + Math.sin(clock.getElapsedTime() * 5)) * 0.01; spriteRef.current!.scale.set(scale, scale, scale) }) const onClick = () => alert('You clicked me!') return ( <sprite ref={spriteRef} onClick={onClick} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)} position={position}> {/* <spriteMaterial attach="material" color={'hotpink'} /> */} <shaderMaterial attach="material" transparent={true} depthWrite={false} uniforms={{ color: { value: new THREE.Color("hotpink") }, }} vertexShader={` varying vec2 vUv; void main() { vUv = uv; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * mvPosition; gl_PointSize = 300.0 / -mvPosition.z; } `} fragmentShader={` uniform vec3 color; varying vec2 vUv; void main() { float dist = length(vUv - vec2(0.5)); float alpha = 1.0 - smoothstep(0.3, 0.5, dist); gl_FragColor = vec4(color, alpha); } `} /> </sprite> ) }
替换后交互事件全部失效,需要修改哪些内容恢复交互功能?
解决方案
问题根源在于Three.js射线检测的机制,以及ShaderMaterial的配置和顶点着色器的错误使用,需要做以下两处修改:
1. 开启材质的alphaTest属性
Three.js默认不会对ShaderMaterial的透明区域做射线检测过滤,添加alphaTest={0.5}后,系统会忽略透明度低于0.5的片元,仅让可见的实心区域响应交互:
<shaderMaterial attach="material" transparent={true} depthWrite={false} alphaTest={0.5} // 添加此行 uniforms={{ color: { value: new THREE.Color("hotpink") }, }} // 其余代码保持不变 />
2. 删除顶点着色器中的gl_PointSize代码
gl_PointSize是给Points(点云)对象使用的属性,而Sprite的尺寸是通过scale控制的,误用该属性会导致Sprite的实际可交互区域与视觉显示区域不匹配,直接删除该行即可:
vertexShader={` varying vec2 vUv; void main() { vUv = uv; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * mvPosition; // 删除 gl_PointSize = 300.0 / -mvPosition.z; 此行 } `}
修复后的完整组件代码
export function Point({ position = [0, 0, 0] }: { position?: [number, number, number] }) { const spriteRef = useRef<THREE.Sprite>(null) const [hovered, setHovered] = useState(false) useCursor(hovered) useFrame(({ camera, clock }) => { spriteRef.current?.lookAt(camera.position); const scale = 0.1 + (1 + Math.sin(clock.getElapsedTime() * 5)) * 0.01; spriteRef.current!.scale.set(scale, scale, scale) }) const onClick = () => alert('You clicked me!') return ( <sprite ref={spriteRef} onClick={onClick} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)} position={position}> <shaderMaterial attach="material" transparent={true} depthWrite={false} alphaTest={0.5} uniforms={{ color: { value: new THREE.Color("hotpink") }, }} vertexShader={` varying vec2 vUv; void main() { vUv = uv; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * mvPosition; } `} fragmentShader={` uniform vec3 color; varying vec2 vUv; void main() { float dist = length(vUv - vec2(0.5)); float alpha = 1.0 - smoothstep(0.3, 0.5, dist); gl_FragColor = vec4(color, alpha); } `} /> </sprite> ) }
内容的提问来源于stack exchange,提问作者Kishieel
相关产品推荐
相关产品推荐

