計(jì)算機(jī)圖形學(xué) 流體粒子系統(tǒng)

一、簡介

此次作業(yè)主要實(shí)現(xiàn)了一個(gè)利用SPH算法計(jì)算的流體,交互方式主要采用鼠標(biāo)鍵盤,另利用安卓手機(jī)的加速度傳感器使用socket通信來控制,場景采用openmesh導(dǎo)入一個(gè)房間模型。

二、操作說明

電腦端:F1切換粒子顯示模式(用point繪制,帶紋理繪制,模型繪制),F(xiàn)2、F3控制鏡頭遠(yuǎn)近,F(xiàn)4切換控制流體容器轉(zhuǎn)動模式(鼠標(biāo)拖拽,手機(jī)加速度傳感器控制),F5切換是否讓房間跟隨容器轉(zhuǎn)動,F(xiàn)11、F12控制光照強(qiáng)度,wasd控制場景移動,上下左右鍵控制流體容器的移動。

安卓端:將電腦與手機(jī)連在同一wifi下,電腦端cmd使用ipconfig查詢ipv4地址,將地址輸入手機(jī),點(diǎn)擊連接按鈕。(連接需在模型加載完畢后,命令行顯示port 9400 listening后連接)

三、初始思路

在剛看到題目要求的粒子系統(tǒng)時(shí),由于沒有大的限制,一時(shí)無從下手。后來在考慮一段時(shí)間后突然想到可以實(shí)現(xiàn)一個(gè)水壺倒水,水落入水盆的場景,這樣可以使用手機(jī)的陀螺儀來控制水壺的傾角,會很有意思,尤其是如果把水盆的盆面正對屏幕,則可以實(shí)現(xiàn)adobe宣傳片中手機(jī)模仿灑水,電腦屏幕上出現(xiàn)反應(yīng)的效果。

因此在考慮過后,預(yù)計(jì)實(shí)現(xiàn)需要完成三塊內(nèi)容:

1、水的粒子系統(tǒng)實(shí)現(xiàn)方式。

2、安卓手機(jī)的交互。

3、水的渲染。

在實(shí)現(xiàn)的過程中發(fā)現(xiàn),由于sph算法非常精細(xì)復(fù)雜,對于算力的要求很高,在我的電腦上,沒有加載場景模型的情況下,512個(gè)水粒子可以無卡頓,而加載后已經(jīng)出現(xiàn)卡頓,如果按照開始的設(shè)想去設(shè)計(jì),最終效果很差,因此最后沒有實(shí)現(xiàn)倒水,而是僅僅將流體放在一個(gè)立方體容器中,進(jìn)行旋轉(zhuǎn)移動控制

四、難點(diǎn)及攻克歷程

1、SPH算法

①在確定一開始的設(shè)想后,我首先實(shí)現(xiàn)了一個(gè)簡單的粒子系統(tǒng),接著查詢了相關(guān)論文和博客,包括動態(tài)的水面模擬[1],OpenGL中基于粒子系統(tǒng)的噴泉模擬實(shí)現(xiàn)[2]等,但這兩者的主要內(nèi)容都在于水面的模擬,而我想實(shí)現(xiàn)的是對于流體細(xì)致到每一個(gè)粒子的模擬,因此找到的幾篇文章都難以幫助我實(shí)現(xiàn)預(yù)想的效果。(最后的事實(shí)證明SPH算法過于精細(xì),對于計(jì)算能力的要求很高,沒能實(shí)現(xiàn)預(yù)想效果,在提前答辯過程中助教指出sph算法并不是對每個(gè)粒子渲染,而是用每個(gè)粒子來計(jì)算一部分區(qū)域的流體的位置,對流體進(jìn)行渲染。)

②接著去問了船建學(xué)院的同學(xué),想知道如何計(jì)算倒水時(shí)的軌跡,一位同學(xué)給了我一個(gè)簡單的方程:

但這僅僅是計(jì)算了一個(gè)水流的直徑,模擬出來的結(jié)果應(yīng)該是一個(gè)管道的形狀,顯然 不是我想要的。

另一個(gè)同學(xué)給我推薦了一個(gè)模擬軟件,我想看需要調(diào)哪些參數(shù)已得知要模擬流體需要考慮哪些因素,但這卻無法得知背后的計(jì)算模型。

