创建着色程序

  1. 创建上下文

    1. let canvas = document.body.querySelector('canvas')
    2. let gl = canvas.getContext('webgl')
  2. 创建着色器

包含三个步骤

  • 源代码 用来给GLSL(GL着色语言使用)
  • opengl提供给js的着色器对象
  • 编译着色器

注意:由于我们使用glsl语言来生成着色器,在生成的过程中,着色器的属性就被确定了,因此如果我们设定的着色器的属性是常量,后期就不可以变化了
举个例子来说,我们给顶点着色器传入红色,那么他就只能是红色,这就是我们在顶点着色器的源代码中传入一个属性变量a_position的原因了,便于我们后期用js来获取变量来动态的修改

function makeShader(gl, type, source) {
    let shader = gl.createShader(type)//创建着色器对象
    gl.shaderSource(shader, source)//绑定着色器的代码
    gl.compileShader(shader)//编译着色器
}

let VSHADER_SOURCE =`
   precision mediump float;
   void main(){
     gl_FragColor=vec4(1,0,0.5,1);
   }`

let vShader = makeShader(gl, gl.VERTEX_SHADER, VSHADER_SOURCE)

let FSHADER_SOURCE = `
    attribute vec4 a_position;
    void main(){
      gl_Position=a_position;
    }`

let fShader = makeShader(gl, gl.FRAGMENT_SHADER, FSHADER_SOURCE)
  1. 在上下文中创建着色程序

一个着色程序匹配一对着色器,因此需要绑定着色器与着色程序,然后将着色程序与上下文连接起来

function makeProgram(gl, vShader, fShader) {
    let program = gl.createProgram()//创建程序
    gl.attachShader(program, vShader)//添加着色器
    gl.attachShader(program, fShader)
    gl.linkProgram(program)//连接程序和上下文
    return program;
}
let program = makeProgram(gl, vShader, fShader)

注意:着色器源码需要严格符合语法要求,类似一些强类型的语言,如果语法错误是生成不了着色器的,可以使用gl.getShaderInfoLog来获取相应的错误信息,也可以使用gl.getShaderParameter来获取shader是否生成成功

传输顶点数据

如前文的所言,我们使用glsl的语言生成着色器,因此我们需要按照glsl的语法定义、传入变量

glsl的变量

glsl的变量命名不能以gl webgl__ _webgl开头,这是语言使用了的关键字或者保留字

与其他语言类似,glsl中的变量包括int型、float型(32位)、double型(64位)、bool型等等
glsl中也有数组,但是需要使用数组的构造函数

float array[3]=float[3](1.2,2.4,3.5);

特别的,因为glsl用于GPU编程,与图形打交道,因此具有vec(float向量)、mat(float矩阵)类型的数据,在加上限定数据的前缀,构成了例如ivec(整形向量),bvec(布尔向量)的类型,在向量或者矩阵后加上数字表示数据的维度,例如ivec4(四维整形向量)
使用对应的构造函数来构造数据

vec3 xiangliang = vec3(1.0,2.0,3.0)
mat2 juzhen = mat2(1.0,1.0,2.0,2.0)
//需要注意的是矩阵构造函数传入的函数是按列的
//因此上面的矩阵是
//[1.0 2.0]
//[1.0 2.0]

还有很多数据结构、构造数据的方式(例如结构体、取样器等),这里只是简单的介绍一下

精度限定

在上文中就已经出现了
其中precision是精度的关键字,用来指定默认的精度
当变量没有指定精度时,会使用其他的变量的精度,如果有多个指定精度的变量,取高精度的变量计算,如果都没有指定精度,那么使用默认的精度计算

precision mediump float;

glsl的精度分为三种

  • highp
  • mediump
  • lowp

对于float变量的精度范围

  • highp (-2的62次方, 2的62次方);
  • mediump (-2的14次方, 2的14次方);
  • lowp (-2,2);

对于int变量的精度范围

  • highp (-2的16次方, 2的16次方);
  • mediump (-2的10次方, 2的10次方);
  • lowp (-2的8次方, 2的8次方);

因此相同的精度的int型可以转化成浮点型

