From a3e7dc47d885f411215688cd0c515fcf8bb35a85 Mon Sep 17 00:00:00 2001
From: Shaun Reed <shaunrd0@gmail.com>
Date: Sun, 16 Mar 2025 20:06:27 -0400
Subject: [PATCH] Factor out more resources.

+ Use QOpenGLShader to get compiled shader code in TreeView.
+ Remove last remaining Qt resources dependency in libqtk.
+ shaders.h in libqtk to define default GLSL shader programs.
---
 README.md                                   |   4 +-
 resources/minimal_resources.qrc             |  22 ++++
 resources/resources.qrc                     |   6 -
 resources/shaders/fragment/model-basic.frag |  11 --
 resources/shaders/fragment/multi-color.frag |   9 --
 resources/shaders/vertex/model-basic.vert   |  16 ---
 resources/shaders/vertex/multi-color.vert   |  16 ---
 resources/skybox/skybox.frag                |   9 --
 resources/skybox/skybox.vert                |  15 ---
 src/app/CMakeLists.txt                      |   7 ++
 src/app/main.cpp                            |  10 +-
 src/app/qtkmainwindow.h                     |  21 ++++
 src/app/qtkscene.cpp                        |   4 +-
 src/designer-plugins/toolbox.cpp            |  18 +--
 src/qtk/CMakeLists.txt                      |   2 +-
 src/qtk/meshrenderer.cpp                    |  24 +++-
 src/qtk/model.h                             |   4 +-
 src/qtk/modelmesh.cpp                       |  29 ++++-
 src/qtk/modelmesh.h                         |   6 +-
 src/qtk/object.cpp                          |  12 ++
 src/qtk/object.h                            |  17 ++-
 src/qtk/qtkapi.h                            |  10 --
 src/qtk/shaders.h                           | 120 ++++++++++++++++++++
 src/qtk/skybox.cpp                          |   9 +-
 24 files changed, 258 insertions(+), 143 deletions(-)
 create mode 100644 resources/minimal_resources.qrc
 delete mode 100644 resources/shaders/fragment/model-basic.frag
 delete mode 100644 resources/shaders/fragment/multi-color.frag
 delete mode 100644 resources/shaders/vertex/model-basic.vert
 delete mode 100644 resources/shaders/vertex/multi-color.vert
 delete mode 100644 resources/skybox/skybox.frag
 delete mode 100644 resources/skybox/skybox.vert
 create mode 100644 src/qtk/shaders.h

