Cesium中的图形技术:Fabric —— 材质JSON规范

1. 简介

Fabric 是 Cesium 中用于描述材质的一种 JSON 规定。数组

材质表现了多边形、折线、椭圆等形状的外观。缓存

使用 Fabric 和 GLSL,能够彻底自定义材质。app

经过几何对象的 material 属性能够建立材质,这个属性是 Cesium.Material 对象。函数

能够这么用:this

// 假设 polygon 是一个 primitive
polygon.appearance.material = Cesium.Material.fromType('color');

这就建立了一个只有颜色的材质,包括透明度的颜色。Cesium.Material.fromType() 方法是一个简写,完整的写法是:编码

polygon.appearance.material = new Cesium.Material({
  fabric: {
    type: 'Color' // 大写
  }
})

每个 Material 均可以有 0 ~ N 个 uniform,这个参数在建立时指定,也能够在渲染后修改。例如,color 类型的 Material 就有格式为 rgba 的颜色 uniform:url

polygon.appearance.material = new Cesium.Material({
  fabric: {
    type: 'Color',
    uniforms: {
      color: new Cesium.Color(1.0, 0.0, 0.0, 0.5)
    }
  }
})

// 修改颜色
polygon.appearance.material.uniforms.color = Cesium.Color.WHITE

2. 内置材质(共计23种,ver1.75)

Cesium 有几个内置的材质。列举两个比较经常使用的:3d

材质类型 截图 描述
type: 'Color' 一个简单的颜色,包括透明通道
type: 'Image' jpg 或 png 贴图类型的材质

全部的内置材质能够简单地使用 Cesium.Material.fromType() 方法建立:code

polygon.appearance.material = Cesium.Material.fromType('Image')
polygon.appearance.material.uniforms.image = 'image.png'

或者用全写法:component

polygon.appearance.material = new Cesium.Material({
  fabric: {
    type: 'Image',
    uniforms: {
      image: 'image.png'
    }
  }
})

从这儿开始,介绍因这个 fabric 对象中的 type 不一样的十几种内置纹理,2.1~2.5

2.1. 机器生成的规律纹理(4种)

只需指定几个参数,就能够生成一些有规律的纹理贴图,不须要依赖外部贴图文件。它们至关于漫反射+透明度的组合。

类型 截图 描述
type: 'Checkerboard' 国际象棋格子
type: 'Stripe' 竖条纹旗帜
type: 'Dot' 行列点阵
type: 'Grid' 线状网格,显示一些网状结构的图形

2.2. 基础材质(6种)

基础材料表达的是各个材质因子表示的材料特征,例如镜面反射强度、自发光。一般,组合在一个 fabric 对象中建立复杂的材质。

注:若是不懂这些东西,能够请教技术美工。

类型 截图 描述
type: 'DiffuseMap' 漫反射贴图,即最多见的贴图,一般是 rgb 三个颜色
type: 'SpecularMap' 单通道贴图,表示的是入射光强度贴图
type: 'AlphaMap' 单通道的不透明度贴图
type: 'NormalMap' 三通道贴图,表示的是法线贴图
type: 'BumpMap' 单通道的凹凸贴图
type: 'EmissionMap' 三通道的自发光贴图

2.3. 折线材质(3种)

折线材质只做用于折线图形。

类型 截图 描述
type: 'PolylineArrow' 箭头线,终点在折线末端
type: 'PolylineGlow' 发光线
type: 'PolylineOutline' 描边线

2.4. Misc 材质(2种)

还有一些材质不属于上面的分类,例如:

类型 截图 描述
type: 'Water' 水面贴图,看起来有水波动效
type: 'RimLighting' 边缘会比较亮

2.5. 公共 uniforms

许多材质是有 image 的,多是一个 base64 编码的字符串或文件路径:

