iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
佛心分享-IT 人自學之術

從空拍到3D展示系列 第 30 篇

Day30. 最後一天: 遊戲重新開始不用再重新載入模型,以及未來待辦與回顧

  • 分享至 

  • xImage
  •  

最後一天,先做一個小改動,再把還沒做完的事列成待辦,最後回顧整個專案。


一、最後的小改動:重新開始不用再重新載入模型

問題

彩蛋全部找到後,按「重新開始」,目前的做法是 location.reload(),也就是重新整理整個網頁。模型約 112 MB,重新整理後要再載入一次,又得等一段時間。

TrackballControls 有一個 reset(),可以把鏡頭拉回原位,不需要重新整理網頁。但直接把 location.reload() 換成 controls.reset(),鏡頭會跑到離模型很近的地方,模型顯得非常大,不是一開始取景的畫面。

原因

three.js 官方文件對 reset() 的說明只有一句:「Resets the controls to its initial state.」(把控制器重設回初始狀態),沒有寫「初始狀態」是什麼時候記下來的。這要讀 three.js r160 的 TrackballControls 原始碼才知道:

建立控制器時(建構函式裡),會執行這幾行:

// for reset
 
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.up0 = this.object.up.clone();
this.zoom0 = this.object.zoom;
  • this.target 是當下的注視點,this.object 是建立控制器時傳進來的相機。
  • clone() 會複製出一個新的向量,跟原本的向量各自獨立。
  • 所以 target0、position0、up0、zoom0 是建立控制器那一刻的注視點、相機位置、相機的上方向、縮放倍率的複本。註解 for reset 也寫明了它們的用途。
    reset() 裡,則是把這四個值複製回去(節錄):
this.reset = function () {
    ...
    scope.target.copy( scope.target0 );
    scope.object.position.copy( scope.position0 );
    scope.object.up.copy( scope.up0 );
    scope.object.zoom = scope.zoom0;
    ...
};
  • scope 就是控制器本身。
  • reset() 沒有參數,做的事就是把注視點和相機還原成這四個值。整份原始碼裡,也只有 reset() 會讀取它們。

再看 scene.js 的順序:

  1. createScene() 先把相機放在 (0, 5, 10),接著建立 TrackballControls。這時注視點是預設的原點,所以 position0 記下 (0, 5, 10),target0 記下原點。
  2. 模型載入完成後,frameObject() 才依照模型大小,把相機和注視點移到適合的位置。
    reset() 回到的是第 1 步記下的位置,不是第 2 步調好的取景位置,所以鏡頭會離模型很近。

解法

target0、position0 在原始碼裡是控制器的屬性(寫成 this.target0、this.position0),可以直接修改。取景完成後,把它們改成目前的位置就好。要注意的是,官方文件的屬性列表裡沒有這兩個,只寫在原始碼裡,日後升級 three.js 版本時要再確認一次。

修改 src/scene.js 的 frameObject:

export function frameObject(gltfObject, camera, controls) {
    // 印出模型的大小與中心,判斷位置或比例有沒有問題
    const box = new THREE.Box3().setFromObject(gltfObject.scene);
    const center = box.getCenter(new THREE.Vector3());
    const size = box.getSize(new THREE.Vector3());
    const maxDim = Math.max(size.x, size.y, size.z);
 
    camera.position.set(center.x, center.y + maxDim, center.z + maxDim);
    camera.lookAt(center);
    camera.far = maxDim * 10;      // 模型很大時,避免遠處被裁掉
    camera.updateProjectionMatrix();
 
    controls.target.copy(center);
    controls.target0.copy(controls.target);     // ★ 把目前的注視點記成 reset 要回到的注視點
    controls.position0.copy(camera.position);   // ★ 把目前的相機位置記成 reset 要回到的位置
}
  • controls.target0.copy(controls.target):copy 會把括號裡向量的 x、y、z 複製到 target0。上一行剛把注視點設成模型中心,所以 reset() 之後會重新看向模型中心。
  • controls.position0.copy(camera.position):把取景後的相機位置複製到 position0,reset() 之後相機就回到這裡。
  • up0、zoom0 不用改:frameObject 沒有改相機的上方向和縮放倍率,建立控制器時記下的值就是我們要的。旋轉時 TrackballControls 會改變相機的上方向,reset() 會一併還原。
  • 用 copy 而不是寫成 controls.target0 = controls.target:後者會讓兩個名字指向同一個向量,之後注視點一移動,target0 也會跟著變,reset() 就沒有意義了。
    修改 src/main.js 的 restartGame:
function restartGame() {
  scene.traverse((obj) => {
    if (obj.userData.isEgg) obj.userData.found = false;
  });
  resetTimer();                      // 同時把 finished 改回 false
  hideResult();
  controls.enabled = true;
  controls.reset();                  // ★ 鏡頭回到取景位置,取代 location.reload()
}
  • controls.reset():取代原本的 location.reload()。網頁沒有重新整理,模型還在,所以不用再載入。
  • 其他幾行原本就有:把每個彩蛋的「已找到」標記清掉、計時歸零、關掉結果視窗。原本這些在重新整理後都會重來,現在不重新整理,就靠它們把遊戲狀態還原。

測試結果

改完後實際測試,這個改動可行:按下「重新開始」後,不用再等模型重新載入,鏡頭就回到一開始的取景畫面。

二、未來待辦

30 天的時間有限,下面這些還沒做,或是做了但不夠好。這些都還沒有實際試過,只是記下方向。

