檢視畫面、邊界和剪輯

說明

本指南說明景觀區中的檢視邊界和裁剪方式。本指南說明如何設定檢視範圍、如何解讀指令的用途,以及檢視畫面邊界對現有景觀子系統的影響。

概念

設定檢視範圍

嵌入器必須為檢視畫面和檢視持有者建立一對符記,並在其檢視畫面中分配空間,以便配置嵌入檢視容器。方法是在嵌入檢視的檢視容器上設定邊界。如要設定檢視畫面的檢視畫面邊界,您必須在其各自的 ViewHolder 上呼叫 SetViewProperties。您可以在檢視畫面建立之前或之後呼叫 SetViewProperties,並連結至 ViewHolder,因此無須擔心設定的順序。邊界本身是在 3D 空間中指定最小和最大點 (xyz) 來設定。

邊界外距和插邊

ViewProperties 結構集中的 bounding_box 屬性是用來設定檢視區塊的邊界。上下限代表軸對齊定界框的最小和最大座標點。

insets_from_mininsets_from_max 屬性會提示定界框和插邊之間的區域可能會遭到遮蓋。「風景」不會使用插邊值來判斷定界框, 但在圖像外繪製的內容

{ bounding_box.min + inset_from_min, bounding_box.max - inset_from_max }

您可以讓其祖系檢視畫面遮蔽。遮蓋的原因與周圍的規則會因產品而異。

範例

// Create a pair of tokens to register a view and view holder in
// the scene graph.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();

// Create the actual view and view holder.
scenic::View view(session, std::move(view_token), "View");
scenic::ViewHolder view_holder(session, std::move(view_holder_token),
                               "ViewHolder");
// Set the bounding box dimensions on the view holder.
view_holder.SetViewProperties({.bounding_box{.min{0, 0, -200}, .max{500, 500, 0}},
                               .inset_from_min{20, 30, 0},
                               .inset_from_max{20, 30, 0}});

上述程式碼會建立 View 和 ViewHolder 組合,其邊界為 (0, 0, -200) 且延伸至 (500, 500, 0)。邊界本身一律採用軸對齊。

座標系統

景觀的 3D 系統由多個座標空間組成。這些空間大致可分為「Layer Space」、「World Space」和「Local Space」。空間點在每個座標系統中都有獨立的座標。轉換矩陣說明如何對應兩個座標空間之間的空間點;系統會將系統 A 中的點座標乘以轉換矩陣,藉此取得系統 B 中的座標 (或是矩陣反方向的相反方向)。

所有景觀座標系統一律右手。

座標空間圖表

圖層空間 (螢幕空間)

圖層空間是與畫面上區域相對應的 2D 座標空間。- 起點:左上角,X 軸指向右側,Y 軸則指向下方的 Y 軸。軸已對齊螢幕。- 尺寸:通常等於顯示尺寸 (全螢幕圖層)。

相機轉換

相機轉換矩陣可將世界座標從世界空間轉換為圖層空間。

這項轉換作業實際上包含幾個步驟。

首先,我們從 World Space 轉換為稱為 Camera Space 的中間空間。這是從 -1 到 1 的正規化三維座標系統,朝著各方向前進。這個座標系統是右手,X 軸指向右邊,Y 軸指向下,Z 軸指向「面向螢幕」。世界空間中相機的位置和方向會定義轉換。

接下來的投影轉換矩陣決定瞭如何將相機空間平縮至圖層空間的 2D 空間。這項轉換會決定要執行的投影類型:正向投影或透視投影。最常見的選擇是正規,因為世界基本上在 Z 方向上扁平,同時維持 X 軸和 Y 軸。

最後,系統可能套用剪輯空間轉換來縮放及轉譯 2D 空間,以啟用放大功能1

世界太空

「世界空間」是場景圖根層級檢視畫面的位置,以及相機的位置。因此整個場景圖已嵌入這個空間。這個空間定義了相機空間與場景的關聯。

轉換節點

場景圖中的每個節點都有與其相關聯的本機空間。這個空間由該節點的轉換矩陣定義,後者會將節點本機空間中的座標轉換為其父項的本機空間。這個矩陣取決於節點在父項定義的位置、旋轉和縮放。