③最后詢問荀琳玲助教得知了SPH算法(荀琳玲助教在作業(yè)完成過程中給了我很多幫助),于是上網(wǎng)查詢,在找了多個(gè)博客后鎖定了這個(gè)博客:SPH算法簡介[3]。

按照我的理解,SPH算法就是將水看成一個(gè)個(gè)粒子組成,粒子之間互相影響,計(jì)算每個(gè)水珠的各種屬性,根據(jù)每個(gè)水珠在一個(gè)時(shí)刻的受力計(jì)算出它下一時(shí)刻的位置,而關(guān)鍵就在于計(jì)算它的受力。

那么,粒子之間互相影響導(dǎo)致的受力如何計(jì)算呢?這里就需要一個(gè)“光滑核”的概念,即粒子的屬性會擴(kuò)散到周圍,并且隨著距離的增加影響逐漸變小,這種隨著距離而衰減的函數(shù)被稱為“光滑核”函數(shù),最大影響半徑為“光滑核半徑”。

設(shè)想流體中某點(diǎn)r(此處不一定有粒子),在光滑核半徑h范圍內(nèi)有數(shù)個(gè)粒子,位置分別是,r0→,r1→,r2→,…rj→,則該處某項(xiàng)屬性A的累加公式為:

我們假設(shè)流體中一個(gè)位置為ri→的點(diǎn),此處的密度為ρ(ri)、壓力為p(ri)、速度為u(ri),可以推導(dǎo)出此處的加速度a (ri)為

ri→處的密度計(jì)算公式最終為:

壓力產(chǎn)生的加速度部分:

粘度產(chǎn)生的加速度部分:

以上兩個(gè)部分的計(jì)算分別由sph_fluid_system類中的_computePressure方法和_computeForce計(jì)算。

那么現(xiàn)在的問題變成了,如何知道哪些粒子在當(dāng)前粒子的光滑核半徑之內(nèi)呢?

這里采用的方法是sph_grid_container類和sph_neighbour_table類一起作用實(shí)現(xiàn)。具體的方式是,grid_container實(shí)現(xiàn)一個(gè)內(nèi)部分為一個(gè)個(gè)小方格的容器,方格是按照順序排好順序的,在particlepool中取得粒子后根據(jù)粒子位置屬性找出方格序號,進(jìn)而找出方格中其它粒子,也就找到了鄰居,加入到neighbour_table,之后按照公式計(jì)算即可。

而在查詢SPH算法相關(guān)資料之前,我已經(jīng)寫了一個(gè)粒子系統(tǒng),結(jié)構(gòu)是particle類和一個(gè)particlepool類,后者負(fù)責(zé)粒子的管理。寫的時(shí)候因?yàn)闆]有設(shè)想到之后會采用復(fù)雜的算法,因此直接將繪制寫為了particle類自身的一個(gè)方法。之后發(fā)現(xiàn)這樣的實(shí)現(xiàn)方式很不清晰,所以最后將繪制統(tǒng)一放到了源.cpp中,將glut與sph系統(tǒng)解耦,也就是說換一個(gè)glew或者glfw這個(gè)sph系統(tǒng)還能用。

源.cpp在每次display時(shí)sph_system調(diào)用tick方法,利用以上所述計(jì)算了粒子的加速度后計(jì)算出下一時(shí)刻粒子的位置,然后源.cpp獲取particlepool中所有粒子的位置進(jìn)行繪制。

2、模型的加載

一開始僅僅在加載粒子模型時(shí)需要使用,因此使用了在作業(yè)一中寫的objloader,之后需要加載大的模型,發(fā)現(xiàn)自己寫的不夠用了。

首先想采用assimp庫加載場景模型,但網(wǎng)上大多數(shù)教程都是glew,而我用的是glut,因此最后采用OpenMesh。此處遇到一個(gè)坑,OpenMesh里應(yīng)該有ws2def.h,與win2sock.h有沖突,而后者是使用安卓進(jìn)行連接時(shí)用到的庫,在嘗試使用命名空間分隔無果后,發(fā)現(xiàn)include順序調(diào)換之后不再報(bào)錯(cuò),在向幾位學(xué)長請教之后發(fā)現(xiàn)命名空間解決的是命名上的沖突,其它的沖突還是得看報(bào)錯(cuò)進(jìn)行具體解決。

