Cesium深入淺出之陰影貼圖【轉】


引子

又偷懶了,說好的周更的,又拖了一個月咯。前面兩篇寫了可視域分析和視頻投影,無一例外的都用到了ShadowMap也就是陰影貼圖,因此覺得又必要單獨寫一篇陰影貼圖的文章。當然了,還有另外一個原因,文章中視頻投影是利用Cesium自帶的Entity方式實現的,毫無技術性可言,在文章結尾我說了可以使用ShadowMap方式來做,原理類似於可視域分析,那么今天我就把實現方式給大家說一下。

預期效果

照例先看一下預期的效果,既然說了陰影貼圖,當然不能滿足於只貼視頻紋理了,這里我放了三張圖,代表着我用了三種紋理:圖片、視頻、顏色。小伙伴驚奇的發現,顏色貼圖不就是可視域分析么?嘿嘿,是的,因為原理都是一樣的嘛。

實現原理

上面說了實現原和可視域分析是一樣的,涉及到的知識點ShadowMap、Frustum、Camera之類的請參考Cesium深入淺出之可視域分析,這里不在贅述。只簡單講一點,陰影貼圖支持不同的紋理,那么我們要做的就是創建一個ShadowMap,然后把不同類型的Texture傳給他就可以了。

具體實現

實現流程與可視域分析也大致相似,類→創建Camera→創建ShadowMap→創建PostProcessStage→創建Frustum,只多了一步設置Texture,當然最核心的內容是在shader里。

因為代碼高度重合,這里就不貼全部代碼了,只貼核心代碼,如果有疑問的可以留言、私信、群里詢問,我看到了都會回答的。

構造函數

復制代碼
 1 // 定義變量
 2 /** 紋理類型:VIDEO、IMAGE、COLOR */
 3 #textureType;
 4 /** 紋理 */
 5 #texture;
 6 /** 觀測點位置 */
 7 #viewPosition;
 8 /** 最遠觀測點位置(如果設置了觀測距離,這個屬性可以不設置) */
 9 #viewPositionEnd;
10 // ...
11 
12 // 構造函數
13 constructor(viewer, options) {
14     super(viewer);
15 
16     // 紋理類型
17     this.#textureType = options.textureType;
18     // 紋理地址(紋理為視頻或圖片的時候需要設置此項)
19     this.#textureUrl = options.textureUrl;
20     // 觀測點位置
21     this.#viewPosition = options.viewPosition;
22     // 最遠觀測點位置(如果設置了觀測距離,這個屬性可以不設置)
23     this.#viewPositionEnd = options.viewPositionEnd;
24 
25     // ...
26 
27     switch (this.#textureType) {
28         default:
29         case VideoShed.TEXTURE_TYPE.VIDEO:
30             this.activeVideo();
31             break;
32         case VideoShed.TEXTURE_TYPE.IMAGE:
33             this.activePicture();
34             break;
35     }
36     this.#refresh()
37     this.viewer.scene.primitives.add(this);
38 }
復制代碼

定義紋理類型,視頻和圖片投影需要引入紋理文件,初始化的時候設置文件路徑,顏色投影不需要任何操作。

視頻紋理

復制代碼
 1 /**
 2  * 投放視頻。
 3  *
 4  * @author Helsing
 5  * @date 2020/09/19
 6  * @param {String} textureUrl 視頻地址。
 7  */
 8 activeVideo(textureUrl = undefined) {
 9     if (!textureUrl) {
10         textureUrl = this.#textureUrl;
11     } else {
12         this.#textureUrl = textureUrl;
13     }
14     const video = this.#createVideoElement(textureUrl);
15     const that = this;
16     if (video /*&& !video.paused*/) {
17         this.#activeVideoListener || (this.#activeVideoListener = function () {
18             that.#texture && that.#texture.destroy();
19             that.#texture = new Texture({
20                 context: that.viewer.scene.context,
21                 source: video,
22                 width: 1,
23                 height: 1,
24                 pixelFormat: PixelFormat.RGBA,
25                 pixelDatatype: PixelDatatype.UNSIGNED_BYTE
26             });
27         });
28         that.viewer.clock.onTick.addEventListener(this.#activeVideoListener);
29     }
30 }
復制代碼

視頻紋理是通過html5的video標簽引入,需要動態創建標簽,不過需要注意的是視頻標簽的釋放是個問題,常規方式並不能徹底釋放,最好不要每次都創建新的標簽。

