四、通过鼠标控制绘制

101 阅读4分钟
let webglDiv = document.getElementById('practice');
let webgl = webglDiv.getContext('webgl');

/*着色器*/
// 顶点着色器
const VERTEX_SHADER_SOURCE = `
        //  attribute 只能用于传递定点数据
       attribute vec4 aPosition ;
        void main(){
          // 要绘制的点的坐标
          gl_Position = aPosition;
          // 点的大小
          gl_PointSize = 20.0;
        }
  `;
// 片元着色器
const FRAGMENT_SHADER_SOURCE = `
        void main(){
          //这里的vec4代表是 vec4(1.0,0.0,0.0,1.0) r,g,b,a
          gl_FragColor =  vec4(1.0 , 0.0 , 0.0 , 1.0)  ;
        }
  `;
const program =  initShader(webgl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);

// 获取变量
const aPosition = webgl.getAttribLocation(program,'aPosition');
const points = [];
webglDiv.onclick  = function (event){
  // x轴的一半
  let divisorX = webglDiv.offsetWidth/2;
  // y轴的一半
  let divisorY = webglDiv.offsetWidth/2;
  let x  =  (event.offsetX - divisorX)/divisorX;
  let y = (divisorX - event.offsetY)/divisorY;


    //绘制多个点
  // points.push({
  //   x,y
  // })
  // for (let i = 0; i < points.length; i++) {
  //   const {x, y} = points[i];
  //   webgl.vertexAttrib2f(aPosition, x, y)
  //   webgl.drawArrays(webgl.POINTS, 0, 1)
  // }

  // 变量赋值
  webgl.vertexAttrib4f(aPosition,x, y,0.0,1.0);
  // 执行绘制
  // drawArrays(绘制的图形,从哪个点开始,使用几个顶点)
  webgl.drawArrays(webgl.POINTS, 0, 1);
}

1、坐标转换

因为webgl的坐标系x轴和y轴只有1,0,-1,原点为0,通过点击函数获取位置信息得到的是px并且是canvas坐标系,所以要将获取的点击位置信息转换成webgl坐标

  • webgl x轴为 从左到右为 -1,0,1,y轴从上到下为 1,0 ,-1
  • 假设canvas高和宽为500px, 对应的canvas x坐标为 0,250,500,想要转换成webgl坐标需要将canvas x轴减去canvas宽的一半,变成 -250,0,250,再除于250,变回得到-1,0,1
  • 对应的canvas y坐标为 0,250,500,想要转换成webgl坐标需要将canvas宽的一半减去canvas y轴,变成 250,0,-250,再除于250,便会得到1,0,-1

2、webgl同步绘图

代码中使用points数组存储之前获取的点,并在下次绘图时反复使用webgl.vertexAttrib2f(aPosition, x, y)、webgl.drawArrays(),为什么不是覆盖而是添加,是因为同步绘图的现象。

原因是 webgl 底层内置颜色缓冲区。它在电脑中会占用一块内存,在我们使用 webgl 绘图的时候,是在颜色缓冲区中画出来,但是图像暂时还未渲染出来。只有 webgl 自己知道,如果我们想要将图像绘制的时候,便执行 webgl.drawArrays(),那就照着缓冲区的图像去画。

  • 颜色缓冲区中存储的图像,只在当前线程有效,我们先在js 主线程中绘图,主线程结束后,会再去执行信息队列里的异步线程
  • 在执行异步线程时,颜色缓冲区就会被webgl系统重置,之前的图像也就没了

或者可以理解为每次点击事件中的循环里调用了drawArrays。这里并没有涉及到新的帧绘制,因为所有的drawArrays调用都在同一个事件处理函数中,也就是在同一帧中。所以,你在循环中的每次drawArrays调用都在绘制一个新的点,而不会清除之前的点。

当你在循环内部调用drawArrays时,每次迭代都会执行一次绘制调用,因此每次都会在画布上绘制一个点。这是因为你在循环中不断地将新的顶点位置通过vertexAttrib2f设置给aPosition,然后调用drawArrays来绘制这个点,所以每次迭代都会在画布上添加一个新点。

但是,当你在不同的点击事件中调用drawArrays时,每次点击都会触发一个新的帧绘制。在这种情况下,如果你没有采取措施来保存之前的点,那么每次新的帧绘制都会清除画布,只显示最新绘制的点。

所以,你在数组中的drawArrays调用没有清除之前的点,是因为所有的绘制操作都在同一帧中。而在不同的点击事件中调用drawArrays,每次点击都会触发一个新的帧绘制,所以需要采取措施来保存之前的点。

WebGL默认情况下在每次绘制新帧时不会保留上一帧的内容。这意味着除非你采取措施来保存这些点,否则每次调用drawArrays时,画布都会被清空,只显示最新绘制的点。为了在每次点击时都绘制一个新点而不清除之前的点,你需要将所有点的数据保存起来,并且在每次点击时重新绘制整个点集。这通常通过以下步骤实现:

  1. 维护一个数组来存储所有点的位置。
  2. 每次点击时,将新点的位置添加到数组中。
  3. 在每次点击后,清除画布并重新绘制所有点。

流程图

image.png