【Blender】客製化建立Attributes

帽捲
Maochinn
Published in
23 min readJul 16, 2024

這篇介紹如何透過Blender將Mesh中每個vertex上增加特定attribute,舉例來說,要在每個vertex上增加UV、normal以及color乃至於其他客制化的attribute,更進一步來說,就是vertex shader中的attribute。

建立UV Map

這當然可以自己寫一個小程式去做,但是這邊就沿用【Blender】客製化生成簡單Mesh的思路去做,將生成出來的Grid進一步增加其他的attribute,一種最簡單的方式就是增加客製化的UV,只是在UV裡面塞自己需要使用的值。

首先,Blender建立Grid的時候可以選擇Generate UVs,如果在建立時有打勾的就會再產生Mesh的時候也會順便產生UV Map,每個vertex的UV會存在其中,我們可以在console輸入

C.active_object.data.uv_layers['UVMap'].data

就可以拿到預設的UV Map,也可以透過len得知裡面有640個元素,換言之,這個Grid總共有640個uv。

可以進一步觀察一下,前四個對應左下角的正方形,同時因為這個Grid有20*8個四邊形,因此總共20*8*4=640個UV。

事實上,不需要每次都要透過console檢視資訊,可以打開Data API,可以看到所有物件詳細的變數內容

例如,可以在Meshes/Gird/UV Loop Layers 中找到名為UVMap的變數,他就是對應前述的變數,在展開可以發現他的Name以及其他性質,也可以看到UV中有640個items。

如果展開來看前四個,基本上與前面透過console檢視的結果一致

這邊要做個解釋,雖然看起來Grid的vertex看起來是21*9=189個,因此直覺uv的數量也會是21*9,但實際上,UV是與四邊形的數量有關,舉例來說,這個Grid一共有20*8個四邊形,其中每個四邊形又由4個vertices組成,因此,一共有20*8*4=640個vertices,此時,究竟是189還是640呢?

其實,兩者都對,但是要注意一個關鍵是,同一個vertex會被多個面(四邊形)所有共用,但在每個面在用這個vertex時可能會有不同的attribute,舉例來說,一個立方體的8個vertices的法向量並不固定,如果單純考慮每個vertex的法向量可能會如同紅筆所示,但是如果是以朝左面的角度來考慮,每個vertex的法向量應該如藍筆所示。

換言之,每個vertex的法向量並不固定,根據每個面的不同可能會有不同的法向量,換個說法,不只是vertex有法向量,每個face或者說是polygon也都會有法向量,因此,比較General的做法應該是要存6面*4點=24個法向量,延伸到其他attribute也是同理,例如UV,尤其是UV在邏輯上是用來在face上貼圖,因此同一個vertex有不同的UV也相當合理。

或者,如果想要自己建立一個也可以透過旁邊的+號來添加一個UV Map,這邊姑且名稱改做Map,其餘的操作都如同上述。

修改UV Map

既然知道了UV存在哪裡,那接下來就直接改就行,只是現在我們要確定的是,UV Map中存的所有值的順序是什麼,也就是每個UV對應哪個vertex,否則我們不知道要以什麼順序去給值,或者說,我們並不知道每一個UV對應的是哪個Vertex,接著,我們可以來看另一個一樣有640個items的Loops

事實上,這個Loops中的每個item會一一對應UV Map中的每個item,如果展開Loops中的前4個items,每個items中會儲存vertex的index,分別是0、1、22、21,而這4個vertex實際上可以透過console存取

bpy.data.objects['Grid'].data.vertices[0]
bpy.data.objects['Grid'].data.vertices[1]
bpy.data.objects['Grid'].data.vertices[22]
bpy.data.objects['Grid'].data.vertices[21]

或者你也可以在Data API中展開Vertices來檢查看看,這邊我就不展示了,然後複製下面的腳本並執行就可以依據修改每個UV,這邊基本上沿用【Blender】客製化生成簡單Mesh的腳本,把每個vertex的position normalize到0~1,以此當作UV。

import bpy

