Houdini學(xué)習(xí)筆記028_梯度能用來干嘛?

這一篇我們來學(xué)習(xí)下圖所示圖案的制作方法。

該圖案可分為兩個部分,本質(zhì)上是相同的??梢钥醋魇怯甑温湓谝粔K起伏的表面上,沿著高度梯度向下或向上流淌。當(dāng)然,自然界中肯定是向下(重力方向)流淌的。

參考教程鏈接點這里。原教程雖然很詳細(xì),但不熟悉代碼的人看起來可能一頭霧水,自己動手時不知從何做起。為了能讓VEX初學(xué)者容易理解,我盡量會解釋每段代碼的意思,以及這樣書寫的緣由。
下面我們從頭開始講起——
首先還是創(chuàng)建一個起伏的平面,使用的是mountain節(jié)點。

高度梯度屬性我們直接用measure節(jié)點來創(chuàng)建,Element type默認(rèn)為Primitives,Measure選擇Gradient,位置P的分量選擇 Y Component。輸出的屬性名稱為gradient(你也可以自行命名為其他名稱)。

放大后是這樣的,這個gradient屬性是一個矢量,起點位于面上,因為是primitive屬性。為什么不設(shè)為點屬性呢?等介紹VEX中的xyzdist函數(shù)時你就知道了。

然后還需要有雨滴,用scatter節(jié)點撒點來表示。測試時數(shù)量可以少一點,如10個。

以上是準(zhǔn)備工作。下面用Point Wrangle節(jié)點來書寫VEX腳本。
寫VEX之前我們先進行簡單的分析,我們的最終目的是得到每個點按照梯度流淌的曲線。而每個點當(dāng)前所在的面(primitive)都是有一個gradient屬性的,這個屬性可以確定當(dāng)下流淌的方向,我們再設(shè)定一個移動距離就可以得到下一時刻點的位置。不斷重復(fù)該過程就可以得到一系列的點坐標(biāo),然后連成一條曲線。
現(xiàn)在擺在我們面前的有兩個問題:
1、每個點所在的高度不同,到達局部最低點所需的步數(shù)也不一樣,循環(huán)條件怎么給?
2、每步移動距離是多少?給一個確定的值還是變化的值?如果變化,怎么變?
結(jié)合實際情況,“水滴”向局部最低點流動時,gradient屬性的y分量肯定是逐漸趨向于0的。也就是與上一個點的P.y差值逐漸趨向于0。我們可以設(shè)置一個容差值tolerance,當(dāng)滿足條件ΔP.y<tolerance時,循環(huán)終止。使用的是while語句。(原教程中使用的是總路徑長度超過一定數(shù)值作為判斷循環(huán)終止的條件,這里我用的是不同的思路)
至于每步移動的距離,我們暫且給一個可調(diào)節(jié)的確定值(step)。
創(chuàng)建Point Wrangle節(jié)點,將scatter節(jié)點連接到其第一個(0號)端口,因為我們是要對scatter的點進行操作。然后將measure節(jié)點連接到第二個端口,因為后面要借助表面的gradient屬性來計算。

定義一個浮點型變量delta來記錄連續(xù)兩個點之間的y方向位置分量的差值,當(dāng)其小于tolerance值時,循環(huán)結(jié)束。delta的初始值大于tolerance即可。

初始的點位置是位于表面上的,但每次移動一段距離后是否還位于表面上則不確定??梢杂?span id="s0sssss00s" class="color-pink-03">xyzdist函數(shù)將其重新投射到表面上。xyzdist函數(shù)的作用是獲取點到模型表面最近距離,這里還有一個作用是返回最近面的編號primnum,從而便于獲取其gradient屬性作為下一步移動的方向。

xyzdist函數(shù)寫法如下,第一個參數(shù)是輸入端口編號,這里為1。第二個參數(shù)是點的坐標(biāo),第三個參數(shù)可以返回距離最近面的編號,第四個參數(shù)返回的是最近距離投射點在面上的uv值。這里分別用pos0、hitprim和hituv來表示,記得變量要初始化,并注意其數(shù)據(jù)類型。

然后將投射點的坐標(biāo)作為新的pos0,寫法為
pos0 = primuv(1, "P", hitprim, hituv);
xyzdist函數(shù)一般和primuv函數(shù)搭配使用。
然后獲取投射面的gradient矢量,同樣是用primuv函數(shù)。
vector dir = primuv(1, "gradient", hitprim, hituv);
這里可以用normalize函數(shù)對dir進行歸一化,還有一點要注意的是gradient的方向默認(rèn)是由低處指向高處的,要往下流動的話應(yīng)該取負(fù)值。移動后新的點坐標(biāo)為:
vector pos1 = pos0 + dir * step;
并重新計算兩點間的delta值作為下一步的判斷條件:
delta = pos0[1] - pos1[1];
為了避免陷入死循環(huán),我們可以用一個變量count來記錄循環(huán)的次數(shù)。當(dāng)超過一定次數(shù)則自動跳出循環(huán)。
count += 1;
if(count > chi("counts")){
? ? ? ? break;
}
同時為了讓點接近局部最低點時步長逐漸收斂,可以給每步移動的距離乘上一個衰減系數(shù)falloff,每循環(huán)一次:
step *= falloff;
到此位置,全部腳本如下所示——

然后就是每步添加新的點,用的是addpoint函數(shù),前面已用過多次。循環(huán)之前先把scatter的點算進去:
int pts[ ] = array();
int?ptid = addpoint(0, pos0);
append(pts, ptid);
pts[ ]數(shù)組是為了后面畫線用,此時得到的結(jié)果如下——

當(dāng)前循環(huán)結(jié)束前別忘了把新的點坐標(biāo)重新設(shè)為pos0,即:
pos0 = pos1;
然后再進行下一次循環(huán)。
最后兩句VEX(畫線,移除原來scatter的點):
addprim(0, "polyline", pts);
removepoint(0, @ptnum);
添加smooth節(jié)點,讓曲線更加平滑。

后續(xù)上色可以用foreach primitive節(jié)點,對每條曲線分別設(shè)置。根據(jù)點編號添加屬性(還是用Attribute Wrangle節(jié)點):
@ratio = (float) @ptnum / (@numpt - 1);

Color節(jié)點的類型選擇Ramp from Attribute,屬性即剛剛設(shè)置的ratio(也可以直接根據(jù)y方向的點坐標(biāo)值P.y來著色)。

最終結(jié)果如下——

想要得到由低處往高處流動的曲線,只要將dir的方向負(fù)號去掉即可。另外,由低處往高處運動時delta值為負(fù),也要反轉(zhuǎn)一下,或者用abs函數(shù)取絕對值。

本篇分享就到這里,感謝各位的閱讀,下回見~