2021年5月23日 星期日

產生漩渦的紋理(Texture)

 產生漩渦的紋理(Texture)

前言

  最近看到一個教學 [ www.youtube.com ] Create a Black Hole in Blender - Iridesium ,影片裡有個步驟是用程式產生漩渦的紋理(Texture),覺得做法很有趣在此作紀錄。


內容

  用程式產生漩渦的紋理(Texture)的做法如下

用程式產生漩渦的紋理(Texture)的做法


圖中被紅圈圈起來的地方是要注意的地方,"Texture Coodinate Node"要使用"Object"作為輸入,"Gradent Texture Node"要使用"Spherical",最後的"MixRGB Node"是我覺得整個作法裡面最神奇的地方,當"Fac"的數值是"1"的時候會是一個有著整齊一環一環的圓圈,當數值是"0"時就單純變成Noise Texture ,數值保持在"0.5~0.8"會很像漩渦,如果要漩渦動起來可以透過"Mapping Node"的"Rotation"的"Z"就可以做出簡單的漩渦動畫。


參考資料

[ www.youtube.com ] Create a Black Hole in Blender - Iridesium

2021年5月12日 星期三

在 Blender 裡使用透射(Transmission)

 在 Blender 裡使用透射(Transmission)

前言

  如果只是單純在 Shader editor 裡將透射(Transmission)調成"1",會看到如下

將透射(Transmission)調成"1"

只是單純將透射(Transmission)調成"1"並不能得到透射(Transmission)的結果,而是變成黑色,顯然這不是透射(Transmission),如果需要透射(Transmission)必須要作一些調整才能得到結果,這裡把學習的過程做個紀錄。


內容

  要開啟透射(Transmission)不只是調成"1",還要在 Material Properties 作如下調整

調整 Material Properties

在  Material Properties 裡將"Screen Space Refraction"開啟,接著到 Render Properties 作如下調整
調整 Render Properties

在 Render Properties 裡將"Screen Space Refractions"開啟,並把子選項"Refraction"也一併開啟,接著就可以看到如下
設定完透射(Transmission)的結果


如果需要在 Material Preview 裡看到結果還需要作如下設定
設定 Material Preview

在 Material Preview 裡將"Scene Lights"與"Scene World"都開啟就可以看到透射(Transmission)的結果。

2021年5月4日 星期二

在 Blender 使用環境貼圖(Environment map)

 在 Blender 使用環境貼圖(Environment map)

前言

  Blender 本身有自帶一些環境貼圖(Environment map),但如果不夠用怎麼辦?這次會介紹如何取得環境貼圖(Environment map)並在 Blender 使用,在此做個紀錄。


內容

  如果覺得 Blender 自帶的環境貼圖(Environment map)不夠用可以到 [ hdrihaven.com ] HDRI Haven 下載,如下圖

在 HDRI Haven 下載環境貼圖(Environment map)

該網站相當貼心,在下載頁面可以選擇解析度來下載,下載的格式為 EXR。


  如何在 Blender 使用環境貼圖(Environment map)呢?在 Shader editor 作如下操作

編輯 Shader

在編輯視窗的上方將"Shader Type"切換為"World",接著新增 Environment texture node ,指定圖片後將"Color"連到 Background node 的"Color"。這樣就可以在 Rendered 下看到結果,但如果要在 Material preview 下看到結果需要做以下操作

在 Material priview 下看到結果


在 Material preview 下的設定將"Scene world"啟動就可以看到結果。


參考資料

[ hdrihaven.com ] HDRI Haven

2021年4月27日 星期二

補強 Blender 的 Shader node

 補強 Blender 的 Shader node

前言

  Blender 的 Shader node 提供的預設節點(node)種類有點少,少到有時一些基本的功能也必須用組合的,這裡開一篇來記錄相關的應用。


內容

Switch color:

  Shader node 並沒有提供 switch node ,但如果需要時該如何做呢?如下圖

switch node 的替代方案

範例的結果為當"Value"小於等於1時會是"紅色",大於且小於等於2時會是"綠色",大於2會是"藍色",要注意的是因為用"Greater Than"來完成,所以剛好等於1時還會是"紅色"。


Not equal:

  Shader node 有提供"=="也就是"Compare",但如果需要"!="(Not equal)呢?如下圖

實現"!="(Not equal)

Greater equal or Less equal:

  Shader node 僅提供"Greater Than"和"Less Than",如下圖

實現">="(Greater equal)

實現"<="(Less equal)

Not:

  Not 的運算是跟輸入相反,如下圖
實現"!"(Not)


Not 的運算必須考慮輸入型態是 float ,所以要先確認是否為"0",接著就可以去確定是否為"1",第二次的檢查可以確定輸入不是"0"就是"1",結果就會產生只有在輸入是"0"時結果為"1"。

And:

  And 的實現如下
實現"&"(And)

每個輸入都要經過兩次"Compare",目的是要將輸入是"0"時輸出"0",輸入是非零值時輸出"1",最後經過"Multiply"即為結果。


