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

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

圖形學(xué)中拾取的幾種思路

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

拾取是圖形交互的核心操作,連接了渲染的畫面和用戶的響應(yīng)。有好幾種不同的拾取方式,每種方式都有其優(yōu)劣勢,所以基本上都需要實現(xiàn)來應(yīng)對不同的需求。簡單來說,拾取物體分為幾種思路:


2D情況下,直接判斷鼠標(biāo)坐標(biāo)是否在某個元素內(nèi)即可,大多數(shù)情況下都可以用矩形和圓來解決。在IMGUI中就是采用這種方式。由于GUI的元素相對規(guī)則,所以這種方法是比較通用的。

3D情況下,有兩種思路:

幾何思路:通過連接攝像機和屏幕坐標(biāo)生成射線,然后與場景中的物體做相交判斷。

渲染思路:對渲染的每個物體給予編號,將編號轉(zhuǎn)換成顏色,然后通過拾取framebuffer的顏色來判斷拾取到了哪個物體。

這里重點討論一下3D的情況,在組件架構(gòu)下,如果要走幾何思路,有兩個問題:


需要給每一個需要檢測的物體配置碰撞體,很多時候這件事情是沒有必要的,特別是編輯器里面,場景對象有些是不需要碰撞器,強行加入反而會很奇怪。

射線檢測精度的問題,本質(zhì)上這件事的核心在于CPU的運算,對于高效的檢測算法可能還要更新搜索結(jié)構(gòu),但即使如此,精度問題依舊存在,特別當(dāng)物體特別靠近攝像機的時候,精度反而更加差。

但如果如果場景中本來就有大量碰撞體,那么用射線檢測的方式就會更加自然。


如果走渲染思路,很明顯的問題就是會增加一個Pass以及GPU和CPU的強行同步,好處當(dāng)然是對原有的場景不做侵入式的影響。在編輯器當(dāng)中很自然就可以拾取物體,不需要做額外的操作。如果考慮優(yōu)化,實際上可以只根據(jù)鼠標(biāo)事件選擇性的增加pass,而沒必要每幀都做額外的渲染。并且可以將渲染放到場景剔除之后進行,而不是重復(fù)性地進行整個管線。


射線檢測

射線檢測一般會基于物理引擎來做:


只要設(shè)置對了碰撞體的位置,物理引擎很容易就可以進行檢測,在這個基礎(chǔ)上可以進一步將鼠標(biāo)操作封裝成事件腳本:


https://oasisengine.cn/0.6/examples#input-pointer

oasisengine.cn/0.6/examples#input-pointer


顏色拾取(Framebuffer Picker)

Framebuffer Picker(這個詞我之前搜索了半天都沒找到太多資料)原理更加簡單,只依賴于渲染API,但是我在實現(xiàn)的時候遇到一系列坑,這也是激發(fā)我寫這篇文章總結(jié)的原因。特別總結(jié)一下。


首先我們需要顏色相互轉(zhuǎn)換的函數(shù):


math::Float3 ColorMaterial::id2Color(uint32_t id) {

? ? if (id >= 0xffffff) {

? ? ? ? std::cout<< "Framebuffer Picker encounter primitive's id greater than " + std::to_string(0xffffff) <<std::endl;

? ? ? ? return math::Float3(0, 0, 0);

? ? }

? ??

? ? return math::Float3((id & 0xff) / 255.0, ((id & 0xff00) >> 8) / 255.0, ((id & 0xff0000) >> 16) / 255.0);

}


uint32_t ColorMaterial::color2Id(const std::array<uint8_t, 4>& color) {

? ? return color[0] | (color[1] << 8) | (color[2] << 16);

}

8-bits剛好可以表示0-255,然后用RGB三個顏色一共3個8-bits就可以表示上萬種物體。


接下來要構(gòu)建一個framebuffer,其實只需要創(chuàng)建一個texture并且綁定到MTLRenderPassDescriptor即可,最后我們需要個函數(shù)來讀取texture當(dāng)中的數(shù)據(jù):


