WebGL入门(三十四)-三维空间中鼠标控制物体旋转,用鼠标控制立方体的旋转

1. demo效果

在这里插入图片描述在这里插入图片描述
如上图,是一个添加了纹理图片的立方体,这个立方体可以通过鼠标按下后上下左右拖动实现立方体的旋转

2. 实现要点

2.1 注册鼠标事件

2.1.1 注册鼠标事件函数的声明

在这个函数中需要注册鼠标按下事件、鼠标松开事件和鼠标移动事件的处理函数,
鼠标按下事件 的处理函数中,需要获取到鼠标按下的位置并将是否拖拽的标识置为true,鼠标松开事件 处理比较简单,只需要将是否拖拽的标识置为false即可,鼠标移动事件 处理函数中需要获取到当前鼠标的的坐标,分别计算出x轴和y轴需要旋转的角度,赋值给该函数的输出参数currentAngle

function initEventHandlers(canvas, currentAngle) {
  var dragging = false //默认鼠标拖动不旋转物体
  var lastX = -1,
    lastY = -1 //鼠标最后的位置

  canvas.onmousedown = function (ev) { //注册鼠标按下事件
    var x = ev.clientX,
      y = ev.clientY
    //鼠标在物体上开始拖动
    var rect = ev.target.getBoundingClientRect()
    if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
      lastX = x
      lastY = y
      dragging = true
    }
  }

  //鼠标松开拖动结束
  canvas.onmouseup = function (ev) {
    dragging = false
  }

  canvas.onmousemove = function (ev) { //注册鼠标移动事件
    var x = ev.clientX,
      y = ev.clientY
    if (dragging) {
      var factor = 100 / canvas.height //旋转因子
      var dx = factor * (x - lastX)
      var dy = factor * (y - lastY)
      //沿Y轴的旋转角度控制在-90到90度之间
      currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0)
      currentAngle[1] = currentAngle[1] + dx
    }
    lastX = x, lastY = y
  }
}

2.1.2 注册鼠标事件函数的调用

这里需要注意的是参数currentAngle是一个数组,索引0的值表示绕X轴旋转的角度,索引1的值表示绕Y轴旋转的角度,调用前要先准备这样一个数组,来接收鼠标事件处理后的旋转角度信息,供后面绘图使用

var currentAngle = [0.0, 0.0] //当前旋转的角度[x-axis, y-axis]
initEventHandlers(canvas, currentAngle) //注册鼠标事件

2.2 纹理图片加载

function initTextures(gl) {

   //创建纹理对象
   var texture = gl.createTexture()
   if (!texture) {
     console.log('创建纹理对象失败')
     return false
   }

   //获取片元着色器uniform变量u_Sampler的存储地址
   var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler')
   if (!u_Sampler) {
     console.log('获取u_Sampler的存储地址失败')
     return false
   }

   var image = new Image()
   if (!image) {
     console.log('创建图片对象失败')
     return false
   }

   image.src = './resources/sky_roof.jpg' //设置纹理资源路径

   //注册图片加载事件的响应函数
   image.onload = function () {
     //调用加载纹理函数
     loadTexture(gl, texture, u_Sampler, image)
   }
   return true
 }

 function loadTexture(gl, texture, u_Sampler, image) {
   gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) //对纹理图像镜像y轴反转
   gl.activeTexture(gl.TEXTURE0) //激活纹理单元0 
   gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image) //纹理图像分配给纹理对象
   gl.uniform1i(u_Sampler, 0) //纹理单元编号0传给片元着色器中uniform变量u_Sampler
 }

2.3 图形绘制

在这里需要注意的是,绘制函数的参数中的currentAngle,它表示鼠标拖拽时计算出的要沿X轴、Y轴旋转的角度,分别存储在下标为0和1的数组中,在该函数中使用时也通过该参数的下标来获取沿X轴、Y轴旋转的角度

var g_MvpMatrix = new Matrix4() //模型视图投影矩阵 
function draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle) {
  //计算模型视图投影矩阵 
  g_MvpMatrix.set(viewProjMatrix) //设置视图投影矩阵 
  g_MvpMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0) //沿X轴旋转设置矩阵
  g_MvpMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0) //沿Y轴旋转设置矩阵

  //模型视图投影矩阵的计算结果传给uniform变量u_MvpMatrix
  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) //绘图
}

3. demo代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title></title>
</head>

