#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define M_Kerr false #if M_Kerr #include #else #include #endif namespace { constexpr std::string_view _loggerCat = "BlackHoleModule"; constexpr std::string_view ProgramName = "BlackHoleProgram"; constexpr std::array QuadVtx = { -1.f, -1.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f, -1.f, 1.f, 0.f, 1.f, -1.f, -1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f }; constexpr openspace::properties::Property::PropertyInfo SolarMassInfo = { "SolarMass", "Solar Mass", "The mass of the blackhole in solar mass units", openspace::properties::Property::Visibility::User }; constexpr openspace::properties::Property::PropertyInfo ColorTextureInfo = { "ColorMap", "Color Texture", "The path to the texture that is used to convert from the magnitude of the star " "to its color. The texture is used as a one dimensional lookup function.", openspace::properties::Property::Visibility::AdvancedUser }; struct [[codegen::Dictionary(RenderableModel)]] Parameters { std::optional SolarMass; std::string colorMap; }; auto lastTime = std::chrono::high_resolution_clock::now(); #include "renderableblackhole_codegen.cpp" } namespace openspace { RenderableBlackHole::RenderableBlackHole(const ghoul::Dictionary& dictionary) : Renderable(dictionary), _solarMass(SolarMassInfo, 4.297e6f), _colorBVMapTexturePath(ColorTextureInfo) { //setRenderBin(Renderable::RenderBin::Background); const Parameters p = codegen::bake(dictionary); _solarMass = p.SolarMass.value_or(_solarMass); constexpr float G = 6.67430e-11; constexpr float c = 2.99792458e8; constexpr float M = 1.9885e30; _rs = 2.0f * G * 8.543e36 / (c * c); setInteractionSphere(_rs); setBoundingSphere(_rs*20); _colorBVMapTexturePath = absPath(p.colorMap).string(); } RenderableBlackHole::~RenderableBlackHole() {} void RenderableBlackHole::initialize() { _blackHoleWarpTable.reserve(_rayCountHighRes * _rayCountHighRes * 4); } void RenderableBlackHole::initializeGL() { const glm::vec2 screenSize = glm::vec2(global::renderEngine->renderingResolution()); ZoneScoped; setupQuad(); setupShaders(); loadEnvironmentTexture(); _viewport.updateViewGrid(screenSize); } void RenderableBlackHole::deinitializeGL() { _warpTableTex = nullptr; _environmentTexture = nullptr; _viewport.viewGrid = nullptr; BaseModule::ProgramObjectManager.release(_program); _program = nullptr; glDeleteBuffers(1, &_quadVbo); glDeleteVertexArrays(1, &_quadVao); } bool RenderableBlackHole::isReady() const { return _program != nullptr; } bool highres = false; void RenderableBlackHole::update(const UpdateData& data) { if (data.modelTransform.translation != _chachedTranslation) { _chachedTranslation = data.modelTransform.translation; _starKDTree.build("${BASE}/sync/http/stars_du/6/stars.speck", _chachedTranslation, { {0, 25 }, {25, 50}, { 50, -1 } }); } _viewport.updateViewGrid(global::renderEngine->renderingResolution()); #if M_Kerr // world-space camera glm::dvec3 camW = global::navigationHandler->camera()->positionVec3(); // 1) Translate into model-centered space glm::dvec3 v = camW - data.modelTransform.translation; // 2) Remove the rotation: for an orthonormal matrix, inverse == transpose glm::dvec3 v_rot = glm::transpose(data.modelTransform.rotation) * v; // 3) Remove scaling (component-wise) glm::dvec3 cameraPosition = v_rot / data.modelTransform.scale; if (glm::distance(cameraPosition, _chacedCameraPos) > 0.01f * _rs) { //kerr(2e11, 0, 0, _rs, 0.99f, _rayCount, _stepsCount, _blackHoleWarpTable); float env_scale = glm::distance(cameraPosition, { 0,0,0 }) / (0.99f * _rs); kerr(cameraPosition.x, cameraPosition.y, cameraPosition.z, _rs, 0.99f, { 3.5f * env_scale, 3.8f * env_scale, 4.0f * env_scale }, _rayCount, _stepsCount, _blackHoleWarpTable); _chacedCameraPos = cameraPosition; highres = false; lastTime = std::chrono::high_resolution_clock::now(); } else if (!highres){ float env_scale = glm::distance(cameraPosition, { 0,0,0 }) / (0.99f * _rs); auto currentTime = std::chrono::high_resolution_clock::now(); std::chrono::duration deltaTime = currentTime - lastTime; float deltaTimeSeconds = deltaTime.count(); if (deltaTimeSeconds > 1.0f) { kerr(cameraPosition.x, cameraPosition.y, cameraPosition.z, _rs, 0.99f, { 3.5f * env_scale, 3.8f * env_scale, 4.0f * env_scale }, _rayCountHighRes, _stepsCount, _blackHoleWarpTable); highres = true; } } #else glm::dvec3 cameraPosition = global::navigationHandler->camera()->positionVec3(); float distanceToAnchor = static_cast(glm::distance(cameraPosition, _chachedTranslation)); if (abs(_rCamera * _rs - distanceToAnchor) > _rs * 0.1) { _rCamera = distanceToAnchor / _rs; schwarzschild({ 12.5f / _rs * static_cast(distanceconstants::Parsec), 37.5f / _rs * static_cast(distanceconstants::Parsec), 40.f / _rs * static_cast(distanceconstants::Parsec)}, _rayCount, _stepsCount, _rCamera, _stepLength, _blackHoleWarpTable); } #endif bindSSBOData(_program, "ssbo_warp_table", _ssboBlackHoleDataBinding, _ssboBlackHoleWarpTable); bindSSBOData(_program, "ssbo_star_map_start_indices", _ssboStarIndicesDataBinding, _ssboStarKDTreeIndices); bindSSBOData(_program, "ssbo_star_map", _ssboStarDataBinding, _ssboStarKDTree); } void RenderableBlackHole::render(const RenderData& renderData, RendererTasks&) { _program->activate(); bindFramebuffer(); glDisable(GL_DEPTH_TEST); ghoul::opengl::TextureUnit enviromentUnit; if (!bindTexture(_uniformCache.environmentTexture, enviromentUnit, _environmentTexture)) { LWARNING("UniformCache is missing 'environmentTexture'"); } ghoul::opengl::TextureUnit viewGridUnit; if (!bindTexture(_uniformCache.viewGrid, viewGridUnit, _viewport.viewGrid)) { LWARNING("UniformCache is missing 'viewGrid'"); } ghoul::opengl::TextureUnit colorBVMapUnit; if (!bindTexture(_uniformCache.colorBVMap, colorBVMapUnit, _colorBVMapTexture)) { LWARNING("UniformCache is missing 'colorBVMap'"); } #ifdef M_Kerr ghoul::opengl::TextureUnit accretionDiskUnit; if (!bindTexture(_uniformCache.accretionDisk, accretionDiskUnit, _accretionDiskTexture)) { LWARNING("UniformCache is missing 'accretionDisk'"); } #endif // M_Kerr SendSchwarzschildTableToShader(); SendStarKDTreeToShader(); interaction::OrbitalNavigator::CameraRotationDecomposition camRot = global::navigationHandler->orbitalNavigator().decomposeCameraRotationSurface( CameraPose{renderData.camera.positionVec3(), renderData.camera.rotationQuaternion()}, *parent() ); // Calculate the camera planes rotation to make sure fisheye works correcly (dcm in sgct projection.cpp) glm::mat4 invViewPlaneTranslationMatrix = glm::translate( glm::mat4(1.f), glm::vec3(static_cast(renderData.camera.eyePositionVec3().x)) ); glm::mat4 viewMatrix = renderData.camera.viewMatrix(); glm::mat4 const CameraPlaneRotation = glm::inverse(viewMatrix * invViewPlaneTranslationMatrix); _program->setUniform( _uniformCache.cameraRotationMatrix, glm::mat4(glm::mat4_cast(camRot.localRotation)) * CameraPlaneRotation ); #if !M_Kerr _program->setUniform( _uniformCache.worldRotationMatrix, glm::mat4(glm::mat4_cast(camRot.globalRotation)) ); if (_uniformCache.r_0 != -1) { _program->setUniform( _uniformCache.r_0, _rCamera ); } #endif drawQuad(); glEnable(GL_DEPTH_TEST); _program->deactivate(); glBindTexture(GL_TEXTURE_2D, 0); } void RenderableBlackHole::SendSchwarzschildTableToShader() { glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboBlackHoleWarpTable); const size_t indexBufferSize = _blackHoleWarpTable.size() * sizeof(float); glBufferData( GL_SHADER_STORAGE_BUFFER, indexBufferSize, _blackHoleWarpTable.data(), GL_STREAM_DRAW ); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } void RenderableBlackHole::SendStarKDTreeToShader() { glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboStarKDTree); size_t indexBufferSize = _starKDTree.mapsSize() * sizeof(float); glBufferData( GL_SHADER_STORAGE_BUFFER, indexBufferSize, _starKDTree.mapsData(), GL_STREAM_DRAW ); glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboStarKDTreeIndices); indexBufferSize = _starKDTree.indicesSize() * sizeof(int); glBufferData( GL_SHADER_STORAGE_BUFFER, indexBufferSize, _starKDTree.indicesData(), GL_STREAM_DRAW ); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } void RenderableBlackHole::setupShaders() { #if M_Kerr _program = BaseModule::ProgramObjectManager.request( "KerrBlackHoleProgram", []() -> std::unique_ptr { return global::renderEngine->buildRenderProgram( "KerrBlackHoleProgram", absPath("${MODULE_BLACKHOLE}/shaders/kerr_vs.glsl"), absPath("${MODULE_BLACKHOLE}/shaders/kerr_fs.glsl") ); } ); ghoul::opengl::updateUniformLocations(*_program, _uniformCache); #else _program = BaseModule::ProgramObjectManager.request( "BlackHoleProgram", []() -> std::unique_ptr { return global::renderEngine->buildRenderProgram( "BlackHoleProgram", absPath("${MODULE_BLACKHOLE}/shaders/schwarzschild_vs.glsl"), absPath("${MODULE_BLACKHOLE}/shaders/schwarzschild_fs.glsl") ); } ); ghoul::opengl::updateUniformLocations(*_program, _uniformCache); #endif } void RenderableBlackHole::setupQuad() { glGenVertexArrays(1, &_quadVao); glBindVertexArray(_quadVao); glGenBuffers(1, &_quadVbo); glBindBuffer(GL_ARRAY_BUFFER, _quadVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(QuadVtx), QuadVtx.data(), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), reinterpret_cast(2 * sizeof(GLfloat))); } void RenderableBlackHole::loadEnvironmentTexture() { //const std::string texturePath = "${MODULE_BLACKHOLE}/rendering/uv.png"; const std::string texturePath = "${BASE}/sync/http/milkyway_textures/2/DarkUniverse_mellinger_8k.jpg"; //const std::string texturePath = "${MODULE_BLACKHOLE}/rendering/skybox.jpg"; _environmentTexture = ghoul::io::TextureReader::ref().loadTexture(absPath(texturePath), 2); if (_environmentTexture) { _environmentTexture->uploadTexture(); } else { LWARNING(std::format("Failed to load environment texture from path '{}'", absPath(texturePath).string())); } _colorBVMapTexture = ghoul::io::TextureReader::ref().loadTexture(absPath(_colorBVMapTexturePath), 1); if (_colorBVMapTexture) { _colorBVMapTexture->uploadTexture(); } else { LWARNING(std::format("Failed to load environment texture from path '{}'", absPath(_colorBVMapTexturePath).string())); } #if M_Kerr _accretionDiskTexture = ghoul::io::TextureReader::ref().loadTexture(absPath("${MODULE_BLACKHOLE}/rendering/accretion_disk.png"), 1); if (_accretionDiskTexture) { _accretionDiskTexture->uploadTexture(); } #endif } void RenderableBlackHole::bindFramebuffer() { const GLint defaultFBO = ghoul::opengl::FramebufferObject::getActiveObject(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); } bool RenderableBlackHole::bindTexture(GLint chacheRegistry, ghoul::opengl::TextureUnit& textureUnit, std::unique_ptr& texture) { if (!texture) return false; textureUnit.activate(); texture->bind(); _program->setUniform(chacheRegistry, textureUnit); return true; } void RenderableBlackHole::drawQuad() { glBindVertexArray(_quadVao); glDrawArrays(GL_TRIANGLES, 0, 6); } void RenderableBlackHole::bindSSBOData( ghoul::opengl::ProgramObject* program, const std::string& ssboName, std::unique_ptr>& ssboBinding, GLuint& ssboID ) { if (ssboID == 0) { glGenBuffers(1, &ssboID); LDEBUG(std::format( "Generating Data Shader Storage Buffer Object id '{}'", ssboID )); } glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboID); ssboBinding = std::make_unique >(); glBindBufferBase( GL_SHADER_STORAGE_BUFFER, ssboBinding->bindingNumber(), ssboID ); program->setSsboBinding(ssboName, ssboBinding->bindingNumber()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } documentation::Documentation RenderableBlackHole::Documentation() { return documentation::Documentation(); } } // namespace openspace