1. 程式人生 > >Cesium官方教程12--材質(Fabric)

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開發者社群