深入解析evanw/glfx.js中的Shader核心模块
前言
在WebGL图像处理库evanw/glfx.js中,Shader模块是整个库的核心组件之一,负责处理WebGL着色器的创建、编译、链接以及渲染过程。本文将深入剖析Shader模块的实现原理和使用方法,帮助读者理解WebGL着色器在图像处理中的关键作用。
Shader模块概述
Shader模块是一个自执行函数表达式(IIFE),返回一个Shader构造函数。它封装了WebGL着色器相关的操作,包括:
- 着色器编译与链接
- uniform变量设置
- 纹理绑定
- 矩形绘制
核心功能解析
1. 类型检查工具函数
模块内部提供了两个简单的类型检查函数:
function isArray(obj) {
return Object.prototype.toString.call(obj) == '[object Array]';
}
function isNumber(obj) {
return Object.prototype.toString.call(obj) == '[object Number]';
}
这些函数用于在设置uniform变量时判断参数类型,确保传入正确的数据类型。
2. 着色器编译
compileSource
函数负责编译GLSL着色器源代码:
function compileSource(type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw 'compile error: ' + gl.getShaderInfoLog(shader);
}
return shader;
}
该函数接收着色器类型(VERTEX_SHADER或FRAGMENT_SHADER)和源代码字符串,返回编译后的着色器对象。如果编译失败,会抛出错误信息。
3. 默认着色器源代码
模块中预置了默认的顶点着色器和片段着色器源代码:
顶点着色器(defaultVertexSource):
attribute vec2 vertex;
attribute vec2 _texCoord;
varying vec2 texCoord;
void main() {
texCoord = _texCoord;
gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
}
片段着色器(defaultFragmentSource):
uniform sampler2D texture;
varying vec2 texCoord;
void main() {
gl_FragColor = texture2D(texture, texCoord);
}
这些默认着色器实现了一个简单的纹理映射功能,当用户不提供自定义着色器时使用。
4. Shader构造函数
Shader构造函数接收可选的顶点和片段着色器源代码:
function Shader(vertexSource, fragmentSource) {
this.vertexAttribute = null;
this.texCoordAttribute = null;
this.program = gl.createProgram();
vertexSource = vertexSource || defaultVertexSource;
fragmentSource = fragmentSource || defaultFragmentSource;
fragmentSource = 'precision highp float;' + fragmentSource; // 添加精度限定符
gl.attachShader(this.program, compileSource(gl.VERTEX_SHADER, vertexSource));
gl.attachShader(this.program, compileSource(gl.FRAGMENT_SHADER, fragmentSource));
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
throw 'link error: ' + gl.getProgramInfoLog(this.program);
}
}
值得注意的是,构造函数会自动为片段着色器添加precision highp float;
精度限定符,这是WebGL的要求。
5. Uniform变量设置
uniforms
方法用于设置着色器的uniform变量:
Shader.prototype.uniforms = function(uniforms) {
gl.useProgram(this.program);
for (var name in uniforms) {
// ... 参数检查 ...
var value = uniforms[name];
if (isArray(value)) {
// 处理数组类型的uniform
switch (value.length) {
case 1: gl.uniform1fv(location, new Float32Array(value)); break;
case 2: gl.uniform2fv(location, new Float32Array(value)); break;
// ... 其他长度处理 ...
}
} else if (isNumber(value)) {
gl.uniform1f(location, value);
} else {
throw 'attempted to set uniform "' + name + '" to invalid value';
}
}
return this;
};
该方法支持设置各种类型的uniform变量,包括:
- 标量(float)
- 向量(float[1-4])
- 矩阵(float[9], float[16])
6. 纹理绑定
textures
方法专门用于绑定纹理:
Shader.prototype.textures = function(textures) {
gl.useProgram(this.program);
for (var name in textures) {
gl.uniform1i(gl.getUniformLocation(this.program, name), textures[name]);
}
return this;
};
由于纹理需要使用gl.uniform1i
而不是gl.uniform1f
来设置,所以单独提供了这个方法。
7. 绘制矩形
drawRect
方法实现了矩形绘制功能:
Shader.prototype.drawRect = function(left, top, right, bottom) {
// 坐标归一化处理
var viewport = gl.getParameter(gl.VIEWPORT);
top = top !== undefined ? (top - viewport[1]) / viewport[3] : 0;
// ... 其他坐标处理 ...
// 设置顶点缓冲区
if (gl.vertexBuffer == null) {
gl.vertexBuffer = gl.createBuffer();
}
gl.bindBuffer(gl.ARRAY_BUFFER, gl.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ left, top, left, bottom, right, top, right, bottom ]), gl.STATIC_DRAW);
// 设置纹理坐标缓冲区
if (gl.texCoordBuffer == null) {
gl.texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, gl.texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 0, 0, 1, 1, 0, 1, 1 ]), gl.STATIC_DRAW);
}
// 启用顶点属性
if (this.vertexAttribute == null) {
this.vertexAttribute = gl.getAttribLocation(this.program, 'vertex');
gl.enableVertexAttribArray(this.vertexAttribute);
}
// ... 纹理坐标属性处理 ...
// 执行绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};
这个方法封装了WebGL绘制矩形的完整流程,包括:
- 坐标归一化处理
- 顶点缓冲区设置
- 纹理坐标缓冲区设置
- 顶点属性启用
- 最终绘制调用
8. 默认着色器获取
模块提供了获取默认着色器的静态方法:
Shader.getDefaultShader = function() {
gl.defaultShader = gl.defaultShader || new Shader();
return gl.defaultShader;
};
这是一个简单的单例模式实现,确保全局只存在一个默认着色器实例。
使用示例
了解Shader模块的内部实现后,我们来看一个典型的使用示例:
// 创建自定义着色器
var customShader = new Shader(
// 顶点着色器
'attribute vec2 vertex; void main() { gl_Position = vec4(vertex, 0.0, 1.0); }',
// 片段着色器
'uniform vec3 color; void main() { gl_FragColor = vec4(color, 1.0); }'
);
// 设置uniform变量
customShader.uniforms({
color: [1.0, 0.5, 0.2] // 橙色
});
// 绘制矩形
customShader.drawRect(0, 0, 100, 100);
最佳实践
- 着色器复用:对于频繁使用的着色器,应该缓存Shader实例而不是重复创建
- 错误处理:着色器编译和链接可能失败,应该用try-catch包裹相关代码
- 资源释放:不再使用的Shader实例应调用destroy()方法释放WebGL资源
- 性能优化:避免在渲染循环中频繁设置相同的uniform变量
总结
evanw/glfx.js中的Shader模块提供了一个简洁而强大的WebGL着色器抽象层,封装了着色器管理、参数设置和绘制等复杂操作。通过分析其实现,我们不仅能够更好地理解WebGL的工作原理,也能学习到如何设计一个高效、易用的图形编程接口。
对于想要深入了解WebGL图像处理的开发者来说,研究这个Shader模块的实现是一个很好的起点。它展示了如何将底层的WebGL API封装成更高级、更易用的接口,同时保持了足够的灵活性来支持各种图像处理效果。