Horse3D引擎将材质抽象为JSON格式的.material文件,统一管理着色器、顶点属性和Uniform值,实现解耦。模块分为Percheron(资源定义)、Diligencier(构建解析)和Dragon(渲染层),MaterialBuilder缓存Shader程序避免重复编译,渲染线程与显示端均通过Material::bind/release使用材质
在最早的三角形示例中,顶点着色器和片段着色器直接嵌入在C++字符串里。虽然能够运行,但每次修改颜色或添加Uniform,都需要重新编译整个项目。多个物体共用Shader时还会产生重复编译问题。
为此,将“材质”抽象为引擎中的核心概念。一个材质文件同时描述三方面内容:使用哪些着色器阶段、顶点属性如何布局、默认Uniform值(颜色、标量、向量、矩阵、纹理)。美术或策划人员只需修改JSON和GLSL文件,无需接触C++代码,实现了真正的解耦。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
.material 文件格式材质文件统一存放在 Materials/ 目录下,采用JSON格式。以三角形材质为例:
{"Attributes": [{ "Name": "aPos", "Dimension": 3 },{ "Name": "aTexCoord", "Dimension": 2 }],"Uniforms": [{ "Name": "u_texture", "Type": "Texture2D", "Value": "Triangle.jpeg" }],"Shaders": [{ "ShaderEnum": "Vertex", "SourceFile": "triangle.vert" },{ "ShaderEnum": "Fragment", "SourceFile": "triangle.frag" }]}
对应的片段着色器也很简洁,仅包含一个纹理采样:
uniform sampler2D u_texture;void main(){fragColor = texture(u_texture, vTexCoord);}
材质相关代码被拆分为两个子项目,依赖关系清晰:
flowchart TBsubgraph Dragon["Dragon(渲染层)"]RT[RenderThread]OB[Object3D]BR[Brush]CA[Camera]IS[IScreen]endsubgraph Diligencier["Diligencier(工具层)"]MB[MaterialBuilder]TL[TextureLoader]FK[FileKit]endsubgraph Percheron["Percheron(资源层)"]MA[Material]IU[IUniform]TX[Texture2D]endRT --> OBOB --> MAOB --> BR & CAIS --> MAMB --> MA & FK & TLTL --> TXMA --> IUIU --> TX
Material、Uniform、Texture2D 等基础资源。.material 文件、编译Shader、加载纹理。这种分层设计使得即使未来需要替换构建工具链,Percheron 中的资源定义也无需修改。
classDiagramIAsset <|-- MaterialIAsset <|-- ITextureITexture <|-- Texture2DMaterial o-- IUniformMaterial o-- Texture2DIUniform <|-- ColorUniformIUniform <|-- FloatUniformIUniform <|-- IntUniformIUniform <|-- Vector2UniformIUniform <|-- Vector3UniformIUniform <|-- Vector4UniformIUniform <|-- Matrix4x4UniformIUniform <|-- Texture2DUniformTexture2DUniform --> Texture2DMaterialBuilder ..> Material : buildsTextureLoader ..> Texture2D : loadsFileKit ..> MaterialBuilder : reads JSON
Material 是运行时的核心容器:
QOpenGLShaderProgram 指针(由 MaterialBuilder 缓存并复用,材质本身不拥有所有权)。IUniform 集合和 Texture2D 列表。bind() 负责绑定程序并遍历所有Uniform上传;release() 解绑程序并释放占用的纹理单元。IUniform 仅暴露一个 useUniform(program) 接口,具体子类负责将值推送到GPU:
ColorUniform、FloatUniform、Vector*Uniform、Matrix4x4Uniform:直接调用 setUniformValue。Texture2DUniform:激活指定纹理单元,绑定 QOpenGLTexture,并将采样器Uniform设置为该单元索引。这样,Material::bind() 无需了解Uniform的具体类型,统一遍历调用接口即可,扩展性良好。
MaterialBuilder 是一个单例,核心职责有三项:
.material JSON;QOpenGLShaderProgram,避免重复编译;Uniforms 段为 Material 填充Uniform和纹理。相对路径以 .material 所在目录为基准进行解析,便于材质资源整体打包移动。
FileKit::readText 负责读取文本文件(JSON、GLSL)。TextureLoader 将 QImage 转换为 QOpenGLTexture,并执行垂直翻转(mirrored())以匹配OpenGL坐标原点。sequenceDiagramparticipant RT as RenderThreadparticipant MB as MaterialBuilderparticipant FK as FileKitparticipant TL as TextureLoaderparticipant MA as MaterialRT->>MB: initializeMaterial(material)MB->>FK: readText(.material)FK-->>MB: JSONMB->>MB: parseShaderSourcesMB->>FK: readText(.vert / .frag)FK-->>MB: source codeMB->>MB: compileAndLinkMB-->>MA: QOpenGLShaderProgramMB->>MB: parseUniformsMB->>TL: loadTexture2D(image)TL-->>MB: Texture2DMB-->>MA: Uniforms & Textures
flowchart LRA[Object3D::render] --> B[Material::bind]B --> C[program->bind]C --> D[遍历 Uniform
useUniform 上传]D --> E[设置 u_model]E --> F[Camera::apply
u_view / u_projection]F --> G[Brush::drawGeometry]G --> H[Material::release]
渲染线程将材质和几何体组装到 Object3D 后,每帧只需调用 Object3D::render(camera)。对于镜像地板等需要动态纹理的Pass,可以传入运行时纹理ID,由 Object3D 设置额外的 u_mirrorTexture 并绑定。
GUI线程的 IScreen 也使用材质系统,区别在于它仅负责将渲染线程产出的共享纹理绘制到一个全屏四边形上。
程序缓存 vs. 材质拥有MaterialBuilder 缓存 QOpenGLShaderProgram,多个 Material 共享同一份程序;Material 析构时只删除自己的Uniform和纹理,避免重复释放。
纹理单元分配
当前按Uniform出现顺序依次分配 0, 1, 2...,在demo中足够使用,但后续需要独立的纹理单元管理器。
JSON 解析容错
遇到不认识的Uniform类型会直接跳过;编译或链接失败时打印shader log。生产环境建议加入更严格的验证和默认回退纹理。
QImage 原点QImage 原点在左上角,OpenGL纹理原点在左下角,加载时必须使用 image.mirrored() 翻转,否则纹理将倒置。
.material JSON 配置加载。Material 统一管理 Shader、Uniform、纹理。MaterialBuilder 提供程序缓存,避免重复编译。Material::bind/release 使用材质。.material 和 .vert/.frag 文件变化,运行时重新编译。Attributes 段的描述真正用于驱动顶点属性布局。本系列记录 Horse3D 游戏引擎从零开始的研发过程。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述