在介绍Renderer的第一篇,我就提到WebGL1.0对应的是OpenGL ES2.0,也就是可编程渲染管线。之所以单独强调这一点,算是为本篇埋下一个伏笔。通过前两篇,我们介绍了VBO和Texture两个比较核心的WebGL概念。假设生产一辆汽车,VBO就相当于这个车的骨架,纹理相当这个车漆,但有了骨架和车漆还不够,还需要一台机器人来加工,最终才能成产出这辆汽车。而Shader模块就是负责这个生产的过程,加工参数(VBO,Texture),执行渲染任务。

这里假设大家对Shader有一个基本的了解,这一块内容也很多,不可能简单两句轻描淡写就豁然开朗,而且我也没有进行过系统的学习,所以就不班门弄斧了。进入主题,来看看Cesium对Shader的封装。

图1:ES2.0可编程渲染管线

上图是可编程渲染管线的一个大概流程,我们关注的两个橙色的圆角矩形部分,分别是顶点着色器和片源着色器。既然是可编程渲染管线,面向Shader的开发者提供了一种称为GLSL的语言,如果你懂C的话,两者语法是相当的,所以从语法层面学习成本不大。

ShaderSource创建

首先,Cesium提供了ShaderSource类来加载GLSL代码,我们来看一下它对应的拷贝构造函数:

  1. ShaderSource.prototype.clone = function() {
  2. return new ShaderSource({
  3. sources : this.sources,
  4. defines : this.defines,
  5. pickColorQuantifier : this.pickColorQualifier,
  6. includeBuiltIns : this.includeBuiltIns
  7. });
  8. };
  • sources
    必须,代码本身,这里是一个数组,可以是多个代码片段的叠加
  • defines

    非必须,执行该代码时声明的预编译宏
  • pickColorQualifier

    非必须,当需要点击选中地物时设置此参数,值为'uniform',下面会介绍其大概
  • includeBuiltIns

    非必须,默认为true,认为需要加载Cesium自带的GLSL变量或function,下面会详细介绍

在使用上,通常只需要指定前两个参数,就可以创建一个顶点或片元着色器,比如在Globe中创建渲染地球的着色器代码就是这么的简单:

  1. // 顶点着色器
  2. this._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
  3. sources : [GroundAtmosphere, GlobeVS]
  4. });
  5.  
  6. // 片元着色器
  7. this._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
  8. sources : [GlobeFS]
  9. });

ShaderSource脚本加载

当然用起来简单,但其内部实现还是有些复杂的,在介绍ShaderSource前需要先了解两个知识点:CzmBuiltins&AutomaticUniforms。

CzmBuiltins

Cesium中提供了一些常用的GLSL文件,文件夹结构如下图:

图2:BuiltIn文件夹清单

如图所示主要分为三类(常量,方法,结构体),这些都是Cesium框架内部比较常用的基本结构和方法,属于内建类型,它们的特点是前缀均为czm_并且通过CzmBuiltins.js(打包时gulp会自动生成该文件)引用所有内建的GLSL代码:

  1. // 1 常量,例如:1 / Pi
  2. onst float czm_oneOverPi = 0.3183098861837907;
  3.  
  4. // 方法,例如:八进制解码,地形数据中用于数据压缩
  5. vec3 czm_octDecode(vec2 encoded)
  6. {
  7. encoded = encoded / 255.0 * 2.0 - 1.0;
  8. vec3 v = vec3(encoded.x, encoded.y, 1.0 - abs(encoded.x) - abs(encoded.y));
  9. if (v.z < 0.0)
  10. {
  11. v.xy = (1.0 - abs(v.yx)) * czm_signNotZero(v.xy);
  12. }
  13.  
  14. return normalize(v);
  15. }
  16.  
  17. // 结构体,例如:材质
  18. struct czm_material
  19. {
  20. vec3 diffuse;
  21. float specular;
  22. float shininess;
  23. vec3 normal;
  24. vec3 emission;
  25. float alpha;
  26. };

AutomaticUniforms

然而作为参数而言,仅仅有这些Const常量还是不够的,比如在一个三维场景中,随着位置的变化,相机的状态也是需要更新的,比如ModelViewMatrix,ProjectMatrix以及ViewPort等变量通常也需要参与到GLSL的计算中,Cesium提供了AutomaticUniform类,用来封装这些内建的变量,构造函数如下:

  1. function AutomaticUniform(options) {
  2. this._size = options.size;
  3. this._datatype = options.datatype;
  4. this.getValue = options.getValue;
  5. }