圖片紋理

復制代碼
 1 /**
 2  * 投放圖片。
 3  *
 4  * @author Helsing
 5  * @date 2020/09/19
 6  * @param {String} textureUrl 圖片地址。
 7  */
 8 activePicture(textureUrl = undefined) {
 9     this.deActiveVideo();
10     if (!textureUrl) {
11         textureUrl = this.#textureUrl;
12     } else {
13         this.#textureUrl = textureUrl;
14     }
15     const that = this;
16     const img = new Image;
17     img.onload = function () {
18         that.#textureType = VideoShed.TEXTURE_TYPE.IMAGE;
19         that.#texture = new Texture({
20             context: that.viewer.scene.context,
21             source: img
22         });
23     };
24     img.onerror = function () {
25         console.log('圖片加載失敗:' + textureUrl)
26     };
27     img.src = textureUrl;
28 }
復制代碼

圖片紋理使用的是Image對象加載的,要注意的是在異步回調中設置紋理。

 

PostProcessStage

復制代碼
 1 /**
 2  * 創建后期程序。
 3  *
 4  * @author Helsing
 5  * @date 2020/09/19
 6  * @ignore
 7  */
 8 #addPostProcessStage() {
 9     const that = this;
10     const bias = that.#shadowMap._isPointLight ? that.#shadowMap._pointBias : that.#shadowMap._primitiveBias;
11     const postStage = new PostProcessStage({
12         fragmentShader: VideoShedFS,
13         uniforms: {
14             helsing_textureType: function () {
15                 return that.#textureType;
16             },
17             helsing_texture: function () {
18                 return that.#texture;
19             },
20             helsing_alpha: function () {
21                 return that.#alpha;
22             },
23             helsing_visibleAreaColor: function () {
24                 return that.#visibleAreaColor;
25             },
26             helsing_invisibleAreaColor: function () {
27                 return that.#invisibleAreaColor;
28             },
29             shadowMap_texture: function () {
30                 return that.#shadowMap._shadowMapTexture;
31             },
32             shadowMap_matrix: function () {
33                 return that.#shadowMap._shadowMapMatrix;
34             },
35             shadowMap_lightPositionEC: function () {
36                 return that.#shadowMap._lightPositionEC;
37             },
38             shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: function () {
39                 const t = new Cartesian2;
40                 t.x = 1 / that.#shadowMap._textureSize.x;
41                 t.y = 1 / that.#shadowMap._textureSize.y;
42                 return Cartesian4.fromElements(t.x, t.y, bias.depthBias, bias.normalShadingSmooth, that.#combinedUniforms1);
43             },
44             shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: function () {
45                 return Cartesian4.fromElements(bias.normalOffsetScale, that.#shadowMap._distance, that.#shadowMap.maximumDistance, that.#shadowMap._darkness, that.#combinedUniforms2);
46             },
47         }
48     });
49     this.#postProcessStage = this.viewer.scene.postProcessStages.add(postStage);
50 }
復制代碼

后處理程序中的重點是向shader中傳入uniforms參數,如紋理類型,可視域顏色、非可視域顏色等。最后就是重頭戲着色器代碼。

復制代碼
  1 export default `
  2 varying vec2 v_textureCoordinates;
  3 uniform sampler2D colorTexture;
  4 uniform sampler2D depthTexture;
  5 uniform sampler2D shadowMap_texture; 
  6 uniform mat4 shadowMap_matrix; 
  7 uniform vec4 shadowMap_lightPositionEC; 
  8 uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
  9 uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
 10 uniform int helsing_textureType;
 11 uniform sampler2D helsing_texture;
 12 uniform float helsing_alpha;
 13 uniform vec4 helsing_visibleAreaColor;
 14 uniform vec4 helsing_invisibleAreaColor;
 15 
 16 vec4 toEye(in vec2 uv, in float depth){
 17     vec2 xy = vec2((uv.x * 2.0 - 1.0),(uv.y * 2.0 - 1.0));
 18     vec4 posInCamera =czm_inverseProjection * vec4(xy, depth, 1.0);
 19     posInCamera =posInCamera / posInCamera.w;
 20     return posInCamera;
 21 }
 22 float getDepth(in vec4 depth){
 23     float z_window = czm_unpackDepth(depth);
 24     z_window = czm_reverseLogDepth(z_window);
 25     float n_range = czm_depthRange.near;
 26     float f_range = czm_depthRange.far;
 27     return (2.0 * z_window - n_range - f_range) / (f_range - n_range);
 28 }
 29 float _czm_sampleShadowMap(sampler2D shadowMap, vec2 uv){
 30     return texture2D(shadowMap, uv).r;
 31 }
 32 float _czm_shadowDepthCompare(sampler2D shadowMap, vec2 uv, float depth){
 33     return step(depth, _czm_sampleShadowMap(shadowMap, uv));
 34 }
 35 float _czm_shadowVisibility(sampler2D shadowMap, czm_shadowParameters shadowParameters){
 36     float depthBias = shadowParameters.depthBias;
 37     float depth = shadowParameters.depth;
 38     float nDotL = shadowParameters.nDotL;
 39     float normalShadingSmooth = shadowParameters.normalShadingSmooth;
 40     float darkness = shadowParameters.darkness;
 41     vec2 uv = shadowParameters.texCoords;
 42     depth -= depthBias;
 43     vec2 texelStepSize = shadowParameters.texelStepSize;
 44     float radius = 1.0;
 45     float dx0 = -texelStepSize.x * radius;
 46     float dy0 = -texelStepSize.y * radius;
 47     float dx1 = texelStepSize.x * radius;
 48     float dy1 = texelStepSize.y * radius;
 49     float visibility = (_czm_shadowDepthCompare(shadowMap, uv, depth)
 50         + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy0), depth)
 51         + _czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy0), depth)
 52         + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy0), depth)
 53         + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, 0.0), depth) 
 54         + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, 0.0), depth)
 55         + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy1), depth)
 56         + _czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy1), depth)
 57         + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy1), depth)
 58     ) * (1.0 / 9.0);
 59     return visibility;
 60 }
 61 vec3 pointProjectOnPlane(in vec3 planeNormal, in vec3 planeOrigin, in vec3 point){
 62     vec3 v01 = point -planeOrigin;
 63     float d = dot(planeNormal, v01) ;
 64     return (point - planeNormal * d);
 65 }
 66 
 67 void main(){
 68     const float PI = 3.141592653589793;
 69     vec4 color = texture2D(colorTexture, v_textureCoordinates);
 70     vec4 currentDepth = texture2D(depthTexture, v_textureCoordinates);
 71     if(currentDepth.r >= 1.0){
 72         gl_FragColor = color;
 73         return;
 74     }
 75     float depth = getDepth(currentDepth);
 76     vec4 positionEC = toEye(v_textureCoordinates, depth);
 77     vec3 normalEC = vec3(1.0);
 78     czm_shadowParameters shadowParameters;
 79     shadowParameters.texelStepSize = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
 80     shadowParameters.depthBias = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
 81     shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
 82     shadowParameters.darkness = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
 83     shadowParameters.depthBias *= max(depth * 0.01, 1.0);
 84     vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz);
 85     float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0);
 86     vec4 shadowPosition = shadowMap_matrix * positionEC;
 87     shadowPosition /= shadowPosition.w;
 88     if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))){
 89         gl_FragColor = color;
 90         return;
 91     }
 92     shadowParameters.texCoords = shadowPosition.xy;
 93     shadowParameters.depth = shadowPosition.z;
 94     shadowParameters.nDotL = nDotL;
 95     float visibility = _czm_shadowVisibility(shadowMap_texture, shadowParameters);
 96     
 97     if (helsing_textureType < 2){ // 視頻或圖片模式
 98         vec4 videoColor = texture2D(helsing_texture, shadowPosition.xy);
 99         if (visibility == 1.0){
100             gl_FragColor =  mix(color, vec4(videoColor.xyz, 1.0), helsing_alpha * videoColor.a);
101         }
102         else{
103             if(abs(shadowPosition.z - 0.0) < 0.01){
104                 return;
105             }
106             gl_FragColor = color;
107         }
108     }
109     else{ // 可視域模式
110         if (visibility == 1.0){
111             gl_FragColor = mix(color, helsing_visibleAreaColor, helsing_alpha);
112         }
113         else{
114             if(abs(shadowPosition.z - 0.0) < 0.01){
115                 return;
116             }
117             gl_FragColor = mix(color, helsing_invisibleAreaColor, helsing_alpha);
118         }
119     }
120 }`;
復制代碼

可以看出着色器代碼並不復雜,而且其中大部分是Cesium中原生的,重點看我標注的部分,視頻和圖片模式時使用混合紋理,可視域模式時混合顏色。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM