Fixed Renderer and added Light Components

This commit is contained in:
2025-08-22 15:01:06 -05:00
parent 9f29fe5c08
commit c670517ab9
10 changed files with 658 additions and 237 deletions

View File

@@ -104,6 +104,8 @@ add_library(Core STATIC
src/core/systems/assets/MeshLoaderObj.h
src/core/systems/Scene/Components/CameraComponent.cpp
src/core/systems/Scene/Components/CameraComponent.h
src/core/systems/Scene/Components/PointLightComponent.cpp
src/core/systems/Scene/Components/PointLightComponent.h
)
target_include_directories(Core PUBLIC src/core)

View File

@@ -83,9 +83,9 @@ namespace OX
{
OX_PROFILE_FUNCTION();
renderer.Begin(GetScene());
renderer.RenderSceneDebugNormals(GetScene(),
0.2f, 1.5f);
if (renderer.Begin(GetScene())) {
renderer.RenderSceneShaded(GetScene());
}
renderer.End();
for (auto &layer: m_layers)

View File

@@ -1,57 +1,47 @@
#include "renderer/Renderer.h"
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <vector>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/compatibility.hpp>
#include "systems/Scene/Scene.h"
#include "systems/Scene/GameObject.h"
#include "systems/Scene/Components/TransformComponent.h"
#include "systems/Scene/Components/MeshComponent.h"
#include "systems/Scene/Components/CameraComponent.h"
#include "systems/Scene/Components/PointLightComponent.h"
namespace OX
{
// If your API names differ, change these two helpers.
// ─────────────────────────────────────────────────────────────────────────────
// Small helpers
// 1) Iterate roots
static inline const std::vector<GameObject *> &SceneRoots(const Scene &s)
{
// EXPECTED: Scene::GetRootObjects() -> const std::vector<GameObject*>&
return s.roots(); // ← change if your API differs
}
// 2) Iterate children
static inline const std::vector<GameObject *> &Children(GameObject *go)
{
// EXPECTED: GameObject::children() -> const std::vector<GameObject*>&
return go->children(); // ← change if your API differs
}
// Accessors for vertex (supports .position/.normal or .pos/.norm)
// Expect vertex layout: pos(3) + normal(3) tightly packed. Avoids field names.
template<class V>
static inline glm::vec3 VPos(const V &v)
inline void ExtractPN(const V &v, glm::vec3 &p, glm::vec3 &n)
{
if constexpr (requires { v.position; }) return glm::vec3(v.position);
else if constexpr (requires { v.pos; }) return glm::vec3(v.pos);
else return glm::vec3(0.0f);
const float *f = reinterpret_cast<const float *>(&v);
p = glm::vec3(f[0], f[1], f[2]);
n = glm::normalize(glm::vec3(f[3], f[4], f[5]));
if (!glm::all(glm::isfinite(n))) n = glm::vec3(0, 1, 0);
}
template<class V>
static inline glm::vec3 VNorm(const V &v)
{
if constexpr (requires { v.normal; }) return glm::vec3(v.normal);
else if constexpr (requires { v.norm; }) return glm::vec3(v.norm);
else return glm::vec3(0, 1, 0);
}
// ── lifecycle ─────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// Lifecycle
Renderer::~Renderer()
{
if (m_lineVBO)
glDeleteBuffers(1, &m_lineVBO);
if (m_lineVAO)
glDeleteVertexArrays(1, &m_lineVAO);
if (m_meshEBO)
glDeleteBuffers(1, &m_meshEBO);
if (m_meshVBO)
glDeleteBuffers(1, &m_meshVBO);
if (m_meshVAO)
glDeleteVertexArrays(1, &m_meshVAO);
if (m_depthRBO)
glDeleteRenderbuffers(1, &m_depthRBO);
@@ -63,7 +53,7 @@ namespace OX
void Renderer::Init(int targetWidth, int targetHeight)
{
CreateFramebuffer(targetWidth, targetHeight);
EnsureLinePipeline();
EnsureMeshPipeline();
}
void Renderer::ResizeTarget(int width, int height)
@@ -84,8 +74,7 @@ namespace OX
glGenTextures(1, &m_colorTex);
glBindTexture(GL_TEXTURE_2D, m_colorTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@@ -95,14 +84,13 @@ namespace OX
glGenFramebuffers(1, &m_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, m_colorTex, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, m_depthRBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTex, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthRBO);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
// ── camera & begin/end ────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// Scene traversal / camera lookup
static void FindPrimaryCamera(const Scene &scene,
const GameObject *&camObj,
@@ -111,183 +99,220 @@ namespace OX
camObj = nullptr;
cam = nullptr;
// Pass 1: primary
for (auto *r: SceneRoots(scene)) {
std::vector<GameObject *> stack{r};
while (!stack.empty()) {
GameObject *n = stack.back();
stack.pop_back();
if (auto *c = n->getComponent<CameraComponent>(); c && c->isPrimary()) {
camObj = n;
cam = c;
return;
auto tryFind = [&](bool requirePrimary)
{
const auto &roots = scene.roots();
for (const auto &rup: roots) {
std::vector<GameObject *> stack{rup.get()};
while (!stack.empty()) {
GameObject *n = stack.back();
stack.pop_back();
if (auto *c = n->getComponent<CameraComponent>()) {
if (!requirePrimary || c->isPrimary()) {
camObj = n;
cam = c;
return true;
}
}
for (const auto &cup: n->children())
stack.push_back(cup.get());
}
for (auto *ch: Children(n)) stack.push_back(ch);
}
}
// Pass 2: first camera
for (auto *r: SceneRoots(scene)) {
std::vector<GameObject *> stack{r};
while (!stack.empty()) {
GameObject *n = stack.back();
stack.pop_back();
if (auto *c = n->getComponent<CameraComponent>()) {
camObj = n;
cam = c;
return;
}
for (auto *ch: Children(n)) stack.push_back(ch);
}
}
return false;
};
if (tryFind(true)) return;
(void) tryFind(false);
}
bool Renderer::BeginScene(const Scene &scene)
static bool ComputeSceneWorldAABB(const Scene &scene,
glm::vec3 &outMin, glm::vec3 &outMax,
glm::mat4 (*BuildModel)(const TransformComponent *))
{
bool any = false;
glm::vec3 wsMin(std::numeric_limits<float>::max());
glm::vec3 wsMax(-std::numeric_limits<float>::max());
const auto &roots = scene.roots();
for (const auto &rup: roots) {
std::vector<GameObject *> stack{rup.get()};
while (!stack.empty()) {
GameObject *go = stack.back();
stack.pop_back();
auto *tr = go->getComponent<TransformComponent>();
auto *mesh = go->getComponent<MeshComponent>();
if (tr && mesh) {
const auto &vin = mesh->vertices();
if (!vin.empty()) {
const glm::mat4 M = BuildModel(tr);
for (const auto &v: vin) {
glm::vec3 lp, ln;
ExtractPN(v, lp, ln);
glm::vec3 wp = glm::vec3(M * glm::vec4(lp, 1.0f));
wsMin = glm::min(wsMin, wp);
wsMax = glm::max(wsMax, wp);
}
any = true;
}
}
for (const auto &cup: go->children())
stack.push_back(cup.get());
}
}
if (!any) return false;
outMin = wsMin;
outMax = wsMax;
return true;
}
static void BuildFramingCameraFromAABB(const glm::vec3 &wsMin, const glm::vec3 &wsMax,
float aspect, glm::mat4 &outV, glm::mat4 &outP)
{
const glm::vec3 center = 0.5f * (wsMin + wsMax);
const glm::vec3 ext = 0.5f * (wsMax - wsMin);
const float radius = glm::length(ext);
const float fovYDeg = 60.0f;
const float fovY = glm::radians(fovYDeg);
const float fovX = 2.0f * std::atan(std::tan(fovY * 0.5f) * aspect);
const float dVert = radius / std::max(0.1f, std::sin(fovY * 0.5f));
const float dHorz = radius / std::max(0.1f, std::sin(fovX * 0.5f));
const float dist = 1.1f * std::max(dVert, dHorz);
const glm::vec3 eye = center + glm::vec3(0, 0, dist);
outV = glm::lookAt(eye, center, glm::vec3(0, 1, 0));
float nearZ = std::max(0.01f, dist - radius * 2.0f);
float farZ = (dist + radius * 2.0f);
if (farZ <= nearZ + 0.01f) farZ = nearZ + 0.02f;
outP = glm::perspective(fovY, std::max(0.001f, aspect), nearZ, farZ);
}
// ─────────────────────────────────────────────────────────────────────────────
// Begin / End
bool Renderer::Begin(const Scene &scene)
{
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glViewport(0, 0, m_size.x, m_size.y);
const float aspect = (m_size.y > 0) ? float(m_size.x) / float(m_size.y) : 1.0f;
const GameObject *camObj = nullptr;
const CameraComponent *cam = nullptr;
FindPrimaryCamera(scene, camObj, cam);
if (!camObj || !cam) {
// No camera — magenta so it's obvious
glDisable(GL_DEPTH_TEST);
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_viewProj = glm::mat4(1.0f);
return false;
glm::mat4 V(1), P(1);
if (camObj && cam) {
if (const auto *camTr = camObj->getComponent<TransformComponent>()) {
P = BuildProjectionMatrix(cam, aspect);
V = BuildViewMatrix(camTr);
} else {
camObj = nullptr; // fallback
}
}
const auto *tr = camObj->getComponent<TransformComponent>();
if (!tr) {
glDisable(GL_DEPTH_TEST);
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_viewProj = glm::mat4(1.0f);
return false;
if (!camObj) {
glm::vec3 wsMin, wsMax;
if (ComputeSceneWorldAABB(scene, wsMin, wsMax, &Renderer::BuildModelMatrix)) {
BuildFramingCameraFromAABB(wsMin, wsMax, aspect, V, P);
} else {
glDisable(GL_DEPTH_TEST);
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_viewProj = glm::mat4(1.0f);
m_cameraPosWS = glm::vec3(0);
m_frameLights.clear();
return false;
}
}
const float aspect = (m_size.y > 0) ? (float) m_size.x / (float) m_size.y : 1.0f;
const glm::mat4 P = BuildProjectionMatrix(cam, aspect);
const glm::mat4 V = BuildViewMatrix(tr);
m_viewProj = P * V;
// Camera world position from inverse view
glm::mat4 invV = glm::inverse(V);
m_cameraPosWS = glm::vec3(invV[3]);
glEnable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glClearColor(0.09f, 0.09f, 0.10f, 1.0f);
glEnable(GL_CULL_FACE);
glClearColor(0.10f, 0.10f, 0.11f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
EnsureLinePipeline();
// Gather lights for this frame (after we know camera)
GatherPointLights(scene);
EnsureMeshPipeline();
return true;
}
void Renderer::EndScene()
void Renderer::End()
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
// ── draw-list collect/sort/draw ───────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// Draw list + render
void Renderer::CollectDrawItemsRecursive(GameObject *go,
const glm::mat4 &view,
std::vector<DrawItem> &out)
void Renderer::CollectDrawList(const Scene &scene, std::vector<DrawItem> &out, glm::mat4 &outView) const
{
auto *tr = go->getComponent<TransformComponent>();
auto *mesh = go->getComponent<MeshComponent>();
if (tr && mesh) {
const glm::mat4 M = BuildModelMatrix(tr);
const glm::vec3 wsO = glm::vec3(M * glm::vec4(0, 0, 0, 1));
const glm::vec3 vsO = glm::vec3(view * glm::vec4(wsO, 1));
DrawItem di;
di.go = go;
di.tr = tr;
di.mesh = mesh;
di.depth = vsO.z;
out.push_back(di);
}
for (auto *ch: Children(go))
CollectDrawItemsRecursive(ch, view, out);
}
void Renderer::RenderSceneDebugNormals(const Scene &scene,
float normalScale,
float lineWidth)
{
// Build draw list
std::vector<DrawItem> list;
list.reserve(256);
// Need current V from cached VP; reconstruct by inverting a dummy P?
// Easier: recompute V from camera again (cheap) to get view-space depth.
// (Optional) compute a view matrix to sort by view-space depth if a camera exists
const GameObject *camObj = nullptr;
const CameraComponent *cam = nullptr;
FindPrimaryCamera(scene, camObj, cam);
if (!camObj || !cam) {
// show a small cross to indicate pipeline working
EnsureLinePipeline();
m_lineShader.Use();
m_lineShader.SetMat4("u_VP", m_viewProj);
m_lineShader.SetVec3("u_Color", {0.9f, 0.75f, 0.3f});
const glm::vec3 cross[] = {
{-0.25f, 0, 0}, {+0.25f, 0, 0},
{0, -0.25f, 0}, {0, +0.25f, 0}
};
glBindVertexArray(m_lineVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_lineVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cross), cross, GL_STREAM_DRAW);
glLineWidth(lineWidth);
glDrawArrays(GL_LINES, 0, 4);
glBindVertexArray(0);
return;
outView = glm::mat4(1.0f);
if (camObj && cam) {
if (const auto *camTr = camObj->getComponent<TransformComponent>())
outView = BuildViewMatrix(camTr);
}
const auto *camTr = camObj->getComponent<TransformComponent>();
const glm::mat4 V = BuildViewMatrix(camTr);
for (auto *r: SceneRoots(scene))
CollectDrawItemsRecursive(r, V, list);
const auto &roots = scene.roots();
for (const auto &rup: roots) {
std::vector<GameObject *> stack{rup.get()};
while (!stack.empty()) {
GameObject *go = stack.back();
stack.pop_back();
// Sort: front-to-back (smaller view-space Z first)
std::stable_sort(list.begin(), list.end(),
[](const DrawItem &a, const DrawItem &b) { return a.depth < b.depth; });
auto *tr = go->getComponent<TransformComponent>();
auto *mesh = go->getComponent<MeshComponent>();
if (tr && mesh) {
const glm::mat4 M = BuildModelMatrix(tr);
const glm::vec3 ws = glm::vec3(M * glm::vec4(0, 0, 0, 1));
const glm::vec3 vs = glm::vec3(outView * glm::vec4(ws, 1));
out.push_back(DrawItem{go, tr, mesh, vs.z});
}
// Build one big line buffer and draw
std::vector<glm::vec3> lines;
lines.reserve(8192);
for (const DrawItem &di: list) {
const auto &verts = di.mesh->vertices();
if (verts.empty()) continue;
const glm::mat4 M = BuildModelMatrix(di.tr);
const glm::mat3 N = glm::mat3(M);
for (const auto &v: verts) {
const glm::vec3 p = glm::vec3(M * glm::vec4(VPos(v), 1.f));
const glm::vec3 n = glm::normalize(N * VNorm(v));
lines.push_back(p);
lines.push_back(p + n * normalScale);
for (const auto &cup: go->children())
stack.push_back(cup.get());
}
}
EnsureLinePipeline();
m_lineShader.Use();
m_lineShader.SetMat4("u_VP", m_viewProj);
m_lineShader.SetVec3("u_Color", {0.9f, 0.75f, 0.3f});
glBindVertexArray(m_lineVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_lineVBO);
glBufferData(GL_ARRAY_BUFFER,
lines.size() * sizeof(glm::vec3),
lines.data(),
GL_STREAM_DRAW);
glLineWidth(lineWidth);
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lines.size()));
glBindVertexArray(0);
std::stable_sort(out.begin(), out.end(),
[](const DrawItem &a, const DrawItem &b) { return a.depth < b.depth; });
}
// ── math helpers ──────────────────────────────────────────────────────────────
void Renderer::RenderSceneShaded(const Scene &scene)
{
std::vector<DrawItem> list;
list.reserve(256);
glm::mat4 V;
CollectDrawList(scene, list, V);
// Set lighting uniforms once per frame
m_meshShader.Use();
UploadFrameLightsUniforms();
for (const DrawItem &di: list)
DrawMeshLambert(di, m_viewProj);
}
// ─────────────────────────────────────────────────────────────────────────────
// Math helpers
glm::mat4 Renderer::BuildModelMatrix(const TransformComponent *tr)
{
@@ -320,37 +345,241 @@ namespace OX
}
}
// ── line pipeline ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// Lights: gather + upload
void Renderer::EnsureLinePipeline()
void Renderer::GatherPointLights(const Scene &scene)
{
if (!m_lineShaderBuilt) {
m_frameLights.clear();
constexpr int MAX_LIGHTS = 32; // keep below typical uniform limits
const auto &roots = scene.roots();
for (const auto &rup: roots) {
std::vector<GameObject *> stack{rup.get()};
while (!stack.empty()) {
GameObject *go = stack.back();
stack.pop_back();
if (auto *lc = go->getComponent<PointLightComponent>()) {
if (lc->enabled() && lc->intensity() > 0.0f && lc->range() > 0.01f) {
if (auto *tr = go->getComponent<TransformComponent>()) {
PointLightGPU L;
auto p = tr->getPosition();
L.positionWS = {p[0], p[1], p[2]};
L.range = lc->range();
L.color = lc->color();
L.intensity = lc->intensity();
m_frameLights.push_back(L);
if ((int) m_frameLights.size() >= MAX_LIGHTS) goto done; // stop early
}
}
}
for (const auto &cup: go->children())
stack.push_back(cup.get());
}
}
done:;
}
void Renderer::UploadFrameLightsUniforms()
{
// Program already bound by m_meshShader.Use()
GLint prog = 0;
glGetIntegerv(GL_CURRENT_PROGRAM, &prog);
if (prog == 0) return;
const int count = (int) m_frameLights.size();
// Scalar uniforms
if (auto loc = glGetUniformLocation(prog, "u_LightCount"); loc >= 0)
glUniform1i(loc, count);
if (auto loc = glGetUniformLocation(prog, "u_Ambient"); loc >= 0)
glUniform3f(loc, m_ambient.x, m_ambient.y, m_ambient.z);
if (auto loc = glGetUniformLocation(prog, "u_SpecColor"); loc >= 0)
glUniform3f(loc, m_specColor.x, m_specColor.y, m_specColor.z);
if (auto loc = glGetUniformLocation(prog, "u_Shininess"); loc >= 0)
glUniform1f(loc, m_shininess);
if (auto loc = glGetUniformLocation(prog, "u_CameraPosWS"); loc >= 0)
glUniform3f(loc, m_cameraPosWS.x, m_cameraPosWS.y, m_cameraPosWS.z);
if (count == 0) return;
// Pack arrays
std::vector<glm::vec3> pos(count), col(count);
std::vector<float> range(count), intensity(count);
for (int i = 0; i < count; i++) {
pos[i] = m_frameLights[i].positionWS;
col[i] = m_frameLights[i].color;
range[i] = m_frameLights[i].range;
intensity[i] = m_frameLights[i].intensity;
}
// Upload arrays (query [0] base)
if (auto loc = glGetUniformLocation(prog, "u_LightPos[0]"); loc >= 0)
glUniform3fv(loc, count, &pos[0].x);
if (auto loc = glGetUniformLocation(prog, "u_LightColor[0]"); loc >= 0)
glUniform3fv(loc, count, &col[0].x);
if (auto loc = glGetUniformLocation(prog, "u_LightRange[0]"); loc >= 0)
glUniform1fv(loc, count, range.data());
if (auto loc = glGetUniformLocation(prog, "u_LightIntensity[0]"); loc >= 0)
glUniform1fv(loc, count, intensity.data());
}
// ─────────────────────────────────────────────────────────────────────────────
// Mesh pipeline
void Renderer::EnsureMeshPipeline()
{
if (!m_meshShaderReady) {
// MAX_LIGHTS must match UploadFrameLightsUniforms() assumptions
const char *vs = R"GLSL(
#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;
uniform mat4 u_M;
uniform mat4 u_VP;
void main(){ gl_Position = u_VP * vec4(aPos,1.0); }
uniform mat3 u_N;
out vec3 vNormalWS;
out vec3 vPosWS;
void main(){
vec4 ws = u_M * vec4(aPos, 1.0);
vPosWS = ws.xyz;
vNormalWS = normalize(u_N * aNormal);
gl_Position = u_VP * ws;
}
)GLSL";
const char *fs = R"GLSL(
#version 330 core
#define MAX_LIGHTS 32
in vec3 vNormalWS;
in vec3 vPosWS;
out vec4 FragColor;
uniform vec3 u_Color;
void main(){ FragColor = vec4(u_Color,1.0); }
// Per-object
uniform vec3 u_BaseColor;
// Per-frame
uniform int u_LightCount;
uniform vec3 u_LightPos[MAX_LIGHTS];
uniform vec3 u_LightColor[MAX_LIGHTS];
uniform float u_LightIntensity[MAX_LIGHTS];
uniform float u_LightRange[MAX_LIGHTS];
uniform vec3 u_Ambient;
uniform vec3 u_SpecColor;
uniform float u_Shininess;
uniform vec3 u_CameraPosWS;
// Smooth inverse-square-ish attenuation with soft edge at range
float smoothAtten(float dist, float range)
{
// 0..1 smooth factor (1 near, 0 at range)
float x = clamp(1.0 - dist / max(range, 1e-4), 0.0, 1.0);
// ease-in curve (square)
float fall = x * x;
// add mild inverse-square to keep highlights crisp
float inv2 = 1.0 / (1.0 + dist*dist);
return fall * inv2;
}
void main(){
vec3 N = normalize(vNormalWS);
vec3 V = normalize(u_CameraPosWS - vPosWS);
vec3 color = u_Ambient * u_BaseColor;
vec3 spec = vec3(0.0);
for (int i=0; i<u_LightCount && i<MAX_LIGHTS; ++i) {
vec3 Lvec = u_LightPos[i] - vPosWS;
float d = length(Lvec);
if (d <= 0.0001) continue;
vec3 L = Lvec / d;
float ndl = max(dot(N, L), 0.0);
if (ndl <= 0.0) continue;
float att = smoothAtten(d, u_LightRange[i]);
vec3 diff = u_LightColor[i] * (u_LightIntensity[i] * ndl * att);
// Blinn-Phong specular
vec3 H = normalize(L + V);
float ndh = max(dot(N, H), 0.0);
float sp = pow(ndh, max(u_Shininess, 1.0)) * att;
spec += u_SpecColor * sp;
color += diff * u_BaseColor;
}
vec3 outCol = color + spec;
FragColor = vec4(outCol, 1.0);
}
)GLSL";
m_lineShader.SetName("DebugLineShader");
m_lineShader.LoadFromSource(vs, fs);
m_lineShaderBuilt = true;
m_meshShader.SetName("PointLitMesh");
m_meshShader.LoadFromSource(vs, fs);
m_meshShaderReady = true;
}
if (m_lineVAO == 0) {
glGenVertexArrays(1, &m_lineVAO);
glGenBuffers(1, &m_lineVBO);
glBindVertexArray(m_lineVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_lineVBO);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void *) 0);
if (m_meshVAO == 0) {
glGenVertexArrays(1, &m_meshVAO);
glGenBuffers(1, &m_meshVBO);
glGenBuffers(1, &m_meshEBO);
glBindVertexArray(m_meshVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_meshVBO);
glEnableVertexAttribArray(0); // pos
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void *) 0);
glEnableVertexAttribArray(1); // normal
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void *) (sizeof(float) * 3));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_meshEBO);
glBindVertexArray(0);
}
}
void Renderer::DrawMeshLambert(const DrawItem &di, const glm::mat4 &VP)
{
const auto &vin = di.mesh->vertices();
const auto &idx = di.mesh->indices();
if (vin.empty() || idx.empty()) return;
// Interleave [px,py,pz, nx,ny,nz]
std::vector<float> packed;
packed.reserve(vin.size() * 6);
for (const auto &v: vin) {
glm::vec3 p, n;
ExtractPN(v, p, n);
packed.insert(packed.end(), {p.x, p.y, p.z, n.x, n.y, n.z});
}
std::vector<uint32_t> indices;
indices.reserve(idx.size());
for (auto i: idx) indices.push_back(static_cast<uint32_t>(i));
const glm::mat4 M = BuildModelMatrix(di.tr);
const glm::mat3 N = glm::mat3(glm::transpose(glm::inverse(M)));
m_meshShader.Use();
m_meshShader.SetMat4("u_M", M);
m_meshShader.SetMat4("u_VP", VP);
m_meshShader.SetMat3("u_N", N);
m_meshShader.SetVec3("u_BaseColor", glm::vec3(0.82f, 0.82f, 0.84f)); // light gray by default
glBindVertexArray(m_meshVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_meshVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * packed.size(), packed.data(), GL_STREAM_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_meshEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * indices.size(), indices.data(), GL_STREAM_DRAW);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
} // namespace OX

View File

@@ -1,12 +1,8 @@
#pragma once
#include <glm/glm.hpp>
#include <GL/glew.h>
#include <vector>
#include <glm/glm.hpp>
#include "types/vec2.h"
#include "systems/Shader.h"
#include "types/vec2.h" // your Vec2i
// fwd
namespace OX
{
class Scene;
@@ -15,6 +11,31 @@ namespace OX
class TransformComponent;
class CameraComponent;
class Shader; // your wrapper
}
#include "systems/Shader.h"
#include <GL/glew.h>
namespace OX
{
struct DrawItem
{
GameObject *go{};
TransformComponent *tr{};
MeshComponent *mesh{};
float depth{};
};
struct PointLightGPU
{
glm::vec3 positionWS{0.0f};
float range{10.0f};
glm::vec3 color{1.0f};
float intensity{1.0f};
};
class Renderer
{
public:
@@ -24,19 +45,13 @@ namespace OX
void ResizeTarget(int width, int height);
// New simple begin/end that bind the FBO and cache VP
bool BeginScene(const Scene &scene);
// Begin locates camera (or auto-frames AABB) and binds FBO
bool Begin(const Scene &scene);
void EndScene();
void End();
// Back-compat (Core.cpp can keep using these)
bool Begin(const Scene &scene) { return BeginScene(scene); }
void End() { EndScene(); }
// Build vector (ordered) and draw (normals for now)
void RenderSceneDebugNormals(const Scene &scene,
float normalScale = 0.1f,
float lineWidth = 1.5f);
// Build draw list (front-to-back) and render with point lights
void RenderSceneShaded(const Scene &scene);
GLuint GetColorTexture() const { return m_colorTex; }
Vec2i GetTargetSize() const { return m_size; }
@@ -44,41 +59,45 @@ namespace OX
private:
void CreateFramebuffer(int width, int height);
void EnsureLinePipeline();
// Scene traversal helpers
static glm::mat4 BuildModelMatrix(const TransformComponent *tr);
static glm::mat4 BuildViewMatrix(const TransformComponent *camTr);
static glm::mat4 BuildProjectionMatrix(const CameraComponent *cam, float aspect);
// draw-list bits (very plain)
struct DrawItem
{
GameObject *go = nullptr;
TransformComponent *tr = nullptr;
MeshComponent *mesh = nullptr;
float depth = 0.0f; // view-space Z of object origin
};
void EnsureMeshPipeline();
void CollectDrawItemsRecursive(GameObject *go,
const glm::mat4 &view,
std::vector<DrawItem> &out);
void CollectDrawList(const Scene &scene, std::vector<DrawItem> &out, glm::mat4 &outView) const;
void GatherPointLights(const Scene &scene);
void UploadFrameLightsUniforms(); // uses glGetIntegerv(GL_CURRENT_PROGRAM, ...)
void DrawMeshLambert(const DrawItem &di, const glm::mat4 &VP);
private:
// Offscreen target
Vec2i m_size{0, 0};
Vec2i m_size{};
GLuint m_fbo = 0;
GLuint m_colorTex = 0;
GLuint m_depthRBO = 0;
// Debug line pipeline
Shader m_lineShader;
GLuint m_lineVAO = 0;
GLuint m_lineVBO = 0;
bool m_lineShaderBuilt = false;
// Mesh pipeline
Shader m_meshShader;
bool m_meshShaderReady = false;
GLuint m_meshVAO = 0;
GLuint m_meshVBO = 0;
GLuint m_meshEBO = 0;
// Cached VP for current frame
// Per-frame state
glm::mat4 m_viewProj{1.0f};
glm::vec3 m_cameraPosWS{0.0f};
std::vector<PointLightGPU> m_frameLights;
// Artistic tuning
glm::vec3 m_ambient{0.06f, 0.065f, 0.07f};
glm::vec3 m_specColor{0.18f, 0.18f, 0.18f};
float m_shininess = 32.0f;
};
} // namespace OX

View File

@@ -0,0 +1,35 @@
#include "PointLightComponent.h"
namespace OX
{
YAML::Node PointLightComponent::Serialize() const
{
YAML::Node n;
n["type"] = "PointLight";
n["enabled"] = m_enabled;
n["color"] = YAML::Node(YAML::NodeType::Sequence);
n["color"].push_back(m_color.x);
n["color"].push_back(m_color.y);
n["color"].push_back(m_color.z);
n["intensity"] = m_intensity;
n["range"] = m_range;
n["cast_shadows"] = m_castShadows;
return n;
}
void PointLightComponent::Deserialize(const YAML::Node &n)
{
if (!n || !n.IsMap()) return;
if (auto v = n["enabled"]) m_enabled = v.as<bool>();
if (auto v = n["intensity"]) m_intensity = v.as<float>();
if (auto v = n["range"]) m_range = v.as<float>();
if (auto v = n["cast_shadows"]) m_castShadows = v.as<bool>();
if (auto c = n["color"]; c && c.IsSequence() && c.size() >= 3) {
m_color.x = c[0].as<float>();
m_color.y = c[1].as<float>();
m_color.z = c[2].as<float>();
}
}
} // namespace OX

View File

@@ -0,0 +1,45 @@
#pragma once
#include "Component.h"
#include <glm/glm.hpp>
#include <yaml-cpp/yaml.h>
namespace OX
{
class PointLightComponent : public Component
{
public:
PointLightComponent() = default;
// Enable / disable
void setEnabled(bool v) { m_enabled = v; }
bool enabled() const { return m_enabled; }
// Color (linear)
void setColor(const glm::vec3 &c) { m_color = c; }
glm::vec3 color() const { return m_color; }
// Luminous intensity (arbitrary units for now)
void setIntensity(float i) { m_intensity = i; }
float intensity() const { return m_intensity; }
// Effective range in world units (for culling / attenuation)
void setRange(float r) { m_range = r; }
float range() const { return m_range; }
// (Reserved for later)
void setCastShadows(bool v) { m_castShadows = v; }
bool castShadows() const { return m_castShadows; }
// Serialization
YAML::Node Serialize() const override;
void Deserialize(const YAML::Node &n) override;
private:
bool m_enabled = true;
glm::vec3 m_color{1.0f, 1.0f, 1.0f};
float m_intensity = 5.0f; // “brightness”
float m_range = 10.0f; // meters / world units
bool m_castShadows = false; // unused for now
};
} // namespace OX

View File

@@ -6,7 +6,6 @@
namespace OX
{
Shader::Shader(const std::string &vertexPath, const std::string &fragmentPath)
{
LoadFromFiles(vertexPath, fragmentPath);
@@ -108,7 +107,7 @@ namespace OX
}
try {
OX_PROFILE_LABEL("Get Write Time");
OX_PROFILE_LABEL("Get Write Time");
auto vertTime = std::filesystem::last_write_time(m_vertexPath);
auto fragTime = std::filesystem::last_write_time(m_fragmentPath);
@@ -118,7 +117,6 @@ namespace OX
}
} catch (const std::exception &e) {
Logger::LogError("Failed to Reload Shader '%s', %s", m_name.c_str(), e.what());
}
}
@@ -169,6 +167,13 @@ namespace OX
glUniform4fv(GetUniformLocation(name), 1, &value[0]);
}
void Shader::SetMat3(const std::string &name, const glm::mat3 &value)
{
glUniformMatrix3fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);
}
void Shader::SetMat4(const std::string &name, const glm::mat4 &value)
{
glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);

View File

@@ -40,6 +40,8 @@ namespace OX
void SetVec4(const std::string &name, const glm::vec4 &value);
void SetMat3(const std::string &name, const glm::mat3 &value);
void SetMat4(const std::string &name, const glm::mat4 &value);
void SetBool(const std::string &name, bool value);

View File

@@ -14,6 +14,7 @@
#include "systems/Scene/Components/TransformComponent.h"
#include "systems/Scene/Components/MeshComponent.h"
#include "systems/Scene/Components/CameraComponent.h"
#include "systems/Scene/Components/PointLightComponent.h"
#include <imgui.h>
@@ -272,6 +273,11 @@ namespace OX
ImGui::Dummy(ImVec2(0, 4));
}
if (go->getComponent<PointLightComponent>()) {
DrawPointLightComponent(go);
ImGui::Dummy(ImVec2(0, 4));
}
// Popup-only add
DrawAddComponentMenu(go);
@@ -478,6 +484,76 @@ namespace OX
}
}
void InspectorWindow::DrawPointLightComponent(GameObject *go)
{
bool toRemove = false;
bool open = ComponentHeader("Point Light", &m_plOpen, &toRemove);
if (open) {
if (auto *pl = go->getComponent<PointLightComponent>()) {
if (ImGui::BeginTable("##pl_tbl", 2,
ImGuiTableFlags_SizingStretchProp |
ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 110.0f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
// Enabled
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Enabled");
ImGui::TableSetColumnIndex(1);
bool enabled = pl->enabled();
if (ImGui::Checkbox("##pl_enabled", &enabled)) pl->setEnabled(enabled);
// Color
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Color");
ImGui::TableSetColumnIndex(1);
glm::vec3 c = pl->color();
if (ImGui::ColorEdit3("##pl_color", &c.x, ImGuiColorEditFlags_NoInputs))
pl->setColor(c);
// Intensity
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Intensity");
ImGui::TableSetColumnIndex(1);
float I = pl->intensity();
if (ImGui::DragFloat("##pl_intensity", &I, 0.05f, 0.0f, 1000.0f))
pl->setIntensity(std::max(0.0f, I));
// Range
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Range");
ImGui::TableSetColumnIndex(1);
float R = pl->range();
if (ImGui::DragFloat("##pl_range", &R, 0.05f, 0.01f, 100000.0f))
pl->setRange(std::max(0.01f, R));
// Shadows (reserved)
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Cast Shadows");
ImGui::TableSetColumnIndex(1);
bool cs = pl->castShadows();
if (ImGui::Checkbox("##pl_shadows", &cs)) pl->setCastShadows(cs);
ImGui::EndTable();
}
}
ImGui::TreePop();
}
if (toRemove) (void) go->removeComponent<PointLightComponent>();
}
// ─────────────────────────────────────────────────────────────────────────────
// Popup-only add
// ─────────────────────────────────────────────────────────────────────────────
@@ -490,6 +566,7 @@ namespace OX
const bool hasTr = go->getComponent<TransformComponent>() != nullptr;
const bool hasMesh = go->getComponent<MeshComponent>() != nullptr;
const bool hasCam = go->getComponent<CameraComponent>() != nullptr;
const bool hasPoint = go->getComponent<PointLightComponent>() != nullptr;
if (ImGui::MenuItem("TransformComponent", nullptr, false, !hasTr)) {
@@ -504,11 +581,15 @@ namespace OX
if (ImGui::MenuItem("MeshComponent", nullptr, false, !hasMesh)) {
go->addComponent(std::make_unique<MeshComponent>());
// If we just added one, clear path field so user can type
m_meshPath.clear();
ImGui::CloseCurrentPopup();
}
if (ImGui::MenuItem("PointLightComponent", nullptr, false, !hasPoint)) {
go->addComponent(std::make_unique<PointLightComponent>());
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}

View File

@@ -41,6 +41,8 @@ namespace OX
void DrawCameraComponent(GameObject *go);
void DrawPointLightComponent(GameObject *go);
// Popup to add components
void DrawAddComponentMenu(GameObject *go);
@@ -50,5 +52,6 @@ namespace OX
std::string m_meshPath;
bool m_trOpen = true;
bool m_camOpen = true;
bool m_plOpen = true;
};
} // namespace OX