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

如何基于二维控制点用Three.js与顶点着色器实现三次贝塞尔曲线?

问题分析与修正方案

核心错误点

  • 几何体顶点错误:原代码直接用四个控制点创建Line,这只会画出连接控制点的直线,而非贝塞尔曲线。需要生成一系列从t=0到t=1的采样点作为几何体顶点。
  • t参数误用:着色器中用position.x作为贝塞尔公式的t值,但t必须是0~1的均匀变量,而非顶点的x坐标。
  • 渲染器未设置尺寸:没有给WebGLRenderer设置画布大小,导致渲染结果显示异常。

修正后的完整代码

<script src="https://threejs.org/build/three.js"></script> 
<script type="module">
const rendSize = new THREE.Vector2(window.innerWidth, window.innerHeight);

var scene, camera, renderer, shaderMat;

function main() {
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(rendSize.x, rendSize.y);
    document.body.appendChild(renderer.domElement);

    scene = new THREE.Scene();

    // 调整正交相机参数适配窗口
    const aspect = rendSize.x / rendSize.y;
    camera = new THREE.OrthographicCamera(-10 * aspect, 10 * aspect, 10, -10, -10.0, 10.0);
  
    const vShader = `
        vec3 toBezier(float t, vec3 P0, vec3 P1, vec3 P2, vec3 P3) {
            float t2 = t * t;
            float t3 = t2 * t;
            float one_minus_t = 1.0 - t;
            float one_minus_t2 = one_minus_t * one_minus_t;
            float one_minus_t3 = one_minus_t2 * one_minus_t;
            return P0 * one_minus_t3 + P1 * 3.0 * t * one_minus_t2 + P2 * 3.0 * t2 * one_minus_t + P3 * t3;
        }

        uniform vec3 p0;
        uniform vec3 p1;
        uniform vec3 p2;
        uniform vec3 p3;
        attribute float t; // 新增t属性,传递0~1的采样值
    
        void main(void) {
            vec3 pos = toBezier(t, p0, p1, p2, p3);
            gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
        }
  `;

    generateCurve(vShader);

    renderer.clear();
    renderer.render(scene, camera);
}

function generateCurve(vShader) {
    const ptosControle = [
        new THREE.Vector3(0.0, 0.0, 0.0),
        new THREE.Vector3(-0.5, Math.sin(Math.PI/2.0)/2.0, 0.0),
        new THREE.Vector3(0.5, Math.sin(3.0*Math.PI/2.0)/2.0, 0.0),
        new THREE.Vector3(1.0, 1.0, 0.0)
    ];

    // 生成贝塞尔曲线的采样点数据:创建100个t值从0到1的顶点
    const sampleCount = 100;
    const positions = [];
    const tValues = [];
    for(let i=0; i<=sampleCount; i++){
        const t = i / sampleCount;
        // 临时位置可以随便填,实际会被着色器替换,这里用原点占位
        positions.push(0, 0, 0);
        tValues.push(t);
    }

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
    geometry.setAttribute('t', new THREE.Float32BufferAttribute(tValues, 1)); // 添加t属性

    shaderMat = new THREE.ShaderMaterial({
        uniforms: {
            p0: { value: ptosControle[0]},
            p1: { value: ptosControle[1]},
            p2: { value: ptosControle[2]},
            p3: { value: ptosControle[3]},
        },
        vertexShader: vShader,
    });

    const bezierCurve = new THREE.Line(geometry, shaderMat);
    scene.add(bezierCurve);

    // 可选:添加控制点连线,方便对比
    const controlLine = new THREE.Line(
        new THREE.BufferGeometry().setFromPoints(ptosControle),
        new THREE.LineBasicMaterial({color: 0xff0000})
    );
    scene.add(controlLine);
}

main();

// 窗口大小变化时更新相机和渲染器
window.addEventListener('resize', () => {
    rendSize.set(window.innerWidth, window.innerHeight);
    renderer.setSize(rendSize.x, rendSize.y);
    const aspect = rendSize.x / rendSize.y;
    camera.left = -10 * aspect;
    camera.right = 10 * aspect;
    camera.updateProjectionMatrix();
    renderer.render(scene, camera);
});
</script>

修改说明

  1. 生成采样顶点:创建100个均匀分布的t值(0到1),作为几何体的顶点属性传递给着色器,确保曲线足够平滑。
  2. 新增t属性:在着色器中添加attribute float t,用这个标准化的t值代入贝塞尔公式,替代原代码中错误使用的position.x。
  3. 优化贝塞尔公式:原公式中P3的系数为t2*t,虽然计算结果一致,但明确写成t3更符合三次贝塞尔公式的标准形式。
  4. 设置渲染器尺寸:给renderer设置窗口大小,并添加窗口 resize 事件监听,保证画面适配窗口变化。
  5. 可选控制点连线:添加红色的控制点连线,便于直观对比贝塞尔曲线与控制点的位置关系。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:12:50