WebGl初学入门

413 阅读4分钟

webgl介绍

什么是webgl

WebGL是一种3D绘图协议,衍生于OpenGLES2.0,可以结合 HTML5JavasSript网页上绘制和渲染二/三维图形

webgl的应用场景

  • 数据可视化
  • 图形/游戏引擎
  • 交互演示、图形渲染
  • 地图
  • VR
  • 物品展示
  • 室内设计
  • 城市规划

webgl的优势

内嵌在浏览器中,不需要安装任何插件即可运行 只需要一个文本编辑器和浏览器,就可以编写三维图形程序

image.png

image.png

webgl开源框架

  1. Three.js: JavaScript 3D WebGL库
  2. Babylonjs:Web3D图形引擎
  3. KickJs:Web的开源图形和游戏引擎
  4. ClayGL:构建可扩展的Web3D应用程序
  5. PlayCanvas:网络游戏和3D图形引擎
  6. WebGLStudio.is和Litescene.is:开源Web 3D图形编辑器和创建器

canvas和webgl的区别

canvas是HTML5新增的一个 DOM 元素

用途:显示 二维三维 的图像

绘制:二维图形可以使用(Canvas APlWebGL API)三维图形使用 WebGL API

canvas Api提供二维绘图的方式-》canvas.getContext('2d') webgl Api提供三维绘图的方式-》canvas.getContext('webgl')

webgl程序

什么是着色器

着色器就是让开发者自己去编写一段程序,用来代替固定染管线,来处理图像的渲染。

image.png

image.png

image.png

流程介绍:

image.png

现在我们来绘制一个点

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')


  // 着色器
  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 必须要存在 main 函数
    void main() {
      // 要绘制的点的坐标
      gl_Position = vec4(0.0,0.0,0.0,1.0);
      // 点的大小
      gl_PointSize = 30.0;
    }
  `; // 顶点着色器

  //  gl_Position vec4(0.0,0.0,0.0,1.0)  x, y, z, w齐次坐标 (x/w, y/w, z/w)

  // gl_FragColor vec4(1.0,0.0,0.0,1.0) r, g, b, a
  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器


  // 创建着色器
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  
  gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
  gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码
  
  // 编译着色器
  gl.compileShader(vertexShader)
  gl.compileShader(fragmentShader)
  
  // 创建一个程序对象
  const program = gl.createProgram();
  
  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)
  
  gl.linkProgram(program)
  
  gl.useProgram(program)

  // 执行绘制

  // 要绘制的图形是什么, 从哪个开始,   使用几个顶点
  gl.drawArrays(gl.POINTS, 0, 1);
</script>

可以看得出来有从创建着色器、编译着色器、创建程序对象这一块的代码是一定的,可以进行封装

function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

  gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
  gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码

  // 编译着色器
  gl.compileShader(vertexShader)
  gl.compileShader(fragmentShader)

  // 创建一个程序对象
  const program = gl.createProgram();

  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)

  gl.linkProgram(program)

  gl.useProgram(program)

  return program;
}

绘制多个点

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    uniform vec4 uPosition;
    // 只传递顶点数据
    attribute vec4 aPosition;
    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    precision mediump float;
    uniform vec2 uColor;
    void main() {
      gl_FragColor = vec4(uColor.r, uColor.g, 0.0,1.0); // vec4
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');

  const uColor = gl.getUniformLocation(program, 'uColor')

  const points = []
  ctx.onclick = function(ev) {
    // 坐标
    const x = ev.clientX
    const y = ev.clientY

    const domPosition = ev.target.getBoundingClientRect();

    const domx = x - domPosition.left
    const domy = y - domPosition.top;

    //计算点在canvas的坐标
    /*
    0 200 400

    -1 0 1

    -200 0 200

    -1 0 1

    需要先 -200 (当前画布的宽度) 然后再 除以 200

    1 0 -1

    0 200 400

    200 0 -200 / 200

    需要先让 200 减这个数,然后再 / 200

    * */
    const halfWidth = ctx.offsetWidth / 2
    const halfHeight = ctx.offsetHeight / 2

    const clickX = (domx - halfWidth) / halfWidth
    const clickY = (halfHeight - domy) / halfHeight

    points.push({
      clickX, clickY
    })

    for (let i = 0; i < points.length; i++) {
      gl.vertexAttrib2f(aPosition, points[i].clickX, points[i].clickY)

      gl.uniform2f(uColor, points[i].clickX, points[i].clickY)
      gl.drawArrays(gl.POINTS, 0, 1);
    }
  }
</script>

声明attribute变量

image.png attribute 变量只能在顶点着色器中使用,不能在片元着色器中使用

获取attribute变量
const aPosition = gl.getAttribLocation(program, 'aPosition');

给attribute变量赋值

image.png

uniform变量 获取uniform变量
const uColor = gl.getUniformLocation(program, 'uColor')

image.png

webgl缓冲区

什么是缓冲区对象

缓冲区对象是WebGL系统中的一块内存区域,可以一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保在其中,供顶点着色器使用。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;
    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)
  // gl.vertexAttrib2f(aPosition, 0.0, 0.0)

  gl.drawArrays(gl.POINTS, 0, 3);
</script>

创建缓冲区对象WebGLRenderingContext.createBuffer() - Web API | MDN

绑定缓冲区对象WebGLRenderingContext.bindBuffer() - Web API | MDN

将数据写入缓存区对象WebGLRenderingContext.bufferData() - Web API | MDN

将缓存区对象分配给一个attribute变量(WebGLRenderingContext.vertexAttribPointer() - Web API | MDN)

缓冲区使用流程

image.png

缓冲区执行流程

image.png

多图形的绘制

image.png