feat(渲染): 添加虚拟分辨率支持并重构相机系统

实现虚拟分辨率渲染系统,支持不同缩放模式
重构相机控制器以使用虚拟分辨率计算可见区域
移除硬编码的屏幕尺寸,改为动态获取
添加分辨率状态管理及相关工具函数
更新窗口和渲染器以处理分辨率变化
This commit is contained in:
2026-04-06 23:17:26 +08:00
parent 35c80247b3
commit 62b0f6dafd
16 changed files with 631 additions and 321 deletions

View File

@@ -2,6 +2,7 @@
#include <frostbite2D/core/window.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/render_resolution.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/event/event.h>
#include <string>
@@ -37,6 +38,26 @@ struct AppConfig {
*/
PlatformType targetPlatform = PlatformType::Auto;
/**
* @brief 是否启用虚拟分辨率
*/
bool useVirtualResolution = false;
/**
* @brief 虚拟分辨率宽度
*/
int virtualWidth = 0;
/**
* @brief 虚拟分辨率高度
*/
int virtualHeight = 0;
/**
* @brief 虚拟分辨率缩放模式
*/
ResolutionScaleMode resolutionMode = ResolutionScaleMode::Fit;
/**
* @brief 创建默认配置
* @return 默认的应用配置实例

View File

@@ -8,30 +8,22 @@
namespace frostbite2D {
/**
* \~chinese
* @brief 鼠标指针类型
*/
enum class CursorType {
Arrow, ///< 指针
TextInput, ///< 文本
Hand, ///< 手
SizeAll, ///< 指向四个方向的箭头
SizeWE, ///< 指向左右方向的箭头
SizeNS, ///< 指向上下方向的箭头
SizeNESW, ///< 指向左下到右上方向的箭头
SizeNWSE, ///< 指向左上到右下方向的箭头
No, ///< 禁止
Arrow,
TextInput,
Hand,
SizeAll,
SizeWE,
SizeNS,
SizeNESW,
SizeNWSE,
No,
};
/**
* \~chinese
* @brief 分辨率
*/
struct Resolution {
uint32_t width = 0; ///< 分辨率宽度
uint32_t height = 0; ///< 分辨率高度
uint32_t refresh_rate = 0; ///< 刷新率
uint32_t width = 0;
uint32_t height = 0;
uint32_t refresh_rate = 0;
Resolution() = default;
@@ -39,41 +31,33 @@ struct Resolution {
: width(width), height(height), refresh_rate(refresh_rate) {}
};
/**
* \~chinese
* @brief 图标
*/
struct Icon {
Icon() = default;
Icon(std::string file_path) : file_path(file_path) {}
std::string file_path; ///< 文件路径
std::string file_path;
#if defined(_WIN32)
uint32_t resource_id = 0; ///< 资源ID仅在windows上生效
uint32_t resource_id = 0;
Icon(uint32_t resource_id) : resource_id(resource_id) {}
#endif
};
/**
* \~chinese
* @brief 窗口设置
*/
struct WindowConfig {
uint32_t width = 640; ///< 窗口宽度
uint32_t height = 480; ///< 窗口高度
std::string title = "frostbite2D Game"; ///< 窗口标题
Icon icon; ///< 窗口图标
bool resizable = false; ///< 窗口大小可调整
bool fullscreen = false; ///< 窗口全屏
bool borderless = false; ///< 无边框窗口
bool decorated = true; ///< 窗口装饰
int multisamples = 0; ///< 多重采样数
bool centered = true; ///< 窗口是否居中
bool vsync = true; ///< 是否启用垂直同步
bool showCursor = true; ///< 是否显示光标
uint32_t width = 640;
uint32_t height = 480;
std::string title = "frostbite2D Game";
Icon icon;
bool resizable = false;
bool fullscreen = false;
bool borderless = false;
bool decorated = true;
int multisamples = 0;
bool centered = true;
bool vsync = true;
bool showCursor = true;
};
class Window {
@@ -81,179 +65,55 @@ public:
Window() = default;
~Window() = default;
/**
* @brief 创建窗口
* @param cfg 窗口配置
* @return 创建是否成功
*/
virtual bool create(const WindowConfig &cfg);
/**
* @brief 销毁窗口
*/
virtual bool create(const WindowConfig& cfg);
virtual void destroy();
/**
* @brief 轮询事件
*/
virtual void poll();
/**
* @brief 交换缓冲区
*/
virtual void swap();
/**
* @brief 设置窗口关闭标志
*/
virtual void close();
/**
* @brief 设置窗口标题
*/
virtual void setTitle(const std::string &title);
/**
* @brief 设置窗口大小
*/
virtual void setTitle(const std::string& title);
virtual void setSize(int w, int h);
/**
* @brief 设置窗口位置
*/
virtual void setPos(int x, int y);
/**
* @brief 设置全屏模式
*/
virtual void setFullscreen(bool fs);
/**
* @brief 设置垂直同步
*/
virtual void setVSync(bool vsync);
/**
* @brief 设置窗口可见性
*/
virtual void setVisible(bool visible);
/**
* @brief 获取窗口宽度
*/
virtual int width() const;
/**
* @brief 获取窗口高度
*/
virtual int height() const;
/**
* @brief 获取窗口大小
*/
virtual int drawableWidth() const;
virtual int drawableHeight() const;
virtual Size size() const;
/**
* @brief 获取窗口位置
*/
virtual Vec2 pos() const;
/**
* @brief 是否全屏
*/
virtual bool fullscreen() const;
/**
* @brief 是否启用垂直同步
*/
virtual bool vsync() const;
/**
* @brief 窗口是否获得焦点
*/
virtual bool focused() const;
/**
* @brief 窗口是否最小化
*/
virtual bool minimized() const;
/**
* @brief 获取内容缩放X
*/
virtual float scaleX() const;
/**
* @brief 获取内容缩放Y
*/
virtual float scaleY() const;
/**
* @brief 设置光标形状
*/
virtual bool refreshMetrics(bool emitResize = false);
virtual void setCursor(CursorType cursor);
/**
* @brief 显示/隐藏光标
*/
virtual void showCursor(bool show);
/**
* @brief 锁定/解锁光标
*/
virtual void lockCursor(bool lock);
/**
* @brief 窗口大小改变回调
*/
using ResizeCb = std::function<void(int, int)>;
/**
* @brief 窗口关闭回调
*/
using CloseCb = std::function<void()>;
/**
* @brief 窗口焦点改变回调
*/
using FocusCb = std::function<void(bool)>;
/**
* @brief 设置大小改变回调
*/
virtual void onResize(ResizeCb cb);
/**
* @brief 设置关闭回调
*/
virtual void onClose(CloseCb cb);
/**
* @brief 设置焦点改变回调
*/
virtual void onFocus(FocusCb cb);
virtual void* native() const;
/**
* @brief 获取原生窗口句柄
*/
virtual void *native() const;
/**
* @brief 获取 SDL 窗口句柄
*/
SDL_Window *sdlWindow() const { return sdlWindow_; }
/**
* @brief 获取 OpenGL 上下文
*/
SDL_Window* sdlWindow() const { return sdlWindow_; }
SDL_GLContext glContext() const { return glContext_; }
private:
SDL_Window *sdlWindow_ = nullptr;
SDL_Window* sdlWindow_ = nullptr;
SDL_GLContext glContext_ = nullptr;
int width_ = 1280;
int height_ = 720;
int drawableWidth_ = 1280;
int drawableHeight_ = 720;
bool fullscreen_ = false;
bool vsync_ = true;
bool focused_ = true;
@@ -269,4 +129,4 @@ private:
FocusCb focusCb_;
};
} // namespace frostbite2D
} // namespace frostbite2D

