Cesium 源码分析 Material

本文详细介绍了Cesium材质系统的工作原理,重点解析了`Material`类中的`fabric`模板结构,包括`type`、`materials`、`components`等关键属性。内容涉及材质模板的验证、glsl代码自定义、子材质处理以及uniform变量管理。通过对源码的分析,展示了如何创建和管理Cesium材质,以及如何确保它们符合规范。
摘要由CSDN通过智能技术生成

       Cesium关于实例的创建都是封装在类的静态函数中,这个习惯很好,方便创建和管理,对外只提供创建的方法,正如Material中的Material.fromType()函数一样,传递参数创建材质。

        Material材质的中的几个概念是了解材质的关键,例如下面这段代码

var material = new Cesium.Material({
            fabric: {
              materials: {
                diffuseMaterial: {
                  type: "DiffuseMap",
                  uniforms: {
                    image: "../images/bumpmap.png",
                  },
                },
                bumpMaterial: {
                  type: "BumpMap",
                  uniforms: {
                    image: "../images/bumpmap.png",
                    strength: 0.8,
                  },
                },
              },
              components: {
                diffuse: "diffuseMaterial.diffuse",
                specular: 0.01,
                normal: "bumpMaterial.normal",
              },
            },
            translucent: false
          });
new Cesium.Material({
    fabric: {					//  材质模板					
      type: "ElevationColorContour",
      materials: {				// 材质
        contourMaterial: {
          type: "ElevationContour",
        },
        elevationRampMaterial: {
          type: "ElevationRamp",
        },
      },
      components: {			// 组合
        diffuse:
          "contourMaterial.alpha == 0.0 ? elevationRampMaterial.diffuse : contourMaterial.diffuse",
        alpha:
          "max(contourMaterial.alpha, elevationRampMaterial.alpha)",
      },
    },
    translucent: false,		// 透明
  });

1、fabric:的内容是材质的模板json;

2、type:是材质的类型,主要用于在缓冲中查找之前创建的材质模板,模板中存储了渲染所需要的所有东西;

3、materials:这是子材质列表,渲染时会将子材质渲染后的结果作为当前材质的输入,且在当前材质的components属性中有相关的引用,例如contourMaterial.alpha中的contourMaterial即为子材质输出纹理,关于contourMaterial.alpha这条语句在Material类中还会做处理,最终会替换为czm_getMaterial_1(materialInput).alpha这条语句,而czm_getMaterial_1对应的时第1个子材质贡献的数据函数,czm_getMaterial_x代表第x个材质的贡献图像的函数;

4、components:这个对象中包含的内容与materials是对应的,例如components中的alpha成员与alphaMaterial子材质名称是对应关系,因为这两个单词都存在alpha关系,components["alpha"]的值就是用相对应的alphaMaterial子材质的结果拼成的字符串,而且"contourMaterial.alpha == 0.0 ? elevationRampMaterial.diffuse : contourMaterial.diffuse"这条语句会拼接到材质的glsl中进行编译;

5、translucent:透明,材质内部对其使用参考了三方面的值,json传入的translucent、fabric对应缓存中的translucent、各个子材质中中的translucent,三个方面只要有一方面为透明,就是最终的透明;

6、uniforms:Material会根据unfiorm中变量的成员名称,动态拼接glsl代码,例如uniforms["image"]中的image名称会在glsl中动态拼接,而且Material类会根据uniforms["image"]中的值判断是什么类型的,例如uniforms["image"]="../images/bumpmap.png"是2D图片类型,则在glsl中对应sampler2D,最终拼接的glsl代码是 uniform sampler2D image;

关键信息说完,下面分析以下源码:

// 模板包含以下属性
var templateProperties = [
  "type",
  "materials",
  "uniforms",
  "components",
  "source",
];

// 组件包含以下属性
var componentProperties = [
  "diffuse",
  "specular",
  "shininess",
  "normal",
  "emission",
  "alpha",
];

function checkForTemplateErrors(material) {

  // 获取材质模板
  var template = material._template;

  // uniform、material、component
  var uniforms = template.uniforms;
  var materials = template.materials;
  var components = template.components;

  // Make sure source and components do not exist in the same template.
  //>>includeStart('debug', pragmas.debug);
  if (defined(components) && defined(template.source)) {
    throw new DeveloperError(
      "fabric: cannot have source and components in the same template."
    );
  }
  //>>includeEnd('debug');

  // Make sure all template and components properties are valid.
  // 确保所有的模板和组件是有效的

  // 材质模板要符合模板的属性
  checkForValidProperties(template, templateProperties, invalidNameError, true);

  // 在规定的组件中查找自定义的组件是否符合规定的组件,找不到就会
  checkForValidProperties(
    components,
    componentProperties,
    invalidNameError,
    true
  );

  // Make sure uniforms and materials do not share any of the same names.
  // 或者材质数组
  var materialNames = [];
  for (var property in materials) {
    if (materials.hasOwnProperty(property)) {
      materialNames.push(property);
    }
  }

  // 材质数组中的材质名称包含 和 uniform成员的名字例如(alphaMaterial 和 components: {alpha: "alphaMaterial.alpha" })
  // 找到就会报错(因为子材质alphaMaterial的输出不能通过自定义的uniform设置给glsl)
  checkForValidProperties(uniforms, materialNames, duplicateNameError, false);
}

