JavaScript WebGL 绘制一条直线
引子
- 创建 WebGL 上下文
- 准备顶点数据并缓冲
- 顶点着色器
- 片段着色器
- 着色器程序
- 绘制
WebGL 基础概念,做一个绘制直线的简单示例。
主要参考以下两篇文章:
-
绘制一个点
-
绘制三角形
-
Origin
-
My GitHub
基础概念中有提过是通过 Canvas 元素使用 WebGL :
const canvasObj = document.querySelector("#demo");
const glContext = canvasObj.getContext("webgl");
if (!glContext) {
alert("浏览器不支持 WebGL");
return;
}
const canvasObj = document.querySelector("#demo");
const glContext = canvasObj.getContext("webgl");
if (!glContext) {
alert("浏览器不支持 WebGL");
return;
}
接着准备顶点数据。
bufferData 方法会把数据复制到当前绑定缓冲对象,该方法提供了管理给定数据的参数:
- STATIC_DRAW : 缓冲区的内容可能经常使用,不会经常更改。
- DYNAMIC_DRAW : 缓冲区的内容可能经常被使用,并且经常更改。
- STREAM_DRAW : 缓冲区的内容可能不会经常使用。
直线的数据不会改变,每次渲染都保持不变,所以这里使用的类型是 STATIC_DRAW 。现在已经把顶点数据储存在显卡的内存中,接着开始准备顶点着色器。
GLSL ES 语言编写,在前端书写形式有两种:
- script 标签包裹,使用时像获取 DOM 对象一样。
- 纯字符串。
每个顶点都有一个 3D 坐标,创建了一个 vec3 类型输入变量 vertexPos ,vec3 表示三元组浮点数向量。
main 是入口函数,gl_Position 是着色器内置的变量,GLSL 中一个变量最多 4 个分量,最后一个分量是用在透视除法上。gl_Position 设置的值会成为该顶点着色器的输出。这里请回想一下基础概念中提到的状态机。
下面是纯字符形式:
/**
* 创建顶点着色器
* @param {*} gl WebGL 上下文
*/
function createVertexShader(gl) {
// 顶点着色器 glsl 代码
const source = `
attribute vec3 vertexPos;
void main(void){
gl_Position = vec4(vertexPos, 1);
}
`;
// 创建着色器
const shader = gl.createShader(gl.VERTEX_SHADER);
// 设置顶点着色器代码
gl.shaderSource(shader, source);
// 编译
gl.compileShader(shader);
// 判断是否编译成功
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("编译着色器报错: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
为了让 WebGL 使用该着色器,必须在运行时动态编译它的源代码。
- createShader 函数创建类型为
gl.VERTEX_SHADER的着色器对象; - compileShader 函数进行编译。
接着准备片段着色器。
Back to topGLSL ES 语言编写。片段着色器所做的是计算像素最后的颜色输出,这里直接简化指定输出白色。gl_FragColor 是内置变量,表示颜色,4 个分量分别对应 R、G、B、A。
/**
* 创建片段着色器
* @param {*} gl WebGL 上下文
*/
function createFragmentShader(gl) {
// 片段着色器 glsl 代码
const source = `
void main(void){
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`;
// 创建着色器
const shader = gl.createShader(gl.FRAGMENT_SHADER);
// 设置片段着色器代码
gl.shaderSource(shader, source);
// 编译
gl.compileShader(shader);
// 判断是否编译成功
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("编译着色器报错: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
},
/**
* 创建片段着色器
* @param {*} gl WebGL 上下文
*/
function createFragmentShader(gl) {
// 片段着色器 glsl 代码
const source = `
void main(void){
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`;
// 创建着色器
const shader = gl.createShader(gl.FRAGMENT_SHADER);
// 设置片段着色器代码
gl.shaderSource(shader, source);
// 编译
gl.compileShader(shader);
// 判断是否编译成功
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("编译着色器报错: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
},
两个着色器都准备好后,需要进行链接合并才能使用。
createProgram 函数创建对象;
当需要激活这个着色器的时候,把该对象作为参数调用 useProgram 函数。
/**
* 初始化着色器程序
* @param {*} gl WebGL 上下文
* @param {*} vertexShader 顶点着色器
* @param {*} fragmentShader 片段着色器
*/
function initShaderProgram(gl, vertexShader, fragmentShader) {
// 创建着色器对象
const shaderProgram = gl.createProgram();
// 添加着色器
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
// 多个着色器合并链接
gl.linkProgram(shaderProgram);
// 创建是否成功检查
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("无法初始化着色器程序: " + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
目前为止,已经把输入顶点数据发送给了 GPU ,并指示了 GPU 如何在顶点和片段着色器中处理它。最后就剩下绘制了。
vertexAttribPointer 函数告诉 WebGL 如何解释顶点数据;
useProgram 函数激活着色器;gl.LINE_STRIP 。 /**
* 初始化着色器程序
* @param {*} gl WebGL 上下文
* @param {*} shaderProgram 着色器程序对象
*/
function draw(gl, shaderProgram) {
// 获取对应数据索引
const vertexPos = gl.getAttribLocation(shaderProgram, "vertexPos");
// 解析顶点数据
gl.vertexAttribPointer(vertexPos, 3, gl.FLOAT, false, 0, 0);
// 启用顶点属性,顶点属性默认是禁用的。
gl.enableVertexAttribArray(vertexPos);
// 激活着色器
gl.useProgram(shaderProgram);
// 绘制
gl.drawArrays(gl.LINE_STRIP, 0, 2);
}
示例,整体的逻辑大概是这样的:
const canvasObj = document.querySelector("#demo");
const glContext = canvasObj.getContext("webgl");
let vertices = [-0.5, -0.5, 0.0, 0.5, -0.5, 0.0]; // 顶点数据
setBuffers(glContext, vertices); // 缓冲数据
const vertexShader = createVertexShader(glContext); // 顶点着色器
const fragmentShader = createFragmentShader(glContext); // 片段着色器
const shaderProgram = initShaderProgram(
glContext,
vertexShader,
fragmentShader
); // 着色器程序对象
draw(glContext, shaderProgram); // 绘制
const canvasObj = document.querySelector("#demo");
const glContext = canvasObj.getContext("webgl");
let vertices = [-0.5, -0.5, 0.0, 0.5, -0.5, 0.0]; // 顶点数据
setBuffers(glContext, vertices); // 缓冲数据
const vertexShader = createVertexShader(glContext); // 顶点着色器
const fragmentShader = createFragmentShader(glContext); // 片段着色器
const shaderProgram = initShaderProgram(
glContext,
vertexShader,
fragmentShader
); // 着色器程序对象
draw(glContext, shaderProgram); // 绘制
这里面涉及很多的方法和变量,一开始的时候真的懵,多看几次亲自敲下代码后会慢慢习惯。
接下来会对期间产生的一些疑问进行总结,见 JavaScript WebGL 基础疑惑点。
Back to top