1. 程式人生 > >【Cesium 顏狗初步】fabric 材質定義與自定義著色器實踐

【Cesium 顏狗初步】fabric 材質定義與自定義著色器實踐

fabric 材質定義:著色器實踐 # 1. 示例程式碼 貼到沙盒裡就可以執行: ``` JS var viewer = new Cesium.Viewer("cesiumContainer"); viewer.scene.globe.depthTestAgainstTerrain = true; viewer.camera.setView({ destination : new Cesium.Cartesian3(-2644963.9889313546, 5763731.142118295, 2199400.7089496767), //世界座標系下的一個座標點 orientation : {//旋轉角度 heading :6.075, pitch :-0.727, roll : 6.283 } }); const extrudedPolygon = new Cesium.PolygonGeometry({ polygonHierarchy : new Cesium.PolygonHierarchy( Cesium.Cartesian3.fromDegreesArray([ 112.41726298378288, 23.290411251106182, 113.67072522399741, 23.560312361463682, 114.09370956893551, 22.590768298743153, 112.83803246418894, 22.285610818885644 ]) ), extrudedHeight: 3000 }); const instance = new Cesium.GeometryInstance({ geometry: extrudedPolygon, id: 'box with height' }); const m = new Cesium.Material({ fabric: { type: 'Color', uniforms: { color: new Cesium.Color(216 / 255.0, 170 / 255.0, 208 / 255.0).withAlpha(0.618), }, } }); const aper = new Cesium.MaterialAppearance({ material : m, }); var p = viewer.scene.primitives.add(new Cesium.Primitive({ geometryInstances: instance, appearance: aper, releaseGeometryInstances: false, compressVertices: false, })); // p.readyPromise.then(v => console.log(v)); const vs = aper.vertexShaderSource; const fs = aper.fragmentShaderSource; const fs2 = aper.getFragmentShaderSource(); console.log(`// 頂點著色器: ${vs}`); console.log(`// 片元著色器: ${fs}`); console.log(`// 片元著色器2: ${fs2}`); ``` # 2. 修改 fabric 物件 ``` js const m = new Cesium.Material({ fabric: { source: `float a = 12.0;`, } }); ``` 隨便定義一個浮點數,發現報錯: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164202561-879569688.png) 加上 `uniform` 限定字,報錯稍微改了一點: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164215266-1918184186.png) 所以,這個 source 是有規則的。 ## 2.1. 必須擁有函式 czm_getMaterial(czm_materialInput materialInput) 我改成這樣: ``` JS source: `czm_material czm_getMaterial(czm_materialInput materialInput) { }`, ``` 報錯變化了: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164230140-1441860951.png) 大意是指,`czm_getMaterial` 這個函式沒有返回值。這很正常,強型別的 GLSL 規定了這個函式的返回值型別是結構體 `czm_material`,那麼再次修改它。 ## 2.2. 必須有返回值:不妨返回個預設值 ``` JS source: `czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); return material; }`, ``` 這時,形狀有顏色了: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164243709-1700595121.png) material 這個變數是一個結構體,通過修改其材質因子即可實現材質修改。 修改其漫反射因子: 注意,glsl 中建立結構體 vec3 的預設值是 (0, 0, 0),現在我想要個粉色,rgb色值是:(216 / 255.0, 170 / 255.0, 208 / 255.0),即 (0.8470588235294118, 0.66666666, 0.8156862745098039) ``` JS source: `czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); material.diffuse = vec3(0.8470588235294118, 0.66666666, 0.8156862745098039); return material; }`, ``` 沒毛病,顏色出來了: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164257630-2086772922.png) ## 2.3. 頂點著色器與片元著色器 你可以在很多個地方獲取材質、外觀的著色器原始碼: - Material.prototype.shaderSource:可讀可寫:當前 material 物件的 source 屬性,支援實時修改 - Appearance.prototype.vertexShaderSource:只讀:當前外觀物件的頂點著色器,僅支援構造時傳入 - Appearance.prototype.fragmentShaderSource:只讀:當前外觀物件的片元著色器,僅支援構造時傳入 - Appearance.prototype.getFragmentShaderSource():返回最終完全版片元著色器原始碼。 上面在 fabric 物件中的 source 屬性指定的 glsl 原始碼,與 console.log(m.shaderSource) 出來的是完全一樣的,所以此處忽略。 當通過 2.2 節中對漫反射屬性的設定後,外觀物件的 `vertexShaderSource` 和 `fragmentShaderSource` 輸出結果如下: ``` glsl // 頂點著色器: attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; attribute vec2 st; attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; varying vec2 v_st; void main() { vec4 p = czm_computePosition(); v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates v_normalEC = czm_normal * normal; // normal in eye coordinates v_st = st; gl_Position = czm_modelViewProjectionRelativeToEye * p; } ``` ``` glsl // 片元著色器: varying vec3 v_positionEC; varying vec3 v_normalEC; varying vec2 v_st; void main() { vec3 positionToEyeEC = -v_positionEC; vec3 normalEC = normalize(v_normalEC); #ifdef FACE_FORWARD normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC); #endif czm_materialInput materialInput; materialInput.normalEC = normalEC; materialInput.positionToEyeEC = positionToEyeEC; materialInput.st = v_st; czm_material material = czm_getMaterial(materialInput); #ifdef FLAT gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #else gl_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC); #endif } ``` ### 捕獲重點:片元著色器 觀察片元著色器程式碼中的主函式,其中有一句呼叫 `czm_material material = czm_getMaterial(materialInput);` 這一句便是對我們在 fabric 物件中寫入的 glsl 程式碼的呼叫。 最終,進入 `#ifdef FLAT` 分支(才疏學淺,不知道這個 FLAT 巨集是什麼),對畫素著色,使用 material 結構的漫反射因子 + 自發光因子 + 透明度因子進行疊加,生成最終的顏色值。所以,這個時候不妨回到 Material 的 source 中,繼續動手腳。 > 是存在直接修改 Appearance 物件 fragmentShader、vertexShader 的大佬的,後面有機會展開說說。 ## 2.4. 牛刀小試:發個光吧 ``` js source: `czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); material.diffuse = vec3(0.8470588235294118, 0.66666666, 0.8156862745098039); material.specular = 1.0; material.shininess = 0.8; return material; }`, ``` ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164318048-1458902949.png) (我偷偷把高度設為了 30000,不然不太明顯) 換個地圖和引數: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164329718-301487639.png) 稍微有那麼一點感覺了。 ``` JS const m = new Cesium.Material({ translucent: false, fabric: { source: `czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); material.diffuse = vec3(0.24313725490196078, 0.7372549019607844, 0.9333333333333333); material.specular = 0.5; material.shininess = 0.8; material.emission = vec3(0.0, 0.66666666, 0.0); return material; }`, } }); ``` # 3. *高階運用:直接修改 Appearance 的片元著色器 用的是 1. 中的程式碼,修改 aper 物件的構造引數,直接將 2.3 中的片元著色器程式碼貼入看看: ``` js const aper = new Cesium.MaterialAppearance({ fragmentShaderSource: ` varying vec3 v_positionEC; varying vec3 v_normalEC; varying vec2 v_st; void main() { vec3 positionToEyeEC = -v_positionEC; vec3 normalEC = normalize(v_normalEC); #ifdef FACE_FORWARD normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC); #endif czm_materialInput materialInput; materialInput.normalEC = normalEC; materialInput.positionToEyeEC = positionToEyeEC; materialInput.st = v_st; czm_material material = czm_getMaterial(materialInput); #ifdef FLAT gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #else gl_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC); #endif } ` }); ``` 沒有問題: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164347504-437889576.png) 所以,基於此模板,只要膽大心細(劃掉)只要對 Cesium 內建的著色器足夠了解,完全可以自己改頂點和片元著色器。 ### 試一試:把 2.2 和 2.4 中的嘗試加入 ``` JS const aper = new Cesium.MaterialAppearance({ fragmentShaderSource: ` varying vec3 v_positionEC; varying vec3 v_normalEC; varying vec2 v_st; void main() { vec3 positionToEyeEC = -v_positionEC; vec3 normalEC = normalize(v_normalEC); #ifdef FACE_FORWARD normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC); #endif czm_materialInput materialInput; materialInput.normalEC = normalEC; materialInput.positionToEyeEC = positionToEyeEC; materialInput.st = v_st; czm_material material = czm_getMaterial(materialInput); material.diffuse = vec3(0.24313725490196078, 0.7372549019607844, 0.9333333333333333); material.emission = vec3(0.0, 0.66666666, 0.0); material.specular = 0.5; material.shininess = 0.8; #ifdef FLAT gl_FragColor = vec4(material.diffuse + material.emission, material.alpha); #else gl_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC); #endif } ` }); ``` # 4. \*\*實驗性記錄:修改 Appearance 的頂點著色器 ``` js const apr = new Cesium.MaterialAppearance({ vertexShaderSource: ` attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; attribute vec2 st; attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; varying vec2 v_st; void main() { float zh = position3DHigh.z * 0.97; float zl = position3DLow.z * 0.97; vec3 th = vec3(position3DHigh.xy, zh); vec3 tl = vec3(position3DLow.xy, zl); vec4 p = czm_translateRelativeToEye(th, tl); v_positionEC = (czm_modelViewRelativeToEye * p).xyz; // position in eye coordinates v_normalEC = czm_normal * normal; // normal in eye coordinates v_st = st; gl_Position = czm_modelViewProjectionRelativeToEye * p; } `, }) ``` 依舊是上方 1. 的例子,只不過在頂點著色器稍微動動手腳,可達到變形的效果: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164403051-675639616.png) 很可惜這個 position3DHigh 和 position3DLow 並不是這個 Primitive 的區域性相對座標,所以直接修改 z = 0 是壓不平的,但是從圖中可略見端倪,猜測這個 z 值是世界座標,後續使用半透明地形看看。 # 5. 給材質的著色器程式碼傳入動態值:uniforms 的運用 `uniform` 在 WebGL 中就是恆定值的意思。一般 WebGL 用 `attribute` 關鍵字指定頂點屬性或外來值,用 `uniform` 關鍵字指定常量,用 `varying` 關鍵字指定頂點著色器、片元著色器共享的變數。 在 Cesium fabric 規則中,fabric.uniforms 的所有變數,在 fabric.source 中可以直接使用。 例如,我需要傳入一個透明度: ``` JS const m = new Cesium.Material({ fabric: { uniforms: { my_var: 0.5, }, source: ` czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); material.diffuse = vec3(0.5, 0.9, 0.3); material.alpha = my_var; return material; } ` } }); ``` 是可以的: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164416483-189838105.png) 列印 這個 m變數,可以輕鬆看到 glsl 程式碼: ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164428135-312271562.png) 並且支援直接對 js 的變數進行修改以重新著色: ``` JS m.uniforms.my_var = 0.9; ``` ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164440238-1892557669.png) ## 注:瞭解 uniforms uniforms 是 fabric 物件的一個屬性,按理說,你可以給這個物件傳遞任何與 glsl 內建結構、資料型別有對應關係的資料,例如上例的 `my_var`,是數字型別,在著色器內部自動解析為 `uniform float my_var_0;` 參考官方給出的二十多種預置 Material,如果你有興趣,可以直接把它們的 source 打印出來觀察。 例如,在鏡面反射材質中,它的 uniforms 就有這兩個: ``` JS uniforms : { image : 'specular.png', channel : 'a' } ``` 一個是圖片路徑,一個是圖片用於鏡面反射強度的通道(此處是 alpha 通道)。 如果你傳遞的是物件,例如最常見的紋理材質中: ``` JS uniforms: { image: 'diffuse.png', my_struct: { x: 10, y: 2 } } ``` 這個 my_struct,最終會傳入一個結構體 `uniform vec2 my_struct_0;` 當然有的時候不要作死,比如這個情況是轉譯不了的: ``` JS uniforms: { my_var: 0.5, my_struct: { x: 12, y: 5, name: { value: 'aaa' }, obj: false, time: 5 } } ``` 會報錯,因為 my_struct 已經超出了 glsl 能理解的型別。 事實上,你在 uniforms 物件中寫的任何資料,在 fabric.components 中一樣能用,並且 Cesium 的內建結構體常量、函式都是可以直接使用的。 從著色器的角度看,一種材質無非就是 `czm_getMaterial()` 函式的返回值罷了。 這裡僅僅改的是材質,屬於片元著色器階段發生的事情,在第四節中已經看到了 Material 中寫的著色器程式碼是如何被 Appearance 物件的片元著色器程式碼呼叫的。如果你想修改 Primitive 的形狀,那就要去修改 Appearance 的頂點著色器。 Primitive 是 Scene 物件下的,在渲染迴圈中它是最終被宰殺的羔羊(劃掉),只要是 Primitive,只要你有能力去修改它的著色器程式碼,就可以自定義很多東西出來。 ## 玩一玩:視覺化紋理座標 將紋理座標作為漫反射顏色寫入,就能看到紋理座標的樣子了: ``` JS const m = new Cesium.Material({ fabric: { uniforms: { my_var: 0.5, }, source: ` czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); material.diffuse = vec3(materialInput.st, 0.0); material.alpha = my_var; return material; } ` } }); ``` ![](https://img2020.cnblogs.com/blog/1097074/202012/1097074-20201215164455665-632122912.png) # 6. 後續想做的 - 研究自帶材質的著色器以及最終生成的頂點著色器、片元著色器 - 繼續結合原始碼,研究各路 Primitive - 研究自帶 glsl 結構體、函式、常量,靈活運用 - 理解頂點著色器的坐