feat(shader): 支持单文件分段着色器并更新内置默认着色器

- 扩展 ShaderLoader 以支持 .glsl 单文件分段格式(#type vertex/fragment)
- 将内置默认着色器从分离的 .vert/.frag 文件合并为单个 default.glsl 文件
- 更新 BuiltinAssetFactory 使用新的加载方式创建默认着色器
- 删除旧的 default.frag 文件,简化着色器资源管理
This commit is contained in:
ChestnutYueyue 2026-03-16 19:18:53 +08:00
parent 3df1703839
commit 1097aeae6c
5 changed files with 111 additions and 68 deletions

View File

@ -17,7 +17,7 @@ public:
explicit ShaderLoader(AssetFileSystem fileSystem); explicit ShaderLoader(AssetFileSystem fileSystem);
/** /**
* @brief * @brief
* @param path .vert/.frag * @param path .glsl .vert/.frag
* @return nullptr * @return nullptr
*/ */
Ptr<Shader> load(const std::string& path) override; Ptr<Shader> load(const std::string& path) override;

View File

@ -1,40 +0,0 @@
#version 320 es
precision highp float;
// 从顶点着色器输入
in vec2 vTexCoord;
in vec4 vColor;
in vec4 vTintColor;
in float vOpacity;
// 纹理采样器
uniform sampler2D uTexture;
// 输出颜色
out vec4 fragColor;
/**
* @brief
*
*
*/
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;
// Alpha 测试:丢弃几乎透明的像素
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,7 +1,7 @@
#type vertex
#version 320 es #version 320 es
precision highp float; precision highp float;
// 全局 UBO (binding = 0) - 每帧更新一次
layout(std140, binding = 0) uniform GlobalUBO { layout(std140, binding = 0) uniform GlobalUBO {
mat4 uViewProjection; mat4 uViewProjection;
vec4 uCameraPosition; vec4 uCameraPosition;
@ -10,39 +10,56 @@ layout(std140, binding = 0) uniform GlobalUBO {
vec2 uScreenSize; vec2 uScreenSize;
}; };
// 材质 UBO (binding = 1) - 每物体更新
layout(std140, binding = 1) uniform MaterialUBO { layout(std140, binding = 1) uniform MaterialUBO {
vec4 uColor; vec4 uColor;
vec4 uTintColor; vec4 uTintColor;
float uOpacity; float uOpacity;
float uPadding[3]; // std140 对齐填充 float uPadding[3];
}; };
// 模型矩阵作为单独的统一变量(每个物体设置)
uniform mat4 uModelMatrix; uniform mat4 uModelMatrix;
// 顶点属性
layout(location = 0) in vec2 aPosition; layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord; layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec4 aColor; layout(location = 2) in vec4 aColor;
// 输出到片段着色器
out vec2 vTexCoord; out vec2 vTexCoord;
out vec4 vColor; out vec4 vColor;
out vec4 vTintColor; out vec4 vTintColor;
out float vOpacity; out float vOpacity;
/**
* @brief 顶点着色器入口
*
* 计算顶点在裁剪空间中的位置,
* 并传递纹理坐标和颜色到片段着色器
*/
void main() { void main() {
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0); gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord; vTexCoord = aTexCoord;
// 混合顶点颜色和材质 UBO 中的颜色
vColor = aColor * uColor; vColor = aColor * uColor;
vTintColor = uTintColor; vTintColor = uTintColor;
vOpacity = uOpacity; 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

@ -1,4 +1,5 @@
#include <assets/builtin/builtin_asset_factory.h> #include <assets/builtin/builtin_asset_factory.h>
#include <assets/loaders/shader_loader.h>
#include <utils/logger.h> #include <utils/logger.h>
namespace extra2d { namespace extra2d {
@ -22,18 +23,9 @@ bool BuiltinAssetFactory::create() {
} }
{ {
std::string vertPath = fileSystem_.assetPath("shader/default.vert"); ShaderLoader shaderLoader(fileSystem_);
std::string fragPath = fileSystem_.assetPath("shader/default.frag"); Ptr<Shader> shader = shaderLoader.load("shader/default.glsl");
if (!fileSystem_.exists(vertPath) || !fileSystem_.exists(fragPath)) { if (!shader) {
return false;
}
std::string vsSource = fileSystem_.readString(vertPath);
std::string fsSource = fileSystem_.readString(fragPath);
if (vsSource.empty() || fsSource.empty()) {
return false;
}
Ptr<Shader> shader = makePtr<Shader>();
if (!shader->loadFromSource(vsSource, fsSource)) {
return false; return false;
} }
defaultShader_ = shaders_.insert(shader); defaultShader_ = shaders_.insert(shader);
@ -92,4 +84,3 @@ Handle<Material> BuiltinAssetFactory::defaultMaterial() const {
Handle<Mesh> BuiltinAssetFactory::defaultQuad() const { return defaultQuad_; } Handle<Mesh> BuiltinAssetFactory::defaultQuad() const { return defaultQuad_; }
} // namespace extra2d } // namespace extra2d

View File

@ -1,16 +1,91 @@
#include <assets/loaders/shader_loader.h> #include <assets/loaders/shader_loader.h>
#include <renderer/shader.h> #include <renderer/shader.h>
#include <utils/logger.h> #include <utils/logger.h>
#include <sstream>
namespace extra2d { namespace extra2d {
namespace {
enum class ShaderStageType {
None,
Vertex,
Fragment
};
static bool parseCombinedShaderSource(const std::string& source,
std::string& vsSource,
std::string& fsSource) {
std::istringstream stream(source);
std::string line;
ShaderStageType stage = ShaderStageType::None;
while (std::getline(stream, line)) {
const size_t first = line.find_first_not_of(" \t");
std::string trimmed = first == std::string::npos ? "" : line.substr(first);
if (!trimmed.empty() && (trimmed.rfind("#type", 0) == 0 || trimmed.rfind("#shader", 0) == 0)) {
std::istringstream directive(trimmed);
std::string key;
std::string value;
directive >> key >> value;
if (value == "vertex" || value == "vert" || value == "vs") {
stage = ShaderStageType::Vertex;
} else if (value == "fragment" || value == "frag" || value == "fs") {
stage = ShaderStageType::Fragment;
} else {
stage = ShaderStageType::None;
}
continue;
}
if (stage == ShaderStageType::Vertex) {
vsSource += line + "\n";
} else if (stage == ShaderStageType::Fragment) {
fsSource += line + "\n";
}
}
return !vsSource.empty() && !fsSource.empty();
}
}
ShaderLoader::ShaderLoader(AssetFileSystem fileSystem) ShaderLoader::ShaderLoader(AssetFileSystem fileSystem)
: fileSystem_(std::move(fileSystem)) {} : fileSystem_(std::move(fileSystem)) {}
Ptr<Shader> ShaderLoader::load(const std::string &path) { Ptr<Shader> ShaderLoader::load(const std::string &path) {
std::string::size_type dotPos = path.rfind('.');
if (dotPos != std::string::npos) {
std::string ext = path.substr(dotPos);
if (ext == ".glsl") {
std::string source = fileSystem_.readString(path);
if (source.empty()) {
E2D_ERROR("ShaderLoader: 读取着色器失败: {}", path);
return Ptr<Shader>();
}
std::string vsSource;
std::string fsSource;
if (!parseCombinedShaderSource(source, vsSource, fsSource)) {
E2D_ERROR("ShaderLoader: 单文件着色器缺少分段标记(#type vertex/#type fragment): {}", path);
return Ptr<Shader>();
}
Ptr<Shader> shader = makePtr<Shader>();
if (!shader->loadFromSource(vsSource, fsSource)) {
E2D_ERROR("ShaderLoader: 编译单文件着色器失败 {}", path);
return Ptr<Shader>();
}
E2D_DEBUG("ShaderLoader: 已加载单文件着色器 {}", path);
return shader;
}
}
std::string basePath = path; std::string basePath = path;
std::string::size_type dotPos = basePath.rfind('.'); dotPos = basePath.rfind('.');
if (dotPos != std::string::npos) { if (dotPos != std::string::npos) {
basePath = basePath.substr(0, dotPos); basePath = basePath.substr(0, dotPos);
} }