feat(场景图系统): 实现完整的场景图模块和示例程序

新增场景图系统核心组件:
- Director 场景管理
- Scene 场景容器
- Node 节点层级
- Component 组件系统
- Transform 变换(含锚点)
- Camera 相机
- SpriteRenderer 精灵渲染

添加场景图示例程序,演示:
- 节点层级和变换继承
- 组件系统使用
- 相机设置
- 精灵渲染

同时优化了渲染系统:
- 修改渲染命令结构
- 添加视口适配器
- 改进着色器错误处理
- 增强材质系统功能
This commit is contained in:
ChestnutYueyue 2026-03-02 04:50:28 +08:00
parent b4be0d84f8
commit 92be7d9d18
42 changed files with 4142 additions and 157 deletions

View File

@ -0,0 +1,96 @@
# 场景图系统示例 (Scene Graph Demo)
这个示例演示了 Extra2D 引擎场景图模块的核心功能。
## 功能演示
### 1. Director 场景管理
- 通过 `SceneModule` 获取 `Director`
- 使用 `runScene()` 启动场景
### 2. Scene 场景容器
- 继承 `Scene` 类创建自定义场景
- 实现 `onEnter()``onExit()` 生命周期方法
- 在 `update()` 中处理场景级逻辑
### 3. Node 节点层级
- 创建自定义节点类(`PlayerNode`, `RotatingDecoration`
- 使用 `addChild()` 构建节点树
- 节点变换自动继承父节点
### 4. Component 组件系统
- `TransformComponent`: 位置、旋转、缩放、锚点
- `SpriteRenderer`: 2D 精灵渲染
- `CameraComponent`: 相机投影和视图
### 5. Transform 变换(含锚点)
- 归一化锚点设置 `[0,1]`
- 锚点影响旋转中心和位置参考点
- 世界变换自动计算
## 运行效果
- 蓝色方块(玩家)沿圆周运动
- 8个彩色小方块围绕中心旋转
- 每个小方块还有自转动画
## 构建和运行
```bash
# 配置(启用示例)
xmake f -p mingw --examples=true
# 构建
xmake
# 运行
./build/examples/scene_graph_demo/scene_graph_demo.exe
```
## 代码结构
```
scene_graph_demo/
├── main.cpp # 程序入口
├── game_scene.h # 场景和节点类定义
├── game_scene.cpp # 场景和节点实现
└── xmake.lua # 构建配置
```
## 关键 API
### 创建场景
```cpp
class GameScene : public Scene {
public:
void onEnter() override;
void update(float dt) override;
};
auto scene = makePtr<GameScene>();
director->runScene(scene);
```
### 创建节点
```cpp
auto node = makePtr<Node>();
node->setPosition(100, 200);
node->setAnchor(0.5f, 0.5f); // 中心锚点
node->setSize(64, 64);
scene->addChild(node);
```
### 添加组件
```cpp
auto sprite = makePtr<SpriteRenderer>();
sprite->setColor(Color::Red);
node->addComponent(sprite);
```
### 设置相机
```cpp
auto camera = makePtr<CameraComponent>();
camera->setOrtho(0, 1280, 0, 720, -1, 1);
cameraNode->addComponent(camera);
scene->setMainCamera(camera);
```

View File

@ -0,0 +1,170 @@
#include "game_scene.h"
#include <cmath>
// ========================================
// PlayerNode 实现
// ========================================
PlayerNode::PlayerNode() {
setName("Player");
setTag(1);
// 设置玩家尺寸
setSize(64.0f, 64.0f);
// 设置锚点为中心点
setAnchor(0.5f, 0.5f);
// 添加精灵渲染组件
auto sprite = makePtr<SpriteRenderer>();
sprite->setColor(Color::Blue);
addComponent(sprite);
}
void PlayerNode::onUpdate(float dt) {
time_ += dt;
// 简单的圆周运动
float radius = 200.0f;
float x = 640.0f + radius * std::cos(time_ * 0.5f);
float y = 360.0f + radius * std::sin(time_ * 0.5f);
setPosition(x, y);
// 根据移动方向旋转
float angle = time_ * 0.5f * 57.2958f + 90.0f;
setRotation(angle);
}
// ========================================
// RotatingDecoration 实现
// ========================================
RotatingDecoration::RotatingDecoration() {
setName("Decoration");
// 设置尺寸
setSize(32.0f, 32.0f);
// 设置锚点为中心
setAnchor(0.5f, 0.5f);
// 添加精灵渲染组件
auto sprite = makePtr<SpriteRenderer>();
sprite->setColor(Color::Yellow);
addComponent(sprite);
}
void RotatingDecoration::onUpdate(float dt) {
// 自转
setRotation(getRotation() + rotationSpeed_ * dt);
}
// ========================================
// GameScene 实现
// ========================================
GameScene::GameScene() {
// 场景初始化
}
void GameScene::onEnter() {
Scene::onEnter();
// 创建相机
createCamera();
// 创建玩家
createPlayer();
// 创建装饰物
createDecorations();
}
void GameScene::onExit() {
// 清理资源
player_.reset();
decorations_.clear();
Scene::onExit();
}
void GameScene::update(float dt) {
Scene::update(dt);
sceneTime_ += dt;
// 更新玩家
if (player_) {
player_->onUpdate(dt);
}
// 更新装饰物
for (auto& decoration : decorations_) {
decoration->onUpdate(dt);
}
}
void GameScene::createPlayer() {
player_ = makePtr<PlayerNode>();
player_->setPosition(640.0f, 360.0f);
addChild(player_);
}
void GameScene::createDecorations() {
// 创建围绕玩家旋转的装饰物
int count = 8;
float radius = 150.0f;
for (int i = 0; i < count; ++i) {
auto decoration = makePtr<RotatingDecoration>();
// 计算位置(圆形分布)
float angle = (2.0f * 3.14159f * i) / count;
float x = 640.0f + radius * std::cos(angle);
float y = 360.0f + radius * std::sin(angle);
decoration->setPosition(x, y);
decoration->setRotationSpeed(45.0f + i * 10.0f);
// 设置不同颜色
auto sprite = decoration->getComponent<SpriteRenderer>();
if (sprite) {
float hue = static_cast<float>(i) / count;
// 简单的HSV到RGB转换
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
sprite->setColor(Color(
std::max(0.0f, std::min(1.0f, r)),
std::max(0.0f, std::min(1.0f, g)),
std::max(0.0f, std::min(1.0f, b)),
1.0f
));
}
decorations_.push_back(decoration);
addChild(decoration);
}
}
void GameScene::createCamera() {
// 创建相机节点
auto cameraNode = makePtr<Node>();
cameraNode->setName("MainCamera");
// 相机位置在(0, 0),这样世界坐标直接映射到屏幕
cameraNode->setPosition(0.0f, 0.0f);
// 添加相机组件
auto camera = makePtr<CameraComponent>();
// 使用标准的2D投影左上角为(0, 0),右下角为(1280, 720)
// Y轴向下bottom=720, top=0
camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f);
cameraNode->addComponent(camera);
// 设置为主相机
setMainCamera(camera);
// 添加相机节点到场景
addChild(cameraNode);
}

View File

@ -0,0 +1,140 @@
#pragma once
#include <extra2d.h>
using namespace extra2d;
/**
* @brief
*
*
*/
class PlayerNode : public Node {
public:
/**
* @brief
*/
PlayerNode();
/**
* @brief
*/
~PlayerNode() override = default;
/**
* @brief
* @param dt
*/
void onUpdate(float dt);
/**
* @brief
* @param speed
*/
void setSpeed(float speed) { speed_ = speed; }
/**
* @brief
* @return
*/
float getSpeed() const { return speed_; }
private:
float speed_ = 200.0f;
float time_ = 0.0f;
};
/**
* @brief
*
*
*/
class RotatingDecoration : public Node {
public:
/**
* @brief
*/
RotatingDecoration();
/**
* @brief
*/
~RotatingDecoration() override = default;
/**
* @brief
* @param dt
*/
void onUpdate(float dt);
/**
* @brief
* @param speed /
*/
void setRotationSpeed(float speed) { rotationSpeed_ = speed; }
private:
float rotationSpeed_ = 90.0f;
};
/**
* @brief
*
*
* -
* -
* -
* -
*/
class GameScene : public Scene {
public:
/**
* @brief
*/
GameScene();
/**
* @brief
*/
~GameScene() override = default;
/**
* @brief
*
*
*/
void onEnter() override;
/**
* @brief 退
*
*
*/
void onExit() override;
/**
* @brief
* @param dt
*/
void update(float dt) override;
private:
/**
* @brief
*/
void createPlayer();
/**
* @brief
*/
void createDecorations();
/**
* @brief
*/
void createCamera();
Ptr<PlayerNode> player_;
std::vector<Ptr<RotatingDecoration>> decorations_;
float sceneTime_ = 0.0f;
};

View File

@ -0,0 +1,79 @@
/**
* @file main.cpp
* @brief
*
* Extra2D
* - Director
* - Scene
* - Node
* - Component
* - Transform
* - Camera
* - SpriteRenderer
*/
#include <extra2d.h>
#include "game_scene.h"
#include <cstdio>
using namespace extra2d;
/**
* @brief
*/
int main(int argc, char **argv) {
// ========================================
// 1. 创建应用
// ========================================
auto app = Application::create();
AppConfig config;
config.title = "Scene Graph Demo - Extra2D";
config.width = 1280;
config.height = 720;
if (!app->init(config)) {
printf("Failed to initialize application!\n");
return -1;
}
printf("Application initialized successfully\n");
printf("Window size: %dx%d\n", app->getWindowWidth(), app->getWindowHeight());
// ========================================
// 2. 获取场景模块和导演
// ========================================
SceneModule *sceneModule = app->getModule<SceneModule>();
if (!sceneModule) {
printf("Failed to get SceneModule!\n");
return -1;
}
Director *director = sceneModule->getDirector();
if (!director) {
printf("Failed to get Director!\n");
return -1;
}
printf("Scene module and director ready\n");
// ========================================
// 3. 创建并运行游戏场景
// ========================================
auto gameScene = makePtr<GameScene>();
director->runScene(gameScene);
printf("Game scene started\n");
// ========================================
// 4. 运行应用主循环
// ========================================
app->run();
// ========================================
// 5. 清理(自动进行)
// ========================================
printf("Application shutting down...\n");
return 0;
}

View File

@ -0,0 +1,87 @@
-- ==============================================
-- 场景图示例 - Xmake 构建脚本
-- 演示 Extra2D 场景图系统的核心功能
-- 支持平台: MinGW (Windows), Nintendo Switch
-- ==============================================
-- 获取当前脚本所在目录(示例根目录)
local example_dir = os.scriptdir()
-- 可执行文件目标
target("scene_graph_demo")
set_kind("binary")
add_files("main.cpp", "game_scene.cpp")
add_includedirs("../../include", ".")
add_deps("extra2d")
-- 使用与主项目相同的平台配置
if is_plat("switch") then
set_targetdir("../../build/examples/scene_graph_demo")
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
local output_dir = path.directory(elf_file)
local nacp_file = path.join(output_dir, "scene_graph_demo.nacp")
local nro_file = path.join(output_dir, "scene_graph_demo.nro")
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
if os.isfile(nacptool) and os.isfile(elf2nro) then
os.vrunv(nacptool, {"--create", "Scene Graph Demo", "Extra2D Team", "1.0.0", nacp_file})
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "scene_graph_demo.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
elseif is_plat("mingw") then
set_targetdir("../../build/examples/scene_graph_demo")
-- 复制资源到输出目录
after_build(function (target)
local target_dir = path.directory(target:targetfile())
-- 复制 romfs 资源
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
end
-- 复制着色器文件
local shader_source = path.join(example_dir, "../../shader")
local shader_target = path.join(target_dir, "shader")
if os.isdir(shader_source) then
if not os.isdir(shader_target) then
os.mkdir(shader_target)
end
os.cp(path.join(shader_source, "*"), shader_target)
print("Copied shaders from " .. shader_source .. " to " .. shader_target)
end
end)
end
target_end()

View File

@ -3,6 +3,7 @@
#include <event/event_bus_macros.h> #include <event/event_bus_macros.h>
#include <renderer/render_types.h> #include <renderer/render_types.h>
#include <types/base/types.h> #include <types/base/types.h>
#include <types/math/mat4.h>
namespace extra2d::events { namespace extra2d::events {
@ -243,6 +244,14 @@ DECLARE_EVENT_0(OnRenderBegin, Engine)
*/ */
DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand) DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand)
/**
* @brief
*
*
* @param viewProjection
*/
DECLARE_EVENT_1(OnRenderSetCamera, Engine, extra2d::Mat4)
/** /**
* @brief * @brief
* *
@ -250,4 +259,11 @@ DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand)
*/ */
DECLARE_EVENT_0(OnRenderEnd, Engine) DECLARE_EVENT_0(OnRenderEnd, Engine)
/**
* @brief
*
* swapBuffers
*/
DECLARE_EVENT_0(OnRenderPresent, Engine)
} // namespace extra2d::events } // namespace extra2d::events

View File

@ -16,6 +16,7 @@
#include <types/math/transform.h> #include <types/math/transform.h>
#include <types/math/vec2.h> #include <types/math/vec2.h>
#include <types/math/vec3.h> #include <types/math/vec3.h>
#include <types/math/mat4.h>
// Context (核心上下文) // Context (核心上下文)
#include <context/context.h> #include <context/context.h>
@ -49,6 +50,16 @@
#include <renderer/mesh.h> #include <renderer/mesh.h>
#include <renderer/uniform_buffer.h> #include <renderer/uniform_buffer.h>
// Scene Graph System
#include <scene/scene_module.h>
#include <scene/director.h>
#include <scene/scene.h>
#include <scene/node.h>
#include <scene/component.h>
#include <scene/components/transform_component.h>
#include <scene/components/sprite_renderer.h>
#include <scene/components/camera_component.h>
// Application // Application
#include <app/application.h> #include <app/application.h>

View File

@ -154,6 +154,13 @@ private:
*/ */
void onModuleConfig(const AppConfig &config); void onModuleConfig(const AppConfig &config);
/**
* @brief
*
* swapBuffers
*/
void onRenderPresent();
SDL_Window *window_ = nullptr; SDL_Window *window_ = nullptr;
SDL_GLContext glContext_ = nullptr; SDL_GLContext glContext_ = nullptr;
bool shouldClose_ = false; bool shouldClose_ = false;
@ -163,6 +170,9 @@ private:
// 模块配置事件监听器 // 模块配置事件监听器
std::unique_ptr<events::OnModuleConfig<AppConfig>::Listener> configListener_; std::unique_ptr<events::OnModuleConfig<AppConfig>::Listener> configListener_;
// 渲染呈现事件监听器
std::unique_ptr<events::OnRenderPresent::Listener> renderPresentListener_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -15,6 +15,7 @@ namespace extra2d {
// 前向声明 // 前向声明
class Material; class Material;
class Texture;
/** /**
* @brief * @brief
@ -25,6 +26,15 @@ struct MaterialParamInfo {
uint32_t size; uint32_t size;
}; };
/**
* @brief
*/
struct TextureSlot {
TextureHandle handle;
uint32_t slot;
std::string uniformName;
};
/** /**
* @brief * @brief
* *
@ -77,8 +87,36 @@ private:
/** /**
* @brief * @brief
* *
* *
* UBO GPU * -
* - UBO
* -
*
* 使
* @code
* // 创建自定义着色器
* auto shader = makePtr<Shader>();
* shader->loadFromFile("custom.vert", "custom.frag");
*
* // 创建材质布局
* auto layout = makePtr<MaterialLayout>();
* layout->addParam("uTintColor", MaterialParamType::Color);
* layout->addParam("uOpacity", MaterialParamType::Float);
* layout->finalize();
*
* // 创建材质
* auto material = makePtr<Material>();
* material->setShader(shader);
* material->setLayout(layout);
* material->setColor("uTintColor", Color::White);
* material->setFloat("uOpacity", 1.0f);
*
* // 设置纹理
* material->setTexture("uTexture", textureHandle, 0);
*
* // 注册到渲染器
* MaterialHandle handle = renderer->registerMaterial(material);
* @endcode
*/ */
class Material : public RefCounted { class Material : public RefCounted {
public: public:
@ -87,6 +125,10 @@ public:
*/ */
Material(); Material();
// ========================================
// 着色器和布局设置
// ========================================
/** /**
* @brief * @brief
* @param layout * @param layout
@ -99,6 +141,16 @@ public:
*/ */
void setShader(Ptr<Shader> shader); void setShader(Ptr<Shader> shader);
/**
* @brief
* @return
*/
Ptr<Shader> getShader() const { return shader_; }
// ========================================
// 参数设置
// ========================================
/** /**
* @brief float * @brief float
* @param name * @param name
@ -134,27 +186,32 @@ public:
*/ */
void setMat4(const std::string& name, const float* value); void setMat4(const std::string& name, const float* value);
// ========================================
// 纹理管理
// ========================================
/** /**
* @brief * @brief
* @param name * @param uniformName uniform
* @param texture * @param texture
* @param slot * @param slot 0-15
*/ */
void setTexture(const std::string& name, TextureHandle texture, uint32_t slot); void setTexture(const std::string& uniformName, TextureHandle texture, uint32_t slot);
/** /**
* @brief * @brief
* * @return
* UBO
* @param uboManager UBO
*/ */
void apply(UniformBufferManager& uboManager); const std::vector<TextureSlot>& getTextures() const { return textures_; }
/** /**
* @brief * @brief
* @return
*/ */
Ptr<Shader> getShader() const { return shader_; } void clearTextures();
// ========================================
// 数据访问
// ========================================
/** /**
* @brief * @brief
@ -168,11 +225,23 @@ public:
*/ */
uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); } uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); }
// ========================================
// 应用材质(渲染时调用)
// ========================================
/**
* @brief
*
* UBO
* @param uboManager UBO
*/
void apply(UniformBufferManager& uboManager);
private: private:
Ptr<MaterialLayout> layout_; // 材质布局 Ptr<MaterialLayout> layout_; // 材质布局
Ptr<Shader> shader_; // 着色器 Ptr<Shader> shader_; // 着色器
std::vector<uint8_t> data_; // 材质数据 std::vector<uint8_t> data_; // 材质数据UBO 缓冲)
std::vector<std::pair<TextureHandle, uint32_t>> textures_; // 纹理列表 std::vector<TextureSlot> textures_; // 纹理槽位列表
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -93,42 +93,50 @@ inline uint32_t getMaterialParamSize(MaterialParamType type) {
} }
/** /**
* @brief 32 * @brief
* *
* 使 * 使
* *
*/ */
struct alignas(32) RenderCommand { struct RenderCommand {
RenderCommandType type; // 命令类型 RenderCommandType type; // 命令类型
uint32_t sortKey; // 排序键材质ID + 层) uint32_t sortKey; // 排序键材质ID + 层)
// 绘制网格命令数据
struct DrawMeshData {
MeshHandle mesh; // 网格句柄
MaterialHandle material; // 材质句柄
Vec2 pos; // 位置
Vec2 scale; // 缩放
float rot; // 旋转角度
Color color; // 顶点颜色
};
// 实例化绘制命令数据
struct DrawInstancedData {
MeshHandle mesh; // 网格句柄
MaterialHandle material; // 材质句柄
uint32_t instanceCount; // 实例数量
uint32_t instanceOffset; // 实例数据偏移
};
// 设置视口命令数据
struct ViewportData {
int32_t x, y; // 视口位置
int32_t width, height; // 视口大小
};
// 清除缓冲区命令数据
struct ClearData {
Color color; // 清除颜色
uint32_t flags; // 清除标志(颜色/深度/模板)
};
union { union {
// 绘制网格命令 DrawMeshData drawMesh;
struct { DrawInstancedData drawInstanced;
MeshHandle mesh; // 网格句柄 ViewportData viewport;
MaterialHandle material; // 材质句柄 ClearData clear;
Transform transform; // 世界变换
} drawMesh;
// 实例化绘制命令
struct {
MeshHandle mesh; // 网格句柄
MaterialHandle material; // 材质句柄
uint32_t instanceCount; // 实例数量
uint32_t instanceOffset; // 实例数据偏移
} drawInstanced;
// 设置视口命令
struct {
int32_t x, y; // 视口位置
int32_t width, height; // 视口大小
} viewport;
// 清除缓冲区命令
struct {
Color color; // 清除颜色
uint32_t flags; // 清除标志(颜色/深度/模板)
} clear;
}; };
/** /**
@ -137,7 +145,40 @@ struct alignas(32) RenderCommand {
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) { RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
drawMesh.mesh = INVALID_MESH_HANDLE; drawMesh.mesh = INVALID_MESH_HANDLE;
drawMesh.material = INVALID_MATERIAL_HANDLE; drawMesh.material = INVALID_MATERIAL_HANDLE;
drawMesh.transform = Transform(); drawMesh.pos = Vec2(0.0f, 0.0f);
drawMesh.scale = Vec2(1.0f, 1.0f);
drawMesh.rot = 0.0f;
drawMesh.color = Color::White;
}
/**
* @brief Transform
*/
void setTransform(const Transform& t) {
drawMesh.pos = t.pos;
drawMesh.scale = t.scale;
drawMesh.rot = t.rot;
}
/**
* @brief
*/
void setColor(const Color& c) {
drawMesh.color = c;
}
/**
* @brief Transform
*/
Transform getTransform() const {
return Transform(drawMesh.pos, drawMesh.scale, drawMesh.rot);
}
/**
* @brief
*/
Color getColor() const {
return drawMesh.color;
} }
}; };

View File

