picking.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Copyright 2016 Joseph Cherlin. All rights reserved.
  3. * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
  4. */
  5. #include "common.h"
  6. #include "bgfx_utils.h"
  7. #include "imgui/imgui.h"
  8. #include <bx/rng.h>
  9. #include <map>
  10. namespace
  11. {
  12. #define RENDER_PASS_SHADING 0 // Default forward rendered geo with simple shading
  13. #define RENDER_PASS_ID 1 // ID buffer for picking
  14. #define RENDER_PASS_BLIT 2 // Blit GPU render target to CPU texture
  15. #define ID_DIM 8 // Size of the ID buffer
  16. class ExamplePicking : public entry::AppI
  17. {
  18. public:
  19. ExamplePicking(const char* _name, const char* _description, const char* _url)
  20. : entry::AppI(_name, _description, _url)
  21. {
  22. }
  23. void init(int32_t _argc, const char* const* _argv, uint32_t _width, uint32_t _height) override
  24. {
  25. Args args(_argc, _argv);
  26. m_width = _width;
  27. m_height = _height;
  28. m_debug = BGFX_DEBUG_NONE;
  29. m_reset = BGFX_RESET_VSYNC;
  30. bgfx::Init init;
  31. init.type = args.m_type;
  32. init.vendorId = args.m_pciId;
  33. init.platformData.nwh = entry::getNativeWindowHandle(entry::kDefaultWindowHandle);
  34. init.platformData.ndt = entry::getNativeDisplayHandle();
  35. init.resolution.width = m_width;
  36. init.resolution.height = m_height;
  37. init.resolution.reset = m_reset;
  38. bgfx::init(init);
  39. // Enable debug text.
  40. bgfx::setDebug(m_debug);
  41. // Set up screen clears
  42. bgfx::setViewClear(RENDER_PASS_SHADING
  43. , BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
  44. , 0x303030ff
  45. , 1.0f
  46. , 0
  47. );
  48. // ID buffer clears to black, which represents clicking on nothing (background)
  49. bgfx::setViewClear(RENDER_PASS_ID
  50. , BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH
  51. , 0x000000ff
  52. , 1.0f
  53. , 0
  54. );
  55. // Create uniforms
  56. u_tint = bgfx::createUniform("u_tint", bgfx::UniformType::Vec4); // Tint for when you click on items
  57. u_id = bgfx::createUniform("u_id", bgfx::UniformType::Vec4); // ID for drawing into ID buffer
  58. // Create program from shaders.
  59. m_shadingProgram = loadProgram("vs_picking_shaded", "fs_picking_shaded"); // Blinn shading
  60. m_idProgram = loadProgram("vs_picking_shaded", "fs_picking_id"); // Shader for drawing into ID buffer
  61. static const char* meshPaths[] =
  62. {
  63. "meshes/orb.bin",
  64. "meshes/column.bin",
  65. "meshes/bunny.bin",
  66. "meshes/cube.bin",
  67. "meshes/tree.bin",
  68. "meshes/hollowcube.bin",
  69. };
  70. static const float meshScale[] =
  71. {
  72. 0.5f,
  73. 0.05f,
  74. 0.5f,
  75. 0.25f,
  76. 0.05f,
  77. 0.05f,
  78. };
  79. m_highlighted = UINT32_MAX;
  80. m_reading = 0;
  81. m_currFrame = UINT32_MAX;
  82. m_fov = 3.0f;
  83. m_cameraSpin = false;
  84. bx::RngMwc mwc; // Random number generator
  85. for (uint32_t ii = 0; ii < 12; ++ii)
  86. {
  87. m_meshes[ii] = meshLoad(meshPaths[ii % BX_COUNTOF(meshPaths)]);
  88. m_meshScale[ii] = meshScale[ii % BX_COUNTOF(meshPaths)];
  89. // For the sake of this example, we'll give each mesh a random color, so the debug output looks colorful.
  90. // In an actual app, you'd probably just want to count starting from 1
  91. uint32_t rr = mwc.gen() % 256;
  92. uint32_t gg = mwc.gen() % 256;
  93. uint32_t bb = mwc.gen() % 256;
  94. m_idsF[ii][0] = rr / 255.0f;
  95. m_idsF[ii][1] = gg / 255.0f;
  96. m_idsF[ii][2] = bb / 255.0f;
  97. m_idsF[ii][3] = 1.0f;
  98. m_idsU[ii] = rr + (gg << 8) + (bb << 16) + (255u << 24);
  99. }
  100. m_timeOffset = bx::getHPCounter();
  101. // Set up ID buffer, which has a color target and depth buffer
  102. m_pickingRT = bgfx::createTexture2D(ID_DIM, ID_DIM, false, 1, bgfx::TextureFormat::RGBA8, 0
  103. | BGFX_TEXTURE_RT
  104. | BGFX_SAMPLER_MIN_POINT
  105. | BGFX_SAMPLER_MAG_POINT
  106. | BGFX_SAMPLER_MIP_POINT
  107. | BGFX_SAMPLER_U_CLAMP
  108. | BGFX_SAMPLER_V_CLAMP
  109. );
  110. m_pickingRTDepth = bgfx::createTexture2D(ID_DIM, ID_DIM, false, 1, bgfx::TextureFormat::D32F, 0
  111. | BGFX_TEXTURE_RT
  112. | BGFX_SAMPLER_MIN_POINT
  113. | BGFX_SAMPLER_MAG_POINT
  114. | BGFX_SAMPLER_MIP_POINT
  115. | BGFX_SAMPLER_U_CLAMP
  116. | BGFX_SAMPLER_V_CLAMP
  117. );
  118. // CPU texture for blitting to and reading ID buffer so we can see what was clicked on.
  119. // Impossible to read directly from a render target, you *must* blit to a CPU texture
  120. // first. Algorithm Overview: Render on GPU -> Blit to CPU texture -> Read from CPU
  121. // texture.
  122. m_blitTex = bgfx::createTexture2D(ID_DIM, ID_DIM, false, 1, bgfx::TextureFormat::RGBA8, 0
  123. | BGFX_TEXTURE_BLIT_DST
  124. | BGFX_TEXTURE_READ_BACK
  125. | BGFX_SAMPLER_MIN_POINT
  126. | BGFX_SAMPLER_MAG_POINT
  127. | BGFX_SAMPLER_MIP_POINT
  128. | BGFX_SAMPLER_U_CLAMP
  129. | BGFX_SAMPLER_V_CLAMP
  130. );
  131. bgfx::TextureHandle rt[2] =
  132. {
  133. m_pickingRT,
  134. m_pickingRTDepth
  135. };
  136. m_pickingFB = bgfx::createFrameBuffer(BX_COUNTOF(rt), rt, true);
  137. imguiCreate();
  138. }
  139. int shutdown() override
  140. {
  141. for (uint32_t ii = 0; ii < 12; ++ii)
  142. {
  143. meshUnload(m_meshes[ii]);
  144. }
  145. // Cleanup.
  146. bgfx::destroy(m_shadingProgram);
  147. bgfx::destroy(m_idProgram);
  148. bgfx::destroy(u_tint);
  149. bgfx::destroy(u_id);
  150. bgfx::destroy(m_pickingFB);
  151. bgfx::destroy(m_pickingRT);
  152. bgfx::destroy(m_pickingRTDepth);
  153. bgfx::destroy(m_blitTex);
  154. imguiDestroy();
  155. // Shutdown bgfx.
  156. bgfx::shutdown();
  157. return 0;
  158. }
  159. bool update() override
  160. {
  161. if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) )
  162. {
  163. // Draw UI
  164. imguiBeginFrame(
  165. m_mouseState.m_mx
  166. , m_mouseState.m_my
  167. , (m_mouseState.m_buttons[entry::MouseButton::Left] ? IMGUI_MBUT_LEFT : 0)
  168. | (m_mouseState.m_buttons[entry::MouseButton::Right] ? IMGUI_MBUT_RIGHT : 0)
  169. | (m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0)
  170. , m_mouseState.m_mz
  171. , uint16_t(m_width)
  172. , uint16_t(m_height)
  173. );
  174. const bgfx::Caps* caps = bgfx::getCaps();
  175. bool blitSupport = 0 != (caps->supported & BGFX_CAPS_TEXTURE_BLIT);
  176. showExampleDialog(this
  177. , !blitSupport
  178. ? "BGFX_CAPS_TEXTURE_BLIT is not supported."
  179. : NULL
  180. );
  181. if (blitSupport)
  182. {
  183. ImGui::SetNextWindowPos(
  184. ImVec2(m_width - m_width / 5.0f - 10.0f, 10.0f)
  185. , ImGuiCond_FirstUseEver
  186. );
  187. ImGui::SetNextWindowSize(
  188. ImVec2(m_width / 5.0f, m_height / 2.0f)
  189. , ImGuiCond_FirstUseEver
  190. );
  191. ImGui::Begin("Settings"
  192. , NULL
  193. , 0
  194. );
  195. ImGui::Image(m_pickingRT, ImVec2(m_width / 5.0f - 16.0f, m_width / 5.0f - 16.0f) );
  196. ImGui::SliderFloat("Field of view", &m_fov, 1.0f, 60.0f);
  197. ImGui::Checkbox("Spin Camera", &m_cameraSpin);
  198. ImGui::End();
  199. bgfx::setViewFrameBuffer(RENDER_PASS_ID, m_pickingFB);
  200. float time = (float)( (bx::getHPCounter() - m_timeOffset) / double(bx::getHPFrequency() ) );
  201. // Set up matrices for basic forward renderer
  202. const float camSpeed = 0.25;
  203. float cameraSpin = (float)m_cameraSpin;
  204. float eyeDist = 2.5f;
  205. const bx::Vec3 at = { 0.0f, 0.0f, 0.0f };
  206. const bx::Vec3 eye =
  207. {
  208. -eyeDist * bx::sin(time*cameraSpin*camSpeed),
  209. 0.0f,
  210. -eyeDist * bx::cos(time*cameraSpin*camSpeed),
  211. };
  212. float view[16];
  213. bx::mtxLookAt(view, eye, at);
  214. float proj[16];
  215. bx::mtxProj(proj, 60.0f, float(m_width) / float(m_height), 0.1f, 100.0f, caps->homogeneousDepth);
  216. // Set up view rect and transform for the shaded pass
  217. bgfx::setViewRect(RENDER_PASS_SHADING, 0, 0, uint16_t(m_width), uint16_t(m_height) );
  218. bgfx::setViewTransform(RENDER_PASS_SHADING, view, proj);
  219. // Set up picking pass
  220. float viewProj[16];
  221. bx::mtxMul(viewProj, view, proj);
  222. float invViewProj[16];
  223. bx::mtxInverse(invViewProj, viewProj);
  224. // Mouse coord in NDC
  225. float mouseXNDC = ( m_mouseState.m_mx / (float)m_width ) * 2.0f - 1.0f;
  226. float mouseYNDC = ((m_height - m_mouseState.m_my) / (float)m_height) * 2.0f - 1.0f;
  227. const bx::Vec3 pickEye = bx::mulH({ mouseXNDC, mouseYNDC, 0.0f }, invViewProj);
  228. const bx::Vec3 pickAt = bx::mulH({ mouseXNDC, mouseYNDC, 1.0f }, invViewProj);
  229. // Look at our unprojected point
  230. float pickView[16];
  231. bx::mtxLookAt(pickView, pickEye, pickAt);
  232. // Tight FOV is best for picking
  233. float pickProj[16];
  234. bx::mtxProj(pickProj, m_fov, 1, 0.1f, 100.0f, caps->homogeneousDepth);
  235. // View rect and transforms for picking pass
  236. bgfx::setViewRect(RENDER_PASS_ID, 0, 0, ID_DIM, ID_DIM);
  237. bgfx::setViewTransform(RENDER_PASS_ID, pickView, pickProj);
  238. // Now that our passes are set up, we can finally draw each mesh
  239. // Picking highlights a mesh so we'll set up this tint color
  240. const float tintBasic[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
  241. const float tintHighlighted[4] = { 0.3f, 0.3f, 2.0f, 1.0f };
  242. for (uint32_t mesh = 0; mesh < 12; ++mesh)
  243. {
  244. const float scale = m_meshScale[mesh];
  245. // Set up transform matrix for each mesh
  246. float mtx[16];
  247. bx::mtxSRT(mtx
  248. , scale, scale, scale
  249. , 0.0f
  250. , time*0.37f*(mesh % 2 ? 1.0f : -1.0f)
  251. , 0.0f
  252. , (mesh % 4) - 1.5f
  253. , (mesh / 4) - 1.25f
  254. , 0.0f
  255. );
  256. // Submit mesh to both of our render passes
  257. // Set uniform based on if this is the highlighted mesh
  258. bgfx::setUniform(u_tint
  259. , mesh == m_highlighted
  260. ? tintHighlighted
  261. : tintBasic
  262. );
  263. meshSubmit(m_meshes[mesh], RENDER_PASS_SHADING, m_shadingProgram, mtx);
  264. // Submit ID pass based on mesh ID
  265. bgfx::setUniform(u_id, m_idsF[mesh]);
  266. meshSubmit(m_meshes[mesh], RENDER_PASS_ID, m_idProgram, mtx);
  267. }
  268. // If the user previously clicked, and we're done reading data from GPU, look at ID buffer on CPU
  269. // Whatever mesh has the most pixels in the ID buffer is the one the user clicked on.
  270. if (m_reading == m_currFrame)
  271. {
  272. m_reading = 0;
  273. std::map<uint32_t, uint32_t> ids; // This contains all the IDs found in the buffer
  274. uint32_t maxAmount = 0;
  275. for (uint8_t *x = m_blitData; x < m_blitData + ID_DIM * ID_DIM * 4;)
  276. {
  277. uint8_t rr = *x++;
  278. uint8_t gg = *x++;
  279. uint8_t bb = *x++;
  280. uint8_t aa = *x++;
  281. if (bgfx::RendererType::Direct3D9 == caps->rendererType)
  282. {
  283. // Comes back as BGRA
  284. uint8_t temp = rr;
  285. rr = bb;
  286. bb = temp;
  287. }
  288. if (0 == (rr|gg|bb) ) // Skip background
  289. {
  290. continue;
  291. }
  292. uint32_t hashKey = rr + (gg << 8) + (bb << 16) + (aa << 24);
  293. std::map<uint32_t, uint32_t>::iterator mapIter = ids.find(hashKey);
  294. uint32_t amount = 1;
  295. if (mapIter != ids.end() )
  296. {
  297. amount = mapIter->second + 1;
  298. }
  299. ids[hashKey] = amount; // Amount of times this ID (color) has been clicked on in buffer
  300. maxAmount = maxAmount > amount
  301. ? maxAmount
  302. : amount
  303. ;
  304. }
  305. uint32_t idKey = 0;
  306. m_highlighted = UINT32_MAX;
  307. if (maxAmount)
  308. {
  309. for (std::map<uint32_t, uint32_t>::iterator mapIter = ids.begin(); mapIter != ids.end(); mapIter++)
  310. {
  311. if (mapIter->second == maxAmount)
  312. {
  313. idKey = mapIter->first;
  314. break;
  315. }
  316. }
  317. for (uint32_t ii = 0; ii < 12; ++ii)
  318. {
  319. if (m_idsU[ii] == idKey)
  320. {
  321. m_highlighted = ii;
  322. break;
  323. }
  324. }
  325. }
  326. }
  327. // Start a new readback?
  328. if (!m_reading
  329. && m_mouseState.m_buttons[entry::MouseButton::Left])
  330. {
  331. // Blit and read
  332. bgfx::blit(RENDER_PASS_BLIT, m_blitTex, 0, 0, m_pickingRT);
  333. m_reading = bgfx::readTexture(m_blitTex, m_blitData);
  334. }
  335. }
  336. imguiEndFrame();
  337. // Advance to next frame. Rendering thread will be kicked to
  338. // process submitted rendering primitives.
  339. m_currFrame = bgfx::frame();
  340. return true;
  341. }
  342. return false;
  343. }
  344. entry::MouseState m_mouseState;
  345. uint32_t m_width;
  346. uint32_t m_height;
  347. uint32_t m_debug;
  348. uint32_t m_reset;
  349. int64_t m_timeOffset;
  350. Mesh* m_meshes[12];
  351. float m_meshScale[12];
  352. float m_idsF[12][4];
  353. uint32_t m_idsU[12];
  354. uint32_t m_highlighted;
  355. // Resource handles
  356. bgfx::ProgramHandle m_shadingProgram;
  357. bgfx::ProgramHandle m_idProgram;
  358. bgfx::UniformHandle u_tint;
  359. bgfx::UniformHandle u_id;
  360. bgfx::TextureHandle m_pickingRT;
  361. bgfx::TextureHandle m_pickingRTDepth;
  362. bgfx::TextureHandle m_blitTex;
  363. bgfx::FrameBufferHandle m_pickingFB;
  364. uint8_t m_blitData[ID_DIM*ID_DIM * 4]; // Read blit into this
  365. uint32_t m_reading;
  366. uint32_t m_currFrame;
  367. float m_fov;
  368. bool m_cameraSpin;
  369. };
  370. } // namespace
  371. ENTRY_IMPLEMENT_MAIN(
  372. ExamplePicking
  373. , "30-picking"
  374. , "Mouse picking via GPU texture readback."
  375. , "https://bkaradzic.github.io/bgfx/examples.html#picking"
  376. );