MDN上关于WebGL2的介绍:
WebGL2RenderingContext 接口在底层使用了 OpenGL ES 3.0 为 HTML 的 <canvas>
元素提供了绘图上下文。
要获取该接口的实例对象需要调用一个 <canvas> 标签对象的 getContext()
函数,并将 "webgl2" 作为参数传递
WebGL的基本流程:
1、获取canvas
想要在页面中通过WebGL2进行绘制,需要先获取到canvas对象:
var canvas = document.querySelector("#webgl-container");
2、将webgl上下文指定为 ‘webgl2’
var gl = canvas.getContext("webgl2");
3、创建顶点着色器和片元着色器,其中着色器的创建经过以下流程
- 初始化shader:
var shader = gl.createShader(type)
- 设置shader的源:
gl.shaderSource(shader,source)
- 编译shader成二进制数据,从而为program所用:
gl.compileShader(shader)
创建着色器的代码片段如下:
var vertexShaderSource = `#version 300 es
in vec4 a_position;
in vec4 a_position1;
void main() {
gl_PointSize = 10.0;
vec4 new_pos = a_position + a_position1;
gl_Position = new_pos;
}
`;
var fragmentShaderSource = `#version 300 es
precision highp float;
out vec4 outColor;
void main() {
outColor = vec4(1, 0, 0.5, 1);
}
`;
function createShader(gl, type, source) {
var shader = gl.createShader(type); //初始化shader
gl.shaderSource(shader, source); //设置shader的源
gl.compileShader(shader); //编译shader成二进制数据,从而为program所用
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); //获取编译结果,如果为true则返回编译完成的shader
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader)); // eslint-disable-line
gl.deleteShader(shader);
return undefined;
}
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);//调用函数创建顶点着色器
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);//调用函数创建片元着色器
4、 创建 WebGL2 程序对象 program ,程序的创建经过以下流程
- 初始化program:
var program = gl.createProgram()
- 将顶点着色器和片元着色器附加到程序上:
gl.attachShader(program, vertexShader)
、gl.attachShader(program, fragmentShader)
; - 将两个着色器组合成一个可以在GPU上执行的程序:
gl.linkProgram(program)
创建 WebGL2 程序对象的代码片段如下:
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram(); //初始化program
gl.attachShader(program, vertexShader); //将顶点着色器添加到program
gl.attachShader(program, fragmentShader); //将片元着色器添加到program
gl.linkProgram(program); //链接program
var success = gl.getProgramParameter(program, gl.LINK_STATUS); //获取链接结果,如果为true则返回链接完成的program
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program)); // eslint-disable-line
gl.deleteProgram(program);
return undefined;
}
var program = createProgram(gl, vertexShader, fragmentShader); //调用函数创建着色器程序
gl.useProgram(program); //使用着色器程序
5、获取指定的属性变量(attribute variable)在指定的着色器程序(shader program)中的位置:
gl.getAttribLocation(program, "a_position");
这个api 返回一个索引(0,1,2,3....),通过在下列步骤中使用这些索引将顶点数据绑定到这些变量。
6、创建缓冲区并绑定数据:
var size = 2; //指定每次从数组中拿几个值
var type = gl.FLOAT; // 指定数据类型
var normalize = false; // 不进行归一化
var stride = 2 * Float32Array.BYTES_PER_ELEMENT; size* 类型中每个元素所占字节
var offset = 0; // 从缓冲区的哪个位置开始取数据
var positions = [0.5, 0, 0.1, 0];
var positionBuffer = gl.createBuffer();//创建缓冲区
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");//获取顶点着色器中a_position的位置,如果不存在,则返回-1
gl.enableVertexAttribArray(positionAttributeLocation);//启用顶点属性数组
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);//将缓冲区绑定到WebGL2的ARRAY_BUFFER 上,后续的操作将会作用在这个缓冲区上
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);//将数据绑定到缓冲区
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);//绑定顶点着色器中的属性
7、(非必要)重设画布大小,并告诉webgl如何从剪辑空间转换值转换到屏幕空间
8、进行绘制:
//清空画布
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
//进行绘制
var primitiveType = gl.POINTS; //绘制点
var offset = 0; // 从第0个开始
var count = 2; // 绘制两次
gl.drawArrays(primitiveType, offset, count);
绘制两个点的完整代码(在vue3 script setup下):
<script setup>
import {onMounted} from "vue";
import vertexShaderSource from "../glsl/point/vertex.glsl";
import fragmentShaderSource from "../glsl/point/fragment.glsl";
function createShader(gl, type, source) {
var shader = gl.createShader(type); //初始化shader
gl.shaderSource(shader, source); //设置shader的源
gl.compileShader(shader); //编译shader成二进制数据,从而为program所用
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); //获取编译结果,如果为true则返回编译完成的shader
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader)); // eslint-disable-line
gl.deleteShader(shader);
return undefined;
}
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram(); //初始化program
gl.attachShader(program, vertexShader); //将顶点着色器添加到program
gl.attachShader(program, fragmentShader); //将片元着色器添加到program
gl.linkProgram(program); //链接program
var success = gl.getProgramParameter(program, gl.LINK_STATUS); //获取链接结果,如果为true则返回链接完成的program
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program)); // eslint-disable-line
gl.deleteProgram(program);
return undefined;
}
function main() {
// Get A WebGL context
var canvas = document.querySelector("#webgl-container");
var gl = canvas.getContext("webgl2");
if (!gl) {
return;
}
// create GLSL shaders, upload the GLSL source, compile the shaders
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);//调用函数创建顶点着色器
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);//调用函数创建片元着色器
var program = createProgram(gl, vertexShader, fragmentShader); //调用函数创建着色器程序
gl.useProgram(program); //使用着色器程序
//设置常量
var size = 2; //指定每次从数组中拿几个值
var type = gl.FLOAT; // 指定数据类型
var normalize = false; // 不进行归一化
var stride = 2 * Float32Array.BYTES_PER_ELEMENT; //size* 类型中每个元素所占字节
var offset = 0; // 从缓冲区的哪个位置开始取数据
var positions = [0.5, 0, 0.1, 0];
// 将数据绑定到缓冲区
var positionBuffer = gl.createBuffer();//创建缓冲区
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");//获取顶点着色器中的属性
gl.enableVertexAttribArray(positionAttributeLocation);//绑定顶点着色器中的属性
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);//将数据绑定到缓冲区
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);//将数据绑定到缓冲区
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);//绑定顶点着色器中的属性
resizeCanvasToDisplaySize(gl.canvas); //重设画布大小
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// draw
var primitiveType = gl.POINTS;
var offset = 0;
var count = 2;
gl.drawArrays(primitiveType, offset, count);
}
function resizeCanvasToDisplaySize(canvas, multiplier) { //重设画布大小的函数
multiplier = multiplier || 1;
const width = canvas.clientWidth * multiplier | 0;
const height = canvas.clientHeight * multiplier | 0;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
return true;
}
return false;
}
onMounted(() => {
main();
});
</script>
<template>
<div class="box">
<canvas id="webgl-container"></canvas>
</div>
</template>
<style scoped>
#webgl-container{
width: 100%;
height: 100%;
background-color: #FFFFFF;
}
.box {
width: 480px;
height: 360px;
border: 1px solid red;
}
</style>
顶点着色器代码:
#version 300 es
in vec4 a_position;
void main() {
gl_PointSize = 10.0;
gl_Position = a_position;
}
片元着色器代码:
#version 300 es
precision highp float;
out vec4 outColor;
void main() {
outColor = vec4(1, 0, 0.5, 1);
}
}