沒看過先看上集,即將揭曉答案...
上圖,是幾乎一整天的磨練,打造的基礎模型。
人對於物理萬物的理解方式總是狹隘的。我們都將從 example 的大海裡面沈沈浮浮。
常常當我想不到夠好的方法的時候,大量閱讀 example 總是能夠給我驚喜。而就像平常的日子一般,我又前往大海裡面尋找我要的那種波紋。沈思靜坐煉仙丹的時候翻過一遍官網的 examples,當時候覺得並無適合的出發點。所以又繼續尋覓。
驚奇的發現這個厲害的小組 Little Workshop,他們完全使用 webgl 去實驗許多新的世界。像是底下圖片當中的一系列,就是用演算去創造無限地景的效果。
繼續發現,就看到這個很神的地景生成。只要點點滑鼠就可以拖拉出整個山水場景,超級美麗而且智慧。
然而,一切都不如預期的複雜或是找不到原始碼。就在這個時候,突然看到 water 的波紋,發現他裡面就有我要找的!使用THREE.PlaneBufferGeometry
當做基底,並利用簡單的算法去產生水的波紋。
當然,雖然山水同為大自然母親,但是我追求的枯山水中的枯跟這個完全天差地遠。我知道我可能找到了山徑的路口,蜿蜒而上吧!走,去實驗。
緣分在於,之前其實就已經概覽過這個水紋的例子,但是當時候覺得他使用一個由作者 yomboprime 自己所撰寫的一個算圖器GPUComputationRenderer
來創造波紋。當中使用一些很神奇的方法,像是概念上許多人都聽過的 ping-pong rendering。理論上,就是把每次 fragment 算完最後的整張資料可以丟給下一個 frame 繼續運算,就像是有了一個記憶之前狀況的變數一般(這在 webgl 上面是非常麻煩的事情)。
...The renderer has actually two render targets per variable, to make ping-pong. Textures from the current frame are used as inputs to render the textures of the next frame...
作者 yomboprime 在註解當中寫道
我在整份 code 裡面找到他產生水位高低的核心區域,並插入自己想的高度運算法則:
...
// heightmapValue.x == 水位的高度
// heightmapValue.y == 各點移動的速度
// heightmapValue.z, heightmapValue.w 沒有使用
// uv 代表的就是現在計算的點的位置
// 所以 len1, len2 代表與波紋中心的距離
float len1 = length(uv - vec2(0.1, 0.2));
float len2 = length(uv - vec2(0.8, 0.4));
// 剩下就是簡單的數學了!直接帶入 sin 去興風作浪
if (len1 < 0.05) {
heightmapValue.x = 0.1;
} else if (len1 < 0.2 && len1 > 0.05) {
heightmapValue.x = pow(sin(len1 * 100.0), 0.5) * 10.0;
} else if (len2 < 0.1 && len2 > 0.03) {
heightmapValue.x = pow(sin(len2 * 200.0), 0.5) * 10.0;
} else {
// 沒有被震動到的部分,就產生單一方向的波紋(利用單一軸的數字)
heightmapValue.x = pow(sin(uv.x * 200.0), 0.5) * 10.0;
}
gl_FragColor = heightmapValue;
也就得到昨天最後的結果了!假如還記得昨天卡關的地方,就是在於陰影的效果處理不好。這邊的關鍵差異在於,他是使用 Three.js 內部預設寫好的材質與打光效果,並把他拉出來修改一點點東西去移植到他的需求上面,而我也是進行類似的過程再一遍。他核心的 ping-pong 我甚至沒用到(計畫可能可以用上做一些炫砲視覺),只是利用他 fragment shader 裡面計算水位的邏輯,來產生我的平面。如此一來,陰影就自動被處理掉啦!
但原始碼當中使用了許多 plugin 與直接 include 的 script,使得整份 dependencies 較為複雜,執行上也遇到許多問題。雖然我認為不用詳述,因為太多其他人不需要知道的細節與腦殘的行為。但是簡述一下也無妨,畢竟假如有人真的遇到了類似的問題,又很剛好看到這篇廢文就可以找我討論!
THREE.OrbitControls
defines 的 USE_MAP 使用
THREE.ShaderMaterial
來改造預設有的THREE.MeshPhongMaterial
ShaderMaterial.lights
一定要設定為 true,才可以打光(預設為 false)
雖然說,這種金屬感的水面也是一種美麗的形式,可是我希望的是不斷的追求,活著就是得為自己創造些什麼意義對吧?所以該怎麼從光滑的表面轉化為砂石般的顆粒質感呢?需要考慮到 shader 的算法加上 javascript 丟資料的方式下,時間軸、uniforms、常數們該怎麼考慮呢?
我當機立斷,振筆疾書就列了幾個可能的手法:
(1)到(3)都算是比較好實作的幾個方向,我也花一點時間就將整個平面改善到一個程度。但是最後是如何產生完整的枯山水庭園效果呢?關鍵差異在於哪個面向?如何產生前面基礎模型的表面功夫...?
這裡放上最新進度的預告,是最新的冥想空間。山的挪移、海的浪潮。
走走走,浪也走了起來,山也搖了起來,我們都起來擺動著啊。
欲知詳情,請待下回分曉。
附上 source code
關於作者
Vibert Thio
致力於將對於技術的深度研究轉化為新型態藝術創作的能量,並思考技術的拓展/侷限與其對於藝術論述/呈現的影響。專長為數位藝術創作、音像程式設計、互動設計,喜愛即時運算的臨場感與不可預測。