diff --git a/README.md b/README.md
index df4870d..95b1315 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 [![All Builds](https://github.com/shaunrd0/qtk/actions/workflows/all-builds.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/build.yml)
 [![Linting](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml/badge.svg)](https://github.com/shaunrd0/qtk/actions/workflows/linting.yml)
 
-Qtk desktop application provides a model loader using [Assimp](https://assimp.org/) within a Qt widget application.
+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 left or right mouse button.
 [QtkWidget](./src/designer-plugins/qtkwidget.h) is the primary QOpenGLWidget used to render the scene and handle input.
 
@@ -72,7 +72,7 @@ project in the root of this repository.
 
 To get textures loading on models look
 into [material files](http://www.paulbourke.net/dataformats/mtl/)
-and see some examples in the `resources/models/` directory.
+and see some examples at [qtk-resources/resources/models](https://git.shaunreed.com/shaunrd0/qtk-resources/src/branch/master/models).
 
 ### Source Builds
 
diff --git a/resources/minimal_resources.qrc b/resources/minimal_resources.qrc
new file mode 100644
index 0000000..cf719b1
--- /dev/null
+++ b/resources/minimal_resources.qrc
@@ -0,0 +1,22 @@
+<RCC>
+    <qresource prefix="/textures">
+        <file alias="plaster.png">images/plaster.png</file>
+        <file alias="crate.png">images/crate.png</file>
+        <file alias="stone.png">images/stone.png</file>
+        <file alias="wood.png">images/wood.png</file>
+        <file>skybox/back.png</file>
+        <file>skybox/bottom.png</file>
+        <file>skybox/front.png</file>
+        <file>skybox/left.png</file>
+        <file>skybox/right.png</file>
+        <file>skybox/top.png</file>
+    </qresource>
+    <qresource prefix="/icons">
+        <file>fontawesome-free-6.2.1-desktop/svgs/solid/cube.svg</file>
+        <file>fontawesome-free-6.2.1-desktop/svgs/regular/trash-can.svg</file>
+        <file>fontawesome-free-6.2.1-desktop/svgs/regular/folder-open.svg</file>
+        <file>fontawesome-free-6.2.1-desktop/svgs/regular/floppy-disk.svg</file>
+        <file>fontawesome-free-6.2.1-desktop/svgs/brands/git-alt.svg</file>
+        <file alias="icon.png">icons/icon.png</file>
+    </qresource>
+</RCC>
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 551eed7..7f88b11 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -28,8 +28,6 @@
         <file alias="solid.vert">shaders/vertex/solid.vert</file>
         <file alias="solid-perspective.frag">shaders/fragment/solid-perspective.frag</file>
         <file alias="solid-perspective.vert">shaders/vertex/solid-perspective.vert</file>
-        <file alias="multi-color.frag">shaders/fragment/multi-color.frag</file>
-        <file alias="multi-color.vert">shaders/vertex/multi-color.vert</file>
         <file alias="rgb-normals.frag">shaders/fragment/rgb-normals.frag</file>
         <file alias="rgb-normals.vert">shaders/vertex/rgb-normals.vert</file>
         <file alias="texture-cubemap.frag">shaders/fragment/texture-cubemap.frag</file>
@@ -44,13 +42,9 @@
         <file alias="solid-specular.vert">shaders/vertex/solid-specular.vert</file>
         <file alias="solid-phong.frag">shaders/fragment/solid-phong.frag</file>
         <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-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>
-        <file alias="skybox.vert">skybox/skybox.vert</file>
     </qresource>
 </RCC>
diff --git a/resources/shaders/fragment/model-basic.frag b/resources/shaders/fragment/model-basic.frag
deleted file mode 100644
index 8af8279..0000000
--- a/resources/shaders/fragment/model-basic.frag
+++ /dev/null
@@ -1,11 +0,0 @@
-#version 330 core
-out vec4 fColor;
-
-in vec2 vTextureCoord;
-
-uniform sampler2D texture_diffuse1;
-
-void main()
-{
-    fColor = texture(texture_diffuse1, vTextureCoord);
-}
diff --git a/resources/shaders/fragment/multi-color.frag b/resources/shaders/fragment/multi-color.frag
deleted file mode 100644
index 896bda7..0000000
--- a/resources/shaders/fragment/multi-color.frag
+++ /dev/null
@@ -1,9 +0,0 @@
-#version 330
-in vec4 vColor;
-
-out vec4 fColor;
-
-void main()
-{
-  fColor = vColor;
-}
diff --git a/resources/shaders/vertex/model-basic.vert b/resources/shaders/vertex/model-basic.vert
deleted file mode 100644
index 3c305ab..0000000
--- a/resources/shaders/vertex/model-basic.vert
+++ /dev/null
@@ -1,16 +0,0 @@
-#version 330 core
-layout (location = 0) in vec3 aPosition;
-layout (location = 1) in vec3 aNormal;
-layout (location = 2) in vec2 aTextureCoord;
-
-out vec2 vTextureCoord;
-
-uniform mat4 uModel;
-uniform mat4 uView;
-uniform mat4 uProjection;
-
-void main()
-{
-    vTextureCoord = aTextureCoord;
-    gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
-}
diff --git a/resources/shaders/vertex/multi-color.vert b/resources/shaders/vertex/multi-color.vert
deleted file mode 100644
index 6d4da59..0000000
--- a/resources/shaders/vertex/multi-color.vert
+++ /dev/null
@@ -1,16 +0,0 @@
-#version 330
-layout(location = 0) in vec3 aPosition;
-layout(location = 1) in vec3 aColor;
-
-out vec4 vColor;
-
-uniform mat4 uModel;  // Model
-uniform mat4 uView; // View
-uniform mat4 uProjection;  // Projection
-
-void main()
-{
-  gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
-
-  vColor = vec4(aColor, 1.0f);
-}
diff --git a/resources/skybox/skybox.frag b/resources/skybox/skybox.frag
deleted file mode 100644
index de8ed1b..0000000
--- a/resources/skybox/skybox.frag
+++ /dev/null
@@ -1,9 +0,0 @@
-#version 330
-uniform samplerCube uTexture;
-
-varying vec3 vTexCoord;
-
-void main()
-{
-  gl_FragColor = texture(uTexture, vTexCoord);
-}
\ No newline at end of file
diff --git a/resources/skybox/skybox.vert b/resources/skybox/skybox.vert
deleted file mode 100644
index 28d9110..0000000
--- a/resources/skybox/skybox.vert
+++ /dev/null
@@ -1,15 +0,0 @@
-#version 330
-layout(location = 0) in vec3 aPosition;
-
-out vec3 vTexCoord;
-
-uniform mat4 uProjectionMatrix;
-uniform mat4 uViewMatrix;
-
-void main()
-{
-  // Strip translation column from camera's 4x4 matrix
-  mat4 view = mat4(mat3(uViewMatrix));
-  gl_Position = uProjectionMatrix * view * vec4(aPosition, 1.0);
-  vTexCoord = aPosition;
-}
\ No newline at end of file
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index c42ab1a..443a8a0 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -17,6 +17,8 @@ if (QTK_GUI_SCENE)
       qtkscene.cpp qtkscene.h
       main.cpp
   )
+
+  qt6_add_big_resources(QTK_GUI_SOURCES "${QTK_RESOURCES}/resources.qrc")
 else()
   # The scene will use a default skybox with no models or examples.
   # Models can be added by click-and-dragging an .obj into the scene.
@@ -24,6 +26,11 @@ else()
       qtkmainwindow.cpp qtkmainwindow.h qtkmainwindow.ui
       main.cpp
   )
+
+  qt6_add_big_resources(
+      QTK_GUI_SOURCES
+      "${QTK_RESOURCES}/minimal_resources.qrc"
+  )
 endif()
 
 qt_add_executable(qtk_gui ${QTK_GUI_SOURCES})
diff --git a/src/app/main.cpp b/src/app/main.cpp
index 58fbca7..11ce085 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -10,17 +10,9 @@
 
 #include "qtkmainwindow.h"
 
-#ifdef QTK_GUI_SCENE
-#include "qtkscene.h"
-using AppScene = QtkScene;
-#else
-using AppScene = EmptyScene;
-#endif
-
 int main(int argc, char * argv[])
 {
-  Q_INIT_RESOURCE(resources);
-
+  initResources();
   QApplication a(argc, argv);
 
   auto window = MainWindow::getMainWindow();
diff --git a/src/app/qtkmainwindow.h b/src/app/qtkmainwindow.h
index 781ad7e..42f0c1f 100644
--- a/src/app/qtkmainwindow.h
+++ b/src/app/qtkmainwindow.h
@@ -62,6 +62,27 @@ class EmptyScene : public Qtk::Scene
     }
 };
 
+/*
+ * Conditionally include the QtkScene header if the example is enabled.
+ * Set AppScene type to use in main() for creating the scene.
+ * Define helper function to initialize Qt resources for the application.
+ *    These resources are different based on if the example is enabled.
+ */
+#ifdef QTK_GUI_SCENE
+#include "qtkscene.h"
+using AppScene = QtkScene;
+inline void initResources()
+{
+  Q_INIT_RESOURCE(resources);
+}
+#else
+using AppScene = EmptyScene;
+inline void initResources()
+{
+  Q_INIT_RESOURCE(minimal_resources);
+}
+#endif
+
 /**
  * MainWindow class to provide an example of using a QtkWidget within a Qt
  * window application.
diff --git a/src/app/qtkscene.cpp b/src/app/qtkscene.cpp
index 9de11bb..527c226 100644
--- a/src/app/qtkscene.cpp
+++ b/src/app/qtkscene.cpp
@@ -42,8 +42,8 @@ void QtkScene::init()
   // Clone qtk-resources if it doesn't already exist.
   QDir repoDir("resources/");
   if (!repoDir.exists()) {
-    qDebug() << "Cloning qtk-resources repository to '"
-             << repoDir.absolutePath() << "'...";
+    qDebug() << "Cloning qtk-resources repository to " << repoDir.absolutePath()
+             << "...";
 
     // Run git clone
     QProcess gitProcess;
diff --git a/src/designer-plugins/toolbox.cpp b/src/designer-plugins/toolbox.cpp
index e03213a..b59b55e 100644
--- a/src/designer-plugins/toolbox.cpp
+++ b/src/designer-plugins/toolbox.cpp
@@ -130,13 +130,8 @@ void ToolBox::createPageShader(const Object * object)
 
   auto shaderView = new QTextEdit;
   shaderView->setReadOnly(true);
-  auto vertexFile = QFile(object->getVertexShader().c_str());
-  if (vertexFile.exists()) {
-    vertexFile.open(QIODeviceBase::ReadOnly);
-    shaderView->setText(vertexFile.readAll());
-    vertexFile.close();
-    mainLayout->addRow(shaderView);
-  }
+  shaderView->setText(object->getVertexShaderSourceCode().c_str());
+  mainLayout->addRow(shaderView);
 
   rowLayout = new QHBoxLayout;
   rowLayout->addWidget(new QLabel("Fragment Shader:"));
@@ -145,13 +140,8 @@ void ToolBox::createPageShader(const Object * object)
 
   shaderView = new QTextEdit;
   shaderView->setReadOnly(true);
-  auto fragmentfile = QFile(object->getFragmentShader().c_str());
-  if (fragmentfile.exists()) {
-    fragmentfile.open(QIODeviceBase::ReadOnly);
-    shaderView->setText(fragmentfile.readAll());
-    fragmentfile.close();
-    mainLayout->addRow(shaderView);
-  }
+  shaderView->setText(object->getFragmentShaderSourceCode().c_str());
+  mainLayout->addRow(shaderView);
 
   widget->setLayout(mainLayout);
 }
diff --git a/src/qtk/CMakeLists.txt b/src/qtk/CMakeLists.txt
index 25ca8bc..34c7cd3 100644
--- a/src/qtk/CMakeLists.txt
+++ b/src/qtk/CMakeLists.txt
@@ -24,6 +24,7 @@ set(
     skybox.h
     texture.h
     transform3D.h
+    shaders.h
 )
 
 set(
@@ -43,7 +44,6 @@ set(
     transform3D.cpp
 )
 
-qt6_add_big_resources(QTK_LIBRARY_SOURCES "${QTK_RESOURCES}/resources.qrc")
 qt_add_library(qtk STATIC EXCLUDE_FROM_ALL)
 target_sources(qtk PRIVATE ${QTK_LIBRARY_SOURCES})
 target_sources(
diff --git a/src/qtk/meshrenderer.cpp b/src/qtk/meshrenderer.cpp
index 04f1ea9..fd8cb12 100644
--- a/src/qtk/meshrenderer.cpp
+++ b/src/qtk/meshrenderer.cpp
@@ -10,6 +10,7 @@
 
 #include "meshrenderer.h"
 #include "scene.h"
+#include "shaders.h"
 #include "texture.h"
 
 using namespace Qtk;
@@ -35,8 +36,7 @@ MeshRenderer::MeshRenderer(const char * name) :
 }
 
 MeshRenderer::MeshRenderer(const char * name, const ShapeBase & shape) :
-    Object(name, shape, QTK_MESH), mVertexShader(":/shaders/multi-color.vert"),
-    mFragmentShader(":/shaders/multi-color.frag"), mDrawType(GL_TRIANGLES)
+    Object(name, shape, QTK_MESH), mDrawType(GL_TRIANGLES)
 {
   mShape = Shape(shape);
   init();
@@ -68,10 +68,22 @@ void MeshRenderer::init()
   mVAO.bind();
 
   mProgram.create();
-  mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
-                                   mVertexShader.c_str());
-  mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
-                                   mFragmentShader.c_str());
+  // If no shader is provided, use a default one.
+  if (mVertexShader.empty()) {
+    mProgram.addShaderFromSourceCode(QOpenGLShader::Vertex,
+                                     QTK_SHADER_VERTEX_MESH);
+  } else {
+    mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
+                                     mVertexShader.c_str());
+  }
+
+  if (mFragmentShader.empty()) {
+    mProgram.addShaderFromSourceCode(QOpenGLShader::Fragment,
+                                     QTK_SHADER_FRAGMENT_MESH);
+  } else {
+    mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
+                                     mFragmentShader.c_str());
+  }
   mProgram.link();
   mProgram.bind();
 
diff --git a/src/qtk/model.h b/src/qtk/model.h
index d87f1cf..ee1ed1d 100644
--- a/src/qtk/model.h
+++ b/src/qtk/model.h
@@ -55,8 +55,8 @@ namespace Qtk
        */
       inline Model(const char * name,
                    const char * path,
-                   const char * vertexShader = ":/shaders/model-basic.vert",
-                   const char * fragmentShader = ":/shaders/model-basic.frag") :
+                   const char * vertexShader = "",
+                   const char * fragmentShader = "") :
           Object(name, QTK_MODEL), mModelPath(path),
           mVertexShader(vertexShader), mFragmentShader(fragmentShader)
       {
diff --git a/src/qtk/modelmesh.cpp b/src/qtk/modelmesh.cpp
index 51e71a5..d7aa0cb 100644
--- a/src/qtk/modelmesh.cpp
+++ b/src/qtk/modelmesh.cpp
@@ -8,6 +8,7 @@
 
 #include "modelmesh.h"
 #include "scene.h"
+#include "shaders.h"
 
 using namespace Qtk;
 
@@ -73,12 +74,12 @@ void ModelMesh::draw(QOpenGLShaderProgram & shader)
  * Private Member Functions
  ******************************************************************************/
 
-void ModelMesh::initMesh(const char * vert, const char * frag)
+void ModelMesh::initMesh(const std::string & vert, const std::string & frag)
 {
   initializeOpenGLFunctions();
 
   // Create VAO, VBO, EBO
-  bool status = mVAO->create();
+  mVAO->create();
   mVBO->create();
   mEBO->create();
 
@@ -97,10 +98,26 @@ void ModelMesh::initMesh(const char * vert, const char * frag)
   mEBO->release();
 
   // Load and link shaders
-  mProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vert);
-  mProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, frag);
-  mProgram->link();
-  mProgram->bind();
+  if (!vert.empty()) {
+    mProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vert.c_str());
+  } else {
+    mProgram->addShaderFromSourceCode(QOpenGLShader::Vertex,
+                                      QTK_SHADER_VERTEX_MODEL);
+  }
+
+  if (!frag.empty()) {
+    mProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, frag.c_str());
+  } else {
+    mProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
+                                      QTK_SHADER_FRAGMENT_MODEL);
+  }
+
+  if (!mProgram->link()) {
+    qDebug() << "Failed to link shader: " << mProgram->log();
+  }
+  if (!mProgram->bind()) {
+    qDebug() << "Failed to bind shader: " << mProgram->log();
+  }
 
   // Positions
   mProgram->enableAttributeArray(0);