3、容器旋轉(zhuǎn),流體的重力向量保持不變。

由于容器和流體的旋轉(zhuǎn)是在渲染階段利用glrotate做的旋轉(zhuǎn),也就是說在流體看來,重力方向還是原本那樣,即在渲染的時(shí)候就跟著旋轉(zhuǎn)了,而不是在世界坐標(biāo)系中的(0,-9.8,0)。(重力向量是源.cpp傳入fluid_system的參數(shù))因此需要在每次rotate之前把新的重力向量傳入fluid_system。新的重力向量是采用數(shù)學(xué)方法推出的。繞x軸轉(zhuǎn)yRotate度,繞y軸轉(zhuǎn)xRotate度,最后的位置是:

X = -9.8 * sin(xRotate / 180 * PI) * sin(yRotate / 180 * PI),

Y = -9.8 * cos(xRotate/180 * PI),

Z = 9.8 * sin(xRotate / 180 * PI) * cos(yRotate / 180 * PI)

此處遇到一個(gè)坑,math.h中采用弧度制,glrotate采用角度制,導(dǎo)致一段時(shí)間怎么調(diào)也不對,而我以為是公式錯(cuò)了,導(dǎo)致在此處耽誤了很長時(shí)間,最后通過在display函數(shù)中加了一個(gè)將重力向量繪制出來查看才發(fā)現(xiàn)了這個(gè)問題并加以解決。

另外,此處的數(shù)學(xué)計(jì)算為之后做交互埋下了一個(gè)伏筆。

4、交互

首先實(shí)現(xiàn)鼠標(biāo)鍵盤交互,像控制遠(yuǎn)近,控制上下左右移動都是為了調(diào)整模型到合適的位置方便,在實(shí)現(xiàn)過程中加入的??梢酝ㄟ^f5切換場景模型是否跟著容器變化角度。視角的變化是采用gltranslate去變換物體位置實(shí)現(xiàn)的。

接著是實(shí)現(xiàn)安卓手機(jī)與之的交互。

首先要解決的問題自然是連接。因?yàn)橐郧白鲞^一個(gè)安卓app的項(xiàng)目,有過cs架構(gòu)的經(jīng)驗(yàn),而那時(shí)服務(wù)器是放在阿里云上的,所以我一開始設(shè)想了這樣的架構(gòu):安卓發(fā)消息給阿里云的服務(wù)器,電腦端發(fā)消息去阿里云服務(wù)器獲取。后來才想到電腦端用的c++不一定不能寫服務(wù)器呀,于是去學(xué)習(xí)了一下socket在安卓端的java和電腦端的c++的使用,建立了連接,此時(shí)也就遇到了在上述的與openmesh沖突的問題。

控制方面,開始時(shí)設(shè)想采用陀螺儀獲取角加速度,讓流體的容器跟著旋轉(zhuǎn),可是實(shí)現(xiàn)完成后發(fā)現(xiàn)這個(gè)實(shí)現(xiàn)無法讓流體容器與手機(jī)的角度同步,很難控制。我想要的效果是,手機(jī)轉(zhuǎn)到什么位置,容器就轉(zhuǎn)到什么位置,但采用陀螺儀的話容器的旋轉(zhuǎn)就變成增量式的了。

后來查找了安卓的多個(gè)傳感器,發(fā)現(xiàn)加速度傳感器的xyz數(shù)值就是重力在三個(gè)軸上的分量,也就是是說,只要解上面那個(gè)公式的方程即可得到xRotate、yRotate的數(shù)值,這樣就可讓容器的轉(zhuǎn)動與手機(jī)同步,即

X = -9.8 * sin(xRotate / 180 * PI) * sin(yRotate / 180 * PI)

Y = -9.8 * cos(xRotate/180 * PI)

Z = 9.8 * sin(xRotate / 180 * PI) * cos(yRotate / 180 * PI).

最終實(shí)現(xiàn)成功了,不過此處的一個(gè)坑是asin、acos在遇到不合理的參數(shù)比如大于1時(shí)會報(bào)nan,出錯(cuò),導(dǎo)致程序掛掉,因此需要檢查一下再輸入。

