WebGL3D引擎Shader的封装
2024-01-23 02:37:49
探索WebGL3D引擎之美:Shader的封装艺术
从零开始手撸WebGL3D引擎5:Shader的封装
大家晚上好!
自上一篇之后停更了很久,但其实mini3d.js一直在进展中。在准备发这篇文章的时候,发现可以写的太多了,这种进度实在是不太行,所以就思忖一下,我写这些文章的初衷是对这个领域有兴趣的同学提供一个入门指南,如果写的东西太多的话,势必会影响阅读体验,所以后来又做了很多减法,最后终于写成了这篇mini3d.js系列文章的第五篇!
在第三篇和第四篇我们实现了WebGL引擎的底层渲染逻辑,包括一些WebGL基础的使用方法,现在是时候实现引擎中的着色器管理了。先简单了解一下Shader是什么?为什么要用Shader?然后我们来封装一波。
什么是Shader?
着色器(Shader)是用来进行图形渲染的程序,它是一个GPU上运行的程序,用于在3D渲染管线中处理顶点、几何体和片段数据,并生成最终的图像。Shader程序可以分为两个阶段:顶点着色器和片段着色器,每个阶段执行不同的任务。
- 顶点着色器: 顶点着色器用于处理单个顶点数据,它可以变换顶点的位置、颜色和纹理坐标等属性。顶点着色器通常用于处理诸如模型变换、光照计算和顶点动画等任务。
- 片段着色器: 片段着色器用于处理单个像素数据,它可以计算像素的颜色、透明度和深度等属性。片段着色器通常用于处理诸如纹理映射、光照计算和阴影等任务。
Shader程序通常使用一种称为GLSL(OpenGL Shading Language)的语言编写。GLSL是一种类似于C语言的语言,它专门用于编写图形着色器程序。
为什么使用Shader?
使用Shader的主要优点有:
- 可编程性: Shader程序可以根据需要进行编程,以实现各种各样的图形效果。
- 高效性: Shader程序可以在GPU上并行执行,从而可以大幅提高图形渲染的效率。
- 跨平台性: Shader程序可以在不同的图形硬件上运行,从而可以实现跨平台的图形渲染。
Shader的封装
现在我们对Shader有了一个基本的了解,接下来我们来封装一波Shader。
我们先定义一个Shader类:
class Shader {
constructor(gl, type, source) {
this.gl = gl;
this.type = type;
this.source = source;
this.shader = null;
}
compile() {
this.shader = this.gl.createShader(this.type);
this.gl.shaderSource(this.shader, this.source);
this.gl.compileShader(this.shader);
if (!this.gl.getShaderParameter(this.shader, this.gl.COMPILE_STATUS)) {
console.error('Shader compilation error: ', this.gl.getShaderInfoLog(this.shader));
this.gl.deleteShader(this.shader);
this.shader = null;
}
}
}
这个类包含了Shader的基本信息,包括Shader的类型、源代码和编译后的Shader对象。compile()方法用于编译Shader源代码。
接下来,我们定义一个ShaderProgram类,这个类用于管理Shader程序:
class ShaderProgram {
constructor(gl) {
this.gl = gl;
this.program = null;
this.shaders = [];
}
addShader(shader) {
this.shaders.push(shader);
}
link() {
this.program = this.gl.createProgram();
for (const shader of this.shaders) {
this.gl.attachShader(this.program, shader.shader);
}
this.gl.linkProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.error('Shader program linking error: ', this.gl.getProgramInfoLog(this.program));
this.gl.deleteProgram(this.program);
this.program = null;
}
}
use() {
this.gl.useProgram(this.program);
}
setUniform(name, value) {
const location = this.gl.getUniformLocation(this.program, name);
if (location === -1) {
console.error(`Uniform '${name}' not found in shader program.`);
return;
}
if (value instanceof Matrix4) {
this.gl.uniformMatrix4fv(location, false, value.elements);
} else if (value instanceof Vector3) {
this.gl.uniform3fv(location, value.elements);
} else if (value instanceof Vector2) {
this.gl.uniform2fv(location, value.elements);
} else if (value instanceof Float32Array) {
this.gl.uniform1fv(location, value);
} else if (typeof value === 'number') {
this.gl.uniform1f(location, value);
} else {
console.error(`Unsupported uniform value type: ${typeof value}`);
}
}
}
这个类包含了ShaderProgram的基本信息,包括Shader程序对象和Shader对象列表。addShader()方法用于向Shader程序中添加Shader对象,link()方法用于链接Shader程序,use()方法用于使用Shader程序,setUniform()方法用于设置Shader程序的uniform变量。
最后,我们定义一个ShaderManager类,这个类用于管理所有的Shader程序:
class ShaderManager {
constructor(gl) {
this.gl = gl;
this.shaders = {};
}
createShaderProgram(name, vertexShaderSource, fragmentShaderSource) {
const vertexShader = new Shader(this.gl, this.gl.VERTEX_SHADER, vertexShaderSource);
vertexShader.compile();
const fragmentShader = new Shader(this.gl, this.gl.FRAGMENT_SHADER, fragmentShaderSource);
fragmentShader.compile();
const shaderProgram = new ShaderProgram(this.gl);
shaderProgram.addShader(vertexShader);
shaderProgram.addShader(fragmentShader);
shaderProgram.link();
this.shaders[name] = shaderProgram;
}
getShaderProgram(name) {
return this.shaders[name];
}
}
这个类包含了ShaderManager的基本信息,包括WebGL上下文对象和Shader程序列表。createShaderProgram()方法用于创建一个新的Shader程序,getShaderProgram()方法用于获取已存在的Shader程序。
小结
至此,我们就完成了Shader的封装。在下一篇文章中,我们将实现一些常用的Shader程序,并探讨如何使用它们来实现各种各样的图形效果。