webgl2 方法解析: linkProgram()

100 阅读3分钟

linkProgram() 是 WebGL2 中的一个重要方法,用于链接已附加了着色器的程序对象。

功能描述

linkProgram() 方法将一个包含顶点着色器和片段着色器的程序对象进行链接,生成可执行的 WebGL 程序。在链接的过程中会将vs和fs代码联合起来检查, 看两个在传输过程中有没有出错。(注: 与attachShader()不同, attachShader只检查当前的shader的语法是否错误)。

详细说明

  1. 链接过程:这个方法将检查附加到程序对象的着色器之间的兼容性,并创建可执行代码。

  2. 验证链接状态:链接完成后,可以使用 gl.getProgramParameter(program, gl.LINK_STATUS) 检查链接是否成功。

  3. 获取链接信息:如果链接失败,可以使用 gl.getProgramInfoLog(program) 获取错误信息。

  4. 典型使用流程

    • 创建程序对象 (gl.createProgram())
    • 附加着色器 (gl.attachShader())
    • 链接程序 (gl.linkProgram())
    • 检查链接状态
    • 使用程序 (gl.useProgram())

使用方法

gl.linkProgram(program);
  • program:要链接的 WebGLProgram 对象

attachShader的区别

attachShader只检查当前的shader的语法是否错误,比如下方的两个shader, attachShader()不会报错,因为它们的语法并没有错

#version 300 es
in vec4 aPosition;
void main() {
    gl_Position = aPosition;
}
#version 300 es
precision highp float;
in vec4 color;
out vec4 fragColor;
void main() {
    fragColor = color;
}

但是它们配合起来工作就会发生错误, 因为vs中没有out, 但是fs中却in了, 所以linkProgram()阶段会报错

示例代码

下方是一个可以直接运行的示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebGL2 linkProgram() 示例</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        canvas { border: 1px solid #000; margin-top: 10px; }
        button { margin: 5px; padding: 8px 15px; }
        .container { margin-bottom: 20px; }
    </style>
</head>
<body>
    <h1>WebGL2 linkProgram() 示例</h1>
    <div class="container">
        <button id="successBtn">测试链接成功</button>
        <button id="failBtn">测试链接失败</button>
    </div>
    <canvas id="glCanvas" width="400" height="300"></canvas>
    <div id="status">状态: 未初始化</div>
​
    <script>
        // 获取Canvas和WebGL2上下文
        const canvas = document.getElementById('glCanvas');
        const gl = canvas.getContext('webgl2');
        const statusDiv = document.getElementById('status');
        
        if (!gl) {
            statusDiv.textContent = '状态: 您的浏览器不支持WebGL2';
            throw new Error('WebGL2 not supported');
        }
​
        // 顶点着色器源码 - 成功案例
        const vsSourceSuccess = `#version 300 es
        in vec4 aPosition;
        out vec4 vColor;
        void main() {
            gl_Position = aPosition;
            vColor = vec4(1.0, 0.0, 0.0, 1.0);
        }`;
​
        // 片段着色器源码 - 成功案例
        const fsSourceSuccess = `#version 300 es
        precision highp float;
        in vec4 vColor;
        out vec4 fragColor;
        void main() {
            fragColor = vColor;
        }`;
​
        // 片段着色器源码 - 失败案例(故意写错)
        const fsSourceFail = `#version 300 es
        precision highp float;
        in vec4 color;
        out vec4 fragColor;
        void main() {
            fragColor = color;
        }`;
​
        // 创建着色器函数
        function createShader(gl, sourceCode, type) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, sourceCode);
            gl.compileShader(shader);
            
            // 检查编译状态
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('着色器编译错误:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            
            return shader;
        }
​
        function createProgram(vertexShader, fragmentShader) {
          // 创建程序
          const program = gl.createProgram();
          gl.attachShader(program, vertexShader);
          gl.attachShader(program, fragmentShader);
          gl.linkProgram(program);
​
          // 检查链接状态
          if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('程序链接错误:', gl.getProgramInfoLog(program));
            gl.deleteProgram(program);
            return null;
          }
          
          // 可以删除着色器了,它们已经链接到程序中
          gl.deleteShader(vertexShader);
          gl.deleteShader(fragmentShader);
​
          return program;
​
        }
​
​
        // 渲染函数
        function render(program) {
            if (!program) return;
            
            gl.useProgram(program);
            
            // 设置顶点数据
            const positionAttributeLocation = gl.getAttribLocation(program, "aPosition");
            const positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            
            // 三角形顶点数据
            const positions = [
                 0.0,  0.5, 0.0,
                -0.5, -0.5, 0.0,
                 0.5, -0.5, 0.0
            ];
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
            
            // 启用并设置顶点属性指针
            gl.enableVertexAttribArray(positionAttributeLocation);
            gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
            
            // 清空画布并绘制
            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.drawArrays(gl.TRIANGLES, 0, 3);
        }
​
        // 按钮事件处理
        document.getElementById('successBtn').addEventListener('click', () => {
            statusDiv.textContent = '状态: 尝试链接成功案例...';
​
            const vertexShader = createShader(gl, vsSourceSuccess, gl.VERTEX_SHADER);
            const fragmentShader = createShader(gl, fsSourceSuccess, gl.FRAGMENT_SHADER);
            const program = createProgram(vertexShader, fragmentShader);
​
            statusDiv.textContent = '状态: 链接成功 - 显示橙色三角形';
            render(program);
        });
​
        document.getElementById('failBtn').addEventListener('click', () => {
            statusDiv.textContent = '状态: 尝试链接失败案例...';
​
            const vertexShader = createShader(gl, vsSourceSuccess, gl.VERTEX_SHADER);
            const fragmentShader = createShader(gl, fsSourceFail, gl.FRAGMENT_SHADER);
            const program = createProgram(vertexShader, fragmentShader);
​
            if (program === null){
              statusDiv.textContent = '状态: 链接失败(如预期) - 错误信息已输出到控制台';
              // 清除画布
              gl.clearColor(0.0, 0.0, 0.0, 1.0);
              gl.clear(gl.COLOR_BUFFER_BIT);
            }
​
        });
​
        // 初始状态
        statusDiv.textContent = '状态: 点击按钮测试链接成功或失败案例';
    </script>
</body>
</html>

注意事项

  • 在链接前必须确保所有需要的着色器都已正确附加
  • 链接失败通常是由于着色器代码错误或着色器间不兼容导致的
  • 链接成功后,可以删除着色器对象以释放资源