写这个博客的初衷:开源的数字地球发展,从最开始的worldWind(纯正的NASA血统,大约从2006年开始流行,当我还是学生的时候用这个框架实现了中石油的林火蔓延模拟系统,以及在这个球上实现救援应急响应的模拟,当时拿了集团的一个奖项,所以我个人对这个版本情有独钟,从软件架构上将,NASA的工作确实非常了不起,整个软件的框架层次分明,而又十分简洁,目前的cesium版本里面能还能找到worldwind的影子,可惜的是JAVA语言开发的这个版本没有得到维护,全网也找不到几个用wwd来开发应用的人,原因可能是JAVA在桌面端的萎缩,JAVA实现OpenGL的特效这方面有限制,当WebGL刚推出的时候,我给NASA开发小组的大佬写了个邮件,咨询是否有计划开发下一代的WebGL版本,得到了大牛的回复,大意是WEB端也需要下载js文件,一样的耗费时间,后来NASA推出了ActiveX 版本的WWJ, 只不过响应者寥寥),到第二代数字地球(OSGEarth大约从2010年以后流行开来,这个版本基于OSG开发的,总体感觉是,软件的架构太复杂。因为OSG本身是通用的三维引擎软件,并非为数字地球量身定制,这个引擎本身就存在过度设计、过度包装的问题,随便在代码里下一个断点,函数调用栈几十层,看得人眼花缭乱,感觉这个软件的作者在抖小聪明,虽然这样评价要得罪部分OSG粉丝)。目前的CESIUM软件大行其道,如日中天,从项目案例上看,NASA已经完全放弃自家的WWJ, 也转移到CESIUM平台做开发。CESIUM社区的繁荣和OSGEARTH社区的没落形成了鲜明的对比,作为一位怀旧的技术人员,我花了点时间将CESIUM后处理特效的炫目效果移植到OSGEARTH,做一个尝试。于是有了这几篇博文。
PostProcessStage的效果:
cesium 中shader的管理机制:
定义一个shaderSouce, option参数里面重要的两个参数 defines 和 sources. defines 定义的shader里面的宏,我猜应该是对shader里面的'#define' 做了一种替换。而sources应该是各种vs fs 的代码段。
function ShaderSource(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var pickColorQualifier = options.pickColorQualifier;
//>>includeStart('debug', pragmas.debug);
if (
defined(pickColorQualifier) &&
pickColorQualifier !== "uniform" &&
pickColorQualifier !== "varying"
) {
throw new DeveloperError(
"options.pickColorQualifier must be 'uniform' or 'varying'."
);
}
//>>includeEnd('debug');
this.defines = defined(options.defines) ? options.defines.slice(0) : [];
this.sources = defined(options.sources) ? options.sources.slice(0) : [];
this.pickColorQualifier = pickColorQualifier;
this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
}
通过上面的代码,定义了两个shaderSource, 只是它们还没有被编译和链接,接着看下面的代码段,
var vs = new ShaderSource({
defines: defines.concat("SKY_FROM_SPACE"),
sources: [SkyAtmosphereCommon, SkyAtmosphereVS],
});
var fs = new ShaderSource({
defines: defines.concat("SKY_FROM_SPACE"),
sources: [SkyAtmosphereCommon, SkyAtmosphereFS],
});
定义出两个具体的shader。
this._spSkyFromSpace = ShaderProgram.fromCache({
context: context,
vertexShaderSource: vs,
fragmentShaderSource: fs,
});
这里才是正在创建经过编译和链接的shader. vs 和 fs 总是成双出现。从名字,不难猜出Cesium 对各种shader做了缓存。继续深入到ShaderProgram.fromCache的底层代码
ShaderCache.prototype.getShaderProgram = function (options) {
// convert shaders which are provided as strings into ShaderSource objects
// because ShaderSource handles all the automatic including of built-in functions, etc.
var vertexShaderSource = options.vertexShaderSource;
var fragmentShaderSource = options.fragmentShaderSource;
var attributeLocations = options.attributeLocations;
if (typeof vertexShaderSource === "string") {
vertexShaderSource = new ShaderSource({
sources: [vertexShaderSource],
});
}
if (typeof fragmentShaderSource === "string") {
fragmentShaderSource = new ShaderSource({
sources: [fragmentShaderSource],
});
}
var vertexShaderText = vertexShaderSource.createCombinedVertexShader(
this._context
);
var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(
this._context
);
var keyword =
vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations);
var cachedShader;
if (defined(this._shaders[keyword])) {
cachedShader = this._shaders[keyword];
// No longer want to release this if it was previously released.
delete this._shadersToRelease[keyword];
} else {
var context = this._context;
var shaderProgram = new ShaderProgram({
gl: context._gl,
logShaderCompilation: context.logShaderCompilation,
debugShaders: context.debugShaders,
vertexShaderSource: vertexShaderSource,
vertexShaderText: vertexShaderText,
fragmentShaderSource: fragmentShaderSource,
fragmentShaderText: fragmentShaderText,
attributeLocations: attributeLocations,
});
cachedShader = {
cache: this,
shaderProgram: shaderProgram,
keyword: keyword,
derivedKeywords: [],
count: 0,
};
// A shader can't be in more than one cache.
shaderProgram._cachedShader = cachedShader;
this._shaders[keyword] = cachedShader;
++this._numberOfShaders;
}
++cachedShader.count;
return cachedShader.shaderProgram;
};
以上代码,从各种 shaderSource的合并,计算出一个keyWord(为啥不搞个hash?), 然后根据keyword 在缓存里面查找是不是已经有这样的shader,如果有的话就直接用这个已有的shader返回。否则创建一个新的ShaderProgram, 并且放入缓存,以备下次查找。
Cesium中的shader:
基本顶点shader
globe._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
sources: [GroundAtmosphere, GlobeVS],
defines: defines,
});
globe._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
sources: fragmentSources,
defines: defines,
});
关于大气的shader
var vs = new ShaderSource({
defines: defines.concat("SKY_FROM_SPACE"),
sources: [SkyAtmosphereCommon, SkyAtmosphereVS],
});
var fs = new ShaderSource({
defines: defines.concat("SKY_FROM_SPACE"),
sources: [SkyAtmosphereCommon, SkyAtmosphereFS],
});
天空盒的shader
var fs = new ShaderSource({
defines: [useHdr ? "HDR" : ""],
sources: [SkyBoxFS],
});
command.shaderProgram = ShaderProgram.fromCache({
context: context,
vertexShaderSource: SkyBoxVS,
fragmentShaderSource: fs,
attributeLocations: this._attributeLocations,
});