std::array<uint8_t, 4> ColorRenderPass::readColorFromRenderTarget(Camera* camera) {

? ? const auto& screenPoint = _pickPos;

? ? auto window =? camera->engine()->canvas()->handle();

? ? int clientWidth, clientHeight;

? ? glfwGetWindowSize(window, &clientWidth, &clientHeight);

? ? int canvasWidth, canvasHeight;

? ? glfwGetFramebufferSize(window, &canvasWidth, &canvasHeight);

? ??

? ? const auto px = (screenPoint.x / clientWidth) * canvasWidth;

? ? const auto py = (screenPoint.y / clientHeight) * canvasHeight;

? ??

? ? const auto viewport = camera->viewport();

? ? const auto viewWidth = (viewport.z - viewport.x) * canvasWidth;

? ? const auto viewHeight = (viewport.w - viewport.y) * canvasHeight;

? ??

? ? const auto nx = (px - viewport.x) / viewWidth;

? ? const auto ny = (py - viewport.y) / viewHeight;

? ? auto texture = renderTarget.colorAttachments[0].texture;

? ? const auto left = std::floor(nx * (texture.width - 1));

? ? const auto bottom = std::floor((1 - ny) * (texture.height - 1));

? ? std::array<uint8_t, 4> pixel;

? ??

? ? [renderTarget.colorAttachments[0].texture getBytes:pixel.data()

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?bytesPerRow:sizeof(uint8_t)*4

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? fromRegion:MTLRegionMake2D(left, canvasHeight - bottom, 1, 1)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?mipmapLevel:0];

? ??

? ? return pixel;

}

這一塊一開始死活弄不對,主要就是缺少了同步操作,首先在MacOS上,MTLTexture的StorageMode要么是Managed要么是Private,在iOS上可以用Shared使得CPU和GPU可以共享內(nèi)存,但MacOS上,GPU和CPU的內(nèi)存是分開的,所以要進行顯式同步:


void ColorRenderPass::postRender(Camera* camera, const RenderQueue& opaqueQueue,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const RenderQueue& alphaTestQueue, const RenderQueue& transparentQueue) {

? ? auto blit = [camera->engine()->_hardwareRenderer.commandBuffer blitCommandEncoder];

? ? [blit synchronizeResource:renderTarget.colorAttachments[0].texture];

? ? [blit endEncoding];

}

同步操作需要創(chuàng)建一個blitCommandEncoder,但即使是這里還是沒有完成同步,因為此時還只是為命令隊列添加操作,真正的同步要commit之后,但commit之后立刻返回如果此時獲取數(shù)據(jù)可能還是錯誤的,所以要強制CPU和GPU同步:


void MetalRenderer::end() {

? ? [commandBuffer presentDrawable:drawable];

? ? [commandBuffer commit];

? ? [commandBuffer waitUntilCompleted];

}

然后再去調(diào)用readColorFromRenderTarget,才能夠正確獲取顏色并且轉(zhuǎn)換成對應(yīng)ID。


類似的同步操作其實是現(xiàn)代圖形API比較困難的部分,因為這些API把同步的控制權(quán)交給了開發(fā)者,但只是抓幀的話,抓到的也都是一幀結(jié)束的時候,所以渲染結(jié)果看上去都是沒問題的,很難找到這里面的問題。


https://oasisengine.cn/0.6/examples#framebuffer-picker

oasisengine.cn/0.6/examples#framebuffer-picker


總結(jié)

做到這一步基本上打通了引擎Runtime和編輯器開發(fā)的橋梁,通過物體的拾取就可以掛載其他輔助的組件,例如Gizmos,進而編輯場景?;蛘咄ㄟ^腳本來調(diào)用raycast對場景的物體進行射線檢測或者動畫拾取。希望你看了這篇文章之后,不會言拾取,必稱射線檢測,不同的方法有不同的適用范圍,可以按需選擇。


圖形學(xué)中拾取的幾種思路的評論 (共 條)

分享到微博請遵守國家法律
大悟县| 山丹县| 龙州县| 和田市| 乐山市| 朝阳市| 尼勒克县| 承德县| 衡山县| 清远市| 图木舒克市| 芜湖县| 北京市| 河津市| 定安县| 贵溪市| 攀枝花市| 颍上县| 渝中区| 会宁县| 大同市| 大洼县| 修文县| 哈巴河县| 邢台市| 澜沧| 兴隆县| 铁力市| 余江县| 曲阳县| 奉贤区| 土默特右旗| 浦江县| 廉江市| 苏尼特左旗| 泰来县| 合山市| 巴里| 靖宇县| 错那县| 东明县|