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>

其中,着色器(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);
创建着色器的步骤大致为:
- 使用
WebGLRenderingContext.createShader()初始化着色器 - 通过
WebGLRenderingContext.shaderSource()挂接 GLSL 源代码 - 最后调用
WebGLRenderingContext.compileShader()完成着色器(shader)的编译
此时 WebGLShader 仍不是可用的形式,它需要被添加到一个 WebGLProgram里
创建着色器程序的步骤大致为:
- 使用
WebGLRenderingContext.createProgram()初始化着色器程序 - 通过
WebGLRenderingContext.attachShader()然后附着顶点着色器和片段着色器 - 最后调用
WebGLRenderingContext.linkProgram()完成着色器(shader)的连接
使用着色器程序(上述代码中):
// Use the program
gl.useProgram(shaderProgram);
// Draw a single triangle
gl.drawArrays(gl.TRIANGLES, 0, 3);
3. Cesium中的Shader
Cesium渲染模块中的Shader对象包含从创建GLSL到创建Shader Program整个流程
流程大致为:

- 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)
Cesium渲染模块之Shader的更多相关文章
- (原)Unreal渲染模块 管线 - 着色器(1)
@author: 白袍小道 转载悄悄说明下 随缘查看,施主开心就好 说明: 本篇继续Unreal搬山部分的渲染模块的Shader部分, 主要牵扯模块RenderCore, ShaderCore, RH ...
- 小强学渲染之Unity Shader编程HelloWorld
第一个简单的顶点vert/片元frag着色器 1)打开Unity 5.6编辑器,新建一个场景后ctrl+s保存命名为Scene_5.默认创建的场景是包含了一摄像机,一平行光,且场景背景是一天空盒而 ...
- (原)Unreal 渲染模块 渲染流程
@author:白袍小道 浏览分享随缘,评论不喷亦可. 扯淡部分: 在temp中,乱七八糟的说了下大致的UE过程.下面我们还是稍微别那么任性,一步步来吧. UE渲染模块牵扯到场景遍历. ...
- Unity5 新功能解析--物理渲染与standard shader
Unity5 新功能解析--物理渲染与standard shader http://blog.csdn.net/leonwei/article/details/48395061 物理渲染是UNITY5 ...
- (原)Unreal渲染模块 管线 - 程序和场景查询
@author: 白袍小道 查看随意,转载随缘 第一部分: 这里主要关心加速算法,和该阶段相关的UE模块的结构和组件的处理. What-HOW-Why-HOW-What(嘿嘿,老规矩) 1.渲 ...
- (原)Unreal渲染模块 源码和实例分析说明
@author:白袍小道 说明 1.由于小道就三境武夫而已,而UE渲染部分不仅管理挺大,而且牵扯技术和内容驳杂,所以才有这篇梳理. 2.尽量会按书籍和资料,源码,小模块的调试和搬山(就是敲键盘)..等 ...
- (原)Unreal 渲染模块引言Temp
@author:白袍小道 引言 本文只在对Unreal渲染模块做一些详细的理解,务求能分析出个大概. 其中框架的思想和实现的过程,是非常值得学习和推敲一二的. 涉及资源系统,材 ...
- Django---Http协议简述和原理,HTTP请求码,HTTP请求格式和响应格式(重点),Django的安装与使用,Django项目的创建和运行(cmd和pycharm两种模式),Django的基础文件配置,Web框架的本质,服务器程序和应用程序(wsgiref服务端模块,jinja2模板渲染模块)的使用
Django---Http协议简述和原理,HTTP请求码,HTTP请求格式和响应格式(重点),Django的安装与使用,Django项目的创建和运行(cmd和pycharm两种模式),Django的基 ...
- DRF框架(一)——restful接口规范、基于规范下使用原生django接口查询和增加、原生Django CBV请求生命周期源码分析、drf请求生命周期源码分析、请求模块request、渲染模块render
DRF框架 全称:django-rest framework 知识点 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful规范下的CBV接口 3.请求组件 ...
- drf框架 - 请求模块 | 渲染模块
Postman接口工具 官方 https://www.getpostman.com/ get请求,携带参数采用Paramspost等请求,提交数据包可以采用三种方式:form-date.urlenc ...
随机推荐
- 12个有用的JavaScript数组技巧
数组是Javascript最常见的概念之一,它为我们提供了处理数据的许多可能性,熟悉数组的一些常用操作是很有必要的. 1.数组去重 1.from()叠加new Set()方法 字符串或数值型数组的去重 ...
- C++ 单向链表手动实现(课后作业版)
单向链表,并实现增删查改等功能 首先定义节点类,类成员包含当前节点的值和下一个节点的地址 /node definition template <typename T> class Node ...
- setState 更新
同步逻辑中,setState异步更新,同步更新会合并为一次更新 异步逻辑中,setState同步更新 this.setState({ data:data,()=>{ } }) 在回调函数里面了解 ...
- 法拉第未来任命新CFO!贾跃亭激动发声
近段时间以来,贾跃亭旗下的的法拉第未来(Faraday Future,简称 FF)可谓是动作频频. 一天前,有媒体报道称,FF 任命 Zvi Glasman 为其首席财务官.其将负责公司财务.投资者关 ...
- lua 添加的时候去重
result = {} ids = {1,9,6,7}affs = {3,2,4,5,6}count =0for s in *ids result[s]=sfor p, v in pairs resu ...
- BZOJ1008 [HNOI2008]越狱 (快速幂,组合)
题目大意 求\(m\)种数字组成的长度为\(n\)的序列的种数,序列中至少有一段连续的数字 分析 用可重排列的种数减去,相邻数字互不相同的序列种数 考虑相邻互不相同,第一个元素有\(m\)种可能,后面 ...
- 中国人民公安大学 Chinese people’ public security university 网络对抗技术 实验报告4
中国人民公安大学 Chinese people' public security university 网络对抗技术 实验报告 实验四 恶意代码技术 学生姓名 陈禹 年级 2018 区队 ...
- PHP Redis - zSet(有序集合)
有序集合与集合一样,string类型元素的集合,不允许重复的成员. 有序集合,每个元素都会关联一个 double 类型的分数.Redis 通过分数为集合的成员进行从小到大的排序 有序集合的成员是唯一的 ...
- DataTable TO List<T>
datatable转list<> public IList<T> GetList<T>(DataTable table){IList<T> list = ...
- 【Appium_python】多进程启动时,没有设置间隔导致连接关闭,以及等待时间,导致用例未执行完成,服务提早关闭。
多进程启动多设备时,没有设置间隔时间,appium服务器以为受到远程攻击,就自动关闭连接,导致服务启动失败, 解决方法:用time.sleep设置时间间隔 也需要增加等待时间,等待其他设备用例都执行完 ...