• Cesium渲染模块之Shader


    1. 引言

    Cesium是一款三维地球和地图可视化开源JavaScript库,使用WebGL来进行硬件加速图形,使用时不需要任何插件支持,基于Apache2.0许可的开源程序,可以免费用于商业和非商业用途

    Cesium官网:Cesium: The Platform for 3D Geospatial

    Cesium GitHub站点:CesiumGS/cesium: An open-source JavaScript library for world-class 3D globes and maps (github.com)

    API文档:Index - Cesium Documentation

    通过阅读源码,理清代码逻辑,有助于扩展与开发,笔者主要参考了以下两个系列的文章

    渲染是前端可视化的核心,本文描述Cesium渲染模块的Shader

    2. WebGL中的Shader

    以下大致是一个最简的WebGL绘制代码:

    <canvas id="canvas">canvas>
    <script>
    const vertexSource = `
    attribute vec3 aPos;
    void main()
    {
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
    `
    const fragmentSource = `
    void main()
    {
    gl_FragColor = vec4(1.0, 0.5, 0.2, 1.0);
    }
    `
    const canvas = document.getElementById('canvas');
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    const gl = canvas.getContext('webgl2');
    if (!gl) {
    alert('WebGL not supported');
    }
    const vertices = new Float32Array([
    -0.5, -0.5, 0.0,
    0.5, -0.5, 0.0,
    0.0, 0.5, 0.0,
    ]);
    const vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    const vao = gl.createVertexArray();
    gl.bindVertexArray(vao);
    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(0)
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexSource);
    gl.compileShader(vertexShader);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentSource);
    gl.compileShader(fragmentShader);
    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);
    gl.clearColor(0.2, 0.3, 0.3, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(shaderProgram);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    script>

    image-20230227214406870

    其中,着色器(Shader)是运行在GPU上的小程序,这些小程序为图形渲染管线的某个特定部分而运行,从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器可以是一个顶点着色器(vertex shader)或片元着色器(fragment shader),每个ShaderProgram都需要这两种类型的着色器。上述代码中创建着色器和着色器程序的代码:

    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexSource);
    gl.compileShader(vertexShader);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentSource);
    gl.compileShader(fragmentShader);
    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    创建着色器的步骤大致为:

    此时 WebGLShader 仍不是可用的形式,它需要被添加到一个 WebGLProgram

    创建着色器程序的步骤大致为:

    使用着色器程序(上述代码中):

    // Use the program
    gl.useProgram(shaderProgram);
    // Draw a single triangle
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    3. Cesium中的Shader

    Cesium渲染模块中的Shader对象包含从创建GLSL到创建Shader Program整个流程

    流程大致为:

    image-20230309155628504

    • Cesium中支持分段编写GLSL代码,包括ShaderStruct、ShaderFunction、ShaderDestination
    • 将分段的代码组合成GLSL,即ShaderSource
    • ShaderBuilder使用ShaderSource创建的ShaderProgram会缓存起来,即ShaderCache
    • 需要新的ShaderProgram时,先查询缓存中是否有,有就复用,无则创建

    在Cesium源码中创建ShaderProgram大多也是这个流程,例如PolylineCollection.js

    const fs = new ShaderSource({
    defines: defines,
    sources: ["in vec4 v_pickColor;\n", this.material.shaderSource, PolylineFS],
    });
    const vsSource = batchTable.getVertexShaderCallback()(PolylineVS);
    const vs = new ShaderSource({
    defines: defines,
    sources: [PolylineCommon, vsSource],
    });
    this.shaderProgram = ShaderProgram.fromCache({
    context: context,
    vertexShaderSource: vs,
    fragmentShaderSource: fs,
    attributeLocations: attributeLocations,
    });

    ShaderProgram.fromCache只是简单的指向ShaderCache.getShaderProgram

    ShaderProgram.fromCache = function (options) {
    // ...
    return options.context.shaderCache.getShaderProgram(options);
    };

    ShaderCache.getShaderProgram逻辑就是先查询缓存中是否有Shader,有就复用,无则创建:

    ShaderCache.prototype.getShaderProgram = function (options) {
    // ...
    let cachedShader;
    if (defined(this._shaders[keyword])) {
    cachedShader = this._shaders[keyword];
    } else {
    const shaderProgram = new ShaderProgram();
    cachedShader = {
    cache: this,
    shaderProgram: shaderProgram,
    keyword: keyword,
    derivedKeywords: [],
    count: 0,
    };
    }
    return cachedShader.shaderProgram;
    };

    注意,此时的ShaderProgram只是个空壳,它并没有真正的创建WebGLProgram对象,但是它具备了创建WebGLProgram对象所需要的条件

    可以参考源码中PointCloud.js

    function createShaders(){
    // ...
    drawCommand.shaderProgram = ShaderProgram.fromCache({
    context: context,
    vertexShaderSource: vs,
    fragmentShaderSource: fs,
    attributeLocations: attributeLocations,
    });
    drawCommand.shaderProgram._bind();
    }

    所以shaderProgram._bind()才创建了WebGLProgram对象

    ShaderProgram.prototype._bind = function () {
    initialize(this);
    this._gl.useProgram(this._program);
    };
    function initialize(shader) {
    // ...
    reinitialize(shader);
    }
    function reinitialize(shader) {
    // ...
    const program = createAndLinkProgram(gl, shader, shader._debugShaders);
    }

    通过多处调用,最后createAndLinkProgram(gl, shader)实现了创建:

    function createAndLinkProgram(gl, shader) {
    const vsSource = shader._vertexShaderText;
    const fsSource = shader._fragmentShaderText;
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vsSource);
    gl.compileShader(vertexShader);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fsSource);
    gl.compileShader(fragmentShader);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    const attributeLocations = shader._attributeLocations;
    if (defined(attributeLocations)) {
    for (const attribute in attributeLocations) {
    if (attributeLocations.hasOwnProperty(attribute)) {
    gl.bindAttribLocation(
    program,
    attributeLocations[attribute],
    attribute
    );
    }
    }
    }
    gl.linkProgram(program);
    gl.deleteShader(vertexShader);
    gl.deleteShader(fragmentShader);
    // ...
    return program;
    }

    值得一说的是,真正的WebGLProgram对象是直到需要绘制时才创建,不需要绘制的就不会创建,这样有效节省了资源:

    function beginDraw(context, framebuffer, passState, shaderProgram, renderState) {
    // ...
    bindFramebuffer(context, framebuffer);
    applyRenderState(context, renderState, passState, false);
    shaderProgram._bind();
    }

    4. 参考资料

    [1]WebGLProgram - Web API 接口参考 | MDN (mozilla.org)

    [2]WebGLShader - Web API 接口参考 | MDN (mozilla.org)

    [3]Cesium原理篇:6 Render模块(3: Shader) - fu*k - 博客园 (cnblogs.com)

    [4]Cesium渲染模块之概述 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

    [5]Cesium DrawCommand 1 不谈地球 画个三角形 - 岭南灯火 - 博客园 (cnblogs.com)

  • 相关阅读:
    Spring Boot集成第三方登录之微信登录
    《Flowable流程引擎从零到壹》Flowable流程引擎介绍和实战项目初始化流程引擎实例
    〔024〕Stable Diffusion 之 模型训练 篇
    [react基础]关于v6版本route的变化,以及常见应用模式
    【FreeSwitch开发实践】UniMRCP编译与安装
    Go-Excelize API源码阅读(三十二)—— UnprotectSheet
    Python面试题总结
    JVM-Java字节码技术笔记
    linux删除软件
    构造函数,原型,实例,类的关系整理
  • 原文地址:https://www.cnblogs.com/jiujiubashiyi/p/17199285.html