Compare commits

...

3 Commits

Author SHA1 Message Date
8dc5d82c8d README screenshots. 2025-03-09 11:43:38 -04:00
78639cf1c2 Reposition models in scene. 2025-03-09 11:11:09 -04:00
941f2d228c Clean up separation between plugins and app. 2025-03-08 14:09:56 -05:00
25 changed files with 106 additions and 62 deletions

View File

@ -3,45 +3,59 @@
[![All Builds](https://github.com/shaunrd0/qtk/actions/workflows/all-builds.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/all-builds.yml)
[![Linting](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml)
Qtk is a Qt OpenGL graphics library created primarily for my own learning
purposes. The library wraps some QOpenGL functionality in convenience classes
Qtk is a Qt OpenGL graphics library that wraps some QOpenGL functionality in convenience classes
that allow rendering geometry in 2D and 3D using custom GLSL shader programs.
The Qtk desktop application provides a model loader using [Assimp](https://assimp.org/) within a Qt widget application.
You can fly around the scene using WASD while holding down the right mouse button.
Object names can be double-clicked in the tree view panel for quick camera navigation.
Properties of the object, like shader code and translation / scale, can be viewed and modified in the side panel.
![](resources/screenshots/screen.png)
All side panels and toolbars are dockable widgets that can be popped out
and reorganized as needed. Panels can also be stacked to create a docked widget with
tabs. The central widget that provides the camera view into the scene cannot be
detached from the main window in this way.
![](resources/screenshots/screen-1.png)
The small triangles floating near 3D models represent the light source being used for the shader.
These appear on models using phong, specular, and diffuse lighting techniques.
The default scene contains basic examples like texture mapping to make a crate from basic cube geometry
![](resources/screenshots/screen-texture.png)
Examples of Ambient, Diffuse, and Specular GLSL shaders.
| Ambient | Diffuse | Specular |
|-------------------------------------------------------|-------------------------------------------------------|--------------------------------------------------------|
| <img src="resources/screenshots/screen-ambient.png"/> | <img src="resources/screenshots/screen-diffuse.png"/> | <img src="resources/screenshots/screen-specular.png"/> |
And more advanced techniques like Phong lighting (ambient + diffuse + specular) and normal mapping.
![](resources/screenshots/screen-phong.png)
| Normal Mapping Disabled | Normal Mapping Enabled |
|------------------------------------------------------|--------------------------------------------------------|
| <img src="resources/screenshots/spartan-phong.png"/> | <img src="resources/screenshots/spartan-normals.png"/> |
See the `View` toolbar menu to enable debug console widgets for open scenes or reopen previously closed panels.
Key features that are planned:
- [x] Runtime loading of `.obj` or similar 3D models.
- [x] Drag-and-drop interaction for adding objects to the scene.
- [x] Shader / object properties panel to modify related settings.
- [ ] Runtime reloading of modified GLSL shaders attached to objects within scenes.
- [ ] Multiple views of a scene at one time.
- [ ] Camera control modes such as panning, orbiting, or following objects.
- [ ] Save / load for scene data. The current inheritance model is temporary.
- [ ] Basic text editor for quickly modifying shaders attached to objects.
- [ ] Shader / object properties panel to modify related settings.
- [ ] Reduce size of application resources and git references.
![](resources/screenshot.png)
Spartan with no normals -
![](resources/spartan-specular.png)
Spartan with normals -
![](resources/spartan-normals.png)
Object names can be double-clicked in the tree view panel for quick camera
navigation. All side panels and toolbars are dockable widgets that can be popped out
and reorganized as needed. Panels can also be stacked to create a docked widget with
tabs. The central widget that provides the camera view into the scene cannot be
detached from the main window in this way. See the `View` menu to enable debug
console widgets for open scenes or reopen previously closed panels.
The small triangles floating near 3D models represent the light source being used for the shader.
These appear on models using phong, specular, and diffuse lighting techniques.
For examples of using the Qtk API, see the `example-app` project in the root of
this repository.

View File

@ -46,8 +46,8 @@
<file alias="solid-phong.vert">shaders/vertex/solid-phong.vert</file>
<file alias="model-basic.frag">shaders/fragment/model-basic.frag</file>
<file alias="model-basic.vert">shaders/vertex/model-basic.vert</file>
<file alias="model-specular.frag">shaders/fragment/model-specular.frag</file>
<file alias="model-specular.vert">shaders/vertex/model-specular.vert</file>
<file alias="model-phong.frag">shaders/fragment/model-phong.frag</file>
<file alias="model-phong.vert">shaders/vertex/model-phong.vert</file>
<file alias="model-normals.frag">shaders/fragment/model-normals.frag</file>
<file alias="model-normals.vert">shaders/vertex/model-normals.vert</file>
<file alias="skybox.frag">skybox/skybox.frag</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 KiB

View File

@ -9,8 +9,6 @@
#include "qtkmainwindow.h"
#include "ui_qtkmainwindow.h"
MainWindow * MainWindow::mainWindow_ = Q_NULLPTR;
/*******************************************************************************
* Constructors / Destructors
******************************************************************************/
@ -27,6 +25,10 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
// Initialize static container for all active QtkWidgets
auto qtkWidgets = findChildren<Qtk::QtkWidget *>();
for (auto & qtkWidget : qtkWidgets) {
// NOTE: Set a temporary scene for the widget to use for initialization.
// This should be replaced by loading a scene, or creating a new (unsaved)
// scene when Qtk is opened.
qtkWidget->setScene(new EmptyScene);
views_.emplace(qtkWidget->getScene()->getSceneName(), qtkWidget);
// Add GUI 'view' toolbar option to show debug console.
@ -36,8 +38,7 @@ MainWindow::MainWindow(QWidget * parent) : QMainWindow(parent)
&Qtk::Scene::sceneUpdated,
this,
&MainWindow::refreshScene);
connect(qtkWidget,
&Qtk::QtkWidget::objectFocusChanged,
connect(qtkWidget, &Qtk::QtkWidget::objectFocusChanged,
ui_->qtk__ToolBox,
&Qtk::ToolBox::updateFocus);
}
@ -70,10 +71,8 @@ MainWindow::~MainWindow()
MainWindow * MainWindow::getMainWindow()
{
if (mainWindow_ == Q_NULLPTR) {
mainWindow_ = new MainWindow;
}
return mainWindow_;
static MainWindow window;
return &window;
}
Qtk::QtkWidget * MainWindow::getQtkWidget(int64_t index)

View File

@ -22,6 +22,38 @@ namespace Ui
class MainWindow;
}
/**
* An empty scene used for initializing all QtkWidgets within the MainWindow.
* This serves as a temporary placeholder for QtkScene (for example), which is
* defined in the separate qtk_gui target. The reason for this separation is to
* support the use of QtkWidgets (the qtk_plugins target) within the Qt Designer
* application without implementations provided in the Qtk Desktop Application.
*
* For the Qtk application, this should be replaced by loading the previous
* scene or creating a new _unsaved_ scene when the application is opened.
* Currently we have essentially hard-coded QtkScene to use as examples for
* testing the application. This means that the only way to create or modify a
* scene is to write code. Any modifications made in the application, such as
* moving or resizing objects, will not persist and cannot be saved.
*
* For users of Qtk Designer Plugins, this means that installing
* the `qtk_plugins` target to Qt Designer allows use all of the designer's
* features to build an interface and position or resize a QtkWidget as needed.
* The QtkWidget also appears as widget in the IDE's toolbars and can be added
* to any new application easily, once the plugins are installed.
*
* Once the application is designed, you can define a custom scene and use the
* Qtk API or Qt OpenGL funtions directly to render to it.
*
* Any application using a QtkWidget can set a custom scene in their main
* function. See the MainWindow::MainWindow constructor as an example.
*/
class EmptyScene : public Qtk::Scene {
void init() override {
setSceneName("Empty Scene");
}
};
/**
* MainWindow class to provide an example of using a QtkWidget within a Qt
* window application.
@ -82,7 +114,6 @@ class MainWindow : public QMainWindow
MainWindow(const MainWindow &) {};
Ui::MainWindow * ui_ {};
static MainWindow * mainWindow_;
/**
* Maps a scene name to the QtkWidget viewing it.

View File

@ -114,7 +114,7 @@ void QtkScene::init()
// Simple cube lighting examples.
/* Phong lighting example on a basic cube. */
mTestPhong = new Qtk::MeshRenderer("phong", Qtk::Cube());
mTestPhong = addObject(new Qtk::MeshRenderer("phongCube", Qtk::Cube()));
mTestPhong->getTransform().setTranslation(3.0f, 0.0f, -2.0f);
// NOTE: You no longer need to manually bind shader program to set uniforms.
// + You can still bind it if you want to for performance reasons.
@ -152,7 +152,7 @@ void QtkScene::init()
// No light source needed for this lighting technique
/* Initialize Ambient example cube */
mTestAmbient = new Qtk::MeshRenderer("ambient", Cube());
mTestAmbient = addObject(new Qtk::MeshRenderer("ambientCube", Cube()));
mTestAmbient->getTransform().setTranslation(7.0f, 0.0f, -2.0f);
mTestAmbient->setShaders(":/shaders/solid-ambient.vert",
":/shaders/solid-ambient.frag");
@ -164,7 +164,7 @@ void QtkScene::init()
// No light source needed for this lighting technique
/* Initialize Diffuse example cube */
mTestDiffuse = new Qtk::MeshRenderer("diffuse", Cube());
mTestDiffuse = addObject(new Qtk::MeshRenderer("diffuseCube", Cube()));
mTestDiffuse->getTransform().setTranslation(9.0f, 0.0f, -2.0f);
mTestDiffuse->setShaders(":/shaders/solid-diffuse.vert",
":/shaders/solid-diffuse.frag");
@ -180,7 +180,7 @@ void QtkScene::init()
mesh->getTransform().scale(0.25f);
/* Initialize Specular example cube */
mTestSpecular = new Qtk::MeshRenderer("specular", Cube());
mTestSpecular = addObject(new Qtk::MeshRenderer("specularCube", Cube()));
mTestSpecular->getTransform().setTranslation(11.0f, 0.0f, -2.0f);
mTestSpecular->setShaders(":/shaders/solid-specular.vert",
":/shaders/solid-specular.frag");
@ -233,8 +233,8 @@ void QtkScene::init()
/* Test alien Model with phong lighting and specular mapping. */
model = addObject(new Qtk::Model("alienTest",
":/models/models/alien-hominid/alien.obj",
":/shaders/model-specular.vert",
":/shaders/model-specular.frag"));
":/shaders/model-phong.vert",
":/shaders/model-phong.frag"));
model->getTransform().setTranslation(3.0f, -1.0f, 10.0f);
model->getTransform().scale(0.15f);
model->setUniform("uMaterial.ambient", QVector3D(1.0f, 1.0f, 1.0f));
@ -242,7 +242,7 @@ void QtkScene::init()
model->setUniform("uMaterial.specular", QVector3D(1.0f, 1.0f, 1.0f));
model->setUniform("uMaterial.ambientStrength", 0.8f);
model->setUniform("uMaterial.diffuseStrength", 0.8f);
model->setUniform("uMaterial.specularStrength", 1.0f);
model->setUniform("uMaterial.specularStrength", 0.5f);
model->setUniform("uMaterial.shine", 32.0f);
model->setUniform("uLight.ambient", QVector3D(1.0f, 1.0f, 1.0f));
@ -260,8 +260,8 @@ void QtkScene::init()
/* Test spartan Model with phong lighting, specular and normal mapping. */
model = addObject(new Qtk::Model("spartanTest",
":/models/models/spartan/spartan.obj",
":/shaders/model-normals.vert",
":/shaders/model-normals.frag"));
":/shaders/model-phong.vert",
":/shaders/model-phong.frag"));
model->getTransform().setTranslation(0.0f, -1.0f, 10.0f);
model->getTransform().scale(2.0f);
model->setUniform("uMaterial.ambient", QVector3D(1.0f, 1.0f, 1.0f));

