forthealllight / blog

📖我的博客,记录学习的一些笔记,如有喜欢,欢迎star
2.3k stars 225 forks source link

优雅的学习webgl(1)—从0开始构造你的第一个webgl程序 #50

Open forthealllight opened 5 years ago

forthealllight commented 5 years ago

优雅的学习webgl(1)—从0开始构造你的第一个webgl程序


    学习webgl也有小半年的时间了,有了一些心得和体会,在这里做一个记录,整个系列的代码都会给出,这篇文章是这个系列的第一篇文章,带你走进webgl的世界。

  • 什么是webgl
  • 用webgl画点
  • 用webgl实现一个彩色正方形

这个系列的源码地址为:源码的地址为: https://github.com/forthealllight/webgl-demo

一、什么是webgl

  在介绍什么是webgl之前,我们来看一个最简单的webgl程序。


<html lang="en">
  <head>
    <title>WebGL Demo</title>
    <meta charset="utf-8">
  </head>

  <body>
    <canvas id="glcanvas" width="640" height="480"></canvas>
  </body>

  <script>
    main();
    // start here
    function main() {
      const canvas = document.querySelector("#glcanvas");
      // Initialize the GL context
      const gl = canvas.getContext("webgl");
      // Only continue if WebGL is available and working
      if (!gl) {
        alert("你的浏览器不支持webgl");
        return;
      }

      // Set clear color to black, fully opaque
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      // Clear the color buffer with specified clear color
      gl.clear(gl.COLOR_BUFFER_BIT);
    }
  </script>
</html>

上述就是一个webgl的例子,做的事情很简单就是清空了webgl画布的颜色。在上述的例子中,我们没有引入什么其他的插件,就构建了一个webgl程序,因为webgl本身就是浏览器层面的。Webgl是内嵌在浏览器中的,你不必安装任何插件或者库,如果浏览器支持webgl,就可以通过getContext的方法创建一个webgl实例。

const gl = canvas.getContext("webgl");

    一提到webgl就容易跟3D渲染绘图联系在一起,实际上这两者并没有绝对关联,webgl也可以用来绘制2D平面图形,2D动画等等。那么webgl到底是什么呢?一句话概括就是:

通过绘图渲染技术OpenGL在浏览器里面进行图形渲染的技术

    了解过图形渲染技术的同学都听过OpenGL等,我们可以通过C语言,在window等平台上编写复杂和渲染出复杂的图形,通过OpenGL绘制和渲染图形有很高的平台要求以及编程语言的限制。而Webgl源自于OpenGL,从OpenGL2.0的着色器行为中诞生了适用于移动式穿戴设备的OpenGL ES标准,在这个标准下演化出了webgl,使得我们可以通过结合javascript语言和GLSL ES着色器语言,在浏览器中绘制出复杂的图形,不需要编译也不需要引入任何插件.

二、用webgl画点

    接着我们来看如何用webgl来绘制一个完整的图案,绘制图案跟上一小节最简单的清空画布的webgl程序不同。我们需要webgl中的两个重要的概念——着色器。     webgl的本质就是通过顶点着色器和片元着色器,将图形渲染到浏览器中。之后我们会详细的介绍顶点着色器和片元着色器,这里可以简单的理解为顶点着色器决定了每个顶点的位置,片元着色器决定了图形的颜色。

1、初始化着色器程序

    绘制图案之前必须进行着色器程序初始化,根据着色球类型创建着色器对象,将着色器对象编译后绑定到程序对象,最后编译和连接程序对象从而完整了初始化的过程,接下来就可以使用程序对象来绘制图形。

    上面的两段话特别绕,总结就是:

GLSL着色器语言是以字符串的形式存在浏览器中的,为了能够将字符串编译成可以在显卡中运行的着色器程序,必须进行着色器程序初始化

在初始化着色器程序中我们必须用到两个对象着色器对象和程序对象,我们来简单介绍一下这两者。

我们用图来区别这两者的关系:

Lark20191204-145309

从这个图可以看出层级关系,我们要初始化着色器程序,就是按照如下的层级关系依次来初始化。

  1. 创建着色器对象(gl.createShader())
  2. 向着色器对象中填充着色器(gl.shaderSource())
  3. 编译着色球(gl.compileShader())
  4. 创建程序对象(gl.createProgram())
  5. 为程序对象分配着色器(gl.attashShader())
  6. 连接程序对象(gl.linkProgram())
  7. 使用程序对象(gl.useProgram())

结合上面图形的层次结构,以及上述的7步初始化着色器的步骤,我们可以来介绍着色器初始化函数initShaderProgram

function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); //创建顶点着色器对象
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);//创建片元着色器对象

  // Create the shader program

  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);

  return shaderProgram;
}

这就是最上层的程序对象的创建过程,至于着色器对象的创建可以通过如下函数 loadShader:

function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);

  return shader;
}

上述我们就完成了着色器程序的初始化,就可以在浏览器中编译GLSL ES语言渲染出图形。下面我们来看最简单的画点

2、画一个点

    我们上一小节将了如何初始化着色器,初始化着色器可以通过initShaderProgram方法,我们可以用webgl来画一个最简单的size为10的点

首先通过字符串的方式定义顶点着色器和片元着色器:

const vsSource = `
    ashouttribute vec4 aVertexPosition;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    void main() {
      gl_Position = vec4(0.0,0.0,0.0,1.0);
      gl_PointSize = 10.0;
    }
  `;

  // Fragment shader program

  const fsSource = `
    void main() {
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
  `;

然后将这两个字符串传入initShaderProgram程序,就完成了着色器初始化。

const shaderProgram = initShaderProgram(gl, vsSource, fsSource)

最后清空画布,并使用这个着色器程序开始画图:

  gl.clearColor(0.0, 0.0, 0.0, 1.0);  
  gl.clearDepth(1.0);               
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  gl.useProgram(shaderProgram);
  gl.drawArrays(gl.POINTS,0,1);

这样就在浏览器中画出了一个点。

Lark20191204-154158

源码的地址为: https://github.com/forthealllight/webgl-demo/tree/master/demo1

三、用webgl实现一个彩色正方形(选择性阅读)

    最后我们来实现一个较为复杂的例子,用webgl来画一个正方形。我们可以通过4次每次画一个点,画4个点,然后将这4个点连起来就成为了一个正方形,此外如果我们要一次性的画出4个点并连成正方形,就需要使用缓冲区。

我们可以简单理解,缓冲区保存了很多信息,我们可以读取缓冲区的信息,在一次绘制中绘出我们想要的图形。

我们来看创建一个缓冲区的步骤:

  1. 创建缓冲区对象(gl.createBuffer())
  2. 绑定缓冲区对象(gl.bindBuffer())
  3. 将数据写入缓冲区对象(gl.bufferData())
  4. 将缓冲区对象分配给一个attribute变量(gl.vertexAttribPointer())
  5. 开启attribute变量(gl.enableVertexAttribArray())

这个五步可以简记为:创建-绑定-写入-分配-开启这么几步,并且我们要使用缓冲区对象这五步是必不可免的.

我们可以定义initBuffers方法来创建缓冲区,我们可以分别创建一个顶点缓冲区(用于读取图形的顶点坐标)以及片元缓冲区(用于读取每个片元的颜色信息)

function initBuffers(gl) {
  //顶点缓冲区
  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  const positions = [
     1.0,  1.0,
    -1.0,  1.0,
     1.0, -1.0,
    -1.0, -1.0,
  ];

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  var colors = [
    1.0,  1.0,  1.0,  1.0,    // white
    1.0,  0.0,  0.0,  1.0,    // red
    0.0,  1.0,  0.0,  1.0,    // green
    0.0,  0.0,  1.0,  1.0,    // blue
  ];
 //颜色缓冲区
  const colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

  return {
    position: positionBuffer,
    color: colorBuffer,
  };
}

然后,在绘图的时候,通过:

gl.bindBuffer();
gl.vertexAttribPointer();
gl.enableVertexAttribArray()

来分别使用缓冲区,除了缓冲区外,我们还需要在顶点着色器和片元着色器中定义全局变量,来接受从缓冲区的传递过来的值。这里先省略,后期在详细介绍缓冲区和着色器的时候会提到.

最后给出绘制出来的图像:

Lark20191204-162205

源码的地址为: https://github.com/forthealllight/webgl-demo/tree/master/demo2

    最后补充一下,在第二张的画一个点的例子中,我们机会没有引入任何插件,实际上虽然webgl是浏览器内置的不需要引入,但是为了方便矩阵操作等,也有一些比较实用的工具

clearJSer commented 4 years ago

最后我们来实现一个较为复杂的例子,用webgl来画一个正方形。我们可以通过3次每次画一个点,画3个点,然后将这3个点连起来就成为了一个正方形,此外如果我们要一次性的画出3个点并连成正方形,就需要使用缓冲区。 这段话里是3个点吗?不应该4个点绘制一个正方形吗?

clearJSer commented 4 years ago

程序步骤这么多,博主都记住手敲了吗?

forthealllight commented 4 years ago

最后我们来实现一个较为复杂的例子,用webgl来画一个正方形。我们可以通过3次每次画一个点,画3个点,然后将这3个点连起来就成为了一个正方形,此外如果我们要一次性的画出3个点并连成正方形,就需要使用缓冲区。 这段话里是3个点吗?不应该4个点绘制一个正方形吗?

写错了,我都是手敲的哦,可以关注一下,这个系列会有10-15篇文章会在2个月内更完

forthealllight commented 4 years ago

程序步骤这么多,博主都记住手敲了吗?

已经修改了