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

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

基本材质
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); }'
}
下面的示例代码,只设置了diffuse
和 specular
分量的值:
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
变量在source
和 components
属性中都可以配置。它具有下面的字段,用来计算材质分量:
名称 | 默认值 | 说明 |
---|---|---|
s |
float |
一维纹理坐标 |
st |
vec2 |
二维纹理坐标 |
str |
vec3 |
三维纹理坐标。注意这些维度不同的纹理坐标不一定分量相同,也就是说不能保证 str.st == st 和st.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 变量来设置diffuse
和alpha
。
{
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 Material
和 Material.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
中引用。比如一个塑料材质可以通过 DiffuseMap
和SpecularMap
两个材质的合并来模拟。
{
type : 'OurMappedPlastic',
materials : {
diffuseMaterial : {
type : 'DiffuseMap'
},
specularMaterial : {
type : 'SpecularMap'
}
},
components : {
diffuse : 'diffuseMaterial.diffuse',
specular : 'specularMaterial.specular'
}
};
这个材质的diffuse
和specular
都是从其他材质中提取的。子属性的名字叫diffuseMaterial
和specularMaterial
(根据类型 DiffuseMap
和SpecularMap
创建的材质。不要搞混 类型 和 实例对象的名称,在 components
和source
属性中,子材质通过名称访问,因为他们都是一个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]);

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!