地形裁剪是通過剔除裁剪面的組合空間范圍內的片源實現
第一步:構建裁剪面,這里我們根據地理坐標的范圍點實現裁剪面的創建(用戶代碼)
1)計算傳入的點范圍的順序是逆時針還是順時針 [isR=true]表示點的順序是逆時針。
1 var x1 = polygon[0].longitude; 2 3 var y1 = polygon[0].latitude; 4 5 var x2 = polygon[1].longitude; 6 7 var y2 = polygon[1].latitude; 8 9 var x3 = polygon[2].longitude; 10 11 var y3 = polygon[2].latitude; 12 13 14 15 var dirRes = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2); 16 17 var isR = dirRes > 0;
逆時針則按照原本順序存儲點,否則從后往前依次存儲點。
1 var points = []; 2 3 if (isR) { 4 5 for (var ii = 0; ii < polygon.length; ii++) { 6 7 points[ii] = Cesium.Cartesian3.fromDegrees(polygon[ii].longitude, polygon[ii].latitude); 8 9 } 10 11 } else { 12 13 var count = 0; 14 15 for (var ii = polygon.length - 1; ii >= 0; ii--) { 16 17 points[count] = Cesium.Cartesian3.fromDegrees(polygon[ii].longitude, polygon[ii].latitude); 18 19 count++; 20 21 } 22 23 }
2)分別計算由三點確定的面(其中第三個點為原點,裁剪面經過原點),midpoint為兩點直線連線的中點,up是單位向量,從原點(0,0,0)出發經過midpoint射線方向的單位向量。right為第一個點到第二點方向的單位向量。normal為垂直於裁剪面的法線向量。originCenteredPlane是法向量normal經過原點的面,distance是中點到originCenteredPlane面的距離,由distance和normal即可確定兩點構成的裁剪面。
1 var pointsLength = points.length; 2 3 var clippingPlanes = []; 4 5 for (var i = 0; i < pointsLength; ++i) { 6 7 var nextIndex = (i + 1) % pointsLength; 8 9 var midpoint = Cesium.Cartesian3.add(points[i], points[nextIndex], new Cesium.Cartesian3()); 10 11 midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint); 12 13 14 15 var up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3()); 16 17 var right = Cesium.Cartesian3.subtract(points[nextIndex], midpoint, new Cesium.Cartesian3()); 18 19 right = Cesium.Cartesian3.normalize(right, right); 20 21 22 23 var normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3()); 24 25 normal = Cesium.Cartesian3.normalize(normal, normal); 26 27 28 29 var originCenteredPlane = new Cesium.Plane(normal, 0.0); 30 31 var distance = Cesium.Plane.getPointDistance(originCenteredPlane, midpoint); 32 33 34 35 clippingPlanes.push(new Cesium.ClippingPlane(normal, distance)); 36 37 }
第二步:Cesium內部實現裁剪面相關參數存儲,裁剪面clippingPlanes的參數存儲在紋理中,具體解析如下:
1)計算存儲裁剪面相關參數所需的紋理寬度和高度大小,pixelsNeeded存儲裁剪面參數所需的紋理的大小,ContextLimits.maximumTextureSize = 8192,紋理最大寬度限制。
1 var pixelsNeeded = useFloatTexture ? this.length : this.length * 2; 2 3 4 5 function computeTextureResolution(pixelsNeeded, result) { 6 7 var maxSize = ContextLimits.maximumTextureSize; 8 9 result.x = Math.min(pixelsNeeded, maxSize); 10 11 result.y = Math.ceil(pixelsNeeded / result.x); 12 13 return result; 14 15 }
2)創建裁剪紋理
1 requiredResolution.y *= 2;//分配所需空間的兩倍,以避免頻繁的紋理重新分配 2 3 4 5 var sampler = new Sampler({ 6 7 wrapS : TextureWrap.CLAMP_TO_EDGE, 8 9 wrapT : TextureWrap.CLAMP_TO_EDGE, 10 11 minificationFilter : TextureMinificationFilter.NEAREST, 12 13 magnificationFilter : TextureMagnificationFilter.NEAREST 14 15 }); 16 17 18 19 if (useFloatTexture) { 20 21 clippingPlanesTexture = new Texture({ 22 23 context : context, 24 25 width : requiredResolution.x, 26 27 height : requiredResolution.y, 28 29 pixelFormat : PixelFormat.RGBA, 30 31 pixelDatatype : PixelDatatype.FLOAT, 32 33 sampler : sampler, 34 35 flipY : false 36 37 }); 38 39 this._float32View = new Float32Array(requiredResolution.x * requiredResolution.y * 4); 40 41 } else { 42 43 clippingPlanesTexture = new Texture({ 44 45 context : context, 46 47 width : requiredResolution.x, 48 49 height : requiredResolution.y, 50 51 pixelFormat : PixelFormat.RGBA, 52 53 pixelDatatype : PixelDatatype.UNSIGNED_BYTE, 54 55 sampler : sampler, 56 57 flipY : false 58 59 }); 60 61 this._uint8View = new Uint8Array(requiredResolution.x * requiredResolution.y * 4); 62 63 } 64 65
3)封裝紋理數據
1 function packPlanesAsFloats(clippingPlaneCollection, startIndex, endIndex) { 2 3 var float32View = clippingPlaneCollection._float32View; 4 5 var planes = clippingPlaneCollection._planes; 6 7 8 9 var floatIndex = 0; 10 11 for (var i = startIndex; i < endIndex; ++i) { 12 13 var plane = planes[i]; 14 15 var normal = plane.normal; 16 17 18 19 float32View[floatIndex] = normal.x; 20 21 float32View[floatIndex + 1] = normal.y; 22 23 float32View[floatIndex + 2] = normal.z; 24 25 float32View[floatIndex + 3] = plane.distance; 26 27 28 29 floatIndex += 4; // each plane is 4 floats 30 31 } 32 33 } 34 35 36 37 packPlanesAsFloats(this, 0, this._planes.length); 38 39 40 41 clippingPlanesTexture.copyFrom({ 42 43 width : clippingPlanesTexture.width, 44 45 height : clippingPlanesTexture.height, 46 47 arrayBufferView : this._float32View 48 49 });
第三步:構建globe時,向globe着色器代碼中添加裁剪面着色器代碼,部分代碼省略,其中width為存儲裁剪面紋理的寬度和高度
1 function getClippingPlaneFloat(width, height) { 2 3 var pixelWidth = 1.0 / width; 4 5 var pixelHeight = 1.0 / height; 6 7 8 9 var pixelWidthString = pixelWidth + ''; 10 11 if (pixelWidthString.indexOf('.') === -1) { 12 13 pixelWidthString += '.0'; 14 15 } 16 17 var pixelHeightString = pixelHeight + ''; 18 19 if (pixelHeightString.indexOf('.') === -1) { 20 21 pixelHeightString += '.0'; 22 23 } 24 25 26 27 var functionString = 28 29 'vec4 getClippingPlane(sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n' + 30 31 '{\n' + 32 33 ' int pixY = clippingPlaneNumber / ' + width + ';\n' + 34 35 ' int pixX = clippingPlaneNumber - (pixY * ' + width + ');\n' + 36 37 ' float u = (float(pixX) + 0.5) * ' + pixelWidthString + ';\n' + // sample from center of pixel 38 39 ' float v = (float(pixY) + 0.5) * ' + pixelHeightString + ';\n' + 40 41 ' vec4 plane = texture2D(packedClippingPlanes, vec2(u, v));\n' + 42 43 ' return czm_transformPlane(plane, transform);\n' + 44 45 '}\n'; 46 47 return functionString; 48 49 } 50 51 52 53 54 55 function clippingFunctionIntersect(clippingPlanesLength) { 56 57 var functionString = 58 59 'float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n' + 60 61 '{\n' + 62 63 ' bool clipped = true;\n' + 64 65 ' vec4 position = czm_windowToEyeCoordinates(fragCoord);\n' + 66 67 ' vec3 clipNormal = vec3(0.0);\n' + 68 69 ' vec3 clipPosition = vec3(0.0);\n' + 70 71 ' float clipAmount = 0.0;\n' + 72 73 ' float pixelWidth = czm_metersPerPixel(position);\n' + 74 75 76 77 ' for (int i = 0; i < ' + clippingPlanesLength + '; ++i)\n' + 78 79 ' {\n' + 80 81 ' vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n' + 82 83 84 85 ' clipNormal = clippingPlane.xyz;\n' + 86 87 ' clipPosition = -clippingPlane.w * clipNormal;\n' + 88 89 90 91 ' float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n' + 92 93 ' clipAmount = max(amount, clipAmount);\n' + 94 95 96 97 ' clipped = clipped && (amount <= 0.0);\n' + 98 99 ' }\n' + 100 101 102 103 ' if (clipped)\n' + 104 105 ' {\n' + 106 107 ' discard;\n' + 108 109 ' }\n' + 110 111 112 113 ' return clipAmount;\n' + 114 115 '}\n'; 116 117 return functionString; 118 119 }
代碼解析:
vec4 position = czm_windowToEyeCoordinates(fragCoord) 待檢測的點
clipNormal = clippingPlane.xyz 垂直於裁剪面的法線向量
clipPosition = -clippingPlane.w * clipNormal; 法向量與裁剪面的交點
float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth; 待待測點與裁剪面上某一點構成的方向向量, dot(clipNormal, (position.xyz - clipPosition)) 方向向量在裁剪面法線方向上的投影長度(|clipNormal| |position.xyz - clipPosition| *cos∠(clipNormal, (position.xyz - clipPosition))),除以pixelWidth得到投影長度的單位長度。
clipAmount = max(amount, clipAmount);
clipped = clipped && (amount <= 0.0);
判斷分量符號是否發生變化,如果最終結果改變則不再范圍內,如果最終結果不變則在范圍內。
注意:面的存儲順序是逆序時,判斷代碼如上。所以在做裁剪時外部需要傳入逆時針存儲的面。