diff --git a/src/qtk/modelmesh.h b/src/qtk/modelmesh.h
index e2dc5b5..eda2979 100644
--- a/src/qtk/modelmesh.h
+++ b/src/qtk/modelmesh.h
@@ -97,8 +97,8 @@ namespace Qtk
       ModelMesh(Vertices vertices,
                 Indices indices,
                 Textures textures,
-                const char * vertexShader = ":/model-basic.vert",
-                const char * fragmentShader = ":/model-basic.frag") :
+                const char * vertexShader = "",
+                const char * fragmentShader = "") :
           mProgram(new QOpenGLShaderProgram),
           mVAO(new QOpenGLVertexArrayObject),
           mVBO(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)),
@@ -146,7 +146,7 @@ namespace Qtk
        * @param vert Path to vertex shader to use for this model.
        * @param frag Path to fragment shader to use for this model.
        */
-      void initMesh(const char * vert, const char * frag);
+      void initMesh(const std::string & vert, const std::string & frag);
 
       /*************************************************************************
        * Private Members
diff --git a/src/qtk/object.cpp b/src/qtk/object.cpp
index 7c9993e..0510477 100644
--- a/src/qtk/object.cpp
+++ b/src/qtk/object.cpp
@@ -9,3 +9,15 @@
 #include "object.h"
 
 using namespace Qtk;
+
+std::string Object::getShaderSourceCode(
+    QOpenGLShader::ShaderType shader_type) const
+{
+  for (const auto & shader : mProgram.shaders()) {
+    if (shader->shaderType() == shader_type) {
+      return shader->sourceCode().toStdString();
+    }
+  }
+  qDebug() << "Failed to find shader of type " << shader_type;
+  return "";
+}
diff --git a/src/qtk/object.h b/src/qtk/object.h
index 2046bd7..342c3e6 100644
--- a/src/qtk/object.h
+++ b/src/qtk/object.h
@@ -51,7 +51,6 @@ namespace Qtk
           mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mBound(false),
           mType(type)
       {
-        initResources();
         setObjectName(name);
       }
 
@@ -60,7 +59,6 @@ namespace Qtk
           mName(name), mVBO(QOpenGLBuffer::VertexBuffer), mShape(shape),
           mBound(false), mType(type)
       {
-        initResources();
         setObjectName(name);
       }
 
@@ -126,6 +124,21 @@ namespace Qtk
         return "Base Object has no fragment shader.";
       }
 
+      [[nodiscard]] virtual std::string getShaderSourceCode(
+          QOpenGLShader::ShaderType shader_type) const;
+
+
+      [[nodiscard]] virtual inline std::string getVertexShaderSourceCode() const
+      {
+        return getShaderSourceCode(QOpenGLShader::Vertex);
+      }
+
+      [[nodiscard]] virtual inline std::string getFragmentShaderSourceCode()
+          const
+      {
+        return getShaderSourceCode(QOpenGLShader::Fragment);
+      }
+
       /*************************************************************************
        * Setters
        ************************************************************************/