所有的内部变量都可以基于该构造函数创建,并添加到AutomaticUniforms数组中,并且在命名上也遵守czm_*的格式,通过命名就可以知道该变量是不是内建的,如果是,则从CzmBuiltins和AutomaticUniforms对应的列表(创建列表并维护的过程则是在ShaderSource中完成的,下面会讲)中找其对应的值就可以,这样,Cesium内部自动调用这些变量而不需要用户来处理,如果不是,则需要用户自己定义一个uniformMap的数组来自己维护。如下是AutomaticUniforms的代码片段,可以看到AutomaticUniforms中创建了czm_viewport变量,类型是vec4,并提供了getValue的方式,负责传值。

  1. var AutomaticUniforms = {
  2. czm_viewport : new AutomaticUniform({
  3. size : 1,
  4. datatype : WebGLConstants.FLOAT_VEC4,
  5. getValue : function(uniformState) {
  6. return uniformState.viewportCartesian4;
  7. }
  8. })
  9. }
  10. return AutomaticUniforms;

但这还有一个问题,只提供了getValue的方式,可以把值传到GLSL中,但这个值是怎么获取的,也就是setValue是如何实现,而且不需要用户来关心。如果你看的足够自信,会发现getValue中有一个uniformState参数,正是UniformState这个类的功劳了,Scene在初始化时会创建该属性,而UniformState提供了update方法,在每一帧Render中都会更新这些变量值,不需要用户自己来维护。

综上所述,也就是Cesium内部有一套内建的变量,常量,方法和结构体,这些内容之间有一套完整的机制保证他们的正常运作,而ShaderSource的第一个作用就是在初始化的时候声明_czmBuiltinsAndUniforms属性,并加载CzmBuiltins和AutomaticUniforms中的内建属性,建立一个全局的黄页,为整个程序服务。另外要强调的是,这个过程是在加载ShaderSource.js脚本时执行的,只会运行一次,不需要每次new ShaderSource的时候执行。

  1. ShaderSource._czmBuiltinsAndUniforms = {};
  2.  
  3. // 合并automatic uniforms和Cesium内建实例
  4. // CzmBuiltins是打包时自动创建的,里面包括所有内建实例的类型和命名
  5. for ( var builtinName in CzmBuiltins) {
  6. if (CzmBuiltins.hasOwnProperty(builtinName)) {
  7. ShaderSource._czmBuiltinsAndUniforms[builtinName] = CzmBuiltins[builtinName];
  8. }
  9. }
  10. // AutomaticUniforms数组是在AutomaticUniforms.js中创建并返回
  11. for ( var uniformName in AutomaticUniforms) {
  12. if (AutomaticUniforms.hasOwnProperty(uniformName)) {
  13. var uniform = AutomaticUniforms[uniformName];
  14. if (typeof uniform.getDeclaration === 'function') {
  15. ShaderSource._czmBuiltinsAndUniforms[uniformName] = uniform.getDeclaration(uniformName);
  16. }
  17. }
  18. }

Shader创建

上面介绍了ShaderSource的创建,当用户创建完VertexShaderSource和FragmentShaderSource后,下面就要创建ShaderProgram,将这两个ShaderSource关联起来。如下是SkyBox中创建ShaderProgram的示例代码:

  1. command.shaderProgram = ShaderProgram.fromCache({
  2. context : context,
  3. vertexShaderSource : SkyBoxVS,
  4. fragmentShaderSource : SkyBoxFS,
  5. attributeLocations : attributeLocations
  6. });

