Cesium實(shí)現(xiàn)更實(shí)用的3D描邊效果(附源碼)

3D對(duì)象描邊章喉,常用于物體的高亮顯示刨裆。本文將詳細(xì)介紹如何在Cesium中實(shí)現(xiàn)該功能:提取物體完整的邊緣聂示,用不同顏色區(qū)分可見和被遮擋部分告唆;根據(jù)法線夾角閾值提取平面邊界棺弊;支持高亮顯示Entity、Primitive和3DTiles等擒悬。(文末附源碼)

圖1.本文實(shí)現(xiàn)的描邊效果

1 問題描述

Cesium提供了Silhouette后期處理可以實(shí)現(xiàn)3D對(duì)象描邊模她,但是描邊結(jié)果并不能完全顯示物體的輪廓,如圖:

圖2.Cesium自帶描邊效果

可以看出所描繪的輪廓存在如下不足:

(1)無法提取被遮擋部分的輪廓懂牧;

(2)未被遮擋部分的輪廓不完整侈净。

熟悉three.js的小伙伴可能已經(jīng)想起了OutlinePass的效果,今天我們就來將這個(gè)效果拿到Cesium中實(shí)現(xiàn)僧凤,不過我們要增加點(diǎn)難度畜侦,把THREE.EdgeGeometry的效果也拿過來,并且不修改幾何體躯保,實(shí)現(xiàn)更加實(shí)用的3D描邊效果旋膳。

2 本文目標(biāo)

Cesium后期處理中同時(shí)實(shí)現(xiàn):

(1)three.js OutlinePass的描邊效果,即提取物體完整的邊界途事,包括被遮擋的部分验懊,同時(shí)可以選擇用不同顏色區(qū)分可見和被遮擋部分擅羞;

(2)three.js EdgeGeometry的描邊效果,即根據(jù)法線夾角閾值提取平面邊界义图。

(3)支持高亮顯示Cesium.Entity减俏、Cesium.Primitive以及Cesium 3D Tiles要素。

3 關(guān)鍵技術(shù)

在開始實(shí)現(xiàn)本文目標(biāo)效果之前歌溉,先介紹一下兩個(gè)關(guān)鍵技術(shù):

(1)Cesium后期處理技術(shù)垄懂;

(2)Cesium多次渲染技術(shù)

3.1 Cesium后期處理

我們先通過幾段代碼了解一下Cesium后期處理開發(fā)流程。

3.1.1 創(chuàng)建一個(gè)基本的后期處理節(jié)點(diǎn)

創(chuàng)建一個(gè)后期處理節(jié)點(diǎn)的JavaScript代碼如下:


其中singleStage.frag為片段著色器代碼文件(直接 import 是因?yàn)樽髡呤褂肕esh-3D Engine的命令行工具作為開發(fā)環(huán)境痛垛,服務(wù)端自動(dòng)轉(zhuǎn)成js文件并返回js代碼)草慧,內(nèi)容如下:


這個(gè)處理節(jié)點(diǎn)沒有做任何加工處理,只是簡單的復(fù)制場景匙头。從中可以看出漫谷,一個(gè)基本的后期處理節(jié)點(diǎn)包含兩大部分: (1) fragmentShader 片段著色器代碼:對(duì)每一幀的渲染結(jié)果進(jìn)行加工處理。簡單解釋一下內(nèi)置變量:

colorTexture 整個(gè)場景的顏色紋理或者前一個(gè)后期處理結(jié)果顏色紋理蹂析;

colorTextureDimensions 顏色紋理尺寸舔示,x為寬度,y為高度电抚;

提示: Cesium會(huì)對(duì)每一個(gè)名為xxx惕稻、類型為Cesium.Texture的uniform變量都增加一個(gè)名為xxxDimensions的uniform變量。用來傳遞紋理尺寸蝙叛。

depthTexture 整個(gè)場景的深度紋理或者前一個(gè)后期處理結(jié)果深度紋理俺祠;

(2) uniforms 向fragmentShader中傳遞外部數(shù)據(jù)〗枇保可傳遞的數(shù)據(jù)類型分為兩大類:

常量類:布爾(Boolean),字符串(String),向量(Cesium.CartesianX)蜘渣,矩陣(Cesium.MatrixX)

