首页 > 技术文章 > WebGL知识点

zhoujingye 2020-04-01 23:03 原文

1. WebGL是什么

    三维绘图模拟技术,可以驱动HTML5中的Canvas渲染三维场景;

    WebGL中的固定渲染管线是不存在的,所有坐标变换需要自己完成,WebGL使用两种类型的着色器完成坐标变换的工作:顶点着色器、片元着色器

 

2. WebGL和canvas使用上的区别

① 页面初始化一个canvas  

<html>  
    <head>  
        <title>WebGL TEST</title>  
    </head>  
    <body>  
        <canvas id="canvas"></canvas>  
    </body>  
</html>

② 从canvas中获得context,获取WebGL的context,canvas是getContext('2d')

var c = document.getElementById('canvas');  
c.width = 500;  
c.height = 300; 

var gl = c.getContext('webgl') || c.getContext('experimental-webgl');  

③ 画面初始化,包含了会话相关的各种处理函数、对象、常量、属性等

// 使用指定常量颜色来清空画面
gl.clear(gl.COLOR_BUFFER_BIT);
// 使用颜色值(RGBA)来清空画面
gl.clearColor(0.0,0.0,0.0,1.0);

 

3.顶点着色器、片元着色器

顶点着色器:顶点着色器的任务是接收顶点的局部坐标,经过内部变换,输出CCV坐标;顶点着色器接受attribute变量,是逐顶点的数据,输出varying变量,也是逐定点的

内部变换包含:局部坐标(初始坐标)->世界坐标(基于世界原点的坐标)->视图坐标(基于观察者的坐标)->CCV坐标(映射在观察者的四棱台体前表面的坐标)

片元着色器:CCV坐标经过光栅化,转化为逐像素的数据,传给片元着色器,片元着色器的任务是确定每个片元的颜色;逐顶点的varying变量数据经过光栅化,成为逐片元的varying变量数据,输入片元着色器,着色器输出结果显示在canvas上  

<!-- ※顶点着色器 -->
      <script id="vs" type="x-shader/x-vertex">  
           attribute vec3 position;  
           uniform   mat4 mvpMatrix;  
           void main(void){  
              gl_Position = mvpMatrix * vec4(position, 1.0);  
           }  
      </script>  
<!-- ※片段着色器 -->  
      <script id="fs" type="x-shader/x-fragment">  
            void main(void){  
              gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);  
            }  
      </script>

 

4.GLSL着色器语言

WebGL无法利用固定渲染管道,所以要使用GLSL着色器语言进行编程;顶点着色器和片元着色器都使用GLSL来编写;

① 必须定义一个main函数,函数里记录要做的处理,顶点着色器必须要把顶点信息传给一个叫做gl_Position的变量。

// attibute修饰符是用来接收不同顶点传来的不同信息
// vec*表示的是向量,*部分是一个2~4的数字,vec3表示的是一个3维的向量。其元素是浮点型
// position变量定义顶点信息
attribute vec3 position;  
void main(void) {  
    gl_Position = position;  
}

② 生成模型、视图、投影矩阵,然后合并,将最终得到的坐标传给顶点着色器

attribute vec3 position; 
// uniform修饰符是用来接收所有顶点一致的情报信息
// mat*表示的是方阵,可指定范围2~4,mat4表示的是4x4的方阵。其元素是浮点型
uniform mat4 mvpMatrix;  
void main(void) {  
    gl_Position = mvpMatrix * position;  
} 

③ GLSL里还有一个重要的修饰符,也就是varying修饰符,是用来连接顶点着色器和片段着色器之间的桥梁。

比如要把绘制的模型变成半透明,要怎么做?
方法虽然有很多,但是一般的做法是,向顶点里添加颜色的情报信息,然后通过操作颜色的透明度的变化来使模型半透明或者完全透明。这时候,如果想操作顶点里的颜色信息和画面上的颜色信息的话,就需要向片段着色器里传入一些必要的信息。首先是顶点着色器部分:
attribute vec4 position;  
attribute vec4 color;  
uniform mat4 mvpMatrix;  
varying vec4 vColor  
void main(void) {  
    vColor = color;  
    gl_Position = mvpMatrix * position;  
}

片段着色器接收通过varying修饰符所定义的变量vColor:

varying vec4 vColor;  
void main(void)  {  
    gl_FragColor = vColor;  
}
和顶点着色器中必需要把数据传给gl_Position类似,片段着色器要把数据传给gl_FragColor,只是与顶点着色器不同的是,片段着色器的gl_FragColor不是必须要赋值的。但是一般都会输出一种什么颜色,所以gl_FragColor就变成必要的了。
 
5. 顶点缓存VBO(vertex buffer object)
VBO保存着一开始局部坐标的顶点信息,通知着色器哪个VBO和哪个attribute变量相关,然后顶点着色器就可以正确的处理这些顶点了。根据前面的内容,顶点缓存相关的处理的具体流程如下:
  • 顶点的各种信息保存到数组里
  • 使用WebGL的方法生成VBO
  • 使用WebGL的方法将数组中的信息传给VBO
  • 顶点着色器中的attribute函数和VBO结合

准备好保存顶点信息的数组之后,使用WebGL的context的方法生成VBO,当然生成的时候VBO是空的,然后将顶点信息的数组传给它。然后,比如把顶点着色器中的attribute函数和VBO关联起来。

6.用webgl在页面上画一个多边形(https://www.jianshu.com/p/45482bde6583

① 从html中获取canvas对象

② 从canvas中获取webgl的context

③ 编译着色器

④ 准备模型数据

⑤ VBO生成合同制

⑥ 坐标变换矩阵的生成和通知

⑦ 发出绘图命令

⑧ 更新canvas并渲染

代码执行第1,2步:

<body>  
      <canvas id="canvas"></canvas>
      <!-- ※顶点着色器 -->
      <script id="vs" type="x-shader/x-vertex">  
           attribute vec3 position;  
           uniform   mat4 mvpMatrix;  
           void main(void){  
              gl_Position = mvpMatrix * vec4(position, 1.0);  
           }  
      </script>  
      <!-- ※片段着色器 -->  
      <script id="fs" type="x-shader/x-fragment">  
            void main(void){  
              gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);  
            }  
      </script>
      <script>
           //首先获取canvas对象并设置其大小
           var c = document.getElementById('canvas');  
           c.width = 500;  
           c.height = 300;
           // 从canvas中获取WebGL的context
           var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
      </script>    
  </body>  

编译着色器,调用WebGL内部的函数,准备一个函数,从着色器的编译,到实际着色器的生成这一连串的流程,都在这一个函数中来完成。

function create_shader(id){  
    // 用来保存着色器的变量  
    var shader;  
    // 根据id从HTML中获取指定的script标签  
    var scriptElement = document.getElementById(id);  
    // 如果指定的script标签不存在,则返回  
    if(!scriptElement){return;}  
    // 判断script标签的type属性  
    switch(scriptElement.type){  
        // 顶点着色器的时候  
        case 'x-shader/x-vertex':  
            shader = gl.createShader(gl.VERTEX_SHADER);  
            break;  
        // 片段着色器的时候  
        case 'x-shader/x-fragment':  
            shader = gl.createShader(gl.FRAGMENT_SHADER);  
            break;  
        default :  
            return;  
    }  
    // 将标签中的代码分配给生成的着色器  
    gl.shaderSource(shader, scriptElement.text);  
    // 编译着色器  
    gl.compileShader(shader);  
    // 判断一下着色器是否编译成功  
    if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){  
        // 编译成功,则返回着色器  
        return shader;  
    }else{
        // 编译失败,弹出错误消息  
        alert(gl.getShaderInfoLog(shader));  
    }  
}  
程序对象的生成和连接
       使用varying修饰符定义的变量,可以从顶点着色器向片段着色器中传递数据。其实,实现从一个着色器向另一个着色器传递数据的,不是别的,就是程序对象。程序对象是管理顶点着色器和片段着色器,或者WebGL程序和各个着色器之间进行数据的互相通信的重要的对象。
那么,生成程序对象,并把着色器传给程序对象,然后连接着色器,将这些处理函数化:
function create_program(vs, fs){  
    // 程序对象的生成  
    var program = gl.createProgram();  
    // 向程序对象里分配着色器  
    gl.attachShader(program, vs);  
    gl.attachShader(program, fs);  
    // 将着色器连接  
    gl.linkProgram(program);  
    // 判断着色器的连接是否成功  
    if(gl.getProgramParameter(program, gl.LINK_STATUS)){
        // 成功的话,将程序对象设置为有效  
        gl.useProgram(program);  
        // 返回程序对象  
        return program;  
    }else{
        // 如果失败,弹出错误信息  
        alert(gl.getProgramInfoLog(program));  
    }  
}  
VBO的生成
      生成VBO的时候使用WebGL的createBuffer函数,这个函数就是用来生成缓存的。但是这个函数并不是用来直接生成VBO的,它只是生成了一个缓存对象,根据它里面保存的内容不同,用途也是不用的。
要操作缓存,首先必须跟WebGL进行绑定,就是说,要向“缓存”这个“光盘”中写入数据的时候,必须连接到WebGL这个“光驱”上。
绑定了缓存之后,使用bufferData函数来向缓存中写入数据,把这些处理写成一个函数,就是下面这样:

function create_vbo(data){  
    // 生成缓存对象  
    var vbo = gl.createBuffer();  
    // 绑定缓存  
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    // 向缓存中写入数据  
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);    
    // 将绑定的缓存设为无效  
    gl.bindBuffer(gl.ARRAY_BUFFER, null); 
    // 返回生成的VBO  
    return vbo;  
}  

进行基本的3D渲染的时候,需要准备3个坐标变换矩阵:

模型变换矩阵、视图变换矩阵、投影变换矩阵

使用minMatrix.js等矩阵变换工具可以获得三个矩阵

 

6. WebGL、OpenGL、OpenGL ES、 Canvas的关系
WebGL 是基于 OpenGL ES 2.0 的 Javascript API,OpenGL ES 是 OpenGL 的子集,专门针对手机/PDA(掌上电脑,如: 条形扫码器,POS机等)/游戏主机等嵌入式设备设计,虽然 OpenGL ES 是 OpenGL 的子集,但是 OpenGL 与 OpenGL ES 还是有一点区别,比如他们的数据类型会存在一些不一样:
  1. OpenGL ES 没有 double 型(浮点)数据类型,而是加入了高性能的定点小数数据类型;

  2. OpenGL ES 没有 glBegin/glEnd/glVertex,只能用 glDrawArrays/glDraw......;

  3. 没有实时将非压缩图片数据转成压缩贴图的功能,程序必须直接提供压缩好的贴图;

  4. ...

OpenGL是最底层的,包含GLSL语言;OpenGL ES是专门针对嵌入式设备设计的,和OpenGL有一些区别;WebGL是基于OpenGL ES2.0的一个可以用在js语言中的3D图像API;Canvas是浏览器中最终呈现WebGL做的工作的地方;ThreeJS是对WebGL进行封装后,更加简便易于使用的3D图像JS库。

 

推荐阅读