vertexShaderSource和fragmentShaderSource都属于之前我们提到的ShaderSource概念,attributeLocations则对应之前的VBO中VertexBuffer。GLSL中变量分为两种,一类是attribute,比如位置,法线,纹理坐标这些,每一个顶点对应的值都不同,一类是uniform,跟顶点无关,值都相同的。这里需要传入attribute变量,而uniform在渲染时才会指定。我们来看一下fromCache的内部实现,详细的介绍一下:

  1. ShaderProgram.fromCache = function(options) {
  2. // Cesium提供了ShaderCache缓存机制,可以重用ShaderProgram
  3. return options.context.shaderCache.getShaderProgram(options);
  4. };
  5.  
  6. ShaderCache.prototype.getShaderProgram = function(options) {
  7.  
  8. // 合并该ShaderProgram所用到的顶点和片元着色器的代码
  9. var vertexShaderText = vertexShaderSource.createCombinedVertexShader();
  10. var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader();
  11.  
  12. // 创建Cache缓存中Key-Value中的Key值
  13. var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations);
  14. var cachedShader;
  15.  
  16. // 如果已存在,则直接用
  17. if (this._shaders[keyword]) {
  18. cachedShader = this._shaders[keyword];
  19.  
  20. // No longer want to release this if it was previously released.
  21. delete this._shadersToRelease[keyword];
  22. } else {
  23. // 如果不存在,则需要创建新的ShaderProgram
  24. var context = this._context;
  25. var shaderProgram = new ShaderProgram({
  26. gl : context._gl,
  27. logShaderCompilation : context.logShaderCompilation,
  28. debugShaders : context.debugShaders,
  29. vertexShaderSource : vertexShaderSource,
  30. vertexShaderText : vertexShaderText,
  31. fragmentShaderSource : fragmentShaderSource,
  32. fragmentShaderText : fragmentShaderText,
  33. attributeLocations : attributeLocations
  34. });
  35.  
  36. // Key-Value中的Value值
  37. cachedShader = {
  38. cache : this,
  39. shaderProgram : shaderProgram,
  40. keyword : keyword,
  41. count : 0
  42. };
  43.  
  44. 添加到Cache中,并更新该Cache容器内总的shader数目
  45. shaderProgram._cachedShader = cachedShader;
  46. this._shaders[keyword] = cachedShader;
  47. ++this._numberOfShaders;
  48. }
  49.  
  50. // 该ShaderProgram的引用计数值
  51. ++cachedShader.count;
  52. return cachedShader.shaderProgram;
  53. };

不难发现,fromCache最终是通过shaderCache.getShaderProgram方法实现ShaderProgram的创建,从这可以看出Cesium提供了ShaderCache缓存机制,可以重用ShaderProgram,通过双面的代码注释可以很好的理解这个过程。另外,1通过createCombinedVertexShader/createCombinedFragmentShader方法,生成最终的GLSL代码(下面会详细介绍),并2创建ShaderProgram。下面讨论一下1和2的具体实现。

文件合并

前面我们提到Cesium提供了丰富的内建函数和变量,这样提高了代码的重用性,正因为如此,很可以出现一个GLSL代码是由多个代码片段组合而成的,因此ShaderSource.sources是一个数组类型,可以加载多个GLSL文件。这样,自然要提供一个多文件合并成一个GLSL代码的方法。

但合并代码并不只是单纯文本的叠加,算是一个简易的语法解析器,特别是一些内建变量的声明,我们来看一下combine代码的大致逻辑:

  1. function combineShader(shaderSource, isFragmentShader) {
  2. var i;
  3. var length;
  4.  
  5. // sources中的文本合并
  6. var combinedSources = '';
  7. var sources = shaderSource.sources;
  8. if (defined(sources)) {
  9. for (i = 0, length = sources.length; i < length; ++i) {
  10. // #line needs to be on its own line.
  11. combinedSources += '\n#line 0\n' + sources[i];
  12. }
  13. }
  14.  
  15. // 去掉代码中的注释部分
  16. combinedSources = removeComments(combinedSources);
  17.  
  18. // 最终的GLSL代码
  19. var result = '';
  20.  
  21. // 支持的版本号
  22. if (defined(version)) {
  23. result = '#version ' + version;
  24. }
  25.  
  26. // 添加预编译宏
  27. var defines = shaderSource.defines;
  28. if (defined(defines)) {
  29. for (i = 0, length = defines.length; i < length; ++i) {
  30. var define = defines[i];
  31. if (define.length !== 0) {
  32. result += '#define ' + define + '\n';
  33. }
  34. }
  35. }
  36.  
  37. // 追加内建变量
  38. if (shaderSource.includeBuiltIns) {
  39. result += getBuiltinsAndAutomaticUniforms(combinedSources);
  40. }
  41.  
  42. result += '\n#line 0\n';
  43.  
  44. // 追加combinedSources中的代码
  45. result += combinedSources;
  46.  
  47. return result;
  48. }

