picking.cpp 12 KB

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