View File

@ -41,36 +41,33 @@ void Qtk::TreeView::updateView(const Qtk::Scene * scene)
mSceneName = scene->getSceneName();
auto objects = scene->getObjects();
for (const auto & object : objects) {
auto item =
new QTreeWidgetItem(QStringList(QString(object->getName().c_str())));
ui->treeWidget->insertTopLevelItem(0, item);
QStringList list(QStringList(QString(object->getName().c_str())));
ui->treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(list));
}
}
void Qtk::TreeView::itemFocus(QTreeWidgetItem * item, int column)
{
QString name = item->text(column);
const QString & name = item->text(column);
auto scene = MainWindow::getMainWindow()->getQtkWidget()->getScene();
auto & transform = Qtk::Scene::getCamera().getTransform();
auto object = scene->getObject(name);
Transform3D * objectTransform;
// If the object is a mesh or model, focus the camera on it.
if (object == Q_NULLPTR) {
qDebug() << "Attempt to get non-existing object with name '" << name
<< "'\n";
} else if (object->getType() == Object::QTK_MESH) {
objectTransform = &dynamic_cast<MeshRenderer *>(object)->getTransform();
} else if (object->getType() == Object::QTK_MODEL) {
objectTransform = &dynamic_cast<Model *>(object)->getTransform();
return;
}
auto focusScale = objectTransform->getScale();
const Transform3D& objectTransform = object->getTransform();
auto & camera_transform = Qtk::Scene::getCamera().getTransform();
auto focusScale = objectTransform.getScale();
float width = focusScale.x() / 2.0f;
float height = focusScale.y() / 2.0f;
QVector3D pos = objectTransform->getTranslation();
QVector3D pos = objectTransform.getTranslation();
// pos.setX(pos.x() + width);
pos.setY(pos.y() + height);
transform.setTranslation(pos);
transform.translate(0.0f, 0.0f, 3.0f);
camera_transform.setTranslation(pos);
camera_transform.translate(0.0f, 0.0f, 3.0f);
// Emit signal from qtk widget for new object focus. Triggers GUI updates.
emit MainWindow::getMainWindow() -> getQtkWidget()->objectFocusChanged(name);

View File

@ -111,6 +111,11 @@ namespace Qtk
return mTransform;
}
[[nodiscard]] inline virtual Transform3D & getTransform()
{
return mTransform;
}
[[nodiscard]] inline virtual std::string getVertexShader() const
{
return "Base Object has no vertex shader.";

View File

@ -118,11 +118,9 @@ void Scene::setSkybox(Skybox * skybox)
void Scene::initSceneObjectName(Object * object)
{
if (!mObjectCount.count(object->getName())) {
mObjectCount[object->getName()] = 1;
} else {
mObjectCount[object->getName()]++;
}
mObjectCount[object->getName()] = mObjectCount.count(object->getName()) + 1;
// If the object exists make it's name unique.
auto count = mObjectCount[object->getName()];
if (count > 1) {
object->setName(object->getName() + " (" + std::to_string(count) + ")");