WebGL拖动控制点绘制贝塞尔曲线——以三次贝塞尔曲线为例

为了实现该功能,这里将功能分成两部分。第一部分是控制点的拖动功能,第二部分是贝塞尔曲线的绘制功能。

控制点的拖动功能:鼠标按下选择点->鼠标移动修改点->鼠标松开释放点。选择点通过发生mousedown事件后遍历控制点数组,判断点击的位置是否和某个点的距离小于一定值,选择第一个满足条件的控制点,并向canvas绑定mousemove事件。修改点为鼠标移动时,将选择的点颜色的R分量改为1.0,即点变为红色,修改点的X和Y坐标为鼠标在画布中的坐标。释放点通过向canvas绑定mouseup事件,解绑mousemove即可。

贝塞尔曲线的绘制功能:直接套贝塞尔曲线的一般参数公式即可

实现的效果

初始状态                                                        选中点并拖动

改变四个点位置

js代码

    //获取上下文
    const canvas = document.getElementById("my_Canvas");
    gl = canvas.getContext("experimental-webgl");

    //编写Shader代码,并编译
    const vertCode = `
      attribute vec3 coordinates;
      attribute vec3 color;

      varying vec3 vColor;

      void main(void){
        gl_Position = vec4(coordinates,1.0);
        gl_PointSize = 10.0;
        vColor = color;
      }
      `;

    const vertShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertShader, vertCode);
    gl.compileShader(vertShader);

    const fragCode = `
      precision mediump float;
      varying vec3 vColor;
      void main(void){
      gl_FragColor = vec4(vColor, 1.0);
      }
      `;

    const fragShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragShader, fragCode);
    gl.compileShader(fragShader);

    //创建并设置着色器程序
    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertShader);
    gl.attachShader(shaderProgram, fragShader);
    gl.linkProgram(shaderProgram);
    gl.useProgram(shaderProgram);

    //定义控制点的顶点,索引
    var vertices = new Float32Array([
      -0.75, 0.0, 0.0,
      -0.25, 0.0, 0,
      0.25, 0.0, 0,
      0.75, 0.0, 0.0,
    ]);

    var indices = new Uint16Array([
      0, 1, 2, 3,
    ]);

    var colors = new Float32Array([
      0.0, 0.0, 0.0,
      0.0, 0.0, 0.0,
      0.0, 0.0, 0.0,
      0.0, 0.0, 0.0,
    ])

    var choose = null;

    //创建缓冲区并向缓冲区传入数据
    const vertex_buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    const coord = gl.getAttribLocation(shaderProgram, "coordinates");
    gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(coord);

    const index_Buffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_Buffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    const color_buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

    const color = gl.getAttribLocation(shaderProgram, "color");
    gl.vertexAttribPointer(color, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(color);



    //进行绘制初始图形
    gl.clearColor(0.5, 0.5, 0.5, 1.0);
    gl.enable(gl.DEPTH_TEST);
    drawPoint()
    drawBezier()

    //传入控制点数据并绘制控制点
    function drawPoint() {
      gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
      gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
      gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawElements(gl.POINTS, indices.length, gl.UNSIGNED_SHORT, 0);

    }

    //通过鼠标的坐标,计算出在[-1,1]区域的坐标,并判断选中了哪个控制点
    function computePosition(clientX, clientY) {
      const click_x = clientX;
      const click_y = clientY;
      const x = (click_x / canvas.width) * 2 - 1;
      const y = 1 - (click_y / canvas.height) * 2;

      for (let i = 0; i <= indices.length; i++) {
        let pointX = vertices[i * 3];
        let pointY = vertices[i * 3 + 1];
        let absX = Math.abs(pointX - x);
        let absY = Math.abs(pointY - y);
        if (absX <= 0.1 && absY <= 0.1) {
          return i;
        }
      }
      return null;
    }

    //鼠标移动时就进行一次刷新画面
    function move(event) {
      let x = (event.clientX / canvas.width) * 2 - 1;
      let y = 1 - (event.clientY / canvas.height) * 2;
      vertices[choose * 3] = x;
      vertices[choose * 3 + 1] = y;
      colors[choose * 3] = 1.0
      drawPoint();
      drawBezier()
    }

    //鼠标按下时选中点
    canvas.addEventListener('mousedown', function (e) {
      choose = computePosition(e.clientX, e.clientY);
      if (choose != null) {
        canvas.addEventListener('mousemove', move)
      }
    })

    //鼠标松开时释放选中的点
    canvas.addEventListener('mouseup', function (event) {
      canvas.removeEventListener('mousemove', move);
      colors[choose * 3] = 0.0;
      drawPoint();
      drawBezier()
    })

    //通过控制点,绘制贝塞尔曲线
    function drawBezier() {
      let b_points = []
      let b_indices = []
      let cnt = 0;
      for (let t = 0; t <= 1; t = t + 0.01) {
        let x = (1 - t) ** 3 * vertices[0] + 3 * (1 - t) ** 2 * t * vertices[3] + 3 * (1 - t) * t ** 2 * vertices[6] + t ** 3 * vertices[9];
        let y = (1 - t) ** 3 * vertices[1] + 3 * (1 - t) ** 2 * t * vertices[4] + 3 * (1 - t) * t ** 2 * vertices[7] + t ** 3 * vertices[10];
        b_points.push(x, y, 0);
        b_indices.push(cnt);
        cnt++;
      }
      gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(b_points), gl.STATIC_DRAW);

      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(b_indices), gl.STATIC_DRAW);

      gl.drawElements(gl.LINE_STRIP, b_indices.length, gl.UNSIGNED_SHORT, 0);
    }

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebGL 绘制带宽度的曲线可以通过使用顶点着色器和片段着色器来实现。具体步骤如下: 1. 定义曲线的顶点:可以使用一个数组来存储曲线的顶点坐标和宽度,例如对于一个二次贝塞尔曲线,可以按照步长(例如 0.01)来计算曲线上的点,并计算出每个点的法向量,然后将顶点坐标和法向量乘以宽度得到具体的顶点坐标。 2. 创建顶点着色器:顶点着色器的作用是将顶点的坐标和法向量传递给片段着色器,并计算出对应的屏幕坐标和法向量。在传递顶点数据时,需要使用 attribute 变量来声明变量类型和名称,例如: ``` attribute vec3 aVertexPosition; attribute vec3 aNormal; ``` 在顶点着色器中,需要使用 varying 变量来声明传递到片段着色器的变量类型和名称,例如: ``` varying vec3 vPosition; varying vec3 vNormal; ``` 然后在顶点着色器中,可以使用 gl_Position 变量来计算出屏幕坐标,例如: ``` void main() { vPosition = aVertexPosition; vNormal = aNormal; gl_Position = projectionMatrix * modelViewMatrix * vec4(aVertexPosition, 1.0); } ``` 3. 创建片段着色器:片段着色器的作用是根据顶点数据计算出每个像素的颜色。在传递顶点数据时,需要使用 varying 变量来声明变量类型和名称,例如: ``` varying vec3 vPosition; varying vec3 vNormal; ``` 然后在片段着色器中,可以使用这些变量来计算出每个像素的颜色,例如: ``` void main() { vec3 color = vec3(1.0, 0.0, 0.0); // 设置曲线的颜色 float width = 0.1; // 设置曲线的宽度 vec3 normal = normalize(vNormal); vec2 offset = vec2(normal.y, -normal.x) * width; // 计算法向量的偏移量 vec2 position = (gl_FragCoord.xy + offset) / resolution; // 计算屏幕坐标 gl_FragColor = vec4(color, 1.0); // 设置像素的颜色 } ``` 4. 绘制曲线:在绘制曲线前,需要启用深度测试和混合功能,可以使用以下代码: ``` gl.enable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); ``` 然后使用 gl.drawArrays 函数来绘制曲线,例如: ``` gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount); ``` 其中,vertexCount 表示顶点数量。 需要注意的是,绘制带宽度的曲线需要使用深度测试和混合来实现遮挡关系和半透明效果,可以使用 gl.depthFunc 和 gl.blendFunc 函数来设置相应的参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值