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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 19:15:59