View File

@@ -17,6 +17,8 @@ public:
float getZoom() const { return zoom_; }
int getViewportWidth() const { return viewportWidth_; }
int getViewportHeight() const { return viewportHeight_; }
float getVisibleWidth() const;
float getVisibleHeight() const;
void lookAt(const Vec2& target);
void move(const Vec2& delta);

View File

@@ -0,0 +1,43 @@
#pragma once
#include <frostbite2D/types/type_math.h>
namespace frostbite2D {
enum class ResolutionScaleMode {
Disabled,
Fit,
FitHeight,
};
struct RenderResolutionState {
int windowWidth = 1280;
int windowHeight = 720;
int drawableWidth = 1280;
int drawableHeight = 720;
int windowViewportX = 0;
int windowViewportY = 0;
int windowViewportWidth = 1280;
int windowViewportHeight = 720;
int viewportX = 0;
int viewportY = 0;
int viewportWidth = 1280;
int viewportHeight = 720;
float contentScaleX = 1.0f;
float contentScaleY = 1.0f;
float scaleX = 1.0f;
float scaleY = 1.0f;
Rect getWindowViewportRect() const {
return Rect(static_cast<float>(windowViewportX),
static_cast<float>(windowViewportY),
static_cast<float>(windowViewportWidth),
static_cast<float>(windowViewportHeight));
}
};
} // namespace frostbite2D

View File

@@ -1,11 +1,12 @@
#pragma once
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/graphics/shader_manager.h>
#include <frostbite2D/graphics/batch.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/render_resolution.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/graphics/shader_manager.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/types.h>
#include <string>
namespace frostbite2D {
@@ -13,55 +14,71 @@ namespace frostbite2D {
class Renderer {
public:
static Renderer& get();
bool init();
void shutdown();
void beginFrame();
void endFrame();
void flush();
void setViewport(int x, int y, int width, int height);
void setWindowSize(int width, int height, float contentScaleX = 1.0f,
float contentScaleY = 1.0f);
void setVirtualResolutionEnabled(bool enabled);
void setVirtualResolution(int width, int height);
void setResolutionScaleMode(ResolutionScaleMode mode);
void setClearColor(float r, float g, float b, float a = 1.0f);
void setClearColor(const Color& color);
void clear(uint32_t flags);
void setCamera(Camera* camera);
Camera* getCamera() { return camera_; }
void drawQuad(const Vec2& pos, const Size& size, float cr = 1.0f, float cg = 1.0f,
float cb = 1.0f, float ca = 1.0f);
int getVirtualWidth() const;
int getVirtualHeight() const;
Rect getViewportRect() const;
const RenderResolutionState& getResolutionState() const {
return resolutionState_;
}
Vec2 screenToVirtual(const Vec2& screenPos) const;
Vec2 virtualToScreen(const Vec2& virtualPos) const;
void drawQuad(const Vec2& pos, const Size& size, float cr = 1.0f,
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f);
void drawQuad(const Vec2& pos, const Size& size, Ptr<Texture> texture);
void drawQuad(const Rect& rect, const Color& color);
void drawSprite(const Vec2& pos, const Size& size, Ptr<Texture> texture);
void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
Ptr<Texture> texture, const Color& color = Color(1, 1, 1, 1));
void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
Ptr<Texture> texture,
const Color& color = Color(1, 1, 1, 1));
void setupBlendMode(BlendMode mode);
ShaderManager& getShaderManager() { return shaderManager_; }
Batch& getBatch() { return batch_; }
private:
Renderer();
// ~Renderer() 在 shutdown() 中手动调用销毁
void updateUniforms();
void recalculateResolutionState();
void syncCameraViewport();
ShaderManager shaderManager_;
Batch batch_;
Camera* camera_ = nullptr;
uint32_t clearColor_[4] = {0, 0, 0, 255};
int viewportX_ = 0;
int viewportY_ = 0;
int viewportWidth_ = 1280;
int viewportHeight_ = 720;
bool useVirtualResolution_ = false;
int virtualWidth_ = 1280;
int virtualHeight_ = 720;
ResolutionScaleMode resolutionMode_ = ResolutionScaleMode::Fit;
RenderResolutionState resolutionState_;
bool initialized_ = false;
Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
};
}
} // namespace frostbite2D

