返回

WebGL3D引擎Shader的封装

前端

探索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程序,并探讨如何使用它们来实现各种各样的图形效果。