這篇文章只討論 PerspectiveCamera
的適配方法javascript
作過手機 H5 的同窗可能會以爲屏幕適配挺麻煩。緣由是設計師提供的設計稿尺寸比固定,可是前端開發者卻要適配不一樣大小、長寬比的目標設備。適配的終極目標無非是最大程度把主體內容優雅地呈現給用戶。開發和設計若是沒有協調好的話可能會妥協比較醜陋的方案,例如因爲設計比例問題,爲了照顧主體內容不被裁剪,只好設備兩邊,或者上下留黑邊這種。前端
不過在 3D 的世界裏,咱們不用擔憂會有黑邊的問題,由於 3D 場景是無限延伸的,總能填滿任何比例的屏幕。java
先看看 PerspectiveCamera 官方 API 說明以下:web
PerspectiveCamera( fov, aspect, near, far ) fov — Camera frustum vertical field of view. aspect — Camera frustum aspect ratio. near — Camera frustum near plane. far — Camera frustum far plane.
上面四個參數都會影響成像結果,fov
和 aspect
設置 XY 平面的範圍,也就是廣度。 near
和 far
影響的是縱深 Z 軸的範圍,也就是深度。縱深只要保證物體離相機距離在這個範圍就能夠了,這是爲了性能而設置的參數,由用戶設置,只渲染必要的東西。實際上真實的相機這兩個值對應的是 0 到 無限遠。ide
這些參數設置好以後,成像就相應肯定了。最後 three.js 把相機拍攝到的矩形區域對應好四個頂點渲染到屏幕上。一樣比例的屏幕看到的圖像是一致的,與屏幕大小無關
</span>。函數
下面我用一個簡單的場景來看一下這些參數對成像的影響。工具
相機 (PerspectiveCamera)性能
一個邊長爲 100 的平面(主體內容範圍),放在世界座標中心。ui
var camera = new THREE.PerspectiveCamera(53, 500 / 500, 0.1, 1000); var planeGemo = new THREE.PlaneGeometry( 100, 100, 10, 10 ) var meshMaterial = new THREE.MeshLambertMaterial(); meshMaterial.color = new THREE.Color(0x2dcaf1); meshMaterial.side = THREE.DoubleSide; var wireFrameMat = new THREE.MeshBasicMaterial(); wireFrameMat.color = new THREE.Color(0xdddddd); wireFrameMat.wireframe = true; var plane = THREE.SceneUtils.createMultiMaterialObject(planeGemo, [meshMaterial, wireFrameMat]); scene.add(plane);
在任何屏幕下,都能最大程度地顯示完整的立方體。最大程度,就是最少多餘空間的意思。下面是要達到效果this
能夠直接想到的一種適配方法是——改變 camera 到目標物體的距離以控制成像的內容,可是這樣作計算成本比較高,並且還有可能影響其餘一些數值,而後須要相應一塊兒計算修改。
我想到改變視角也能夠達到控制成像內容多少的目的,因而我想可不能夠只經過改變 fov 一個數值,達到我要的效果。
fov 官網的定義翻譯過來是垂直方向的視角大小。咱們先規定好相機到平面的距離爲 100,而後試試看能不能經過計算設置 fov 值,恰好讓平面填滿一個寬高比爲 1:1 的屏幕。
plane.position.set(0,0,0); camera.position.set(0,0,100); camera.lookAt(new THREE.Vector3);
觀察上面的圖,能夠很容易求出 fov 的值, fov = arctan((100/2)/100) * 2; fov 爲 0.9272952180016122,約等於 53 度。
camera.fov = Math.atan((100/2)/100) * 2 * (180 / Math.PI); camera.updateProjectionMatrix();
設置完剛剛求出的 fov 值,將場景渲染到 寬高比爲 1:1 的畫布上。
渲染結果和預想的同樣,平面恰好填滿了 1:1 的畫布。
下面在固定的 fov 下,使用 dat.gui 工具調整寬高比,觀察渲染區域的變化。
由於fov設置的是垂直方向的視角範圍,能夠看到不管咱們怎麼改變寬高比例,垂直方向的渲染範圍,都是一致的。水平方向則是以裁剪的方式顯示。也就是說當咱們設置好視角讓垂直方向範圍恰好等於主體內容的範圍,只要寬高比大於1,咱們獲得的渲染結果,已是最佳的了。問題就只剩下當寬高比小於1的狀況了。
寬高比小於1的時候,垂直方向顯示的高度恰好是等於主體內容的高度。爲了能讓水平方向完整顯示主體內容,咱們只有將垂直方向範圍增大,也就是將 fov 設置一個更大的值,此時水平方向的範圍也會隨之增大。當將 fov調整到 水平方向恰好能顯示主體內容時,垂直方向此時顯示的範圍是超過主體內容垂直方向的範圍的。其中的關係,其實能夠用很簡單的函數求出來。
已知 照相機到主題內容的距離爲 d
正方形主體內容的邊長爲 w
設寬高比爲 r,求照相機垂直方向的視角 f
當 r >= 1 時,照相機拍攝到的垂直方向範圍等於 w
當 r > 1d * tan(f/2) * 2 = w
當 r < 1 時,照相機拍攝到的水平方向範圍等於 w,垂直方向範圍應該是 w/rd * tan(f/2) * 2 = w/r
這樣,任意寬高比例的屏幕應該對應多大的垂直視角就肯定了。
var controls = new function () { camera.position.z = CAMERA_TO_MAIN_DIS; this.width = 500; this.height = 500; this.planeRY = 0; /** * 計算相機 fov 的函數 * @param d : 在相機前方 d 距離 * @param w : 想要看到最大正方形區域邊長爲 w * @param r : 屏幕寬高比 */ function calcFov(d, w, r) { var f; var vertical = w; if (r < 1) { vertical = vertical/r; } f = Math.atan(vertical/d/2)*2 * (180 / Math.PI); return f; } this.redraw = ()=>{ webGLRenderer.setSize(this.width, this.height); plane.rotation.y = this.planeRY; camera.fov = calcFov(CAMERA_TO_MAIN_DIS, MAIN_CONTENT_WIDTH, this.width / this.height); camera.aspect = this.width / this.height; camera.updateProjectionMatrix(); } }
效果:
demo 的完整代碼:http://codepen.io/JasonTurbo/pen/ZLwJMo
原文連接:http://gnauhca.com/blog/2016/11/24/threejs/THREEJS%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D/