@ -11,6 +11,7 @@
#include <renderer/shader.h> #include <renderer/shader.h>
#include <renderer/texture.h> #include <renderer/texture.h>
#include <renderer/uniform_buffer.h> #include <renderer/uniform_buffer.h>
#include <renderer/viewport_adapter.h>
#include <vector> #include <vector>
namespace extra2d { namespace extra2d {
@ -169,10 +170,23 @@ public:
/** /**
* @brief * @brief
* @param color * @param color
* @param flags 使 CLEAR_COLOR_FLAG, CLEAR_DEPTH_FLAG, CLEAR_STENCIL_FLAG * @param flags 使 CLEAR_COLOR_FLAG, CLEAR_DEPTH_FLAG,
* CLEAR_STENCIL_FLAG
*/ */
void clear(const Color &color, uint32 flags = CLEAR_COLOR_FLAG); void clear(const Color &color, uint32 flags = CLEAR_COLOR_FLAG);
/**
* @brief
* @return
*/
ViewportAdapter &getViewportAdapter() { return viewportAdapter_; }
/**
* @brief const版本
* @return const引用
*/
const ViewportAdapter &getViewportAdapter() const { return viewportAdapter_; }
private: private:
//=========================================================================== //===========================================================================
// 事件处理器 // 事件处理器
@ -191,6 +205,12 @@ private:
*/ */
void onRenderSubmit(const RenderCommand &cmd); void onRenderSubmit(const RenderCommand &cmd);
/**
* @brief
* @param viewProj
*/
void onRenderSetCamera(const Mat4 &viewProj);
/** /**
* @brief * @brief
* *
@ -341,16 +361,17 @@ private:
} }
}; };
HandlePool<Material> materialPool_; // 材质资源池 HandlePool<Material> materialPool_; // 材质资源池
HandlePool<Mesh> meshPool_; // 网格资源池 HandlePool<Mesh> meshPool_; // 网格资源池
HandlePool<Texture> texturePool_; // 纹理资源池 HandlePool<Texture> texturePool_; // 纹理资源池
//=========================================================================== //===========================================================================
// 命令缓冲区 // 命令缓冲区
//=========================================================================== //===========================================================================
std::array<RenderCommand, MAX_RENDER_COMMANDS> commandBuffer_; // 预分配命令缓冲区 std::array<RenderCommand, MAX_RENDER_COMMANDS>
uint32 commandCount_ = 0; // 当前命令数量 commandBuffer_; // 预分配命令缓冲区
uint32 commandCount_ = 0; // 当前命令数量
//=========================================================================== //===========================================================================
// UBO 管理器 // UBO 管理器
@ -372,6 +393,7 @@ private:
events::OnRenderBegin::Listener onRenderBeginListener_; events::OnRenderBegin::Listener onRenderBeginListener_;
events::OnRenderSubmit::Listener onRenderSubmitListener_; events::OnRenderSubmit::Listener onRenderSubmitListener_;
events::OnRenderSetCamera::Listener onRenderSetCameraListener_;
events::OnRenderEnd::Listener onRenderEndListener_; events::OnRenderEnd::Listener onRenderEndListener_;
events::OnResize::Listener onResizeListener_; events::OnResize::Listener onResizeListener_;
events::OnShow::Listener onShowListener_; events::OnShow::Listener onShowListener_;
@ -380,17 +402,17 @@ private:
// 状态标志 // 状态标志
//=========================================================================== //===========================================================================
bool glInitialized_ = false; // GL 是否已初始化 bool glInitialized_ = false; // GL 是否已初始化
//=========================================================================== //===========================================================================
// 渲染统计 // 渲染统计
//=========================================================================== //===========================================================================
struct Stats { struct Stats {
uint32 commandsSubmitted = 0; // 提交的命令数 uint32 commandsSubmitted = 0; // 提交的命令数
uint32 commandsExecuted = 0; // 执行的命令数 uint32 commandsExecuted = 0; // 执行的命令数
uint32 drawCalls = 0; // 绘制调用次数 uint32 drawCalls = 0; // 绘制调用次数
uint32 batches = 0; // 批次数 uint32 batches = 0; // 批次数
} stats_; } stats_;
//=========================================================================== //===========================================================================
@ -399,6 +421,18 @@ private:
int32 viewportX_ = 0, viewportY_ = 0; int32 viewportX_ = 0, viewportY_ = 0;
int32 viewportWidth_ = 0, viewportHeight_ = 0; int32 viewportWidth_ = 0, viewportHeight_ = 0;
//===========================================================================
// 视口适配器
//===========================================================================
ViewportAdapter viewportAdapter_; // 视口适配器
//===========================================================================
// 相机矩阵
//===========================================================================
Mat4 viewProjectionMatrix_; // 当前视图投影矩阵
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -0,0 +1,139 @@
#pragma once
#include <types/base/types.h>
#include <types/math/vec2.h>
#include <types/math/rect.h>
#include <types/math/color.h>
#include <glm/mat4x4.hpp>
namespace extra2d {
/**
* @brief
*/
enum class ViewportMode {
AspectRatio, // 保持宽高比,可能有黑边
Stretch, // 拉伸以填满屏幕
Center, // 居中显示
Custom // 自定义
};
/**
* @brief
*/
enum class LetterboxPosition {
Center,
LeftTop,
RightTop,
LeftBottom,
RightBottom
};
/**
* @brief
*/
struct ViewportConfig {
float logicWidth = 1280.0f; // 逻辑宽度
float logicHeight = 720.0f; // 逻辑高度
ViewportMode mode = ViewportMode::AspectRatio;
LetterboxPosition letterboxPosition = LetterboxPosition::Center;
Color letterboxColor = Color::Black;
bool autoScaleInCenterMode = true;
float customScale = 1.0f;
Vec2 customOffset = Vec2::Zero;
Rect customViewport = Rect::Zero;
};
/**
* @brief
*/
struct ViewportResult {
Rect viewport; // 视口矩形
float scaleX = 1.0f; // X方向缩放
float scaleY = 1.0f; // Y方向缩放
float uniformScale = 1.0f; // 统一缩放
Vec2 offset; // 视口偏移
bool hasLetterbox = false; // 是否有黑边
struct Letterbox {
Rect top;
Rect bottom;
Rect left;
Rect right;
} letterbox;
};
/**
* @brief
*
*
*/
class ViewportAdapter {
public:
ViewportAdapter();
ViewportAdapter(float logicWidth, float logicHeight);
~ViewportAdapter() = default;
// 配置设置
void setConfig(const ViewportConfig& config);
const ViewportConfig& getConfig() const { return config_; }
void setLogicSize(float width, float height);
void setMode(ViewportMode mode);
void setLetterboxPosition(LetterboxPosition position);
void setLetterboxColor(const Color& color);
// 更新和计算
void update(int screenWidth, int screenHeight);
const ViewportResult& getResult() const { return result_; }
// 坐标转换
Vec2 screenToLogic(const Vec2& screenPos) const;
Vec2 logicToScreen(const Vec2& logicPos) const;
Vec2 screenToLogic(float x, float y) const;
Vec2 logicToScreen(float x, float y) const;
// 矩阵获取
glm::mat4 getMatrix() const;
glm::mat4 getInvMatrix() const;
// 区域检测
bool isInViewport(const Vec2& screenPos) const;
bool isInLetterbox(const Vec2& screenPos) const;
// Getter 方法
float getLogicWidth() const { return config_.logicWidth; }
float getLogicHeight() const { return config_.logicHeight; }
Size getLogicSize() const { return Size(config_.logicWidth, config_.logicHeight); }
int getScreenWidth() const { return screenWidth_; }
int getScreenHeight() const { return screenHeight_; }
Size getScreenSize() const { return Size(static_cast<float>(screenWidth_), static_cast<float>(screenHeight_)); }
float getScaleX() const { return result_.scaleX; }
float getScaleY() const { return result_.scaleY; }
float getUniformScale() const { return result_.uniformScale; }
Vec2 getOffset() const { return result_.offset; }
Rect getViewport() const { return result_.viewport; }
bool hasLetterbox() const { return result_.hasLetterbox; }
const ViewportResult::Letterbox& getLetterbox() const { return result_.letterbox; }
private:
void calculateAspectRatio();
void calculateStretch();
void calculateCenter();
void calculateCustom();
void calculateLetterbox();
void applyLetterboxPosition(float extraWidth, float extraHeight);
ViewportConfig config_;
ViewportResult result_;
int screenWidth_ = 0;
int screenHeight_ = 0;
mutable glm::mat4 viewportMatrix_;
mutable glm::mat4 inverseViewportMatrix_;
mutable bool matrixDirty_ = true;
};
} // namespace extra2d

83
include/scene/component.h Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include <types/ptr/ref_counted.h>
namespace extra2d {
// 前向声明
class Node;
/**
* @brief
*
*
*/
class Component : public RefCounted {
public:
/**
* @brief
*/
virtual ~Component() = default;
/**
* @brief
* @param owner
*/
virtual void onAttach(Node* owner);
/**
* @brief
*/
virtual void onDetach();
/**
* @brief
*/
virtual void onEnable();
/**
* @brief
*/
virtual void onDisable();
/**
* @brief
* @param dt
*/
virtual void update(float dt);
/**
* @brief
*/
virtual void render();
/**
* @brief
* @return
*/
Node* getOwner() const { return owner_; }
/**
* @brief
* @param enabled
*/
void setEnabled(bool enabled);
/**
* @brief
* @return
*/
bool isEnabled() const { return enabled_; }
/**
* @brief
* @return
*/
virtual const char* getTypeName() const = 0;
protected:
Node* owner_ = nullptr;
bool enabled_ = true;
};
} // namespace extra2d

View File

@ -0,0 +1,280 @@
#pragma once
#include <scene/component.h>
#include <types/math/rect.h>
#include <types/math/mat4.h>
#include <types/math/vec2.h>
namespace extra2d {
/**
* @brief
*
*
*/
class CameraComponent : public Component {
public:
static constexpr const char* TYPE_NAME = "Camera";
/**
* @brief
*/
enum class ProjectionType {
Orthographic, // 正交投影
Perspective // 透视投影
};
/**
* @brief
*/
CameraComponent();
/**
* @brief
* @return
*/
const char* getTypeName() const override { return TYPE_NAME; }
// ========================================
// 投影设置
// ========================================
/**
* @brief
* @param type
*/
void setProjectionType(ProjectionType type);
/**
* @brief
* @return
*/
ProjectionType getProjectionType() const { return projType_; }
/**
* @brief
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
void setOrtho(float left, float right, float bottom, float top, float near, float far);
/**
* @brief
* @param fov
* @param aspect
* @param near
* @param far
*/
void setPerspective(float fov, float aspect, float near, float far);
// ========================================
// 变换属性(缩放、旋转)
// ========================================
/**
* @brief
* @param zoom 1.0>1<1
*/
void setZoom(float zoom);
/**
* @brief
* @return
*/
float getZoom() const { return zoom_; }
/**
* @brief
* @param degrees
*/
void setRotation(float degrees);
/**
* @brief
* @return
*/
float getRotation() const { return rotation_; }
// ========================================
// 矩阵获取
// ========================================
/**
* @brief
* @return
*/
Mat4 getViewMatrix() const;
/**
* @brief
* @return
*/
Mat4 getProjectionMatrix() const;
/**
* @brief
* @return
*/
Mat4 getViewProjectionMatrix() const;
// ========================================
// 坐标转换
// ========================================
/**
* @brief
* @param screenPos
* @return
*/
Vec2 screenToWorld(const Vec2& screenPos) const;
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return
*/
Vec2 screenToWorld(float x, float y) const;
/**
* @brief
* @param worldPos
* @return
*/
Vec2 worldToScreen(const Vec2& worldPos) const;
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return
*/
Vec2 worldToScreen(float x, float y) const;
// ========================================
// 视口
// ========================================
/**
* @brief
* @param viewport
*/
void setViewport(const Rect& viewport);
/**
* @brief
* @return
*/
Rect getViewport() const { return viewport_; }
// ========================================
// 边界限制
// ========================================
/**
* @brief
* @param bounds
*/
void setBounds(const Rect& bounds);
/**
* @brief
*/
void clearBounds();
/**
* @brief
* @return
*/
bool hasBounds() const { return hasBounds_; }
/**
* @brief
*/
void clampToBounds();
// ========================================
// 移动相机
// ========================================
/**
* @brief
* @param offset
*/
void move(const Vec2& offset);
/**
* @brief
* @param x X方向偏移量
* @param y Y方向偏移量
*/
void move(float x, float y);
/**
* @brief
* @param target
*/
void lookAt(const Vec2& target);
private:
/**
* @brief
*/
void markViewDirty() { viewDirty_ = true; vpDirty_ = true; }
/**
* @brief
*/
void markProjDirty() { projDirty_ = true; vpDirty_ = true; }
/**
* @brief
*/
void updateViewMatrix() const;
/**
* @brief
*/
void updateProjectionMatrix() const;
ProjectionType projType_ = ProjectionType::Orthographic;
// 投影参数
float left_ = 0.0f;
float right_ = 800.0f;
float bottom_ = 600.0f;
float top_ = 0.0f;
float near_ = -1.0f;
float far_ = 1.0f;
// 透视投影参数
float fov_ = 60.0f;
float aspect_ = 16.0f / 9.0f;
// 变换属性
float zoom_ = 1.0f;
float rotation_ = 0.0f;
// 视口
Rect viewport_;
// 边界限制
Rect bounds_;
bool hasBounds_ = false;
// 缓存矩阵mutable 用于 const 方法中延迟计算)
mutable Mat4 viewMatrix_ = Mat4(1.0f);
mutable Mat4 projMatrix_ = Mat4(1.0f);
mutable Mat4 vpMatrix_ = Mat4(1.0f);
// 脏标记
mutable bool viewDirty_ = true;
mutable bool projDirty_ = true;
mutable bool vpDirty_ = true;
};
} // namespace extra2d

View File

@ -0,0 +1,121 @@
#pragma once
#include <scene/component.h>
#include <renderer/render_types.h>
#include <types/math/color.h>
namespace extra2d {
/**
* @brief
*
* 2D精灵
*
* 使
* -
* - 使
*
* 使
* @code
* // 方式1使用完整材质推荐
* auto material = makePtr<Material>();
* material->setShader(customShader);
* material->setTexture("uTexture", textureHandle, 0);
* material->setColor("uTintColor", Color::Red);
* MaterialHandle matHandle = renderer->registerMaterial(material);
* sprite->setMaterial(matHandle);
*
* // 方式2只设置纹理使用默认材质
* sprite->setTexture(textureHandle);
* @endcode
*/
class SpriteRenderer : public Component {
public:
static constexpr const char* TYPE_NAME = "SpriteRenderer";
/**
* @brief
*/
SpriteRenderer();
/**
* @brief
* @return
*/
const char* getTypeName() const override { return TYPE_NAME; }
/**
* @brief
* @param owner
*/
void onAttach(Node* owner) override;
// ========================================
// 材质设置(推荐方式)
// ========================================
/**
* @brief
*
*
* @param material
*/
void setMaterial(MaterialHandle material);
/**
* @brief
* @return
*/
MaterialHandle getMaterial() const { return material_; }
// ========================================
// 纹理设置(便捷方式)
// ========================================
/**
* @brief
*
* 使
*
* @param texture
*/
void setTexture(TextureHandle texture);
/**
* @brief
* @return
*/
TextureHandle getTexture() const { return texture_; }
// ========================================
// 颜色(顶点颜色)
// ========================================
/**
* @brief
* @param color
*/
void setColor(const Color& color);
/**
* @brief
* @return
*/
Color getColor() const { return color_; }
// ========================================
// 渲染
// ========================================
/**
* @brief
*/
void render() override;
private:
MaterialHandle material_ = INVALID_MATERIAL_HANDLE; // 材质句柄
TextureHandle texture_ = INVALID_TEXTURE_HANDLE; // 纹理句柄(便捷方式)
Color color_ = Color::White; // 顶点颜色
};
} // namespace extra2d

View File

@ -0,0 +1,167 @@
#pragma once
#include <scene/component.h>
#include <types/math/transform.h>
#include <types/math/vec2.h>
#include <types/math/mat4.h>
namespace extra2d {
/**
* @brief
*
*
*
*/
class TransformComponent : public Component {
public:
static constexpr const char* TYPE_NAME = "Transform";
/**
* @brief
*/
TransformComponent();
/**
* @brief
* @return
*/
const char* getTypeName() const override { return TYPE_NAME; }
// ========================================
// 本地变换
// ========================================
/**
* @brief
* @param pos
*/
void setPosition(const Vec2& pos);
/**
* @brief
* @param rot
*/
void setRotation(float rot);
/**
* @brief
* @param scale
*/
void setScale(const Vec2& scale);
/**
* @brief
* @return
*/
Vec2 getPosition() const { return local_.pos; }
/**
* @brief
* @return
*/
float getRotation() const { return local_.rot; }
/**
* @brief
* @return
*/
Vec2 getScale() const { return local_.scale; }
// ========================================
// 锚点
// ========================================
/**
* @brief
* @param anchor [0,1]
*/
void setAnchor(const Vec2& anchor);
/**
* @brief
* @param x X锚点
* @param y Y锚点
*/
void setAnchorPoint(float x, float y);
/**
* @brief
* @return
*/
Vec2 getAnchor() const { return anchor_; }
// ========================================
// 尺寸
// ========================================
/**
* @brief
* @param size
*/
void setSize(const Vec2& size);
/**
* @brief
* @param width
* @param height
*/
void setSize(float width, float height);
/**
* @brief
* @return
*/
Vec2 getSize() const { return size_; }
/**
* @brief
* @return
*/
Vec2 getAnchorOffset() const;
// ========================================
// 世界变换
// ========================================
/**
* @brief
* @return
*/
Transform getWorldTransform() const;
/**
* @brief
* @return
*/
Mat4 getWorldMatrix() const;
// ========================================
// 脏标记管理
// ========================================
/**
* @brief
*/
void setDirty();
/**
* @brief
* @return
*/
bool isDirty() const { return dirty_; }
private:
/**
* @brief
*/
void updateWorldTransform() const;
Transform local_;
mutable Transform world_;
mutable bool dirty_ = true;
Vec2 anchor_ = Vec2(0.5f, 0.5f);
Vec2 size_ = Vec2(100.0f, 100.0f);
};
} // namespace extra2d

132
include/scene/director.h Normal file
View File

@ -0,0 +1,132 @@
#pragma once
#include <scene/scene.h>
#include <types/ptr/intrusive_ptr.h>
#include <types/ptr/ref_counted.h>
#include <stack>
namespace extra2d {
// 前向声明
class CameraComponent;
/**
* @brief
*
*
*/
class Director : public RefCounted {
public:
/**
* @brief
*/
Director();
/**
* @brief
*/
~Director() override;
// 禁止拷贝
Director(const Director&) = delete;
Director& operator=(const Director&) = delete;
// ========================================
// 生命周期
// ========================================
/**
* @brief
* @return
*/
bool init();
/**
* @brief
*/
void shutdown();
// ========================================
// 场景管理
// ========================================
/**
* @brief
* @param scene
*/
void runScene(Ptr<Scene> scene);
/**
* @brief
* @param scene
*/
void replaceScene(Ptr<Scene> scene);
/**
* @brief
* @param scene
*/
void pushScene(Ptr<Scene> scene);
/**
* @brief
*/
void popScene();
/**
* @brief
*/
void end();
// ========================================
// 属性获取
// ========================================
/**
* @brief
* @return
*/
Scene* getRunningScene() const;
/**
* @brief
* @return
*/
CameraComponent* getMainCamera() const;
/**
* @brief
* @return
*/
bool isRunning() const { return running_; }
// ========================================
// 生命周期
// ========================================
/**
* @brief
* @param dt
*/
void update(float dt);
/**
* @brief
*/
void render();
/**
* @brief
* @param camera
*/
void setMainCamera(CameraComponent* camera) { mainCamera_ = camera; }
private:
Ptr<Scene> runningScene_;
std::stack<Ptr<Scene>> sceneStack_;
CameraComponent* mainCamera_ = nullptr;
bool running_ = false;
bool needEnd_ = false;
};
} // namespace extra2d

322
include/scene/node.h Normal file
View File

@ -0,0 +1,322 @@
#pragma once
#include <scene/component.h>
#include <types/math/transform.h>
#include <types/math/vec2.h>
#include <types/math/mat4.h>
#include <types/ptr/intrusive_ptr.h>
#include <string>
#include <vector>
namespace extra2d {
// 前向声明
class Scene;
class TransformComponent;
/**
* @brief
*
*
*/
class Node : public RefCounted {
public:
/**
* @brief
*/
Node();
/**
* @brief
*/
~Node() override;
// 禁止拷贝
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
// ========================================
// 层级关系
// ========================================
/**
* @brief
* @param child
*/
void addChild(Ptr<Node> child);
/**
* @brief
* @param child
*/
void removeChild(Node* child);
/**
* @brief
*/
void removeFromParent();
/**
* @brief
* @return
*/
Node* getParent() const { return parent_; }
/**
* @brief
* @return
*/
const std::vector<Ptr<Node>>& getChildren() const { return children_; }
/**
* @brief
*/
void removeAllChildren();
// ========================================
// 组件管理
// ========================================
/**
* @brief
* @tparam T
* @param component
* @return
*/
template<typename T>
T* addComponent(Ptr<T> component) {
static_assert(std::is_base_of<Component, T>::value, "T must be derived from Component");
T* ptr = component.get();
components_.push_back(component);
ptr->onAttach(this);
// 如果是 TransformComponent缓存它
if (auto* transform = dynamic_cast<TransformComponent*>(ptr)) {
transform_ = transform;
}
return ptr;
}
/**
* @brief
* @tparam T
* @return nullptr
*/
template<typename T>
T* getComponent() const {
for (const auto& comp : components_) {
if (auto* ptr = dynamic_cast<T*>(comp.get())) {
return ptr;
}
}
return nullptr;
}
/**
* @brief
* @tparam T
*/
template<typename T>
void removeComponent() {
for (auto it = components_.begin(); it != components_.end(); ++it) {
if (dynamic_cast<T*>(it->get())) {
(*it)->onDetach();
components_.erase(it);
return;
}
}
}
// ========================================
// 变换便捷接口
// ========================================
/**
* @brief
* @param pos
*/
void setPosition(const Vec2& pos);
/**
* @brief
* @param x X坐标
* @param y Y坐标
*/
void setPosition(float x, float y);
/**
* @brief
* @return
*/
Vec2 getPosition() const;
/**
* @brief
* @param rot
*/
void setRotation(float rot);
/**
* @brief
* @return
*/
float getRotation() const;
/**
* @brief
* @param scale
*/
void setScale(const Vec2& scale);
/**
* @brief
* @param scale
*/
void setScale(float scale);
/**
* @brief
* @param x X轴缩放
* @param y Y轴缩放
*/
void setScale(float x, float y);
/**
* @brief
* @return
*/
Vec2 getScale() const;
/**
* @brief
* @param size
*/
void setSize(const Vec2& size);
/**
* @brief
* @param width
* @param height
*/
void setSize(float width, float height);
/**
* @brief
* @return
*/
Vec2 getSize() const;
/**
* @brief
* @param anchor [0,1]
*/
void setAnchor(const Vec2& anchor);
/**
* @brief
* @param x X锚点
* @param y Y锚点
*/
void setAnchor(float x, float y);
/**
* @brief
* @return
*/
Vec2 getAnchor() const;
/**
* @brief
* @return
*/
Transform getWorldTransform() const;
/**
* @brief
* @return
*/
Mat4 getWorldMatrix() const;
// ========================================
// 属性
// ========================================
/**
* @brief
* @param name
*/
void setName(const std::string& name) { name_ = name; }
/**
* @brief
* @return
*/
const std::string& getName() const { return name_; }
/**
* @brief
* @param tag
*/
void setTag(int32 tag) { tag_ = tag; }
/**
* @brief
* @return
*/
int32 getTag() const { return tag_; }
/**
* @brief
* @param visible
*/
void setVisible(bool visible) { visible_ = visible; }
/**
* @brief
* @return
*/
bool isVisible() const { return visible_; }
/**
* @brief TransformComponent
* @return TransformComponent
*/
TransformComponent* getTransform() const { return transform_; }
// ========================================
// 生命周期
// ========================================
/**
* @brief
*/
void onEnter();
/**
* @brief
*/
void onExit();
/**
* @brief
* @param dt
*/
void update(float dt);
/**
* @brief
*/
void render();
private:
std::string name_;
int32 tag_ = 0;
bool visible_ = true;
Node* parent_ = nullptr;
std::vector<Ptr<Node>> children_;
std::vector<Ptr<Component>> components_;
TransformComponent* transform_ = nullptr;
};
} // namespace extra2d

122
include/scene/scene.h Normal file
View File

@ -0,0 +1,122 @@
#pragma once
#include <scene/node.h>
#include <types/ptr/intrusive_ptr.h>
#include <string>
#include <vector>
namespace extra2d {
// 前向声明
class CameraComponent;
/**
* @brief
*
*
*/
class Scene : public RefCounted {
public:
/**
* @brief
*/
Scene();
/**
* @brief
*/
~Scene() override;
// 禁止拷贝
Scene(const Scene&) = delete;
Scene& operator=(const Scene&) = delete;
// ========================================
// 节点管理
// ========================================
/**
* @brief
* @param node
*/
void addChild(Ptr<Node> node);
/**
* @brief
* @param node
*/
void removeChild(Node* node);
/**
* @brief
*/
void removeAllChildren();
/**
* @brief
* @return
*/
const std::vector<Ptr<Node>>& getChildren() const { return rootNodes_; }
/**
* @brief
* @param name
* @return nullptr
*/
Node* findNode(const std::string& name);
/**
* @brief
* @param tag
* @return nullptr
*/
Node* findNodeByTag(int32 tag);
// ========================================
// 相机管理
// ========================================
/**
* @brief
* @param camera
*/
void setMainCamera(CameraComponent* camera);
/**
* @brief
* @return
*/
CameraComponent* getMainCamera() const { return mainCamera_; }
// ========================================
// 生命周期
// ========================================
/**
* @brief
*/
virtual void onEnter();
/**
* @brief 退
*/
virtual void onExit();
/**
* @brief
* @param dt
*/
virtual void update(float dt);
/**
* @brief
*/
virtual void render();
protected:
std::vector<Ptr<Node>> rootNodes_;
CameraComponent* mainCamera_ = nullptr;
bool entered_ = false;
};
} // namespace extra2d

View File

@ -0,0 +1,50 @@
#pragma once
#include <event/events.h>
#include <module/module.h>
#include <module/module_registry.h>
#include <scene/director.h>
namespace extra2d {
/**
* @brief
*
*
*/
class SceneModule : public Module {
// 自动注册到模块系统,优先级为 4在 Renderer 之后)
E2D_REGISTER_MODULE(SceneModule, "Scene", 4)
public:
SceneModule();
~SceneModule() override;
// 禁止拷贝
SceneModule(const SceneModule &) = delete;
SceneModule &operator=(const SceneModule &) = delete;
// 允许移动
SceneModule(SceneModule &&) noexcept;
SceneModule &operator=(SceneModule &&) noexcept;
// Module 接口实现
bool init() override;
void shutdown() override;
/**
* @brief
* @return
*/
Director *getDirector() const { return director_.get(); }
private:
Ptr<Director> director_;
// 事件监听器
events::OnUpdate::Listener onUpdateListener_;
events::OnRenderBegin::Listener onRenderBeginListener_;
events::OnRenderEnd::Listener onRenderEndListener_;
};
} // namespace extra2d

14
include/types/math/mat4.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <glm/mat4x4.hpp>
namespace extra2d {
/**
* @brief 4x4
*
* 使 glm::mat4
*/
using Mat4 = glm::mat4;
} // namespace extra2d

View File

@ -2,6 +2,9 @@
#include <types/base/types.h> #include <types/base/types.h>
#include <types/math/vec2.h> #include <types/math/vec2.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <cmath> #include <cmath>
namespace extra2d { namespace extra2d {
@ -27,18 +30,28 @@ struct Transform {
bool operator!=(const Transform& o) const { return !(*this == o); } bool operator!=(const Transform& o) const { return !(*this == o); }
/** /**
* @brief 4x4 * @brief 4x4 使 glm
* @param out 16float * @param out 16float
*
* -> ->
* v' = T * R * S * v
*/ */
void toMatrix(float* out) const { void toMatrix(float* out) const {
float c = std::cos(rot); // 使用 glm 构建矩阵
float s = std::sin(rot); // glm::translate(matrix, v) 返回 matrix * T
// 所以顺序是:先平移,再旋转,最后缩放
// 结果是 T * R * S对于列向量 vv' = T * R * S * v
// 这意味着先应用 S然后 R然后 T正确的顺序
glm::mat4 matrix = glm::mat4(1.0f);
matrix = glm::translate(matrix, glm::vec3(pos.x, pos.y, 0.0f));
matrix = glm::rotate(matrix, rot, glm::vec3(0.0f, 0.0f, 1.0f));
matrix = glm::scale(matrix, glm::vec3(scale.x, scale.y, 1.0f));
// 列主序矩阵 // 复制到输出数组glm 已经是列主序)
out[0] = scale.x * c; out[1] = scale.x * s; out[2] = 0.0f; out[3] = 0.0f; const float* src = glm::value_ptr(matrix);
out[4] = -scale.y * s; out[5] = scale.y * c; out[6] = 0.0f; out[7] = 0.0f; for (int i = 0; i < 16; ++i) {
out[8] = 0.0f; out[9] = 0.0f; out[10] = 1.0f; out[11] = 0.0f; out[i] = src[i];
out[12] = pos.x; out[13] = pos.y; out[14] = 0.0f; out[15] = 1.0f; }
} }
static const Transform Identity; static const Transform Identity;

View File

@ -1,31 +1,36 @@
#version 320 es #version 320 es
precision mediump float; precision highp float;
// 材质 UBO // 从顶点着色器输入
layout(std140, binding = 1) uniform MaterialUBO {
vec4 tintColor;
float opacity;
} uMaterial;
// 输入从顶点着色器
in vec2 vTexCoord; in vec2 vTexCoord;
in vec4 vColor; in vec4 vColor;
// 纹理采样器 // 纹理采样器
uniform sampler2D uTexture; uniform sampler2D uTexture;
// 材质参数
uniform vec4 uTintColor;
uniform float uOpacity;
// 输出颜色 // 输出颜色
out vec4 fragColor; out vec4 fragColor;
/**
* @brief
*
*
*/
void main() { void main() {
// 采样纹理 // 采样纹理
vec4 texColor = texture(uTexture, vTexCoord); vec4 texColor = texture(uTexture, vTexCoord);
// 应用顶点颜色、材质颜色和透明度 // 混合:纹理 * 顶点颜色 * 色调
fragColor = texColor * vColor * uMaterial.tintColor; fragColor = texColor * vColor * uTintColor;
fragColor.a *= uMaterial.opacity;
// 丢弃完全透明的像素 // 应用透明度
fragColor.a *= uOpacity;
// Alpha 测试:丢弃几乎透明的像素
if (fragColor.a < 0.01) { if (fragColor.a < 0.01) {
discard; discard;
} }

View File

@ -1,16 +1,14 @@
#version 320 es #version 320 es
precision highp float;
// 全局 UBO // 视图投影矩阵
layout(std140, binding = 0) uniform GlobalUBO { uniform mat4 uViewProjection;
mat4 viewProjection;
vec2 screenSize;
float time;
} uGlobal;
// 实例 UBO // 模型矩阵
layout(std140, binding = 2) uniform InstanceUBO { uniform mat4 uModelMatrix;
mat4 transforms[256];
} uInstances; // 顶点颜色(覆盖顶点属性中的颜色)
uniform vec4 uColor;
// 顶点属性 // 顶点属性
layout(location = 0) in vec2 aPosition; layout(location = 0) in vec2 aPosition;
@ -21,14 +19,15 @@ layout(location = 2) in vec4 aColor;
out vec2 vTexCoord; out vec2 vTexCoord;
out vec4 vColor; out vec4 vColor;
/**
* @brief
*
*
*
*/
void main() { void main() {
// 获取实例变换矩阵 gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
mat4 instanceTransform = uInstances.transforms[gl_InstanceID];
// 计算最终位置
gl_Position = uGlobal.viewProjection * instanceTransform * vec4(aPosition, 0.0, 1.0);
// 传递纹理坐标和颜色
vTexCoord = aTexCoord; vTexCoord = aTexCoord;
vColor = aColor; // 使用 uniform 颜色覆盖顶点属性颜色
vColor = uColor;
} }

View File

@ -79,6 +79,9 @@ void Context::tick(float dt) {
// 3. 渲染结束并执行绘制 // 3. 渲染结束并执行绘制
events::OnRenderEnd::emit(); events::OnRenderEnd::emit();
// 4. 呈现渲染结果(交换缓冲区)
events::OnRenderPresent::emit();
} }
ModuleRegistry &Context::modules() { return ModuleRegistry::instance(); } ModuleRegistry &Context::modules() { return ModuleRegistry::instance(); }

View File

@ -49,6 +49,11 @@ bool WindowModule::init() {
configListener_->bind( configListener_->bind(
[this](const AppConfig &config) { this->onModuleConfig(config); }); [this](const AppConfig &config) { this->onModuleConfig(config); });
// 监听渲染呈现事件
renderPresentListener_ =
std::make_unique<events::OnRenderPresent::Listener>();
renderPresentListener_->bind([this]() { this->onRenderPresent(); });
return true; return true;
} }
@ -90,6 +95,7 @@ bool WindowModule::pollEvents() {
while (SDL_PollEvent(&evt)) { while (SDL_PollEvent(&evt)) {
switch (evt.type) { switch (evt.type) {
case SDL_QUIT: case SDL_QUIT:
E2D_LOG_INFO("SDL_QUIT event received");
shouldClose_ = true; shouldClose_ = true;
// 发送窗口关闭事件 // 发送窗口关闭事件
events::OnClose::emit(); events::OnClose::emit();
@ -280,6 +286,10 @@ void WindowModule::swapBuffers() {
} }
} }
void WindowModule::onRenderPresent() {
swapBuffers();
}
void *WindowModule::getGLContext() const { return glContext_; } void *WindowModule::getGLContext() const { return glContext_; }
} // namespace extra2d } // namespace extra2d

View File

@ -4,10 +4,17 @@
namespace extra2d { namespace extra2d {
// ========================================
// MaterialLayout 实现 // MaterialLayout 实现
// ========================================
MaterialLayout::MaterialLayout() = default; MaterialLayout::MaterialLayout() = default;
/**
* @brief
* @param name
* @param type
*/
void MaterialLayout::addParam(const std::string& name, MaterialParamType type) { void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
if (finalized_) { if (finalized_) {
E2D_LOG_WARN("Cannot add param to finalized MaterialLayout"); E2D_LOG_WARN("Cannot add param to finalized MaterialLayout");
@ -22,27 +29,42 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
params_[name] = info; params_[name] = info;
} }
/**
* @brief std140
*
* std140
* -
* - 16
* - vec3 16
*/
void MaterialLayout::finalize() { void MaterialLayout::finalize() {
if (finalized_) return; if (finalized_) return;
// 计算 std140 布局的偏移
// std140 规则:
// - 标量和向量:偏移必须是其大小的倍数
// - 数组和结构体:偏移必须是 16 的倍数
// - vec3 后面需要填充到 16 字节边界
uint32_t offset = 0; uint32_t offset = 0;
for (auto& pair : params_) { for (auto& pair : params_) {
auto& info = pair.second; auto& info = pair.second;
// 对齐到参数大小的倍数 // 计算对齐要求
uint32_t alignment = info.size; uint32_t alignment = 4; // 默认 4 字节对齐
if (info.type == MaterialParamType::Mat4) {
alignment = 16; // mat4 需要 16 字节对齐 switch (info.type) {
} else if (info.type == MaterialParamType::Vec3) { case MaterialParamType::Float:
alignment = 16; // vec3 在 std140 中占 16 字节 alignment = 4; // float: 4 字节对齐
} else if (alignment < 4) { break;
alignment = 4; // 最小 4 字节对齐 case MaterialParamType::Vec2:
alignment = 8; // vec2: 8 字节对齐
break;
case MaterialParamType::Vec3:
case MaterialParamType::Vec4:
case MaterialParamType::Color:
alignment = 16; // vec3/vec4/color: 16 字节对齐std140 规则)
break;
case MaterialParamType::Mat4:
alignment = 16; // mat4: 16 字节对齐
break;
default:
alignment = 4;
break;
} }
// 对齐偏移 // 对齐偏移
@ -57,6 +79,11 @@ void MaterialLayout::finalize() {
finalized_ = true; finalized_ = true;
} }
/**
* @brief
* @param name
* @return nullptr
*/
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const { const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
auto it = params_.find(name); auto it = params_.find(name);
if (it != params_.end()) { if (it != params_.end()) {
@ -65,10 +92,16 @@ const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const
return nullptr; return nullptr;
} }
// ========================================
// Material 实现 // Material 实现
// ========================================
Material::Material() = default; Material::Material() = default;
/**
* @brief
* @param layout
*/
void Material::setLayout(Ptr<MaterialLayout> layout) { void Material::setLayout(Ptr<MaterialLayout> layout) {
layout_ = layout; layout_ = layout;
if (layout_ && layout_->isFinalized()) { if (layout_ && layout_->isFinalized()) {
@ -76,10 +109,19 @@ void Material::setLayout(Ptr<MaterialLayout> layout) {
} }
} }
/**
* @brief
* @param shader
*/
void Material::setShader(Ptr<Shader> shader) { void Material::setShader(Ptr<Shader> shader) {
shader_ = shader; shader_ = shader;
} }
/**
* @brief float
* @param name
* @param value
*/
void Material::setFloat(const std::string& name, float value) { void Material::setFloat(const std::string& name, float value) {
if (!layout_) return; if (!layout_) return;
@ -89,6 +131,11 @@ void Material::setFloat(const std::string& name, float value) {
} }
} }
/**
* @brief vec2
* @param name
* @param value
*/
void Material::setVec2(const std::string& name, const Vec2& value) { void Material::setVec2(const std::string& name, const Vec2& value) {
if (!layout_) return; if (!layout_) return;
@ -98,6 +145,14 @@ void Material::setVec2(const std::string& name, const Vec2& value) {
} }
} }
/**
* @brief vec4
* @param name
* @param x X
* @param y Y
* @param z Z
* @param w W
*/
void Material::setVec4(const std::string& name, float x, float y, float z, float w) { void Material::setVec4(const std::string& name, float x, float y, float z, float w) {
if (!layout_) return; if (!layout_) return;
@ -108,6 +163,11 @@ void Material::setVec4(const std::string& name, float x, float y, float z, float
} }
} }
/**
* @brief
* @param name
* @param value
*/
void Material::setColor(const std::string& name, const Color& value) { void Material::setColor(const std::string& name, const Color& value) {
if (!layout_) return; if (!layout_) return;
@ -117,6 +177,11 @@ void Material::setColor(const std::string& name, const Color& value) {
} }
} }
/**
* @brief mat4
* @param name
* @param value
*/
void Material::setMat4(const std::string& name, const float* value) { void Material::setMat4(const std::string& name, const float* value) {
if (!layout_) return; if (!layout_) return;
@ -126,17 +191,50 @@ void Material::setMat4(const std::string& name, const float* value) {
} }
} }
void Material::setTexture(const std::string& name, TextureHandle texture, uint32_t slot) { /**
// 纹理存储在单独的列表中 * @brief
textures_.push_back({texture, slot}); * @param uniformName uniform
* @param texture
* @param slot 0-15
*/
void Material::setTexture(const std::string& uniformName, TextureHandle texture, uint32_t slot) {
// 查找是否已存在相同名称的纹理
for (auto& texSlot : textures_) {
if (texSlot.uniformName == uniformName) {
texSlot.handle = texture;
texSlot.slot = slot;
return;
}
}
// 添加新的纹理槽位
textures_.push_back({texture, slot, uniformName});
} }
/**
* @brief
*/
void Material::clearTextures() {
textures_.clear();
}
/**
* @brief
*
* UBO
* @param uboManager UBO
*/
void Material::apply(UniformBufferManager& uboManager) { void Material::apply(UniformBufferManager& uboManager) {
if (!shader_) return; if (!shader_) return;
// 绑定着色器 // 绑定着色器
shader_->bind(); shader_->bind();
// 设置 Uniform Block 绑定
shader_->setUniformBlock("GlobalUBO", GLOBAL_UBO_BINDING);
shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING);
shader_->setUniformBlock("InstanceUBO", INSTANCE_UBO_BINDING);
// 上传材质数据到 UBO // 上传材质数据到 UBO
if (!data_.empty()) { if (!data_.empty()) {
auto* ubo = uboManager.acquireMaterialUBO(static_cast<uint32_t>(data_.size())); auto* ubo = uboManager.acquireMaterialUBO(static_cast<uint32_t>(data_.size()));
@ -145,14 +243,6 @@ void Material::apply(UniformBufferManager& uboManager) {
ubo->bind(MATERIAL_UBO_BINDING); ubo->bind(MATERIAL_UBO_BINDING);
} }
} }
// 设置 Uniform Block 绑定(如果着色器支持)
shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING);
// TODO: 绑定纹理
// for (const auto& [texture, slot] : textures_) {
// // 通过纹理句柄获取纹理并绑定
// }
} }
} // namespace extra2d } // namespace extra2d

View File

@ -25,6 +25,8 @@ void Mesh::setVertices(const Vertex* vertices, uint32_t count) {
if (vao_ == 0) { if (vao_ == 0) {
glGenVertexArrays(1, &vao_); glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_); glGenBuffers(1, &vbo_);
E2D_LOG_INFO("Created VAO: {}, VBO: {}", vao_, vbo_);
} }
glBindVertexArray(vao_); glBindVertexArray(vao_);
@ -56,6 +58,8 @@ void Mesh::setVertices(const Vertex* vertices, uint32_t count) {
glBindVertexArray(0); glBindVertexArray(0);
vertexCount_ = count; vertexCount_ = count;
E2D_LOG_INFO("Set {} vertices for VAO {}", count, vao_);
} }
void Mesh::setIndices(const uint16_t* indices, uint32_t count) { void Mesh::setIndices(const uint16_t* indices, uint32_t count) {
@ -102,11 +106,31 @@ void Mesh::unbind() const {
void Mesh::draw() const { void Mesh::draw() const {
if (vao_ == 0) return; if (vao_ == 0) return;
// 检查 OpenGL 错误
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
static bool loggedOnce = false;
if (!loggedOnce) {
E2D_LOG_ERROR("OpenGL error before draw: {}", err);
loggedOnce = true;
}
}
if (indexCount_ > 0) { if (indexCount_ > 0) {
glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr); glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr);
} else { } else {
glDrawArrays(GL_TRIANGLES, 0, vertexCount_); glDrawArrays(GL_TRIANGLES, 0, vertexCount_);
} }
// 检查绘制后的错误
err = glGetError();
if (err != GL_NO_ERROR) {
static bool loggedOnce2 = false;
if (!loggedOnce2) {
E2D_LOG_ERROR("OpenGL error after draw: {}", err);
loggedOnce2 = true;
}
}
} }
void Mesh::drawInstanced(uint32_t instanceCount) const { void Mesh::drawInstanced(uint32_t instanceCount) const {

View File

@ -1,8 +1,12 @@
#include <algorithm> #include <algorithm>
#include <event/events.h> #include <event/events.h>
#include <glm/gtc/type_ptr.hpp>
#include <renderer/renderer_module.h> #include <renderer/renderer_module.h>
#include <utils/logger.h> #include <utils/logger.h>
// SDL for window size query
#include <SDL.h>
namespace extra2d { namespace extra2d {
RendererModule::RendererModule() = default; RendererModule::RendererModule() = default;
@ -93,6 +97,8 @@ bool RendererModule::init() {
onRenderBeginListener_.bind([this]() { onRenderBegin(); }); onRenderBeginListener_.bind([this]() { onRenderBegin(); });
onRenderSubmitListener_.bind( onRenderSubmitListener_.bind(
[this](const RenderCommand &cmd) { onRenderSubmit(cmd); }); [this](const RenderCommand &cmd) { onRenderSubmit(cmd); });
onRenderSetCameraListener_.bind(
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
onRenderEndListener_.bind([this]() { onRenderEnd(); }); onRenderEndListener_.bind([this]() { onRenderEnd(); });
onResizeListener_.bind([this](int32_t w, int32_t h) { onResize(w, h); }); onResizeListener_.bind([this](int32_t w, int32_t h) { onResize(w, h); });
@ -122,11 +128,23 @@ void RendererModule::onWindowShow() {
return; return;
} }
// 设置默认视口 // 获取实际窗口大小并设置视口
setViewport(0, 0, 800, 600); // 查询当前 SDL 窗口大小
int windowWidth = 800, windowHeight = 600;
SDL_Window *sdlWindow = SDL_GL_GetCurrentWindow();
if (sdlWindow) {
SDL_GetWindowSize(sdlWindow, &windowWidth, &windowHeight);
E2D_LOG_INFO("Setting initial viewport to window size: {}x{}", windowWidth,
windowHeight);
} else {
E2D_LOG_WARN("Could not get SDL window, using default viewport 800x600");
}
setViewport(0, 0, static_cast<int32>(windowWidth),
static_cast<int32>(windowHeight));
// 启用深度测试和混合 // 禁用深度测试和背面剔除2D渲染不需要
glEnable(GL_DEPTH_TEST); // glDisable(GL_DEPTH_TEST);
// glDisable(GL_CULL_FACE);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -223,7 +241,16 @@ void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) {
viewportY_ = y; viewportY_ = y;
viewportWidth_ = width; viewportWidth_ = width;
viewportHeight_ = height; viewportHeight_ = height;
glViewport(x, y, width, height);
// 更新视口适配器
viewportAdapter_.update(width, height);
// 获取适配后的视口
auto result = viewportAdapter_.getResult();
glViewport(static_cast<GLint>(result.viewport.x),
static_cast<GLint>(result.viewport.y),
static_cast<GLsizei>(result.viewport.w),
static_cast<GLsizei>(result.viewport.h));
} }
void RendererModule::clear(const Color &color, uint32_t flags) { void RendererModule::clear(const Color &color, uint32_t flags) {
@ -253,6 +280,10 @@ void RendererModule::onRenderBegin() {
return; return;
} }
// 清除屏幕(黑色背景)
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 清空命令缓冲区 // 清空命令缓冲区
commandCount_ = 0; commandCount_ = 0;
@ -280,6 +311,19 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
stats_.commandsSubmitted++; stats_.commandsSubmitted++;
} }
void RendererModule::onRenderSetCamera(const Mat4 &viewProj) {
// 如果 GL 未初始化,跳过
if (!glInitialized_) {
return;
}
// 保存视图投影矩阵
viewProjectionMatrix_ = viewProj;
// 更新全局 UBO 中的视图投影矩阵
uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4));
}
void RendererModule::onRenderEnd() { void RendererModule::onRenderEnd() {
// 如果 GL 未初始化,跳过渲染 // 如果 GL 未初始化,跳过渲染
if (!glInitialized_) { if (!glInitialized_) {
@ -361,50 +405,81 @@ void RendererModule::drawBatch(uint32_t start, uint32_t count,
MaterialHandle materialHandle, MaterialHandle materialHandle,
MeshHandle meshHandle) { MeshHandle meshHandle) {
auto material = getMaterial(materialHandle); auto material = getMaterial(materialHandle);
// 如果材质无效,使用默认材质
if (!material) {
material = getMaterial(defaultMaterialHandle_);
}
auto mesh = getMesh(meshHandle); auto mesh = getMesh(meshHandle);
// 如果网格无效,使用默认四边形
if (!mesh) {
mesh = getMesh(defaultQuadHandle_);
}
if (!material || !mesh) if (!material || !mesh)
return; return;
// 应用材质(绑定着色器、上传 UBO、绑定纹理 // 获取着色器
material->apply(uniformManager_); auto shader = material->getShader();
if (!shader)
return;
// 绑定着色器
shader->bind();
// 设置视图投影矩阵
shader->setMat4("uViewProjection", glm::value_ptr(viewProjectionMatrix_));
// 设置材质参数
// 注意:直接使用默认值,因为材质数据布局可能不匹配
// tintColor 和 opacity 由着色器默认值处理
shader->setVec4("uTintColor", 1.0f, 1.0f, 1.0f, 1.0f);
shader->setFloat("uOpacity", 1.0f);
// 绑定材质中的纹理
const auto &textureSlots = material->getTextures();
bool hasMaterialTexture = false;
for (const auto &slot : textureSlots) {
if (slot.handle != INVALID_TEXTURE_HANDLE) {
auto texture = getTexture(slot.handle);
if (texture) {
texture->bind(slot.slot);
// 设置采样器 uniform
shader->setInt(slot.uniformName, static_cast<int>(slot.slot));
hasMaterialTexture = true;
}
}
}
// 如果材质没有纹理,绑定默认纹理
if (!hasMaterialTexture && defaultTextureHandle_ != INVALID_TEXTURE_HANDLE) {
auto defaultTexture = getTexture(defaultTextureHandle_);
if (defaultTexture) {
defaultTexture->bind(0);
shader->setInt("uTexture", 0);
}
}
// 绑定网格 // 绑定网格
mesh->bind(); mesh->bind();
if (count == 1) { // 对每个实例单独绘制
// 单绘制 for (uint32_t i = 0; i < count; ++i) {
Transform transform = commandBuffer_[start + i].getTransform();
float matrix[16];
transform.toMatrix(matrix);
// 设置模型矩阵
shader->setMat4("uModelMatrix", matrix);
// 设置顶点颜色
Color color = commandBuffer_[start + i].getColor();
shader->setVec4("uColor", color.r, color.g, color.b, color.a);
// 绘制
mesh->draw(); mesh->draw();
} else { stats_.drawCalls++;
// 批量绘制 - 使用实例化渲染
// 上传变换矩阵到 UBO
std::vector<float> transforms;
transforms.reserve(count * 16);
for (uint32_t i = 0; i < count; ++i) {
const auto &transform = commandBuffer_[start + i].drawMesh.transform;
// 将 Transform 转换为 4x4 矩阵并添加到 transforms
// 这里简化处理,实际需要完整的矩阵转换
float matrix[16];
transform.toMatrix(matrix);
transforms.insert(transforms.end(), matrix, matrix + 16);
}
// 更新实例 UBO
auto *instanceUBO = uniformManager_.acquireMaterialUBO(
static_cast<uint32_t>(transforms.size() * sizeof(float)));
if (instanceUBO) {
instanceUBO->update(
transforms.data(),
static_cast<uint32_t>(transforms.size() * sizeof(float)));
instanceUBO->bind(INSTANCE_UBO_BINDING);
}
// 实例化渲染
mesh->drawInstanced(count);
} }
stats_.drawCalls++;
} }
void RendererModule::executeCommand(const RenderCommand &cmd) { void RendererModule::executeCommand(const RenderCommand &cmd) {

View File

@ -52,21 +52,27 @@ bool Shader::loadFromSource(const std::string& vsSource, const std::string& fsSo
std::string processedVS = addVersionIfNeeded(vsSource, GL_VERTEX_SHADER); std::string processedVS = addVersionIfNeeded(vsSource, GL_VERTEX_SHADER);
std::string processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER); std::string processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER);
E2D_LOG_INFO("Compiling vertex shader...");
// 编译顶点着色器 // 编译顶点着色器
GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS); GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS);
if (vs == 0) { if (vs == 0) {
E2D_LOG_ERROR("Vertex shader compilation failed");
return false; return false;
} }
E2D_LOG_INFO("Compiling fragment shader...");
// 编译片段着色器 // 编译片段着色器
GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS); GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS);
if (fs == 0) { if (fs == 0) {
E2D_LOG_ERROR("Fragment shader compilation failed");
glDeleteShader(vs); glDeleteShader(vs);
return false; return false;
} }
E2D_LOG_INFO("Linking shader program...");
// 链接程序 // 链接程序
if (!linkProgram(vs, fs)) { if (!linkProgram(vs, fs)) {
E2D_LOG_ERROR("Shader program linking failed");
glDeleteShader(vs); glDeleteShader(vs);
glDeleteShader(fs); glDeleteShader(fs);
return false; return false;
@ -76,7 +82,7 @@ bool Shader::loadFromSource(const std::string& vsSource, const std::string& fsSo
glDeleteShader(vs); glDeleteShader(vs);
glDeleteShader(fs); glDeleteShader(fs);
E2D_LOG_DEBUG("Shader program created successfully"); E2D_LOG_INFO("Shader program created successfully, program ID: {}", program_);
return true; return true;
} }
@ -131,6 +137,19 @@ void Shader::setMat4(const std::string& name, const float* value) {
GLint location = getUniformLocation(name); GLint location = getUniformLocation(name);
if (location != -1) { if (location != -1) {
glUniformMatrix4fv(location, 1, GL_FALSE, value); glUniformMatrix4fv(location, 1, GL_FALSE, value);
// 调试输出设置成功的uniform
static int logCount = 0;
if (logCount < 3) {
E2D_LOG_INFO("Set uniform '{}' at location {}", name, location);
logCount++;
}
} else {
// 调试输出未找到的uniform
static bool loggedOnce = false;
if (!loggedOnce) {
E2D_LOG_WARN("Uniform '{}' not found in shader", name);
loggedOnce = true;
}
} }
} }

View File

@ -25,6 +25,7 @@ bool UniformBuffer::create(uint32_t size, uint32_t binding) {
glBindBuffer(GL_UNIFORM_BUFFER, ubo_); glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubo_);
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBuffer(GL_UNIFORM_BUFFER, 0);
E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding); E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding);
@ -142,6 +143,7 @@ void UniformBufferManager::resetMaterialUBOs() {
void UniformBufferManager::updateGlobalUBO(const void* data, uint32_t size) { void UniformBufferManager::updateGlobalUBO(const void* data, uint32_t size) {
if (globalUBO_) { if (globalUBO_) {
globalUBO_->update(data, size); globalUBO_->update(data, size);
globalUBO_->bind(GLOBAL_UBO_BINDING);
} }
} }

View File

@ -0,0 +1,431 @@
#include <algorithm>
#include <glm/gtc/matrix_transform.hpp>
#include <renderer/viewport_adapter.h>
namespace extra2d {
/**
* @brief
*/
ViewportAdapter::ViewportAdapter() = default;
/**
* @brief
* @param logicWidth
* @param logicHeight
*/
ViewportAdapter::ViewportAdapter(float logicWidth, float logicHeight) {
config_.logicWidth = logicWidth;
config_.logicHeight = logicHeight;
}
/**
* @brief
* @param config
*/
void ViewportAdapter::setConfig(const ViewportConfig &config) {
config_ = config;
matrixDirty_ = true;
}
/**
* @brief
* @param width
* @param height
*/
void ViewportAdapter::setLogicSize(float width, float height) {
config_.logicWidth = width;
config_.logicHeight = height;
matrixDirty_ = true;
}
/**
* @brief
* @param mode
*/
void ViewportAdapter::setMode(ViewportMode mode) {
config_.mode = mode;
matrixDirty_ = true;
}
/**
* @brief
* @param position
*/
void ViewportAdapter::setLetterboxPosition(LetterboxPosition position) {
config_.letterboxPosition = position;
matrixDirty_ = true;
}
/**
* @brief
* @param color
*/
void ViewportAdapter::setLetterboxColor(const Color &color) {
config_.letterboxColor = color;
}
/**
* @brief
* @param screenWidth
* @param screenHeight
*/
void ViewportAdapter::update(int screenWidth, int screenHeight) {
if (screenWidth_ == screenWidth && screenHeight_ == screenHeight &&
!matrixDirty_) {
return;
}
screenWidth_ = screenWidth;
screenHeight_ = screenHeight;
matrixDirty_ = true;
result_.hasLetterbox = false;
result_.letterbox.top = Rect::Zero;
result_.letterbox.bottom = Rect::Zero;
result_.letterbox.left = Rect::Zero;
result_.letterbox.right = Rect::Zero;
switch (config_.mode) {
case ViewportMode::AspectRatio:
calculateAspectRatio();
break;
case ViewportMode::Stretch:
calculateStretch();
break;
case ViewportMode::Center:
calculateCenter();
break;
case ViewportMode::Custom:
calculateCustom();
break;
}
}
/**
* @brief
*
*
*/
void ViewportAdapter::calculateAspectRatio() {
if (config_.logicHeight <= 0.0f || screenHeight_ <= 0) {
result_ = ViewportResult();
return;
}
float logicAspect = config_.logicWidth / config_.logicHeight;
float screenAspect =
static_cast<float>(screenWidth_) / static_cast<float>(screenHeight_);
if (screenAspect > logicAspect) {
// 屏幕更宽,以高度为基准
result_.uniformScale =
static_cast<float>(screenHeight_) / config_.logicHeight;
result_.scaleX = result_.uniformScale;
result_.scaleY = result_.uniformScale;
result_.viewport.w = config_.logicWidth * result_.uniformScale;
result_.viewport.h = static_cast<float>(screenHeight_);
result_.offset.x = (screenWidth_ - result_.viewport.w) / 2.0f;
result_.offset.y = 0.0f;
} else {
// 屏幕更高,以宽度为基准
result_.uniformScale =
static_cast<float>(screenWidth_) / config_.logicWidth;
result_.scaleX = result_.uniformScale;
result_.scaleY = result_.uniformScale;
result_.viewport.w = static_cast<float>(screenWidth_);
result_.viewport.h = config_.logicHeight * result_.uniformScale;
result_.offset.x = 0.0f;
result_.offset.y = (screenHeight_ - result_.viewport.h) / 2.0f;
}
result_.viewport.x = result_.offset.x;
result_.viewport.y = result_.offset.y;
applyLetterboxPosition(static_cast<float>(screenWidth_) - result_.viewport.w,
static_cast<float>(screenHeight_) -
result_.viewport.h);
calculateLetterbox();
}
/**
* @brief
*
*
*/
void ViewportAdapter::calculateStretch() {
result_.scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
result_.scaleY = static_cast<float>(screenHeight_) / config_.logicHeight;
result_.uniformScale = std::min(result_.scaleX, result_.scaleY);
result_.viewport.x = 0.0f;
result_.viewport.y = 0.0f;
result_.viewport.w = static_cast<float>(screenWidth_);
result_.viewport.h = static_cast<float>(screenHeight_);
result_.offset = Vec2::Zero;
result_.hasLetterbox = false;
}
/**
* @brief
*
*
*/
void ViewportAdapter::calculateCenter() {
float displayWidth = config_.logicWidth;
float displayHeight = config_.logicHeight;
if (config_.autoScaleInCenterMode) {
float scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
float scaleY = static_cast<float>(screenHeight_) / config_.logicHeight;
result_.uniformScale = std::min(scaleX, scaleY);
if (result_.uniformScale < 1.0f) {
displayWidth = config_.logicWidth * result_.uniformScale;
displayHeight = config_.logicHeight * result_.uniformScale;
} else {
result_.uniformScale = 1.0f;
}
result_.scaleX = result_.uniformScale;
result_.scaleY = result_.uniformScale;
} else {
result_.scaleX = 1.0f;
result_.scaleY = 1.0f;
result_.uniformScale = 1.0f;
}
result_.offset.x = (screenWidth_ - displayWidth) / 2.0f;
result_.offset.y = (screenHeight_ - displayHeight) / 2.0f;
result_.viewport.x = result_.offset.x;
result_.viewport.y = result_.offset.y;
result_.viewport.w = displayWidth;
result_.viewport.h = displayHeight;
applyLetterboxPosition(static_cast<float>(screenWidth_) - displayWidth,
static_cast<float>(screenHeight_) - displayHeight);
calculateLetterbox();
}
/**
* @brief
*
* 使
*/
void ViewportAdapter::calculateCustom() {
result_.scaleX = config_.customScale;
result_.scaleY = config_.customScale;
result_.uniformScale = config_.customScale;
if (config_.customViewport.empty()) {
float displayWidth = config_.logicWidth * config_.customScale;
float displayHeight = config_.logicHeight * config_.customScale;
result_.offset = config_.customOffset;
result_.viewport.x = result_.offset.x;
result_.viewport.y = result_.offset.y;
result_.viewport.w = displayWidth;
result_.viewport.h = displayHeight;
} else {
result_.viewport = config_.customViewport;
result_.offset.x = config_.customViewport.x;
result_.offset.y = config_.customViewport.y;
}
calculateLetterbox();
}
/**
* @brief
*
*
*/
void ViewportAdapter::calculateLetterbox() {
result_.hasLetterbox = false;
float screenW = static_cast<float>(screenWidth_);
float screenH = static_cast<float>(screenHeight_);
if (result_.offset.y > 0.0f) {
result_.hasLetterbox = true;
result_.letterbox.top = Rect(0.0f, 0.0f, screenW, result_.offset.y);
result_.letterbox.bottom = Rect(0.0f, result_.offset.y + result_.viewport.h,
screenW, result_.offset.y);
}
if (result_.offset.x > 0.0f) {
result_.hasLetterbox = true;
result_.letterbox.left = Rect(0.0f, 0.0f, result_.offset.x, screenH);
result_.letterbox.right = Rect(result_.offset.x + result_.viewport.w, 0.0f,
result_.offset.x, screenH);
}
}
/**
* @brief
* @param extraWidth
* @param extraHeight
*/
void ViewportAdapter::applyLetterboxPosition(float extraWidth,
float extraHeight) {
if (extraWidth <= 0.0f && extraHeight <= 0.0f) {
return;
}
switch (config_.letterboxPosition) {
case LetterboxPosition::Center:
break;
case LetterboxPosition::LeftTop:
if (extraWidth > 0.0f) {
result_.offset.x = 0.0f;
}
if (extraHeight > 0.0f) {
result_.offset.y = 0.0f;
}
break;
case LetterboxPosition::RightTop:
if (extraWidth > 0.0f) {
result_.offset.x = extraWidth;
}
if (extraHeight > 0.0f) {
result_.offset.y = 0.0f;
}
break;
case LetterboxPosition::LeftBottom:
if (extraWidth > 0.0f) {
result_.offset.x = 0.0f;
}
if (extraHeight > 0.0f) {
result_.offset.y = extraHeight;
}
break;
case LetterboxPosition::RightBottom:
if (extraWidth > 0.0f) {
result_.offset.x = extraWidth;
}
if (extraHeight > 0.0f) {
result_.offset.y = extraHeight;
}
break;
}
result_.viewport.x = result_.offset.x;
result_.viewport.y = result_.offset.y;
}
/**
* @brief
* @param screenPos
* @return
*/
Vec2 ViewportAdapter::screenToLogic(const Vec2 &screenPos) const {
return Vec2((screenPos.x - result_.offset.x) / result_.scaleX,
(screenPos.y - result_.offset.y) / result_.scaleY);
}
/**
* @brief
* @param logicPos
* @return
*/
Vec2 ViewportAdapter::logicToScreen(const Vec2 &logicPos) const {
return Vec2(logicPos.x * result_.scaleX + result_.offset.x,
logicPos.y * result_.scaleY + result_.offset.y);
}
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return
*/
Vec2 ViewportAdapter::screenToLogic(float x, float y) const {
return screenToLogic(Vec2(x, y));
}
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return
*/
Vec2 ViewportAdapter::logicToScreen(float x, float y) const {
return logicToScreen(Vec2(x, y));
}
/**
* @brief
* @return
*/
glm::mat4 ViewportAdapter::getMatrix() const {
if (matrixDirty_) {
viewportMatrix_ = glm::mat4(1.0f);
viewportMatrix_ = glm::translate(
viewportMatrix_, glm::vec3(result_.offset.x, result_.offset.y, 0.0f));
viewportMatrix_ = glm::scale(
viewportMatrix_, glm::vec3(result_.scaleX, result_.scaleY, 1.0f));
matrixDirty_ = false;
}
return viewportMatrix_;
}
/**
* @brief
* @return
*/
glm::mat4 ViewportAdapter::getInvMatrix() const {
if (matrixDirty_) {
getMatrix();
}
inverseViewportMatrix_ = glm::inverse(viewportMatrix_);
return inverseViewportMatrix_;
}
/**
* @brief
* @param screenPos
* @return true
*/
bool ViewportAdapter::isInViewport(const Vec2 &screenPos) const {
return result_.viewport.contains(screenPos);
}
/**
* @brief
* @param screenPos
* @return true
*/
bool ViewportAdapter::isInLetterbox(const Vec2 &screenPos) const {
if (!result_.hasLetterbox) {
return false;
}
if (!result_.letterbox.top.empty() &&
result_.letterbox.top.contains(screenPos)) {
return true;
}
if (!result_.letterbox.bottom.empty() &&
result_.letterbox.bottom.contains(screenPos)) {
return true;
}
if (!result_.letterbox.left.empty() &&
result_.letterbox.left.contains(screenPos)) {
return true;
}
if (!result_.letterbox.right.empty() &&
result_.letterbox.right.contains(screenPos)) {
return true;
}
return false;
}
} // namespace extra2d

42
src/scene/component.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <scene/component.h>
#include <scene/node.h>
namespace extra2d {
void Component::onAttach(Node* owner) {
owner_ = owner;
}
void Component::onDetach() {
owner_ = nullptr;
}
void Component::onEnable() {
// 子类可重写
}
void Component::onDisable() {
// 子类可重写
}
void Component::update(float dt) {
// 子类可重写
(void)dt;
}
void Component::render() {
// 子类可重写
}
void Component::setEnabled(bool enabled) {
if (enabled_ != enabled) {
enabled_ = enabled;
if (enabled_) {
onEnable();
} else {
onDisable();
}
}
}
} // namespace extra2d

View File

@ -0,0 +1,313 @@
#include <algorithm>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <scene/components/camera_component.h>
#include <scene/node.h>
#include <utils/logger.h>
namespace extra2d {
/**
* @brief
*
* (0, 0) (800, 600)
*/
CameraComponent::CameraComponent() { setOrtho(0, 800, 600, 0, -1, 1); }
/**
* @brief
* @param type
*/
void CameraComponent::setProjectionType(ProjectionType type) {
projType_ = type;
markProjDirty();
}
/**
* @brief
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*
* 2D游戏Y轴向下增长
* (bottom > top) Y轴向下
*/
void CameraComponent::setOrtho(float left, float right, float bottom, float top,
float near, float far) {
projType_ = ProjectionType::Orthographic;
left_ = left;
right_ = right;
bottom_ = bottom;
top_ = top;
near_ = near;
far_ = far;
markProjDirty();
E2D_LOG_DEBUG("Ortho projection set: L={}, R={}, B={}, T={}, N={}, F={}",
left, right, bottom, top, near, far);
}
/**
* @brief
* @param fov
* @param aspect
* @param near
* @param far
*/
void CameraComponent::setPerspective(float fov, float aspect, float near,
float far) {
projType_ = ProjectionType::Perspective;
fov_ = fov;
aspect_ = aspect;
near_ = near;
far_ = far;
markProjDirty();
}
/**
* @brief
* @param zoom 1.0
*/
void CameraComponent::setZoom(float zoom) {
zoom_ = std::max(0.001f, zoom);
markViewDirty();
markProjDirty();
}
/**
* @brief
* @param degrees
*/
void CameraComponent::setRotation(float degrees) {
rotation_ = degrees;
markViewDirty();
}
/**
* @brief
*
* -> ->
* View = T(-position) * R(-rotation) * S(1/zoom)
*/
void CameraComponent::updateViewMatrix() const {
if (!viewDirty_)
return;
viewMatrix_ = Mat4(1.0f);
// 获取相机位置(从节点获取)
Vec2 position;
if (owner_) {
position = owner_->getPosition();
}
// 1. 平移(最后应用):将相机位置移到原点
viewMatrix_ =
glm::translate(viewMatrix_, glm::vec3(-position.x, -position.y, 0.0f));
// 2. 旋转(中间应用)
if (rotation_ != 0.0f) {
constexpr float DEG_TO_RAD = 3.14159265358979323846f / 180.0f;
viewMatrix_ = glm::rotate(viewMatrix_, -rotation_ * DEG_TO_RAD,
glm::vec3(0.0f, 0.0f, 1.0f));
}
// 3. 缩放(最先应用)
if (zoom_ != 1.0f) {
viewMatrix_ =
glm::scale(viewMatrix_, glm::vec3(1.0f / zoom_, 1.0f / zoom_, 1.0f));
}
viewDirty_ = false;
}
/**
* @brief
*
* 2D游戏Y轴向下增长
* 使 glm::ortho
*/
void CameraComponent::updateProjectionMatrix() const {
if (!projDirty_)
return;
if (projType_ == ProjectionType::Orthographic) {
// 2D正交投影直接使用 setOrtho 设置的参数
// Y轴向下传入 (bottom > top)
projMatrix_ = glm::ortho(left_, right_, bottom_, top_, near_, far_);
} else {
// 透视投影
float fovRad = glm::radians(fov_);
projMatrix_ = glm::perspective(fovRad, aspect_, near_, far_);
}
projDirty_ = false;
}
/**
* @brief
* @return
*/
Mat4 CameraComponent::getViewMatrix() const {
updateViewMatrix();
return viewMatrix_;
}
/**
* @brief
* @return
*/
Mat4 CameraComponent::getProjectionMatrix() const {
updateProjectionMatrix();
return projMatrix_;
}
/**
* @brief
* @return
*/
Mat4 CameraComponent::getViewProjectionMatrix() const {
if (vpDirty_) {
vpMatrix_ = getProjectionMatrix() * getViewMatrix();
vpDirty_ = false;
}
return vpMatrix_;
}
/**
* @brief
* @param screenPos
* @return
*/
Vec2 CameraComponent::screenToWorld(const Vec2 &screenPos) const {
// 使用逆视图投影矩阵转换
Mat4 invVP = glm::inverse(getViewProjectionMatrix());
glm::vec4 ndc(screenPos.x, screenPos.y, 0.0f, 1.0f);
glm::vec4 world = invVP * ndc;
return Vec2(world.x, world.y);
}
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return
*/
Vec2 CameraComponent::screenToWorld(float x, float y) const {
return screenToWorld(Vec2(x, y));
}
/**
* @brief
* @param worldPos
* @return
*/
Vec2 CameraComponent::worldToScreen(const Vec2 &worldPos) const {
glm::vec4 world(worldPos.x, worldPos.y, 0.0f, 1.0f);
glm::vec4 screen = getViewProjectionMatrix() * world;
return Vec2(screen.x, screen.y);
}
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @return
*/
Vec2 CameraComponent::worldToScreen(float x, float y) const {
return worldToScreen(Vec2(x, y));
}
/**
* @brief
* @param viewport
*/
void CameraComponent::setViewport(const Rect &viewport) {
viewport_ = viewport;
}
/**
* @brief
* @param bounds
*/
void CameraComponent::setBounds(const Rect &bounds) {
bounds_ = bounds;
hasBounds_ = true;
}
/**
* @brief
*/
void CameraComponent::clearBounds() { hasBounds_ = false; }
/**
* @brief
*/
void CameraComponent::clampToBounds() {
if (!hasBounds_ || !owner_)
return;
float viewportWidth = (right_ - left_) / zoom_;
float viewportHeight = (bottom_ - top_) / zoom_;
float minX = bounds_.x + viewportWidth * 0.5f;
float maxX = bounds_.x + bounds_.w - viewportWidth * 0.5f;
float minY = bounds_.y + viewportHeight * 0.5f;
float maxY = bounds_.y + bounds_.h - viewportHeight * 0.5f;
Vec2 pos = owner_->getPosition();
if (minX > maxX) {
pos.x = bounds_.x + bounds_.w * 0.5f;
} else {
pos.x = std::clamp(pos.x, minX, maxX);
}
if (minY > maxY) {
pos.y = bounds_.y + bounds_.h * 0.5f;
} else {
pos.y = std::clamp(pos.y, minY, maxY);
}
owner_->setPosition(pos);
markViewDirty();
}
/**
* @brief
* @param offset
*/
void CameraComponent::move(const Vec2 &offset) {
if (!owner_)
return;
Vec2 pos = owner_->getPosition();
pos += offset;
owner_->setPosition(pos);
markViewDirty();
}
/**
* @brief
* @param x X方向偏移量
* @param y Y方向偏移量
*/
void CameraComponent::move(float x, float y) { move(Vec2(x, y)); }
/**
* @brief
* @param target
*/
void CameraComponent::lookAt(const Vec2 &target) {
if (!owner_)
return;
owner_->setPosition(target);
markViewDirty();
}
} // namespace extra2d

View File

@ -0,0 +1,52 @@
#include <event/events.h>
#include <scene/components/sprite_renderer.h>
#include <scene/node.h>
#include <utils/logger.h>
namespace extra2d {
SpriteRenderer::SpriteRenderer() {
// 默认材质和纹理为无效句柄
// 渲染时会使用默认材质
}
void SpriteRenderer::onAttach(Node *owner) { Component::onAttach(owner); }
void SpriteRenderer::setMaterial(MaterialHandle material) {
material_ = material;
}
void SpriteRenderer::setTexture(TextureHandle texture) { texture_ = texture; }
void SpriteRenderer::setColor(const Color &color) { color_ = color; }
void SpriteRenderer::render() {
if (!isEnabled() || !owner_ || !owner_->isVisible()) {
return;
}
// 获取世界变换(已包含锚点计算)
Transform worldTransform = owner_->getWorldTransform();
// 构建渲染命令
RenderCommand cmd;
cmd.type = RenderCommandType::DrawMesh;
// 计算排序键简单实现材质ID + 纹理ID
uint32_t materialId = static_cast<uint32_t>(material_ & 0xFFFFFFFF);
uint32_t textureId = static_cast<uint32_t>(texture_ & 0xFFFFFFFF);
cmd.sortKey = (materialId << 16) | (textureId & 0xFFFF);
// 设置网格(使用默认四边形)
cmd.drawMesh.mesh = INVALID_MESH_HANDLE; // 渲染器会使用默认四边形
cmd.drawMesh.material = material_;
cmd.setTransform(worldTransform);
// 设置顶点颜色
cmd.setColor(color_);
// 提交渲染命令
events::OnRenderSubmit::emit(cmd);
}
} // namespace extra2d

View File

@ -0,0 +1,163 @@
#include <scene/components/transform_component.h>
#include <scene/node.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/constants.hpp>
namespace extra2d {
TransformComponent::TransformComponent() {
local_.pos = Vec2(0, 0);
local_.scale = Vec2(1, 1);
local_.rot = 0;
}
void TransformComponent::setPosition(const Vec2& pos) {
if (local_.pos != pos) {
local_.pos = pos;
setDirty();
}
}
void TransformComponent::setRotation(float rot) {
if (local_.rot != rot) {
local_.rot = rot;
setDirty();
}
}
void TransformComponent::setScale(const Vec2& scale) {
if (local_.scale != scale) {
local_.scale = scale;
setDirty();
}
}
void TransformComponent::setAnchor(const Vec2& anchor) {
// 限制锚点在 [0, 1] 范围内
Vec2 clampedAnchor(
anchor.x < 0 ? 0 : (anchor.x > 1 ? 1 : anchor.x),
anchor.y < 0 ? 0 : (anchor.y > 1 ? 1 : anchor.y)
);
if (anchor_ != clampedAnchor) {
anchor_ = clampedAnchor;
setDirty();
}
}
void TransformComponent::setAnchorPoint(float x, float y) {
setAnchor(Vec2(x, y));
}
void TransformComponent::setSize(const Vec2& size) {
if (size_ != size) {
size_ = size;
setDirty();
}
}
void TransformComponent::setSize(float width, float height) {
setSize(Vec2(width, height));
}
Vec2 TransformComponent::getAnchorOffset() const {
// 锚点偏移 = (锚点 - 0.5) * 尺寸
// 这样 (0.5, 0.5) 锚点偏移为 0(0, 0) 锚点偏移为 (-0.5 * w, -0.5 * h)
return Vec2(
(anchor_.x - 0.5f) * size_.x,
(anchor_.y - 0.5f) * size_.y
);
}
void TransformComponent::setDirty() {
dirty_ = true;
// 递归标记所有子节点为脏
if (owner_) {
for (auto& child : owner_->getChildren()) {
if (auto* transform = child->getTransform()) {
transform->setDirty();
}
}
}
}
/**
* @brief
*
* (size)
* 1x1的四边形配合64x64的尺寸会正确显示为64x64像素大小
*/
void TransformComponent::updateWorldTransform() const {
if (!dirty_) return;
// 计算本地旋转(度数转弧度)
float rotRad = local_.rot * glm::pi<float>() / 180.0f;
float c = std::cos(rotRad);
float s = std::sin(rotRad);
// 获取锚点偏移(尺寸已包含在计算中)
Vec2 anchorOffset = getAnchorOffset();
// 计算世界变换
if (owner_ && owner_->getParent()) {
// 有父节点,需要组合变换
Transform parentWorld = owner_->getParent()->getWorldTransform();
// 本地旋转后的锚点偏移
float rotatedOffsetX = anchorOffset.x * c - anchorOffset.y * s;
float rotatedOffsetY = anchorOffset.x * s + anchorOffset.y * c;
// 组合变换
// 1. 先应用父节点的缩放到锚点偏移
float finalOffsetX = rotatedOffsetX * parentWorld.scale.x;
float finalOffsetY = rotatedOffsetY * parentWorld.scale.y;
// 2. 计算世界位置:父位置 + 旋转后的本地位置 + 锚点偏移
float parentCos = std::cos(parentWorld.rot);
float parentSin = std::sin(parentWorld.rot);
float localRotatedX = local_.pos.x * parentCos - local_.pos.y * parentSin;
float localRotatedY = local_.pos.x * parentSin + local_.pos.y * parentCos;
world_.pos.x = parentWorld.pos.x + localRotatedX * parentWorld.scale.x + finalOffsetX;
world_.pos.y = parentWorld.pos.y + localRotatedY * parentWorld.scale.y + finalOffsetY;
// 世界旋转和缩放(包含尺寸)
world_.rot = parentWorld.rot + rotRad;
world_.scale.x = parentWorld.scale.x * local_.scale.x * size_.x;
world_.scale.y = parentWorld.scale.y * local_.scale.y * size_.y;
} else {
// 无父节点,本地变换即世界变换
world_.pos.x = local_.pos.x + anchorOffset.x * c - anchorOffset.y * s;
world_.pos.y = local_.pos.y + anchorOffset.x * s + anchorOffset.y * c;
world_.rot = rotRad;
// 缩放包含尺寸
world_.scale.x = local_.scale.x * size_.x;
world_.scale.y = local_.scale.y * size_.y;
}
dirty_ = false;
}
Transform TransformComponent::getWorldTransform() const {
updateWorldTransform();
return world_;
}
Mat4 TransformComponent::getWorldMatrix() const {
updateWorldTransform();
// 构建世界矩阵
Mat4 matrix = Mat4(1.0f);
// 平移
matrix = glm::translate(matrix, glm::vec3(world_.pos.x, world_.pos.y, 0.0f));
// 旋转Z轴
matrix = glm::rotate(matrix, world_.rot, glm::vec3(0.0f, 0.0f, 1.0f));
// 缩放
matrix = glm::scale(matrix, glm::vec3(world_.scale.x, world_.scale.y, 1.0f));
return matrix;
}
} // namespace extra2d

154
src/scene/director.cpp Normal file
View File

@ -0,0 +1,154 @@
#include <scene/director.h>
#include <scene/components/camera_component.h>
#include <event/events.h>
#include <utils/logger.h>
namespace extra2d {
Director::Director() {
}
Director::~Director() {
shutdown();
}
bool Director::init() {
return true;
}
void Director::shutdown() {
// 结束当前场景
if (runningScene_) {
runningScene_->onExit();
runningScene_.reset();
}
// 清空场景栈
while (!sceneStack_.empty()) {
sceneStack_.pop();
}
running_ = false;
}
void Director::runScene(Ptr<Scene> scene) {
if (!scene) return;
// 结束当前场景
if (runningScene_) {
runningScene_->onExit();
}
// 清空场景栈
while (!sceneStack_.empty()) {
sceneStack_.pop();
}
// 运行新场景
runningScene_ = scene;
runningScene_->onEnter();
running_ = true;
needEnd_ = false;
}
void Director::replaceScene(Ptr<Scene> scene) {
if (!scene) return;
// 结束当前场景
if (runningScene_) {
runningScene_->onExit();
}
// 替换场景
runningScene_ = scene;
runningScene_->onEnter();
}
void Director::pushScene(Ptr<Scene> scene) {
if (!scene) return;
// 暂停当前场景
if (runningScene_) {
// 可以在这里添加暂停逻辑
sceneStack_.push(runningScene_);
}
// 运行新场景
runningScene_ = scene;
runningScene_->onEnter();
}
void Director::popScene() {
if (sceneStack_.empty()) {
// 没有场景可以弹出,结束运行
end();
return;
}
// 结束当前场景
if (runningScene_) {
runningScene_->onExit();
}
// 弹出栈顶场景
runningScene_ = sceneStack_.top();
sceneStack_.pop();
// 恢复场景
// 可以在这里添加恢复逻辑
}
void Director::end() {
needEnd_ = true;
running_ = false;
if (runningScene_) {
runningScene_->onExit();
runningScene_.reset();
}
}
Scene* Director::getRunningScene() const {
return runningScene_.get();
}
CameraComponent* Director::getMainCamera() const {
if (runningScene_) {
return runningScene_->getMainCamera();
}
return nullptr;
}
void Director::update(float dt) {
if (!running_ || !runningScene_) return;
runningScene_->update(dt);
}
void Director::render() {
if (!running_ || !runningScene_) return;
// 从场景获取相机并上传矩阵
CameraComponent* camera = runningScene_->getMainCamera();
if (camera) {
Mat4 viewProj = camera->getViewProjectionMatrix();
// 调试输出:打印视图投影矩阵
static bool logged = false;
if (!logged) {
const float* m = glm::value_ptr(viewProj);
E2D_LOG_INFO("ViewProjection Matrix:");
E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[0], m[4], m[8], m[12]);
E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[1], m[5], m[9], m[13]);
E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[2], m[6], m[10], m[14]);
E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[3], m[7], m[11], m[15]);
logged = true;
}
events::OnRenderSetCamera::emit(viewProj);
}
runningScene_->render();
}
} // namespace extra2d