但最終實(shí)現(xiàn)效果也沒有預(yù)期的好,因?yàn)樯婕暗搅司W(wǎng)絡(luò),無法做到實(shí)時(shí)流暢的響應(yīng),還是會有延遲,考慮更流暢的方式可能是利用計(jì)算機(jī)攝像頭或者專門用于操控的設(shè)備來實(shí)現(xiàn)控制。

五、其它技術(shù)實(shí)現(xiàn)

1、場景:房間模型,可通過f4切換調(diào)整。開始時(shí)給了材質(zhì)的定義,發(fā)現(xiàn)效果很差,最后將其去掉,調(diào)整了一下光照位置讓它真實(shí)感強(qiáng)一些。

2、光照:通過設(shè)置一個(gè)light_intensity值作為light0的ambient,diffuse,specular的輸入,通過f11、f12增大減小light_intensity值調(diào)整光照。因?yàn)橐{(diào)整,因此將光照的初始化放在display函數(shù)中,且與房間模型的相對位置不隨glrotate改變。

3、粒子系統(tǒng)物理仿真:sph算法

4、粒子系統(tǒng)模型切換:開始時(shí)采用自己寫的objloader,后來既然用了openmesh就直接也用它讀入了一個(gè)cube模型,因?yàn)橛?jì)算量本來就很大,就沒有讀入更復(fù)雜的模型。

5、粒子系統(tǒng)光照、紋理映射:在texture.h中實(shí)現(xiàn)紋理的讀入,然后在粒子位置四周畫了一個(gè)立方體每一面都貼上相同的waterball.jpg這張紋理。另外,透明效果通過glBlendFunc混合顏色來實(shí)現(xiàn)。

6、交互控制:鼠標(biāo)鍵盤、安卓設(shè)備。

六、總結(jié)

這次大作業(yè)雖然沒有實(shí)現(xiàn)預(yù)期那么好的效果,但是在利用安卓手機(jī)進(jìn)行交互這一點(diǎn)還是增添了一點(diǎn)趣味性,并且在實(shí)現(xiàn)的過程中讓我意識到了adobe宣傳片的效果的大致實(shí)現(xiàn)思路,同時(shí)也意識到,利用網(wǎng)絡(luò)進(jìn)行實(shí)時(shí)的交互這種方式不可靠,不夠快也不夠穩(wěn)定安全,我覺得這也是需要特定交互硬件的原因之一;在利用SPH算法實(shí)現(xiàn)流體的過程中,我開始時(shí)認(rèn)為這個(gè)算法過于精細(xì),一定有近似算法,在較低計(jì)算量的情況下實(shí)現(xiàn)不差于它的效果。經(jīng)過提前答辯時(shí)助教的點(diǎn)撥才意識到了問題所在,流體不是通過對每個(gè)粒子都進(jìn)行渲染組成的,而是根據(jù)粒子的位置計(jì)算出周圍流體的位置,然后只對流體看得到的部分進(jìn)行渲染。

但是由于提前答辯,有些倉促,在sph算法和交互上花了過多的時(shí)間,導(dǎo)致時(shí)間沒有按照打分點(diǎn)來分配,甚至寫著代碼跨了年,在其它方面的效果不是很好。有很多需要提升的空間,尤其是SPH算法,自己花了大量的力氣但渲染這塊沒做好導(dǎo)致最后的效果不好,之后會深入研究,爭取能夠?qū)崿F(xiàn)一開始的設(shè)想效果。

總的來說,此次大作業(yè)的完成過程中,學(xué)到了很多,同時(shí)也收獲了一定的成就感,后續(xù)的改善空間也很大,將來如果要實(shí)現(xiàn)adobe宣傳片中那種酷炫的效果,此次作業(yè)也能提供很多參考。

最后,感謝老師的教學(xué)和助教的指導(dǎo),以及由于提前答辯獲得了很多來自老師和助教的反饋,收貨很大。

代碼量:pc端約1400? 安卓端約200

參考博客/論文:

[1]動態(tài)的水面模擬,http://blog.csdn.net/zju_fish1996/article/details/52317363

[2]OpenGL中基于粒子系統(tǒng)的噴泉模擬實(shí)現(xiàn),https://wenku.baidu.com/view/c79b56d476eeaeaad1f33068.html

[3]SPH算法簡介 https://thecodeway.com/blog/?p=83

Learnopengl? https://learnopengl-cn.github.io/intro/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容