注释部分是基本的逻辑,1合并sources中的文件,2删除注释,3提取版本信息,4拼出最终的代码。4.1版本声明,4.2预编译宏,4.3内建变量的声明,4.4加载步骤1中的代码。这里的逻辑都还比较容易理解,但4.3,内建变量的声明还是比较复杂的,我们专门介绍一下。

  1. function getBuiltinsAndAutomaticUniforms(shaderSource) {
  2. var dependencyNodes = [];
  3. // 获取Main根节点
  4. var root = getDependencyNode('main', shaderSource, dependencyNodes);
  5. // 生成该root依赖的所有节点,保存在dependencyNodes
  6. generateDependencies(root, dependencyNodes);
  7. // 根据依赖关系排序
  8. sortDependencies(dependencyNodes);
  9.  
  10. // 创建需要的内建变量声明
  11. var builtinsSource = '';
  12. for (var i = dependencyNodes.length - 1; i >= 0; --i) {
  13. builtinsSource = builtinsSource + dependencyNodes[i].glslSource + '\n';
  14. }
  15.  
  16. return builtinsSource.replace(root.glslSource, '');
  17. }

该部分的重点在于对dependencyNode的维护,我们先看看该节点的结构:

  1. dependencyNode = {
  2. name : name,
  3. glslSource : glslSource,
  4. dependsOn : [],
  5. requiredBy : [],
  6. evaluated : false
  7. };

如下图,是根节点对应的值:

其中,name就是名称,根节点就是main函数入口;glslSource则是其内部的代码;dependsOn是他依赖的节点;requiredBy是依赖他的节点;evaluated用来表示该节点是否已经解析过。有了根节点root,下面就是顺藤摸瓜,最终构建出所有节点的队列,这就是generateDependencies函数做的事情,伪代码如下:

  1. function generateDependencies(currentNode, dependencyNodes) {
  2. // 更新标识,当前节点已经结果过
  3. currentNode.evaluated = true;
  4.  
  5. // 正则表达式,搜索当前代码中符合czm_*的所有内建变量或函数
  6. var czmMatches = currentNode.glslSource.match(/\bczm_[a-zA-Z0-9_]*/g);
  7. if (defined(czmMatches) && czmMatches !== null) {
  8. // remove duplicates
  9. czmMatches = czmMatches.filter(function(elem, pos) {
  10. return czmMatches.indexOf(elem) === pos;
  11. });
  12.  
  13. // 遍历czmMatches找到的所有符合规范的变量,建立依赖关系,是一个双向链表
  14. czmMatches.forEach(function(element) {
  15. if (element !== currentNode.name && ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)) {
  16. var referencedNode = getDependencyNode(element, ShaderSource._czmBuiltinsAndUniforms[element], dependencyNodes);
  17. // currentNodetNode依赖referencedNode
  18. currentNode.dependsOn.push(referencedNode);
  19. // referencedNode被currentNodetNode依赖
  20. referencedNode.requiredBy.push(currentNode);
  21.  
  22. // recursive call to find any dependencies of the new node
  23. generateDependencies(referencedNode, dependencyNodes);
  24. }
  25. });
  26. }
  27. }

有了这个节点队列还并不能满足要求,因为队列中的元素是按照在glsl代码中出现的先后顺序来解析的,而元素之间也存在一个依赖关系,所以我们需要一个过程,把这个无序队列转化为一个有依赖关系的双向链表,这就是sortDependencies函数的工作。这其实是一个树的广度优先的遍历,左右上的顺序,遍历的过程中会解除requiredBy的关联,有兴趣的可以看一下源码。最后会判断是否有循环依赖的错误情况。也算是一个依赖关系的语法解析器。

至此,ShaderSource基本完成了自己的核心使命,当然,如果是拾取状态,属于特殊情况,则会更新片源着色器的代码,对于选中的地物赋予选中风格(颜色),对应的函数为:ShaderSource.createPickFragmentShaderSource。

ShaderProgram创建

有了最终版本的着色器代码后,终于可以创建ShaderProgram了,构造函数如下,本身也是一个空壳,只有在渲染中第一次使用该ShaderProgram时进行WebGL层面的调用,避免不必要的资源消耗:

  1. function ShaderProgram(options) {
  2. this._vertexShaderSource = options.vertexShaderSource;
  3. this._vertexShaderText = options.vertexShaderText;
  4. this._fragmentShaderSource = options.fragmentShaderSource;
  5. this._fragmentShaderText = modifiedFS.fragmentShaderText;
  6.  
  7. this.id = nextShaderProgramId++;
  8. }

渲染状态

主要介绍渲染状态中ShaderProgram的相关操作。