207
src/scene/node.cpp Normal file
View File

@ -0,0 +1,207 @@
#include <scene/node.h>
#include <scene/components/transform_component.h>
namespace extra2d {
Node::Node() {
// 自动添加 TransformComponent
auto transform = makePtr<TransformComponent>();
transform_ = transform.get();
components_.push_back(transform);
transform_->onAttach(this);
}
Node::~Node() {
// 先分离所有组件
for (auto& comp : components_) {
comp->onDetach();
}
components_.clear();
// 移除所有子节点
removeAllChildren();
// 从父节点移除
if (parent_) {
parent_->removeChild(this);
}
}
void Node::addChild(Ptr<Node> child) {
if (!child || child.get() == this) return;
// 从原父节点移除
if (child->parent_) {
child->parent_->removeChild(child.get());
}
child->parent_ = this;
children_.push_back(child);
}
void Node::removeChild(Node* child) {
if (!child) return;
for (auto it = children_.begin(); it != children_.end(); ++it) {
if (it->get() == child) {
child->parent_ = nullptr;
children_.erase(it);
return;
}
}
}
void Node::removeFromParent() {
if (parent_) {
parent_->removeChild(this);
}
}
void Node::removeAllChildren() {
for (auto& child : children_) {
child->parent_ = nullptr;
}
children_.clear();
}
// ========================================
// 变换便捷接口
// ========================================
void Node::setPosition(const Vec2& pos) {
if (transform_) {
transform_->setPosition(pos);
}
}
void Node::setPosition(float x, float y) {
setPosition(Vec2(x, y));
}
Vec2 Node::getPosition() const {
return transform_ ? transform_->getPosition() : Vec2(0, 0);
}
void Node::setRotation(float rot) {
if (transform_) {
transform_->setRotation(rot);
}
}
float Node::getRotation() const {
return transform_ ? transform_->getRotation() : 0.0f;
}
void Node::setScale(const Vec2& scale) {
if (transform_) {
transform_->setScale(scale);
}
}
void Node::setScale(float scale) {
setScale(Vec2(scale, scale));
}
void Node::setScale(float x, float y) {
setScale(Vec2(x, y));
}
Vec2 Node::getScale() const {
return transform_ ? transform_->getScale() : Vec2(1, 1);
}
void Node::setSize(const Vec2& size) {
if (transform_) {
transform_->setSize(size);
}
}
void Node::setSize(float width, float height) {
setSize(Vec2(width, height));
}
Vec2 Node::getSize() const {
return transform_ ? transform_->getSize() : Vec2(100, 100);
}
void Node::setAnchor(const Vec2& anchor) {
if (transform_) {
transform_->setAnchor(anchor);
}
}
void Node::setAnchor(float x, float y) {
setAnchor(Vec2(x, y));
}
Vec2 Node::getAnchor() const {
return transform_ ? transform_->getAnchor() : Vec2(0.5f, 0.5f);
}
Transform Node::getWorldTransform() const {
return transform_ ? transform_->getWorldTransform() : Transform();
}
Mat4 Node::getWorldMatrix() const {
return transform_ ? transform_->getWorldMatrix() : Mat4();
}
// ========================================
// 生命周期
// ========================================
void Node::onEnter() {
// 通知所有组件
for (auto& comp : components_) {
// 组件没有 onEnter可以在这里扩展
}
// 递归通知子节点
for (auto& child : children_) {
child->onEnter();
}
}
void Node::onExit() {
// 递归通知子节点
for (auto& child : children_) {
child->onExit();
}
// 通知所有组件
for (auto& comp : components_) {
// 组件没有 onExit可以在这里扩展
}
}
void Node::update(float dt) {
// 更新所有组件
for (auto& comp : components_) {
if (comp->isEnabled()) {
comp->update(dt);
}
}
// 递归更新子节点
for (auto& child : children_) {
child->update(dt);
}
}
void Node::render() {
if (!visible_) return;
// 渲染所有组件
for (auto& comp : components_) {
if (comp->isEnabled()) {
comp->render();
}
}
// 递归渲染子节点
for (auto& child : children_) {
child->render();
}
}
} // namespace extra2d