上面是关于fabric规格的检查,只有符合下面的规格,才是合法的。

// 模板包含以下属性
var templateProperties = [
  "type",
  "materials",
  "uniforms",
  "components",
  "source",
];

// 组件包含以下属性
var componentProperties = [
  "diffuse",
  "specular",
  "shininess",
  "normal",
  "emission",
  "alpha",
];

下面的代码,是用于拼接czm_getMaterial函数使用的,从中可以看出glsl代码是可以自定义的,其中针对多材质,添加了czm_gammaCorrect,可见如果是自己传入的纹理最后需要进行gamma矫正为线性纹理,而多材质已经假定子材质传出的就是线性纹理,没有必要在进行伽马矫正了。

// 创建czm_getMaterial方法体使用源码或者组件
function createMethodDefinition(material) {
  // 材质模板中的组件
  var components = material._template.components;
  // 获取模板的源码
  var source = material._template.source;
  // 
  if (defined(source)) {
    // 用于自定义shader的顶点、像素着色器
    material.shaderSource += source + "\n";
  } else {
    // 给材质创建源码(获取材质)
    material.shaderSource +=
      "czm_material czm_getMaterial(czm_materialInput materialInput)\n{\n";
    material.shaderSource +=
      "czm_material material = czm_getDefaultMaterial(materialInput);\n";

    if (defined(components)) {

      // 多材质
      var isMultiMaterial =
        Object.keys(material._template.materials).length > 0;

      for (var component in components) {
        // 如果是自身的成员
        if (components.hasOwnProperty(component)) {

          if (component === "diffuse" || component === "emission") {  // 组件是漫反射或者自发光

            // 是否为融合(融合需要子材质,将子材质中的输出作为当前材质的输入)
            var isFusion =
              isMultiMaterial &&                                  // 有多个材质
              isMaterialFused(components[component], material);   // 为多个材质指定不同的组件

            // gamma矫正
            var componentSource = isFusion
              ? components[component]                               //上一个材质传出的材质颜色已经经过了gamma矫正了
              : "czm_gammaCorrect(" + components[component] + ")";  //没有使用别的材质作为输入时,需要自己处理gamma矫正

            // 拼接漫反射或者自发光源码 material.diffuse = czm_gammaCorrect(custom.diffuse);
            material.shaderSource +=
              "material." + component + " = " + componentSource + "; \n";

          } else if (component === "alpha") {                   // 组件是alpha通道

            //设置alpha参数 material.alpha = 1.0
            material.shaderSource +=
              "material.alpha = " + components.alpha + "; \n";

          } else {
            // 设置组件  material.speculer = custom.speculer
            material.shaderSource +=
              "material." + component + " = " + components[component] + ";\n";

          }

        }
      }
    }
    // 拼上返回值
    material.shaderSource += "return material;\n}\n";
  }
}

下面这段代码指定了glsl中uniform字符串的拼接,以及cpu中uniform的对应内存数据处理,其中针对纹理会另外传入纹理大小sample1Dimensions字段。