每個節點的「本機空間」可以乘以每個轉換矩陣,將每個轉換矩陣向上相乘,在場景圖的根層級結束(它們的轉換矩陣就是根號本地空間到世界空間的轉換),而這可以關聯到世界空間。這個相乘的矩陣稱為節點的「全域轉換」。

節點的轉換矩陣是由該節點的轉譯、縮放和旋轉組合而成。系統會縮放應用程式順序,然後旋轉應用程式,最後再翻譯。由於這些值都可以寫入其各自的轉換矩陣,而且矩陣的乘法順序,因此我們可以按照以下方式編寫完整轉換作業:

parent_local_point = translation matrix * rotation_matrix * scale_matrix * local_point

範例

我們有一個場景圖,由節點 Node1 和 Node2 組成。Node1 是 Node2 的子項,而 P1 是 Node1 本機空間 (Node1 與 Node2) 中的點,兩者都有旋轉、縮放和轉譯。如要取得 P1 的世界空間座標,然後依序合併所有父項的轉換: p1_world_position = (translation_node2 * rotation_node2 * scale_node2) * (translation_node1 * rotation_node1 * scale_node1) * p1_local_position

當地空間 (Scenic View Space) 2

由於並非每個工作階段都完全掌握整個場景圖,因此全都改為在檢視畫面的本機座標空間中處理。這是一個工作階段定義的空間,定義其節點的轉換,以及輸入座標的傳送空間。

  • 起點:檢視畫面的「背面」。檢視畫面的 Z 維度通常設為 -1000 到 0 (這是左手座標系統開始時,只有右手才能開始的副作用),因此檢視畫面的起點位於最大 Z 值,但 X 和 Y 值最小。通常沿著世界空間軸對齊。

檢視邊界

檢視畫面範圍是由本機座標指定,且其世界空間位置是由檢視節點的全域轉換決定。

輸入座標來自「Layer Space」,通常與畫面左上方的起點像素座標對應。輸入系統可與合成器和相機搭配使用,透過射頻投放與命中測試的方式,從輸入裝置座標對應至世界空間。

範例

// Create a view and view-holder token pair.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
scenic::View view(session, std::move(view_token), "View");
scenic::ViewHolder view_holder(session, std::move(view_holder_token),
                               "ViewHolder");

// Add the view holder as a child of the scene.
scene.AddChild(view_holder);

// Translate the view holder and set view bounds.
view_holder.SetTranslation(100, 100, 200);
view_holder.SetViewProperties({.bounding_box{.max{500, 500, 200}}});

在上述程式碼中,本機空間中的檢視畫面邊界在 (0, 0, 0)(500, 500, 200) 的最小值和最大值之間,但因為世界空間中的檢視畫面邊界為 (100, 100, 200) 所轉譯的父項節點,因此實際上會有世界空間邊界的最小值和最大值 (100, 100, 200)(600, 600, 400)。不過,該檢視畫面本身不會顯示這些世界空間的邊界,且只能在自己的當地空間內查看邊界。

以幾何圖形置中

幾何圖形的質心中心 (例如 RoundedRectangle) 是其中心,而對於檢視畫面,其邊界的質心則是最小座標。也就是說,如果檢視畫面和圓角矩形都具有相同的平移,圓角矩形的中心則轉譯為檢視畫面邊界的最小座標。如要修正這個問題,請在形狀節點上套用其他平移,以將其移至檢視畫面邊界的中心。

置中幾何圖形圖

對線框轉譯進行偵錯

為協助偵錯檢視畫面邊界,您可以在「線框模式」模式中算繪邊界的邊緣,查看檢視畫面在「世界空間」中的確切位置。此功能可以使用景觀指令,以每次檢視畫面套用:

// This command turns on wireframe rendering of the specified
// view, which can aid in debugging.
struct SetEnableDebugViewBoundsCmd {
    uint32 view_id;
    bool display_bounds;
};

這個指令會納入 view id,並使用 bool 來切換是否要顯示檢視畫面範圍。預設顯示顏色為白色,但您可以在指定檢視容器上執行 SetViewHolderBoundsColorCmd,藉此選擇不同的顏色:

// This command determines the color to be set on a view holder’s debug
// wireframe bounding box.
struct SetViewHolderBoundsColorCmd {
    uint32 view_holder_id;
    ColorRgbValue color;
};

Ray 投放及點擊測試

將地圖圖層空間座標投放到場景的幾何圖形和座標,進行光柵測試。最後,輸入內容會傳送至具有檢視本機座標的檢視畫面。如座標系統一節所述,檢視區域座標是由檢視節點的全域轉換決定,該節點會從 World Space 對應至本機空間。

爆笑少女

插入觸控輸入事件時,我們必須瞭解以下兩件事:1. 要將輸入事件傳送至哪些用戶端?2. 用戶端本機座標系統中輸入事件的座標為何?

從圖層空間轉換為世界空間的輸入座標,需要處理輸入系統、合成器層和相機。

輸入座標空間

為了找出將事件傳送給哪個用戶端,我們會執行命中測試。方法是將一個光射 (在世界太空) 投射在場景中,以查看相互交集的物件,然後將輸入內容傳送至最接近的命中物件。接著,我們選取在場景中點的位置時,我們會針對命中物件本身擁有的檢視畫面,計算世界空間到當地空間轉換,並使用這個結果將輸入事件傳送至本機座標中的用戶端。

原始輸入座標是螢幕像素中的二維座標。輸入系統和合成器必須達成共識,上述慣例是以裝置座標為 3 個維度 (藍色) 表示,其中檢視音量為深度 1,鄰近平面的值為 z = 0,遙遠的平面位於 z = -1。因此,輸入系統會計算觸控座標的世界空間座標 (透過反轉相機轉換) 來建構命中射線,並將這些座標設為起點 (以 Z 為 0,方向為 (0, 0, 1)),並朝向場景。

在世界空間中,上述的命中線源自 (x, y, -1000),方向為 (0, 0, 1000)

規則

執行命中測試時,風景必須針對 ViewNode 的邊界執行測試,然後再判斷光線是否應繼續檢查該節點的子項。

  • 如果光是完全錯過檢視區塊的定界框,則系統不會命中該檢視區塊的子項。

  • 如果射線與定界框相交,則只有存在於射線入口範圍中的幾何圖形,才會視為命中。例如無法點擊裁剪的幾何圖形。

如果忘記設定檢視畫面的邊界,則該檢視畫面子項的任何幾何圖形都無法命中。這是因為邊界為空值,且會無限小,也就是說螢幕不會算繪任何幾何圖形。

在偵錯模式中,空值定界框會在 escher::BoundingBox 類別中觸發 FXL_DCHECK,指出定界框維度必須大於或等於 2。

特殊情況

光柵與定界框側面,只是只取出邊緣,就不算是命中。由於構成定界框的六個平面本身就是夾層,因此直接位於夾層上的任何內容也會遭到裁切。

碰撞

如果光射轉換偵測到同一距離超過兩次或更多命中,就會發生碰撞。衝突表示命中目標在場景中重疊且佔用同一個位置。系統會將此視為錯誤行為,而在發生衝突時,Looker 不提供命中測試訂購保證。用戶端必須避免衝突。

發生衝突的方式有兩種:

  • 同一個檢視畫面中的節點之間衝突。自有檢視區塊必須確保檢視區塊中的元素位置正確。

  • 不同檢視畫面中的節點之間衝突。父項檢視畫面必須防止其子項裁剪邊界之間的交集。

此外,最佳做法是遵循這些規則,避免視覺內容出現多面向衝突。

偵測到衝突時,系統會依工作階段 ID 和資源 ID 記錄衝突節點的警告。


  1. 更好的做法是在圖中插入縮放節點,然後直接操控,這樣更能處理放大問題。這種方法造成某些應用程式問題,例如應用程式大小和應分配的記憶體 (因為指標事件是以縮放功能核發),因此剪輯空間轉換作業成為替代方案。此外,由於輸入內容目前是在攝影機空間中處理,因此難免有不幸的連帶效果,滑動手勢在放大後可以縮小。 

  2. 請留意常見的 3D 圖形模型-檢視投影標記法 (這裡稱為 Camera Space),因此與 View Space 不同。