背景
最近工作上频繁接触webgl,因为不熟悉每每看到shader中的语法总感觉脑袋大,所以打算开始从零学习一下webgl,文章只做记录学习历程,那就直接开始吧!
开始
可以配合着这个文章食用。
我还是对webgl有一些概念的,说一下我的理解就是用来对图元进行光栅化的程序,首先说光栅化是什么,我的理解是将看到的东西画到屏幕上,那这个过程会经历什么比如画一个三角形,首先我要确定三角形画在哪里也就是顶点的位置,然后确认顶点后填充颜色,颜色其实就是一个个像素组成的,gpu做的就是将一个个gpu渲染上颜色这个过程叫光栅化。
ok,总结一下,一个webgl着色器程序,它需要有两部分组成:
- 顶点着色器(VertexShader)
- 片元着色器(FragmentShader)
好,以上两个着色器即可组成一个着色器程序。
代码实践
着色器创建流程
- 首先获取webgl的上下文
- 编写顶点和片元着色器代码
- 实例创建顶点和片元shader
- 创建着色器程序Program,并注入两个shader
下面代码不包含glsl代码
function initWebgl() {
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
// 平面空间大小
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 清空画布
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
return gl;
}
const gl = initWebgl();
// 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
function createShader(gl, type, source) {
const shader = gl.createShader(type); // 创建着色器对象
gl.shaderSource(shader, source); // 提供数据源
gl.compileShader(shader); // 编译 -> 生成着色器
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
const vertexShaderSource = document.querySelector('#vertex-shader-2d').text;
const fragmentShaderSource = document.querySelector('#fragment-shader-2d').text;
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建着色器程序
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// 着色器创建成功
const program = createProgram(gl, vertexShader, fragmentShader);
至此准备工作已做好
数据传递流程
现在我想画一个三角形就需要用到顶点着色器,传递给它三个顶点的位置
- 首先先寻找要赋值的顶点着色器变量
- 创建一个缓冲区
- 再创建一个buffer,绑定在缓冲区中
- 将数据写入buffer
- enableVertexAttribArray:开启属性,这时变量和缓冲区建立好链接了
- vertexAttribPointer:规定数据如何读取
代码体现
// 找出顶点着色器中a_position变量
// 这要注意:寻找属性值位置(和全局属性位置)应该在初始化的时候完成,而不是在渲染循环中。
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
// 创建一个缓冲
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 三个顶点坐标
const positions = [0, 0, 0, 0.5, 0.7, 0];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 告诉gl是用那个着色器程序
gl.useProgram(program);
// 启用属性
gl.enableVertexAttribArray(positionAttributeLocation);
// 告诉属性怎么从positionBuffer中读取数据 (ARRAY_BUFFER)
const size = 2; // 每次迭代运行提取两个单位数据
const type = gl.FLOAT; // 每个单位的数据类型是32位浮点型
const normalize = false; // 不需要归一化数据
const stride = 0; // 0 = 移动单位数量 * 每个单位占用内存(sizeof(type))
// 每次迭代运行运动多少内存到下一个数据开始点
const offset = 0; // 从缓冲起始位置开始读取
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
附一些语法规则定义:
开始绘制
需要制定绘制规则,因为gl不知道你是要画三个点还是一个三角形,还有程序执行次数。
代码体现
// 设置图元类型为三角形
const primitiveType = gl.TRIANGLES;
const drawArraysOffset = 0;
// 程序执行次数
const count = 3;
gl.drawArrays(primitiveType, drawArraysOffset, count);
在顶点绘制完成后,gl能够知道哪些像素是需要通过片元着色器进行上色的,至此基础的三角形绘制完毕!
ok,总结一下
一个基础的着色器程序主要流程是:
- 创建webgl上下文
- 创建顶点着色器和片元着色器组成一个完整的着色器程序
- 数据传递,今天的demo只进行了顶点数据传递
- 执行着色器程序(可配置执行规则)
本人对数据传递有些模糊,特画了个流程图清晰一下
附文章:
https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-fundamentals.html