我们新建一个 html 文件然后使用 chrome 浏览器来绘制简易图像。
最终生成的调用方式如下:
glUnlink();
glInit({
vertexShaderCode: `
attribute vec2 a_pos;
uniform vec2 resol;
void main(){
// 坐标转换
gl_Position = vec4((a_pos / resol * 2.0 - 1.0) * vec2(1,-1), 0 , 1);
}
`,
fragmentShaderCode: `
void main(){
gl_FragColor = vec4(0,1,0,1);
}
`,
varArr: { a_pos: [20, 120, 20, 100, 100, 100, 100, 120] },
uniformArr: { resol: [600, 400] },
});
glDrawArrays("TRIANGLE_FAN", 0, 4);
这样我们只需要专心处理 shader 和把 shader 要的数据交待一下就能看到效果,方便我们快速测试和学习 webgl 。
这里将以像素为单位的 canvas 坐标系转换成 [-1,1] 的投影区间,坐标原点在左上角,因为所有 css 的定位坐标系 y 轴也是向下的,因此如果要转换成投影矩阵正常的 y 轴向上的坐标系,需要 乘以 vec2(1,-1)。
第一步:新建一个 canvas 元素,获取 webgl 的运行上下文,这里没做各种兼容处理,准备导出要使用的方法
<body>
<canvas width="600" height="400" id="cs"></canvas>
<script>
const { glInit, glDrawArrays, glUnlink } = (() => {
const canvas = document.getElementById("cs");
const gl = canvas.getContext("experimental-webgl");
let vertexShader = "";
let fragmentShader = "";
// 重置画布
let _program = null;
let getProgram = () => {
if (!_program) {
_program = gl.createProgram();
}
return _program;
};
let delProgram = () => {
if (_program) {
gl.deleteProgram(_program);
}
_program = null;
};
第二步:shader 中变量访问地址和内存地址的绑定
// 给 顶点shader 的 vec2 变量绑定 顶点信息buffer 的内存地址并规定读取方式
const injectV2Attribute = (varConfig) => {
for (let varname in varConfig) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(varConfig[varname]),
gl.STATIC_DRAW
);
// 拿到固定的shader变量内存地址
const varPosition = gl.getAttribLocation(getProgram(), varname);
// 允许shader读buffer
gl.enableVertexAttribArray(varPosition);
// 规定读取buffer的方式
gl.vertexAttribPointer(varPosition, 2, gl.FLOAT, false, 0, 0);
}
};
// 给 顶点shader 的 vec2 常量 找到 内存中此值的地址
const injectV2Uniform = (uniformConfig) => {
for (let uname in uniformConfig) {
const varPosition = gl.getUniformLocation(getProgram(), uname);
gl.uniform2f(varPosition, ...uniformConfig[uname]);
}
};
这里学习下 gl.vertexAttribPointer 用来规定读取数据的方式:
参数1:变量地址
参数2:顶点组成部分,这里因为是画二维图像,因此给 shader x 和 y 坐标就行,z 轴坐标为 0,最后摄像机距离默认 1 表示比例
参数3:Float32Array 和 gl.FLOAT 遥相呼应,使用 4 字节存储
参数4:可以简单理解 true 的话顶点存储占更多字节数,精度高,false 可能会只用 单字节 存值,比如颜色这种上限 255 的,具体还是跟传入的实际数据有关
参数5:每隔多个个存储单位取下一个值,0 相当于值是紧密排列
参数6:取值偏移,结合参数5 可以精确间隔取值
第三步:通用初始化封装
// 编译shader,报错则打印日志
function createShader(gl, sourceCode, type) {
var shader = gl.createShader(type);
gl.shaderSource(shader, sourceCode);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var info = gl.getShaderInfoLog(shader);
throw "Could not compile WebGL program. \n\n" + info;
}
return shader;
}
return {
glInit({ vertexShaderCode, fragmentShaderCode, varArr, uniformArr }) {
// shader
vertexShader = createShader(gl, vertexShaderCode, gl.VERTEX_SHADER);
fragmentShader = createShader(
gl,
fragmentShaderCode,
gl.FRAGMENT_SHADER
);
// 启动流程
gl.attachShader(getProgram(), vertexShader);
gl.attachShader(getProgram(), fragmentShader);
gl.linkProgram(getProgram());
gl.useProgram(getProgram());
// 日志
if (!gl.getProgramParameter(getProgram(), gl.LINK_STATUS)) {
var info = gl.getProgramInfoLog(getProgram());
throw "Could not compile WebGL program. \n\n" + info;
}
varArr && injectV2Attribute(varArr);
uniformArr && injectV2Uniform(uniformArr);
},
glDrawArrays(...args) {
gl.drawArrays(gl[args[0]], ...args.slice(1));
},
glUnlink() {
delProgram();
},
};
})();
这里学习下 gl.drawArrays 的第一个参数连三角形时的模式,共有三种:
第一种 gl.TRIANGLES:即每 3 个点画一个三角网格
后面两种画网格方式可以减少需要存储顶点数量,量级从 3*N 变为 N + 2。
第二种 gl.TRIANGLE_STRIP:strip 翻译过来就是彩带,因为三角形连成的形状是长条状
第三种 gl.TRIANGLE_FAN:fan 翻译过来就是扇子,因为三角形连成形状近似蒲扇
有了上面的简单封装画三角网格就会方便很多!