裂隙彼方的幻影
之前曾經用shader成功做出夢想中的水流效果,因此之後就開始研究是否能把不同的效果疊加在上面,呈現更多不一樣的變化。實驗的思路分成兩個方向,一是純粹改變外觀形狀,二是讓其他動態線條追上水流的變化。目前雖然只暫時成功了關於形狀的部分,但這也算是一種進度,因此決定作為筆記先記錄下來。
使用的效果有兩種:
- fbm:水流背景
- shockwave:形狀變化的定義
fbm的部分,沿用了之前曾做過的雲水,所以重點在控制變形的 shockwave 。濾鏡的參考來源是andras這位作者,原本的效果是有點類似墨漬在紙上暈染開來的感覺,經過加工後把它改成類似在虛空中撕開時間縫隙的視覺感。
將原本的濾鏡拆解後,主要的功能函數基本上可以分成4個部分,依參數的使用層級排序為:
- rectToPolar
- effect
- line
- sw
首先 rectToPolar 這個函數負責的是「形狀呈現的基礎位置」。
- p:畫面座標。
- ms:時間。
- a + r:配合atan + PI,回傳有固定範圍的擴張圓。
vec2 rectToPolar(vec2 p, vec2 ms) { p -= ms / 1.35; const float PI = 3.1415926534; float r = length(p); float a = ((atan(p.y+0.2, p.x) / PI) * 0.85 + 0.5) * ms.x; return vec2(a, r); }
effect接收rectToPolar回傳的參數後,再藉由fbm產生的參數重定義顯示座標。
- p:rectToPolar處理過的範圍。
- o:原點,這裡傳入的是float(0.0)。
- e:透過abs()和sqrt(),緩和變動的範圍。如果想要比較尖銳的形狀,可以省略這段處理。
float effect(vec2 p, float o) { p *= 1.5; float f1 = fbm(p * vec2(13.0, 1.0) + 100.0 + vec2(0.0, o) ); float e = fbm(p * vec2(15.0, 1.0) + vec2(f1 * 0.85, o)); e = abs(e) * sqrt(p.y / 5.0); return e * 0.75; }
之後line負責的是「以圓為基礎擴張出去的線條曲化度」。
- v:畫面座標值。
- from, to:開始與結束的數值。to的值受effect函數影響。
- d:搭配傳入的參數(v, from, to),定義最後起點到終點的最大值,讓形狀擴張到一定的距離後自動停下來。
- smoothstep:WebGL的函數之一,不想啃官方文件的話其實在The Book of Shaders可以找到中文說明。用來將線條平滑曲化,傳入的三個值類似(start, end, value)這樣的定義。
float line(float v, float from, float to, float f) { float d = max(from - v, v - to); return 1.0 - smoothstep(0.0, f, d); }
最後sw把前三個搭配在一起,產生出要在畫面上變動的值:
- 首先用rectToPolar產生橢圓的範圍座標p。
- 用line產生x軸的偏移曲線。
- 將p傳入effect,產生從0到結束位移點兩種不一樣的濾鏡範圍。
- 最後混合time的變化,透過line產生y軸的偏移曲線。
- 回傳平滑後的形狀。
float sw(vec2 p, vec2 ms) { p = rectToPolar(p, ms); p.x = mod(p.x + 2.95, ms.x); // Create the seem mask at that offset const float b = 0.5; const float d = 0.04; float seem = line(p.x, -1.0, d, b) + line(p.x, ms.x - d, ms.x + 1.0, b); seem = min(seem, 1.0); float s1 = effect(p, 0.0); // Create another noise to fade to, but the seem has the be at a different position p.x = mod(p.x + 3.6, ms.x); float s2 = effect(p, -1020.0); // Blend them together float s = s1; s = mix(s1, s2, seem); float perc = min( max(abs(sin(u_time * 0.1)), u_time * 0.1), 1.0); float f1 = perc * 0.25; float f2 = perc * 1.; float m = line(p.y, 0.1, f1 + s * f2 * sin(1.5), 0.2); return smoothstep(0.31, 0.6, m); }
完成以上系列行為,最後要將參數輸出到畫布上。使用fbm的部分略過不看,下面就只列出在main裡引用shockwave相關函數的部分:
- c:用sw()回傳的函數s加工後的參數。先除後加是為了產生形狀再把顏色疊上去。
- col:把fbm產生的函數f和c混合,最後再稍微加一點隨機變化。
vec2 p = gl_FragCoord.xy / u_resolution.xy; float m = u_resolution.x / u_resolution.y; vec2 ms = vec2(m, 1.5); float c = .90; float s = sw(p, ms); c /= s; c += s; float t = random(p * 2.0); vec3 pic = vec3(f); vec3 col = mix(color, pic, c); // Some grain col -= (2.0 - s) * t * 0.04; gl_FragColor = vec4((f*f*f + .26 * f*f + .15 * f) * col,1.0);
成品大概是這種感覺。雖然結果和最初的想像有落差,但意外的看起來還可以⋯⋯?其實一開始是想找有沒有方法能做出把墨滴進水裡的擴散感,後來因為單純相加的效果不好,重複嘗試後才轉向這種呈現方式,也算是另類的收穫。
最後,順便記錄一下自我流派式的學習心得吧。雖然不明白其他人是如何學習的,但我算是「累積式」。在學習路途上,我其實挺常間歇性的陷入某種循環:有想要的效果→搜尋資料→嘗試→做不出來→自我厭惡→loop。從旁觀角度看雖然有些智障,身處其中時還真是笑不出來的狀況。
說到底,擁有藝術和程式兩面性的生成藝術和普通的繪畫創作本就不同,但對我來說要把兩者徹底分開一直都有困難。和直接拿著筆不同,最容易挫折的不是畫不出好東西,而是知道自己畫不出來卻不明白該用什麼方式改進,當自我厭惡積累到某種程度時可能會就此放棄。
所以我現在變成了順其自然的累積式。簡單來說就是看多過做,嘗試時發現看不懂就繼續加長搜尋資料的週期,過段時間回來就會像突然打通任督二脈一樣成功做出來。當然這方式缺點是學習時間超~級長,但每個人的學習狀態各有不同,或許在他人眼中算不上好,如果能讓自己繼續堅持學習,我想那肯定就會是對自己來說的「好方法」了。