""" 地形材质和纹理系统 提供完整的地形材质和纹理管理功能,支持多层纹理混合、程序化材质生成等高级功能 """ import os from panda3d.core import Material, Texture, TextureStage, Vec4, PNMImage, Filename from panda3d.core import GeoMipTerrain, Shader, ShaderAttrib class TerrainMaterials: """ 地形材质和纹理系统类 提供完整的地形材质和纹理管理功能 """ def __init__(self, world): self.world = world self.materials = {} # 存储材质 self.terrain_layers = {} # 存储地形图层信息 self.texture_atlases = {} # 存储纹理图集 def create_material(self, name, ambient=(0.5, 0.5, 0.5, 1.0), diffuse=(0.8, 0.8, 0.8, 1.0), specular=(0.2, 0.2, 0.2, 1.0), shininess=5.0, emissive=(0.0, 0.0, 0.0, 1.0)): """ 创建新材质 """ material = Material() material.setName(name) material.setAmbient(ambient) material.setDiffuse(diffuse) material.setSpecular(specular) material.setShininess(shininess) material.setEmission(emissive) self.materials[name] = material print(f"创建材质: {name}") return material def apply_material_to_terrain(self, terrain_node, material_name): """ 为地形应用材质 """ if material_name in self.materials: material = self.materials[material_name] terrain_node.setMaterial(material) print(f"为地形应用材质: {material_name}") return True else: print(f"材质 {material_name} 不存在") return False def load_texture(self, texture_path): """ 加载纹理 """ try: # 检查文件是否存在 if not os.path.exists(texture_path): print(f"纹理文件不存在: {texture_path}") return None texture = self.world.loader.loadTexture(texture_path) if texture: print(f"纹理加载成功: {texture_path}") return texture else: print(f"纹理加载失败: {texture_path}") return None except Exception as e: print(f"加载纹理时出错: {e}") return None def apply_texture_to_terrain(self, terrain_node, texture_path, texture_stage_name="diffuse"): """ 为地形应用纹理 """ texture = self.load_texture(texture_path) if texture: # 创建纹理阶段 texture_stage = TextureStage(texture_stage_name) # 应用纹理到地形 terrain_node.setTexture(texture_stage, texture) print(f"为地形应用纹理: {texture_path}") return True return False def apply_multi_texture_to_terrain(self, terrain_node, texture_paths, blend_factors=None): """ 为地形应用多纹理混合 texture_paths: 纹理路径列表 blend_factors: 混合因子列表,控制每个纹理的混合权重 """ if not texture_paths: print("纹理路径列表为空") return False # 如果没有提供混合因子,使用默认值 if blend_factors is None: blend_factors = [1.0] * len(texture_paths) # 确保混合因子数量与纹理数量一致 if len(blend_factors) != len(texture_paths): print("混合因子数量与纹理数量不匹配") return False # 应用每个纹理 for i, (texture_path, blend_factor) in enumerate(zip(texture_paths, blend_factors)): texture = self.load_texture(texture_path) if texture: # 为每个纹理创建不同的纹理阶段 texture_stage = TextureStage(f"texture_{i}") texture_stage.setSort(i) # 设置排序 # 应用纹理 terrain_node.setTexture(texture_stage, texture) # 如果支持,设置混合因子 if hasattr(texture_stage, 'setBlendFactor'): texture_stage.setBlendFactor(blend_factor) print(f"为地形应用 {len(texture_paths)} 个多纹理混合") return True def create_terrain_layer_system(self, terrain_node, layer_configs): """ 创建地形图层系统 layer_configs: 图层配置列表,每个配置包含纹理路径、混合贴图等信息 """ try: # 清除现有的地形纹理 terrain_node.clearTexture() # 为每个图层创建纹理阶段 for i, layer_config in enumerate(layer_configs): # 加载漫反射纹理 if 'diffuse' in layer_config: diffuse_texture = self.load_texture(layer_config['diffuse']) if diffuse_texture: diffuse_stage = TextureStage(f"diffuse_{i}") diffuse_stage.setSort(i * 10) # 设置排序 terrain_node.setTexture(diffuse_stage, diffuse_texture) # 加载法线纹理 if 'normal' in layer_config: normal_texture = self.load_texture(layer_config['normal']) if normal_texture: normal_stage = TextureStage(f"normal_{i}") normal_stage.setMode(TextureStage.M_normal) # 设置为法线贴图模式 normal_stage.setSort(i * 10 + 1) terrain_node.setTexture(normal_stage, normal_texture) # 加载混合纹理(控制图层混合) if 'blend' in layer_config: blend_texture = self.load_texture(layer_config['blend']) if blend_texture: blend_stage = TextureStage(f"blend_{i}") blend_stage.setMode(TextureStage.M_blend) # 设置为混合模式 blend_stage.setSort(i * 10 + 2) terrain_node.setTexture(blend_stage, blend_texture) # 保存图层信息 self.terrain_layers[terrain_node] = layer_configs print(f"为地形创建 {len(layer_configs)} 个图层系统") return True except Exception as e: print(f"创建地形图层系统时出错: {e}") return False def update_terrain_layer(self, terrain_node, layer_index, layer_config): """ 更新地形图层 """ if terrain_node not in self.terrain_layers: print("地形没有图层系统") return False if layer_index >= len(self.terrain_layers[terrain_node]): print("图层索引超出范围") return False # 更新图层配置 self.terrain_layers[terrain_node][layer_index] = layer_config # 重新应用纹理 return self.create_terrain_layer_system(terrain_node, self.terrain_layers[terrain_node]) def create_procedural_terrain_material(self, terrain_node, base_color=(0.5, 0.6, 0.3, 1.0), roughness=0.8, metallic=0.1, uv_scale=(1.0, 1.0), use_normal_map=True): """ 创建程序化地形材质 """ try: # 创建基础材质 material = Material() material.setName(f"procedural_terrain_{id(terrain_node)}") material.setBaseColor(base_color) material.setRoughness(roughness) material.setMetallic(metallic) # 应用材质 terrain_node.setMaterial(material) # 如果使用RenderPipeline,可以应用PBR效果 if hasattr(self.world, 'render_pipeline'): try: effect_options = { "normal_mapping": use_normal_map, "parallax_mapping": False, "uv_scale": uv_scale } self.world.render_pipeline.set_effect( terrain_node, "effects/terrain.yaml", # 假设有专门的地形效果文件 effect_options, 10 # 排序值 ) print("应用PBR地形效果") except Exception as e: print(f"应用PBR效果失败: {e}") self.materials[material.getName()] = material print(f"创建程序化地形材质: {material.getName()}") return material except Exception as e: print(f"创建程序化地形材质时出错: {e}") return None def create_texture_atlas(self, atlas_name, texture_paths, atlas_size=(1024, 1024)): """ 创建纹理图集 """ try: # 创建新的PNMImage作为图集 atlas_image = PNMImage(atlas_size[0], atlas_size[1]) atlas_image.fill(0, 0, 0) # 填充黑色背景 # 计算每个纹理在图集中的位置 num_textures = len(texture_paths) grid_size = int(math.ceil(math.sqrt(num_textures))) tile_width = atlas_size[0] // grid_size tile_height = atlas_size[1] // grid_size # 存储纹理坐标信息 texture_coords = {} # 将每个纹理复制到图集中 for i, texture_path in enumerate(texture_paths): if not os.path.exists(texture_path): print(f"纹理文件不存在: {texture_path}") continue # 加载纹理图像 texture_image = PNMImage(Filename.fromOsSpecific(texture_path)) if texture_image.empty(): print(f"无法加载纹理图像: {texture_path}") continue # 计算在图集中的位置 row = i // grid_size col = i % grid_size x_offset = col * tile_width y_offset = row * tile_height # 将纹理复制到图集 atlas_image.blendSubImage(texture_image, x_offset, y_offset, 0, 0, min(tile_width, texture_image.getXSize()), min(tile_height, texture_image.getYSize())) # 计算纹理坐标 u0 = x_offset / atlas_size[0] v0 = y_offset / atlas_size[1] u1 = (x_offset + tile_width) / atlas_size[0] v1 = (y_offset + tile_height) / atlas_size[1] texture_coords[os.path.basename(texture_path)] = (u0, v0, u1, v1) # 保存图集到临时文件 temp_path = f"/tmp/{atlas_name}_atlas.png" atlas_image.write(Filename.fromOsSpecific(temp_path)) # 加载图集纹理 atlas_texture = self.world.loader.loadTexture(temp_path) if atlas_texture: self.texture_atlases[atlas_name] = { 'texture': atlas_texture, 'coords': texture_coords, 'size': atlas_size } print(f"创建纹理图集: {atlas_name}") return atlas_texture except Exception as e: print(f"创建纹理图集时出错: {e}") return None def apply_texture_atlas_to_terrain(self, terrain_node, atlas_name, texture_name): """ 为地形应用纹理图集中的特定纹理 """ if atlas_name not in self.texture_atlases: print(f"纹理图集 {atlas_name} 不存在") return False atlas_info = self.texture_atlases[atlas_name] if texture_name not in atlas_info['coords']: print(f"纹理 {texture_name} 不在图集中") return False # 应用图集纹理 texture_stage = TextureStage("atlas_diffuse") terrain_node.setTexture(texture_stage, atlas_info['texture']) # 设置纹理坐标偏移(需要着色器支持) coords = atlas_info['coords'][texture_name] print(f"应用纹理图集 {atlas_name} 中的 {texture_name}") return True def update_material_property(self, material_name, property_name, value): """ 更新材质属性 """ if material_name in self.materials: material = self.materials[material_name] setter_name = f'set{property_name.capitalize()}' if hasattr(material, setter_name): # 特殊处理某些属性 if property_name in ['ambient', 'diffuse', 'specular', 'emission'] and isinstance(value, (list, tuple)): getattr(material, setter_name)(Vec4(*value)) else: getattr(material, setter_name)(value) print(f"更新材质 {material_name} 的属性 {property_name} 为 {value}") return True print(f"无法更新材质 {material_name} 的属性 {property_name}") return False def get_material_info(self, material_name): """ 获取材质信息 """ if material_name in self.materials: material = self.materials[material_name] return { "name": material.getName(), "ambient": material.getAmbient(), "diffuse": material.getDiffuse(), "specular": material.getSpecular(), "shininess": material.getShininess(), "emissive": material.getEmission() } return None def create_splatting_material(self, terrain_node, splat_map_path, texture_paths): """ 创建地形分层混合材质(Splating) splat_map_path: 控制混合的灰度图路径 texture_paths: 纹理路径列表(通常4个:R、G、B、A通道分别控制) """ try: # 加载混合图 splat_texture = self.load_texture(splat_map_path) if not splat_texture: print("无法加载混合图") return False # 加载纹理图集 for i, texture_path in enumerate(texture_paths): texture = self.load_texture(texture_path) if texture: texture_stage = TextureStage(f"splat_texture_{i}") texture_stage.setSort(i) terrain_node.setTexture(texture_stage, texture) # 应用混合图 splat_stage = TextureStage("splat_map") splat_stage.setMode(TextureStage.M_blend) terrain_node.setTexture(splat_stage, splat_texture) print(f"创建分层混合材质,使用 {len(texture_paths)} 个纹理") return True except Exception as e: print(f"创建分层混合材质时出错: {e}") return False def apply_terrain_shader(self, terrain_node, shader_path, shader_params=None): """ 为地形应用自定义着色器 """ try: # 加载着色器 shader = Shader.load(Shader.SL_GLSL, shader_path + ".vert", shader_path + ".frag") # 应用着色器 terrain_node.setShader(shader) # 设置着色器参数 if shader_params: for param_name, param_value in shader_params.items(): terrain_node.setShaderInput(param_name, param_value) print(f"为地形应用着色器: {shader_path}") return True except Exception as e: print(f"应用着色器时出错: {e}") return False