feat(渲染): 实现2D渲染风格系统

添加渲染风格预设配置,支持像素风、平滑2D和混合模式
新增纹理采样控制、顶点像素对齐和UV收缩优化
为相机和场景添加渲染风格覆盖功能
This commit is contained in:
2026-04-07 00:15:48 +08:00
parent 62b0f6dafd
commit 875af43f88
18 changed files with 414 additions and 51 deletions

View File

@@ -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;
}
};

View File

@@ -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

View 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

View File

@@ -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;

View File

@@ -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;
};
}
}

View File

@@ -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);

View File

@@ -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;
};
}

View File

@@ -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_) {

View File

@@ -148,6 +148,7 @@ void TextSprite::RenderText() {
);
if (texture) {
texture->setSampling(TextureSampling::Linear);
SetTexture(texture);
SetSizeToTexture();
}

View File

@@ -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_);
}

View File

@@ -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_);

View File

@@ -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_);
}

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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();
{

View File

@@ -381,7 +381,7 @@ void GameMap::updateLayerPositions(const Vec2 &cameraFocus) {
return;
}
Vec2 cameraPos = camera->getPosition();
Vec2 cameraPos = camera->getRenderPosition();
// 主视图平移完全交给底层
// Camera这里只保留地图层自己的静态校准和少量视差偏移。