绑定ShaderProgram

代码如上,在渲染时会先绑定该ShaderProgram,如果是第一次则会初始化。注释是里面的关键逻辑,应该比较容易理解,这里值得强调的是对uniform的区分,方便后面渲染中参数的传值。

  1. ShaderProgram.prototype._bind = function() {
  2. // 初始化
  3. initialize(this);
  4. // 绑定
  5. this._gl.useProgram(this._program);
  6. };
  7.  
  8. function initialize(shader) {
  9. // 如果已经创建,则不需要初始化
  10. if (defined(shader._program)) {
  11. return;
  12. }
  13.  
  14. var gl = shader._gl;
  15. // 创建该Program,如果编译有错,则抛出异常
  16. var program = createAndLinkProgram(gl, shader, shader._debugShaders);
  17. // 获取attribute变量的数目
  18. var numberOfVertexAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
  19. // 获取uniform变量的列表
  20. var uniforms = findUniforms(gl, program);
  21. // 根据czm_*规则区分uniform,分为自定义uniform和内建uniform
  22. var partitionedUniforms = partitionUniforms(shader, uniforms.uniformsByName);
  23.  
  24. // 保存属性
  25. shader._program = program;
  26. shader._numberOfVertexAttributes = numberOfVertexAttributes;
  27. shader._vertexAttributes = findVertexAttributes(gl, program, numberOfVertexAttributes);
  28. shader._uniformsByName = uniforms.uniformsByName;
  29. shader._uniforms = uniforms.uniforms;
  30. shader._automaticUniforms = partitionedUniforms.automaticUniforms;
  31. shader._manualUniforms = partitionedUniforms.manualUniforms;
  32.  
  33. shader.maximumTextureUnitIndex = setSamplerUniforms(gl, program, uniforms.samplerUniforms);
  34. }
findUniforms

createUniform封装了所有Uniform类型的创建方法,并提供set函数,实现变量值和WebGL之间的传递。构造函数如下:

  1. function createUniform(gl, activeUniform, uniformName, location) {
  2. switch (activeUniform.type) {
  3. case gl.FLOAT:
  4. return new UniformFloat(gl, activeUniform, uniformName, location);
  5. case gl.FLOAT_VEC2:
  6. return new UniformFloatVec2(gl, activeUniform, uniformName, location);
  7. case gl.FLOAT_VEC3:
  8. return new UniformFloatVec3(gl, activeUniform, uniformName, location);
  9. case gl.FLOAT_VEC4:
  10. return new UniformFloatVec4(gl, activeUniform, uniformName, location);
  11. case gl.SAMPLER_2D:
  12. case gl.SAMPLER_CUBE:
  13. return new UniformSampler(gl, activeUniform, uniformName, location);
  14. case gl.INT:
  15. case gl.BOOL:
  16. return new UniformInt(gl, activeUniform, uniformName, location);
  17. case gl.INT_VEC2:
  18. case gl.BOOL_VEC2:
  19. return new UniformIntVec2(gl, activeUniform, uniformName, location);
  20. case gl.INT_VEC3:
  21. case gl.BOOL_VEC3:
  22. return new UniformIntVec3(gl, activeUniform, uniformName, location);
  23. case gl.INT_VEC4:
  24. case gl.BOOL_VEC4:
  25. return new UniformIntVec4(gl, activeUniform, uniformName, location);
  26. case gl.FLOAT_MAT2:
  27. return new UniformMat2(gl, activeUniform, uniformName, location);
  28. case gl.FLOAT_MAT3:
  29. return new UniformMat3(gl, activeUniform, uniformName, location);
  30. case gl.FLOAT_MAT4:
  31. return new UniformMat4(gl, activeUniform, uniformName, location);
  32. default:
  33. throw new RuntimeError('Unrecognized uniform type: ' + activeUniform.type + ' for uniform "' + uniformName + '".');
  34. }
  35. }

这样,我们找到所有的uniforms,并根据其对应的type来封装,set方法相当于虚函数,不同的类型有不同的实现方法,这样的好处是在传值时直接调用set方法,而不需要因为类型的不同而分散注意力。

_setUniforms

我们在ShaderProgram初始化的时候,已经完成了对attribute变量的赋值过程,现在则是对uniform变量的赋值。这里分为两种情况,自定义和内建uniform两种情况,严格说还包括纹理的samplerUniform变量。

uniformMap

