切換
舊版
前往
大廳
主題

捏捏(暫定) - 開發日誌

紅茶讓我High整晚 | 2021-03-10 00:29:07 | 巴幣 16 | 人氣 225

前言
我不知道為什麼突然想打個仿論文格式的玩意兒,東西是沒什麼高深的技術或理論,但寫成這樣就一整個屌上300%,巴哈怎麼可能會出現這種東西,我一定是嗑錯藥了。

作者 : Brian Tu(就是我的拉)

1.介紹

相信許多電繪的人在使用CSP時,一定都繪有個困擾--變形的運算太慢了。不知道是技術限制還是硬體支援考量,影像處理基本上都是靠CPU下去硬算,雖然可以減少老電腦出現硬體無法支援的狀況,但也產生了一個非常明顯的問題 : 運算真的有夠慢。變形、扭曲、濾鏡(特別是模糊),每次預覽的等待時間都爆炸久,為了節省我自己廣大畫手的寶貴時間,於是決定開發一套軟體,能夠方便的提供捏手及模糊等功能,透過GPU強大的平行運算能力來提高效率。

2.方法

目前以C++和OpenGL API來實現這些功能,之前偶然上了某堂課爆學了一下OpenGL跟GLSL Shader的使用,寫了演化模擬器跟模擬飛行器,然後成功把之前的CloudWorks移植上去了,咳咳,這又是另一個故事了。

2.1 pipline
目前的render pipeline大致如下:

1.啟動FrameBuffer1 => Bursh Shader從texture2偏置採樣 => 寫入Texture1
2.啟動FrameBuffer2 => 複製texture1到texture2
3.正常渲染於Glut視窗,套用texture1作為映射,輸入shader套用映射產生變形的圖像。

其中1、2步是後來改的,原先只有一步同時讀取寫入texture,但後來一直有嚴重的雜訊影響,後增加第二步,避免同時讀取寫入造成資料源被變更。

2.2 映射
目前捏手的部分已經完成,方法上並不是直接使原本的圖像變形,更動原圖的像素值,而是透過變形一層映射圖,最後再透過映射圖去顯示變形後的圖像,避免原本的圖像因為多次的採樣而出現資料的損失。
圖1.視覺化的映射圖,紅色及綠色分別代表映射x及y。

透過映射圖,就可以對應到貼圖上的座標,貼圖座標值域都是0~1。圖2是傷心青蛙透過圖1的映射後得到的結果,由於映射圖沒有變形過,因此映射出原圖。
圖2.傷心青蛙透過圖1映射後結果,沒有任何變化

圖3中為不同的映射圖產生的結果,可以看到我們將映射圖的x左右翻轉,即紅色通道左右翻轉後,映射出了一隻左右相反的傷心青蛙。
圖3.傷心青蛙透過映射(左)後結果(右)。

2.3 扭曲
其實蠻簡單的,雖然先前考慮過位移映射圖,但目前先採用直接映射的方式,所以映射圖的變形就稍微變簡單了。

首先要做的事情是對滑鼠路徑的處理,由於Glut在滑鼠事件的觸發上仍很容易導致不連續的狀況出現,所以我們首先得考慮最近兩次採樣的滑鼠位置,計算其距離d,當d超過我們設置的門檻值MIN_STEP,就將路徑切割成長度不大於MIN_STEP的段落,最後把參數送入shader中以迴圈處理。實作其實相當簡單,程式如下:

float stepLength = m3dGetVectorLength(displacement);
if (stepLength > MIN_STEP)
{
    mouseStep[2] = ceil(stepLength / MIN_STEP);
    mouseStep[0] = displacement[0] / mouseStep[2];
    mouseStep[1] = displacement[1] / mouseStep[2];
}

其中為了方便傳輸,mouseStep這個三維向量包含了單步位移及步數的資訊。

接下來就是Shader中的部分,主要是對筆畫路徑做疊加,最後對映射圖進行偏置採樣。柔筆刷的實作採用smoothstep,比較方便做硬度和半徑的控制,當然,exp(-r)也是個不錯的選擇拉。程式部分如下

vec2 d;
for(float i=0;i<step.z;i++)
{
  mouse += step.xy;
  
  float r = length(gl_FragCoord.xy - mouse.xy);
  float strength = smoothstep(brushSize, 0.0, r)*pressure;
  d += displacement*strength/step.z;
}

相當簡單,就是計算路徑上每個切割點的滑鼠位置對該像素的距離r,再計算筆刷強度strength,乘上滑鼠的位移displacement,就可以知道該次滑鼠在這個像素的影響了,疊加起來就可以近似整條筆刷路徑的影響。需要注意的是,我的實作中滑鼠是從當前位置loop至前個滑鼠位置,因此displacement和step.xy為反方向。

最後最關鍵的則是映射,既然知道該點的像素被位移了多少,我們在最後採樣時只要往這個偏置的反方向採樣就可以了。需要注意的是計算滑鼠位移時已經是反向的了,所以下面直接相加TexCoord+d才是正確的。

FragColor = texture(sampler0, TexCoord+d);

最後測試對映射圖的變形,如圖4所示,在畫面中順時鐘劃一個圓,可以看到顏色被拖曳、扭曲,且筆刷柔度也正常作用。
圖4. 測試變形shader結果(左)及映射後的傷心青蛙(右)

3.結果

由圖4可以看出,整個程式運作正常,能夠透過映射圖的扭曲映射原圖,避免原圖在多次採樣後,產生一些細節的損失。除此之外整體運作都相當流暢,並且能夠及時看到變形的結果。相較於CSP,可以說是有大幅的改進,基本上與photoshop中的液化差不多。圖5為其他捏手變形結果。

圖5. 測試變形,憂鬱青蛙(左)及我不知道怎麼形容青蛙(右)

4.討論

目前實作的階段已經完成了最核心的捏手演算部分,除了捏手操作外,目前也實現了圖像的復原、滾輪調整筆刷大小、筆刷預覽等功能。但目前的圖片來源仍然是檔案,未來希望可以加入對於剪貼簿圖像的讀取及寫入,才能達成實際、方便的應用。另外也可以加入其他圖像變形功能,或者高斯模糊等濾鏡功能,解決CSP極度非常爆炸緩慢的問題。

5.問題回顧

加入這段寫一些途中遇到的問題或解法,方便日後回顧。

1.需要將shader渲染結果保存時,如果用glCopyTexImage2d會因為輸出到glut時值域被限制在0~1導致數值的範圍被限制,改以FrameBuffer綁定texture的方法可以避免這個狀況發生。

2.FrameBuffer綁定的texture與該次渲染輸出時使用到的採樣texture相同,會造成採樣源被改變,使某些區塊採樣的結果錯誤,大部分會以偏移或碎片偏移誤差的形式出現。


@版權聲明如下
以上文章內容請勿轉載或使用於商業項目,新的Source Code之後會放上Github,如果要使用這些程式碼在營利項目請通知原作者。
送禮物贊助創作者 !
0
留言

創作回應

愛德莉雅.萊茵斯提爾
憂鬱青蛙喜極而泣XD
2021-03-10 07:58:14
紅茶讓我High整晚
話了好幾天終於弄出來了 連我都喜極而泣
2021-03-10 13:20:27

更多創作