注意:字符串類型的uniform值可以是:圖片路徑;前續(xù)節(jié)點(diǎn)名稱肺然,用以訪問節(jié)點(diǎn)的顏色紋理蔫缸。

回調(diào)函數(shù):返回值類型同常量類。在后續(xù)的文章中會(huì)用到這類uniform际起。

3.1.2拾碌、創(chuàng)建多個(gè)后處理節(jié)點(diǎn)

有些效果可能需要多次對(duì)場景進(jìn)行加工、合成才能實(shí)現(xiàn)加叁。Cesium提供PostProcessStageComposite類用來解決此類需求倦沧。示意代碼如下:


參數(shù)inputPreviousStageTexture值解釋:

true:stages中各節(jié)點(diǎn)的colorTexture為其前一節(jié)點(diǎn)的顏色紋理;

false:stages所有節(jié)點(diǎn)的colorTexture相同(不考慮uniforms中自定義colorTexture的情況)它匕。

3.1.3展融、設(shè)置選中對(duì)象

Cesium后期處理兩個(gè)類都提供selected屬性,用來生成選中對(duì)象id查詢紋理豫柬。selected接受的對(duì)象只有一個(gè)要求:包含pickId或者pickIds屬性告希。凡是可以通過Cesium.Scene pick方法拾取到的對(duì)象扑浸,都可以找到對(duì)應(yīng)的pickId,反過來燕偶,如果想要被pick到喝噪,也需要再創(chuàng)建DrawCommand的時(shí)候生成pickId。底層不同對(duì)象構(gòu)建pickId的邏輯差別很大指么,導(dǎo)致獲取pickId的方法也不盡相同酝惧。

Entity和Primitive 從picked.primitive._pickIds查找;

3D Tiles要素 已單體化的要素:picked.pickId;未單體化的瓦片:picked.content._model._pickIds;

3.2伯诬、Cesium多次渲染

后期處理技術(shù)允許我們對(duì)場景渲染結(jié)果進(jìn)行加工處理晚唇,但是有些情況(比如SMAA、MSAA抗鋸齒算法盗似,以及接下來我們要實(shí)現(xiàn)的輪廓提取等)哩陕,我們需要對(duì)場景全部或者部分對(duì)象進(jìn)行臨時(shí)修改、顯隱控制并重新渲染赫舒。Cesium并沒有提供現(xiàn)成的技術(shù)悍及,所以我們需要?jiǎng)邮秩?shí)現(xiàn),這部分涉及Cesium底層渲染技術(shù)接癌,這里不展開介紹心赶,有必要的話可以專門寫一篇來補(bǔ)充,這里列出接口定義和使用示例缺猛。

3.2.1园担、實(shí)現(xiàn)功能

在Cesium主渲染流程完成后,指定后期處理節(jié)點(diǎn)(stage)開始前枯夜,將選中的對(duì)象或者沒有被選中的對(duì)象渲染到緩沖區(qū),然后在該后期處理節(jié)點(diǎn)通過texture屬性獲取當(dāng)前通道的顏色數(shù)據(jù)艰山,通過depthTexture獲取深度數(shù)據(jù)湖雹。

3.2.2、接口定義(TypeScript)


關(guān)鍵參數(shù)說明:

renderType 設(shè)置需要渲染的對(duì)象:

all——當(dāng)前渲染隊(duì)列中的所有繪圖命令

selected——渲染隊(duì)列中被選中的對(duì)象關(guān)聯(lián)的繪圖命令曙搬,只過濾用整個(gè)對(duì)象內(nèi)所有幾何體的所有頂點(diǎn)pickId都相同的情況摔吏,如果將pickId寫入幾何體的頂點(diǎn)則需要手動(dòng)在shader中過濾(通過調(diào)用czm_selected()來判斷是否為選中對(duì)象)

unselected——渲染隊(duì)列中的所有未被選中的對(duì)象關(guān)聯(lián)的繪圖命令

stage 設(shè)置綁定的后期處理節(jié)點(diǎn)。必須綁定后期處理節(jié)點(diǎn)纵装,否則渲染通道將不會(huì)被調(diào)用執(zhí)行渲染工作征讲,也無法獲取depthTexture和exture。

