使用視口

前言

可以將 Viewport 想像成一個投影遊戲內容的螢幕。為了能看到遊戲畫面,我們需要有一個用來繪製的表面,而這個表面就是根視口(Root Viewport)。

../../_images/subviewportnode.webp

子視口 是一種可以加入場景的視口,讓你能有多個可用來繪製的表面。當我們繪製到子視口時,這個視口就被稱為算繪目標。你可以透過取得其對應的 紋理 來存取算繪目標的內容。透過將子視口作為算繪目標,可以同時算繪多個場景,或是將畫面輸出到 ViewportTexture,並將其套用到場景內的物件上,例如動態天空盒等情境。

子視口 有多種用途,例如:

  • 在 2D 遊戲中算繪 3D 物件

  • 在 3D 遊戲中算繪 2D 元素

  • 算繪動態紋理

  • 於執行時動態產生程式化紋理

  • 在同一場景中算繪多個相機畫面

這些用法的共通點在於,你可以像在另一個螢幕上一樣,將物件繪製到某個紋理上,接著可以自由決定如何使用這個產生出來的紋理。

Godot 另一種視口是 視窗。它們可以將內容投影到一個視窗上。雖然根視口本身就是一個視窗,但它的彈性有限。如果你想要使用某個視口的紋理,多數情況下會使用 子視口

輸入

Viewport 也負責將適當調整和縮放過的輸入事件傳遞給其子節點。預設情況下,子視口 不會自動接收輸入,除非它們從其直接的 SubViewportContainer 父節點接收到輸入。在這種情況下,可以透過 Disable Input 屬性來停用輸入。

../../_images/input.webp

更多關於 Godot 如何處理輸入事件的資訊,請參閱 輸入事件教學

監聽器

Godot 支援 3D 音效(無論是在 2D 或 3D 節點中)。詳細內容請參閱 音訊串流教學。若要讓這類型的音效能夠被聽見,必須將 Viewport 啟用為監聽器(2D 或 3D 皆可)。如果你使用 子視口 來顯示你的 World3DWorld2D,請務必記得啟用這個功能!

相機(2D 與 3D)

當你在場景中使用 Camera3DCamera2D 時,該相機畫面會顯示在自下而上最近的父 Viewport 上。例如,以下層級結構:

../../_images/cameras.webp

CameraA 會顯示在根節點的 Viewport 上,並繪製 MeshACameraB 則會被 子視口 及其下的 MeshB 捕獲。即使 MeshB 在場景樹中,依然不會被繪製到根視口。反之,MeshA 也不會出現在子視口,因為子視口只會抓取層級結構中自己底下的節點。

每個 Viewport 僅能有一個啟用中的相機。如果有多個相機,請確保目標相機已將 current 屬性設為啟用,或用以下方式設為目前相機:

camera.make_current()

預設情況下,所有相機會算繪其所屬世界中的所有物件。在 3D 場景下,相機可以透過 cull_mask 屬性搭配 VisualInstance3Dlayer 屬性,來限制哪些物件需要被算繪。

縮放與拉伸

子視口 擁有 size 屬性,代表該子視口的像素尺寸。若該子視口是 SubViewportContainer 子節點,這個值會被覆蓋;否則則會直接決定其解析度。

你也可以透過呼叫以下方法,將 子視口 的 2D 內容縮放,讓解析度與其尺寸設定不同:

sub_viewport.set_size_2d_override(Vector2i(width, height)) # Custom size for 2D.
sub_viewport.set_size_2d_override_stretch(true) # Enable stretch for custom size.

關於根視口(Root Viewport)縮放與拉伸的相關資訊,請參考 多解析度教學

世界

在 3D 場景中,每個 Viewport 會擁有一個 World3D。這個 World3D 就像一個結合物理與算繪的宇宙。所有基於 Node3D 的節點都會註冊到最近的父視口所屬的 World3D。預設情況下,新增的視口本身不包含 World3D,而是沿用其父視口的 World3D。根視口(Root Viewport)則一定有自己的 World3D,也是預設物件算繪到的地方。

你可以透過設定 ViewportWorld 3D 屬性,指定一個專屬的 World3D。這將使該 Viewport 下所有子節點都與父視口的 World3D 隔離,彼此不互動。這在有些情境下非常實用,例如你想在遊戲畫面上額外顯示一個獨立的 3D 角色(類似星海爭霸的頭像)。

若你只想讓 Viewport 顯示單一物件,又不想手動建立 World3D ,可以啟用該視口的 Own World3D 屬性。這在你想在 World2D 場景中動態生成 3D 角色或物件時會很有用。

在 2D 中,每個 Viewport 都會有自己的 World2D。大部分情況下這樣就足夠了,如果你有共用需求,可以透過程式碼手動設定該視口的 world_2d 屬性來達成。

有關這種用法的範例,請參考範例專案: [3D in 2D] 及 [2D in 3D]。

擷取

你可以取得 Viewport 畫面的擷取結果。對於根視口來說,這就等同於截圖。方法如下:

# Retrieve the captured Image using get_image().
var img = get_viewport().get_texture().get_image()
# Convert Image to ImageTexture.
var tex = ImageTexture.create_from_image(img)
# Set sprite texture.
sprite.texture = tex

但如果你在 _ready()Viewport 初始化的第一幀呼叫這段程式碼,會因為畫面還沒渲染好而取得空白紋理。你可以這樣處理(例如):

# Wait until the frame has finished before getting the texture.
await RenderingServer.frame_post_draw
# You can get the image after this.

視口容器

如果 子視口SubViewportContainer 的子節點,該子視口就會自動啟用並顯示其內容。整體結構如下:

../../_images/container.webp

如果父節點 SubViewportContainerStretch 屬性設為 true,那麼 子視口 就會完全覆蓋其父容器的範圍。

備註

SubViewportContainer 的大小不能小於其底下 SubViewport 的尺寸。

算繪

由於 Viewport 是另一個算繪表面的入口,因此它提供了一些可與專案設定不同的算繪屬性。你可以為每個視口選擇不同層級的 MSAA。預設為「停用」。

如果你確定這個 Viewport 只會用於 2D,可以 停用 3D。這樣 Godot 會限制該視口的繪製方式。相較於啟用 3D,停用 3D 會稍微快一些且佔用更少記憶體。如果你的視口不需要 3D 算繪,建議關閉這個選項。

備註

若你需要在視口中算繪 3D 陰影,請將該視口的 positional_shadow_atlas_size 屬性設為大於 0 的值,否則陰影將不會被算繪。預設情況下,專案設定在桌面平台為 4096,行動裝置則為 2048

Godot 也提供 Debug Draw 功能,可自訂各種 Viewport 內部的繪製方式。Debug Draw 預設為「停用」。你可從多種模式中選擇,例如 Unshaded (無陰影)、 Overdraw (疊加)、 Wireframe (線框)等。完整列表請參閱 Viewport 屬性說明

  • Debug Draw = Disabled (預設):以一般方式顯示場景內容。

../../_images/default_scene.webp
  • Debug Draw = Unshaded:無陰影會讓場景不受光照影響,所有物件都以其原始色(Albedo)呈現為純色。

../../_images/unshaded.webp
  • Debug Draw = Overdraw:疊加模式會將網格以半透明加法方式繪製,可讓你觀察網格重疊情形。

../../_images/overdraw.webp
  • Debug Draw = Wireframe:線框模式僅以網格的三角形邊線來描繪場景。

../../_images/wireframe.webp

備註

目前在使用「相容性」算繪模式時,Debug Draw 各模式**無法使用**,畫面會顯示為一般模式。

算繪目標

當算繪至 子視口 時,其內容在場景編輯器中將不會顯示。若要顯示內容,必須將該子視口的 ViewportTexture 繪製到某個地方。例如可以用程式碼這樣取得:

# This gives us the ViewportTexture.
var tex = viewport.get_texture()
sprite.texture = tex

或者也可以在編輯器內指定為 "New ViewportTexture"

../../_images/texturemenu.webp

然後選擇你想要使用的 Viewport

../../_images/texturepath.webp

每一幀,Viewport 的紋理會用預設清除色(或若 Transparent BG 設為 true 時則用透明色)來清除。你可以將 Clear Mode 設為 NeverNext Frame,來改變這個行為。Never 表示永遠不會清除,Next Frame 則會在下一幀清除並自動切回 Never。

預設情況下,當 SubViewportViewportTexture 在某幀被繪製時,才會重新算繪;若不可見則不算繪。你可以將 更新模式 設定為 Never (永不)、 Once (一次)、 Always (總是)或 When Parent Visible (當父節點可見時)來調整這個行為。Never 及 Always 會分別永不或總是重新算繪;Once 則在下一幀重新算繪後自動切回 Never。這種彈性可讓你只算繪一次影像、重複使用紋理,而不用每幀都消耗算繪資源。

備註

建議參考 Viewport 相關範例,這些專案可以在範例集的 viewport 資料夾找到,或直接前往 https://github.com/godotengine/godot-demo-projects/tree/master/viewport