EG/plugins/user/terrain_editor/materials/terrain_materials.py
2025-12-12 16:16:15 +08:00

417 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
地形材质和纹理系统
提供完整的地形材质和纹理管理功能,支持多层纹理混合、程序化材质生成等高级功能
"""
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