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); }'
}
下面的示例程式碼,只設置了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]);
中國最專業的Cesium開發者社群