128
src/scene/scene.cpp Normal file
View File

@ -0,0 +1,128 @@
#include <scene/scene.h>
#include <scene/components/camera_component.h>
#include <queue>
namespace extra2d {
Scene::Scene() {
}
Scene::~Scene() {
if (entered_) {
onExit();
}
removeAllChildren();
}
void Scene::addChild(Ptr<Node> node) {
if (!node) return;
node->removeFromParent();
node->onEnter();
rootNodes_.push_back(node);
}
void Scene::removeChild(Node* node) {
if (!node) return;
for (auto it = rootNodes_.begin(); it != rootNodes_.end(); ++it) {
if (it->get() == node) {
node->onExit();
rootNodes_.erase(it);
return;
}
}
}
void Scene::removeAllChildren() {
for (auto& node : rootNodes_) {
node->onExit();
}
rootNodes_.clear();
}
Node* Scene::findNode(const std::string& name) {
// 广度优先搜索
std::queue<Node*> queue;
for (auto& node : rootNodes_) {
queue.push(node.get());
}
while (!queue.empty()) {
Node* current = queue.front();
queue.pop();
if (current->getName() == name) {
return current;
}
for (auto& child : current->getChildren()) {
queue.push(child.get());
}
}
return nullptr;
}
Node* Scene::findNodeByTag(int32 tag) {
// 广度优先搜索
std::queue<Node*> queue;
for (auto& node : rootNodes_) {
queue.push(node.get());
}
while (!queue.empty()) {
Node* current = queue.front();
queue.pop();
if (current->getTag() == tag) {
return current;
}
for (auto& child : current->getChildren()) {
queue.push(child.get());
}
}
return nullptr;
}
void Scene::setMainCamera(CameraComponent* camera) {
mainCamera_ = camera;
}
void Scene::onEnter() {
entered_ = true;
// 通知所有根节点
for (auto& node : rootNodes_) {
node->onEnter();
}
}
void Scene::onExit() {
entered_ = false;
// 通知所有根节点
for (auto& node : rootNodes_) {
node->onExit();
}
}
void Scene::update(float dt) {
// 更新所有根节点
for (auto& node : rootNodes_) {
node->update(dt);
}
}
void Scene::render() {
// 渲染所有根节点
for (auto& node : rootNodes_) {
node->render();
}
}
} // namespace extra2d