Or:

  Or 的實現如下
實現"|"(Or)

與 And 類似,但最後的運算改成"Add",並記得需要"Clamp",因為當輸入都是"1"時,輸出必須還是"1"。

Nand:

  Nand 的實現如下
實現 Nand

在 And 的結尾加上 Not 即為所求。


Nor:

  Nor 的實現如下
實現 Nor

在 Or 的結尾加上 Not 即為所求。

Xor:

  Xor 的實現如下
實現 Xor

將兩個輸入拿來"Compare"後再過 Not 即為所求。

Xnor:

  Xnor 的實現如下
實現 Xnor

移除 Xor 最後的 Not 即為所求。

2021年4月20日 星期二

在 Blender 匯入 Blend 檔裡的資料

在 Blender 匯入 Blend 檔裡的資料

前言

  由於 Blender 2.8 以後改採 Shader node 的方式來調整材質(Material),材質(Material)的內容可能變得相當複雜,要複製一個材質(Material)總不能從新編輯一個一樣的,能匯入嗎?這是很自然的想法,答案是可以的,在此把學習的過程做個紀錄。


內容

  在開始解說如何匯入前先來看到 Blender 是如何儲存資料的,如下圖操作

顯示 Blend 檔的結構

接著如下圖

Blend 檔的結構


可以在上圖發現 Blend 檔儲存相當多的資料,如 Mesh 、 Material ... Object 等,這些並不是全部的種類,有些種類會在有的時候才儲存,如 Shader node,上圖不過是預設的場景(Scene)所儲存的內容。


  在了解到 Blender 檔有儲存那些資料後接著就是如何匯入,如下圖操作

操作匯入 Blender 檔的資料

接著選擇要從哪個 Blender 檔匯入,如下圖
在要匯入的 Blender 檔上雙擊滑鼠

這個操作介面有個特別之處,在 Blender 檔上雙擊滑鼠並非選取該檔,而是把該檔像是資料夾一樣打開,範例使用 myMaterialDepot.blend 是一個示範用的檔案,並不具有特別意義。雙擊後可以看到以下
欲匯入的 Blend 檔的資料結構


這個會跟在觀看 Blend 檔的視窗顯示一樣的結構,接著選取要匯入的資料(Material、Mesh ... Object 等)用雙擊滑鼠或用右下的"Apend"按鈕就可以完成匯入。

2021年4月18日 星期日

最近學的 Python 新寫法

 最近學的 Python  新寫法

前言

  由於 Python 並非我擅長的語言,但因為工作環境時常會需要一些簡短的 Python 程式碼,如 Blender 的 Script ,所以寫法一直沒有進步,容易寫出冗長的程式碼,最近抽空學習了 Python 的新寫法,在此做個紀錄。


內容

瀏覽 List :

ar = [ 'one', 'two', 'three']
#Old style loop
for ele in range( len( ar ) ):
    print(ar[ ele ])
#New style loop1
for ele in ar:
    print(ele)
#New style loop2
for i, ele in enumerate( ar ):
    print( 'i:', i, ' ele:', ele)


舊的寫法很冗長,不但要 range() 還要 len() 搭配才能瀏覽陣列的內容,新的寫法簡單的多,如果有需要 index 變數可以搭配 enumerate() 來瀏覽。


瀏覽 Dictionary :

my_map = {
    'a' : 1,
    'b' : 2,
    'c' : 3
}
#Old style
for key in my_map:
    print( 'key:', key , ' value:', my_map[ key ] )
#New style1
for key, value in my_map.items():
    print( 'key:', key, ' value:', value)
#New style2
for value in my_map.values():
    print( value )


舊的寫法可以視為只取 key 值,然後再從 key 取得 value ,取得 value 時會較冗長,新的寫法透過 Dictionary 的 items() ,如果很確定只需要取 value 的話可以透 Dictionary 的 values() 來取得。


字串:

a = 'string'
b = 1234
#Old style
print( 'a:', a, "b:", b)
#New style
print( f'a:{a} b:{b}' )


舊的寫法會需要很多的',',新的寫法在字串的前方加'f',在字串裡可以透過"{}"來取得變數,整體看起來非常直覺與簡短。

 

參考資料

[ Mr. Opengate ] Pythonic 實踐:實用的 python 慣用法整理

2021年4月6日 星期二

在 Blender 開發 Addon 時載入自己的 Module

 在 Blender 開發 Addon 時載入自己的 Module

前言

  由於需要從 Blender 匯出自製遊戲引擎的格式,所以就需要開發 Addon ,但由於要支援的規格越來越多,所以程式碼就越來越多,全部都塞在一個檔案寫起來並不是很舒服,之前不拆成其它 Module 來載入是因為找不到方法來載入,官方並沒有提供載入 Module 的範例,但最近在 Valve 的 Source 引擎提供給 Blender 的 Addon 裡發現它可以載入自己的 Module ,所以就來研究是如何做的,在此做個紀錄。


