feat: 添加图像显示示例并支持纹理覆盖
- 新增 image_display_demo 示例项目,展示如何加载和显示纹理 - 在 SpriteRenderer 中添加纹理覆盖支持,允许组件指定独立纹理 - 修改渲染管线,在绑定着色器时设置默认纹理单元 - 更新纹理加载逻辑,简化纹理数据上传接口 - 扩展渲染命令结构,包含可选的纹理覆盖句柄 - 调整命令队列提交接口,支持纹理覆盖参数传递
This commit is contained in:
parent
8f03fa80fb
commit
dcb3162525
|
|
@ -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 |
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 缓冲区)
|
||||||
|
|
|
||||||
|
|
@ -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 ? "有效" : "空",
|
||||||
|
|
|
||||||
|
|
@ -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 需要与具体的顶点缓冲区绑定才能工作
|
||||||
|
|
|
||||||
|
|
@ -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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 替换旧的纹理句柄
|
// 替换旧的纹理句柄
|
||||||
|
|
|
||||||
|
|
@ -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_);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue