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 了。

相關文章
相關標籤/搜索