上一篇:纹理系统 | 下一篇:几何体系统 | 返回目录
📚 快速导航
📋 目录
- 引言
- 学习目标
- 材质系统概念
- 材质数据结构
- 材质配置文件
- 材质系统架构
- 材质加载流程
- 配置文件解析
- 字符串工具扩展
- 渲染器集成
- 使用示例
- 常见问题
- 练习与挑战
- 下一步
📖 引言
在上一个教程中,我们实现了纹理系统,能够高效地管理纹理资源。但是在真实的3D渲染中,物体的外观不仅仅由纹理决定,还包括颜色、光泽度、金属度等多种属性。这些属性的集合称为材质(Material)。
目前我们的渲染代码直接使用纹理和颜色,这导致:
- 硬编码问题- 颜色和纹理在代码中写死
- 无法复用- 相同外观的物体需要重复设置
- 难以调整- 修改外观需要重新编译
- 缺乏组织- 大量属性散落在各处
材质系统解决了这些问题。它允许我们:
- 在配置文件中定义材质
- 复用材质到多个对象
- 运行时加载和切换材质
- 统一管理渲染属性
🎯 学习目标
| 目标 | 描述 |
|---|---|
| 🎯 理解材质概念 | 掌握材质在渲染管线中的作用 |
| 🎯 设计配置格式 | 创建可读的材质配置文件 |
| 🎯 实现材质系统 | 使用与纹理系统相同的模式 |
| 🎯 解析配置文件 | 从文本文件加载材质属性 |
| 🎯 集成到渲染器 | 将材质连接到着色器 |
材质系统概念
什么是材质?
材质定义了物体表面的视觉属性:
材质 = 颜色 + 纹理 + 光照属性 + ... 示例1:木头材质 - 漫反射颜色: (0.6, 0.4, 0.2) - 漫反射贴图: "wood_diffuse.png" - 法线贴图: "wood_normal.png" - 粗糙度: 0.8 - 金属度: 0.0 示例2:金属材质 - 漫反射颜色: (0.9, 0.9, 0.9) - 金属度: 1.0 - 粗糙度: 0.2材质 vs 纹理
纹理(Texture): ┌───────────┐ │ 图像数据 │ 单纯的像素信息 │ 512x512 │ └───────────┘ 材质(Material): ┌─────────────────────────┐ │ 漫反射颜色: vec4(1,1,1,1) │ │ 漫反射贴图: Texture* │ 属性的集合 │ 法线贴图: Texture* │ │ 金属度: 0.5 │ └─────────────────────────┘材质系统流程
材质数据结构
纹理用途枚举
首先定义纹理在材质中的用途:
engine/src/resources/resource_types.h
/** * @brief 纹理在材质中的用途 */typedefenumtexture_use{TEXTURE_USE_UNKNOWN=0x00,// 未知用途TEXTURE_USE_MAP_DIFFUSE=0x01// 漫反射贴图// 未来可扩展:// TEXTURE_USE_MAP_NORMAL = 0x02// TEXTURE_USE_MAP_SPECULAR = 0x04// TEXTURE_USE_MAP_ROUGHNESS = 0x08}texture_use;纹理映射结构
/** * @brief 纹理映射:将纹理与用途关联 */typedefstructtexture_map{texture*texture;// 纹理指针texture_use use;// 用途标志}texture_map;材质结构
#defineMATERIAL_NAME_MAX_LENGTH256/** * @brief 材质:定义物体表面的视觉属性 */typedefstructmaterial{u32 id;// 材质IDu32 generation;// 版本号(用于热重载)u32 internal_id;// 渲染器内部IDcharname[MATERIAL_NAME_MAX_LENGTH];// 材质名称vec4 diffuse_colour;// 漫反射颜色texture_map diffuse_map;// 漫反射贴图// TODO: 未来扩展// texture_map normal_map;// texture_map specular_map;// f32 shininess;// f32 metalness;}material;纹理结构更新
为纹理添加名称字段,方便材质系统引用:
#defineTEXTURE_NAME_MAX_LENGTH512typedefstructtexture{u32 id;u32 width;u32 height;u8 channel_count;b8 has_transparency;u32 generation;charname[TEXTURE_NAME_MAX_LENGTH];// 新增:纹理名称void*internal_data;}texture;数据结构关系
material "test_material" ├─ diffuse_colour: (1.0, 1.0, 1.0, 1.0) └─ diffuse_map ├─ use: TEXTURE_USE_MAP_DIFFUSE └─ texture → texture "paving" ├─ width: 1024 ├─ height: 1024 ├─ name: "paving" └─ internal_data → vulkan_texture_data📄 材质配置文件
材质配置使用.kmt(Kohi Material) 格式。
文件格式
# 这是注释 version=0.1 name=材质名称 diffuse_colour=R G B A diffuse_map_name=纹理名称示例配置文件
assets/materials/test_material.kmt
#material file version=0.1 name=test_material diffuse_colour=1.0 1.0 1.0 1.0 diffuse_map_name=paving配置字段说明
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
version | 字符串 | 配置文件版本 | 0.1 |
name | 字符串 | 材质唯一名称 | test_material |
diffuse_colour | vec4 | 漫反射颜色(RGBA) | 1.0 1.0 1.0 1.0 |
diffuse_map_name | 字符串 | 漫反射贴图名称 | paving |
配置文件示例
木头材质
# assets/materials/wood.kmt version=0.1 name=wood diffuse_colour=0.6 0.4 0.2 1.0 diffuse_map_name=wood_diffuse金属材质
# assets/materials/metal.kmt version=0.1 name=metal diffuse_colour=0.8 0.8 0.8 1.0 diffuse_map_name=metal_diffuse无纹理材质(纯色)
# assets/materials/red.kmt version=0.1 name=red diffuse_colour=1.0 0.0 0.0 1.0 # diffuse_map_name留空,使用默认纹理材质系统架构
材质系统的设计几乎完全复用了纹理系统的模式。
系统状态
engine/src/systems/material_system.c
/** * @brief 材质引用信息 */typedefstructmaterial_reference{u64 reference_count;// 引用计数u32 handle;// 材质数组中的索引b8 auto_release;// 是否自动释放}material_reference;/** * @brief 材质系统状态 */typedefstructmaterial_system_state{material_system_config config;// 配置material default_material;// 默认材质// 已注册材质数组material*registered_materials;// 哈希表用于快速查找hashtable registered_material_table;}material_system_state;staticmaterial_system_state*state_ptr=0;配置结构
engine/src/systems/material_system.h
#pragmaonce#include"defines.h"#include"resources/resource_types.h"#defineDEFAULT_MATERIAL_NAME"default"/** * @brief 材质系统配置 */typedefstructmaterial_system_config{u32 max_material_count;// 最大材质数量}material_system_config;/** * @brief 材质配置(从文件加载) */typedefstructmaterial_config{charname[MATERIAL_NAME_MAX_LENGTH];b8 auto_release;vec4 diffuse_colour;chardiffuse_map_name[TEXTURE_NAME_MAX_LENGTH];}material_config;// 系统函数b8material_system_initialize(u64*memory_requirement,void*state,material_system_config config);voidmaterial_system_shutdown(void*state);// 材质操作material*material_system_acquire(constchar*name);material*material_system_acquire_from_config(material_config config);voidmaterial_system_release(constchar*name);系统内存布局
Material System内存布局: ┌────────────────────────────────────────┐ │ material_system_state (结构体) │ ├────────────────────────────────────────┤ │ registered_materials[4096] (数组) │ │ - material 0 │ │ - material 1 │ │ - ... │ │ - material 4095 │ ├────────────────────────────────────────┤ │ hashtable memory (哈希表数据) │ │ - material_reference[4096] │ └────────────────────────────────────────┘材质加载流程
系统初始化
b8material_system_initialize(u64*memory_requirement,void*state,material_system_config config){if(config.max_material_count==0){KFATAL("material_system_initialize - config.max_material_count must be > 0.");returnfalse;}// 计算内存需求u64 struct_requirement=sizeof(material_system_state);u64 array_requirement=sizeof(material)*config.max_material_count;u64 hashtable_requirement=sizeof(material_reference)*config.max_material_count;*memory_requirement=struct_requirement+array_requirement+hashtable_requirement;if(!state){returntrue;// 第一次调用,返回内存需求}state_ptr=state;state_ptr->config=config;// 设置数组指针void*array_block=state+struct_requirement;state_ptr->registered_materials=array_block;// 设置哈希表内存void*hashtable_block=array_block+array_requirement;// 创建哈希表hashtable_create(sizeof(material_reference),config.max_material_count,hashtable_block,false,&state_ptr->registered_material_table);// 填充无效引用material_reference invalid_ref;invalid_ref.auto_release=false;invalid_ref.handle=INVALID_ID;invalid_ref.reference_count=0;hashtable_fill(&state_ptr->registered_material_table,&invalid_ref);// 初始化所有材质为无效u32 count=state_ptr->config.max_material_count;for(u32 i=0;i<count;++i){state_ptr->registered_materials[i].id=INVALID_ID;state_ptr->registered_materials[i].generation=INVALID_ID;state_ptr->registered_materials[i].internal_id=INVALID_ID;}// 创建默认材质if(!create_default_material(state_ptr)){KFATAL("Failed to create default material.");returnfalse;}returntrue;}材质获取(从文件名)
/** * @brief 从文件名获取材质 * @param name 材质名称(不含扩展名) * @return 材质指针,失败返回NULL */material*material_system_acquire(constchar*name){// 加载配置文件material_config config;char*format_str="assets/materials/%s.%s";charfull_file_path[512];// TODO: 尝试不同扩展名string_format(full_file_path,format_str,name,"kmt");if(!load_configuration_file(full_file_path,&config)){KERROR("Failed to load material file: '%s'.",full_file_path);return0;}// 从配置获取材质returnmaterial_system_acquire_from_config(config);}材质获取(从配置)
/** * @brief 从配置获取材质 * @param config 材质配置 * @return 材质指针,失败返回NULL */material*material_system_acquire_from_config(material_config config){// 特殊处理默认材质if(strings_equali(config.name,DEFAULT_MATERIAL_NAME)){return&state_ptr->default_material;}material_reference ref;if(state_ptr&&hashtable_get(&state_ptr->registered_material_table,config.name,&ref)){// 首次加载时设置auto_releaseif(ref.reference_count==0){ref.auto_release=config.auto_release;}ref.reference_count++;if(ref.handle==INVALID_ID){// 材质不存在,需要创建// 1. 查找空闲槽位u32 count=state_ptr->config.max_material_count;material*m=0;for(u32 i=0;i<count;++i){if(state_ptr->registered_materials[i].id==INVALID_ID){ref.handle=i;m=&state_ptr->registered_materials[i];break;}}// 2. 确保找到空闲槽位if(!m||ref.handle==INVALID_ID){KFATAL("material_system_acquire - No more material slots.");return0;}// 3. 加载材质if(!load_material(config,m)){KERROR("Failed to load material '%s'.",config.name);return0;}// 4. 设置generationif(m->generation==INVALID_ID){m->generation=0;}else{m->generation++;}// 5. 设置IDm->id=ref.handle;KTRACE("Material '%s' created, ref_count=%i.",config.name,ref.reference_count);}else{KTRACE("Material '%s' already exists, ref_count=%i.",config.name,ref.reference_count);}// 更新哈希表hashtable_set(&state_ptr->registered_material_table,config.name,&ref);return&state_ptr->registered_materials[ref.handle];}KERROR("material_system_acquire_from_config failed for '%s'.",config.name);return0;}材质释放
/** * @brief 释放材质 * @param name 材质名称 */voidmaterial_system_release(constchar*name){// 忽略默认材质if(strings_equali(name,DEFAULT_MATERIAL_NAME)){return;}material_reference ref;if(state_ptr&&hashtable_get(&state_ptr->registered_material_table,name,&ref)){if(ref.reference_count==0){KWARN("Tried to release non-existent material: '%s'",name);return;}ref.reference_count--;if(ref.reference_count==0&&ref.auto_release){material*m=&state_ptr->registered_materials[ref.handle];// 销毁材质destroy_material(m);// 重置引用ref.handle=INVALID_ID;ref.auto_release=false;KTRACE("Released material '%s'. Unloaded (ref_count=0, auto_release=true).",name);}else{KTRACE("Released material '%s', ref_count=%i (auto_release=%s).",name,ref.reference_count,ref.auto_release?"true":"false");}// 更新哈希表hashtable_set(&state_ptr->registered_material_table,name,&ref);}else{KERROR("material_system_release failed for '%s'.",name);}}材质加载与销毁
/** * @brief 加载材质数据 */b8load_material(material_config config,material*m){kzero_memory(m,sizeof(material));// 复制名称string_ncopy(m->name,config.name,MATERIAL_NAME_MAX_LENGTH);// 设置漫反射颜色m->diffuse_colour=config.diffuse_colour;// 加载漫反射贴图if(string_length(config.diffuse_map_name)>0){m->diffuse_map.use=TEXTURE_USE_MAP_DIFFUSE;m->diffuse_map.texture=texture_system_acquire(config.diffuse_map_name,true);if(!m->diffuse_map.texture){KWARN("Unable to load texture '%s' for material '%s', using default.",config.diffuse_map_name,m->name);m->diffuse_map.texture=texture_system_get_default_texture();}}else{m->diffuse_map.use=TEXTURE_USE_UNKNOWN;m->diffuse_map.texture=0;}// TODO: 其他贴图(法线、高光等)// 请求渲染器资源if(!renderer_create_material(m)){KERROR("Failed to acquire renderer resources for material '%s'.",m->name);returnfalse;}returntrue;}/** * @brief 销毁材质 */voiddestroy_material(material*m){KTRACE("Destroying material '%s'...",m->name);// 释放纹理引用if(m->diffuse_map.texture){texture_system_release(m->diffuse_map.texture->name);}// 释放渲染器资源renderer_destroy_material(m);// 清零并无效化kzero_memory(m,sizeof(material));m->id=INVALID_ID;m->generation=INVALID_ID;m->internal_id=INVALID_ID;}默认材质
b8create_default_material(material_system_state*state){kzero_memory(&state->default_material,sizeof(material));state->default_material.id=INVALID_ID;state->default_material.generation=INVALID_ID;string_ncopy(state->default_material.name,DEFAULT_MATERIAL_NAME,MATERIAL_NAME_MAX_LENGTH);// 白色材质,使用默认纹理state->default_material.diffuse_colour=vec4_one();state->default_material.diffuse_map.use=TEXTURE_USE_MAP_DIFFUSE;state->default_material.diffuse_map.texture=texture_system_get_default_texture();if(!renderer_create_material(&state->default_material)){KFATAL("Failed to create default material.");returnfalse;}returntrue;}配置文件解析
配置文件解析是材质系统的关键部分。
解析流程
实现代码
b8load_configuration_file(constchar*path,material_config*out_config){file_handle f;if(!filesystem_open(path,FILE_MODE_READ,false,&f)){KERROR("Unable to open material file: '%s'.",path);returnfalse;}// 逐行读取charline_buf[512]="";char*p=&line_buf[0];u64 line_length=0;u32 line_number=1;while(filesystem_read_line(&f,511,&p,&line_length)){// 去除空白char*trimmed=string_trim(line_buf);line_length=string_length(trimmed);// 跳过空行和注释if(line_length<1||trimmed[0]=='#'){line_number++;continue;}// 查找 '=' 符号i32 equal_index=string_index_of(trimmed,'=');if(equal_index==-1){KWARN("Formatting issue in file '%s' line %ui: '=' not found.",path,line_number);line_number++;continue;}// 提取变量名(最多64字符)charraw_var_name[64];kzero_memory(raw_var_name,sizeof(char)*64);string_mid(raw_var_name,trimmed,0,equal_index);char*trimmed_var_name=string_trim(raw_var_name);// 提取值(最多446字符)charraw_value[446];kzero_memory(raw_value,sizeof(char)*446);string_mid(raw_value,trimmed,equal_index+1,-1);// 读取剩余部分char*trimmed_value=string_trim(raw_value);// 解析变量if(strings_equali(trimmed_var_name,"version")){// TODO: 版本检查}elseif(strings_equali(trimmed_var_name,"name")){string_ncopy(out_config->name,trimmed_value,MATERIAL_NAME_MAX_LENGTH);}elseif(strings_equali(trimmed_var_name,"diffuse_map_name")){string_ncopy(out_config->diffuse_map_name,trimmed_value,TEXTURE_NAME_MAX_LENGTH);}elseif(strings_equali(trimmed_var_name,"diffuse_colour")){// 解析颜色if(!string_to_vec4(trimmed_value,&out_config->diffuse_colour)){KWARN("Error parsing diffuse_colour in '%s'. Using white.",path);out_config->diffuse_colour=vec4_one();}}// TODO: 更多字段// 清空行缓冲区kzero_memory(line_buf,sizeof(char)*512);line_number++;}filesystem_close(&f);returntrue;}解析示例
输入文件内容: name=test_material diffuse_colour=1.0 1.0 1.0 1.0 解析过程: 1. 读取 "name=test_material" - trimmed = "name=test_material" - equal_index = 4 - var_name = "name" - value = "test_material" - config.name = "test_material" 2. 读取 "diffuse_colour=1.0 1.0 1.0 1.0" - var_name = "diffuse_colour" - value = "1.0 1.0 1.0 1.0" - string_to_vec4("1.0 1.0 1.0 1.0") - config.diffuse_colour = vec4(1, 1, 1, 1)字符串工具扩展
材质系统需要一些新的字符串操作函数。
string_to_vec4
engine/src/core/kstring.h
/** * @brief 将字符串解析为vec4 * @param str 格式:"x y z w" * @param out_vector 输出向量 * @return 成功返回true */KAPI b8string_to_vec4(constchar*str,vec4*out_vector);engine/src/core/kstring.c
b8string_to_vec4(constchar*str,vec4*out_vector){if(!str||!out_vector){returnfalse;}kzero_memory(out_vector,sizeof(vec4));// 使用sscanf解析i32 result=sscanf(str,"%f %f %f %f",&out_vector->x,&out_vector->y,&out_vector->z,&out_vector->w);returnresult==4;}其他字符串函数
// 获取子字符串KAPIvoidstring_mid(char*dest,constchar*source,i32 start,i32 length);// 字符串去除空白KAPIchar*string_trim(char*str);// 查找字符位置KAPI i32string_index_of(constchar*str,charc);这些函数的实现细节请参考源代码。
🔗 渲染器集成
渲染器接口更新
engine/src/renderer/renderer_frontend.h
// 材质操作b8renderer_create_material(structmaterial*material);voidrenderer_destroy_material(structmaterial*material);engine/src/renderer/renderer_frontend.c
b8renderer_create_material(structmaterial*material){returnstate_ptr->backend.create_material(material);}voidrenderer_destroy_material(structmaterial*material){state_ptr->backend.destroy_material(material);}Vulkan后端实现
材质的internal_id用于索引Vulkan着色器的对象状态:
engine/src/renderer/vulkan/vulkan_backend.c
b8vulkan_renderer_create_material(material*material){// 为材质获取着色器资源u32 object_id=0;if(!vulkan_material_shader_acquire_resources(&context,&context.material_shader,&object_id)){KERROR("Failed to acquire shader resources for material '%s'.",material->name);returnfalse;}material->internal_id=object_id;returntrue;}voidvulkan_renderer_destroy_material(material*material){// 释放着色器资源if(material->internal_id!=INVALID_ID){vulkan_material_shader_release_resources(&context,&context.material_shader,material->internal_id);material->internal_id=INVALID_ID;}}使用材质渲染
更新渲染调用以使用材质:
// 旧代码:直接使用纹理geometry_render_data data={};data.object_id=0;data.textures[0]=some_texture;// 新代码:使用材质geometry_render_data data={};data.material=my_material;data.model=transform_matrix;材质着色器会从材质中提取颜色和纹理:
voidvulkan_material_shader_use(...){// 从材质获取颜色vec4 diffuse=material->diffuse_colour;// 从材质获取纹理texture*diffuse_tex=material->diffuse_map.texture;// 更新uniform和描述符...}使用示例
示例1:加载并使用材质
voidsetup_scene(){// 获取材质material*wood_mat=material_system_acquire("wood");material*metal_mat=material_system_acquire("metal");// 创建使用材质的对象create_cube(wood_mat);create_sphere(metal_mat);}voidrender_object(object*obj){geometry_render_data data={};data.material=obj->material;data.model=obj->transform;renderer_draw_geometry(data);}voidcleanup_scene(){// 释放材质material_system_release("wood");material_system_release("metal");}示例2:程序化创建材质
voidcreate_custom_material(){material_config config={};string_ncopy(config.name,"custom_red",MATERIAL_NAME_MAX_LENGTH);config.auto_release=true;config.diffuse_colour=vec4_create(1.0f,0.0f,0.0f,1.0f);// 不设置diffuse_map_name,使用默认纹理material*red_mat=material_system_acquire_from_config(config);// 使用材质...material_system_release("custom_red");}示例3:材质库
typedefstructmaterial_library{material*wood;material*metal;material*stone;material*grass;}material_library;voidload_material_library(material_library*lib){lib->wood=material_system_acquire("wood");lib->metal=material_system_acquire("metal");lib->stone=material_system_acquire("stone");lib->grass=material_system_acquire("grass");}voidunload_material_library(material_library*lib){material_system_release("wood");material_system_release("metal");material_system_release("stone");material_system_release("grass");}❓ 常见问题
❓ 材质和纹理有什么区别?纹理是原始的图像数据,就像一张照片。
材质是如何使用这些纹理和其他属性的集合:
纹理 = 数据 "wood_diffuse.png" (512x512图像) 材质 = 属性集合 - 颜色: (0.6, 0.4, 0.2) - 漫反射贴图: "wood_diffuse.png" - 法线贴图: "wood_normal.png" - 粗糙度: 0.8一个纹理可以被多个材质使用:
"metal_diffuse.png" 被使用于: - "shiny_metal" 材质 (粗糙度=0.1) - "rusty_metal" 材质 (粗糙度=0.7)❓ 为什么材质需要internal_id?internal_id是材质在渲染器中的资源ID:
material{id=5// 材质系统中的索引internal_id=12// Vulkan着色器中的object_id}当材质被创建时:
renderer_create_material(material)→vulkan_material_shader_acquire_resources(...,&object_id)→ material->internal_id=object_id渲染时使用internal_id:
render(material)→vulkan_shader_use(material->internal_id)→ 使用descriptor_sets[internal_id]这样材质系统和渲染器可以独立管理各自的资源。
❓ diffuse_map.use字段有什么用?use字段标记纹理的用途,为未来扩展预留:
material{diffuse_map.use=TEXTURE_USE_MAP_DIFFUSE normal_map.use=TEXTURE_USE_MAP_NORMAL specular_map.use=TEXTURE_USE_MAP_SPECULAR}着色器可以根据use字段决定如何采样:
if(map.use==TEXTURE_USE_MAP_DIFFUSE){// 漫反射采样color=texture(diffuse_sampler,uv);}elseif(map.use==TEXTURE_USE_MAP_NORMAL){// 法线采样normal=texture(normal_sampler,uv).rgb*2.0-1.0;}❓ 如何支持多个漫反射贴图?当前实现只支持一个漫反射贴图。要支持多个,需要:
- 修改材质结构
typedefstructmaterial{// ...texture_map diffuse_maps[4];// 数组u32 diffuse_map_count;}material;- 修改配置格式
diffuse_map_name_0=texture1 diffuse_map_name_1=texture2- 修改着色器
layout(set = 1, binding = 1) uniform sampler2D diffuse_samplers[4]; void main() { vec4 color = vec4(0); for (int i = 0; i < diffuse_map_count; ++i) { color += texture(diffuse_samplers[i], uv); } color /= diffuse_map_count; }❓ .kmt格式可以扩展吗?完全可以!添加新属性很简单:
- 在material结构中添加字段
typedefstructmaterial{// ...f32 metalness;f32 roughness;}material;- 在material_config中添加
typedefstructmaterial_config{// ...f32 metalness;f32 roughness;}material_config;- 在解析器中处理
elseif(strings_equali(var_name,"metalness")){config->metalness=string_to_f32(value);}- 更新配置文件
metalness=0.8 roughness=0.2📝 练习与挑战
练习1:添加法线贴图支持
扩展材质系统以支持法线贴图:
typedefstructmaterial{// ... 现有字段 ...texture_map normal_map;}material;配置文件:
normal_map_name=wood_normal查看提示- 在
material结构中添加texture_map normal_map - 在
material_config中添加char normal_map_name[...] - 在
load_configuration_file中解析normal_map_name - 在
load_material中获取法线纹理 - 更新着色器绑定法线贴图采样器
练习2:实现材质继承
允许材质继承另一个材质的属性:
# metal_rusty.kmt base=metal diffuse_colour=0.7 0.5 0.3 1.0查看提示- 在配置中添加
base字段 - 如果存在base,先加载基础材质
- 复制基础材质的所有属性到新材质
- 用配置文件中的属性覆盖
if(config.base_name[0]!='\0'){material*base=material_system_acquire(config.base_name);*m=*base;// 复制所有属性material_system_release(config.base_name);}// 然后应用配置覆盖m->diffuse_colour=config.diffuse_colour;练习3:材质编辑器
创建一个简单的材质编辑器,运行时修改材质:
voidmaterial_editor_update(material*mat,f32 delta_time){if(input_is_key_down(KEY_R))mat->diffuse_colour.r+=delta_time;if(input_is_key_down(KEY_G))mat->diffuse_colour.g+=delta_time;if(input_is_key_down(KEY_B))mat->diffuse_colour.b+=delta_time;// 保存到文件if(input_was_key_pressed(KEY_S)){save_material_to_file(mat,"modified.kmt");}}查看提示实现save_material_to_file:
voidsave_material_to_file(material*mat,constchar*path){file_handle f;filesystem_open(path,FILE_MODE_WRITE,false,&f);filesystem_write_line(&f,"version=0.1\n");charline[512];string_format(line,"name=%s\n",mat->name);filesystem_write_line(&f,line);string_format(line,"diffuse_colour=%f %f %f %f\n",mat->diffuse_colour.r,mat->diffuse_colour.g,mat->diffuse_colour.b,mat->diffuse_colour.a);filesystem_write_line(&f,line);filesystem_close(&f);}🔗 下一步
在本教程中,我们实现了完整的材质系统:
- ✅ 材质数据结构(颜色+纹理映射)
- ✅ .kmt配置文件格式
- ✅ 材质系统(引用计数+哈希表)
- ✅ 配置文件解析器
- ✅ 与纹理系统和渲染器集成
材质系统是渲染管线的核心组件。下一步,我们可以:
- 几何体系统- 管理网格和顶点数据
- 资源系统- 统一管理所有资源类型
- 场景系统- 组织场景中的对象
- 实体组件系统(ECS)- 更灵活的游戏对象架构
结语
恭喜你完成了本教程!材质系统让我们可以用数据驱动的方式定义物体外观。
通过复用纹理系统的设计模式,我们快速实现了一个功能完整的材质系统。从简单的配置文件到复杂的引用计数,每个部分都协同工作,为游戏提供了灵活的材质管理能力。
关键要点回顾:
- 材质是纹理和属性的集合
- .kmt格式提供了可读的配置方式
- 引用计数自动管理材质生命周期
- 材质系统与纹理系统紧密协作
- 统一的接口简化了材质使用
随着引擎的发展,材质系统可以扩展支持PBR材质、程序化材质、材质动画等高级特性。
如果你有任何问题或建议,欢迎在GitHub上提出issue。
关注公众号,获取更多引擎开发资讯:
觉得有帮助?请作者喝杯咖啡:
📧 作者: 上手实验室
🔗 项目地址: https://github.com/travisvroman/kohi
上一篇:纹理系统 | 下一篇:几何体系统