polygon.appearance.material.uniforms.image = 'image.png';
polygon.appearance.material.uniforms.image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAC/SURBVDhPrZPRDYQgEEQpjVKuFEvhw0IoxU6QgQwMK+vdx5FsooT3GHdjCM4qZnnnHvvkYoxFi/uvIhwiRCClXFC6v5UQ1uQAsbrkHCLsbaPjFgIzQQc1yUOwu33ePGE3BQUaee2BpjhbP5YUmkAlbNzsAURfBDqJnMIyyv4JjsCCgCnIR32uZUfcJuGBOwEk6bOKhoAADh31EIq3MgFg1mgkE1BA2AoUZoo2iZ3gyqGgmMDC/xWwkfb3/eUd7A1v3kxjNW9taQAAAABJRU5ErkJggg==';

有的材质要求贴图有三个颜色份量,而其余材料(如高光和透明贴图)的贴图只须要一个颜色份量。

能够指定贴图的通道。例如,镜面反射的材质中,镜面反射默认取贴图的自 r 颜色通道,可是能够修改它为 a 通道:

polygon.appearance.material = new Cesium.Material({
  fabric : {
    type : 'SpecularMap',
    uniforms : {
      image : 'specular.png',
      channel : 'a'
    }
  }
});

这意味着,容许把各类贴图集中到一个贴图文件种,而后使用不一样的通道便可,减小加载请求次数。

有些材质的贴图纹理能够重复屡次绘制,例如水平或垂直上的重复:

polygon.appearance.material = new Cesium.Material({
  fabric: {
    type: 'DiffuseMap',
    uniforms: {
      image: 'diffuse.png',
      repeat: {
        x: 10,
        y: 2
      }
    }
  }
})

3. 建立新材质

使用 fabric 对象 + GLSL 代码和其余素材,就能够建立材质。

若是一个材质不想被复用,那么就不要指定它的 type 属性。

let fabric = {
  // ...
}

polygon.appearance.material = new Cesium.Material({
  fabric: fabric
})

当 fabric 对象中的 type 属性以前是没有指定过的,那么在第一次调用 new Cesium.Material() 时,这个新的 fabric 材质将被缓存,随后再次 new Material 或 Material.fromType() 时将从缓存中取用。

let fabric = {
   type : 'MyNewMaterial',
   // ...其余 fabric JSON 的属性
}
polygon.appearance.material = new Cesium.Material({
  fabric : fabric
});
// ... 而后在另外一处须要这个 fabric
anotherPolygon..appearance.material = Material.fromType('MyNewMaterial');

3.1. 组件

白色的漫反射材质或许是最经常使用的:

let fabric = {
  components: {
    diffuse: 'vec3(1.0)'
  }
}

稍微复杂一些,加一点镜面反射,使得正射视角看反光最强,侧面变弱:

let fabric = {
  components : {
    diffuse : 'vec3(0.5)',
    specular : '0.1'
  }
}

components 属性包含了 fabric 所定义的材质的各类子因素。每一个子因素均使用简短的 glsl 代码字符串表示,所以上面写的 vec(0.5) 就表示 rgb 均为 0.5 的一种颜色。这个简单的 glsl 代码可使用全部 glsl 内置的函数,例如 mix、cos、texture2D 等。components 能够定义 6 个属性:

名称 默认值 描述
diffuse 'vec3(0.0)' 漫反射颜色,即物体的基本颜色
specular '0.0' 镜面反射,定义的是单方向反射光强度
shininess '1.0' 镜面反射的清晰度,这个值越大会出现更小的高光光斑
normal 法线,默认没法线
emission 'vec3(0.0)' 自发光,默认不发光
alpha '1.0' 不透明度,0.0 是彻底透明,1.0 是不透明。

3.2. 源代码

components 还有一个更强大而灵活的选择是 glsl 源代码,经过 glsl 的方式修改材质。这个途径将设置的 glsl 代码传递到 czm_getMaterial 函数,这个函数执行后返回材质的 components:

struct czm_materialInput
{
  float s;
  vec2 st;
  vec3 str;
  mat3 tangentToEyeMatrix;
  vec3 positionToEyeEC;
  vec3 normalEC;
};

