Cesium官方教程12--材质(Fabric)

原文地址:https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric

介绍

Fabric 是Cesium中基于JSON格式来描述materials的机制。材质描述多边形、折线、椭球等对象的外观特征。
材质可以简单的是覆盖一张图片,或者是条纹或者棋盘图案。使用Fabric 和GLSL,可以从零开始写脚本新建材质,也可以从现有的材质中派生。比如潮湿碎裂的砖块可以使用程序生成的纹理、凹凸贴图和反射贴图来组合。
对象通过material 属性来支持材质效果。当前这些对象是多边形、折线、椭球等(这篇文章写的较早,其实现在已经很多几何体都支持材质了)。

polygon.material = Material.fromType('Color');

上面,Color是一个内置材质,它表示了包含透明度在内的一个颜色值。Material.fromType是简略写法,完整的Fabric的JSON应该是这样的:

polygon.material = new Cesium.Material({
  fabric : {
    type : 'Color'
  }
});

每一个材质包含0或者更多个uniforms,uniform是一种输入参数变量,在创建材质时或者创建材质后修改。比如 , Color有一个 color uniform ,它包含red, green, blue, 和alpha四个部件。

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

// 把红色半透明修改为 白色不透明
polygon.material.uniforms.color = Cesium.Color.WHITE;

内置材质

Cesium有一些内置材质,应用最广泛的是这两个:

名称 效果图 描述
Color
包含透明通道的颜色值
Image
jpg或者png格式的图片,可以带透明通道,用rgb表示颜色,a表示透明度

如同上面的 Color 一样,所有的内置材质都可以这么创建。比如:

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

或者

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

程序生成的纹理 (Procedural Textures)

程序生成的纹理,他们不依赖于外部图片文件,是通过GPU编程计算的图案,他们可以表示颜色和透明。

名称 效果图 描述
棋盘图Checkerboard
明暗交替组成的棋盘图。
条纹Stripe
水平或者垂直方向明暗交替的图案
斑点Dot
按行列排列的点组成
网格Grid
网格边线,描述3D体的时候有用

基本材质

Base materials represent fine-grain fundamental material characteristics, such as how much incoming light is reflected in a single direction, i.e., the specular intensity, or how much light is emitted, i.e., the emission. These materials can be used as is, but are more commonly combinedusing Fabric to create a more complex material.

