feat(渲染): 实现2D渲染风格系统
添加渲染风格预设配置,支持像素风、平滑2D和混合模式 新增纹理采样控制、顶点像素对齐和UV收缩优化 为相机和场景添加渲染风格覆盖功能
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <frostbite2D/core/window.h>
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/render_style.h>
|
||||
#include <frostbite2D/graphics/render_resolution.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
@@ -58,6 +59,12 @@ struct AppConfig {
|
||||
*/
|
||||
ResolutionScaleMode resolutionMode = ResolutionScaleMode::Fit;
|
||||
|
||||
/**
|
||||
* @brief 默认 2D 渲染风格预设
|
||||
* 启动时作为世界/UI 渲染的基础风格,Scene 可按需覆盖
|
||||
*/
|
||||
RenderStyleProfileId renderStyleProfile = RenderStyleProfileId::Hybrid2D;
|
||||
|
||||
/**
|
||||
* @brief 创建默认配置
|
||||
* @return 默认的应用配置实例
|
||||
@@ -69,6 +76,7 @@ struct AppConfig {
|
||||
config.organization = "frostbite";
|
||||
config.targetPlatform = PlatformType::Auto;
|
||||
config.windowConfig = WindowConfig();
|
||||
config.renderStyleProfile = RenderStyleProfileId::Hybrid2D;
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,32 +7,37 @@ namespace frostbite2D {
|
||||
class Camera {
|
||||
public:
|
||||
Camera();
|
||||
|
||||
|
||||
void setPosition(const Vec2& pos);
|
||||
void setZoom(float zoom);
|
||||
void setViewport(int width, int height);
|
||||
void setFlipY(bool flip) { flipY_ = flip; } // 调试用:Y轴翻转开关
|
||||
|
||||
void setFlipY(bool flip) { flipY_ = flip; } // Debug Y-axis flip.
|
||||
void setPixelSnapEnabled(bool enabled) { pixelSnapEnabled_ = enabled; }
|
||||
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
Vec2 getRenderPosition() const;
|
||||
Vec2 snapWorldPosition(const Vec2& position) const;
|
||||
float getZoom() const { return zoom_; }
|
||||
int getViewportWidth() const { return viewportWidth_; }
|
||||
int getViewportHeight() const { return viewportHeight_; }
|
||||
float getVisibleWidth() const;
|
||||
float getVisibleHeight() const;
|
||||
|
||||
bool isPixelSnapEnabled() const { return pixelSnapEnabled_; }
|
||||
|
||||
void lookAt(const Vec2& target);
|
||||
void move(const Vec2& delta);
|
||||
void zoomAt(float factor, const Vec2& screenPos);
|
||||
|
||||
|
||||
glm::mat4 getViewMatrix() const;
|
||||
glm::mat4 getProjectionMatrix() const;
|
||||
|
||||
private:
|
||||
|
||||
private:
|
||||
Vec2 position_;
|
||||
float zoom_ = 1.0f;
|
||||
int viewportWidth_ = 1280;
|
||||
int viewportHeight_ = 720;
|
||||
bool flipY_ = false; // 调试用:Y轴翻转开关
|
||||
bool flipY_ = false; // Debug Y-axis flip.
|
||||
bool pixelSnapEnabled_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace frostbite2D
|
||||
|
||||
72
Frostbite2D/include/frostbite2D/graphics/render_style.h
Normal file
72
Frostbite2D/include/frostbite2D/graphics/render_style.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 2D 渲染风格预设 ID
|
||||
*/
|
||||
enum class RenderStyleProfileId {
|
||||
PixelArt2D, ///< 世界与 UI 都按像素风策略渲染
|
||||
Smooth2D, ///< 世界与 UI 都按平滑 2D 策略渲染
|
||||
Hybrid2D ///< 世界走像素风,UI 走平滑策略
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 渲染风格作用层级
|
||||
*/
|
||||
enum class RenderStyleLayerRole {
|
||||
World, ///< 世界层,如地图、角色、场景特效
|
||||
UI ///< UI 层,如 HUD、菜单、文字
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单个层级的渲染风格开关集合
|
||||
*/
|
||||
struct RenderStyleLayerSettings {
|
||||
bool cameraPixelSnap = false; ///< 是否让相机位置吸附到像素网格
|
||||
bool vertexPixelSnap = false; ///< 是否让世界顶点在提交前做像素对齐
|
||||
bool pixelArtSampling = false; ///< 是否优先使用像素风采样(如 GL_NEAREST)
|
||||
bool shrinkSubTextureUVs = false; ///< 是否对子纹理 UV 做向内收缩,减轻图集边缘闪烁
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一套完整的 2D 渲染风格设置
|
||||
*/
|
||||
struct RenderStyleSettings {
|
||||
RenderStyleLayerSettings world; ///< 世界层使用的渲染风格
|
||||
RenderStyleLayerSettings ui; ///< UI 层使用的渲染风格
|
||||
|
||||
/**
|
||||
* @brief 根据预设 ID 生成对应的渲染风格
|
||||
*/
|
||||
static RenderStyleSettings FromProfile(RenderStyleProfileId profile) {
|
||||
RenderStyleSettings settings;
|
||||
|
||||
switch (profile) {
|
||||
case RenderStyleProfileId::PixelArt2D:
|
||||
settings.world = {true, true, true, true};
|
||||
settings.ui = {true, true, true, true};
|
||||
break;
|
||||
case RenderStyleProfileId::Smooth2D:
|
||||
settings.world = {false, false, false, false};
|
||||
settings.ui = {false, false, false, false};
|
||||
break;
|
||||
case RenderStyleProfileId::Hybrid2D:
|
||||
default:
|
||||
settings.world = {true, true, true, true};
|
||||
settings.ui = {false, false, false, false};
|
||||
break;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定层级对应的风格设置
|
||||
*/
|
||||
const RenderStyleLayerSettings& layer(RenderStyleLayerRole role) const {
|
||||
return role == RenderStyleLayerRole::UI ? ui : world;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <frostbite2D/graphics/batch.h>
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/render_resolution.h>
|
||||
#include <frostbite2D/graphics/render_style.h>
|
||||
#include <frostbite2D/graphics/shader.h>
|
||||
#include <frostbite2D/graphics/shader_manager.h>
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
@@ -31,6 +32,26 @@ public:
|
||||
void setClearColor(float r, float g, float b, float a = 1.0f);
|
||||
void setClearColor(const Color& color);
|
||||
void clear(uint32_t flags);
|
||||
/**
|
||||
* @brief 设置应用默认渲染风格预设
|
||||
*/
|
||||
void setDefaultRenderStyleProfile(RenderStyleProfileId profile);
|
||||
/**
|
||||
* @brief 获取应用默认渲染风格预设
|
||||
*/
|
||||
RenderStyleProfileId getDefaultRenderStyleProfile() const {
|
||||
return defaultRenderStyleProfile_;
|
||||
}
|
||||
/**
|
||||
* @brief 切换当前正在使用的渲染风格上下文
|
||||
*/
|
||||
void setActiveRenderStyleProfile(RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role);
|
||||
/**
|
||||
* @brief 将指定风格作用到某个相机
|
||||
*/
|
||||
void applyRenderStyleToCamera(Camera* camera, RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role) const;
|
||||
|
||||
void setCamera(Camera* camera);
|
||||
Camera* getCamera() { return camera_; }
|
||||
@@ -53,6 +74,28 @@ public:
|
||||
Ptr<Texture> texture,
|
||||
const Color& color = Color(1, 1, 1, 1));
|
||||
void setupBlendMode(BlendMode mode);
|
||||
/**
|
||||
* @brief 获取当前激活的层级风格设置
|
||||
*/
|
||||
const RenderStyleLayerSettings& getActiveRenderStyle() const {
|
||||
return activeRenderStyle_;
|
||||
}
|
||||
/**
|
||||
* @brief 当前风格是否要求顶点像素对齐
|
||||
*/
|
||||
bool shouldSnapVertices() const { return activeRenderStyle_.vertexPixelSnap; }
|
||||
/**
|
||||
* @brief 当前风格是否优先使用像素风采样
|
||||
*/
|
||||
bool shouldUsePixelArtSampling() const {
|
||||
return activeRenderStyle_.pixelArtSampling;
|
||||
}
|
||||
/**
|
||||
* @brief 当前风格是否需要对子纹理 UV 做向内收缩
|
||||
*/
|
||||
bool shouldShrinkSubTextureUVs() const {
|
||||
return activeRenderStyle_.shrinkSubTextureUVs;
|
||||
}
|
||||
|
||||
ShaderManager& getShaderManager() { return shaderManager_; }
|
||||
Batch& getBatch() { return batch_; }
|
||||
@@ -67,6 +110,10 @@ private:
|
||||
ShaderManager shaderManager_;
|
||||
Batch batch_;
|
||||
Camera* camera_ = nullptr;
|
||||
RenderStyleProfileId defaultRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
|
||||
RenderStyleProfileId activeRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
|
||||
RenderStyleLayerRole activeRenderStyleRole_ = RenderStyleLayerRole::World;
|
||||
RenderStyleLayerSettings activeRenderStyle_;
|
||||
|
||||
uint32_t clearColor_[4] = {0, 0, 0, 255};
|
||||
bool useVirtualResolution_ = false;
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
enum class TextureSampling {
|
||||
Linear,
|
||||
PixelArt
|
||||
};
|
||||
|
||||
class Texture : public RefObject {
|
||||
public:
|
||||
static Ptr<Texture> loadFromFile(const std::string& path);
|
||||
@@ -16,11 +21,13 @@ public:
|
||||
|
||||
~Texture();
|
||||
|
||||
void bind(uint32_t slot = 0);
|
||||
void bind(uint32_t slot = 0, bool preferPixelArtSampling = false);
|
||||
void unbind();
|
||||
|
||||
void setWrapMode(uint32_t wrapS, uint32_t wrapT);
|
||||
void setFilterMode(uint32_t minFilter, uint32_t magFilter);
|
||||
void setSampling(TextureSampling sampling);
|
||||
TextureSampling getSampling() const { return sampling_; }
|
||||
|
||||
int getWidth() const { return width_; }
|
||||
int getHeight() const { return height_; }
|
||||
@@ -29,16 +36,21 @@ public:
|
||||
|
||||
private:
|
||||
Texture(int width, int height, uint32_t id);
|
||||
void applyFilter(uint32_t minFilter, uint32_t magFilter);
|
||||
void applySampling(bool preferPixelArtSampling);
|
||||
|
||||
uint32_t textureID_ = 0;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int channels_ = 0;
|
||||
std::string path_;
|
||||
TextureSampling sampling_ = TextureSampling::Linear;
|
||||
uint32_t appliedMinFilter_ = 0;
|
||||
uint32_t appliedMagFilter_ = 0;
|
||||
|
||||
Texture() = default;
|
||||
Texture(const Texture&) = delete;
|
||||
Texture& operator=(const Texture&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ struct Quad {
|
||||
|
||||
static Quad createTextured(const Rect& destRect, const Rect& srcRect,
|
||||
const Vec2& texSize, float cr = 1.0f,
|
||||
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f) {
|
||||
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f,
|
||||
bool shrinkSubTextureUVs = false) {
|
||||
Quad q;
|
||||
|
||||
Vec2 bl(destRect.left(), destRect.bottom());
|
||||
@@ -65,10 +66,38 @@ struct Quad {
|
||||
Vec2 tl(destRect.left(), destRect.top());
|
||||
Vec2 tr(destRect.right(), destRect.top());
|
||||
|
||||
Vec2 uvBL(srcRect.left() / texSize.x, srcRect.bottom() / texSize.y);
|
||||
Vec2 uvBR(srcRect.right() / texSize.x, srcRect.bottom() / texSize.y);
|
||||
Vec2 uvTL(srcRect.left() / texSize.x, srcRect.top() / texSize.y);
|
||||
Vec2 uvTR(srcRect.right() / texSize.x, srcRect.top() / texSize.y);
|
||||
auto resolveUvX = [&srcRect, &texSize, shrinkSubTextureUVs](bool useRightEdge) {
|
||||
float pixelInset = 0.0f;
|
||||
if (!shrinkSubTextureUVs) {
|
||||
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x;
|
||||
}
|
||||
if (useRightEdge && srcRect.right() < texSize.x) {
|
||||
pixelInset = -0.5f;
|
||||
} else if (!useRightEdge && srcRect.left() > 0.0f) {
|
||||
pixelInset = 0.5f;
|
||||
}
|
||||
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x +
|
||||
pixelInset / texSize.x;
|
||||
};
|
||||
|
||||
auto resolveUvY = [&srcRect, &texSize, shrinkSubTextureUVs](bool useBottomEdge) {
|
||||
float pixelInset = 0.0f;
|
||||
if (!shrinkSubTextureUVs) {
|
||||
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y;
|
||||
}
|
||||
if (useBottomEdge && srcRect.bottom() < texSize.y) {
|
||||
pixelInset = -0.5f;
|
||||
} else if (!useBottomEdge && srcRect.top() > 0.0f) {
|
||||
pixelInset = 0.5f;
|
||||
}
|
||||
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y +
|
||||
pixelInset / texSize.y;
|
||||
};
|
||||
|
||||
Vec2 uvBL(resolveUvX(false), resolveUvY(true));
|
||||
Vec2 uvBR(resolveUvX(true), resolveUvY(true));
|
||||
Vec2 uvTL(resolveUvX(false), resolveUvY(false));
|
||||
Vec2 uvTR(resolveUvX(true), resolveUvY(false));
|
||||
|
||||
q.vertices[0] = Vertex(bl, uvBL, cr, cg, cb, ca);
|
||||
q.vertices[1] = Vertex(br, uvBR, cr, cg, cb, ca);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/graphics/render_style.h>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
@@ -18,6 +19,20 @@ public:
|
||||
void Render() override;
|
||||
|
||||
bool OnEvent(const Event& event) override;
|
||||
/**
|
||||
* @brief 为当前场景指定渲染风格预设
|
||||
*/
|
||||
void SetRenderStyleProfile(RenderStyleProfileId profile);
|
||||
/**
|
||||
* @brief 清除场景级渲染风格覆盖,回退到应用默认值
|
||||
*/
|
||||
void ClearRenderStyleProfileOverride();
|
||||
bool HasRenderStyleProfileOverride() const { return hasRenderStyleProfileOverride_; }
|
||||
/**
|
||||
* @brief 解析当前场景最终应使用的渲染风格
|
||||
*/
|
||||
RenderStyleProfileId ResolveRenderStyleProfile(
|
||||
RenderStyleProfileId defaultProfile) const;
|
||||
|
||||
static Scene* GetCurrent();
|
||||
|
||||
@@ -25,9 +40,10 @@ private:
|
||||
bool dispatchToChildren(const Event& event);
|
||||
|
||||
static Scene* current_;
|
||||
bool hasRenderStyleProfileOverride_ = false;
|
||||
RenderStyleProfileId renderStyleProfileOverride_ = RenderStyleProfileId::Hybrid2D;
|
||||
|
||||
friend class SceneManager;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Ptr<Sprite> Sprite::createFromFile(const std::string &path) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
texture->setSampling(TextureSampling::PixelArt);
|
||||
|
||||
auto sprite = MakePtr<Sprite>(texture);
|
||||
sprite->SetSizeToTexture();
|
||||
@@ -38,6 +39,7 @@ Ptr<Sprite> Sprite::createFromMemory(const uint8* data, int width, int height, i
|
||||
"Failed to create texture from memory");
|
||||
return nullptr;
|
||||
}
|
||||
texture->setSampling(TextureSampling::PixelArt);
|
||||
|
||||
auto sprite = MakePtr<Sprite>(texture);
|
||||
sprite->SetSizeToTexture();
|
||||
@@ -73,7 +75,8 @@ void Sprite::Render() {
|
||||
Quad quad = Quad::createTextured(
|
||||
destRect, srcRect,
|
||||
Vec2((float)texture_->getWidth(), (float)texture_->getHeight()),
|
||||
color_.r, color_.g, color_.b, color_.a * worldOpacity
|
||||
color_.r, color_.g, color_.b, color_.a * worldOpacity,
|
||||
renderer.shouldShrinkSubTextureUVs()
|
||||
);
|
||||
|
||||
if (flippedX_) {
|
||||
@@ -180,6 +183,7 @@ Shader* Sprite::getActiveShader() const {
|
||||
}
|
||||
|
||||
Quad Sprite::createQuad() const {
|
||||
Renderer& renderer = Renderer::get();
|
||||
Rect srcRect = srcRect_;
|
||||
if (srcRect.empty()) {
|
||||
srcRect = Rect(0, 0, texture_->getWidth(), texture_->getHeight());
|
||||
@@ -201,7 +205,8 @@ Quad Sprite::createQuad() const {
|
||||
float worldOpacity = GetWorldOpacity();
|
||||
Quad quad = Quad::createTextured(
|
||||
destRect, srcRect, texSize,
|
||||
color_.r, color_.g, color_.b, color_.a * worldOpacity
|
||||
color_.r, color_.g, color_.b, color_.a * worldOpacity,
|
||||
renderer.shouldShrinkSubTextureUVs()
|
||||
);
|
||||
|
||||
if (flippedX_) {
|
||||
|
||||
@@ -148,6 +148,7 @@ void TextSprite::RenderText() {
|
||||
);
|
||||
|
||||
if (texture) {
|
||||
texture->setSampling(TextureSampling::Linear);
|
||||
SetTexture(texture);
|
||||
SetSizeToTexture();
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ bool Application::initCoreModules() {
|
||||
|
||||
// 设置窗口清除颜色和视口
|
||||
renderer_->setClearColor(0.0f, 0.0f, 0.0f);
|
||||
renderer_->setDefaultRenderStyleProfile(config_.renderStyleProfile);
|
||||
renderer_->setVirtualResolutionEnabled(config_.useVirtualResolution);
|
||||
renderer_->setResolutionScaleMode(config_.resolutionMode);
|
||||
if (config_.virtualWidth > 0 && config_.virtualHeight > 0) {
|
||||
@@ -214,7 +215,9 @@ bool Application::initCoreModules() {
|
||||
ScopedStartupTrace stageTrace("Camera setup");
|
||||
camera_ = new Camera();
|
||||
camera_->setViewport(renderer_->getVirtualWidth(), renderer_->getVirtualHeight());
|
||||
camera_->setFlipY(true); // 启用 Y 轴翻转,(0,0) 在左上角
|
||||
camera_->setFlipY(true); // Use top-left as (0,0).
|
||||
renderer_->applyRenderStyleToCamera(
|
||||
camera_, config_.renderStyleProfile, RenderStyleLayerRole::World);
|
||||
renderer_->setCamera(camera_);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,15 +98,25 @@ void Batch::submitQuad(const Quad& quad, const Transform2D& transform,
|
||||
|
||||
flushIfNeeded(newKey, shader, texture);
|
||||
|
||||
Renderer& renderer = Renderer::get();
|
||||
glm::mat4 mat = transform.matrix;
|
||||
Camera* camera = renderer.getCamera();
|
||||
Vec2 snapOffset = Vec2::Zero();
|
||||
if (camera && renderer.shouldSnapVertices()) {
|
||||
glm::vec4 referencePos(quad.vertices[0].position.x,
|
||||
quad.vertices[0].position.y, 0.0f, 1.0f);
|
||||
glm::vec4 transformedReference = mat * referencePos;
|
||||
Vec2 referenceWorld(transformedReference.x, transformedReference.y);
|
||||
snapOffset = camera->snapWorldPosition(referenceWorld) - referenceWorld;
|
||||
}
|
||||
uint16_t indexOffset = static_cast<uint16_t>(currentBatch_.vertices.size());
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Vertex v = quad.vertices[i];
|
||||
glm::vec4 pos(v.position.x, v.position.y, 0.0f, 1.0f);
|
||||
glm::vec4 transformed = mat * pos;
|
||||
v.position.x = transformed.x;
|
||||
v.position.y = transformed.y;
|
||||
v.position.x = transformed.x + snapOffset.x;
|
||||
v.position.y = transformed.y + snapOffset.y;
|
||||
|
||||
currentBatch_.vertices.push_back(v);
|
||||
}
|
||||
@@ -161,7 +171,9 @@ void Batch::flushCurrentBatch() {
|
||||
currentBatch_.shader->setTexture("u_texture", 0);
|
||||
}
|
||||
if (currentBatch_.texture) {
|
||||
currentBatch_.texture->bind(0);
|
||||
Renderer& renderer = Renderer::get();
|
||||
bool preferPixelArtSampling = renderer.shouldUsePixelArtSampling();
|
||||
currentBatch_.texture->bind(0, preferPixelArtSampling);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
|
||||
@@ -1,47 +1,80 @@
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kMinZoom = 0.01f;
|
||||
|
||||
float clampZoom(float zoom) {
|
||||
return std::max(zoom, kMinZoom);
|
||||
}
|
||||
|
||||
Vec2 snapPositionToPixelGrid(const Vec2& position, float zoom) {
|
||||
float safeZoom = clampZoom(zoom);
|
||||
return Vec2(std::round(position.x * safeZoom) / safeZoom,
|
||||
std::round(position.y * safeZoom) / safeZoom);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Camera::Camera() : position_(0, 0), zoom_(1.0f) {}
|
||||
|
||||
void Camera::setPosition(const Vec2 &pos) { position_ = pos; }
|
||||
void Camera::setPosition(const Vec2& pos) { position_ = pos; }
|
||||
|
||||
void Camera::setZoom(float zoom) { zoom_ = zoom; }
|
||||
void Camera::setZoom(float zoom) { zoom_ = clampZoom(zoom); }
|
||||
|
||||
void Camera::setViewport(int width, int height) {
|
||||
viewportWidth_ = width;
|
||||
viewportHeight_ = height;
|
||||
}
|
||||
|
||||
Vec2 Camera::getRenderPosition() const {
|
||||
return snapWorldPosition(position_);
|
||||
}
|
||||
|
||||
Vec2 Camera::snapWorldPosition(const Vec2& position) const {
|
||||
if (!pixelSnapEnabled_) {
|
||||
return position;
|
||||
}
|
||||
return snapPositionToPixelGrid(position, zoom_);
|
||||
}
|
||||
|
||||
float Camera::getVisibleWidth() const {
|
||||
float safeZoom = std::max(zoom_, 0.01f);
|
||||
float safeZoom = clampZoom(zoom_);
|
||||
return static_cast<float>(viewportWidth_) / safeZoom;
|
||||
}
|
||||
|
||||
float Camera::getVisibleHeight() const {
|
||||
float safeZoom = std::max(zoom_, 0.01f);
|
||||
float safeZoom = clampZoom(zoom_);
|
||||
return static_cast<float>(viewportHeight_) / safeZoom;
|
||||
}
|
||||
|
||||
void Camera::lookAt(const Vec2 &target) { position_ = target; }
|
||||
void Camera::lookAt(const Vec2& target) { position_ = target; }
|
||||
|
||||
void Camera::move(const Vec2 &delta) { position_ = position_ + delta; }
|
||||
void Camera::move(const Vec2& delta) { position_ = position_ + delta; }
|
||||
|
||||
void Camera::zoomAt(float factor, const Vec2 &screenPos) {
|
||||
Vec2 worldBefore = screenPos - position_;
|
||||
zoom_ *= factor;
|
||||
Vec2 worldAfter = screenPos - position_;
|
||||
void Camera::zoomAt(float factor, const Vec2& screenPos) {
|
||||
float previousZoom = clampZoom(zoom_);
|
||||
Vec2 worldBefore(position_.x + screenPos.x / previousZoom,
|
||||
position_.y + screenPos.y / previousZoom);
|
||||
zoom_ = clampZoom(zoom_ * factor);
|
||||
Vec2 worldAfter(position_.x + screenPos.x / zoom_,
|
||||
position_.y + screenPos.y / zoom_);
|
||||
position_ = position_ + (worldBefore - worldAfter);
|
||||
}
|
||||
|
||||
glm::mat4 Camera::getViewMatrix() const {
|
||||
float safeZoom = clampZoom(zoom_);
|
||||
Vec2 renderPosition = getRenderPosition();
|
||||
|
||||
glm::mat4 view = glm::mat4(1.0f);
|
||||
view[3][0] = -position_.x;
|
||||
view[3][1] = -position_.y;
|
||||
view[0][0] = zoom_;
|
||||
view[1][1] = zoom_;
|
||||
view = glm::scale(view, glm::vec3(safeZoom, safeZoom, 1.0f));
|
||||
view = glm::translate(view,
|
||||
glm::vec3(-renderPosition.x, -renderPosition.y, 0.0f));
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -49,15 +82,11 @@ glm::mat4 Camera::getProjectionMatrix() const {
|
||||
float left = 0.0f;
|
||||
float right = static_cast<float>(viewportWidth_);
|
||||
float bottom, top;
|
||||
|
||||
|
||||
if (flipY_) {
|
||||
// Y 轴向下:(0,0) 在左上角(2D 游戏常用)
|
||||
// glm::ortho(left, right, bottom, top, ...)
|
||||
// 这里 bottom 是屏幕底部(值大),top 是屏幕顶部(值小)
|
||||
bottom = static_cast<float>(viewportHeight_);
|
||||
top = 0.0f;
|
||||
} else {
|
||||
// Y 轴向上:(0,0) 在左下角(OpenGL 默认)
|
||||
bottom = 0.0f;
|
||||
top = static_cast<float>(viewportHeight_);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ int roundToInt(float value) {
|
||||
return static_cast<int>(std::lround(value));
|
||||
}
|
||||
|
||||
RenderStyleLayerSettings resolveRenderStyleLayer(RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role) {
|
||||
return RenderStyleSettings::FromProfile(profile).layer(role);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Renderer& Renderer::get() {
|
||||
@@ -23,7 +28,10 @@ Renderer& Renderer::get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
Renderer::Renderer() = default;
|
||||
Renderer::Renderer() {
|
||||
activeRenderStyle_ =
|
||||
resolveRenderStyleLayer(defaultRenderStyleProfile_, activeRenderStyleRole_);
|
||||
}
|
||||
|
||||
bool Renderer::init() {
|
||||
if (initialized_) {
|
||||
@@ -157,6 +165,34 @@ void Renderer::clear(uint32_t flags) {
|
||||
glClear(flags);
|
||||
}
|
||||
|
||||
void Renderer::setDefaultRenderStyleProfile(RenderStyleProfileId profile) {
|
||||
defaultRenderStyleProfile_ = profile;
|
||||
setActiveRenderStyleProfile(profile, activeRenderStyleRole_);
|
||||
}
|
||||
|
||||
void Renderer::setActiveRenderStyleProfile(RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role) {
|
||||
if (initialized_ &&
|
||||
(activeRenderStyleProfile_ != profile || activeRenderStyleRole_ != role)) {
|
||||
batch_.flush();
|
||||
}
|
||||
|
||||
activeRenderStyleProfile_ = profile;
|
||||
activeRenderStyleRole_ = role;
|
||||
activeRenderStyle_ = resolveRenderStyleLayer(profile, role);
|
||||
}
|
||||
|
||||
void Renderer::applyRenderStyleToCamera(Camera* camera,
|
||||
RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role) const {
|
||||
if (!camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderStyleLayerSettings settings = resolveRenderStyleLayer(profile, role);
|
||||
camera->setPixelSnapEnabled(settings.cameraPixelSnap);
|
||||
}
|
||||
|
||||
void Renderer::setCamera(Camera* camera) {
|
||||
if (initialized_ && camera_ != camera) {
|
||||
batch_.flush();
|
||||
@@ -412,7 +448,9 @@ void Renderer::drawQuad(const Vec2& pos, const Size& size, float cr, float cg,
|
||||
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),
|
||||
1.0f, 1.0f, 1.0f, 1.0f,
|
||||
shouldShrinkSubTextureUVs());
|
||||
Transform2D transform = Transform2D::identity();
|
||||
|
||||
auto* shader = shaderManager_.getShader("sprite");
|
||||
@@ -437,7 +475,8 @@ void Renderer::drawSprite(const Vec2& pos, const Rect& srcRect,
|
||||
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);
|
||||
color.b, color.a,
|
||||
shouldShrinkSubTextureUVs());
|
||||
Transform2D transform = Transform2D::identity();
|
||||
|
||||
auto* shader = shaderManager_.getShader("sprite");
|
||||
|
||||
@@ -84,6 +84,8 @@ Ptr<Texture> Texture::loadFromFile(const std::string& path) {
|
||||
auto texture = Ptr<Texture>(new Texture(width, height, textureID));
|
||||
texture->path_ = resolvedPath;
|
||||
texture->channels_ = channels;
|
||||
texture->appliedMinFilter_ = GL_LINEAR;
|
||||
texture->appliedMagFilter_ = GL_LINEAR;
|
||||
|
||||
return texture;
|
||||
}
|
||||
@@ -111,6 +113,8 @@ Ptr<Texture> Texture::createFromMemory(const uint8* data, int width, int height,
|
||||
|
||||
auto texture = Ptr<Texture>(new Texture(width, height, textureID));
|
||||
texture->channels_ = channels;
|
||||
texture->appliedMinFilter_ = GL_LINEAR;
|
||||
texture->appliedMagFilter_ = GL_LINEAR;
|
||||
|
||||
return texture;
|
||||
}
|
||||
@@ -129,12 +133,16 @@ Ptr<Texture> Texture::createEmpty(int width, int height) {
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
return Ptr<Texture>(new Texture(width, height, textureID));
|
||||
auto texture = Ptr<Texture>(new Texture(width, height, textureID));
|
||||
texture->appliedMinFilter_ = GL_LINEAR;
|
||||
texture->appliedMagFilter_ = GL_LINEAR;
|
||||
return texture;
|
||||
}
|
||||
|
||||
void Texture::bind(uint32_t slot) {
|
||||
void Texture::bind(uint32_t slot, bool preferPixelArtSampling) {
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID_);
|
||||
applySampling(preferPixelArtSampling);
|
||||
}
|
||||
|
||||
void Texture::unbind() {
|
||||
@@ -150,9 +158,35 @@ void Texture::setWrapMode(uint32_t wrapS, uint32_t wrapT) {
|
||||
|
||||
void Texture::setFilterMode(uint32_t minFilter, uint32_t magFilter) {
|
||||
bind();
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
|
||||
applyFilter(minFilter, magFilter);
|
||||
sampling_ = (minFilter == GL_NEAREST && magFilter == GL_NEAREST)
|
||||
? TextureSampling::PixelArt
|
||||
: TextureSampling::Linear;
|
||||
unbind();
|
||||
}
|
||||
|
||||
void Texture::setSampling(TextureSampling sampling) {
|
||||
sampling_ = sampling;
|
||||
appliedMinFilter_ = 0;
|
||||
appliedMagFilter_ = 0;
|
||||
}
|
||||
|
||||
void Texture::applyFilter(uint32_t minFilter, uint32_t magFilter) {
|
||||
if (appliedMinFilter_ == minFilter && appliedMagFilter_ == magFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
|
||||
appliedMinFilter_ = minFilter;
|
||||
appliedMagFilter_ = magFilter;
|
||||
}
|
||||
|
||||
void Texture::applySampling(bool preferPixelArtSampling) {
|
||||
bool useNearest =
|
||||
sampling_ == TextureSampling::PixelArt && preferPixelArtSampling;
|
||||
applyFilter(useNearest ? GL_NEAREST : GL_LINEAR,
|
||||
useNearest ? GL_NEAREST : GL_LINEAR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,6 +43,21 @@ bool Scene::OnEvent(const Event& event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Scene::SetRenderStyleProfile(RenderStyleProfileId profile) {
|
||||
renderStyleProfileOverride_ = profile;
|
||||
hasRenderStyleProfileOverride_ = true;
|
||||
}
|
||||
|
||||
void Scene::ClearRenderStyleProfileOverride() {
|
||||
hasRenderStyleProfileOverride_ = false;
|
||||
}
|
||||
|
||||
RenderStyleProfileId Scene::ResolveRenderStyleProfile(
|
||||
RenderStyleProfileId defaultProfile) const {
|
||||
return hasRenderStyleProfileOverride_ ? renderStyleProfileOverride_
|
||||
: defaultProfile;
|
||||
}
|
||||
|
||||
bool Scene::dispatchToChildren(const Event& event) {
|
||||
if (event.isPropagationStopped()) {
|
||||
return false;
|
||||
|
||||
@@ -5,6 +5,18 @@
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
namespace {
|
||||
|
||||
RenderStyleProfileId resolveSceneRenderStyleProfile(const Scene* scene,
|
||||
const Renderer& renderer) {
|
||||
if (!scene) {
|
||||
return renderer.getDefaultRenderStyleProfile();
|
||||
}
|
||||
return scene->ResolveRenderStyleProfile(renderer.getDefaultRenderStyleProfile());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneManager& SceneManager::get() {
|
||||
static SceneManager instance;
|
||||
return instance;
|
||||
@@ -123,13 +135,23 @@ void SceneManager::ClearAll() {
|
||||
|
||||
void SceneManager::Update(float deltaTime) {
|
||||
if (!sceneStack_.empty()) {
|
||||
Renderer& renderer = Renderer::get();
|
||||
RenderStyleProfileId profile =
|
||||
resolveSceneRenderStyleProfile(sceneStack_.back().Get(), renderer);
|
||||
renderer.applyRenderStyleToCamera(renderer.getCamera(), profile,
|
||||
RenderStyleLayerRole::World);
|
||||
sceneStack_.back()->Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void SceneManager::UpdateUI(float deltaTime) {
|
||||
Renderer& renderer = Renderer::get();
|
||||
for (auto& scene : uiSceneStack_) {
|
||||
if (scene) {
|
||||
RenderStyleProfileId profile =
|
||||
resolveSceneRenderStyleProfile(scene.Get(), renderer);
|
||||
renderer.applyRenderStyleToCamera(scene->GetCamera(), profile,
|
||||
RenderStyleLayerRole::UI);
|
||||
scene->Update(deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -138,8 +160,15 @@ void SceneManager::UpdateUI(float deltaTime) {
|
||||
void SceneManager::Render() {
|
||||
Renderer& renderer = Renderer::get();
|
||||
Camera* worldCamera = renderer.getCamera();
|
||||
RenderStyleProfileId worldProfile = renderer.getDefaultRenderStyleProfile();
|
||||
|
||||
if (!sceneStack_.empty()) {
|
||||
worldProfile =
|
||||
resolveSceneRenderStyleProfile(sceneStack_.back().Get(), renderer);
|
||||
renderer.applyRenderStyleToCamera(worldCamera, worldProfile,
|
||||
RenderStyleLayerRole::World);
|
||||
renderer.setActiveRenderStyleProfile(worldProfile,
|
||||
RenderStyleLayerRole::World);
|
||||
sceneStack_.back()->Render();
|
||||
}
|
||||
|
||||
@@ -148,6 +177,11 @@ void SceneManager::Render() {
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderStyleProfileId uiProfile =
|
||||
resolveSceneRenderStyleProfile(scene.Get(), renderer);
|
||||
renderer.applyRenderStyleToCamera(scene->GetCamera(), uiProfile,
|
||||
RenderStyleLayerRole::UI);
|
||||
renderer.setActiveRenderStyleProfile(uiProfile, RenderStyleLayerRole::UI);
|
||||
renderer.setCamera(scene->GetCamera());
|
||||
scene->Render();
|
||||
}
|
||||
@@ -155,6 +189,7 @@ void SceneManager::Render() {
|
||||
if (renderer.getCamera() != worldCamera) {
|
||||
renderer.setCamera(worldCamera);
|
||||
}
|
||||
renderer.setActiveRenderStyleProfile(worldProfile, RenderStyleLayerRole::World);
|
||||
}
|
||||
|
||||
bool SceneManager::DispatchEvent(const Event& event) {
|
||||
|
||||
@@ -46,6 +46,7 @@ int main(int argc, char **argv) {
|
||||
config.virtualWidth = 1066;
|
||||
config.virtualHeight = 600;
|
||||
config.resolutionMode = ResolutionScaleMode::FitHeight;
|
||||
config.renderStyleProfile = RenderStyleProfileId::PixelArt2D;
|
||||
|
||||
Application &app = Application::get();
|
||||
{
|
||||
|
||||
@@ -381,7 +381,7 @@ void GameMap::updateLayerPositions(const Vec2 &cameraFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vec2 cameraPos = camera->getPosition();
|
||||
Vec2 cameraPos = camera->getRenderPosition();
|
||||
|
||||
// 主视图平移完全交给底层
|
||||
// Camera,这里只保留地图层自己的静态校准和少量视差偏移。
|
||||
|
||||
Reference in New Issue
Block a user