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