Blender:Geometry Node 應用於營造法式生成大木作構件初探-卷殺折線標準化篇

摘要

本文探索了透過修改 Blender 原始碼來實作客製化的 Geometry Node。

前言

上一篇文提到計算卷殺的折線非常麻煩,需要計算各個等分點並計算交點。不過其實《營造法式》中提到的卷殺的折線其實十分固定,不同部位的折線只要等分的數字相同,皆可以被視作同樣的折線經過線性變換,也就是說我們只要計算一個折線後透過仿射變換就能套用到其他構件上的不同位置上,只要等分數相等。

另外我們也可以透過調整等分數來調整折線的柔化程度,例如實例中有些栱頭卷殺的等分點就不是 4 * 4,可能是 3 * 3,藉由簡單的線性變換也能控制卷殺曲線的長寬。

那麼不同等分的折線有沒有辦法找出各點的座標的公式呢?我發現其實可以。以下就用長寬相等的矩形,以二等分和三等分的卷殺為例。

二與三等分的卷殺推導公式 

簡單繪製兩等分與三等分的卷殺如下圖:

若以 Start 為原點,矩形為 1 * 1 的正方形,Start 和 End 在圖中只有繪製在三等分的圖上,實際上二等分亦同樣位置。

Start 和 End 的點座標為:
Start(0, 0)
End  (1, 1)

二等分的 D1 點座標為:
D1(1/3, 2/3)

三等分的 T1、T2 點座標為:
T1(1/6, 3/6)
T2(3/6, 5/6)

此時還未看見規律,但是依照順序從 Start 到 End 逐一計算座標差值即可以發現規律:

二等分的座標差值:
D1-Start(1/3, 2/3)
End-D1  (2/3, 1/3)

三等分的座標差值:
T1-Start(1/6, 3/6)
T2-T1   (2/6, 2/6)
End-T2  (3/6, 1/6)

從此可以發現各個點的座標會是等差級數。當有 N 等分,分母就是前 N 個自然數的和,而各個點由 Start 開始到 End 的 Index K 為 [0, N],共 N + 1 個點。
X 的分子便是前 K 個自然數的和,而 Y 的分子為反向,為由 N 開始 K 個的遞減自然數和,而這樣才能使得最後一個點座標為 (1, 1)。

折線點公式

N為自然數時,對於 N 等分的卷殺其產生的折線上, Index K 由 0 開始時,其第 K 點 PK 座標為:

PK(SK/SN, TN/SN)
SK = 1 + 2 + 3 + 4 + 5 + ... + K       = K( K     + 1) / 2
TK = N + (N - 1) + ... + (N - (K - 1)) = K(2N - K + 1) / 2

在以上的公式可以發現到折線是繪製在 XY 平面上的,起點為 (0, 0),終點為 (1, 1)。如果折線不在 XY 平面上,或是旋轉了呢?就需要用到仿射變換。

另外要注意的是,其實折線點的公式其實不只一種。實際上作圖後發現各點也可以由StartEnd上的等分點推算,藉由在各個等分點以法向量進行偏移得到折線點。

驗證

實際以四等分作圖,確定這個公式是正確的,圖的部分就不再額外繪製。

轉換成 Geometry Node

把座標的公式轉換成節點很容易,不過有沒有辦法一個節點就能把 N + 1 個點的座標計算完甚至是直接繪製好呢?這個需要透過 C++ 寫一個客製化的節點,不過 Blender 自製客製化的節點需要修改 Blender 原始碼並建置。我參考了此文章,不過因為該文章使用的是多年前的 Blender ,最新版本已經有些不同。

修改後的 Blender  Github 專案

Node 輸入輸出

在使用 C++ 寫一個客製化的節點時首先還是要確認這個 Node 的輸入參數和輸出有什麼。
基本上如果看到上面的公式可以知道有兩個參數 N 和 K,K 只需要內部計算,無需顯示。

另外如果要在空間中對 XY 平面上的 2D 圖形做仿射變換,需要知道變換後的 X 與 Y 軸向量與起始點,這樣就能處理旋轉縮放和位移,所以輸入和輸出分別是:

輸入:
N      : uint
Start  :
Vector3
X Axis : Vector3
Y Axis : Vector3

輸出:
卷殺折線 :Curve

Node 參考範本

比起從頭到尾無中生有寫一個 Node ,參考現有的 Node 會容易很多,我參考的是:

node_geo_curve_primitive_line.cc

主要是因為功能和輸出輸入很類似,我們的客製化節點要輸出多段折線,而上面提到的這個節點是單段直線。

實作成果

以令栱測試,下圖紅點為 Start,紅線對應原本的 X 軸向量,綠線對應原本的 Y 軸向量。以下皮中點為原點,將各項數據輸入 Node 後即產生卷殺的切線。實作中左右折線其實也用鏡像處理。

輸入的參數如下:

左側折線:
Start(-16U,   0)
XAxis(0   ,  9U)
YAxis(-16U ,  0)

右側折線:
Start(36U ,  9U)
XAxis(-20U,   0)
YAxis(0   , -9U)


static Curves *create_entasis_line_curve(const int n, const float3 start, const float3 xAxis, const float3 yAxis)
{
  Curves *curves_id = bke::curves_new_nomain_single(n + 1, CURVE_TYPE_POLY);
  bke::CurvesGeometry &curves = curves_id->geometry.wrap();

  float sn = (n * (n + 1)) >> 1;

  float4x4 trs = float4x4(float4(xAxis), float4(yAxis), float4(math::cross(xAxis, yAxis)), float4(start));

  MutableSpan<float3> positions = curves.positions_for_write();
  for(const int k : IndexRange(n + 1))
  {
    float sk = (k * (k + 1)) >> 1;
    float tk = (k * (2 * n - k + 1)) >> 1;
    float3 pk = float3(sk, tk, 0) / sn;
    positions[k] = math::transform_point(trs, pk);
  }

  return curves_id; 

留言

這個網誌中的熱門文章

Unity:嘗試製作可相交的視差遮蔽貼圖Shader (Parallax Occlusion Mapping Shader with Pixel Depth Offset)

Blender:Geometry Node 應用於營造法式生成大木作構件初探-梭柱篇