list = []
list.append((0.0000, 0.0000))
list.append((45.0000, 0.0000))
list.append((90.0000, 0.0000))
list.append((135.0000, 0.0000))
list.append((180.0000, 0.0000))
list.append((225.0000, 0.0000))
list.append((270.0000, 0.0000))
list.append((315.0000, 0.0000))
list.append((360.0000, 0.0000))
list.append((405.0000, 0.0000))
list.append((450.0000, 0.0000))
list.append((495.0000, 0.0000))
list.append((540.0000, 0.0000))
list.append((585.0000, 0.0000))
list.append((630.0000, 0.0000))
list.append((675.0000, 0.0000))
list.append((720.0000, 0.0000))
list.append((765.0000, 0.0000))
list.append((810.0000, 0.0000))
list.append((855.0000, 0.0000))
list.append((900.0000, 0.0000))
list.append((0.0000, 50.0000))
list.append((45.0000, 50.0000))
list.append((90.0000, 50.0000))
list.append((135.0000, 50.0000))
list.append((180.0000, 50.0000))
list.append((225.0000, 50.0000))
list.append((270.0000, 50.0000))
list.append((315.0000, 50.0000))
list.append((360.0000, 50.0000))
list.append((405.0000, 50.0000))
list.append((450.0000, 50.0000))
list.append((495.0000, 50.0000))
list.append((540.0000, 50.0000))
list.append((585.0000, 50.0000))
list.append((630.0000, 50.0000))
list.append((675.0000, 50.0000))
list.append((720.0000, 50.0000))
list.append((765.0000, 50.0000))
list.append((810.0000, 50.0000))
list.append((855.0000, 50.0000))
list.append((900.0000, 50.0000))
list.append((0.0000, 100.0000))
list.append((45.0000, 100.0000))
list.append((90.0000, 100.0000))
list.append((135.0000, 100.0000))
list.append((180.0000, 100.0000))
list.append((225.0000, 100.0000))
list.append((270.0000, 100.0000))
list.append((315.0000, 100.0000))
list.append((360.0000, 100.0000))
list.append((405.0000, 100.0000))
list.append((450.0000, 100.0000))
list.append((495.0000, 100.0000))
list.append((540.0000, 100.0000))
list.append((585.0000, 100.0000))
list.append((630.0000, 100.0000))
list.append((675.0000, 100.0000))
list.append((720.0000, 100.0000))
list.append((765.0000, 100.0000))
list.append((810.0000, 100.0000))
list.append((855.0000, 100.0000))
list.append((900.0000, 100.0000))
list.append((0.0000, 150.0000))
list.append((45.0000, 150.0000))
list.append((90.0000, 150.0000))
list.append((135.0000, 150.0000))
list.append((180.0000, 150.0000))
list.append((225.0000, 150.0000))
list.append((270.0000, 150.0000))
list.append((315.0000, 150.0000))
list.append((360.0000, 150.0000))
list.append((405.0000, 150.0000))
list.append((450.0000, 150.0000))
list.append((495.0000, 150.0000))
list.append((540.0000, 150.0000))
list.append((585.0000, 150.0000))
list.append((630.0000, 150.0000))
list.append((675.0000, 150.0000))
list.append((720.0000, 150.0000))
list.append((765.0000, 150.0000))
list.append((810.0000, 150.0000))
list.append((855.0000, 150.0000))
list.append((900.0000, 150.0000))
list.append((0.0000, 200.0000))
list.append((45.0000, 200.0000))
list.append((90.0000, 200.0000))
list.append((135.0000, 200.0000))
list.append((180.0000, 200.0000))
list.append((225.0000, 200.0000))
list.append((270.0000, 200.0000))
list.append((315.0000, 200.0000))
list.append((360.0000, 200.0000))
list.append((405.0000, 200.0000))
list.append((450.0000, 200.0000))
list.append((495.0000, 200.0000))
list.append((540.0000, 200.0000))
list.append((585.0000, 200.0000))
list.append((630.0000, 200.0000))
list.append((675.0000, 200.0000))
list.append((720.0000, 200.0000))
list.append((765.0000, 200.0000))
list.append((810.0000, 200.0000))
list.append((855.0000, 200.0000))
list.append((900.0000, 200.0000))
list.append((0.0000, 250.0000))
list.append((45.0000, 250.0000))
list.append((90.0000, 250.0000))
list.append((135.0000, 250.0000))
list.append((180.0000, 250.0000))
list.append((225.0000, 250.0000))
list.append((270.0000, 250.0000))
list.append((315.0000, 250.0000))
list.append((360.0000, 250.0000))
list.append((405.0000, 250.0000))
list.append((450.0000, 250.0000))
list.append((495.0000, 250.0000))
list.append((540.0000, 250.0000))
list.append((585.0000, 250.0000))
list.append((630.0000, 250.0000))
list.append((675.0000, 250.0000))
list.append((720.0000, 250.0000))
list.append((765.0000, 250.0000))
list.append((810.0000, 250.0000))
list.append((855.0000, 250.0000))
list.append((900.0000, 250.0000))
list.append((0.0000, 300.0000))
list.append((45.0000, 300.0000))
list.append((90.0000, 300.0000))
list.append((135.0000, 300.0000))
list.append((180.0000, 300.0000))
list.append((225.0000, 300.0000))
list.append((270.0000, 300.0000))
list.append((315.0000, 300.0000))
list.append((360.0000, 300.0000))
list.append((405.0000, 300.0000))
list.append((450.0000, 300.0000))
list.append((495.0000, 300.0000))
list.append((540.0000, 300.0000))
list.append((585.0000, 300.0000))
list.append((630.0000, 300.0000))
list.append((675.0000, 300.0000))
list.append((720.0000, 300.0000))
list.append((765.0000, 300.0000))
list.append((810.0000, 300.0000))
list.append((855.0000, 300.0000))
list.append((900.0000, 300.0000))
list.append((0.0000, 350.0000))
list.append((45.0000, 350.0000))
list.append((90.0000, 350.0000))
list.append((135.0000, 350.0000))
list.append((180.0000, 350.0000))
list.append((225.0000, 350.0000))
list.append((270.0000, 350.0000))
list.append((315.0000, 350.0000))
list.append((360.0000, 350.0000))
list.append((405.0000, 350.0000))
list.append((450.0000, 350.0000))
list.append((495.0000, 350.0000))
list.append((540.0000, 350.0000))
list.append((585.0000, 350.0000))
list.append((630.0000, 350.0000))
list.append((675.0000, 350.0000))
list.append((720.0000, 350.0000))
list.append((765.0000, 350.0000))
list.append((810.0000, 350.0000))
list.append((855.0000, 350.0000))
list.append((900.0000, 350.0000))
list.append((0.0000, 400.0000))
list.append((45.0000, 400.0000))
list.append((90.0000, 400.0000))
list.append((135.0000, 400.0000))
list.append((180.0000, 400.0000))
list.append((225.0000, 400.0000))
list.append((270.0000, 400.0000))
list.append((315.0000, 400.0000))
list.append((360.0000, 400.0000))
list.append((405.0000, 400.0000))
list.append((450.0000, 400.0000))
list.append((495.0000, 400.0000))
list.append((540.0000, 400.0000))
list.append((585.0000, 400.0000))
list.append((630.0000, 400.0000))
list.append((675.0000, 400.0000))
list.append((720.0000, 400.0000))
list.append((765.0000, 400.0000))
list.append((810.0000, 400.0000))
list.append((855.0000, 400.0000))
list.append((900.0000, 400.0000))