texture 獲取顏色紋理橡娄。請(qǐng)?jiān)趗niform回調(diào)函數(shù)中獲取诗箍,因?yàn)榧y理在后期處理節(jié)點(diǎn)的update被調(diào)用時(shí)才會(huì)生成,提前獲取不到挽唉。

depthTexture 獲取深度紋理滤祖。

shaderRedefine 指定shader重定義方式筷狼,即指示在shader代碼追加到繪圖命令本身的shader之后,如何執(zhí)行繪圖邏輯:

add——并且調(diào)用原始的main函數(shù)匠童,執(zhí)行默認(rèn)繪圖邏輯埂材,追加部分的繪圖邏輯;

replace——不調(diào)用原始的main函數(shù),只執(zhí)行追加部分的繪圖邏輯汤求。

vertexShader 追加的頂點(diǎn)著色器代碼俏险,可選。

fragmentShader 追加的片元著色器代碼扬绪,可選竖独。

內(nèi)置的預(yù)編譯定義(在頂點(diǎn)和片元著色器中都可以訪問):

HAS_NORMAL 指示頂點(diǎn)著色器中存在名為normal的屬性。

HAS_V_NORMAL 指示頂點(diǎn)屬性中不存在為normal的屬性勒奇,但存在名為v_normal的varying變量预鬓。

內(nèi)置的函數(shù)czm_selected:識(shí)別當(dāng)前像元是否為選中對(duì)象。

3.2.3赊颠、使用示例

創(chuàng)建后期渲染通道

片段著色代碼(renderPass.frag):


javascript代碼:


在后期處理節(jié)點(diǎn)中使用

片元著色器代碼(readMaskPass.frag):


javascript代碼:


4格二、實(shí)現(xiàn)過程

準(zhǔn)備這么久,終于到了正文了竣蹦。實(shí)現(xiàn)過程整體分為以下幾步:

(1)渲染整個(gè)場景顶猜,獲得場景顏色和深度;

(2)渲染被選中對(duì)象痘括,將法線保存到顏色紋理长窄,同時(shí)得到該對(duì)象深度紋理;

(3)提取選中對(duì)象輪廓纲菌;

(4)將輪廓疊加到場景挠日。

其中關(guān)鍵的步驟是(2)和(3),而(3)中的Shader代碼幾乎可以直接照搬t(yī)hree.js OutlinePass的代碼翰舌,相對(duì)容易嚣潜;(2)是比較困難,難點(diǎn)在于如何只渲染被選中對(duì)象椅贱,這就用到了上文介紹的Cesium多次渲染技術(shù)了懂算。下面分步詳細(xì)介紹。

4.1庇麦、渲染整個(gè)場景

這一步在創(chuàng)建場景之后Cesium就一直進(jìn)行计技,我們只需要?jiǎng)?chuàng)建好場景即可。


4.2山橄、渲染選中對(duì)象

這一步?jīng)Q定了我們實(shí)現(xiàn)的效果有別與Cesium自帶的描邊算法垮媒,因?yàn)檫吘壸R(shí)別算法原理基本是一樣的,差別在于輸入的場景顏色和深度。

首先為提取外邊界的做準(zhǔn)備涣澡,片元著色器只需要通過czm_selected來判斷是否為被選中對(duì)象(要素)贱呐,如果不是則discard即可;其次我們還需要提取平面邊界入桂,即提取所有由近似處于同一平面的三角面組成的多邊形邊界奄薇,為此我們將法線保存到顏色紋理中。

頂點(diǎn)著色器如下(normalDepth.vert):


片元著色器代碼如下(normalDepth.frag):


創(chuàng)建CesiumRenderPass實(shí)例抗愁,這里不需要外部參數(shù)馁蒂,JavaScript代碼如下:


4.3、提取選中對(duì)象輪廓

我們先來實(shí)現(xiàn)僅提取對(duì)象邊緣的算法蜘腌,不做遮擋檢測(cè)沫屡,所有邊緣使用同一顏色描繪。

這部分的shader代碼直接取自 three.js? OutlinePass 的 getEdgeDetectionMaterial 函數(shù)撮珠,只對(duì)uniform做點(diǎn)處理沮脖。關(guān)鍵代碼如下(singleEdgeColor.frag):

