feat: 添加图像显示示例并支持纹理覆盖

- 新增 image_display_demo 示例项目,展示如何加载和显示纹理
- 在 SpriteRenderer 中添加纹理覆盖支持,允许组件指定独立纹理
- 修改渲染管线,在绑定着色器时设置默认纹理单元
- 更新纹理加载逻辑,简化纹理数据上传接口
- 扩展渲染命令结构,包含可选的纹理覆盖句柄
- 调整命令队列提交接口,支持纹理覆盖参数传递
This commit is contained in:
ChestnutYueyue 2026-03-16 21:11:15 +08:00
parent 8f03fa80fb
commit dcb3162525
12 changed files with 269 additions and 14 deletions

View File

@ -0,0 +1,98 @@
#include <cstdio>
#include <extra2d.h>
#include <renderer/rhi_module.h>
using namespace extra2d;
class ImageDisplayScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
createCamera();
createImageSprite();
}
void update(float dt) override {
Scene::update(dt);
if (textureLoaded_) {
return;
}
auto *rhiModule = RHIModule::get();
if (!rhiModule || !rhiModule->getDevice()) {
return;
}
auto assets = getAssets();
if (!assets || !spriteRenderer_) {
return;
}
auto texture = assets->load<Texture>("assets/test.png");
if (texture.isValid()) {
spriteRenderer_->setTexture(texture);
printf("图片加载成功: assets/test.png\n");
} else {
printf("图片加载失败: assets/test.png\n");
}
textureLoaded_ = true;
}
private:
void createCamera() {
auto cameraNode = makePtr<Node>();
cameraNode->setName("MainCamera");
cameraNode->setPosition(0.0f, 0.0f);
auto camera = makePtr<CameraComponent>();
camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f);
cameraNode->addComponent(camera);
setMainCamera(camera);
addChild(cameraNode);
}
void createImageSprite() {
auto spriteNode = makePtr<Node>();
spriteNode->setName("ImageSprite");
spriteNode->setPosition(640.0f, 360.0f);
// spriteNode->setSize(384.0f, 384.0f);
spriteNode->setAnchor(0.5f, 0.5f);
spriteRenderer_ = makePtr<SpriteRenderer>();
// spriteRenderer_->setColor(Color::White);
spriteNode->addComponent(spriteRenderer_);
addChild(spriteNode);
}
Ptr<SpriteRenderer> spriteRenderer_;
bool textureLoaded_ = false;
};
int main(int argc, char **argv) {
auto app = Application::create();
AppConfig config;
config.title = "Image Display Demo - Extra2D";
config.width = 1280;
config.height = 720;
if (!app->init(config)) {
printf("应用程序初始化失败!\n");
return -1;
}
SceneModule *sceneModule = app->getModule<SceneModule>();
if (!sceneModule) {
printf("获取场景模块失败!\n");
return -1;
}
Director *director = sceneModule->getDirector();
if (!director) {
printf("获取导演器失败!\n");
return -1;
}
auto scene = makePtr<ImageDisplayScene>();
director->runScene(scene);
app->run();
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

View File

@ -0,0 +1,65 @@
#type vertex
#version 320 es
precision highp float;
layout(std140, binding = 0) uniform GlobalUBO {
mat4 uViewProjection;
vec4 uCameraPosition;
float uTime;
float uDeltaTime;
vec2 uScreenSize;
};
layout(std140, binding = 1) uniform MaterialUBO {
vec4 uColor;
vec4 uTintColor;
float uOpacity;
float uPadding[3];
};
uniform mat4 uModelMatrix;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec4 aColor;
out vec2 vTexCoord;
out vec4 vColor;
out vec4 vTintColor;
out float vOpacity;
void main() {
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord;
vColor = aColor * uColor;
vTintColor = uTintColor;
vOpacity = uOpacity;
}
#type fragment
#version 320 es
precision highp float;
in vec2 vTexCoord;
in vec4 vColor;
in vec4 vTintColor;
in float vOpacity;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
vec4 texColor = texture(uTexture, vTexCoord);
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
texColor = vec4(1.0, 1.0, 1.0, 1.0);
}
fragColor = texColor * vColor * vTintColor;
fragColor.a *= vOpacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -0,0 +1,79 @@
local example_dir = os.scriptdir()
target("image_display_demo")
set_kind("binary")
add_files("main.cpp")
add_includedirs("../../include")
add_deps("extra2d")
if is_plat("switch") then
set_targetdir("../../build/examples/image_display_demo")
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
local output_dir = path.directory(elf_file)
local nacp_file = path.join(output_dir, "image_display_demo.nacp")
local nro_file = path.join(output_dir, "image_display_demo.nro")
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
local romfs = path.join(example_dir, "romfs")
local assets_source = path.join(example_dir, "../scene_graph_demo/romfs/assets")
local assets_target = path.join(romfs, "assets")
local shader_source = path.join(example_dir, "../../shader")
local shader_target = path.join(romfs, "shader")
if not os.isdir(romfs) then
os.mkdir(romfs)
end
if not os.isdir(assets_target) then
os.mkdir(assets_target)
end
if os.isdir(assets_source) then
os.cp(path.join(assets_source, "**"), assets_target)
end
if os.isdir(shader_source) then
if not os.isdir(shader_target) then
os.mkdir(shader_target)
end
os.cp(path.join(shader_source, "*"), shader_target)
end
if os.isfile(nacptool) and os.isfile(elf2nro) then
os.vrunv(nacptool, {"--create", "Image Display Demo", "Extra2D Team", "1.0.0", nacp_file})
if os.isdir(romfs) then
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
end
end)
elseif is_plat("mingw") then
set_targetdir("../../build/examples/image_display_demo")
after_build(function (target)
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
local assets_source = path.join(example_dir, "../scene_graph_demo/romfs/assets")
local shader_source = path.join(example_dir, "../../shader")
local shader_target = path.join(target_dir, "shader")
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
if os.isdir(assets_source) then
os.cp(path.join(assets_source, "**"), assets_dir)
print("Copied assets from " .. assets_source .. " to " .. assets_dir)
end
if os.isdir(shader_source) then
if not os.isdir(shader_target) then
os.mkdir(shader_target)
end
os.cp(path.join(shader_source, "*"), shader_target)
print("Copied shaders from " .. shader_source .. " to " .. shader_target)
end
end)
end
target_end()

