WebGL技巧:选中三维物体

选中三维物体

此处采用了取巧的方法来判断选中的物体:将物体绘制成不同颜色,根据鼠标点击处颜色来判断选中的物体。

示例程序PickObject展示了选中三维物体的一种方式,重要的细节如下:

  • 示例程序在点击的时候重绘立方体,通过向着色器传值并重绘将立方体绘制为红色,再提取点击处的颜色:如果点击处为红色,则代表选中,否则未选中。选中状态判断完毕后,再向向着色器传值、重绘,恢复本来颜色。

在这里插入图片描述

从颜色缓冲区读取点击处颜色的方法为:

gl.readPixels(x, y, width, height, format, type, pixels)
从颜色缓冲区中读取由x、y、width、height参数确定的矩形块中的所有像素值,并保存在pixels指定的数组中。
参数:
x、y: 指定颜色缓冲区中矩形块左上角的坐标,同时也是读取的第一个像素的坐标
width、height: 指定矩形块的宽度和高度,以像素为单位
format: 指定像素值的颜色格式,必须为gl.RGBA
type: 指定像素值的数据格式,必须为gl.UNSIGNED_BYTE
pixels: 指定用来接收像素值数据的Uint8Array类型化数组
返回值:
错误:
INVALID_VALUE: pixels为null,或者,width或height是负值
INVALID_OPERATION: pixels的长度不够存储所有像素值数据
INVALID_ENUM: format或type的值无效

相信有了这一方法,程序的思路就呼之欲出了。

实现选中判断的程序设计如下:

  1. 着色器设计:顶点着色器中加入bool变量u_Clicked,采用if结构,对v_Clolor进行区别处理。

  2. 鼠标点击事件:采用document中的方法获取点击处坐标,将坐标转换为WebGL坐标,获取颜色。

    首先判断是否在窗口中,主要使用点击事件中ev对象的ev.target.getBoundingClientRect()方法和clientX、clientY属性.

      canvas.onmousedown = function (ev) {
        let x = ev.clientX, y = ev.clientY
        let rect = ev.target.getBoundingClientRect()
        if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
          // 检查是否点击在物体上
          let x_in_canvas = x - rect.left, y_in_canvas = rect.bottom - y
          let picked = check(gl, n, x_in_canvas, y_in_canvas, currentAngle, u_Clicked, viewProjMatrix, u_MvpMatrix)
          if (picked) alert('The cube was selected!')
        }
      }
    

    点击在窗口中时,直接将立方体重绘为红色,查看点击处的颜色判断是否点击到,之后再重绘为正常颜色。

    function check(gl, n, x_in_canvas, y_in_canvas, currentAngle, u_Clicked, viewProjMatrix, u_MvpMatrix) {
      let picked = false
      gl.uniform1i(u_Clicked, 1) // 将立方体绘制为红色
      draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix)
      // 读取点击位置的像素颜色值
      let pixels = new Uint8Array(4) // 储存像素的数组
      gl.readPixels(x_in_canvas, y_in_canvas, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
      if (pixels[0] == 255)// 如果pixels[0]是255则说明点击在物体上
        picked = true
      // 重绘正常状态立方体
      gl.uniform1i(u_Clicked, 0)
      draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix)
    
      return picked
    }
    

全部代码如下所示:

// PickObject.js
// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'uniform bool u_Clicked;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_Position = u_MvpMatrix * a_Position;\n' +
  ' if (u_Clicked){\n' +
  ' v_Color = vec4(1.0,0.0,0.0,1.0);\n' +
  ' } else{\n' +
  ' v_Color = a_Color;\n' +
  ' }\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_FragColor = v_Color;\n' +
  '}\n'
// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取webgl上下文
  let gl = getWebGLContext(canvas)
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL')
    return
  }
  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to initialize shaders')
    return
  }
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.enable(gl.DEPTH_TEST)

  // 设置顶点坐标和颜色
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the positions of the vertices')
    return
  }

  // 矩阵准备
  let viewProjMatrix = new Matrix4()
  viewProjMatrix.setPerspective(30.0, canvas.width / canvas.clientHeight, 1.0, 10)
  viewProjMatrix.lookAt(3.0, 3.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
  // 获取u_MvpMatrix地址
  let u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix')
  if (!u_MvpMatrix) {
    console.log('Failed to get the storage location of u_MvpMatrix')
    return -1
  }
  // 获取u_Clicked地址
  let u_Clicked = gl.getUniformLocation(gl.program, 'u_Clicked')
  if (!u_Clicked) {
    console.log('Failed to get the storage location of u_Clicked')
    return -1
  }
  gl.uniform1i(u_Clicked, 0) // 将false传给u_Clicked变量

  let currentAngle = 0.0 // 当前旋转角度
  // 注册事件响应函数
  canvas.onmousedown = function (ev) {
    let x = ev.clientX, y = ev.clientY
    let rect = ev.target.getBoundingClientRect()
    if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
      // 检查是否点击在物体上
      let x_in_canvas = x - rect.left, y_in_canvas = rect.bottom - y
      let picked = check(gl, n, x_in_canvas, y_in_canvas, currentAngle, u_Clicked, viewProjMatrix, u_MvpMatrix)
      if (picked) alert('The cube was selected!')
    }
  }
  draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix)
}
// 顶点坐标
function initVertexBuffers(gl) {
  // 数据准备
  // 顶点坐标
  let vertices = new Float32Array([
    1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // v0-v1-v2-v3 front
    1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, // v0-v3-v4-v5 right
    1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
    -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, // v1-v6-v7-v2 left
    -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // v7-v4-v3-v2 down
    1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, // v4-v7-v6-v5 back
  ])
  // 顶点颜色
  let colors = new Float32Array([
    0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, // v0-v1-v2-v3 front(blue)
    0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, // v0-v3-v4-v5 right(green)
    1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, // v0-v5-v6-v1 up(red)
    1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, // v1-v6-v7-v2 left
    1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v7-v4-v3-v2 down
    0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, 0.4, 1.0, 1.0, // v4-v7-v6-v5 back
  ])
  // 顶点索引
  let indices = new Uint8Array([
    0, 1, 2, 0, 2, 3, // 前
    4, 5, 6, 4, 6, 7, // 右
    8, 9, 10, 8, 10, 11, // 上
    12, 13, 14, 12, 14, 15, // 左
    16, 17, 18, 16, 18, 19, // 下
    20, 21, 22, 20, 22, 23, // 后
  ])

  // 将顶点坐标和颜色写入缓冲区
  if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) {
    return -1
  }
  if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color')) {
    return -1
  }

  // 索引
  let indexBuffer = gl.createBuffer()
  if (!indexBuffer) {
    console.log('Failed to create indexBuffer')
    return -1
  }
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)

  //返回顶点数量
  return indices.length
}
function initArrayBuffer(gl, data, num, type, attribute) {
  let buffer = gl.createBuffer()
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

  let a_attribute = gl.getAttribLocation(gl.program, attribute)
  if (a_attribute < 0) {
    console.log('Failed to get the storage location of ' + attribute)
    return false
  }
  gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0)
  gl.enableVertexAttribArray(a_attribute)

  return true
}

function check(gl, n, x_in_canvas, y_in_canvas, currentAngle, u_Clicked, viewProjMatrix, u_MvpMatrix) {
  let picked = false
  gl.uniform1i(u_Clicked, 1) // 将立方体绘制为红色
  draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix)
  // 读取点击位置的像素颜色值
  let pixels = new Uint8Array(4) // 储存像素的数组
  gl.readPixels(x_in_canvas, y_in_canvas, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
  if (pixels[0] == 255)// 如果pixels[0]是255则说明点击在物体上
    picked = true
  // 重绘正常状态立方体
  gl.uniform1i(u_Clicked, 0)
  draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix)

  return picked
}

var g_MvpMatrix = new Matrix4() // 模型视图投影矩阵
// 绘制函数
function draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix) {
  // 计算模型视图投影矩阵
  g_MvpMatrix.set(viewProjMatrix)
  g_MvpMatrix.rotate(currentAngle, 0.0, 1.0, 0.0)
  gl.uniformMatrix4fv(u_MvpMatrix, false, g_MvpMatrix.elements)

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
}

选中表面

采用同样的方式,也可以对表面进行选择。此处采用颜色缓冲区中的α分量记录“每个像素属于哪个面”的信息。

rgba的每个分量都是8比特,所以仅使用一个分类最多可以区分255个物体

此处将顶点着色器展示如下:

// PickFace.js
// 顶点着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'attribute float a_Face;\n' + // 表面编号(attribute只能使用float相关变量)
  'uniform mat4 u_MvpMatrix;\n' +
  'uniform int u_PickedFace;\n' + // 被选中表面的编号
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_Position = u_MvpMatrix * a_Position;\n' +
  ' int face = int(a_Face);\n' + // 转为int类型
  ' vec3 color = (face == u_PickedFace) ? vec3(1.0):a_Color.rgb;\n' + 
  ' if (u_PickedFace == 0){\n' + // 将表面编号写入α分量
  ' v_Color = vec4(color,a_Face/255.0);\n' +
  ' } else{\n' +
  ' v_Color = vec4(color,a_Color.a);\n' +
  ' }\n' +
  '}\n'

从着色器的设计基本可以知道程序的思路。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【资源说明】 JavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zipJavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zipJavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zip JavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zip JavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zipJavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zip JavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zip JavaScript基于WebGL技术实现的三维BS端开发平台(含Cesium 的核心操作库+UI组件库).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价,也适用于小白学习入门进阶。当然也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或者热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载,沟通交流,互相学习,共同进步!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值