WebGL笔记:使用鼠标绘制多个线条应用及绘制动感线性星座及修复Mac系统下的渲染缺陷问题

这篇具有很好参考价值的文章主要介绍了WebGL笔记:使用鼠标绘制多个线条应用及绘制动感线性星座及修复Mac系统下的渲染缺陷问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用鼠标绘制多个线条

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

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()
    })
  }
}
  • 属性

    • 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()
}

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>
  • 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>
  • 通过v_Alpha接收透明度,然后设置片元的颜色

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

const lb = new lineBox(gl)

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

const compose = new Compose();

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

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

3.6 )取消右击提示

// 取消右击提示
canvas.oncontextmenu = function() {
  return false;
}

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)
    }
  }
});
  • 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)
    }
    
  • 建立两个顶点数据o1,o2,如果鼠标点击了其它顶点,o1的数据就是此顶点的数据

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

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

  • 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)
    }
    
  • 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)
    }
    
  • 如果鼠标点击了其它顶点,就让多边形的最后一个顶点数据为此顶点

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

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

    function popVertice() {
      poly.geoData.pop()
      compose.children.pop()
      poly = null
    }
    

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
  }
});
  • 基于鼠标是否划上顶点,设置鼠标的视觉状态

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

  • 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
    }
    
  • 遍历 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
      }
    }
    

2.9 )连续渲染方法

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

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

  • 这里着重说一下,在mac系统下,在用鼠标绘制线条的时候,就是线条的效果是断开的,如下图
WebGL笔记:使用鼠标绘制多个线条应用及绘制动感线性星座及修复Mac系统下的渲染缺陷问题,Canvas | Webgl | Three.js,webgl
  • 这个效果是由片源着色器导致的,这个片元着色器还是以前用于绘制原点的片元着色器

    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;
        }
    }
    
  • 这个着色器在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);
        }
    }
    
  • 2)给 Poly 对象添加两个属性

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

    init() {
        ...
        if (circleDot) {
          this.u_IsPOINTS = gl.getUniformLocation(gl.program, "u_IsPOINTS");
        }
    }
    
  • 4 )在渲染的时候,如果是圆点,就基于绘图方式修改 uniform 变量文章来源地址https://www.toymoban.com/news/detail-717330.html

    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);
        }
    }
    

到了这里,关于WebGL笔记:使用鼠标绘制多个线条应用及绘制动感线性星座及修复Mac系统下的渲染缺陷问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包