內容

  Valve 的 Source 引擎提供給 Blender 的 Addon 可以在 [ steamreview.org ] Blender Source Tools 下載。在研究中發現以前從沒想過把 Addon 裝起來跟直接在 Blender 的文字介面有何不同,所以總是在文字介面裡做實驗,所以 Import 自己的 Module 總是會失敗,但我看了 Valve 提供的 Addon ,裡面遽然可以直接 Import 自己的 Module ,本以為事情解決了,但發現一些小毛病!如果在安裝的環境下開發,當程式被修改時, Blender 並不會去 Reload 它,這裡我找到兩種方法來 Reload ,一種是直接用 Blender 的操作介面來 Reload ,如下圖

Reload Addon 在 Blender 的介面

這個方法很簡潔,但不知為什麼每次 Reload 時會小小的頓一下,所以我個人比較建議第二種方法。第二種方法是利用 Addon 的啟動按鍵來 Reload ,如下圖

利用 Addon 的起用來 Reload

這個方法有個麻煩, 只是單純的利用關閉後再啟用會發現其實它不會被 Relaod ,但如果主程式(__init__.py)的修改日期有變動時,它就會 Reload,這種 Reload 操作會負責一點,但可以確定只 Reload 這個 Addon。解決了不會 Reload 後又發現當自己的 Module 被修改時,也不會 Reload! Reload 似乎只針對主程式(__init__.py),所以需要在主程式增加 Reload 自己的 Module ,這段程式碼我在 Valve 的 Addon 裡找到了如下


 # Python doesn't reload package sub-modules at the same time as __init__.py!
import importlib, sys
for filename in [ f for f in os.listdir(os.path.dirname(os.path.realpath(__file__))) if f.endswith(".py") ]:
	if filename == os.path.basename(__file__): continue
	module = sys.modules.get("{}.{}".format(__name__,filename[:-3]))
	if module: importlib.reload(module)

# clear out any scene update funcs hanging around, e.g. after a script reload
from bpy.app.handlers import depsgraph_update_pre, depsgraph_update_post
for func in depsgraph_update_post:
	if func.__module__.startswith(__name__):
		depsgraph_update_post.remove(func)
 

要在主程式裡加這段才可以 Reload 自己的 Module 。完整的範例程式碼如下

__init__.py

bl_info = {
	"name": "My export addon",
	"author": "Hosee",
	"version": (1, 0, 0),
	"blender": (2, 80, 0),
	"category": "Import-Export",
	"location": "",
	"wiki_url": "",
	"tracker_url": "",
	"description": "My custom export addon."
}
#
import bpy, os
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator

#follow code from Valve
# Python doesn't reload package sub-modules at the same time as __init__.py!
import importlib, sys
for filename in [ f for f in os.listdir(os.path.dirname(os.path.realpath(__file__))) if f.endswith(".py") ]:
	if filename == os.path.basename(__file__): continue
	module = sys.modules.get("{}.{}".format(__name__,filename[:-3]))
	if module: importlib.reload(module)

# clear out any scene update funcs hanging around, e.g. after a script reload
from bpy.app.handlers import depsgraph_update_pre, depsgraph_update_post
for func in depsgraph_update_post:
	if func.__module__.startswith(__name__):
		depsgraph_update_post.remove(func)
#
#import my module
from . import utility
#
class ExportSomeData(Operator, ExportHelper):
	bl_idname = "export_test.something" 
	bl_label = "Export something"

	filename_ext = ".txt"

	filter_glob: StringProperty(
		default="*.txt",
		options={'HIDDEN'},
		maxlen=255,  # Max internal buffer length, longer would be clamped.
	)

	use_setting: BoolProperty(
		name="Example Boolean",
		description="Example Tooltip",
		default=True,
	)

	type: EnumProperty(
		name="Example Enum",
		description="Choose between two items",
		items=(
			('OPT_A', "First Option", "Description one"),
			('OPT_B', "Second Option", "Description two"),
		),
		default='OPT_A',
	)

	def execute(self, context):
		utility.do_something()
		return {"FINISHED"}


# Only needed if you want to add into a dynamic menu
def menu_func_export(self, context):
	self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator")


def register():
	bpy.utils.register_class(ExportSomeData)
	bpy.types.TOPBAR_MT_file_export.append(menu_func_export)


def unregister():
	bpy.utils.unregister_class(ExportSomeData)
	bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)

if __name__ == "__main__":
		register()


utility.py

def do_something():
	print('Do something')


完整的程式碼可以在 [ Gitlab ] basic_export_addon 下載。

 

  雖然照著上述的做法就可以載入自己的 Module ,但還是有些麻煩,如修改後要測試要經過 Reload 才能看到結果,或是修改後如果要搭配版本管理(Version control)時,要把修改的那一份(Blender 安裝的資料夾)複製到版本管理(Version control)才能 Commit ,這些麻煩目前還找不到方法解決,如果解決了會再寫一篇。


參考資料

[ steamreview.org ] Blender Source Tools


相關資料

[ Gitlab ] basic_export_addon