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

如何用Three.js实现仅显示3D圆锥内部的平面相交区域?

实现圆锥与平面的相交区域可视化(React + Three.js)

问题描述

现有React结合Three.js的代码可渲染圆锥与半透明平面的动画,但当前平面是完整显示的,需求是仅展示圆锥内部的平面相交区域。

简洁实现方案

使用Three.js的自定义着色器材质(ShaderMaterial),在片元着色器中通过圆锥的几何方程判断平面上的点是否处于圆锥内部,非内部区域直接丢弃片元,仅保留相交部分。

修改后的完整代码

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

export const App = () => {
  const mountRef = useRef(null);
  const [planeAngle, setPlaneAngle] = useState(Math.PI / 4); // Initial angle in radians

  useEffect(() => {
    const width = 800;
    const height = 600;

    // Scene setup
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(width, height);
    mountRef.current.appendChild(renderer.domElement);

    // Cone parameters
    const length = 2;
    const coneHeight = length * Math.SQRT2;
    const coneRadius = length / Math.SQRT2;
    // Cone mesh (wireframe)
    const coneGeometry = new THREE.ConeGeometry(coneRadius, coneHeight, 32);
    const coneMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
    const cone = new THREE.Mesh(coneGeometry, coneMaterial);
    cone.position.y = 0;
    scene.add(cone);

    // Plane: 使用自定义着色器材质,仅保留圆锥内部区域
    const planeSize = length * Math.SQRT2;
    const planeGeometry = new THREE.PlaneGeometry(planeSize, planeSize);
    
    // 自定义着色器代码
    const planeVertexShader = `
      varying vec3 worldPosition;

      void main() {
        worldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
        gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
      }
    `;

    const planeFragmentShader = `
      uniform vec3 conePosition;
      uniform float coneHeight;
      uniform float coneRadius;
      varying vec3 worldPosition;

      void main() {
        // 圆锥的几何方程:顶点在(conePosition.x, conePosition.y + coneHeight/2, conePosition.z),底部在(conePosition.y - coneHeight/2)
        vec3 delta = worldPosition - conePosition;
        float yOffset = coneHeight / 2.0 - delta.y;
        float radialDistanceSq = delta.x * delta.x + delta.z * delta.z;
        float maxRadialDistanceSq = (coneRadius / coneHeight * yOffset) * (coneRadius / coneHeight * yOffset);

        // 判断是否在圆锥内部,不在则丢弃片元
        if (radialDistanceSq > maxRadialDistanceSq || yOffset < 0.0 || yOffset > coneHeight) {
          discard;
        }

        gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
      }
    `;

    const planeMaterial = new THREE.ShaderMaterial({
      vertexShader: planeVertexShader,
      fragmentShader: planeFragmentShader,
      side: THREE.DoubleSide,
      transparent: true,
      uniforms: {
        conePosition: { value: cone.position },
        coneHeight: { value: coneHeight },
        coneRadius: { value: coneRadius }
      }
    });

    const plane = new THREE.Mesh(planeGeometry, planeMaterial);
    scene.add(plane);

    // Position camera
    camera.position.set(3, 3, 5);
    camera.lookAt(0, 0, 0);

    // Animation
    const animate = () => {
      requestAnimationFrame(animate);

      // Update plane rotation
      plane.rotation.set(planeAngle, 0, 0);
      plane.position.set(0, 0, 0);

      renderer.render(scene, camera);
    };

    animate();

    // Cleanup
    return () => {
      mountRef.current.removeChild(renderer.domElement);
    };
  }, [planeAngle]);

  const handleAngleChange = (event) => {
    setPlaneAngle(parseFloat(event.target.value));
  };

  return (
    <div>
      <div ref={mountRef}></div>
      <div style={{ position: 'absolute', bottom: '10px', left: '10px', color: 'white' }}>
        Plane Angle: 
        <input 
          type="range"
          min="0"
          max={Math.PI / 2}
          step="0.01"
          value={planeAngle}
          onChange={handleAngleChange}
          style={{ width: '200px' }}
        />
        {(planeAngle * 180 / Math.PI).toFixed(2)}°
      </div>
    </div>
  );
};

关键代码解释

  1. 顶点着色器:将平面顶点的世界坐标传递给片元着色器,用于后续的圆锥内部判断。
  2. 片元着色器:
    • 通过uniform变量获取圆锥的位置、高度、半径参数。
    • 计算当前片元在世界空间中相对于圆锥的位置,代入圆锥的几何方程:
      • 圆锥的径向距离平方需小于等于对应高度处的最大半径平方
      • 同时限制片元处于圆锥的高度范围内(避免显示圆锥外部的平面部分)
    • 不满足条件的片元通过discard指令直接丢弃,仅保留圆锥内部的平面区域。
  3. 材质配置:开启transparent和DoubleSide,保证半透明效果和平面两面都能正确显示相交区域。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:34:54