Shadertoy | 水母生物的創作小記

2021-03-02 壞印表機

提前預警下,能看到文末的都是合格的關注者。

這兩天在 Shadertoy 上新了一個小作品 —— [ 水母生物 ]。期間獲得了一些新的知識,於是決定好好整理一篇推送,來分享下這個小創作的細節。廢話不多說先看一下這個 [ 水母生物 ] 的實時錄屏(無後期,只加了音效)。

 https://www.shadertoy.com/view/ttdSWN

這個水母小作品有幾個有意思的技術細節,下面會分別分享如何解決:

    1. 上百個觸手如何實時計算以節省性能?

    2. 水母頂部傘狀物隨著呼吸節奏的折皺變化。

    3. 模擬次表面散射的假的透光著色效果。

    4. 玻璃盒子與光線的相交計算。

    5. 玻璃表面的汙漬著色和 HUD 掃描效果。

1. 上百個觸手如何計算以節省性能?


在 Raymarching 裡,對空間進行取餘操作是常見的克隆物體的手段,舉個最簡單的例子,我們在世界坐標原點 (0, 0, 0) 位置放置一個球體, Raymarching 裡的代碼將是這樣的:

float sdSphere( vec3 p, float r ) {
return length(p) - r;
}
float map( vec3 p ) {
float radius = 0.5;
return sdSphere( p, radius );
}

如果我們想對這個球體進行克隆,只需要在 map() 函數的一開始加入一行代碼,對 p 位置參數進行一個取餘操作。

float map( vec3 p ) {
// Modify p to clone spheres
p = mod( p, vec3(2.0) ) - 1.0;
float radius = 0.5;
return sdSphere( p, radius );
}

基於這個克隆思路,我們觀察水母的觸手,是呈環形分布在一個圓周上,我們是否可以對這個圓周進行分割呢?假設現在有 10 個分布在圓周上的觸角,我們可以基於角度 ( TWO_PI / 10.0 ) 從俯視圖角度將空間等分成 10 份。這 10 個子空間的觸角是完全相同的。

我們編寫一個名為 circleClone() 的函數,來實現環狀分布克隆的功能。circleClone() 代碼如下,其中 num 變量決定將空間等分成多少份;id 變量記錄著當前位置所屬區域的索引,後面我們可以用這個 id 來為不同的觸角設置不同的波動偏移、長度、顏色等等。

/*
* @param p: 位置
* @param num: 切分的數量
* @param id: 返回當前位置所屬的切分區域序號
*/
vec3 circleClone( vec3 p, float num, out float id ) {
float angleArea = PI * 2.0 / num;
float len = length( p.xz );
float originangle = atan( p.x, p.z );
float angle = mod( originangle, angleArea ) - angleArea * 0.5;
// id: 區分不同的區域
id = floor( originangle / angleArea );

vec3 modPos = vec3( cos( angle ) * len, p.y, sin( angle ) * len );
return modPos;
}

利用這個函數,我們就可以實現,無論多少個觸手,即使成百上千,都只需要進行 1 次觸手的 SDF 計算就可以。下面兩個動圖展示了用不同的 num 參數去切割空間的效果。

2. 水母頂部傘狀物隨著呼吸節奏的折皺變化

這其實是一個很細節的處理,水母頂部的傘狀物在舒展和收縮的時候,表面的皺褶會有不同程度的變化,舒展時皺褶展開變少,收縮時皺褶變多。

怎麼給水母的傘狀物表面添加皺褶,並控制這種變化呢?其實就是通過控制 noise 來實現的。注意下面 noise() 函數的參數,我們使用的不是位置 p,而是 normalize(p),這樣保證相同方向(以原點為出發點)得到的噪聲值是一樣的。

// _NoiseScale 保證皺褶在豎直方向上拉伸
vec3 _NoiseScale = vec3( 8.0, 0.2, 8.0 );
// _NoiseAmp 控制皺褶的強度。
// 第1個 smoothstep 控制強度從上到下遞增
// 第2個 smoothstep 控制強度隨舒展收縮程度變化
float _NoiseAmp = 0.4 * smoothstep( radius, -radius, p.y ) * smoothstep(-0.4, 0.2, shake);
float n = noise( normalize( p ) * _NoiseScale + time * 0.5 );
float d = sdSphere( p, radius );
// Offset d by noise
d += n * _NoiseAmp;

3. 模擬SSS的假的透光著色效果


沒有加SSS的效果

加了SSS的效果

對比上面兩張效果圖,不難看出加了 SSS之後 ,更能體現水母表面透光的感覺。在代碼裡,我實現 Fake SSS 的方式很簡單,通過計算當前表面的厚度 thickness,除以光源之間的距離的平方,得到一個係數。最後利用該係數混合顏色的時候,使用光源和表面顏色的乘積來作為 SSS 的顏色。

這種計算明顯不是基於物理的,但可以大概模擬出我們想要的透光效果,如果你有更好的方法,可以在評論區評論交流。

// thickness:當前表面指向光源位置的厚度
// hitdata: 包含了sss等表面材質信息的變量
// distToLight: 當前表面點和光源之間的距離
// lCol: 光源的顏色
float sss = (1.0 - thickness) * hitdata.sss / (0.0001 + pow(distToLight, 2.0));
col = mix(col, lCol*hitdata.col, sss);

4. 玻璃盒子與光線的相交計算


因為水母被困在一個玻璃盒子裡,所以我們的光線首先要和玻璃盒計算相交,然後再計算出折射後的新的光線方向,才能好對水母進行 Raymarching 計算。

我們當然可以先對盒子進行一個 Raymarching 的計算,求出其和光線交點位置,再進行水母的計算。但是 Raymarching 畢竟是一個吃顯卡的算法,所以我選擇用 Ray-Box Intersection 的相關算法,直接一次求出光線和盒子的交點、以及交點位置的法線。

這裡有一個小插曲,本來我使用的是 scratchapixel 網站上《A Minimal Ray-Tracer: Rendering Simple Shapes (Sphere, Cube, Disk, Plane, etc.)》裡的方法,但是這篇文章畢竟介紹的是在 CPU 的代碼,移植到 GPU 後雖然可以正常運行,但卻顯得有點複雜。

很幸運上傳到 Shadertoy 之後, iq 留言推薦了一篇他的博文,裡面有在 shader 裡直接求解光線和盒子相交的算法,簡直不要太簡潔優雅。

   

// Ray-Box intersection
// http://iquilezles.org/www/articles/boxfunctions/boxfunctions.htm
/*
* @param ro: 攝像機位置
* @param rd: 攝像機光線
* @param txx: 世界空間轉到盒子空間的矩陣
* @param txi: 盒子空間轉到世界空間的矩陣
* @param nearInfo: 最近相交點的信息
* @param farInfo : 最遠相交點的信息
*/
void iBox( in vec3 ro, in vec3 rd, in mat4 txx, in mat4 txi, in vec3 rad, out vec4 nearInfo, out vec4 farInfo )
{
// convert from ray to box space
vec3 rdd = (txx*vec4(rd,0.0)).xyz;
vec3 roo = (txx*vec4(ro,1.0)).xyz;

// ray-box intersection in box space
vec3 m = 1.0/rdd;
vec3 n = m*roo;
vec3 k = abs(m)*rad;

vec3 t1 = -n - k;
vec3 t2 = -n + k;

float tN = max( max( t1.x, t1.y ), t1.z );
float tF = min( min( t2.x, t2.y ), t2.z );

if( tN > tF || tF < 0.0) {
nearInfo = vec4(9999.0);
farInfo = vec4(9999.0);
return;
}

vec3 norN = -sign(rdd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz);
vec3 norF = -sign(rdd)*step(t2.xyz,t2.yzx)*step(t2.xyz,t2.zxy);

// convert to ray space
norN = (txi * vec4(norN,0.0)).xyz;
norF = (txi * vec4(norF,0.0)).xyz;

nearInfo = vec4(tN, norN);
farInfo = vec4(tF, norF);
}

5. 玻璃表面的汙漬著色和 HUD 掃描效果

Shadertoy 不允許自己上傳自定義貼圖,所以我從它給的貼圖裡選了一張比較合適的,來製作玻璃表面的汙漬感。

選用的Shadertoy上的貼圖

原理很簡單,我們考慮最簡單的情況,當光線和玻璃表面相交的時候,一部分光線反射,一部分折射。其中反射光線的顏色值可以從環境貼圖裡取;而折射光線的顏色值則是水母的著色結果。我們利用上面貼圖的 R 通道值控制 fresnel 的值,即反射和折射光線的混合比,從而實現玻璃汙漬的感覺。

// iChannel1:環境貼圖
// iChannel2:玻璃紋理貼圖
// nearPoint: 光線和盒子相交位置
// nor:法線
// reflectionCol: 反射顏色
// refractionCol: 折射顏色

vec3 sc = tex3D( iChannel2, nearPoint, nor ).rgb;
float baseF = 0.1 + sc.r * 0.9;
float fresnel = baseF + ( 1.0 - baseF ) * pow( 1.0 - max( dot( -rd, nor ), 0.0), 2.2);

col = mix( reflectionCol, refractionCol, fresnel );

玻璃表面的 HUD 掃描效果也很有意思的,根據當前交點的位置,計算它的 uv 值,然後根據 uv 判斷當前位置在不在方格線上和中心圓點上,如果在,當前點就設置為 HUD 亮色。同樣的,每個方格子都有自己獨一無二(可能有少部分重複的不過不重要嘻嘻)的 id,通過這些 id 我們可以偽造出隨機閃爍的效果。

// Simple HUD
// nearPoint:盒子和光線相交點位置
// nor:盒子和光線相交點法線

vec2 uv;
if (nor.x != 0.0) uv = nearPoint.yz;
if (nor.y != 0.0) uv = nearPoint.xz;
if (nor.z != 0.0) uv = nearPoint.xy;
vec2 uv1 = uv * 5.0;

// id:每個方格子都有自己的「唯一」索引
float id = sin( floor( uv1.x ) * 10.0 ) + cos( floor( uv1.y ) * 10.0 );
id = sin( id * 10.0 + time * 3.6 );

// 取餘操作將 uv 限制在 0.0 - 1.0 之間
uv1 = mod( uv1, vec2( 1.0 ) );

// grid 變量存儲著當前位置在不在方格線上,0為不在,1為在
float grid = smoothstep( 0.97 - lineWidth , 1.0 - lineWidth , max(uv1.x, uv1.y) ) * 0.5;

// 隨機的方格中心圓點
float centerDot = smoothstep( dotRadius, dotRadius - 0.01, length(uv1 - vec2(0.5)));
centerDot *= smoothstep( 0.5, 1.0, id );

// 合併方格和中心圓點
grid = max( grid, centerDot );

// 掃描效果
grid = max( 0.0, min( 1.0, grid * sin( time * 1.8 + nearPoint.y * 1.0 ) ) );

加了HUD效果的玻璃盒子

Breakdown 分解:


下面的 Breakdown 視頻展示了從 盒子->水母->SSS和反射效果->自發光->HUD 掃描->氣泡 的遞進合成過程。

結語:

完整的原始碼在 Shadertoy 網站上,歡迎交流指正。

https://www.shadertoy.com/view/ttdSWN

最後我將 Shadertoy 上的場景移植到 Unity裡,可以更加方便地操控攝像機、調整參數、自定義更豐富的貼圖、保存序列幀、和傳統的 Mesh 混合渲染等等。有興趣的同學推薦一個 Youtube 上 Peer Play 的教程,他講解了如何在 Unity 裡面玩轉 Raymaching。

https://www.youtube.com/watch?v=oPnft4z9iJs&t=371s

將Shadertoy移植到Unity

這篇推送的內容已經太多了,差不多該結束了,如果你能看到了這裡,我花了一天時間整理這些東西就不虧了。未來一年計劃性保持失業狀態,打算扎進 Unity 和 WebGL 的世界裡,再加上最近疫情嚴重只能宅家,可能會比去年更新頻率上一個臺階。

再見,玩狼人殺去了。

- Twitter: @SenZh4

- Instagram: @sen_zhengys

- Behance: @Sen Zheng(鄭越升)

- 站酷: @鄭越升