struct czm_material
{
  vec3 diffuse;
  float specular;
  float shininess;
  vec3 normal;
  vec3 emission;
  float alpha;
};

czm_material czm_getMaterial(czm_materialInput materialInput);

默认状况下,材质的默认值会被返回:

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    return czm_getDefaultMaterial(materialInput);
}

这个时候的 fabric 对象是:

let fabric = {
  components: {
    source: `czm_material czm_getMaterial(czm_materialInput materialInput) { return czm_getDefaultMaterial(materialInput); }`
  }
}

上面修改了漫反射和镜面反射的例子能够经过 glsl 改写为:

let fabric = {
  source: `czm_material czm_getMaterial(czm_materialInput materialInput)
           {
          	  czm_material m = czm_getDefaultMaterial(materialInput);
          	  m.diffuse = vec3(0.5);
           	  m.specular = 0.5;
              return m;
           }`
}

使用 glsl 代替 components 虽然看起来代码比较冗长,可是提供了灵活性。

若是不是有特别的需求,使用 components 属性指定材质的各类因子就能够了。可是,不论是哪种,在这些 glsl 代码中,都是能够直接使用 glsl 的内置函数和 Cesium 预约义的 函数、结构体、常量的。

3.3. 输入

materialInput 变量在 source 和 components 中都可以使用,在 glsl 代码的定义中,这个变量是 czm_materialInput 结构体有以下字段:

名称 类型 描述
s float 一维纹理坐标
st vec2 二维纹理坐标
str vec3 三维纹理坐标,三维纹理的二维部分不必定就是二维纹理坐标,切记。例如,在一个椭球几何中,s可能就是从下到上,st多是经纬度,str三维纹理就是包围盒的三轴方向。
tangentToEyeMatrix mat3 用于法线贴图、凹凸贴图的转换矩阵,转换切线空间坐标到视图坐标
positionToEyeEC vec3 从 fragment 到 视图空间坐标的向量(不知道这个 fragment 说的是什么),用于反射和折射等。向量的长度是 fragment 到视图(相机)的距离。
normalEC vec3 fragment 在视图坐标中的法线(已归一化),做用于凹凸贴图、反射、折射等

例如能够这么设置来可视化纹理坐标:

let fabric = {
  components: {
    diffuse: 'vec3(materialInput.st, 0.0)'
  }
}

同样的,能够把 diffuse 组件设置为 materialInput.normalEC 来可视化法线。

除了 materialInput 这个传入的参数,还能够访问 Cesium 提供的 uniform 变量。

例如,能够经过一个 color uniform 去设置 diffuse 组件和 alpha 组件,来建立本身的 Color 材质:

let fabric = {
  type: 'MyColor',
  uniforms: {
    color: new Color(1.0, 0.0, 0.0, 1.0)
  },
  components: {
    diffuse: 'color.rgb',
    alpha: 'color.a'
  }
}

在 fabric 中,glsl 中的 uniform 变量、new Cesium.Material()Cesium.Material.fromType() 返回的 js 对象中的 uniform 变量与 uniforms 属性的子属性(例如这里的 uniforms.color)具备相同的名称。

子属性的值(对于标量来讲)或子属性(对于向量来讲)即 uniform 的值。

官方这说的什么东西...

下例,经过 image uniform 来实现自定义的 DiffuseMap 材质:

let fabric = {
  type: 'OurDiffuseMap',
  uniforms: {
    image: 'czm_defaultImage'
  },
  components: {
    diffuse: 'texture2D(image, materialInput.st).rgb'
  }
}

czm_defaultImage 是 1x1 分辨率的图片,根据上面的说法,这能够从 dataurl 或图片文件中获取,例如:

polygon.appearance.material = Material.fromType('OurDiffuseMap');
polygon.appearance.material.uniforms.image = 'diffuse.png';

还有一个多维数据集的变量:czm_defaultCubeMap

支持 glsl 的 uniform 类型,例如 float、vec三、mat4 等。

对于 uniform 数组还不支持,不过在规划中了。

译者注

