B-5 2024/6/12
VulkanSceneGraph(VSG)+imgui和动态调整
- 用imgui和vulkan实现一些简单的功能
- 如何在渲染循环里动态的去修改物体的状态和管线的状态
- 可以用动态数组等方式去修改,vsgexamples里也有例子,如下两个,尤其是第一个例子也提供了imgui的界面
vsgdynamicstate
vsgdynamicvertex
- 在第二个例子里,实现了平移茶壶,而我借用这个例子实现了颜色的修改,关键代码如下:
void apply(vsg::BindVertexBuffers& bvd)
{
if (bvd.arrays.empty()) return;
bvd.arrays[2]->data->accept(*this);
bufferInfoSet.insert(bvd.arrays[2]);
}
std::vector<vsg::ref_ptr<vsg::vec4Value>> getColorList()
{
std::vector<vsg::ref_ptr<vsg::vec4Value>> colorList(colorSet.size());
auto color_itr = colorList.begin();
for (auto& color : colorSet)
{
(*color_itr++) = const_cast<vsg::vec4Value*>(color);
}
return colorList;
}
std::set<vsg::vec4Value*> colorSet;
std::set<vsg::vec3Array*> verticesSet;
std::set<vsg::ref_ptr<vsg::BufferInfo>> bufferInfoSet;
- 这段代码在class FindVertexData : public vsg::Visitor里面
- 把0换成了2,因为0是位置,1是法向,2是颜色。(可能是我没有绑定texture的缘故,所以2变成了颜色)
- 下面是在渲染循环里做的修改
while (viewer->advanceToNextFrame())
{
if (gui->selectID == 666) {
for (auto& color : colorList)
{
color->value() = vsg::vec4{ 0.3, 0.0, 0.9, 1.0 };
color->dirty();
}
}else for(auto& color : colorList)
{
color->value() = vsg::vec4{ 0.3, 0.8, 0.3, 1.0 };
color->dirty();
}
viewer->handleEvents();
viewer->update();
viewer->recordAndSubmit();
viewer->present();
}
- 如何修改管线的状态呢,就要用到第一个例子了。但是第一个例子只提供了修改线宽的接口
- 想要实现修改其他渲染模式,我进行了尝试没有成功
- 因为在调用vulkan底层函数的时候,找不到调用的那个函数,只能找到vkCmdSetLineWidth,其他的在vulkan_core.h里的函数却找不到,可能是要修改Extensions.h和Extensions.cpp文件,添加改函数,然后重新编译vsg。有点麻烦,大家可以去尝试尝试。
- 下面是实现imgui的代码
- 外面用了一个Params结构体来放置MyGui的变量。这是因为,MyGui是静态类,所以所有的变量都是静态变量不可改,所以放外面
- 或者在前面加mutable关键字也可以,就像这样。这样子就可以把静态类里面的成员变量变成可修改的
private: mutable bool stepoutput;
#include <vsg/all.h>
#include <vsgImGui/RenderImGui.h>
#include <vsgImGui/SendEventsToImGui.h>
#include <vsgImGui/imgui.h>
struct Params : public vsg::Inherit<vsg::Object, Params>
{
bool showGui = true; // you can toggle this with your own EventHandler and key
bool showDemoWindow = false;
bool showSecondWindow = false;
bool showImPlotDemoWindow = false;
bool showLogoWindow = true;
bool showImagesWindow = false;
float clearColor[3]{0.2f, 0.2f, 0.4f}; // Unfortunately, this doesn't change dynamically in vsg
uint32_t counter = 0;
float dist = 0.f;
};
class MyGui : public vsg::Inherit<vsg::Command, MyGui>
{
public:
vsg::ref_ptr<vsgImGui::Texture> texture;
vsg::ref_ptr<Params> params;
MyGui(vsg::ref_ptr<Params> in_params, vsg::ref_ptr<vsg::Options> options = {}) :
params(in_params)
{
auto texData = vsg::read_cast<vsg::Data>("textures/VSGlogo.png", options);
texture = vsgImGui::Texture::create_if(texData, texData);
}
// we need to compile textures before we can use them for rendering
void compile(vsg::Context& context) override
{
if (texture) texture->compile(context);
}
// Example here taken from the Dear imgui comments (mostly)
void record(vsg::CommandBuffer& cb) const override
{
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
if (params->showGui)
{
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("Some useful message here."); // Display some text (you can use format strings too)
ImGui::Checkbox("Demo Window", ¶ms->showDemoWindow); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", ¶ms->showSecondWindow);
ImGui::Checkbox("ImPlot Demo Window", ¶ms->showImPlotDemoWindow);
if (texture)
{
ImGui::Checkbox("Images Window", ¶ms->showImagesWindow);
}
ImGui::SliderFloat("float", ¶ms->dist, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)¶ms->clearColor); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
params->counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", params->counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (params->showSecondWindow)
{
ImGui::Begin("Another Window", ¶ms->showSecondWindow); // Pass a pointer to our bool variable (the window will have a close button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
params->showSecondWindow = false;
ImGui::End();
}
if (params->showDemoWindow)
{
ImGui::ShowDemoWindow(¶ms->showDemoWindow);
}
if (params->showImPlotDemoWindow)
{
ImPlot::ShowDemoWindow(¶ms->showImPlotDemoWindow);
}
// UV for a square in the logo texture
if (texture)
{
ImVec2 squareUV(static_cast<float>(texture->height) / texture->width, 1.0f);
if (params->showLogoWindow)
{
// Copied from imgui_demo.cpp simple overlay
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
const float PAD = 10.0f;
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!
ImVec2 work_size = viewport->WorkSize;
ImVec2 window_pos, window_pos_pivot;
window_pos.x = work_pos.x + PAD;
window_pos.y = work_pos.y + work_size.y - PAD;
window_pos_pivot.x = 0.0f;
window_pos_pivot.y = 1.0f;
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
window_flags |= ImGuiWindowFlags_NoMove;
ImGui::SetNextWindowBgAlpha(0.0f); // Transparent background
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("vsgCS UI", nullptr, window_flags);
// Display a square from the VSG logo
const float size = 128.0f;
ImGui::Image(texture->id(cb.deviceID), ImVec2(size, size), ImVec2(0.0f, 0.0f), squareUV);
ImGui::End();
ImGui::PopStyleVar();
}
if (params->showImagesWindow)
{
ImGui::Begin("Image Window", ¶ms->showImagesWindow);
ImGui::Text("An texture:");
// The logo texture is big, show it at half size
ImGui::Image(texture->id(cb.deviceID), ImVec2(texture->width / 2.0f, texture->height / 2.0f));
// We could make another component class for ImageButton, but we will take a short cut
// and reuse the descriptor set from our existing texture.
//
// Make a small square button
if (ImGui::ImageButton("Button", texture->id(cb.deviceID),
ImVec2(32.0f, 32.0f),
ImVec2(0.0f, 0.0f),
squareUV))
params->counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", params->counter);
ImGui::End();
}
}
}
};
int main(int argc, char** argv)
{
auto options = vsg::Options::create();
options->sharedObjects = vsg::SharedObjects::create();
options->fileCache = vsg::getEnv("VSG_FILE_CACHE");
options->paths = vsg::getEnvPaths("VSG_FILE_PATH");
#ifdef vsgXchange_all
// add vsgXchange's support for reading and writing 3rd party file formats
options->add(vsgXchange::all::create());
#endif
auto windowTraits = vsg::WindowTraits::create();
windowTraits->windowTitle = "vsgimgui";
// set up defaults and read command line arguments to override them
vsg::CommandLine arguments(&argc, argv);
arguments.read(options);
auto event_read_filename = arguments.value(std::string(""), "-i");
auto event_output_filename = arguments.value(std::string(""), "-o");
windowTraits->debugLayer = arguments.read({"--debug", "-d"});
windowTraits->apiDumpLayer = arguments.read({"--api", "-a"});
arguments.read("--screen", windowTraits->screenNum);
arguments.read("--display", windowTraits->display);
arguments.read("--samples", windowTraits->samples);
auto numFrames = arguments.value(-1, "-f");
auto fontFile = arguments.value<vsg::Path>({}, "--font");
auto fontSize = arguments.value<float>(30.0f, "--font-size");
if (arguments.errors()) return arguments.writeErrorMessages(std::cerr);
try
{
auto vsg_scene = vsg::Group::create();
vsg::ref_ptr<vsg::EllipsoidModel> ellipsoidModel;
if (argc > 1)
{
vsg::Path filename = arguments[1];
if (auto node = vsg::read_cast<vsg::Node>(filename, options); node)
{
vsg_scene->addChild(node);
ellipsoidModel = node->getRefObject<vsg::EllipsoidModel>("EllipsoidModel");
}
}
// create the viewer and assign window(s) to it
auto viewer = vsg::Viewer::create();
vsg::ref_ptr<vsg::Window> window(vsg::Window::create(windowTraits));
if (!window)
{
std::cout << "Could not create window." << std::endl;
return 1;
}
viewer->addWindow(window);
// compute the bounds of the scene graph to help position camera
vsg::ComputeBounds computeBounds;
vsg_scene->accept(computeBounds);
vsg::dvec3 centre = (computeBounds.bounds.min + computeBounds.bounds.max) * 0.5;
double radius = vsg::length(computeBounds.bounds.max - computeBounds.bounds.min) * 0.6;
// These are set statically because the geometry in the class is expanded in the shader
double nearFarRatio = 0.01;
// set up the camera
auto lookAt = vsg::LookAt::create(centre + vsg::dvec3(0.0, -radius * 3.5, 0.0), centre, vsg::dvec3(0.0, 0.0, 1.0));
vsg::ref_ptr<vsg::ProjectionMatrix> perspective;
if (ellipsoidModel)
{
perspective = vsg::EllipsoidPerspective::create(lookAt, ellipsoidModel, 30.0, static_cast<double>(window->extent2D().width) / static_cast<double>(window->extent2D().height), nearFarRatio, 0.0);
}
else
{
perspective = vsg::Perspective::create(30.0, static_cast<double>(window->extent2D().width) / static_cast<double>(window->extent2D().height), nearFarRatio * radius, radius * 400.5);
}
auto camera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(window->extent2D()));
// The commandGraph will contain a 2 stage renderGraph: 1) 3D scene 2) ImGui (by default also includes clearing of depth buffers)
auto commandGraph = vsg::CommandGraph::create(window);
auto renderGraph = vsg::RenderGraph::create(window);
commandGraph->addChild(renderGraph);
// create the normal 3D view of the scene
auto view = vsg::View::create(camera);
view->addChild(vsg::createHeadlight());
view->addChild(vsg_scene);
renderGraph->addChild(view);
if (fontFile)
{
auto foundFontFile = vsg::findFile(fontFile, options);
if (foundFontFile)
{
// convert native filename to UTF8 string that is compatible with ImGui.
std::string c_fontFile = foundFontFile.string();
// initialize ImGui
ImGui::CreateContext();
// read the font via ImGui, which will then be current when vsgImGui::RenderImGui initializes the rest of ImGui/Vulkan below
ImGuiIO& io = ImGui::GetIO();
auto imguiFont = io.Fonts->AddFontFromFileTTF(c_fontFile.c_str(), fontSize);
if (!imguiFont)
{
std::cout << "Failed to load font: " << c_fontFile << std::endl;
return 0;
}
}
}
// Create the ImGui node and add it to the renderGraph
auto params = Params::create();
auto renderImGui = vsgImGui::RenderImGui::create(window, MyGui::create(params, options));
renderGraph->addChild(renderImGui);
// Add the ImGui event handler first to handle events early
viewer->addEventHandler(vsgImGui::SendEventsToImGui::create());
// add close handler to respond to the close window button and pressing escape
viewer->addEventHandler(vsg::CloseHandler::create(viewer));
viewer->addEventHandler(vsg::Trackball::create(camera, ellipsoidModel));
viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});
viewer->compile();
vsg::ref_ptr<vsg::RecordEvents> recordEvents;
if (!event_output_filename.empty())
{
recordEvents = vsg::RecordEvents::create();
viewer->addEventHandler(recordEvents);
}
vsg::ref_ptr<vsg::PlayEvents> playEvents;
if (!event_read_filename.empty())
{
auto read_events = vsg::read(event_read_filename);
if (read_events)
{
playEvents = vsg::PlayEvents::create(read_events, viewer->start_point().time_since_epoch());
}
}
// rendering main loop
while (viewer->advanceToNextFrame() && (numFrames < 0 || (numFrames--) > 0))
{
viewer->handleEvents();
viewer->update();
viewer->recordAndSubmit();
viewer->present();
}
}
catch (const vsg::Exception& ve)
{
std::cerr << "[Exception] - " << ve.message << std::endl;
}
return 0;
}