<body>
  <!--通过canvas标签创建一个400px*400px大小的画布-->
  <canvas id="webgl" width="400" height="400"></canvas>
  <script type="text/javascript" src="./lib/cuon-matrix.js"></script>
  <script>
    //顶点着色器
    var VSHADER_SOURCE = '' +
      'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
      'attribute vec2 a_TexCoord;\\n' + //声明attribute变量a_TexCoord,用来存放纹理坐标
      'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
      'varying vec2 v_TexCoord;\\n' + //声明varying变量v_TexCoord,用来向片元着色器传值纹理坐标
      'void main(){\\n' +
      '  gl_Position = u_MvpMatrix * a_Position;\\n' + //将模型视图投影组合矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position
      '  v_TexCoord = a_TexCoord;\\n' + //将纹理坐标传给片元着色器
      '}\\n'
      
    //片元着色器
    var FSHADER_SOURCE = '' +
      '#ifdef GL_ES\\n' +
      ' precision mediump float;\\n' + // 设置精度
      '#endif\\n' +
      'uniform sampler2D u_Sampler;\\n' + //声明uniform变量u_Sampler,存放第一个纹理单元编号
      'varying vec2 v_TexCoord;\\n' + //声明varying变量v_TexCoord,用来接收顶点着色器传送的纹理坐标
      'void main(){\\n' +
      ' gl_FragColor = texture2D(u_Sampler,v_TexCoord);\\n' + //将texture2D函数抽取纹理单元0纹素颜色赋值给内置变量gl_FragColor
      '}\\n'

    //初始化着色器函数
    function initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE) {
      //创建顶点着色器对象
      var vertexShader = loadShader(gl, gl.VERTEX_SHADER, VSHADER_SOURCE)
      //创建片元着色器对象
      var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, FSHADER_SOURCE)

      if (!vertexShader || !fragmentShader) {
        return null
      }

      //创建程序对象program
      var program = gl.createProgram()
      if (!gl.createProgram()) {
        return null
      }
      //分配顶点着色器和片元着色器到program
      gl.attachShader(program, vertexShader)
      gl.attachShader(program, fragmentShader)
      //链接program
      gl.linkProgram(program)

      //检查程序对象是否连接成功
      var linked = gl.getProgramParameter(program, gl.LINK_STATUS)
      if (!linked) {
        var error = gl.getProgramInfoLog(program)
        console.log('程序对象连接失败: ' + error)
        gl.deleteProgram(program)
        gl.deleteShader(fragmentShader)
        gl.deleteShader(vertexShader)
        return null
      }
      //使用program
      gl.useProgram(program)
      gl.program = program
      //返回程序program对象
      return program
    }

    function loadShader(gl, type, source) {
      // 创建顶点着色器对象
      var shader = gl.createShader(type)
      if (shader == null) {
        console.log('创建着色器失败')
        return null
      }

      // 引入着色器源代码
      gl.shaderSource(shader, source)

      // 编译着色器
      gl.compileShader(shader)

      // 检查顶是否编译成功
      var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
      if (!compiled) {
        var error = gl.getShaderInfoLog(shader)
        console.log('编译着色器失败: ' + error)
        gl.deleteShader(shader)
        return null
      }

      return shader
    }

    function init() {
      //通过getElementById()方法获取canvas画布
      var canvas = document.getElementById('webgl')
      //通过方法getContext()获取WebGL上下文
      var gl = canvas.getContext('webgl')
      //初始化着色器
      if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('初始化着色器失败')
        return
      }

      // 设置canvas的背景色
      gl.clearColor(0.0, 0.0, 0.0, 1.0)

      //初始化顶点坐标和顶点颜色
      var n = initVertexBuffers(gl)
      setMatrixAndDraw(gl, n, canvas)
    }

    //设置矩阵并绘图
    function setMatrixAndDraw(gl, n, canvas) {

      //开启隐藏面消除
      gl.enable(gl.DEPTH_TEST)

      //清空颜色和深度缓冲区
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

      //获取顶点着色器uniform变量u_MvpMatrix的存储地址
      var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix')
      if (!u_MvpMatrix) {
        console.log('获取uniform变量u_MvpMatrix的存储地址失败')
        return
      }

      //创建视图投影矩阵
      var viewProjMatrix = new Matrix4()
      viewProjMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0)
      viewProjMatrix.lookAt(3.0, 3.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)

      var currentAngle = [0.0, 0.0] //当前旋转的角度[x-axis, y-axis]
      initEventHandlers(canvas, currentAngle) //注册鼠标事件

      //设置纹理
      if (!initTextures(gl)) {
        console.log('初始化纹理失败')
        return
      }

      var tick = function () { //开始绘图
        draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle)
        requestAnimationFrame(tick, canvas)
      }
      tick()
    }

    //初始化顶点坐标和顶点颜色
    function initVertexBuffers(gl) {

      var v0 = [1.0, 1.0, 1.0]
      var v1 = [-1.0, 1.0, 1.0]
      var v2 = [-1.0, -1.0, 1.0]
      var v3 = [1.0, -1.0, 1.0]
      var v4 = [1.0, -1.0, -1.0]
      var v5 = [1.0, 1.0, -1.0]
      var v6 = [-1.0, 1.0, -1.0]
      var v7 = [-1.0, -1.0, -1.0]

      //顶点
      var vertices = new Float32Array([
        ...v0, ...v1, ...v2, ...v3, // 前
        ...v0, ...v3, ...v4, ...v5, // 右
        ...v0, ...v5, ...v6, ...v1, // 上
        ...v1, ...v6, ...v7, ...v2, // 左
        ...v7, ...v4, ...v3, ...v2, // 下
        ...v4, ...v7, ...v6, ...v5 // 后
      ])

      // 纹理
      var texCoords = new Float32Array([
        1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v1-v2-v3 前
        0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // v0-v3-v4-v5 右
        1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // v0-v5-v6-v1 上
        1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v1-v6-v7-v2 左
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // v7-v4-v3-v2 下
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 // v4-v7-v6-v5 后
      ])

      // 绘制的索引
      var 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, texCoords, 2, gl.FLOAT, 'a_TexCoord')) {
        return -1
      }

      //创建缓冲区对象
      var indexBuffer = gl.createBuffer()

      //将顶点索引写入缓冲区对象
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)

      return indices.length
    }

    function initEventHandlers(canvas, currentAngle) {
      var dragging = false //默认鼠标拖动不旋转物体
      var lastX = -1,
        lastY = -1 //鼠标最后的位置

      canvas.onmousedown = function (ev) { //注册鼠标按下事件
        var x = ev.clientX,
          y = ev.clientY
        //鼠标在物体上开始拖动
        var rect = ev.target.getBoundingClientRect()
        if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
          lastX = x
          lastY = y
          dragging = true
        }
      }

      //鼠标松开拖动结束
      canvas.onmouseup = function (ev) {
        dragging = false
      }

      canvas.onmousemove = function (ev) { //注册鼠标移动事件
        var x = ev.clientX,
          y = ev.clientY
        if (dragging) {
          var factor = 100 / canvas.height //旋转因子
          var dx = factor * (x - lastX)
          var dy = factor * (y - lastY)
          //沿Y轴的旋转角度控制在-90到90度之间
          currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0)
          currentAngle[1] = currentAngle[1] + dx
        }
        lastX = x, lastY = y
      }
    }

    var g_MvpMatrix = new Matrix4() //模型视图投影矩阵 
    function draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle) {
      //计算模型视图投影矩阵 
      g_MvpMatrix.set(viewProjMatrix) //设置视图投影矩阵 
      g_MvpMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0) //沿X轴旋转设置矩阵
      g_MvpMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0) //沿Y轴旋转设置矩阵

      //模型视图投影矩阵的计算结果传给uniform变量u_MvpMatrix
      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) //绘图
    }

    function initArrayBuffer(gl, data, num, type, attribute) {
      //创建缓冲区对象
      var buffer = gl.createBuffer()
      //将顶点坐标和顶点颜色信息写入缓冲区对象
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
      gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)

      //获取顶点着色器attribute变量存储地址, 分配缓存并开启
      var a_Attribute = gl.getAttribLocation(gl.program, attribute)
      gl.vertexAttribPointer(a_Attribute, num, type, false, 0, 0)
      gl.enableVertexAttribArray(a_Attribute)

      return true
    }

    function initTextures(gl) {

      //创建纹理对象
      var texture = gl.createTexture()
      if (!texture) {
        console.log('创建纹理对象失败')
        return false
      }

      //获取片元着色器uniform变量u_Sampler的存储地址
      var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler')
      if (!u_Sampler) {
        console.log('获取u_Sampler的存储地址失败')
        return false
      }

      var image = new Image()
      if (!image) {
        console.log('创建图片对象失败')
        return false
      }

      image.src = './resources/sky_roof.jpg' //设置纹理资源路径

      //注册图片加载事件的响应函数
      image.onload = function () {
        //调用加载纹理函数
        loadTexture(gl, texture, u_Sampler, image)
      }
      return true
    }

    function loadTexture(gl, texture, u_Sampler, image) {
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) //对纹理图像镜像y轴反转
      gl.activeTexture(gl.TEXTURE0) //激活纹理单元0 
      gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image) //纹理图像分配给纹理对象
      gl.uniform1i(u_Sampler, 0) //纹理单元编号0传给片元着色器中uniform变量u_Sampler
    }

    init()
  </script>
</body>

</html>

© 版权声明
THE END
喜欢就支持一下吧
点赞406 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容