news 2026/3/10 4:31:08

教程 31 - 材质系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
教程 31 - 材质系统

上一篇:纹理系统 | 下一篇:几何体系统 | 返回目录


📚 快速导航


📋 目录
  • 引言
  • 学习目标
  • 材质系统概念
  • 材质数据结构
  • 材质配置文件
  • 材质系统架构
  • 材质加载流程
  • 配置文件解析
  • 字符串工具扩展
  • 渲染器集成
  • 使用示例
  • 常见问题
  • 练习与挑战
  • 下一步

📖 引言

在上一个教程中,我们实现了纹理系统,能够高效地管理纹理资源。但是在真实的3D渲染中,物体的外观不仅仅由纹理决定,还包括颜色、光泽度、金属度等多种属性。这些属性的集合称为材质(Material)

目前我们的渲染代码直接使用纹理和颜色,这导致:

  1. 硬编码问题- 颜色和纹理在代码中写死
  2. 无法复用- 相同外观的物体需要重复设置
  3. 难以调整- 修改外观需要重新编译
  4. 缺乏组织- 大量属性散落在各处

材质系统解决了这些问题。它允许我们:

  • 在配置文件中定义材质
  • 复用材质到多个对象
  • 运行时加载和切换材质
  • 统一管理渲染属性

🎯 学习目标

目标描述
🎯 理解材质概念掌握材质在渲染管线中的作用
🎯 设计配置格式创建可读的材质配置文件
🎯 实现材质系统使用与纹理系统相同的模式
🎯 解析配置文件从文本文件加载材质属性
🎯 集成到渲染器将材质连接到着色器

材质系统概念

什么是材质?

材质定义了物体表面的视觉属性:

材质 = 颜色 + 纹理 + 光照属性 + ... 示例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 │ └─────────────────────────┘

材质系统流程

acquire材质
查找哈希表
release材质
游戏代码
材质系统
已加载?
增加引用计数
读取.kmt文件
解析配置
获取纹理
创建材质
返回材质指针
渲染对象
减少引用计数

材质数据结构

纹理用途枚举

首先定义纹理在材质中的用途:

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_colourvec4漫反射颜色(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;}
❓ 如何支持多个漫反射贴图?

当前实现只支持一个漫反射贴图。要支持多个,需要:

  1. 修改材质结构
typedefstructmaterial{// ...texture_map diffuse_maps[4];// 数组u32 diffuse_map_count;}material;
  1. 修改配置格式
diffuse_map_name_0=texture1 diffuse_map_name_1=texture2
  1. 修改着色器
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格式可以扩展吗?

完全可以!添加新属性很简单:

  1. 在material结构中添加字段
typedefstructmaterial{// ...f32 metalness;f32 roughness;}material;
  1. 在material_config中添加
typedefstructmaterial_config{// ...f32 metalness;f32 roughness;}material_config;
  1. 在解析器中处理
elseif(strings_equali(var_name,"metalness")){config->metalness=string_to_f32(value);}
  1. 更新配置文件
metalness=0.8 roughness=0.2

📝 练习与挑战

练习1:添加法线贴图支持

扩展材质系统以支持法线贴图:

typedefstructmaterial{// ... 现有字段 ...texture_map normal_map;}material;

配置文件:

normal_map_name=wood_normal
查看提示
  1. material结构中添加texture_map normal_map
  2. material_config中添加char normal_map_name[...]
  3. load_configuration_file中解析normal_map_name
  4. load_material中获取法线纹理
  5. 更新着色器绑定法线贴图采样器

练习2:实现材质继承

允许材质继承另一个材质的属性:

# metal_rusty.kmt base=metal diffuse_colour=0.7 0.5 0.3 1.0
查看提示
  1. 在配置中添加base字段
  2. 如果存在base,先加载基础材质
  3. 复制基础材质的所有属性到新材质
  4. 用配置文件中的属性覆盖
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配置文件格式
  • ✅ 材质系统(引用计数+哈希表)
  • ✅ 配置文件解析器
  • ✅ 与纹理系统和渲染器集成

材质系统是渲染管线的核心组件。下一步,我们可以:

  1. 几何体系统- 管理网格和顶点数据
  2. 资源系统- 统一管理所有资源类型
  3. 场景系统- 组织场景中的对象
  4. 实体组件系统(ECS)- 更灵活的游戏对象架构

结语

恭喜你完成了本教程!材质系统让我们可以用数据驱动的方式定义物体外观。

通过复用纹理系统的设计模式,我们快速实现了一个功能完整的材质系统。从简单的配置文件到复杂的引用计数,每个部分都协同工作,为游戏提供了灵活的材质管理能力。

关键要点回顾

  • 材质是纹理和属性的集合
  • .kmt格式提供了可读的配置方式
  • 引用计数自动管理材质生命周期
  • 材质系统与纹理系统紧密协作
  • 统一的接口简化了材质使用

随着引擎的发展,材质系统可以扩展支持PBR材质、程序化材质、材质动画等高级特性。

如果你有任何问题或建议,欢迎在GitHub上提出issue。


关注公众号,获取更多引擎开发资讯:


觉得有帮助?请作者喝杯咖啡:


📧 作者: 上手实验室
🔗 项目地址: https://github.com/travisvroman/kohi


上一篇:纹理系统 | 下一篇:几何体系统

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/5 2:23:33

bugku ——各种绕过哟

启动靶场读取源码<?php highlight_file(flag.php); $_GET[id] urldecode($_GET[id]); $flag flag{xxxxxxxxxxxxxxxxxx}; if (isset($_GET[uname]) and isset($_POST[passwd])) {if ($_GET[uname] $_POST[passwd])print passwd can not be uname.;else if (sha1($_GET[un…

作者头像 李华
网站建设 2026/3/5 3:39:42

运行时端的执行流程-–-behaviac

原文 在运行时端&#xff08;下面以C版来加以说明&#xff0c;C#版基本类似&#xff09;&#xff0c;整个组建的更新可以通过Workspace::Update()函数来执行&#xff0c;该函数主要包括两大功能&#xff1a; 调用DebugUpdate()函数来更新一些连调和热加载相关的功能。根据m_b…

作者头像 李华
网站建设 2026/3/8 23:01:43

教程7:行为树的连调-–-behaviac

原文 本文档描述的是3.6及以后版本&#xff0c;对于3.5及以前的老版本请参考分类“3.5”。对于行为树的调试&#xff0c;behaviac提供了连调和离线调试两大功能。 连调功能是在游戏运行的时候&#xff0c;编辑器可以连上游戏&#xff0c;实时的查看树的运行情况、变量的当前值…

作者头像 李华