本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.
是的,正如標題所示,我們又有一個和數學有關的帖子了.有一天我在想,當我們通勤時間長達一小時左右,沒有網際網路沒有膝上型電腦,只有一臺iPad
時,我們能做什麼.幸運的是,現在iPad
有了神奇的Swift Playgrounds
應用了.
讓我們以一個全新的playground開始,只執行基本的計算核心.因為當前版本的Swift Playgrounds
不支援編輯Auxiliary Source Files輔助資原始檔
,就是我們通常存放Swift
和Metal
檔案的地方,所以我們將不得不在playground主頁面寫程式碼,還好不太複雜.我們要做的是修改我們的MetalView
初始化器,給它輸入一個額外的引數-我們的著色器/核心程式碼.然後我們開始生成程式碼,只需給這個長的字串新增幾行就好.
讓我們以一個亮藍色的背景顏色開始:
let shader =
"#include <metal_stdlib>\n" +
"using namespace metal;" +
"kernel void k(texture2d<float,access::write> o[[texture(0)]]," +
" uint2 gid[[thread_position_in_grid]]) {" +
" float3 color = float3(0.5, 0.8, 1.0);" +
" o.write(float4(color, 1.0), gid);" +
"}"
複製程式碼
如果你現在執行playground,輸出影像會像這樣:
下一步,我們繪製一個漸變.我們將當前畫素座標劃分到螢幕尺寸上,得到UV-一對 (0-1) 之間的浮點數.然後將固定的顏色與Y相乘-UV
的垂直分量會給我們一個漸變:
" int width = o.get_width();" +
" int height = o.get_height();" +
" float2 uv = float2(gid) / float2(width, height);" +
" color *= uv.y;" +
複製程式碼
輸出影像會像這樣:
接下來我們換個更好的背景.一個看起來像日落的平滑漸變.我們可以用mix來混合顏色.我們告訴函式垂直混合顏色,用Y分量來切換顏色:
" float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1 - uv.y));" +
複製程式碼
輸出影像會像這樣:
從這裡,我們就能畫一個黑色的洞.我將用距離函式(length)在螢幕中間 (0.5, 0.5) 畫黑色來實現,並在外面新增越來越多的顏色,直到螢幕角落達到最大值.把最後一行替換為:
" float2 q = uv - float2(0.5);" +
" color *= length(q);" +
複製程式碼
輸出影像會像這樣:
下一步,我們用smootstep來繪製一個圓形,裡面填充上黑色,外面藍色,在r和 (r + 0.01) 之間用混合色.用下面替換最後一行:
" float r = 0.2;" +
" color *= smoothstep(r, r + 0.01, length(q));" +
複製程式碼
輸出影像會像這樣:
如果我們對圓形邊緣不滿,可以用數學函式比如cos和atan2讓它 凹凸不平 .我們產生了9個凸起(頻率),凸起高度(振幅)是0.1:
" float r = 0.2 + 0.1 * cos(atan2(q.x, q.y) * 9.0);" +
複製程式碼
輸出影像會像這樣:
新增X座標到餘弦相位,產生一個彎曲效果:
" float r = 0.2 + 0.1 * cos(atan2(q.x, q.y) * 9.0 + 20.0 * q.x);" +
複製程式碼
輸出影像會像這樣:
你可以新增一個很小的值如0.1到餘弦中來旋轉它們:
" float r = 0.2 + 0.1 * cos(atan2(q.x, q.y) * 9.0 + 20.0 * q.x + 1.0);" +
複製程式碼
輸出影像會像這樣:
你覺得這看起來像棕櫚樹的樹冠了麼,我也覺得像!我們可以用abs來畫樹幹,這個函式給我們水平/垂直距離而不是歐幾里得距離(對一個給定的點)如長度,所以讓我們用X距離在原有基礎上再新增幾行程式碼(我們將重用r和color):
" r = 0.015;" +
" color *= smoothstep(r, r + 0.002, abs(q.x));" +
複製程式碼
輸出影像會像這樣:
我們可以用另一個Y軸的smoothstep來移除不需要的樹幹部分:
" color *= 1.0 - (1.0 - smoothstep(r, r + 0.002, abs(q.x))) * smoothstep(0.0, 0.1, q.y);" +
複製程式碼
輸出影像會像這樣:
因為樹冠和樹幹都用到了q,修改這個值將會移動所有的影像:
" float2 q = uv - float2(0.67, 0.29);" +
複製程式碼
輸出影像會像這樣:
通過引入一個sin函式我們可以彎曲樹幹.頻率太小彎曲不夠,但頻率太高又彎曲太多,所以2.0正好. 2.5的振幅將樹幹的基準向螢幕邊緣移動到正好的距離(把符號從 + 改成 - 會把基準向另一邊移動):
" color *= 1.0 - (1.0 - smoothstep(r, r + 0.002, abs(q.x - 0.25 * sin(2.0 * q.y)))) * smoothstep(0.0, 0.1, q.y);" +
複製程式碼
輸出影像會像這樣:
樹幹又太光滑了.再用cos來新增些不規則變化.高的頻率低的振幅看上去正是我們想要的:
" r = 0.015 + 0.002 * cos (120.0 * q.y);" +
複製程式碼
輸出影像會像這樣:
還有,樹幹根部通常會改變地面附近的形狀,所以exp函式正是我們需要的,因為他在開始時增長緩慢,然後向著天空快速增長.我們用衰減因子為 -50.0:
" r = 0.015 + 0.002 * cos (120.0 * q.y) + exp(-50.0 * (1.0 - uv.y));" +
複製程式碼
輸出影像會像這樣:
我們可以用sqrt來得到一個更大的數(當用於小數時),用來增強第二種顏色的表現.日落即將完成:
" float3 color = mix(float3(1.0, 0.6, 0.1), float3(0.5, 0.8, 1.0), sqrt(1 - uv.y));" +
複製程式碼
最終iPad上的圖片應該看起來像:
總結,我們看到了如何用sqrt來塑造形狀的過渡,用cos來在形狀在建立凸起和凹陷,用exp來創造麴線,用smoothstep來處理閾值/臨界點,abs來獲得對稱性,mix來獲得混合.又在通勤路上了?為什麼不來看看這個漂亮的三葉草是怎麼建立的呢:
我要再次感謝Inigo Quilez,因為他激勵我寫下更多的關於用數學繪圖的文章.本教程中的數學都歸功於他.
原始碼source code 已釋出在Github上.
下次見!