diff --git a/src/qtk/qtkapi.h b/src/qtk/qtkapi.h
index 82ab817..9464720 100644
--- a/src/qtk/qtkapi.h
+++ b/src/qtk/qtkapi.h
@@ -22,16 +22,6 @@
 #define QTKAPI
 #endif
 
-/**
- * Initialize Qt resources required by the Qtk library.
- * This cannot be defined within any namespace, but can be called by ctors.
- * See object.h for example.
- */
-inline void initResources()
-{
-  Q_INIT_RESOURCE(resources);
-}
-
 namespace Qtk
 {
   /**
diff --git a/src/qtk/shaders.h b/src/qtk/shaders.h
new file mode 100644
index 0000000..2dc6ff4
--- /dev/null
+++ b/src/qtk/shaders.h
@@ -0,0 +1,120 @@
+/*##############################################################################
+## Author: Shaun Reed                                                         ##
+## Legal: All Content (c) 2025 Shaun Reed, all rights reserved                ##
+## About: Default GLSL shaders to use for objects if no shader if provided.   ##
+##                                                                            ##
+## Contact: shaunrd0@gmail.com  | URL: www.shaunreed.com | GitHub: shaunrd0   ##
+##############################################################################*/
+#ifndef QTK_SHADERS_H
+#define QTK_SHADERS_H
+
+//
+// Model
+
+#define QTK_SHADER_VERTEX_MODEL \
+  R"(
+#version 330 core
+layout (location = 0) in vec3 aPosition;
+layout (location = 1) in vec3 aNormal;
+layout (location = 2) in vec2 aTextureCoord;
+
+out vec2 vTextureCoord;
+
+uniform mat4 uModel;
+uniform mat4 uView;
+uniform mat4 uProjection;
+
+void main()
+{
+    vTextureCoord = aTextureCoord;
+    gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
+}
+)"
+
+#define QTK_SHADER_FRAGMENT_MODEL \
+  R"(
+#version 330 core
+out vec4 fColor;
+
+in vec2 vTextureCoord;
+
+uniform sampler2D texture_diffuse1;
+
+void main()
+{
+    fColor = texture(texture_diffuse1, vTextureCoord);
+}
+)"
+
+//
+// MeshRenderer
+
+#define QTK_SHADER_VERTEX_MESH \
+  R"(
+#version 330
+layout(location = 0) in vec3 aPosition;
+layout(location = 1) in vec3 aColor;
+
+out vec4 vColor;
+
+uniform mat4 uModel;  // Model
+uniform mat4 uView; // View
+uniform mat4 uProjection;  // Projection
+
+void main()
+{
+  gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0);
+
+  vColor = vec4(aColor, 1.0f);
+}
+)"
+
+#define QTK_SHADER_FRAGMENT_MESH \
+  R"(
+#version 330
+in vec4 vColor;
+
+out vec4 fColor;
+
+void main()
+{
+  fColor = vColor;
+}
+)"
+
+//
+// Skybox
+
+#define QTK_SHADER_VERTEX_SKYBOX \
+  R"(
+#version 330
+layout(location = 0) in vec3 aPosition;
+
+out vec3 vTexCoord;
+
+uniform mat4 uProjectionMatrix;
+uniform mat4 uViewMatrix;
+
+void main()
+{
+  // Strip translation column from camera's 4x4 matrix
+  mat4 view = mat4(mat3(uViewMatrix));
+  gl_Position = uProjectionMatrix * view * vec4(aPosition, 1.0);
+  vTexCoord = aPosition;
+}
+)"
+
+#define QTK_SHADER_FRAGMENT_SKYBOX \
+  R"(
+#version 330
+uniform samplerCube uTexture;
+
+varying vec3 vTexCoord;
+
+void main()
+{
+  gl_FragColor = texture(uTexture, vTexCoord);
+}
+)"
+
+#endif  // QTK_SHADERS_H
diff --git a/src/qtk/skybox.cpp b/src/qtk/skybox.cpp
index c02323e..78b4eee 100644
--- a/src/qtk/skybox.cpp
+++ b/src/qtk/skybox.cpp
@@ -8,6 +8,7 @@
 
 #include "skybox.h"
 #include "scene.h"
+#include "shaders.h"
 #include "texture.h"
 
 using namespace Qtk;
@@ -80,10 +81,10 @@ void Skybox::init()
 
   // Set up shader program
   mProgram.create();
-  mProgram.addShaderFromSourceFile(QOpenGLShader::Vertex,
-                                   ":/shaders/skybox.vert");
-  mProgram.addShaderFromSourceFile(QOpenGLShader::Fragment,
-                                   ":/shaders/skybox.frag");
+  mProgram.addShaderFromSourceCode(QOpenGLShader::Fragment,
+                                   QTK_SHADER_FRAGMENT_SKYBOX);
+  mProgram.addShaderFromSourceCode(QOpenGLShader::Vertex,
+                                   QTK_SHADER_VERTEX_SKYBOX);
   mProgram.link();
   mProgram.bind();