View File

@@ -180,9 +180,13 @@ bool Application::initCoreModules() {
}
window_->onResize([this](int width, int height) {
renderer_->setViewport(0, 0, width, height);
camera_->setViewport(width, height);
SDL_Log("Window resized to %dx%d", width, height);
if (!renderer_ || !window_) {
return;
}
renderer_->setWindowSize(width, height, window_->scaleX(), window_->scaleY());
SDL_Log("Window resized to %dx%d (drawable %dx%d)", width, height,
window_->drawableWidth(), window_->drawableHeight());
});
// 初始化渲染器
@@ -197,13 +201,19 @@ bool Application::initCoreModules() {
// 设置窗口清除颜色和视口
renderer_->setClearColor(0.0f, 0.0f, 0.0f);
renderer_->setViewport(0, 0, config_.windowConfig.width, config_.windowConfig.height);
renderer_->setVirtualResolutionEnabled(config_.useVirtualResolution);
renderer_->setResolutionScaleMode(config_.resolutionMode);
if (config_.virtualWidth > 0 && config_.virtualHeight > 0) {
renderer_->setVirtualResolution(config_.virtualWidth, config_.virtualHeight);
}
renderer_->setWindowSize(window_->width(), window_->height(), window_->scaleX(),
window_->scaleY());
// 创建并设置相机
{
ScopedStartupTrace stageTrace("Camera setup");
camera_ = new Camera();
camera_->setViewport(config_.windowConfig.width, config_.windowConfig.height);
camera_->setViewport(renderer_->getVirtualWidth(), renderer_->getVirtualHeight());
camera_->setFlipY(true); // 启用 Y 轴翻转,(0,0) 在左上角
renderer_->setCamera(camera_);
}
@@ -282,6 +292,17 @@ void Application::mainLoop() {
break;
}
if (sdlEvent.type == SDL_WINDOWEVENT && window_) {
switch (sdlEvent.window.event) {
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
window_->refreshMetrics(true);
break;
default:
break;
}
}
auto event = convertSDLEvent(sdlEvent);
if (event) {
dispatchEvent(*event);

View File

@@ -4,13 +4,11 @@
#include <frostbite2D/resource/asset.h>
#include <glad/glad.h>
namespace frostbite2D {
bool Window::create(const WindowConfig &cfg) {
bool Window::create(const WindowConfig& cfg) {
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
#ifndef __SWITCH__
if (cfg.fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
@@ -28,7 +26,6 @@ bool Window::create(const WindowConfig &cfg) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
@@ -38,17 +35,11 @@ bool Window::create(const WindowConfig &cfg) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.multisamples);
}
int x = SDL_WINDOWPOS_CENTERED;
int y = SDL_WINDOWPOS_CENTERED;
if (!cfg.centered) {
x = SDL_WINDOWPOS_UNDEFINED;
y = SDL_WINDOWPOS_UNDEFINED;
}
// 创建窗口
sdlWindow_ =
SDL_CreateWindow(cfg.title.c_str(), x, y, cfg.width, cfg.height, flags);
int x = cfg.centered ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED;
int y = cfg.centered ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED;
sdlWindow_ = SDL_CreateWindow(cfg.title.c_str(), x, y, cfg.width, cfg.height,
flags);
if (!sdlWindow_) {
SDL_Log("Failed to create window: %s", SDL_GetError());
return false;
@@ -72,23 +63,12 @@ bool Window::create(const WindowConfig &cfg) {
}
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
refreshMetrics(false);
// 获取实际的可绘制大小(考虑高 DPI 和 Switch 等平台)
int drawableWidth, drawableHeight;
SDL_GL_GetDrawableSize(sdlWindow_, &drawableWidth, &drawableHeight);
width_ = drawableWidth;
height_ = drawableHeight;
// 计算缩放比例
int windowWidth, windowHeight;
SDL_GetWindowSize(sdlWindow_, &windowWidth, &windowHeight);
if (windowWidth > 0 && windowHeight > 0) {
scaleX_ = static_cast<float>(drawableWidth) / static_cast<float>(windowWidth);
scaleY_ = static_cast<float>(drawableHeight) / static_cast<float>(windowHeight);
}
fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
vsync_ = cfg.vsync;
cursorVisible_ = cfg.showCursor;
showCursor(cfg.showCursor);
#ifndef __SWITCH__
if (!cfg.icon.file_path.empty()) {
@@ -113,9 +93,11 @@ bool Window::create(const WindowConfig &cfg) {
}
void Window::destroy() {
if (sdlWindow_) {
if (glContext_) {
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
}
if (sdlWindow_) {
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
}
@@ -129,16 +111,32 @@ void Window::swap() {
}
}
void Window::close() { shouldClose_ = true; }
void Window::close() {
shouldClose_ = true;
if (closeCb_) {
closeCb_();
}
}
void Window::setTitle(const std::string &title) {}
void Window::setTitle(const std::string& title) {
if (sdlWindow_) {
SDL_SetWindowTitle(sdlWindow_, title.c_str());
}
}
void Window::setSize(int w, int h) {
if (sdlWindow_) {
SDL_SetWindowSize(sdlWindow_, w, h);
refreshMetrics(true);
return;
}
width_ = w;
height_ = h;
drawableWidth_ = w;
drawableHeight_ = h;
scaleX_ = 1.0f;
scaleY_ = 1.0f;
if (resizeCb_) {
resizeCb_(w, h);
}
@@ -150,23 +148,54 @@ void Window::setPos(int x, int y) {
}
}
void Window::setFullscreen(bool fs) { fullscreen_ = fs; }
void Window::setFullscreen(bool fs) {
fullscreen_ = fs;
if (!sdlWindow_) {
return;
}
void Window::setVSync(bool vsync) { vsync_ = vsync; }
#ifndef __SWITCH__
Uint32 flags = fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
SDL_SetWindowFullscreen(sdlWindow_, flags);
refreshMetrics(true);
#endif
}
void Window::setVisible(bool visible) {}
void Window::setVSync(bool vsync) {
vsync_ = vsync;
if (glContext_) {
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
}
}
void Window::setVisible(bool visible) {
if (!sdlWindow_) {
return;
}
if (visible) {
SDL_ShowWindow(sdlWindow_);
} else {
SDL_HideWindow(sdlWindow_);
}
}
int Window::width() const { return width_; }
int Window::height() const { return height_; }
int Window::drawableWidth() const { return drawableWidth_; }
int Window::drawableHeight() const { return drawableHeight_; }
Size Window::size() const {
return Size{static_cast<float>(width_), static_cast<float>(height_)};
}
Vec2 Window::pos() const {
if (sdlWindow_) {
int x, y;
int x = 0;
int y = 0;
SDL_GetWindowPosition(sdlWindow_, &x, &y);
return Vec2{static_cast<float>(x), static_cast<float>(y)};
}
@@ -185,11 +214,90 @@ float Window::scaleX() const { return scaleX_; }
float Window::scaleY() const { return scaleY_; }
void Window::setCursor(CursorType cursor) {}
bool Window::refreshMetrics(bool emitResize) {
if (!sdlWindow_) {
return false;
}
void Window::showCursor(bool show) { cursorVisible_ = show; }
int prevWidth = width_;
int prevHeight = height_;
int prevDrawableWidth = drawableWidth_;
int prevDrawableHeight = drawableHeight_;
void Window::lockCursor(bool lock) { cursorLocked_ = lock; }
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
SDL_GL_GetDrawableSize(sdlWindow_, &drawableWidth_, &drawableHeight_);
if (width_ > 0 && height_ > 0) {
scaleX_ = static_cast<float>(drawableWidth_) / static_cast<float>(width_);
scaleY_ = static_cast<float>(drawableHeight_) / static_cast<float>(height_);
} else {
scaleX_ = 1.0f;
scaleY_ = 1.0f;
}
Uint32 windowFlags = SDL_GetWindowFlags(sdlWindow_);
focused_ = (windowFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
minimized_ = (windowFlags & SDL_WINDOW_MINIMIZED) != 0;
fullscreen_ = (windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0 ||
(windowFlags & SDL_WINDOW_FULLSCREEN) != 0;
bool changed = prevWidth != width_ || prevHeight != height_ ||
prevDrawableWidth != drawableWidth_ ||
prevDrawableHeight != drawableHeight_;
if (emitResize && changed && resizeCb_) {
resizeCb_(width_, height_);
}
return true;
}
void Window::setCursor(CursorType cursor) {
SDL_SystemCursor systemCursor = SDL_SYSTEM_CURSOR_ARROW;
switch (cursor) {
case CursorType::Arrow:
systemCursor = SDL_SYSTEM_CURSOR_ARROW;
break;
case CursorType::TextInput:
systemCursor = SDL_SYSTEM_CURSOR_IBEAM;
break;
case CursorType::Hand:
systemCursor = SDL_SYSTEM_CURSOR_HAND;
break;
case CursorType::SizeAll:
systemCursor = SDL_SYSTEM_CURSOR_SIZEALL;
break;
case CursorType::SizeWE:
systemCursor = SDL_SYSTEM_CURSOR_SIZEWE;
break;
case CursorType::SizeNS:
systemCursor = SDL_SYSTEM_CURSOR_SIZENS;
break;
case CursorType::SizeNESW:
systemCursor = SDL_SYSTEM_CURSOR_SIZENESW;
break;
case CursorType::SizeNWSE:
systemCursor = SDL_SYSTEM_CURSOR_SIZENWSE;
break;
case CursorType::No:
systemCursor = SDL_SYSTEM_CURSOR_ARROW;
break;
}
SDL_Cursor* sdlCursor = SDL_CreateSystemCursor(systemCursor);
if (sdlCursor) {
SDL_SetCursor(sdlCursor);
}
}
void Window::showCursor(bool show) {
cursorVisible_ = show;
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
}
void Window::lockCursor(bool lock) {
cursorLocked_ = lock;
SDL_SetRelativeMouseMode(lock ? SDL_TRUE : SDL_FALSE);
}
void Window::onResize(ResizeCb cb) { resizeCb_ = cb; }
@@ -197,6 +305,6 @@ void Window::onClose(CloseCb cb) { closeCb_ = cb; }
void Window::onFocus(FocusCb cb) { focusCb_ = cb; }
void *Window::native() const { return nullptr; }
void* Window::native() const { return nullptr; }
} // namespace frostbite2D

View File

@@ -1,5 +1,6 @@
#include <frostbite2D/graphics/camera.h>
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
namespace frostbite2D {
@@ -14,6 +15,16 @@ void Camera::setViewport(int width, int height) {
viewportHeight_ = height;
}
float Camera::getVisibleWidth() const {
float safeZoom = std::max(zoom_, 0.01f);
return static_cast<float>(viewportWidth_) / safeZoom;
}
float Camera::getVisibleHeight() const {
float safeZoom = std::max(zoom_, 0.01f);
return static_cast<float>(viewportHeight_) / safeZoom;
}
void Camera::lookAt(const Vec2 &target) { position_ = target; }
void Camera::move(const Vec2 &delta) { position_ = position_ + delta; }
@@ -54,4 +65,4 @@ glm::mat4 Camera::getProjectionMatrix() const {
return glm::ortho(left, right, bottom, top, -1.0f, 1.0f);
}
} // namespace frostbite2D
} // namespace frostbite2D

View File

@@ -1,11 +1,23 @@
#include "SDL_log.h"
#include <SDL2/SDL.h>
#include <algorithm>
#include <cmath>
#include <frostbite2D/graphics/renderer.h>
#include <glad/glad.h>
#include <glm/gtc/type_ptr.hpp>
namespace frostbite2D {
namespace {
constexpr float kMinContentScale = 0.01f;
int roundToInt(float value) {
return static_cast<int>(std::lround(value));
}
} // namespace
Renderer& Renderer::get() {
static Renderer instance;
return instance;
@@ -13,29 +25,29 @@ Renderer& Renderer::get() {
Renderer::Renderer() = default;
// 移除析构函数中的自动销毁,改为在 Application::shutdown() 中手动调用
bool Renderer::init() {
if (initialized_) {
return true;
}
if (!batch_.init()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize batch system");
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to initialize batch system");
return false;
}
//初始化着色器管理器
if (!shaderManager_.init("assets/shaders")) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize shader manager");
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to initialize shader manager");
return false;
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
SDL_Log("Renderer initialized");
initialized_ = true;
recalculateResolutionState();
return true;
}
@@ -46,11 +58,12 @@ void Renderer::shutdown() {
}
void Renderer::beginFrame() {
glViewport(viewportX_, viewportY_, viewportWidth_, viewportHeight_);
glClearColor(clearColor_[0] / 255.0f, clearColor_[1] / 255.0f,
clearColor_[2] / 255.0f, clearColor_[3] / 255.0f);
glViewport(resolutionState_.viewportX, resolutionState_.viewportY,
resolutionState_.viewportWidth, resolutionState_.viewportHeight);
glClearColor(clearColor_[0] / 255.0f, clearColor_[1] / 255.0f,
clearColor_[2] / 255.0f, clearColor_[3] / 255.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
updateUniforms();
batch_.begin();
}
@@ -64,14 +77,66 @@ void Renderer::flush() {
}
void Renderer::setViewport(int x, int y, int width, int height) {
viewportX_ = x;
viewportY_ = y;
viewportWidth_ = width;
viewportHeight_ = height;
if (camera_) {
camera_->setViewport(width, height);
}
resolutionState_.viewportX = x;
resolutionState_.viewportY = y;
resolutionState_.viewportWidth = std::max(width, 1);
resolutionState_.viewportHeight = std::max(height, 1);
float safeScaleX = std::max(resolutionState_.contentScaleX, kMinContentScale);
float safeScaleY = std::max(resolutionState_.contentScaleY, kMinContentScale);
resolutionState_.windowViewportX = roundToInt(
static_cast<float>(resolutionState_.viewportX) / safeScaleX);
resolutionState_.windowViewportY = roundToInt(
static_cast<float>(resolutionState_.viewportY) / safeScaleY);
resolutionState_.windowViewportWidth = roundToInt(
static_cast<float>(resolutionState_.viewportWidth) / safeScaleX);
resolutionState_.windowViewportHeight = roundToInt(
static_cast<float>(resolutionState_.viewportHeight) / safeScaleY);
int logicalWidth = std::max(getVirtualWidth(), 1);
int logicalHeight = std::max(getVirtualHeight(), 1);
resolutionState_.scaleX =
static_cast<float>(resolutionState_.windowViewportWidth) /
static_cast<float>(logicalWidth);
resolutionState_.scaleY =
static_cast<float>(resolutionState_.windowViewportHeight) /
static_cast<float>(logicalHeight);
syncCameraViewport();
}
void Renderer::setWindowSize(int width, int height, float contentScaleX,
float contentScaleY) {
resolutionState_.windowWidth = std::max(width, 1);
resolutionState_.windowHeight = std::max(height, 1);
resolutionState_.contentScaleX = std::max(contentScaleX, kMinContentScale);
resolutionState_.contentScaleY = std::max(contentScaleY, kMinContentScale);
resolutionState_.drawableWidth = std::max(
roundToInt(static_cast<float>(resolutionState_.windowWidth) *
resolutionState_.contentScaleX),
1);
resolutionState_.drawableHeight = std::max(
roundToInt(static_cast<float>(resolutionState_.windowHeight) *
resolutionState_.contentScaleY),
1);
recalculateResolutionState();
}
void Renderer::setVirtualResolutionEnabled(bool enabled) {
useVirtualResolution_ = enabled;
recalculateResolutionState();
}
void Renderer::setVirtualResolution(int width, int height) {
virtualWidth_ = std::max(width, 1);
virtualHeight_ = std::max(height, 1);
recalculateResolutionState();
}
void Renderer::setResolutionScaleMode(ResolutionScaleMode mode) {
resolutionMode_ = mode;
recalculateResolutionState();
}
void Renderer::setClearColor(float r, float g, float b, float a) {
@@ -98,15 +163,169 @@ void Renderer::setCamera(Camera* camera) {
}
camera_ = camera;
if (camera) {
camera_->setViewport(viewportWidth_, viewportHeight_);
}
syncCameraViewport();
if (initialized_) {
updateUniforms();
}
}
int Renderer::getVirtualWidth() const {
if (useVirtualResolution_) {
return std::max(virtualWidth_, 1);
}
return std::max(resolutionState_.windowWidth, 1);
}
int Renderer::getVirtualHeight() const {
if (useVirtualResolution_) {
return std::max(virtualHeight_, 1);
}
return std::max(resolutionState_.windowHeight, 1);
}
Rect Renderer::getViewportRect() const {
return resolutionState_.getWindowViewportRect();
}
Vec2 Renderer::screenToVirtual(const Vec2& screenPos) const {
Rect viewport = getViewportRect();
if (viewport.width() <= 0.0f || viewport.height() <= 0.0f) {
return screenPos;
}
float localX = screenPos.x - viewport.left();
float localY = screenPos.y - viewport.top();
return Vec2(localX * static_cast<float>(getVirtualWidth()) / viewport.width(),
localY * static_cast<float>(getVirtualHeight()) /
viewport.height());
}
Vec2 Renderer::virtualToScreen(const Vec2& virtualPos) const {
Rect viewport = getViewportRect();
float logicalWidth = static_cast<float>(std::max(getVirtualWidth(), 1));
float logicalHeight = static_cast<float>(std::max(getVirtualHeight(), 1));
return Vec2(viewport.left() + virtualPos.x * viewport.width() / logicalWidth,
viewport.top() + virtualPos.y * viewport.height() /
logicalHeight);
}
void Renderer::recalculateResolutionState() {
resolutionState_.windowWidth = std::max(resolutionState_.windowWidth, 1);
resolutionState_.windowHeight = std::max(resolutionState_.windowHeight, 1);
resolutionState_.contentScaleX =
std::max(resolutionState_.contentScaleX, kMinContentScale);
resolutionState_.contentScaleY =
std::max(resolutionState_.contentScaleY, kMinContentScale);
resolutionState_.drawableWidth = std::max(
roundToInt(static_cast<float>(resolutionState_.windowWidth) *
resolutionState_.contentScaleX),
1);
resolutionState_.drawableHeight = std::max(
roundToInt(static_cast<float>(resolutionState_.windowHeight) *
resolutionState_.contentScaleY),
1);
int windowViewportX = 0;
int windowViewportY = 0;
int windowViewportWidth = resolutionState_.windowWidth;
int windowViewportHeight = resolutionState_.windowHeight;
if (useVirtualResolution_) {
float widthScale = static_cast<float>(resolutionState_.windowWidth) /
static_cast<float>(std::max(virtualWidth_, 1));
float heightScale = static_cast<float>(resolutionState_.windowHeight) /
static_cast<float>(std::max(virtualHeight_, 1));
switch (resolutionMode_) {
case ResolutionScaleMode::Disabled:
resolutionState_.scaleX = widthScale;
resolutionState_.scaleY = heightScale;
break;
case ResolutionScaleMode::FitHeight: {
float uniformScale = heightScale;
float fittedWidth = static_cast<float>(virtualWidth_) * uniformScale;
if (fittedWidth > static_cast<float>(resolutionState_.windowWidth)) {
uniformScale = std::min(widthScale, heightScale);
}
windowViewportWidth = std::clamp(
roundToInt(static_cast<float>(virtualWidth_) * uniformScale), 1,
resolutionState_.windowWidth);
windowViewportHeight = std::clamp(
roundToInt(static_cast<float>(virtualHeight_) * uniformScale), 1,
resolutionState_.windowHeight);
windowViewportX =
(resolutionState_.windowWidth - windowViewportWidth) / 2;
windowViewportY =
(resolutionState_.windowHeight - windowViewportHeight) / 2;
resolutionState_.scaleX =
static_cast<float>(windowViewportWidth) /
static_cast<float>(std::max(virtualWidth_, 1));
resolutionState_.scaleY =
static_cast<float>(windowViewportHeight) /
static_cast<float>(std::max(virtualHeight_, 1));
break;
}
case ResolutionScaleMode::Fit:
default: {
float uniformScale = std::min(widthScale, heightScale);
windowViewportWidth = std::clamp(
roundToInt(static_cast<float>(virtualWidth_) * uniformScale), 1,
resolutionState_.windowWidth);
windowViewportHeight = std::clamp(
roundToInt(static_cast<float>(virtualHeight_) * uniformScale), 1,
resolutionState_.windowHeight);
windowViewportX =
(resolutionState_.windowWidth - windowViewportWidth) / 2;
windowViewportY =
(resolutionState_.windowHeight - windowViewportHeight) / 2;
resolutionState_.scaleX =
static_cast<float>(windowViewportWidth) /
static_cast<float>(std::max(virtualWidth_, 1));
resolutionState_.scaleY =
static_cast<float>(windowViewportHeight) /
static_cast<float>(std::max(virtualHeight_, 1));
break;
}
}
} else {
resolutionState_.scaleX = 1.0f;
resolutionState_.scaleY = 1.0f;
}
resolutionState_.windowViewportX = windowViewportX;
resolutionState_.windowViewportY = windowViewportY;
resolutionState_.windowViewportWidth = windowViewportWidth;
resolutionState_.windowViewportHeight = windowViewportHeight;
resolutionState_.viewportX = roundToInt(
static_cast<float>(windowViewportX) * resolutionState_.contentScaleX);
resolutionState_.viewportY = roundToInt(
static_cast<float>(windowViewportY) * resolutionState_.contentScaleY);
resolutionState_.viewportWidth = std::max(
roundToInt(static_cast<float>(windowViewportWidth) *
resolutionState_.contentScaleX),
1);
resolutionState_.viewportHeight = std::max(
roundToInt(static_cast<float>(windowViewportHeight) *
resolutionState_.contentScaleY),
1);
syncCameraViewport();
if (initialized_) {
updateUniforms();
}
}
void Renderer::syncCameraViewport() {
if (!camera_) {
return;
}
camera_->setViewport(getVirtualWidth(), getVirtualHeight());
}
void Renderer::setupBlendMode(BlendMode mode) {
switch (mode) {
case BlendMode::None:
@@ -116,38 +335,37 @@ void Renderer::setupBlendMode(BlendMode mode) {
case BlendMode::Normal:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA);
break;
case BlendMode::Additive:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE,
GL_ONE, GL_ONE);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE);
break;
case BlendMode::Screen:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_COLOR,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_COLOR, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA);
break;
case BlendMode::Premultiplied:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA);
break;
case BlendMode::Subtractive:
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
break;
case BlendMode::Multiply:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA);
break;
}
}
@@ -160,7 +378,7 @@ void Renderer::updateUniforms() {
spriteShader->setMat4("u_view", camera_->getViewMatrix());
spriteShader->setMat4("u_projection", camera_->getProjectionMatrix());
}
auto* coloredShader = shaderManager_.getShader("colored_quad");
if (coloredShader) {
coloredShader->use();
@@ -177,57 +395,57 @@ void Renderer::updateUniforms() {
}
}
void Renderer::drawQuad(const Vec2& pos, const Size& size, float cr, float cg,
float cb, float ca) {
void Renderer::drawQuad(const Vec2& pos, const Size& size, float cr, float cg,
float cb, float ca) {
Rect rect(pos.x, pos.y, size.width, size.height);
Quad quad = Quad::create(rect, cr, cg, cb, ca);
Transform2D transform = Transform2D::identity();
auto* shader = shaderManager_.getShader("colored_quad");
if (shader) {
shader->use();
}
batch_.submitQuad(quad, transform, nullptr, shader, BlendMode::Normal);
}
void Renderer::drawQuad(const Vec2& pos, const Size& size, Ptr<Texture> texture) {
Rect rect(pos.x, pos.y, size.width, size.height);
Quad quad = Quad::createTextured(rect, Rect(0, 0, size.width, size.height),
Vec2(size.width, size.height));
Vec2(size.width, size.height));
Transform2D transform = Transform2D::identity();
auto* shader = shaderManager_.getShader("sprite");
if (shader) {
shader->use();
}
batch_.submitQuad(quad, transform, texture, shader, BlendMode::Normal);
}
void Renderer::drawQuad(const Rect& rect, const Color& color) {
drawQuad(Vec2(rect.left(), rect.top()),
Size(rect.width(), rect.height()),
color.r, color.g, color.b, color.a);
drawQuad(Vec2(rect.left(), rect.top()), Size(rect.width(), rect.height()),
color.r, color.g, color.b, color.a);
}
void Renderer::drawSprite(const Vec2& pos, const Size& size, Ptr<Texture> texture) {
drawQuad(pos, size, texture);
}
void Renderer::drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
Ptr<Texture> texture, const Color& color) {
void Renderer::drawSprite(const Vec2& pos, const Rect& srcRect,
const Vec2& texSize, Ptr<Texture> texture,
const Color& color) {
Rect destRect(pos.x, pos.y, srcRect.width(), srcRect.height());
Quad quad = Quad::createTextured(destRect, srcRect, texSize,
color.r, color.g, color.b, color.a);
Quad quad = Quad::createTextured(destRect, srcRect, texSize, color.r, color.g,
color.b, color.a);
Transform2D transform = Transform2D::identity();
auto* shader = shaderManager_.getShader("sprite");
if (shader) {
shader->use();
}
batch_.submitQuad(quad, transform, texture, shader, BlendMode::Normal);
}
}
} // namespace frostbite2D

View File

@@ -1,8 +1,11 @@
#include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/scene/ui_scene.h>
namespace frostbite2D {
UIScene::UIScene() {
Renderer& renderer = Renderer::get();
camera_.setViewport(renderer.getVirtualWidth(), renderer.getVirtualHeight());
camera_.setFlipY(true);
camera_.setZoom(1.0f);
camera_.setPosition(Vec2::Zero());

View File

@@ -45,7 +45,7 @@ private:
bool debugEnabled_ = false;
bool initialized_ = false;
float zoom_ = 1.2f;
float zoom_ = 1.0f;
float debugMoveSpeed_ = 800.0f;
};

View File

@@ -90,9 +90,9 @@ private:
int backgroundRepeatWidth_ = 0;
/// 地图配置里的整体 Y 偏移;既影响层位置,也影响地板校准。
int mapOffsetY_ = 0;
bool debugMode_ = true;
bool debugMode_ = false;
/// 硬编码调试开关:关闭后忽略可行走区域检测,允许角色自由移动。
bool movableAreaCheckEnabled_ = false;
bool movableAreaCheckEnabled_ = true;
/// 当前地图正在播放的背景音乐。
Ptr<Music> currentMusic_;
};

View File

@@ -34,9 +34,18 @@ int main(int argc, char **argv) {
AppConfig config = AppConfig::createDefault();
config.appName = "Frostbite2D Test App";
config.appVersion = "1.0.0";
#ifdef SWITCH
config.windowConfig.width = 1280;
config.windowConfig.height = 720;
#else
config.windowConfig.width = 1066;
config.windowConfig.height = 600;
#endif
config.windowConfig.title = "Frostbite2D - Async Init Demo";
config.useVirtualResolution = true;
config.virtualWidth = 1066;
config.virtualHeight = 600;
config.resolutionMode = ResolutionScaleMode::FitHeight;
Application &app = Application::get();
{
@@ -76,19 +85,20 @@ int main(int argc, char **argv) {
auto LoadingScene = MakePtr<Scene>();
SceneManager::get().PushScene(LoadingScene);
auto Background = Sprite::createFromFile("assets/ImagePacks2/Loading0.jpg");
Background->SetSize(1280, 720);
auto Background =
Sprite::createFromFile("assets/ImagePacks2/Loading0.png");
Background->SetSize(1066, 600);
LoadingScene->AddChild(Background);
auto BackgroundBar =
Sprite::createFromFile("assets/ImagePacks2/Loading1.png");
BackgroundBar->SetPosition(0, 686);
BackgroundBar->SetPosition(0 - 107, 566);
LoadingScene->AddChild(BackgroundBar);
auto LoadCircleSp =
Sprite::createFromFile("assets/ImagePacks2/Loading2.png");
LoadCircleSp->SetAnchor(Vec2(0.5f, 0.5f));
LoadCircleSp->SetPosition(1280 / 2.0f, 686 - 60);
LoadCircleSp->SetPosition(1066 / 2.0f, 566 - 60);
LoadCircleSp->SetBlendMode(BlendMode::Additive);
LoadCircleSp->AddUpdateListener([](Actor &self, float dt) {
auto rotation = self.GetRotation();

View File

@@ -10,8 +10,6 @@ namespace frostbite2D {
namespace {
constexpr float kScreenWidth = 1280.0f;
constexpr float kScreenHeight = 720.0f;
constexpr int kDebugStickDeadzone = 8000;
float normalizeControllerAxis(int16 value) {
@@ -180,10 +178,10 @@ void GameCameraController::applyFocus() const {
return;
}
float halfWidth = kScreenWidth * 0.5f / zoom_;
float halfHeight = kScreenHeight * 0.5f / zoom_;
Vec2 cameraPos(focus_.x - halfWidth, focus_.y - halfHeight);
camera->setZoom(zoom_);
float halfWidth = camera->getVisibleWidth() * 0.5f;
float halfHeight = camera->getVisibleHeight() * 0.5f;
Vec2 cameraPos(focus_.x - halfWidth, focus_.y - halfHeight);
camera->setPosition(cameraPos);
map_->ApplyCameraFocus(focus_);
}

View File

@@ -43,14 +43,14 @@ void GameMapTestScene::onEnter() {
{
ScopedStartupTrace stageTrace("GameMapTestScene character construction");
character_ = MakePtr<CharacterObject>();
if (!character_->Construction(0)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
character_ = MakePtr<CharacterObject>();
if (!character_->Construction(0)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"GameMapTestScene: failed to construct default character");
character_.Reset();
} else {
Vec2 spawnPos =
map_->ClampCameraFocus(map_->GetDefaultCameraFocus(), 1.2f);
map_->ClampCameraFocus(map_->GetDefaultCameraFocus(), 1.0f);
character_->SetCharacterPosition(spawnPos);
character_->EnableEventReceive();
character_->SetEventPriority(-100);
@@ -59,7 +59,6 @@ void GameMapTestScene::onEnter() {
}
cameraController_.SetMap(map_.Get());
// cameraController_.SetZoom(1.2f);
cameraController_.SetTarget(character_.Get());
cameraController_.SetDebugEnabled(false);
if (character_) {

View File

@@ -117,7 +117,6 @@ void GameTown::AddCharacter(RefPtr<Actor> actor, int areaIndex) {
AddChild(mapIt->map);
cameraController_.SetMap(mapIt->map.Get());
cameraController_.SetZoom(1.2f);
cameraController_.SetTarget(actor.Get());
cameraController_.SnapToDefaultFocus();