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