最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

圖形學的三種拾取實現(xiàn)與比較

2023-06-14 18:16 作者:我夢見珍妮  | 我要投稿

優(yōu)缺點及其應用場景:

Framebuffer Picker :


消耗性能低;

對于射線碰撞,沒有精度問題;

但是只能拾取Mesh這種粒度;

raycaster:


除了拾取Mesh還可以拾取圖元,比如三角形;

依靠物理系統(tǒng),消耗性能比較高;

會有精度問題,比如圖紙放大縮小上百倍很難拾取(本人因放大縮小圖紙感受到了精度問題);

射線檢測raycaster:

射線包圍盒是一種常用的方法,在 CPU 中進行拾取,性能較好,但是精度較差。當拾取頻率不高時,可以考慮使用像素級精度的幀緩沖拾取Framebuffer Picker.射線投射涉及將射線投射到場景中并檢查對象和射線之間的碰撞。這樣做有一些缺點;如果您有多個具有許多三角形的復雜網(wǎng)格形狀,則計算碰撞可能會很昂貴,可以使用包圍盒來判斷,但是因為包圍盒比較簡單,拾取邊緣誤差較大。此外,您需要編寫算法或使用第三方庫來遍歷您的場景并計算碰撞。這為小型純圖形項目增加了不必要的工作量。


射線拾取的原理就是坐標變換,相信熟悉圖形流水線的都非常清楚:

物體坐標系->(模型矩陣)世界坐標系->(視角矩陣)view/camera坐標系->(投影矩陣)裁剪坐標系->(透視除法)NDC坐標系->(窗口變換)窗口坐標系;

所以射線拾取就是反過來:窗口坐標系->NDC坐標系 ->世界坐標系.


射線數(shù)學表達: Ray = o+kv (o原點,)


/**

? ? ?* 射線拾取函數(shù)

? ? ?* 選中的網(wǎng)格模型變?yōu)榘胪该餍Ч?/p>

? ? ?*/

? ? function ray() {

? ? ? ? var Sx = event.clientX;//鼠標單擊位置橫坐標

? ? ? ? var Sy = event.clientY;//鼠標單擊位置縱坐標

? ? ? ? //屏幕坐標轉標準設備坐標

? ? ? ? var x = ( Sx / window.innerWidth ) * 2 - 1;//標準設備橫坐標

? ? ? ? var y = -( Sy / window.innerHeight ) * 2 + 1;//標準設備縱坐標

? ? ? ? var standardVector? = new THREE.Vector3(x, y, 0.5);//標準設備坐標

? ? ? ? //標準設備坐標轉世界坐標

? ? ? ? var worldVector = standardVector.unproject(camera);

? ? ? ? //射線投射方向單位向量(worldVector坐標減相機位置坐標)

? ? ? ? var ray = worldVector.sub(camera.position).normalize();

? ? ? ? //創(chuàng)建射線投射器對象

? ? ? ? var raycaster = new THREE.Raycaster(camera.position, ray);

? ? ? ? //返回射線選中的對象

? ? ? ? var intersects = raycaster.intersectObjects([boxMesh,sphereMesh,cylinderMesh]);

? ? ? ? if (intersects.length > 0) {

? ? ? ? ? ? intersects[0].object.material.transparent = true;

? ? ? ? ? ? intersects[0].object.material.opacity = 0.6;

? ? ? ? }

? ? }


unproject是Vector3對象的方法,作用是把標準設備坐標轉化為世界坐標,方法的參數(shù)是相機對象camera,該方法的使用可以參考Vector3對象的project方法, 這兩個方法的作用都是進行坐標變換,只不過方向是反過來的。


拾取Mesh上的圖元比如三角形或者頂點;


blender里面就是用來GPU與ray的拾取方式:



顏色/GPU 拾?。‵ramebuffer Picker)

在將場景渲染到屏幕上時,GPU已經(jīng)在進行該mesh所有必要的計算,以確定每個像素應該具有什么顏色。深度測試會丟棄看不見的片元。當點擊位于a(x,y)的像素,這個像素的顏色實際上是特定場景中的Object。這種技術的想法是為在渲染每個對象時通過推送常量為它們提供唯一標識符,然后在填充顏色緩沖區(qū)的同時將其渲染到額外的幀緩沖區(qū)目標 glFramebufferRenderbuffer。渲染完成后,將紋理讀回主機,并使用鼠標坐標查找對象標識符。OpenGL 提供了一個 glReadPixels 函數(shù)它是利用顏色的6位16進制表示,以顏色作為ID,在后臺渲染出紋理后,根據(jù)鼠標坐標下的紋理顏色,進行ID的查詢進行拾取操作.。當然,你不想把它渲染到屏幕上。像素信息將在緩沖區(qū)中可用,但不會顯示在屏幕上。該技術的問題在于,需要再次將場景渲染為圖像(FBO的RTT)。但是,對象拾取僅在用戶單擊鼠標按鈕時完成,因此該性能損失僅在該時間發(fā)生!在繪圖中,這是帶有幾個對象的原始場景。每個對象都將指定一種唯一的顏色。threejs渲染的圖像將如下所示:


