仿動(dòng)森海岸線生成

漂亮的海岸線

向往夏威夷清澈湛藍(lán)的海水散罕,干凈柔軟的沙灘赃泡,可惜疫情讓我們無(wú)法遠(yuǎn)足.

只要心中有沙,哪里都是馬爾代夫∩猛快來(lái)動(dòng)森小憩一會(huì)吧


老大:
嗯,這個(gè)沙灘不錯(cuò),蜿蜒曲折,煞是好看呀狼,就是它了,看看怎么實(shí)現(xiàn)损离。
碼農(nóng):
這個(gè)簡(jiǎn)單哥艇,讓美術(shù)做個(gè)模型放上去就行了。僻澎。貌踏。
老大:
有你沒(méi)你都一樣,你準(zhǔn)備去度個(gè)長(zhǎng)假嗎窟勃?
碼農(nóng):
不祖乳,不,我馬上就去想辦法秉氧!


仔細(xì)觀察眷昆,沙灘應(yīng)該是一個(gè)面片,控制其中一部分頂點(diǎn)沉入水平面以下汁咏,就出現(xiàn)了水沙交界線亚斋,蜿蜒曲折嗎,有幾種實(shí)現(xiàn)方式攘滩,可以給地編提供個(gè)頂點(diǎn)筆刷帅刊,還可以畫(huà)個(gè)曲線出來(lái),用來(lái)標(biāo)記交界線漂问。筆刷的方式比較簡(jiǎn)單赖瞒,但是數(shù)據(jù)存儲(chǔ)量較大,要把頂點(diǎn)坐標(biāo)都記錄下來(lái)以便還原蚤假,我們就采用第二種方式來(lái)實(shí)現(xiàn)栏饮。既然是曲線,非Beizer莫屬勤哗。

首先給場(chǎng)景中加一個(gè)Bezier功能抡爹,這個(gè)就不細(xì)說(shuō)了,網(wǎng)上資源一大把芒划。直接上效果


Bezier曲線

現(xiàn)在交界線有了,下面就是怎么做出沙灘的斜坡了欧穴,首先做一個(gè)面片做地形民逼,通過(guò)編輯面片的頂點(diǎn)Y坐標(biāo),產(chǎn)生斜坡:


彎曲示意圖

Bezier曲線的特性是可以推進(jìn)time,算出曲線上的點(diǎn)涮帘,這樣我們就可以細(xì)分曲線拼苍,算出很多的采樣點(diǎn),連接相鄰的采樣點(diǎn),在線段右邊的頂點(diǎn)向下做個(gè)偏移疮鲫,根據(jù)離線段的距離算出偏移量


細(xì)分曲線

上圖中最大的長(zhǎng)方形是地形的面片吆你,曲線是畫(huà)出來(lái)的Bezier海岸線,左下是局部放大俊犯,其中A,B,C,D是曲線上的幾個(gè)采樣點(diǎn)妇多,用直線連接相鄰采樣點(diǎn),形成線段AB,BC,CD. 圖中小圓圈代表地形面片的三角形頂點(diǎn)燕侠。下面面臨的問(wèn)題就是

  • 怎么確定每個(gè)頂點(diǎn)要和哪段線段進(jìn)行計(jì)算
  • 怎么算頂點(diǎn)在線段的左右邊
  • 頂點(diǎn)離線段的長(zhǎng)度,比如 |EF|者祖。

在分析上面三個(gè)問(wèn)題之前,擺在我們面前的是如何遍歷采樣點(diǎn)形成的線段和地形的所有頂點(diǎn)的問(wèn)題绢彤。最直觀的方式是第一層循環(huán)遍歷采樣線段(相鄰采樣點(diǎn)連線七问,下同),第二層循環(huán)遍歷地形所有頂點(diǎn)茫舶。這里我們遇到的問(wèn)題是:

  • 為了讓海岸線看起來(lái)弧度自然械巡,我們細(xì)分Bezier曲線做的比較密,同樣為了彎曲時(shí)不出現(xiàn)棱角饶氏,地形面片網(wǎng)格也很密坟比,這意味著線段多,頂點(diǎn)多嚷往,計(jì)算量是二者的乘積葛账。
  • 另一個(gè)是我們不好判斷哪個(gè)頂點(diǎn)該和哪個(gè)線段做計(jì)算的問(wèn)題。
  • 左邊右邊可以通過(guò)向量點(diǎn)擊確定
  • 點(diǎn)到線段的距離也有現(xiàn)成的公式皮仁。

