picking.cpp 12 KB

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