有幾種方法可以進行對象拾取。 如果有現(xiàn)有的物理引擎,則可以從鼠標位置投射光線。 不幸的是,我們小引擎還沒有物理引擎,所以我將討論另一種方法,即使用 GPU。 其實說白了緩沖區(qū)拾取就是用空間(多一份數(shù)據(jù))換時間(拾取快),另外由于緩沖區(qū)拾取不需要遍歷模型,所以模型是可以做合批的。


準備好兩份數(shù)據(jù),一份渲染輸出到屏幕,一份渲染到FBO,同時把每個物體的信息存起來。

創(chuàng)建一個webglRenderTarget()(FBO,不直接輸出到屏幕)。

渲染FBO,通過獲取到的顏色位換算回ID值,判斷點擊了那個物體。

通過ID值獲取到點擊的物體的信息,在生成一個正方體套在點擊物體外面,表示高亮。

最后正常渲染場景,輸出到顏色緩沖區(qū)(屏幕)。

Vulkan GPU拾取對象實現(xiàn)


GPU的picking優(yōu)化降低顯存消耗:

GPU的RTT Pick有一些小技巧,比如把RTT的尺寸設置為4個像素,降低渲染一幀的負擔之類的,makeFrustum可以設置一下視窗的位置和尺寸,調整一下尺寸。相應的代碼也得改一下,主要是像素位置要對應上。具體實現(xiàn):就是調節(jié)framebuffer的尺寸,最后你要返回一個Image來解譯ID。這個Image可以很小。我記得是2x2(至于為什么最小是22,因為在GPU中的幀緩存或者說紋理的數(shù)據(jù)結構就是平鋪多維數(shù)組,就是俗稱的分塊,最小是22);回歸正題,在設置RTT的時候,要設置一下Framebuffer object的大小,這個尺寸可以通過camera的投影矩陣來設置,RTT要attach一個紋理,這個紋理尺寸就是可以是2x2。然后通過設置makeFrustrum,來設置RTT Camera的透視投影為你想要的像素位置,然后將針對該像素左下2x2這個大小區(qū)域進行繪制。當然具體makeFrustrum的參數(shù)不是2x2,而是反算到你的NDC Space下的,大概是的。你繪制的場景不變,只是繪制的投影矩陣變了,拾取只關心鼠標周圍的像素ID,不關心距離很遠的那些像素,這樣就不用消耗過多的顯存。特別注意!是渲染的場景不變,不是渲染的窗口不變。渲染的窗口通過設置投影矩陣來聚焦到鼠標周圍,然后繪制的紋理要設置到一個小尺寸下,繪制出來后,圖像的00點就是你鼠標位置處的那個像素。如果你設置了100x100的紋理,你就會看到從鼠標位置開始,左下100x100那么大的圖像。


這個思想在分屏渲染里用的很多實際上就是假設這一個屏幕是一個2x2的窗口了,另外用的是RTT技術而已。想象一下你現(xiàn)在有一個10x10個屏幕,然后你想渲染1000x1000這么大的圖像。那么每個圖像就是100x100。你怎么去渲染這單個圖像呢?其實就是你每次移動就渲染其他新的,但是永遠也不可能說能看完一整張,也不用看完一整張圖!本人最近也在看GAMES104游戲引擎架構,當你要實現(xiàn)一個小引擎或者工作實操上有很多小trick,比如可以延遲查詢來降低回傳同步造成的CPU卡頓,比如交互操作用不著實時渲染查詢,每一秒渲染一次也就夠了,這就可以降低大量的數(shù)據(jù)傳輸卡頓了。讀到這相信你能跟我一樣理解什么事工業(yè)界什么事學術界的區(qū)別~


第三種方法~ 深度值拾取:

第三種方法前輩大佬說08年測繪院就有寫過,看來我還是在玩泥巴哈哈哈哈~~~鼠標位置可以轉換到gl_FragCoord.xy,深度值是FragCoord.z,這就是Projection后的[-1,1]區(qū)間坐標值(opengl是-1到1,而DX是0到1),然后invVP得到世界坐標的值。這就是一個普通的轉換,如果是從gbuffer返回來,那就說的高級一點就是coordinate regeneration但是換湯不換藥。


該方法的工作原理如下:沿鼠標射線獲取整個深度范圍,并將其存儲為有限的固定大小。 我選擇 DEPTH_ARRAY_SCALE 32用于演示目的,但實際上應該在 1000 左右。 在片段著色器中,我們將實體 ID 寫入相應的深度桶 bucket中。為了創(chuàng)建存儲bucket.,使用綁定到片元著色器的寫到存儲緩沖區(qū)。


#define DEPTH_ARRAY_SCALE 32


layout(set=0, binding = 3) buffer writeonly s_Write_t

{

? ? uint data[DEPTH_ARRAY_SCALE];

} s_Write;


我們通過 push_constant 以 uniform 的形式將UNIQUE_ID和MOUSE_POS傳遞給片段著色器


layout(push_constant) uniform PushConsts

{

? ? vec2 MOUSE_POS;

? ? uint UNIQUE_ID;

} pushC;


然后在片段著色器中,我們得到頂點著色器使用 gl_FragCoord.z 計算的當前深度值。 這個值應該在 0 和 1 之間。我們通過將它乘以數(shù)組的長度 (DEPTH_ARRAY_SCALE) 來放大它。 這給了我們深度桶 bucket的索引。 如果我們當前著色的像素接近當前鼠標坐標,我們將唯一 ID 寫入該索引位置。


// get the depth and scale it up by

// the total number of buckets in depth array

uint zIndex = uint(gl_FragCoord.z * DEPTH_ARRAY_SCALE);


if( length( pushC.MOUSE_POS - gl_FragCoord.xy) < 1)

{

? ? s_Write.data[zIndex] = pushC.UNIQUE_ID;

}


以上是片元著色器~

在主機端,你可以做兩件事之一,要么使用 HOST_VISIBLE 存儲緩沖區(qū),并保持它的持久映射。 或者,使用 DEVICE_LOCAL 存儲緩沖區(qū),并在寫入片段后執(zhí)行 bufferCopy。 我選擇了前者,因為它更容易。

您現(xiàn)在在主機上擁有的是一個數(shù)組,其中數(shù)組中的每個索引都代表鼠標射線上的某個深度。 我們遍歷數(shù)組并找到最接近的非零值。


auto * v = ... get mapped memory ...

auto u = static_cast<uint32_t*>(v);



uint32_t SELECTED_OBJECT_ID = 0;



for(size_t i=0;i<DEPTH_ARRAY_SCALE;i++)

{

? ? if( u[i] != 0)

? ? {

? ? ? ? SELECTED_OBJECT_ID = u[i];

? ? ? ? break;

? ? }

}

// we have to zero out the memory each frame

std::memset(v, 0, DEPTH_ARRAY_SCALE * sizeof(uint32_t));


效果圖如下:



總結:

打通了引擎Runtime和編輯器開發(fā)的橋梁,通過物體的拾取就可以掛載其他輔助的組件,例如Gizmos,進而編輯場景。或者通過腳本來調用raycast對場景的物體進行射線檢測或者動畫拾取。不會言拾取,必稱射線檢測,不同的方法有不同的適用范圍,可以按需選擇,甚至兩者混用。


參考資料:

圖形學中拾取的幾種思路以及坑

Vulkan實現(xiàn)GPU拾取

渲染和編輯器之間的邊界——第 2 部分:拾取

Vulkan 鼠標拾取使用storage buffers

threejs中的拾取交互


圖形學的三種拾取實現(xiàn)與比較的評論 (共 條)

分享到微博請遵守國家法律
洞头县| 特克斯县| 靖边县| 屏边| 松溪县| 沿河| 沛县| 绥宁县| 平阳县| 浪卡子县| 利川市| 柳州市| 泰宁县| 简阳市| 大埔区| 江陵县| 交口县| 那坡县| 杭锦后旗| 涡阳县| 仙桃市| 嘉禾县| 固原市| 灌阳县| 界首市| 四会市| 乐昌市| 中江县| 仙游县| 甘南县| 涞水县| 龙陵县| 耒阳市| 琼海市| 九台市| 麟游县| 深圳市| 余江县| 昌江| 定南县| 巩留县|