現(xiàn)在不好解決的就是計(jì)算量大籍琳,效率低以及不好確定頂點(diǎn)屬于哪個(gè)采樣線段。有沒(méi)有更好的解決方案呢贷祈?答案是肯定的趋急。

我們放棄采用采樣線段的方案,改用角度細(xì)分势誊,我們先確定Bezier曲線包圍盒的中心點(diǎn)呜达,然后把中心點(diǎn)和每個(gè)采樣點(diǎn)做連線。這樣每相鄰的兩個(gè)采樣點(diǎn)和中心連線粟耻,就會(huì)形成一個(gè)夾角查近,落在這個(gè)夾角所覆蓋的扇形區(qū)域內(nèi)的所有頂點(diǎn),我們就和這兩個(gè)(后面說(shuō)為什么是兩個(gè))采樣點(diǎn)做計(jì)算挤忙。為了便于理解霜威,上圖:


角度細(xì)分

圖中多了一個(gè)Bezier曲線包圍盒以及中心點(diǎn),中心點(diǎn)和采樣點(diǎn)的連線(只畫(huà)出了部分連線册烈,360度都會(huì)被角度細(xì)分).下面給出包圍盒以及中心點(diǎn)的算法:

        int segmentCount = bezier.Segments * bezier.CurveCount;
        //計(jì)算包圍盒
        for (int i = 0; i < segmentCount ; i++)
        {
            float time = (float)i / (segmentCount - 1);
            Vector3 point = bezier.GetPoint(time);
            checkPointList.Add(point); //檢查點(diǎn)收集到一個(gè)List中戈泼,以便后面使用
            if (i == 0)
            {
                boundingBox = new Vector4(point.x, point.y, point.x, point.y);
            }
            else
            {
                if (point.x - boundingBox.x < 0)
                {
                    boundingBox.x = point.x;
                }
                if (point.z - boundingBox.y < 0)
                {
                    boundingBox.y = point.z;
                }
                if (point.x > boundingBox.z)
                {
                    boundingBox.z = point.x;
                }
                if (point.z > boundingBox.w)
                {
                    boundingBox.w = point.z;
                }
            }
        }
        //取中心點(diǎn)
        centerPoint.Set(boundingBox.x + (boundingBox.z - boundingBox.x) / 2f, 0, boundingBox.y + (boundingBox.w - boundingBox.y) / 2f);

然后再遍歷一遍,把所有連線的角度記錄下來(lái):

        //角度值的容器
        checkAngleArray = new float[checkPointList.Count];

        //把中心點(diǎn)和每個(gè)檢查點(diǎn)的連線,相對(duì)于X軸正方向的夾角存下來(lái)
        int index = 0;
        foreach (Vector3 point in checkPoints)
        {
            Vector3 relativePoint = point - centerPoint;
            float angle = Vector3.Angle(Vector3.right, relativePoint);
            if (relativePoint.z < 0)
            {
                angle = -angle;
            }
            checkAngleArray [index++] = angle;
        }

下面就該遍歷每個(gè)頂點(diǎn)了大猛,一層循環(huán)大大減少了計(jì)算量:

        Mesh mesh = obj.GetComponent<MeshFilter>().mesh;
        Vector3[] vertices = mesh.vertices;
        //中心點(diǎn)坐標(biāo)轉(zhuǎn)換到本地坐標(biāo)
        Vector3 localCenterPoint = obj.transform.InverseTransformPoint(centerPoint);
        //遍歷頂點(diǎn)
        for (int i = 0; i < vertices.Length; i++)
        {
            Vector3 vertex = vertices[i];
            //To do process vertex
        }

框架已經(jīng)搭起來(lái)了扭倾,下面就是每個(gè)頂點(diǎn)具體怎么計(jì)算的問(wèn)題了,我們當(dāng)然可以用上面提到的向量點(diǎn)乘確定左右邊挽绩,頂點(diǎn)到線段的距離確定Y值偏移量膛壹,但是這樣的計(jì)算量還是比較大的,我們可以采用近似算法:
我們把頂點(diǎn)E也和中心點(diǎn)(標(biāo)記為O,下同) 連接起來(lái)形成 EO琼牧,那我們只要計(jì)算 |EO| - |BO|就可以了恢筝,如果大于0則在右側(cè),小于零在左側(cè)巨坊,得到的那瞬郏可以確定Y值向下的偏移量。一次計(jì)算解決了兩個(gè)問(wèn)題趾撵,效率飆升侄柔,雖然結(jié)果不是很準(zhǔn)確,但是還有補(bǔ)救辦法占调,就是我們?cè)俸虲做一次計(jì)算 |EO| - |CO|,同樣得到一個(gè)Y值偏移量暂题,然后根據(jù)角BOE/BOC進(jìn)行一個(gè)差值。確定最終Y偏移值.(這就是前面為什么說(shuō)要和兩個(gè)檢查點(diǎn)做計(jì)算的原因)
下面給出偽代碼:

//計(jì)算頂點(diǎn)到中心點(diǎn)的夾角
            Vector3 vertextLine = vertex - localCenterPoint; //頂點(diǎn)到中心點(diǎn)的連線
            float angle = Vector3.Angle(Vector3.right, vertextLine); //頂點(diǎn)到中心點(diǎn)的角度
            //獲取檢查點(diǎn)索引
            int index = GetCheckPointIndex(angle);
            //頂點(diǎn)到中心點(diǎn)的距離
            float vertexDistance = Vector3.Distance(vertex, localCenterPoint);
            //檢查點(diǎn)到中心的距離
            float checkDistance = Vector3.Distance(obj.transform.InverseTransformPoint(checkPoints[index]), localCenterPoint);

            float distance = vertexDistance - checkDistance;
            if(distance > 0)
            {
                //計(jì)算頂點(diǎn)的高度偏移值
                float delta = distance * distance * heightScale;
                vertex.y -= delta;
            }

上面的屬于偽代碼究珊,大家在理解程序意圖的前提下很容易完善薪者,這里主要是沒(méi)有加入和第二個(gè)檢查點(diǎn)的計(jì)算以及最終差值的部分。
另外這里有個(gè)函數(shù)是GetCheckPointIndex(angle)剿涮,這個(gè)是根據(jù)頂點(diǎn)和中心點(diǎn)連線EO的角度言津,快速索引到落在那個(gè)細(xì)分角度覆蓋范圍,這里面也有效率優(yōu)化取试,首先根據(jù)角度悬槽,除以每個(gè)檢查點(diǎn)覆蓋的平均角度(360/檢查點(diǎn)個(gè)數(shù)),大體算出第幾個(gè)檢查點(diǎn)瞬浓,然后從這個(gè)檢查點(diǎn)再向前或者向后遍歷尋找初婆,這樣大大提高了效率.代碼就不貼了,請(qǐng)自行完善猿棉。

到這里磅叛,核心算法基本就完成了,看看最終效果吧:


效果1

效果2

畫(huà)面糙了些铺根,湊合看宪躯,核心是算法思想,以及解決問(wèn)題的思路位迂,解決過(guò)程。希望能夠幫到有需要各位碼農(nóng)。

碼農(nóng):
老大掂林,快來(lái)呀臣缀,沙灘做好了,就等你的北冰洋汽水了泻帮。精置。。

全劇終
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锣杂,一起剝皮案震驚了整個(gè)濱河市脂倦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌元莫,老刑警劉巖赖阻,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異踱蠢,居然都是意外死亡火欧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門茎截,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)苇侵,“玉大人,你說(shuō)我怎么就攤上這事企锌∮芘ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵撕攒,是天一觀的道長(zhǎng)陡鹃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)打却,這世上最難降的妖魔是什么杉适? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮柳击,結(jié)果婚禮上猿推,老公的妹妹穿的比我還像新娘。我一直安慰自己捌肴,他們只是感情好蹬叭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著状知,像睡著了一般秽五。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饥悴,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天坦喘,我揣著相機(jī)與錄音盲再,去河邊找鬼。 笑死瓣铣,一個(gè)胖子當(dāng)著我的面吹牛答朋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棠笑,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼梦碗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蓖救?” 一聲冷哼從身側(cè)響起洪规,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎循捺,沒(méi)想到半個(gè)月后斩例,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巨柒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年樱拴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洋满。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晶乔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牺勾,到底是詐尸還是另有隱情正罢,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布驻民,位于F島的核電站翻具,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏回还。R本人自食惡果不足惜裆泳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柠硕。 院中可真熱鬧工禾,春花似錦、人聲如沸蝗柔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)癣丧。三九已至槽畔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胁编,已是汗流浹背厢钧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工鳞尔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坏快。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓铅檩,卻偏偏與公主長(zhǎng)得像憎夷,于是被迫代替她去往敵國(guó)和親莽鸿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容