function createUniform(material, uniformId) {

  var strict = material._strict;
  // 拿到传入的uniform变量
  var materialUniforms = material._template.uniforms;
  var uniformValue = materialUniforms[uniformId];

  // 获取uniform类型,对应cpu的类型找到gpu的类型
  var uniformType = getUniformType(uniformValue);

  //>>includeStart('debug', pragmas.debug);
  if (!defined(uniformType)) {
    throw new DeveloperError(
      "fabric: uniform '" + uniformId + "' has invalid type."
    );
  }
  //>>includeEnd('debug');

  var replacedTokenCount;

  if (uniformType === "channels") {
    // 如果是channels变量使用uniform的值替换glsl中的变量,返回替换的个数   例如:uniforms: {color: "rgba"}, 替换源码"uniform vec4 color" 为 "uniform vec4 rgba"
    replacedTokenCount = replaceToken(material, uniformId, uniformValue, false);
    //>>includeStart('debug', pragmas.debug);
    // 如果replacedTokenCount代表模板中设置了无用的uniform
    if (replacedTokenCount === 0 && strict) {
      throw new DeveloperError(
        "strict: shader source does not use channels '" + uniformId + "'."
      );
    }
    //>>includeEnd('debug');
  } else {
    // Since webgl doesn't allow texture dimension queries in glsl, create a uniform to do it.
    // Check if the shader source actually uses texture dimensions before creating the uniform.
    // 由于webgl不允许在glsl中进行纹理维度查询,请创建一个统一的查询。在创建统一之前,检查着色器源是否实际使用纹理尺寸。
    if (uniformType === "sampler2D") {
      // 如果传入纹理,就必须传入纹理尺寸,添加纹理尺寸
      var imageDimensionsUniformName = uniformId + "Dimensions";
      // 如果glsl代码中存在纹理尺寸
      if (getNumberOfTokens(material, imageDimensionsUniformName) > 0) {
        // 在unfirom中添加ivec3的数据,绘制时传入数据使用
        materialUniforms[imageDimensionsUniformName] = {
          type: "ivec3",
          x: 1,
          y: 1,
        };
        // 创建unform
        createUniform(material, imageDimensionsUniformName);
      }
    }

    // Add uniform declaration to source code.
    // 在glsl中添加uniform的声明
    var uniformDeclarationRegex = new RegExp(
      // uniform vec4 color
      "uniform\\s+" + uniformType + "\\s+" + uniformId + "\\s*;"
    );

    // unfiorm vec4 color
    if (!uniformDeclarationRegex.test(material.shaderSource)) {
      // 如果没有找到上面的代码段,就在代码的前面加上
      var uniformDeclaration = "uniform " + uniformType + " " + uniformId + ";";
      material.shaderSource = uniformDeclaration + material.shaderSource;
    }

    // 例如:diffuse_1
    var newUniformId = uniformId + "_" + material._count++;
    // 将代码中的 uniform diffuse_1 替换为 uniform diffuse_11   ????
    replacedTokenCount = replaceToken(material, uniformId, newUniformId);
    //>>includeStart('debug', pragmas.debug);
    if (replacedTokenCount === 1 && strict) {
      throw new DeveloperError(
        "strict: shader source does not use uniform '" + uniformId + "'."
      );
    }
    //>>includeEnd('debug');

    // Set uniform value
    // 设置变量值最后确定的uniform值,将这个值放在material.uniforms中而不是material._uniforms中
    material.uniforms[uniformId] = uniformValue;

    if (uniformType === "sampler2D") {
      // 设置2维图片
      material._uniforms[newUniformId] = function () {
        return material._textures[uniformId];
      };
      material._updateFunctions.push(createTexture2DUpdateFunction(uniformId));
    } else if (uniformType === "samplerCube") {
      // 设置cube图片
      material._uniforms[newUniformId] = function () {
        return material._textures[uniformId];
      };
      material._updateFunctions.push(createCubeMapUpdateFunction(uniformId));
    } else if (uniformType.indexOf("mat") !== -1) {
      // 
      var scratchMatrix = new matrixMap[uniformType]();
      // 设置矩阵
      material._uniforms[newUniformId] = function () {
        return matrixMap[uniformType].fromColumnMajorArray(
          material.uniforms[uniformId],           // 在material.uniforms中取值
          scratchMatrix
        );
      };
    } else {
      // 设置其他值
      material._uniforms[newUniformId] = function () {
        return material.uniforms[uniformId];    // 在material.uniforms中取值
      };
    }
  }
}

下面是创建子材质的相关代码,子材质中的czm_getMaterial函数会根据会根据在当前材质中的排序自动改变名称为czm_getMaterial_x,并且与当前材质中相应的czm_getMaterial_x函数对应。

// 创建子材质
function createSubMaterials(material) {
  // 严格模式
  var strict = material._strict;

  // 子材质
  var subMaterialTemplates = material._template.materials;
  // 遍历子材质名称
  for (var subMaterialId in subMaterialTemplates) {
    if (subMaterialTemplates.hasOwnProperty(subMaterialId)) {

      // Construct the sub-material.
      // 构造子材质
      var subMaterial = new Material({
        strict: strict,
        fabric: subMaterialTemplates[subMaterialId],
        count: material._count,
      });

      // 子材质数量??
      material._count = subMaterial._count;

      // 合并所有的材质中的unfiorm
      material._uniforms = combine(
        material._uniforms,
        subMaterial._uniforms,
        true
      );

      // 子材质
      material.materials[subMaterialId] = subMaterial;

      // 透明度数组合并
      material._translucentFunctions = material._translucentFunctions.concat(
        subMaterial._translucentFunctions
      );

      // Make the material's czm_getMaterial unique by appending the sub-material type.
      // 针对子材质中的 czm_getMaterial创建新的方法: czm_getMaterial_0替换原来的方法
      var originalMethodName = "czm_getMaterial";
      var newMethodName = originalMethodName + "_" + material._count++;

      // 使用新的方法名,替换原来的所有方法名
      replaceToken(subMaterial, originalMethodName, newMethodName);
      // 子材质中的源码 与 材质源码 合并
      material.shaderSource = subMaterial.shaderSource + material.shaderSource;

      // Replace each material id with an czm_getMaterial method call.
      // 使用czm_getMaterial方法替换每一个材质
      var materialMethodCall = newMethodName + "(materialInput)";   
      // 将材质源码中的elevationRampMaterial.alpha子材质材质名称 使用 czm_getMaterial_1(materialInput).alpha 替换
      // 将材质中与子材质相关的名称,替换成方法,方法中就可以从子材质的输出图像中获取纹理了??
      var tokensReplacedCount = replaceToken(
        material,
        subMaterialId,
        materialMethodCall
      );
      //>>includeStart('debug', pragmas.debug);
      if (tokensReplacedCount === 0 && strict) {
        throw new DeveloperError(
          "strict: shader source does not use material '" + subMaterialId + "'."
        );
      }
      //>>includeEnd('debug');
    }
  }
}

而材质的具体

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值