这段是真够烧脑子的,不知所云。

梳理一下,在 fabric 这个对象中,uniforms 下的全部属性均被 glsl 认做是 uniform 变量,能够直接当变量使用,例如上例中的 texture2D(image, materialInput.st) 中的 image 参数。

在这里,规定了 image 这个 uniform 的类型是 Cesium 内置的 czm_defaultImage 结构体类型。

3.4. 复合材质

到如今为止,可使用内置的材质或者经过指定 fabric 中的 components 属性(或直接使用 glsl 源代码)来建立材质对象。

还能够经过现有材质来建立复合类型的材质。

fabric 对象有一个 materials 属性,它的每个子对象都可以是 fabric 对象,最终即一个材质对象。在 materials 中设置的子一级 fabric,能够在最顶级的 fabric 的 components、source 中引用。例如,如今将 DiffuseMap 材质 和 SpecularMap 材质建立一个复合材质:

let rootFabric = {
  type: 'OurMappedPlastic',
  materials: {
    diffuseMaterial: {
      type: 'DiffuseMap' // 基本类型中的一种
    },
    specularMaterial: {
      type: 'SpecularMap'
    }
  },
  components: {
    diffuse: 'diffuseMaterial.diffuse',
    specular: 'specularMaterial.specular'
  }
}

这个 rootFabric 材质拥有两个组件:diffuse(漫反射)和 specular(镜面反射强度),而这两个组件的值是来自 materials 中的两个子 fabric。显然,在 materials 定义的两个子属性的名称,将会在 components 的 glsl 代码中被做为变量使用,直接点出 diffuse 和 specular 字段。

而后就能够像其余材质同样使用它了:

let m = Cesium.Material.fromType('OurMappedPlastic')
polygon.appearance.material = m;

m.materials.diffuseMaterial.uniforms.image = 'diffuseMap.png';
m.materials.specularMaterial.uniforms.image = 'specularMap.png';

4. 语法规定

用了这么多 fabric 对象,其实这个 fabric 对象是有一些规定的。

在 Cesium 的官方打包包中,找到 Documentation/Schemas/Fabric,就是 fabric 对象的规定。诸如 typematerialsuniformscomponentssource 这几个属性均能找到详细的定义。

5. 渲染流水线中的材质

从渲染的角度看,一种材质实际上是一个 glsl 函数:czm_getMaterial。片元着色器须要构造一个 czm_MaterialInput 结构体变量,调用 czm_getMaterial 函数,而后生成 czm_material 结构变量,传递给照明函数来计算片元的颜色。

在 js 中,fabric 对象应该有一个 material 属性。当此属性发生变更时,图元的 update 函数触发,而后将 fabric 材质最终的 glsl 代码与默认的片元着色器代码合并在一块儿,而后再将 uniform 合并:

const fsSource = this.material.shaderSource + ourFragmentShaderSource;
this._drawUniforms = combine([this._uniforms, this.material._uniforms]);

6. 笔者注

这篇文章较为详细地介绍了 Appearance 对象中的 fabric 属性构成。

fabric 是一个有官方规定如何写的 js 对象,它拥有 5 个属性:

  • type
  • materials
  • source
  • components
  • uniforms

其中,type 用于声明 fabric 对象最终会生成什么材质,若是是官方内置的,直接用官方内置的(2.1~2.4),不然则建立自定义的材质并缓存。

materials 容许再塞进去子一级的 fabric,构成复合材质。

uniforms 是一些全局变量,例如你能够在这里写一个 myUniformVariable,而后你就能够在 components 或者 source 的 glsl 代码中用到这个 uniform 变量了。

source 是 glsl 源代码,它主要是对 czm_getMaterial 这个 Cesium 内置的 glsl 函数的实现,返回值是 czm_material

components 是几个基本材质因子的 glsl 代码快捷入口,是 source 的一种简略实现。

建立好 fabric 材质对象后,随之就能够建立 Appearance 对象,与几何实例一块儿建立 Primitive 了。

相关文章
相关标签/搜索