• WebGL笔记:使用鼠标绘制多个线条应用及绘制动感线性星座


    使用鼠标绘制多个线条

    • 多个线条,肯定不是一笔画过的,而是多次画的线条
    • 既然是多线,那就需要有个容器来管理它们

    1 )建立容器对象

    建立一个 lineBox 对象,作为承载多边形的容器

    // lineBox.js
    export default class lineBox {
      constructor(gl) {
        this.gl = gl
        this.children = []
      }
      add(obj) {
        obj.gl = this.gl
        this.children.push(obj)
      }
      updateVertices(params) {
        this.children.forEach(ele => {
          ele.updateVertices(params)
        })
      }
      draw() {
        this.children.forEach(ele => {
          ele.init()
          ele.draw()
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 属性

      • gl webgl上下文对象
      • children 子级
    • 方法

      • add() 添加子对象
      • updateVertices() 更新子对象的顶点数据
      • draw() 遍历子对象绘图,每个子对象对应一个buffer 对象,所以在子对象绘图之前要先初始化

    2 )场景应用

    场景:鼠标点击画布,绘制多边形路径,鼠标右击,取消绘制,鼠标再次点击,绘制新的多边形

    import LineBox from './lineBox'
    import Poly from './poly'
    
    // 容器
    const lb = new LineBox(gl)
    // 当前正在绘制的多边形
    let poly = null
    
    // 取消右击提示
    canvas.oncontextmenu = function() {
        return false
    }
    
    // 鼠标点击事件
    canvas.addEventListener("mousedown", (event) => {
        if(event.button === 2) {
            popVertice()
        } else {
            const { x, y } = getMousePosInWebgl(event, canvas)
            poly ? poly.addVertice(x,y) : crtPoly(x,y)
        }
        render()
    })
    
    // 鼠标移动
    canvas.addEventListener("mousemove", (event) => {
        if (poly) {
            const { x, y } = getMousePosInWebgl(event, canvas)
            poly.setVertice(poly.count - 1, x, y)
            render()
        }
    })
    
    // 删除最后一个顶点
    function popVertice() {
        poly.popVertice()
        poly = null
    }
    
    // 创建多边形
    function crtPoly(x,y) {
        poly = new Poly({
            vertices:[x,y,x,y],
            types:['POINTS','LINE_STRIP']
        })
        lb.add(poly)
    }
    
    // 渲染方法
    function render() {
        gl.clear(gl.COLOR_BUFFER_BIT)
        lb.draw()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    3 )场景应用

    • 画一个星座
      • 鼠标第1次点击画布时
      • 创建多边形
      • 绘制2个点
      • 鼠标移动时
      • 当前多边形最后一个顶点随鼠标移动
      • 鼠标接下来点击画布时
      • 新建一个点
      • 鼠标右击时
      • 删除最后一个随鼠标移动的点
    • 顶点要有闪烁动画
    • 建立顶点的时候,如果鼠标点击了其它顶点,就不要再显示新的顶点

    3.1 )建立顶点着色器

    <script id="vertexShader" type="x-shader/x-vertex">
          attribute vec4 a_Attr;
          varying float v_Alpha;
          void main() {
              gl_Position = vec4(a_Attr.x, a_Attr.y, 0.0, 1.0);
              gl_PointSize = a_Attr.z;
              v_Alpha = a_Attr.w;
          }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • a_Attr() 是一个4维向量,其参数结构为(x, y, z, w)
      • x,y代表位置
      • z代表顶点尺寸
      • w代表顶点透明度,w会通过 varying 变量 v_Alpha 传递给片元

    3.2 )建立片元着色器

    <script id="fragmentShader" type="x-shader/x-fragment">
          precision mediump float;
          varying float v_Alpha;
          void main() {
              float dist = distance(gl_PointCoord, vec2(0.5,0.5));
              if(dist < 0.5) {
                gl_FragColor = vec4(0.87, 0.91, 1.0, v_Alpha);
              } else {
                discard;
              }
          }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 通过v_Alpha接收透明度,然后设置片元的颜色

    3.3 )建立夜空对象,用于承载多边形

    const lb = new lineBox(gl)
    
    • 1

    3.4 )建立合成对象,用于对顶点数据做补间运算

    const compose = new Compose();
    
    • 1

    3.5 )声明两个变量,用于表示当前正在绘制的多边形和鼠标划上的点

    // 当前正在绘制的多边形
    let poly = null
    // 鼠标划上的点
    let point = null
    
    • 1
    • 2
    • 3
    • 4

    3.6 )取消右击提示

    // 取消右击提示
    canvas.oncontextmenu = function() {
      return false;
    }
    
    • 1
    • 2
    • 3
    • 4

    3.7 )鼠标按下事件

    // 鼠标按下事件
    canvas.addEventListener("mousedown", (event) => {
      if(event.button === 2) {
        // 右击删除顶点
        poly && popVertice()
      } else {
        const {x,y} = getMousePosInWebgl(event, canvas)
        if(poly) {
          // 连续添加顶点
          addVertice(x,y)
        } else {
          // 建立多边形
          crtPoly(x, y)
        }
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • getMousePosInWebgl() 方法是用于获取鼠标在webgl 画布中的位置,我们之前说过。

    • crtPoly() 创建多边形

      function crtPoly(x, y) {
        let o1 = point ? point : { x, y, pointSize: random(), alpha: 1 }
        const o2 = { x, y, pointSize: random(), alpha: 1 }
        poly = new Poly({
          size: 4,
          attrName: 'a_Attr',
          geoData: [o1,o2],
          types: ['POINTS','LINE_STRIP']
        })
        lb.add(poly)
        crtTrack(o1)
        crtTrack(o2)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 建立两个顶点数据o1,o2,如果鼠标点击了其它顶点,o1的数据就是此顶点的数据

    • 顶点的尺寸是一个随机数random()

      function random() {
        return Math.random() * 8.0 + 3.0
      }
      
      • 1
      • 2
      • 3
    • 基于两个顶点数据,建立多边形对象和两个时间轨对象

    • crtTrack() 建立时间轨

      function crtTrack(obj) {
        const { pointSize } = obj
        const track = new Track(obj)
        track.start = new Date()
        track.timeLen = 2000
        track.loop = true
        track.keyMap = new Map([
          [
            "pointSize",
            [
              [500, pointSize],
              [1000, 0],
              [1500, pointSize],
            ],
          ],
          [
            "alpha",
            [
              [500, 1],
              [1000, 0],
              [1500, 1],
            ],
          ],
        ]);
        compose.add(track)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
    • addVertice() 添加顶点

      function addVertice(x, y) {
        const { geoData } = poly
        if(point) {
          geoData[geoData.length-1] = point
        }
        let obj = { x, y, pointSize:random(), alpha: 1 }
        geoData.push(obj)
        crtTrack(obj)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 如果鼠标点击了其它顶点,就让多边形的最后一个顶点数据为此顶点

    • 建立下一个顶点的顶点数据,添加新的顶点,建立新的时间轨

    • popVertice() 删除最后一个顶点

      function popVertice() {
        poly.geoData.pop()
        compose.children.pop()
        poly = null
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    3.8 )鼠标移动事件

    canvas.addEventListener("mousemove", (event) => {
      const { x, y } = getMousePosInWebgl(event, canvas)
      point = hoverPoint(x, y)
      if(point) {
        canvas.style.cursor = 'pointer'
      } else {
        canvas.style.cursor = 'default'
      }
      if(poly) {
        const obj = poly.geoData[poly.geoData.length-1]
        obj.x = x
        obj.y = y
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 基于鼠标是否划上顶点,设置鼠标的视觉状态

    • 设置正在绘制的多边形的最后一个顶点点位

    • hoverPoint() 检测所有顶点的鼠标划入,返回顶点数据

      function hoverPoint(mx, my) {
        for(let { geoData } of lb.children) {
          for(let obj of geoData) {
            if(poly && obj === poly.geoData[poly.geoData.length-1]) {
              continue
            }
            const delta = {
              x: mx - obj.x,
              y: my - obj.y
            }
            const { x,y } = glToCssPos(delta, canvas)
            const dist = x * x + y * y;
            if(dist < 100) {
              return obj
            }
          }
        }
        return null
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 遍历 lb 中的所有顶点数据,忽略绘图时随鼠标移动的点,获取鼠标和顶点的像素距离,若此距离小于10像素,返回此点;否则,返回null

    • glToCssPos() webgl坐标系转css坐标系,将之前说过的getMousePosInWebgl() 方法逆向思维即可

      function glToCssPos({x,y},{width,height}){
        const [halfWidth, halfHeight] = [width / 2, height / 2]
        return {
          x:x*halfWidth,
          y:-y*halfHeight
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    2.9 )连续渲染方法

    !(function animate() {
      compose.update(new Date())
      lb.updateVertices(['x', 'y', 'pointSize', 'alpha'])
      render()
      requestAnimationFrame(animate)
    })();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 更新动画数据
    • 更新Vertices 数据
    • render() 渲染
      function render(){
        gl.clear(gl.COLOR_BUFFER_BIT)
        lb.draw()
      }
      
      • 1
      • 2
      • 3
      • 4

    mac系统下绘制线条兼容性问题解决

    • 这里着重说一下,在mac系统下,在用鼠标绘制线条的时候,就是线条的效果是断开的,如下图

    • 这个效果是由片源着色器导致的,这个片元着色器还是以前用于绘制原点的片元着色器

      precision mediump float;    
      void main(){ 
          float dist = distance(gl_PointCoord, vec2(0.5,0.5));
          if(dist<0.5){
              gl_FragColor = vec4(1,1,0,1);
          } else {
              discard;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 这个着色器在mac系统会把一部分线条中的片元给过滤掉,也就是走了后面的discard方法放弃了一部分片元的绘制

    • 而这个问题在window电脑里就没有

    解决

    • 需要告诉着色器当前的绘图方式, 如果我是用 POINTS 方法去绘图的话,那么就过滤一下圆圈以外的片元,也就是说只画圆圈以内的,就像现在这个逻辑一样
    • 那如果我的绘图方式不是points,而是线或者面之类的那我就直接正常绘图就可以了

    接下来咱们看一下代码实现

    • 1)先给片元着色器添加一个uniform 变量

      precision mediump float;
      uniform bool u_IsPOINTS;
      void main() {
          if(u_IsPOINTS) { 
              float dist = distance(gl_PointCoord,vec2(0.5,0.5));
              if(dist < 0.5) {
                  gl_FragColor = vec4(1,1,0,1);
              } else {
                  discard;
              }
          } else {
              gl_FragColor = vec4(1,1,0,1);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 2)给 Poly 对象添加两个属性

        const defAttr = () => ({
          circleDot: false,
          u_IsPOINTS: null,
          ...
        })
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • circleDot 是否是圆点
      • u_IsPOINTS uniform变量
    • 3)在初始化方法中,如果是圆点,就获取一下uniform 变量

      init() {
          ...
          if (circleDot) {
            this.u_IsPOINTS = gl.getUniformLocation(gl.program, "u_IsPOINTS");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 4 )在渲染的时候,如果是圆点,就基于绘图方式修改 uniform 变量

      draw(types = this.types) {
          const {gl,count,u_IsPOINTS,circleDot} = this;
          for (let type of types) {
              circleDot && gl.uniform1f(u_IsPOINTS, type==='POINTS');
              gl.drawArrays(gl[type],0,count);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
  • 相关阅读:
    记录一次IDEA非法字符‘\ufeff‘报错
    java反序列化专项
    网络和Linux网络_2(套接字编程)socket+UDP网络通信代码
    【阿旭机器学习实战】【13】决策树分类模型实战:泰坦尼克号生存预测
    LeetCode_双指针_中等_633.平方数之和
    在ubuntu20.04上面跑通rocket-chip仿真,用rocket-tools工具
    关于javascript编写
    Selenium定向爬取PubMed生物医学摘要信息
    竞赛 题目:基于python的验证码识别 - 机器视觉 验证码识别
    05-流式操作:使用 Flux 和 Mono 构建响应式数据流
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/133438488