其中c1、c2芯急、c3勺届、c4為當(dāng)前像元按線寬(outlineWidth)偏移一定像元之后采樣到的顏色,顏色的r娶耍、g免姿、b三通道保存的是選中對(duì)象的法線數(shù)據(jù),a通道為場景透明度榕酒,a大于0則是選中對(duì)象內(nèi)部胚膊,a為0表示選中對(duì)象外部。

關(guān)鍵的JavaScript代碼如下:


maskTexture和maskDepthTexture分別是4.2處理結(jié)果的顏色和深度紋理想鹰。

效果如下圖:


4.4紊婉、輪廓遮擋檢測(cè)

這部分和three.js OutlinePass有點(diǎn)區(qū)別,主要在深度讀取和比較方法不同辑舷。關(guān)鍵的shader代碼如下:根據(jù)深度識(shí)別被遮擋部分:


其中深度compareDepth定義如下:


maskDepthTexture為4.2渲染結(jié)果深度紋理肩榕。效果如下圖:

圖4.遮擋檢測(cè)效果?被遮擋部分顏色更淺

4.5 提取平面邊界

THREE.EdgeGeometry邊緣提取的核心原理是:計(jì)算相鄰兩個(gè)三角面的法線夾角,如果夾角小于給定閾值則認(rèn)為兩者在同一平面惩妇,進(jìn)而刪除公共邊。那么在shader中如何實(shí)現(xiàn)呢筐乳?前文已經(jīng)做好了足夠的準(zhǔn)備了歌殃,我們只需要按照 4.2 和 4.3 比較深度和透明度的方法,再實(shí)現(xiàn)一個(gè)比較頂點(diǎn)法線的方法即可蝙云。核心shader代碼如下:


其中 thresholdAngle 為相鄰三角面法線夾角閾值氓皱,單位為弧度,通過uniform傳入。

我們還需要對(duì) main 函數(shù)進(jìn)行修改波材,主要是5.2中 d 的計(jì)算股淡,有變化:


至此核心的步驟就全部完成了。想要得到上文的各個(gè)效果圖廷区,我們還需將得到的輪廓效果疊加到原始場景中唯灵。

4.6、將輪廓疊加到場景

這里簡單粗暴的將兩張紋理貼圖的顏色做加運(yùn)算隙轻。


其中l(wèi)ineTexture為輪廓提取節(jié)點(diǎn)的顏色紋理埠帕,colorTexture為原始場景的顏色紋理。

5玖绿、應(yīng)用示例

本文大體把實(shí)現(xiàn)流程的關(guān)鍵細(xì)節(jié)列出來敛瓷,最后我們將這些細(xì)節(jié)封裝,以便在項(xiàng)目中應(yīng)用斑匪。再貼一下封裝好的接口定義(createEdgeStage.js):


5.1 效果視頻



視頻:展示參數(shù)設(shè)置和Entity呐籽、3D Tiles高亮顯示效果

5.2、示例代碼




最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚀瘸,一起剝皮案震驚了整個(gè)濱河市狡蝶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苍姜,老刑警劉巖牢酵,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異衙猪,居然都是意外死亡馍乙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門垫释,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丝格,“玉大人,你說我怎么就攤上這事棵譬∠则颍” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵订咸,是天一觀的道長曼尊。 經(jīng)常有香客問我,道長脏嚷,這世上最難降的妖魔是什么骆撇? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮父叙,結(jié)果婚禮上神郊,老公的妹妹穿的比我還像新娘肴裙。我一直安慰自己,他們只是感情好涌乳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布蜻懦。 她就那樣靜靜地躺著,像睡著了一般夕晓。 火紅的嫁衣襯著肌膚如雪宛乃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天运授,我揣著相機(jī)與錄音烤惊,去河邊找鬼。 笑死吁朦,一個(gè)胖子當(dāng)著我的面吹牛柒室,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逗宜,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雄右,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了纺讲?” 一聲冷哼從身側(cè)響起擂仍,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熬甚,沒想到半個(gè)月后逢渔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乡括,尸身上長有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
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望栅受。 院中可真熱鬧将硝,春花似錦、人聲如沸屏镊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽而芥。三九已至律罢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棍丐,已是汗流浹背误辑。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留歌逢,地道東北人巾钉。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像秘案,于是被迫代替她去往敵國和親砰苍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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