View File

@ -0,0 +1,56 @@
#include <scene/scene_module.h>
namespace extra2d {
SceneModule::SceneModule() = default;
SceneModule::~SceneModule() {
shutdown();
}
SceneModule::SceneModule(SceneModule &&) noexcept = default;
SceneModule &SceneModule::operator=(SceneModule &&) noexcept = default;
bool SceneModule::init() {
// 创建导演
director_ = makePtr<Director>();
if (!director_->init()) {
return false;
}
// 绑定事件
onUpdateListener_.bind([this](float dt) {
if (director_) {
director_->update(dt);
}
});
onRenderBeginListener_.bind([this]() {
// 渲染开始,触发场景渲染
if (director_) {
director_->render();
}
});
onRenderEndListener_.bind([]() {
// 渲染结束,清理工作
});
return true;
}
void SceneModule::shutdown() {
if (director_) {
director_->shutdown();
director_.reset();
}
// 重置监听器(会自动注销)
onUpdateListener_.reset();
onRenderBeginListener_.reset();
onRenderEndListener_.reset();
}
} // namespace extra2d

View File

@ -0,0 +1,15 @@
#include <types/math/vec2.h>
#include <types/math/rect.h>
namespace extra2d {
// Vec2 静态成员定义
const Vec2 Vec2::Zero = Vec2(0.0f, 0.0f);
const Vec2 Vec2::One = Vec2(1.0f, 1.0f);
const Vec2 Vec2::UnitX = Vec2(1.0f, 0.0f);
const Vec2 Vec2::UnitY = Vec2(0.0f, 1.0f);
// Rect 静态成员定义
const Rect Rect::Zero = Rect(0.0f, 0.0f, 0.0f, 0.0f);
} // namespace extra2d

View File

@ -94,6 +94,7 @@ define_extra2d_engine()
-- 示例程序目标(作为子项目) -- 示例程序目标(作为子项目)
if is_config("examples","true") then if is_config("examples","true") then
includes("examples/hello_world", {rootdir = "examples/hello_world"}) includes("examples/hello_world", {rootdir = "examples/hello_world"})
includes("examples/scene_graph_demo", {rootdir = "examples/scene_graph_demo"})
end end