# Store to UV Map
##################################
# https://s-nako.work/2021/02/how-to-get-mapping-of-uv-and-vertex/
mesh = bpy.data.objects['Grid']
mesh_loops = mesh.data.loops
vertices = mesh.data.vertices

for i, mesh_loop in enumerate(mesh_loops):
mesh_uv_loop = mesh.data.uv_layers['Map'].data[i]
mesh_uv_loop.uv[0] = list[mesh_loop.vertex_index][0] / 900.0
mesh_uv_loop.uv[1] = list[mesh_loop.vertex_index][1] / 400.0

注意到這邊Mesh跟UV Map的名稱要是GridMap,其中,mesh_loops中會有640個items,然後一一對應填到mesh_uv_loop中,mesh_loop.vertex_index則是每個item的vertex編號,因為list中也是按照相同順序去儲存的,因此就可以利用mesh_loop.vertex_index拿到該UV對應的vertex position,其中,list中的position是(0~900, 0~400),然後將其(900, 400)再填到UV Map。

接著我們可以打開UV Editing的Layout,在中見畫面中選取所有的Vertices,可以看到目前有兩個UV Map,並且記得選擇到Map,就可以在左邊看到對應的UV座標。

也可以只選取部分的vertex,也可以看到對應的UV

事實上,這個Map與預設的UV Map並無二致,但如果我們稍加修改

    mesh_uv_loop.uv[0] = list[mesh_loop.vertex_index][0] / 900.0
mesh_uv_loop.uv[1] = list[mesh_loop.vertex_index][1] / 400.0

把400改成900,然後去執行

    mesh_uv_loop.uv[0] = list[mesh_loop.vertex_index][0] / 900.0
mesh_uv_loop.uv[1] = list[mesh_loop.vertex_index][1] / 900.0

回到UV Editing可以看到不同的結果

以此類推,這個只是針對UV去做的典型操作,但是既然你已經知道每個UV點對應哪個vertex,也知道如何修改,那此時就可以寫各種不同的attribute到UV,舉例來說,給定某種UV就可以變成各種奇怪的形狀

事實上,UV Maps下面就有Attributes的選項,展開就可以發現這個Map的attribute是定義在Face Corner上的,也就是以每個面的角落AKA四方形的vertex,這也與前面的觀察一致。

補充一個小點,在Data API中還有一個名為Polygon的array,他總共有160個items,其實就是對應20*8個四方型,如果展開第一個Polygon來看,第0個polygon由第0、1、22、21個vertex所組成,而最後一個polygon則由第166、167、188、187個vertex所組成,事實上,這邊是規定了每一個polygon由幾號vertex組成,因為Loops他只是一大串陣列,並不知道哪幾個item是屬於同一個面。

第0個polygon與第159個polygon

因此,每個Polygon中有Loop Start(S)與Loop Total(T)這兩個變數,他們用來記錄這個polygon由Loops中第S個item到第S+T個item所組成舉例來說,Loop Start = 0、Loop Total = 4表示該Polygon由Loops[0]、Loops[1]、Loops[2]、Loops[3]所組成,最後一個polygon則是由Loops[636]、Loops[637]、Loops[638]、Loops[639]組成的,同時,Loops中的每個item也都知道各自對應的vertex,以此就可以確實組成一個polygon。

總結來說,以我們這個20*8格的Grid來說,總共有21*9個vertices,20*8個polygon,其中每個polygon都是四邊形,也就是每個Polygon由4個點構成,因此Loops中共有20*8*4個items。

--

--