名称 效果图 描述
漫反射贴图DiffuseMap
一张图片定义了光在所有方向上的散射颜色,一般是个三维向量(vec3
高光反射贴图 SpecularMap
一张图片,定义了光在某一个方向上的反射颜色 ,一般是个标量(scalar),通常用来模拟某个光亮的平面,比如陆地上的水面。
透明贴图AlphaMap
一张图片,定义了材质透明度 ,一般是个标量(scalar)。通常让一部分表面透明或者半透明,比如栅栏
法向贴图NormalMap
个人感觉原文这张法向贴图不太对
一张图片,定义了在切线空间定义了表面的法向量,一般是个三维向量( vec3)。法向贴图在不增加几何体复杂度的前提下,提升了表面渲染的细节
凹凸贴图BumpMap
一张图片,定义了表面的高度 ,一般是个标量(scalar)。就像法向贴图,也可以在不增加几何体复杂度的前提下,提升了表面渲染的细节 ,它通过相邻像素之间的差异来微调法向量
自发光贴图 EmissionMap
一张图片,定义了材质在所有方向上发光颜色,一般是个三维向量(vec3)。比如走廊里的灯泡

折线材质

这只一种只能添加到折线几何体上的材质。

名称 效果图 描述
带箭头折线PolylineArrow
在折线尾端增加一个箭头
泛光折线 PolylineGlow
折线泛光
带边界折线PolylineOutline
带边界折线

其他材质

还有一些不适合归到其他类的材质

名称 效果图 描述
水面 Water
带波纹动画的水面
外轮廓高亮 RimLighting
高亮边缘或者轮廓

了解更多材质,可以去看下这个 Cesium Materials Plugin.

通用的Uniforms

很多材质都有一个image uniform,它是一个图片访问地址,或者数据URI。

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

一些材质,比如Diffuse 和 NormalMap 都要求图片至少有RGB三个通道。另一个材质,比如Specular和Alpha要求图片有一个通道。我们可以指定渲染的时候从哪些通道(或者什么顺序)从原始图片中读取数据,通过 channel这个字符串uniform来设置。比如,默认Specular材质是从 r读取高光反射参数。不过我们可以如下修改它:

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

这就是说可以把多个材质的信息放到一个图片里,比如在同一个图片内,用rgb通道存储diffuse值,用a通道存储specular值。也就是说,我们的图片只需要载入一次。
通常材质里有一个repeat uniform,它控制了图片在水平和垂直方向重复了多少次。这个在表面重复贴图的时候很方便:

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

创建新的材质

使用Fabric,只需要一点点GLSL或者其他材质就可以了。
如果不打算复用材质,那么不要设置type参数。

var fabric = {
   // 没有类型
   //fabric的剩余JSON值
};
polygon.material = new Cesium.Material({
  fabric : fabric
});

当在new Cesium.Material时,传入一个不存在的 type类型之后,这个材质将被缓存下来。下次调用 new Cesium.Material 或者 Material.fromType 就会引用缓存里的,就如同我们内置的材质一样,那时候就不需要提供整个Fabric的定义,而仅仅传递 type以及想更改的 uniforms值。

var fabric = {
   type : 'MyNewMaterial',
   //剩余JSON值
};
polygon.material = new Cesium.Material({
  fabric : fabric
});
//再次使用的时候,只需要这样
anotherPolygon.material = Material.fromType('MyNewMaterial');

Components

或许最简单有趣的材质就是纯白色散射光:

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

稍微复杂一点,增加一个高光元素,当视角正对反射光的时候更亮一些,当视角在边上的时候稍微亮一些。

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

components属性包含了 定义了材质外观的子属性。每个子属性是一个GLSL的代码段,比如上面的vec3(0.5) ,它实际创建了一个三维向量,每个分量都设置为 0.5。这里可以访问所有的GLSL函数,包括 mix,cos,texture2D`等等。现在有5种子属性:

名称 默认值 说明
diffuse 'vec3(0.0)' 材质的散射光通道,使用 vec3定义了光在所有方向的散射值
specular 0.0 材质的高光属性。这个定义了材质的反射强度。
shininess 1.0 高光反射的锐度,值越大越创建一个更小的高亮光斑
normal 材质的法向属性。使用 vec3定义了在视点空间的表面法向量。一般在法向贴图上使用。默认是表面法向量。
emission 'vec3(0.0)' 材质的自发光属性。使用 vec3定义了所有方向上灯光发出的颜色。 默认是vec3(0.0),没有自发光。
alpha 1.0 材质的透明度。 使用一个float值定义,0.0表示全透明; 1.0表示不透明。

综上所述,子属性或者components 定义了材质的特点。他们是材质的输出值,是光照系统的输入值。

代码

提供完整的GLSL代码是一种比前面 components 更灵活的方式。通过自定义czm_getMaterial函数,返回材质的各个分量。代码如下:

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 这么定义:

{
  source : 'czm_material czm_getMaterial(czm_materialInput materialInput) { return czm_getDefaultMaterial(materialInput); }'
}

下面的示例代码,只设置了diffusespecular分量的值:

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_materialInput m = czm_getDefaultMaterial(materialInput);
    m.diffuse = vec3(0.5);
    m.specular = 0.5;
    return m;
}

source相对 components更加繁琐,但是更灵活,比如定义一些公用的函数,共享每个分量的计算过程等等。有个原则就是优先使用components属性,除非明确需要实现 czm_getMaterial函数。也就是说 components的子属性实际也是实现czm_getMaterial函数。而两种方式下,我们都可以访问GLSL 的内置函数和Cesium提供的GLSL函数(functions), 变量(uniforms), and 常量(constants)(链接已失效)。

材质输入

materialInput 变量在sourcecomponents属性中都可以配置。它具有下面的字段,用来计算材质分量:

名称 默认值 说明
s float 一维纹理坐标
st vec2 二维纹理坐标
str vec3 三维纹理坐标。注意这些维度不同的纹理坐标不一定分量相同,也就是说不能保证 str.st == stst.s == s。比如对于椭球体。一维纹理坐标s是从下到上。二维纹理坐标st是经度纬度。三维纹理坐标str是沿着坐标轴的包围盒
tangentToEyeMatrix mat3 从片元的切线空间转到视点空间的转换矩阵,在法向贴图和凹凸贴图时使用。
positionToEyeEC vec3 从片元到视点之间的向量,为了反射和折射计算。向量的模表示了从片元到视点的距离。
normalEC vec3 片元在视点空间的单位化后的法向量,在凹凸贴图、反射、折射的时候使用。

把纹理坐标的st值显示出来的简单方法:

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

类似的,查看视点坐标下的法向量,只需要 把materialInput.normalEC 设置到 diffuse 分量上。
除此之外,在materialInput里,可以访问uniforms变量,包括Cesium 提供的内置变量 uniforms 和 材质设置的uniforms变量。比如,我们可以设置自定义的Color材质,依据一个color 变量来设置diffusealpha

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

Fabric中,uniform属性的子属性是GLSL中的uniform变量名 ,也是 new MaterialMaterial.fromType返回中JavaScript的对象属性名。子属性的值也是GLSL中uniform变量的值。(这块意思就是说uniform下的属性和值在GLSL的GPU环境和js的内存环境中一致的)。
可以通过一个自定义的 image变量来实现材质的DiffuseMap

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

上面代码里,'czm_defaultImage'是一个1x1的图片。前面说过,这个值可以是一个图片URL地址或者 数据URI。比如用户可以使用我们自定义的OurDiffuseMap 材质,这么来设置纹理:

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

也有一个内置的立体贴图:czm_defaultCubeMap。GLSL 标准的uniform变量类型float, vec3, mat4都是支持的。Uniform数组还不支持,但是已经在计划内 roadmap

材质的合并

至此,我们可以使用内置的材质,可以通过设置材质的components来自定义 ,或者实现完整的GLSL代码source来自定义。我们还可以通过继承已有的材质来新建材质。
Fabric 有个materials属性,它的每个子属性也是Fabric材质。他们的材质可以可以在 components 或者source 中引用。比如一个塑料材质可以通过 DiffuseMapSpecularMap两个材质的合并来模拟。

{
  type : 'OurMappedPlastic',
  materials : {
    diffuseMaterial : {
      type : 'DiffuseMap'
    },
    specularMaterial : {
      type : 'SpecularMap'
    }
  },
  components : {
      diffuse : 'diffuseMaterial.diffuse',
      specular : 'specularMaterial.specular'
  }
};

这个材质的diffusespecular 都是从其他材质中提取的。子属性的名字叫diffuseMaterialspecularMaterial(根据类型 DiffuseMapSpecularMap创建的材质。不要搞混 类型 和 实例对象的名称,在 componentssource 属性中,子材质通过名称访问,因为他们都是一个czm_material 结构,所以可以访问.diffuse.specular分量。
基于这个Fabric材质,可以这么用我们的材质:

var m = Material.fromType('OurMappedPlastic');
polygon.material = m;

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

Fabric 格式

Fabric 是基于JSON 格式格式定义。这格式定义里详细描述了Fabric的属性和子属性,包括 type, materials, uniforms, components, 和 source等。那里面有一些JSON的格式示例,但是没有必要去看。
对于一些严格要求的Fabric文件,可以使用一些类似 JSV的工具去验证Fabric格式。

渲染流水线中的材质

Polygon, PolylineCollection, Ellipsoid, CustomSensorVolume等几何体 已经 和材质系统集成。大部分用户只需要简单的设置material就可以了。可是,用户还是想实现自己的材质渲染代码。直接了当的去做就行了。

在渲染阶段,材质就是一段GLSL函数czm_getMaterial 和 一些uniform变量。片段着色器需要构造一个 czm_MaterialInput结构,然后调用czm_getMaterial方法,把获得的 czm_material 结果传递给光照处理函数去计算图元颜色。
在JavaScript代码里,这些对象应该有一个 material属性。当这个属性变换的时候,update 函数应该把材质的GLSL代码转为对象的片段着色器代码,并且把对象和材质的uniform变量合并起来。

var fsSource =  this.material.shaderSource +  ourFragmentShaderSource;

this._drawUniforms = combine([this._uniforms, this.material._uniforms]);
中国最专业的Cesium开发者社区

推荐阅读更多精彩内容