View File

@ -16,6 +16,7 @@ namespace extra2d {
class CommandQueue; class CommandQueue;
class Material; class Material;
class Mesh; class Mesh;
class Texture;
class UniformBufferManager; class UniformBufferManager;
template <typename T> class Ptr; template <typename T> class Ptr;
@ -295,6 +296,7 @@ public:
* @param color * @param color
*/ */
void submitDraw(Ptr<Material> material, Ptr<Mesh> mesh, void submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
Ptr<Texture> textureOverride,
const struct Transform &transform, const Color &color, const struct Transform &transform, const Color &color,
uint32_t sortKey = 0); uint32_t sortKey = 0);

View File

@ -39,6 +39,7 @@ struct RenderCommand {
struct DrawMeshData { struct DrawMeshData {
Handle<Mesh> mesh; // 网格句柄 Handle<Mesh> mesh; // 网格句柄
Handle<Material> material; // 材质句柄 Handle<Material> material; // 材质句柄
Handle<Texture> texture; // 纹理句柄(用于默认材质覆盖)
Vec2 pos; // 位置 Vec2 pos; // 位置
Vec2 scale; // 缩放 Vec2 scale; // 缩放
float rot; // 旋转角度 float rot; // 旋转角度
@ -69,6 +70,7 @@ struct RenderCommand {
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) { RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
drawMesh.mesh = Handle<Mesh>::invalid(); drawMesh.mesh = Handle<Mesh>::invalid();
drawMesh.material = Handle<Material>::invalid(); drawMesh.material = Handle<Material>::invalid();
drawMesh.texture = Handle<Texture>::invalid();
drawMesh.pos = Vec2(0.0f, 0.0f); drawMesh.pos = Vec2(0.0f, 0.0f);
drawMesh.scale = Vec2(1.0f, 1.0f); drawMesh.scale = Vec2(1.0f, 1.0f);
drawMesh.rot = 0.0f; drawMesh.rot = 0.0f;

View File

@ -360,6 +360,7 @@ UniformBuffer *CommandQueue::getCurrentMaterialUBO() const {
} }
void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh, void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
Ptr<Texture> textureOverride,
const struct Transform &transform, const struct Transform &transform,
const Color &color, uint32_t sortKey) { const Color &color, uint32_t sortKey) {
if (!material || !mesh || !material->getShader()) { if (!material || !mesh || !material->getShader()) {
@ -391,12 +392,18 @@ void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
// 设置纹理 // 设置纹理
const auto &textures = material->getTextures(); const auto &textures = material->getTextures();
cmd.textureCount = cmd.textureCount = 0;
static_cast<uint32_t>(std::min(textures.size(), size_t(8))); for (const auto &textureSlot : textures) {
for (uint32_t i = 0; i < cmd.textureCount; ++i) { if (!textureSlot.texture) {
if (textures[i].texture) { continue;
cmd.textures[i] = textures[i].texture->getHandle();
} }
uint32_t slot = std::min(textureSlot.slot, 7u);
cmd.textures[slot] = textureSlot.texture->getHandle();
cmd.textureCount = std::max(cmd.textureCount, slot + 1);
}
if (textureOverride) {
cmd.textures[0] = textureOverride->getHandle();
cmd.textureCount = std::max(cmd.textureCount, 1u);
} }
// 分配材质 UBO 空间(使用 CPU 缓冲区) // 分配材质 UBO 空间(使用 CPU 缓冲区)

View File

@ -199,11 +199,13 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
defaultMeshPtr_ = mesh; defaultMeshPtr_ = mesh;
} }
Texture *texture = assets->get(cmd.drawMesh.texture);
if (material && mesh) { if (material && mesh) {
Transform transform(cmd.drawMesh.pos, cmd.drawMesh.scale, Transform transform(cmd.drawMesh.pos, cmd.drawMesh.scale,
cmd.drawMesh.rot); cmd.drawMesh.rot);
commandQueue_->submitDraw(Ptr<Material>(material), Ptr<Mesh>(mesh), commandQueue_->submitDraw(Ptr<Material>(material), Ptr<Mesh>(mesh),
transform, cmd.drawMesh.color, cmd.sortKey); Ptr<Texture>(texture), transform,
cmd.drawMesh.color, cmd.sortKey);
stats_.commandsSubmitted++; stats_.commandsSubmitted++;
} else { } else {
E2D_WARN("提交绘制命令失败: 材质={}, 网格={}", material ? "有效" : "", E2D_WARN("提交绘制命令失败: 材质={}, 网格={}", material ? "有效" : "",

View File

@ -61,6 +61,10 @@ void GLPipeline::bind() {
// 绑定着色器程序 // 绑定着色器程序
if (shaderProgram_ != 0) { if (shaderProgram_ != 0) {
glUseProgram(shaderProgram_); glUseProgram(shaderProgram_);
GLint textureLocation = glGetUniformLocation(shaderProgram_, "uTexture");
if (textureLocation != -1) {
glUniform1i(textureLocation, 0);
}
} }
// 注意VAO 需要与具体的顶点缓冲区绑定才能工作 // 注意VAO 需要与具体的顶点缓冲区绑定才能工作

View File

@ -94,12 +94,7 @@ bool Texture::loadFromMemory(const uint8_t *data, int width, int height,
// 上传纹理数据 // 上传纹理数据
if (data) { if (data) {
// 注意update 方法的参数需要根据实际 RHI 接口调整 texture->update(data, 0);
// 这里假设 update 接受数据指针
texture->update(
data, static_cast<size_t>(width * height * getBytesPerPixel(format)));
// 生成 mipmap
// 注意generateMipmap 方法需要在 RHI 接口中实现
} }
// 获取纹理句柄 // 获取纹理句柄
@ -227,8 +222,7 @@ bool Texture::reloadFromMemory(const uint8_t *data, int width, int height,
// 上传纹理数据 // 上传纹理数据
if (data) { if (data) {
texture->update( texture->update(data, 0);
data, static_cast<size_t>(width * height * getBytesPerPixel(format)));
} }
// 替换旧的纹理句柄 // 替换旧的纹理句柄

View File

@ -36,6 +36,7 @@ void SpriteRenderer::render() {
// 如果没有指定网格,使用默认的四边形网格 // 如果没有指定网格,使用默认的四边形网格
cmd.drawMesh.mesh = Handle<Mesh>::invalid(); // RendererModule 会使用默认网格 cmd.drawMesh.mesh = Handle<Mesh>::invalid(); // RendererModule 会使用默认网格
cmd.drawMesh.material = material_.isValid() ? material_ : Handle<Material>::invalid(); cmd.drawMesh.material = material_.isValid() ? material_ : Handle<Material>::invalid();
cmd.drawMesh.texture = texture_.isValid() ? texture_ : Handle<Texture>::invalid();
cmd.setTransform(worldTransform); cmd.setTransform(worldTransform);
cmd.setColor(color_); cmd.setColor(color_);

View File

@ -95,6 +95,7 @@ define_extra2d_engine()
if is_config("examples","true") then if is_config("examples","true") then
includes("examples/hello_world", {rootdir = "examples/hello_world"}) includes("examples/hello_world", {rootdir = "examples/hello_world"})
includes("examples/scene_graph_demo", {rootdir = "examples/scene_graph_demo"}) includes("examples/scene_graph_demo", {rootdir = "examples/scene_graph_demo"})
includes("examples/image_display_demo", {rootdir = "examples/image_display_demo"})
end end