WebGL浮點數精度最大的問題是就是由於js是64位精度的,js往着色器裏面穿的時候只能是32位浮點數,有效數是8位,精度丟失比較嚴重。git
繼續嘗試發現mapbox中也有相似問題:https://github.com/mapbox/mapbox-gl-js/issues/7268github
map.renderEngin.gl.getShaderPrecisionFormat( map.renderEngin.gl.VERTEX_SHADER, map.renderEngin.gl.HIGH_FLOAT )
getDistanceScales() { // {latitude, longitude, zoom, scale, highPrecision = false} let center = this.center; let latitude = center.lat; let longitude = center.lng; let scale = this.zoomScale(this.zoom); let highPrecision = true; // Calculate scale from zoom if not provided scale = scale !== undefined ? scale : this.zoomToScale(zoom); // assert(Number.isFinite(latitude) && Number.isFinite(longitude) && Number.isFinite(scale)); const result = {}; const worldSize = TILE_SIZE * scale; const latCosine = Math.cos(latitude * DEGREES_TO_RADIANS); /** * Number of pixels occupied by one degree longitude around current lat/lon: pixelsPerDegreeX = d(lngLatToWorld([lng, lat])[0])/d(lng) = scale * TILE_SIZE * DEGREES_TO_RADIANS / (2 * PI) pixelsPerDegreeY = d(lngLatToWorld([lng, lat])[1])/d(lat) = -scale * TILE_SIZE * DEGREES_TO_RADIANS / cos(lat * DEGREES_TO_RADIANS) / (2 * PI) */ const pixelsPerDegreeX = worldSize / 360; const pixelsPerDegreeY = pixelsPerDegreeX / latCosine; /** * Number of pixels occupied by one meter around current lat/lon: */ const altPixelsPerMeter = worldSize / EARTH_CIRCUMFERENCE / latCosine; /** * LngLat: longitude -> east and latitude -> north (bottom left) * UTM meter offset: x -> east and y -> north (bottom left) * World space: x -> east and y -> south (top left) * * Y needs to be flipped when converting delta degree/meter to delta pixels */ result.pixelsPerMeter = [altPixelsPerMeter, altPixelsPerMeter, altPixelsPerMeter]; result.metersPerPixel = [1 / altPixelsPerMeter, 1 / altPixelsPerMeter, 1 / altPixelsPerMeter]; result.pixelsPerDegree = [pixelsPerDegreeX, pixelsPerDegreeY, altPixelsPerMeter]; result.degreesPerPixel = [1 / pixelsPerDegreeX, 1 / pixelsPerDegreeY, 1 / altPixelsPerMeter]; /** * Taylor series 2nd order for 1/latCosine f'(a) * (x - a) = d(1/cos(lat * DEGREES_TO_RADIANS))/d(lat) * dLat = DEGREES_TO_RADIANS * tan(lat * DEGREES_TO_RADIANS) / cos(lat * DEGREES_TO_RADIANS) * dLat */ if (highPrecision) { const latCosine2 = DEGREES_TO_RADIANS * Math.tan(latitude * DEGREES_TO_RADIANS) / latCosine; const pixelsPerDegreeY2 = pixelsPerDegreeX * latCosine2 / 2; const altPixelsPerDegree2 = worldSize / EARTH_CIRCUMFERENCE * latCosine2; const altPixelsPerMeter2 = altPixelsPerDegree2 / pixelsPerDegreeY * altPixelsPerMeter; result.pixelsPerDegree2 = [0, pixelsPerDegreeY2, altPixelsPerDegree2]; result.pixelsPerMeter2 = [altPixelsPerMeter2, 0, altPixelsPerMeter2]; } // Main results, used for converting meters to latlng deltas and scaling offsets return result; }
對於project_uCommonUnitsPerWorldUnit來講就是計算在精度和緯度上,一度表明的瓦片像素數目。對於project_uCommonUnitsPerWorldUnit2來講這裏面用了一個泰勒級數的二階展開(諮詢了下管戈,泰勒級數展開項越多表明模擬值偏差越小,這裏用到了第二級)主要是在着色器中在`project_uCommonUnitsPerWorldUnit + project_uCommonUnitsPerWorldUnit2 * dy`這裏作精度補償api
gl.uniform3f(this.project_uCommonUnitsPerWorldUnit, distanceScles.pixelsPerDegree[0], distanceScles.pixelsPerDegree[1], distanceScles.pixelsPerDegree[2]);
vec2 project_offset(vec2 offset) { float dy = offset.y; // if (project_uCoordinateSystem == COORDINATE_SYSTEM_LNGLAT_AUTO_OFFSET) { dy = clamp(dy, -1., 1.); // } vec3 commonUnitsPerWorldUnit = project_uCommonUnitsPerWorldUnit + project_uCommonUnitsPerWorldUnit2 * dy; // return vec4(offset.xyz * commonUnitsPerWorldUnit, offset.w); return vec2(offset.xy * commonUnitsPerWorldUnit.xy); } // 返回在v3 api中的3d座標系下的座標, 採用高精度模式 vec2 project_view_local_position3(vec2 latlngHigh, vec2 latlngLow) { vec2 centerCoordHigh = project_position(center.xy + center.zw, zoom); // Subtract high part of 64 bit value. Convert remainder to float32, preserving precision. float X = latlngHigh.x - center.x; float Y = latlngHigh.y - center.y; return project_offset(vec2(X + latlngLow.x, Y + latlngLow.y)); }
最終效果:ide