创建着色程序
创建上下文
let canvas = document.body.querySelector('canvas')let gl = canvas.getContext('webgl')
创建着色器
包含三个步骤
- 源代码 用来给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)
- 在上下文中创建着色程序
一个着色程序匹配一对着色器,因此需要绑定着色器与着色程序,然后将着色程序与上下文连接起来
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次方);
变量限定
- 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属性
因为attribute属性是从缓冲区获得数据,因此我们需要操作缓冲区var positionAttributeLocation = gl.getAttribLocation(program, "a_position");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)
- 创建缓冲区
- 将缓冲区绑定到webgl对象上,有两种类型的,ARRAY_BUFFER和ELEMENT_ARRAY_BUFFER
- 创建顶点数据
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)数据在