对应自定义的变量,会构造一个uniformMap赋给DrawCommand(后续会介绍,负责整个渲染的调度,将VBO,Texture,Framebuffer和Shader串联起来),如下是一个最简单的UniformMap示例:

  1. var uniformMap = {
  2. u_initialColor : function() {
  3. return this.properties.initialColor;
  4. }

其中u_initialColor就是该uniform变量的name,return则是其返回值。接下来我们来看看setUniforms代码:

  1. ShaderProgram.prototype._setUniforms = function(uniformMap, uniformState, validate) {
  2. var len;
  3. var i;
  4.  
  5. if (defined(uniformMap)) {
  6. var manualUniforms = this._manualUniforms;
  7. len = manualUniforms.length;
  8. for (i = 0; i < len; ++i) {
  9. var mu = manualUniforms[i];
  10. mu.value = uniformMap[mu.name]();
  11. }
  12. }
  13.  
  14. var automaticUniforms = this._automaticUniforms;
  15. len = automaticUniforms.length;
  16. for (i = 0; i < len; ++i) {
  17. var au = automaticUniforms[i];
  18. au.uniform.value = au.automaticUniform.getValue(uniformState);
  19. }
  20.  
  21. // It appears that assigning the uniform values above and then setting them here
  22. // (which makes the GL calls) is faster than removing this loop and making
  23. // the GL calls above. I suspect this is because each GL call pollutes the
  24. // L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
  25. var uniforms = this._uniforms;
  26. len = uniforms.length;
  27. for (i = 0; i < len; ++i) {
  28. uniforms[i].set();
  29. }
  30. };

首先,无论是manualUniforms还是automaticUniforms,都是经过createUniform封装后的uniform,这里更新它们的value,通过uniformMap或getValue方法,这两个在上面的内容中已经介绍过,然后uniforms[i].set(),实现最终向WebGL的传值。这里我保留了Cesium的注释,里面是一个很有意思的性能调优,不妨自己看看。

总结

终于写完了,有一种如释重负的感觉。ShaderProgram本身并不复杂,但本身是一个面向过程的方式,Cesium为了达到面向状态的目的做了大量的封装,在使用中更容易理解和维护。本文主要介绍这种封装的思路和技巧,对我而言,这个过程中还是很有收获,也加深了我对Shader的理解。我一直担心很多人可能看完后似懂非懂,确实知识点很多,而且之间的联系也很紧密,关键是需要对WebGL在这一块的内容需要有一个扎实的认识,才能较好的解读这层封装的意义。我尽量说的详细一些,但精力和能力有限,我自认为对这一块了解已经很清晰了,但也不敢打包票。所以,如果真的想要了解,还是需要亲自调试代码,亲自查探一下本文中提到的相关代码部分。

另外,个人认为shadersource在combine函数中还是很消耗计算的,如果执行ShaderProgram.fromCache都会执行此函数两遍(顶点和片元),所以这也是一个性能隐患处,比如GlobeSurfaceTile,如果每一个Tile都从ShaderCache中获取对应的ShaderProgram,尽管ShaderProgram只会创建一次,但每一次在Cache中通过Key查找Value的过程中,构建Key的代价也是很大的,这也是为什么Cesium有提供了GlobeSurfaceShaderSet来的原因所在(之一)。

最后要提醒一下,本文主要提供的是一个大概的流程,对于一些特殊情况并未涉及,比如GlobeSurfaceTile中,有可能出现多影像图层叠加的情况,也就是多重纹理,但N不固定的情况,GlobeSurfaceShaderSet.prototype.getShaderProgram中对这种情况进行了特殊处理。

Cesium原理篇:6 Render模块(3: Shader)的更多相关文章

  1. Cesium原理篇:5最长的一帧之影像

    如果把地球比做一个人,地形就相当于这个人的骨骼,而影像就相当于这个人的外表了.之前的几个系列,我们全面的介绍了Cesium的地形内容,详见: Cesium原理篇:1最长的一帧之渲染调度 Cesium原 ...

  2. Cesium原理篇:7最长的一帧之Entity(下)

    上一篇,我们介绍了当我们添加一个Entity时,通过Graphics封装其对应参数,通过EntityCollection.Add方法,将EntityCollection的Entity传递到DataSo ...

  3. Cesium原理篇:3最长的一帧之地形(2:高度图)

           这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面.        此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维 ...

  4. Cesium原理篇:6 Renderer模块(1: Buffer)

    刚刚结束完地球切片的渲染调度后,打算介绍一下目前大家都很关注的3D Tiles方面的内容,但发现要讲3D Tiles,或者充分理解它,需要对DataSource,Primitive要有基础,而这要求对 ...

  5. Cesium原理篇:6 Renderer模块(1: Buffer)【转】

    https://www.bbsmax.com/A/n2d9P1Q5Dv/ 刚刚结束完地球切片的渲染调度后,打算介绍一下目前大家都很关注的3D Tiles方面的内容,但发现要讲3D Tiles,或者充分 ...

  6. Cesium原理篇:GroundPrimitive

    今天来看看GroundPrimitive,选择GroundPrimitive有三个目的:1 了解GroundPrimitive和Primitive的区别和关系 2 createGeometry的特殊处 ...

  7. Cesium原理篇:GroundPrimitive【转】

    今天来看看GroundPrimitive,选择GroundPrimitive有三个目的:1 了解GroundPrimitive和Primitive的区别和关系 2 createGeometry的特殊处 ...

  8. Cesium原理篇:6 Render模块(3: Shader)【转】

    https://www.cnblogs.com/fuckgiser/p/5975274.html 在介绍Renderer的第一篇,我就提到WebGL1.0对应的是OpenGL ES2.0,也就是可编程 ...

  9. Cesium原理篇:6 Render模块(4: FBO)

    Cesium不仅仅提供了FBO,也就是Framebuffer类,而且整个渲染过程都是在FBO中进行的.FBO,中文就是帧缓冲区,通常都属于高级用法,但其实,如果你了解了它的基本原理后,用起来还是很简单 ...

随机推荐

  1. 【四】搭建Markdown的编辑器

    本系列有五篇:分别是 [一]Ubuntu14.04+Jekyll+Github Pages搭建静态博客:主要是安装方面 [二]jekyll 的使用 :主要是jekyll的配置 [三]Markdown+ ...

  2. 《Linux内核设计与实现》读书笔记 第三章 进程管理

    第三章进程管理 进程是Unix操作系统抽象概念中最基本的一种.我们拥有操作系统就是为了运行用户程序,因此,进程管理就是所有操作系统的心脏所在. 3.1进程 概念: 进程:处于执行期的程序.但不仅局限于 ...

  3. Android安全开发之安全使用HTTPS

    Android安全开发之安全使用HTTPS 1.HTTPS简介 阿里聚安全的应用漏洞扫描器中有证书弱校验.主机名弱校验.webview未校验证书的检测项,这些检测项是针对APP采用HTTPS通信时容易 ...

  4. Java工作环境笔记

    环境 1. Jvm最简生存指南: http://www.importnew.com/10127.html 2. 所有路径中,不要出现中文,即使开始的时候,调试Tomcat时,路径有中文也可以,你真不知 ...

  5. Kotlin笔记

    官网: http://kotlinlang.org/ http://kotlinlang.org/docs/reference/ 中文教程: http://kotlindoc.com/ Gradle: ...

  6. ECMAScript 6入门

    预计在2014年底,ECMAScript 6将会正式发布,他的草案在13年3月份被冻结,后续提出新特性将会移至ECMASript 7中.目前还没有哪款浏览器实现了ES6的全部内容,兼容性最强的一款要数 ...

  7. 辛巴学院-Unity-剑英陪你零基础学c#系列(二)顺序

    这不是草稿 辛巴学院:正大光明的不务正业.   上一次的教程写出来之后,反馈还是挺多的,有很多都做了修改,也有一些让人崩溃,不得不说上几句.有些人有些很奇怪的地方,你写篇东西,被看了以后不说他感觉怎么 ...

  8. K-均值聚类算法

    K-均值聚类算法 聚类是一种无监督的学习算法,它将相似的数据归纳到同一簇中.K-均值是因为它可以按照k个不同的簇来分类,并且不同的簇中心采用簇中所含的均值计算而成. K-均值算法 算法思想 K-均值是 ...

  9. java.lang.IllegalArgumentException: 'sessionFactory' or 'hibernateTemplate' is required

    java.lang.IllegalArgumentException: 'sessionFactory' or 'hibernateTemplate' is required 严重: Exceptio ...

  10. Base 64 编码

    原创地址:http://www.cnblogs.com/jfzhu/p/4020097.html 转载请注明出处 (一)Encoding VS. Encryption 很多人都以为编码(Encodin ...