WebGL是通过OpenGL ES在HTML的<canvas></canvas>上渲染图形的。
...
<canvas id="main-canvas"></canvas>
...
let gl = document.getElementById("main-canvas").getContext("webgl");
if (!gl) {
alert("无法使用WebGL!");
} else {
console.log(gl);
}
这样,就获取到了在canvas上渲染webgl的上下文。
接下来,在canvas上一切的渲染操作,最终都是要依靠这个gl变量来控制的。
不过在此之前,需要作一下前期准备。
着色器准备
首先要准备一下着色器的源码,着色器(shader)是由GLSL(OpenGL Shadering Language,一种类C++语言)编写的计算机程序。着色器是成对出现的,分别是顶点着色器:
<script type="notjs" id="vertex-shader-2d">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
和片段着色器:
<script type="notjs" id="fragment-shader-2d">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0.3, 0.5, 1);
}
</script>
顶点着色器接受一些输入,输出的是裁剪平面的顶点坐标。
因为最终在canvas上渲染图形还是要使用JavaScript而不是GLSL,所以需要获取这些GLSL代码,然后编译处理成JavaScript的对象。所以:
// 获取着色器源码
const vertex_shader_source = document.querySelector("#vertex-shader-2d").text,
fragment_shader_source = document.querySelector("#fragment-shader-2d").text;
然后根据这两个源码,生成一个着色器程序的js对象:
// 根据源码创建着色程序
function createProgram(gl, vertexShaderSource, fragmentShaderSource) {
let vertex_shader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource),
fragment_shader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
let program = gl.createProgram();
gl.attachShader(program, vertex_shader);
gl.attachShader(program, fragment_shader);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
然后调用该函数,program变量就是包含了这一对着色器源码的js对象:
let program = createProgram(gl, vertex_shader_source, fragment_shader_source);
因为在着色器中有一个输入值,即顶点着色器中的a_position,着色器接收输入值必须从缓冲区中获取,而获取到的值需要在着色器中的正确位置放入,所以要找到顶点着色器中a_position的位置:
let position_attribute_location = gl.getAttribLocation(program, "a_position");
缓冲区准备
着色器设置好了之后,就要开始准备数据,作为着色器的输入了。
上文说过,作为着色器的输入数据,必须是在缓冲区中的数据。而现在没有缓冲区,也没有数据,所以接下来创建这两个东西,然后把数据放入缓冲区中。
先创建缓冲区:
let position_buffer = gl.createBuffer();
然后使用ARRAY_BUFFER绑定点,绑定到这个position_buffer(使用ARRAY_BUFFER说明要用顶点数组方式绘图):
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
缓冲区准备好了,接下来准备数据:
let positions = [
0, 0,
0, 0.2,
0.7, 0,
-0.5, -0.5,
-0.4, -0.2,
-0.7, 0,
];
然后通过ARRAY_BUFFER绑定点,将数据放到缓冲区中:
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
准备画布
由于WebGL的裁剪空间是一个横纵坐标都是在[-1, 1]范围内的矩形,而我们的画布的尺寸一般不是这个,所以需要作一下尺寸上的映射:
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
然后清空一下画布:
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
渲染
要开始渲染了。首先我们要指定着色器程序:
gl.useProgram(program);
然后启用a_position这个输入值(属性):
gl.enableVertexAttribArray(position_attribute_location);
之后准备将缓冲区的数据输送到着色器:
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(position_attribute_location, size, type, normalize, stride, offset);
对这里参数的说明:
- size=2表示每次迭代读取两个数据,即x和y。由于顶点着色器中gl_Position的类型是vec4,包含x,y,z,w四个数据,而这里只需要前两个x和y。
- type=gl_FLOAT表示使用的是32为浮点类型的数据。
- normalize=false表示不需要归一化数据。
- offset=0表示从缓冲区的开始位置读取数据。
最后绘制:
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
结果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>GLSL-test</title>
</head>
<body>
<canvas id="main-canvas"></canvas>
<!-- 顶点着色器 -->
<script type="notjs" id="vertex-shader-2d">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
<!-- 片段着色器 -->
<script type="notjs" id="fragment-shader-2d">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0.3, 0.5, 1);
}
</script>
<script>
// 创建着色器
function createShader(gl, type, source) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// 根据源码创建着色程序
function createProgram(gl, vertexShaderSource, fragmentShaderSource) {
let vertex_shader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource),
fragment_shader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
let program = gl.createProgram();
gl.attachShader(program, vertex_shader);
gl.attachShader(program, fragment_shader);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
function main() {
// 使用Canvas
let main_canvas = document.querySelector("#main-canvas");
main_canvas.setAttribute("width", "640");
main_canvas.setAttribute("height", "640");
let gl = main_canvas.getContext("webgl");
if (!gl) {
alert("无法使用WebGL!");
} else {
console.log(gl);
}
// 获取着色器源码
const vertex_shader_source = document.querySelector("#vertex-shader-2d").text,
fragment_shader_source = document.querySelector("#fragment-shader-2d").text;
// 在GPU中创建着色程序
let program = createProgram(gl, vertex_shader_source, fragment_shader_source);
// 着色程序输入值的位置
let position_attribute_location = gl.getAttribLocation(program, "a_position");
// 创建缓冲区,并绑定到ARRAY_BUFFER绑定点
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
// 向绑定点指向的缓冲区中存放数据
let positions = [
0, 0,
-0.3, 0.2,
0.2, 0.5,
-0.5, -0.5,
-0.4, -0.2,
-0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 准备画布
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // 将裁减空间与canvas尺寸对应
gl.clearColor(0, 0, 0, 0); // 清空画布
gl.clear(gl.COLOR_BUFFER_BIT);
// 使用准备好的着色器程序
gl.useProgram(program);
// 启用位置属性
gl.enableVertexAttribArray(position_attribute_location);
// 指定从缓冲中读取数据的方式
gl.vertexAttribPointer(position_attribute_location, 2, gl.FLOAT, false, 0, 0);
// 执行绘制
gl.drawArrays(gl.TRIANGLES, 0, 6); // 根据ARRAY_BUFFER缓冲区的数据绘制内容
}
main();
</script>
</body>
</html>
参考
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API/Tutorial
来源:CSDN
作者:ValdisW
链接:https://blog.csdn.net/sinat_36461778/article/details/104079929