相關焦點

  • Unity3d-UI-Shader-太極圖案
    準備階段基於ui的shader,先找到對應unity3d版本的內置shader包,下載下來之後找到UI-Default.shader,拷貝該文件修改名字為要編輯效果的名字。調整shader 名稱為 OEngine/UI/TaiChi,創建材質球TaiChi.mat,設置shader為OEngine/UI/TaiChi。
  • 水母百科|小發水母介紹及如何飼養?
    01小發水母水母名片中文學名:小發水母英文俗名:Hairy Jellyfish/小發水母是日本的特有物種,在日本本州和九州的內灣都有其蹤跡。它們主要在冬季到春季(1-4月份)出現,到櫻花散落時就會消失,所以又被人們看作能感知春天到來的水母。
  • 愛乾淨的小機靈桃花水母
    近日,在海南五指山市通什鎮什會村的一口水井裡,村民發現了數十隻拇指般大小的水生物,生物呈乳白色、通體透明,由絮狀主體及4個略帶橢圓形的肢體分別連接一條外環構成,身體周邊長滿的觸角像飄落水中的桃花。據當地農業農村局工作人員介紹,通過外觀的判定推測其應該是有著「水中大熊貓」和水中「活化石」之稱的桃花水母的一種。
  • 燈塔水母是什麼生物簡介 都挺好蘇明成為什麼說明玉是燈塔水母
    【燈塔水母是什麼生物簡介 都挺好蘇明成為什麼說明玉是燈塔水母】《都挺好》中,蘇明成被放了,蘇明玉也出院了。這個結果,大家都不滿意,但是呢,蘇明玉確實留了後招。讓蘇明成念了懺悔書還錄了視頻,這讓他覺得是羞辱。回到家的蘇明成更是變得神經兮兮的。還說蘇明玉是燈塔水母。蘇明成說了自己是貓科動物,蘇明玉的「燈塔水母」是水螅綱動物。
  • 寧鄉一水庫驚現水母!
    近日,家住寧鄉市壩塘鎮橫田灣村的廖建華在釣魚時發現,村子裡的向陽水庫裡,突然多了一種疑似水母的生物。最近一段時間,他和朋友發現水庫裡多了種特殊的生物。「剛開始只是覺得好奇,畢竟水母都是在海洋生活的,後來在網上搜索發現很有可能是罕見的淡水水母。」
  • 水母兇猛
    全球變暖、過度捕撈、富營養化、外來物種入侵和海岸帶工程建設等,均被認為是有害水母種群暴發的可能因素。    水母天敵在減少    需加大保護海龜力度    水母的種類非常多,大小的範圍也很大,小的直徑一毫米不到,研究的時候要在解剖鏡下進行觀察,大的直徑有幾米大,像最大的北極霞水母,傘蓋直徑大於6米,觸手伸開的話能達到50米左右。
  • 合肥海洋世界|六一兒童節 探秘水母寶寶成長記
    水母是水生環境中重要的浮遊生物,屬於刺絲胞動物缽水母綱。它的身體外形就像一把透明傘,傘狀體的直徑有大有小,大水母的傘狀體直徑可達2米。海月水母的水母體是透明的,一般闊25-49釐米,有4條明顯的馬蹄狀生殖腺。它們會用觸手來捕捉獵物,如水母體、浮遊生物及軟體動物。但觸手的活動有限,它們也只是會隨波逐流。生活在大西洋、太平洋、印度洋。
  • 水母,最大的浮遊生物,美味的海蜇
    沒想到,水母的世界,給了我極大的震撼。我在其它的海洋館看過水母,也在大海邊抓過水母。水母,在海水中飄來飄去,像一把把打開的傘。我卻從來沒有關注過,它們屬於什麼。水母是魚、是哺乳還是兩棲動物?我沒想過。原來,它與上述幾種都沒關係。它屬於浮遊生物。沒看錯,是浮遊生物,世界上最大的浮遊生物。為什麼?因為最大水母的「傘蓋」,其直徑能達到兩米。什麼概念?比一個正常男人雙臂展開還要大。
  • 教程|夢幻的水母
    綜合創意班年齡  6-8歲課時  90分鐘工具材料   卡紙、色粉筆 創作構思 如果說到海洋裡美麗夢幻的生物,水母一定在這個行列當中。欣賞水母的視頻和圖片,參考圖片寫生進行水母形態和色彩的表現,在觀察的基礎上,還可以通過自己的審美對水母進行點線面的豐富和裝扮,體現出水母色彩豐富、線條豐富的美感。參考素材
  • 地球上唯一不死生物!:燈塔水母
    日前有科學家宣布,一種被稱為「燈塔水母」(Turritopsis nutricula)的微型海洋生物,很可能是世界上唯一不會死亡的生物。除非環境糟糕導致的死亡,從理論上來說燈塔水母是不會死亡的。對於某個燈塔水母,科學家根本無法說出它的年齡大小,也許這個水母從燈塔水母這個物種出現的時候,就已經存在了呢!
  • 美麗的水母和樹枝狀的水螅竟是同一種生物!~
    我們都看過水母,一個個如傘蓋一樣在海中翩翩起舞,唯美多姿。但這種生物竟然和樹枝觸手一樣的水螅是同一種生物!水母屬於腔腸動物門,即刺胞動物門。而腔腸動物門分為水螅綱、缽水母綱和珊瑚綱。腔腸動物有兩種形態,分為水母型和水螅型,我們所看到的水母是呈現水母型的腔腸動物,水母所進行的繁殖方式是有性繁殖,而呈樹枝狀的水螅是呈水螅狀態的腔腸動物,進行無性繁殖。水母在有性生殖後產生的受精卵會附著在巖石等物體上,生成小水螅。
  • 方舟:喜歡吃生物毒素的古巨龜,是名副其實的水母殺手
    今天小編要給大家介紹的是ARK Additions: Archelon!這款模組裡的生物古巨龜。古巨龜、水母古巨龜喜歡吃的食物中有生物毒素,生物毒素我們可以通過獵殺水母獲得。平時我們獵殺水母通常會使用龍王鯨,因為龍王鯨免疫水母的電流攻擊,所以這就意味著龍王鯨可以自由的攻擊不會因此受到影響。古巨龜也是這樣,它免疫水母的電流攻擊,古巨龜在攻擊水母后還有一個水母殺手(Jellyfish Killer)buff,可以提高古巨龜1.5倍的傷害,它才是名副其實的水母殺手。小夥伴們要注意,古巨龜馴服後是不吃肉的,它只吃飼料和生物毒素。
  • Unity PBR Standard Shader 實現詳解(一)
    最近在學習Unity的PBR shader,有一點自己的結論,特來這裡分享一下。我自己是一個美術工作者,不是程式設計師,所以更多的是講實現效果。以及一些必須知道的原則。站在美術的角度的一些新的思考。數學原理上只提出,但不會深入分析。但這篇文章也不是純美術向的,我不會涉及美術的製作部分:比如高低模,烘焙,貼圖繪製這類的。著重於的是TA的應用方面:Shader。
  • 雙語認知小課堂 | 水母
    But despite their name, jellyfish aren't actually fish—they're invertebrates, or animals with no backbones.水母已經漂流了數百萬年,甚至在恐龍還沒有生活在地球上之前就已經漂流了幾百萬年。這個果凍狀生物沿著洋流遊動,在冷、暖的海水,在深水和海岸線的數量很多。
  • 《朗克歷險記(Reventure)》遊戲配置要求一覽
    《朗克歷險記(Reventure)》是一款非常有趣的像素風冒險遊戲,很多玩家都不太清楚這款遊戲的配置要求怎麼樣,今天小編就給大家帶來遊戲的配置要求,希望能對大家有所幫助,一起來看看吧。 《朗克歷險記(Reventure)》是一款非常有趣的像素風冒險遊戲,很多玩家都不太清楚這款遊戲的配置要求怎麼樣
  • 方舟生存進化缽水母 海洋最深處的危險生物
    在方舟生存進化中,我們是可以馴服許多的動物的,缽水母是深海生物之一。下面小編就來介紹一下方舟生存進化缽水母吧。
  • 水母是水母嗎?大多數人看不出來,他們不是同一個生物
    也有許多人認為水母是我們的食物之一,為什麼?我想他們可能混淆了水母和水母,誤以為水母是水母,也可以被我們吃掉。一直認為水母是水母的人,當他們看到這些,他們害怕被驚呆,心裡想:水母不是水母嗎?怎麼可能呢?他們不是都是同一種生物,怎麼會不是一個物種嗎?我想清楚地告訴你,水母不是水母,但水母是水母。怎麼了?怎麼會有這樣的關係嗎?
  • 《述嵐記》震撼的不僅僅是歌詞,還有整個創作團隊!
    在這首《述嵐記》的歌詞中,蘊含著許多熟悉的俠嵐術,相信嵐粉們和小編一樣,初聽到這些歌詞唱出時,是滿滿的震撼與感動,那些陪伴我們一同成長的角色,那些鬥志激昂呼喊技能的畫面,紛紛衝破記憶的大門,洶湧而出。而能用寥寥幾句,就將我們帶入了童年回憶的詞作沈行之(微博ID@沈不行),也是很厲害的人物呢,她曾經為多位古風圈的音樂人們創作過撼人心扉的歌詞,與司夏、Aseen捷、人衣大人、Braska、Amuro等諸多大家熟知的大大們合作~其實不單單詞作,《述嵐記》的整個創作團隊都是大神啊!
  • 水母揭生物睡眠的奧秘 無腦無脊椎生物也需睡眠
    【環球網綜合報導】據俄羅斯衛星網9月24日報導,最近,一項針對沉睡海底的原始水母的研究告一段落,科學家在揭開生物睡眠奧秘的道路上取得新的進展。該研究表明,睡眠非常重要, 即使是沒有大腦的生物也需要它。仙女水母屬(Cassiopea)是生活在海底的無腦、無脊椎生物。
  • 「水中大熊貓」桃花水母到底算不算珍稀生物?
    「水中大熊貓」桃花水母到底算不算珍稀生物?它們是一類瀕臨絕跡、古老而珍稀的腔腸動物,是地球上最低等級生物之一。  關於發現桃花水母的報導,在金華並不新鮮,各縣(市、區)均曾有發現桃花水母的報導。  離金華市區不遠的金東區嶺下鎮三汶塘水庫,就曾數次發現了桃花水母,這個水庫也因為被發現了桃花水母而被印證「水質好」。