变量限定

  • const 与js的const差不多,只读,必须初始化
  • attribute 属性,只能用于vertex shader,也就是顶点着色器,一般用于保存顶点和法线数据,可以在数据缓冲区中读取数据
  • uniform 在运行时shader无法改变uniform的变量,一般用来保存程序传递给shader的变换矩阵、材质、光照参数等
  • varying 主要负责顶点着色器向片段着色器传递数据

    内置变量

    顶点着色器变量
    gl_Posotion 输出变量,用来保存顶点着色器中的位置向量
    gl_PointSize 输出变量,一个float变量,用来设置点的高度(像素)
    gl_VertexID 输入变量,只读,用来保存点的索引或者顶点的数量
    片段着色器变量
    gl_FragCoord 输入变量,只读,保存的是片段的向量坐标,原点是窗口的左下角,为(0,0,0)可以调用x、y、z值,gl_FragCoord.x
    gl_FrontFacing 输入变量,只读,是一个bool值,true表示当前片段是正面向,false表示当前片段是背面向
    gl_FragDepth 输出变量,用来设置片段的深度,范围在0.0-1.0,默认是gl_FrontFacing的z分量的值,但是修改这个变量会有一定的风险

    缓冲区与数据

    找到之前定义的attribute属性
    var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
    
    因为attribute属性是从缓冲区获得数据,因此我们需要操作缓冲区
    let positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
    var positions = [
    0, 0,
    0, 0.5,
    0.7, 0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW)
    
  1. 创建缓冲区
  2. 将缓冲区绑定到webgl对象上,有两种类型的,ARRAY_BUFFER和ELEMENT_ARRAY_BUFFER
  3. 创建顶点数据
  4. webgl要求强类型的数据,因此将数据转化成32位的浮点数据,然后指明这个缓冲区的数据使用情况,为STATIC_DRAW,代表缓冲区的数据在之后的使用中不会变化,另外有STREAM_DRAW,和DYNAMIC_DRAW,分别代表小幅变化大幅变化,webgl会根据数据的使用情况进行优化

    将数据绑定到gl.ARRAY_BUFFER这个绑定点上,就形成了缓冲区-绑定点-数据的连线,这样取数据就是从对应的缓冲区,就算之后更改数据,缓冲区是不会更改的。

    渲染

    画布的大小

    canvas本身的width和height是实际的像素,另外js设置的大小也是实际的像素
    使用css控制的是显示的像素大小
    因此下面的canvas实际上是放大后的内容,即内容会变模糊

    <canvas width="300" height="300" style="height:600px; width:600px;"></canvas>
    

    屏幕空间的渲染

    webgl的裁剪空间是 -1=>+1,其中0,0就是canvas的中心
    没有实际的大小,因此我们需要告知webgl实际的画布大小

    gl.viewport(0,0,gl.canvas.width,gl.canvas.height)
    

    这句话的意思就是 -1=>+1分别对应-canvas.width=>+canvas.width
    第一次创建WebGL上下文的时候WebGL会设置视域大小和画布大小匹配, 但是在那之后就需要你自己设置。当你改变画布大小就需要告诉WebGL新的视域设置

    画布的颜色

    webgl以状态机的方式进行工作,因此在渲染之前需要设置好对应的参数

    gl.clearColor(1, 0.5, 0, 0.5);
    gl.clear(gl.COLOR_BUFFER_BIT);
    

    第一行用于clear画布的颜色,范围是0-1,对应rgba
    第二行用于清空颜色的缓冲区,其他还有DEPTH_BUFFER_BIT、STENCIL_BUFFER_BIT,分别是深度缓冲区以及模板缓冲区

    使用着色程序

    通知webgl使用我们写好的一个着色器对生成的着色程序

    gl.useProgram(program);
    

    指定数据的读取方式

    // 告诉属性怎么从positionBuffer中读取数据 (ARRAY_BUFFER)
    var size = 2;          // 每次迭代运行提取两个单位数据
    var type = gl.FLOAT;   // 每个单位的数据类型是32位浮点型
    var normalize = false; // 不需要归一化数据
    var stride = 0;        // 0 = 移动单位数量 * 每个单位占用内存(sizeof(type))
                        // 每次迭代运行运动多少内存到下一个数据开始点
    var offset = 0;        // 从缓冲起始位置开始读取
    gl.vertexAttribPointer(
     positionAttributeLocation, size, type, normalize, stride, offset)
    

    数据在