手機體驗

  • 手機螢幕小,彩蛋很難找:就算把模型放到最大,還是很難看出彩蛋在哪,放大的效果有限。可以思考的方向,例如:輕點地面時把注視點移過去、加大彩蛋的大小。
  • 雙指平移不好操作:可以移動,但很難移到想去的地方。

遊戲內容

  • 操作說明:目前畫面上沒有任何說明,玩家不知道能怎麼操作。依照 TrackballControls r160 原始碼,滑鼠是左鍵旋轉、右鍵左右平移或前後縮放、滾輪縮放;鍵盤的 A、S、D 分別是旋轉、縮放、平移,但要「按住按鍵+滑鼠拖曳」,不是按一下就有作用;觸控是單指旋轉、雙指平移與縮放。這些都應該寫在說明裡。
  • 重新開始時,彩蛋換到隨機位置:目前彩蛋固定在 Blender 裡擺的位置,玩過一次就知道答案。要隨機擺放,得先找到地面的高度,讓彩蛋貼在地上,還要讓彩蛋之間保持距離。

模型大小與載入速度

3D model 約 112 MB,是載入慢的主要原因。可以研究的方向:

  • Blender 的 glTF 匯出選項裡,可以選貼圖的圖片格式(PNG、JPEG、WebP 等)和圖片品質,也有 Draco 網格壓縮的選項。
  • 如果用 Draco 壓縮,網頁端載入時要怎麼設定,需要先讀 three.js 的文件。
  • 減少模型的面數,需要先讀 Blender 手冊。

模型品質

  • 模型邊緣還沒有完全修乾淨。
  • 2D Map 和 3D Model 兩種航拍方式的建模結果,還沒有並排比較。

三、系列回顧:從空拍到 3D 展示

這 30 天,把竹南獅山親子公園從空拍照片,一路變成可以在網頁上玩的找彩蛋小遊戲。整個流程是:

階段 用的工具 產出
空拍 Litchi 規畫航線,無人機自動拍攝 大量重疊的空拍照片
建模 WebODM 公園的 3D 模型
修模、埋彩蛋 Blender 修掉邊緣、放進角色模型當彩蛋,匯出 .glb
展示 Sketchfab 不用寫程式,就能在網頁上旋轉觀看模型
互動小遊戲 Three.js 點擊找彩蛋、計時、找到數量、結果畫面
部署 GitHub Pages + Cloudflare R2 網頁放 GitHub Pages,模型放 R2

專案程式碼:https://github.com/smallhand/drone_threejs
遊戲網址:https://smallhand.github.io/drone_threejs/


四、想法與心得

有點哀傷,昨天 Day29 文章已打好,忘了按發表文章,所以斷在 Day28 QQ

不過每天都還是持續產出一篇文章,不管好壞我都很開心自己是個很有毅力的人,尤其遇到兩次連假有安排出遊時要事先準備好文章,其實蠻辛苦的;很慶幸有 AI 輔助,縮短了不少時間!

對於平日從事責任制的工作來說,這是個蠻有挑戰的活動;
透過這次的活動,我也因此知道空拍機除了拍照、錄影以外,還可以把照片建模並透過 Three.js 做出一點有趣的設計。
我不確定這樣的操作過網友沒有人做過,但至少這是我以前不知道的領域。
善用 AI 幫助我在 30 天內達到我想要的目的,其實蠻開心的!

當初會想參加鐵人主要是因為現在 AI 很發達,公司不斷推出要員工善用 AI,自己很好奇在未知領域下,是否真的能透過 AI 順利完成目標。

這 30 天下,我認為技術層面沒有太大問題! 不過有幾點是我認為 AI 最弱的地方,也是自己在現今時代該培養的能力。

  1. 做出合理、好的規畫
    • 像是 30 天的安排,如果沒有明確說出一天大概要研究的量限縮在 1小時左右,就會遇到 AI 規畫某一天的任務是上傳照片,就沒了;或是突然迸出要操作的資訊,但前面沒有一些背景知識的介紹。
    • 一開始專案用 Litchi 自動飛行並建立模型,跟考照一點關係都沒有,但 AI 卻有一篇是要牽扯考照路線和 Litchi 的任務規畫有一點關係,讓內容變得很突兀
  2. 架構上的規畫能力
    • 如果不懂軟體架構、或是軟體技術 eg. SOLID 原則、design pattern, code smells...,直接放任 AI 寫程式碼,通常就會遇到一個 js 檔包山包海,所以中間就有一篇開始說明依功能做程式碼切割;其實 AI 不是做不到切功能,但它需要人下指導棋給它
  3. 系統性的答案
    • 請 AI 產生的東西,都是他各種濃縮的內容,一個學問,要在一個回答內做到細部說明、有系統地回應,其實非常的難,我認為它的強項在於收集與統整;但不在展開項目有系統地逐步教學;然而誘導他整理出資源,人再自己去消化學習,把知道的系統性知識請它整理,才會是有效的合作模式

最後謝謝花時間閱讀的你,我知道這系列不是很好的內容,很多時候都是草草趕出來的;這一系列比較是自我的挑戰跟實驗性的文章,如果內容上有問題,都歡迎高人指點,小的會努力繼續鑽研更多的技術內容。


上一篇
Day 29. 手機輕點沒模型反應: 找出原因,改用 pointer 事件
系列文
從空拍到3D展示 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言