1295 lines
47 KiB
Python
1295 lines
47 KiB
Python
"""
|
||
地形渲染器
|
||
负责渲染程序化生成的地形
|
||
"""
|
||
|
||
import numpy as np
|
||
import math
|
||
from typing import Dict, Any, List, Tuple
|
||
|
||
class TerrainRenderer:
|
||
"""
|
||
地形渲染器
|
||
负责渲染程序化生成的地形,包括网格生成、纹理映射、光照计算等
|
||
"""
|
||
|
||
def __init__(self, plugin):
|
||
"""
|
||
初始化地形渲染器
|
||
|
||
Args:
|
||
plugin: 程序化地形生成插件实例
|
||
"""
|
||
self.plugin = plugin
|
||
self.enabled = False
|
||
self.initialized = False
|
||
|
||
# 渲染配置
|
||
self.render_params = {
|
||
'resolution': plugin.config.get('terrain_size', 512),
|
||
'lod_levels': plugin.config.get('lod_levels', 5),
|
||
'chunk_size': plugin.config.get('chunk_size', 32),
|
||
'max_distance': 1000.0,
|
||
'lod_distance_factor': 2.0
|
||
}
|
||
|
||
# 光照参数
|
||
self.lighting_params = {
|
||
'ambient_light': 0.3,
|
||
'diffuse_light': 0.7,
|
||
'specular_light': 0.2,
|
||
'light_direction': [0.5, 0.5, 0.5],
|
||
'light_color': [1.0, 1.0, 1.0]
|
||
}
|
||
|
||
# 材质参数
|
||
self.material_params = {
|
||
'diffuse_texture': True,
|
||
'normal_mapping': True,
|
||
'specular_mapping': False,
|
||
'texture_scale': 1.0,
|
||
'tessellation_factor': 1.0
|
||
}
|
||
|
||
# 着色器参数
|
||
self.shader_params = {
|
||
'vertex_shader': 'terrain_vertex.glsl',
|
||
'fragment_shader': 'terrain_fragment.glsl',
|
||
'geometry_shader': None,
|
||
'tessellation_control_shader': None,
|
||
'tessellation_evaluation_shader': None
|
||
}
|
||
|
||
# 渲染状态
|
||
self.render_buffers = {}
|
||
self.vertex_arrays = {}
|
||
self.textures = {}
|
||
self.shaders = {}
|
||
|
||
# LOD管理
|
||
self.lod_chunks = {}
|
||
self.visible_chunks = set()
|
||
|
||
# 统计信息
|
||
self.stats = {
|
||
'chunks_rendered': 0,
|
||
'vertices_rendered': 0,
|
||
'triangles_rendered': 0,
|
||
'render_time': 0.0,
|
||
'average_render_time': 0.0,
|
||
'lod_switches': 0
|
||
}
|
||
|
||
print("✓ 地形渲染器已创建")
|
||
|
||
def initialize(self) -> bool:
|
||
"""
|
||
初始化地形渲染器
|
||
|
||
Returns:
|
||
是否初始化成功
|
||
"""
|
||
try:
|
||
# 初始化渲染资源(在实际实现中会初始化GPU资源)
|
||
self._initialize_render_resources()
|
||
|
||
self.initialized = True
|
||
print("✓ 地形渲染器初始化完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形渲染器初始化失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def enable(self) -> bool:
|
||
"""
|
||
启用地形渲染器
|
||
|
||
Returns:
|
||
是否启用成功
|
||
"""
|
||
try:
|
||
if not self.initialized:
|
||
print("✗ 地形渲染器未初始化")
|
||
return False
|
||
|
||
self.enabled = True
|
||
print("✓ 地形渲染器已启用")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形渲染器启用失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def disable(self):
|
||
"""禁用地形渲染器"""
|
||
try:
|
||
self.enabled = False
|
||
print("✓ 地形渲染器已禁用")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形渲染器禁用失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def finalize(self):
|
||
"""清理地形渲染器资源"""
|
||
try:
|
||
self.disable()
|
||
|
||
# 清理渲染资源
|
||
self._cleanup_render_resources()
|
||
|
||
self.initialized = False
|
||
print("✓ 地形渲染器资源已清理")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形渲染器资源清理失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def update(self, dt: float):
|
||
"""
|
||
更新地形渲染器状态
|
||
|
||
Args:
|
||
dt: 时间增量
|
||
"""
|
||
try:
|
||
if not self.enabled:
|
||
return
|
||
|
||
# 更新LOD
|
||
self._update_lod()
|
||
|
||
# 更新可见区块
|
||
self._update_visible_chunks()
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形渲染器更新失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _initialize_render_resources(self):
|
||
"""初始化渲染资源"""
|
||
try:
|
||
# 在实际实现中,这里会初始化GPU缓冲区、纹理、着色器等
|
||
print(" → 初始化渲染缓冲区...")
|
||
print(" → 初始化着色器...")
|
||
print(" → 初始化纹理...")
|
||
|
||
# 模拟创建一些基本的渲染资源
|
||
self.render_buffers['terrain_vertices'] = []
|
||
self.render_buffers['terrain_indices'] = []
|
||
self.vertex_arrays['terrain_vao'] = []
|
||
self.textures['terrain_diffuse'] = []
|
||
self.textures['terrain_normal'] = []
|
||
self.shaders['terrain_shader'] = []
|
||
|
||
except Exception as e:
|
||
print(f"✗ 渲染资源初始化失败: {e}")
|
||
|
||
def _cleanup_render_resources(self):
|
||
"""清理渲染资源"""
|
||
try:
|
||
# 在实际实现中,这里会清理GPU资源
|
||
self.render_buffers.clear()
|
||
self.vertex_arrays.clear()
|
||
self.textures.clear()
|
||
self.shaders.clear()
|
||
self.lod_chunks.clear()
|
||
self.visible_chunks.clear()
|
||
|
||
except Exception as e:
|
||
print(f"✗ 渲染资源清理失败: {e}")
|
||
|
||
def render_terrain(self, camera_position: List[float], view_matrix: np.ndarray,
|
||
projection_matrix: np.ndarray) -> bool:
|
||
"""
|
||
渲染地形
|
||
|
||
Args:
|
||
camera_position: 摄像机位置
|
||
view_matrix: 视图矩阵
|
||
projection_matrix: 投影矩阵
|
||
|
||
Returns:
|
||
是否渲染成功
|
||
"""
|
||
try:
|
||
if not self.enabled:
|
||
print("✗ 地形渲染器未启用")
|
||
return False
|
||
|
||
import time
|
||
render_start_time = time.time()
|
||
|
||
print("✓ 开始渲染地形...")
|
||
|
||
# 更新摄像机相关数据
|
||
self._update_camera_data(camera_position, view_matrix, projection_matrix)
|
||
|
||
# 准备渲染数据
|
||
self._prepare_render_data()
|
||
|
||
# 执行渲染
|
||
self._execute_rendering()
|
||
|
||
# 更新统计信息
|
||
render_time = time.time() - render_start_time
|
||
self.stats['render_time'] = render_time
|
||
self.stats['chunks_rendered'] += len(self.visible_chunks)
|
||
|
||
if self.stats['chunks_rendered'] > 0:
|
||
self.stats['average_render_time'] = (
|
||
self.stats['average_render_time'] * (self.stats['chunks_rendered'] - 1) + render_time
|
||
) / self.stats['chunks_rendered']
|
||
|
||
print(f"✓ 地形渲染完成,耗时: {render_time:.3f}秒")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形渲染失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _update_camera_data(self, camera_position: List[float], view_matrix: np.ndarray,
|
||
projection_matrix: np.ndarray):
|
||
"""更新摄像机相关数据"""
|
||
try:
|
||
# 在实际实现中,这里会更新着色器中的摄像机参数
|
||
pass
|
||
except Exception as e:
|
||
print(f"✗ 摄像机数据更新失败: {e}")
|
||
|
||
def _prepare_render_data(self):
|
||
"""准备渲染数据"""
|
||
try:
|
||
# 在实际实现中,这里会准备顶点缓冲区、索引缓冲区等
|
||
pass
|
||
except Exception as e:
|
||
print(f"✗ 渲染数据准备失败: {e}")
|
||
|
||
def _execute_rendering(self):
|
||
"""执行渲染"""
|
||
try:
|
||
# 在实际实现中,这里会调用GPU渲染命令
|
||
print(" → 绑定着色器程序...")
|
||
print(" → 设置光照参数...")
|
||
print(" → 绑定纹理...")
|
||
print(" → 渲染可见区块...")
|
||
|
||
# 模拟渲染过程
|
||
for chunk_key in self.visible_chunks:
|
||
print(f" → 渲染区块 {chunk_key}")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 渲染执行失败: {e}")
|
||
|
||
def _update_lod(self):
|
||
"""更新LOD级别"""
|
||
try:
|
||
# 在实际实现中,这里会根据摄像机距离更新每个区块的LOD级别
|
||
pass
|
||
except Exception as e:
|
||
print(f"✗ LOD更新失败: {e}")
|
||
|
||
def _update_visible_chunks(self):
|
||
"""更新可见区块"""
|
||
try:
|
||
# 在实际实现中,这里会根据视锥体剔除确定可见区块
|
||
if self.plugin.terrain_manager:
|
||
terrain_data = self.plugin.terrain_manager.get_terrain_data()
|
||
if terrain_data.get('is_generating', False):
|
||
# 如果地形正在生成,暂时不更新可见区块
|
||
return
|
||
|
||
# 获取可见区块列表
|
||
visible_chunk_keys = self.plugin.terrain_manager.get_visible_chunks()
|
||
self.visible_chunks = set(visible_chunk_keys)
|
||
|
||
except Exception as e:
|
||
print(f"✗ 可见区块更新失败: {e}")
|
||
|
||
def set_render_parameters(self, params: Dict[str, Any]):
|
||
"""
|
||
设置渲染参数
|
||
|
||
Args:
|
||
params: 渲染参数字典
|
||
"""
|
||
self.render_params.update(params)
|
||
print(f"✓ 渲染参数已更新: {self.render_params}")
|
||
|
||
def set_lighting_parameters(self, params: Dict[str, Any]):
|
||
"""
|
||
设置光照参数
|
||
|
||
Args:
|
||
params: 光照参数字典
|
||
"""
|
||
self.lighting_params.update(params)
|
||
print(f"✓ 光照参数已更新: {self.lighting_params}")
|
||
|
||
def set_material_parameters(self, params: Dict[str, Any]):
|
||
"""
|
||
设置材质参数
|
||
|
||
Args:
|
||
params: 材质参数字典
|
||
"""
|
||
self.material_params.update(params)
|
||
print(f"✓ 材质参数已更新: {self.material_params}")
|
||
|
||
def set_shader_parameters(self, params: Dict[str, Any]):
|
||
"""
|
||
设置着色器参数
|
||
|
||
Args:
|
||
params: 着色器参数字典
|
||
"""
|
||
self.shader_params.update(params)
|
||
print(f"✓ 着色器参数已更新: {self.shader_params}")
|
||
|
||
def get_stats(self) -> Dict[str, Any]:
|
||
"""
|
||
获取统计信息
|
||
|
||
Returns:
|
||
统计信息字典
|
||
"""
|
||
return self.stats.copy()
|
||
|
||
def reset_stats(self):
|
||
"""重置统计信息"""
|
||
self.stats = {
|
||
'chunks_rendered': 0,
|
||
'vertices_rendered': 0,
|
||
'triangles_rendered': 0,
|
||
'render_time': 0.0,
|
||
'average_render_time': 0.0,
|
||
'lod_switches': 0
|
||
}
|
||
print("✓ 地形渲染器统计信息已重置")
|
||
|
||
def generate_terrain_mesh(self, heightmap: np.ndarray, resolution: int = None) -> Dict[str, np.ndarray]:
|
||
"""
|
||
生成地形网格
|
||
|
||
Args:
|
||
heightmap: 高度图数据
|
||
resolution: 网格分辨率
|
||
|
||
Returns:
|
||
包含顶点、法线、纹理坐标和索引的字典
|
||
"""
|
||
try:
|
||
if resolution is None:
|
||
resolution = self.render_params['resolution']
|
||
|
||
print(f"✓ 开始生成 {resolution}x{resolution} 地形网格...")
|
||
|
||
# 创建网格数据
|
||
vertices = []
|
||
normals = []
|
||
texcoords = []
|
||
indices = []
|
||
|
||
# 生成顶点数据
|
||
height, width = heightmap.shape
|
||
scale_x = 1.0 / (width - 1)
|
||
scale_y = 1.0 / (height - 1)
|
||
|
||
# 生成顶点
|
||
for y in range(height):
|
||
for x in range(width):
|
||
# 顶点位置
|
||
vertex_x = (x / (width - 1)) * 2.0 - 1.0 # -1 到 1
|
||
vertex_y = heightmap[y, x] # 高度值
|
||
vertex_z = (y / (height - 1)) * 2.0 - 1.0 # -1 到 1
|
||
vertices.extend([vertex_x, vertex_y, vertex_z])
|
||
|
||
# 纹理坐标
|
||
texcoord_u = x / (width - 1)
|
||
texcoord_v = 1.0 - y / (height - 1) # 翻转V坐标
|
||
texcoords.extend([texcoord_u, texcoord_v])
|
||
|
||
# 计算法线
|
||
normals = self._calculate_normals(heightmap)
|
||
|
||
# 生成索引(三角形列表)
|
||
for y in range(height - 1):
|
||
for x in range(width - 1):
|
||
# 第一个三角形
|
||
indices.append(y * width + x)
|
||
indices.append(y * width + x + 1)
|
||
indices.append((y + 1) * width + x)
|
||
|
||
# 第二个三角形
|
||
indices.append((y + 1) * width + x)
|
||
indices.append(y * width + x + 1)
|
||
indices.append((y + 1) * width + x + 1)
|
||
|
||
# 转换为numpy数组
|
||
vertices_array = np.array(vertices, dtype=np.float32)
|
||
normals_array = np.array(normals, dtype=np.float32)
|
||
texcoords_array = np.array(texcoords, dtype=np.float32)
|
||
indices_array = np.array(indices, dtype=np.uint32)
|
||
|
||
# 更新统计信息
|
||
self.stats['vertices_rendered'] = len(vertices) // 3
|
||
self.stats['triangles_rendered'] = len(indices) // 3
|
||
|
||
print(f"✓ 地形网格生成完成: {len(vertices)//3} 顶点, {len(indices)//3} 三角形")
|
||
return {
|
||
'vertices': vertices_array,
|
||
'normals': normals_array,
|
||
'texcoords': texcoords_array,
|
||
'indices': indices_array
|
||
}
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形网格生成失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return {
|
||
'vertices': np.array([], dtype=np.float32),
|
||
'normals': np.array([], dtype=np.float32),
|
||
'texcoords': np.array([], dtype=np.float32),
|
||
'indices': np.array([], dtype=np.uint32)
|
||
}
|
||
|
||
def _calculate_normals(self, heightmap: np.ndarray) -> List[float]:
|
||
"""
|
||
计算法线
|
||
|
||
Args:
|
||
heightmap: 高度图数据
|
||
|
||
Returns:
|
||
法线数据列表
|
||
"""
|
||
try:
|
||
height, width = heightmap.shape
|
||
normals = []
|
||
|
||
for y in range(height):
|
||
for x in range(width):
|
||
# 计算相邻点的高度
|
||
h_left = heightmap[y, max(0, x-1)]
|
||
h_right = heightmap[y, min(width-1, x+1)]
|
||
h_up = heightmap[max(0, y-1), x]
|
||
h_down = heightmap[min(height-1, y+1), x]
|
||
|
||
# 计算梯度
|
||
dx = (h_right - h_left) * width / 2.0
|
||
dy = (h_down - h_up) * height / 2.0
|
||
dz = 2.0
|
||
|
||
# 计算法线
|
||
normal = [-dx, -dy, dz]
|
||
norm = math.sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2])
|
||
if norm > 0:
|
||
normal = [n/norm for n in normal]
|
||
else:
|
||
normal = [0.0, 0.0, 1.0]
|
||
|
||
normals.extend(normal)
|
||
|
||
return normals
|
||
|
||
except Exception as e:
|
||
print(f"✗ 法线计算失败: {e}")
|
||
# 返回默认法线
|
||
height, width = heightmap.shape
|
||
return [0.0, 0.0, 1.0] * height * width
|
||
|
||
def generate_terrain_texture(self, heightmap: np.ndarray, biome_map: np.ndarray = None,
|
||
moisture_map: np.ndarray = None, temperature_map: np.ndarray = None) -> np.ndarray:
|
||
"""
|
||
生成地形纹理
|
||
|
||
Args:
|
||
heightmap: 高度图数据
|
||
biome_map: 生物群落图数据(可选)
|
||
moisture_map: 湿度图数据(可选)
|
||
temperature_map: 温度图数据(可选)
|
||
|
||
Returns:
|
||
纹理数据 (RGBA格式)
|
||
"""
|
||
try:
|
||
height, width = heightmap.shape
|
||
print(f"✓ 开始生成 {width}x{height} 地形纹理...")
|
||
|
||
# 创建RGBA纹理数据
|
||
texture_data = np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
# 如果有生物群落数据,基于生物群落生成纹理
|
||
if biome_map is not None and biome_map.shape == heightmap.shape:
|
||
texture_data = self._generate_biome_based_texture(biome_map)
|
||
else:
|
||
# 基于高度生成基础纹理
|
||
texture_data = self._generate_height_based_texture(heightmap)
|
||
|
||
# 如果有湿度和温度数据,添加细节
|
||
if moisture_map is not None and moisture_map.shape == heightmap.shape:
|
||
texture_data = self._apply_moisture_effects(texture_data, moisture_map)
|
||
|
||
if temperature_map is not None and temperature_map.shape == heightmap.shape:
|
||
texture_data = self._apply_temperature_effects(texture_data, temperature_map)
|
||
|
||
print("✓ 地形纹理生成完成")
|
||
return texture_data
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形纹理生成失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
# 返回默认纹理(蓝色)
|
||
height, width = heightmap.shape
|
||
return np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
def _generate_biome_based_texture(self, biome_map: np.ndarray) -> np.ndarray:
|
||
"""
|
||
基于生物群落生成纹理
|
||
|
||
Args:
|
||
biome_map: 生物群落图数据
|
||
|
||
Returns:
|
||
纹理数据
|
||
"""
|
||
try:
|
||
height, width = biome_map.shape
|
||
texture_data = np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
# 生物群落颜色映射
|
||
biome_colors = {
|
||
0: [0, 0, 128, 255], # ocean - 深蓝色
|
||
1: [255, 255, 0, 255], # beach - 黄色
|
||
2: [144, 238, 144, 255], # plains - 浅绿色
|
||
3: [34, 139, 34, 255], # forest - 绿色
|
||
4: [0, 100, 0, 255], # jungle - 深绿色
|
||
5: [255, 165, 0, 255], # desert - 橙色
|
||
6: [139, 137, 137, 255], # mountain - 灰色
|
||
7: [255, 255, 255, 255], # snow - 白色
|
||
8: [0, 128, 128, 255], # taiga - 青色
|
||
9: [173, 216, 230, 255], # tundra - 淡蓝色
|
||
10: [128, 128, 0, 255], # swamp - 橄榄色
|
||
11: [255, 215, 0, 255] # savanna - 金色
|
||
}
|
||
|
||
# 为每个像素分配颜色
|
||
for y in range(height):
|
||
for x in range(width):
|
||
biome_id = biome_map[y, x]
|
||
color = biome_colors.get(biome_id, [128, 128, 128, 255]) # 默认灰色
|
||
texture_data[y, x] = color
|
||
|
||
return texture_data
|
||
|
||
except Exception as e:
|
||
print(f"✗ 生物群落纹理生成失败: {e}")
|
||
height, width = biome_map.shape
|
||
return np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
def _generate_height_based_texture(self, heightmap: np.ndarray) -> np.ndarray:
|
||
"""
|
||
基于高度生成纹理
|
||
|
||
Args:
|
||
heightmap: 高度图数据
|
||
|
||
Returns:
|
||
纹理数据
|
||
"""
|
||
try:
|
||
height, width = heightmap.shape
|
||
texture_data = np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
# 为每个像素根据高度分配颜色
|
||
for y in range(height):
|
||
for x in range(width):
|
||
height_value = heightmap[y, x]
|
||
|
||
# 根据高度选择颜色
|
||
if height_value < 0.2:
|
||
# 水域 - 蓝色
|
||
r, g, b = 0, 0, int(128 + height_value * 127)
|
||
elif height_value < 0.3:
|
||
# 浅水/海滩 - 浅蓝色
|
||
r, g, b = int(height_value * 255), int(height_value * 255), 255
|
||
elif height_value < 0.5:
|
||
# 平原 - 绿色
|
||
r, g, b = 0, int(100 + height_value * 155), 0
|
||
elif height_value < 0.7:
|
||
# 丘陵 - 棕色
|
||
r, g, b = int(139 * height_value), int(69 * height_value), int(19 * height_value)
|
||
elif height_value < 0.9:
|
||
# 山地 - 灰色
|
||
r, g, b = int(100 + height_value * 155), int(100 + height_value * 155), int(100 + height_value * 155)
|
||
else:
|
||
# 雪山 - 白色
|
||
r, g, b = 255, 255, 255
|
||
|
||
texture_data[y, x] = [r, g, b, 255]
|
||
|
||
return texture_data
|
||
|
||
except Exception as e:
|
||
print(f"✗ 高度纹理生成失败: {e}")
|
||
height, width = heightmap.shape
|
||
return np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
def _apply_moisture_effects(self, texture_data: np.ndarray, moisture_map: np.ndarray) -> np.ndarray:
|
||
"""
|
||
应用湿度效果到纹理
|
||
|
||
Args:
|
||
texture_data: 原始纹理数据
|
||
moisture_map: 湿度图数据
|
||
|
||
Returns:
|
||
处理后的纹理数据
|
||
"""
|
||
try:
|
||
processed_texture = texture_data.copy()
|
||
height, width = moisture_map.shape
|
||
|
||
# 根据湿度调整颜色
|
||
for y in range(height):
|
||
for x in range(width):
|
||
moisture_value = moisture_map[y, x]
|
||
|
||
# 增加蓝色调表示湿润
|
||
if moisture_value > 0.6:
|
||
processed_texture[y, x, 2] = min(255, processed_texture[y, x, 2] + int(50 * moisture_value))
|
||
|
||
# 减少红色和绿色通道表示湿润
|
||
if moisture_value > 0.5:
|
||
processed_texture[y, x, 0] = max(0, processed_texture[y, x, 0] - int(30 * moisture_value))
|
||
processed_texture[y, x, 1] = max(0, processed_texture[y, x, 1] - int(30 * moisture_value))
|
||
|
||
return processed_texture
|
||
|
||
except Exception as e:
|
||
print(f"✗ 湿度效果应用失败: {e}")
|
||
return texture_data
|
||
|
||
def _apply_temperature_effects(self, texture_data: np.ndarray, temperature_map: np.ndarray) -> np.ndarray:
|
||
"""
|
||
应用温度效果到纹理
|
||
|
||
Args:
|
||
texture_data: 原始纹理数据
|
||
temperature_map: 温度图数据
|
||
|
||
Returns:
|
||
处理后的纹理数据
|
||
"""
|
||
try:
|
||
processed_texture = texture_data.copy()
|
||
height, width = temperature_map.shape
|
||
|
||
# 根据温度调整颜色
|
||
for y in range(height):
|
||
for x in range(width):
|
||
temperature_value = temperature_map[y, x]
|
||
|
||
# 增加热色调表示温暖
|
||
if temperature_value > 0.7:
|
||
processed_texture[y, x, 0] = min(255, processed_texture[y, x, 0] + int(50 * (temperature_value - 0.7) / 0.3))
|
||
|
||
# 增加冷色调表示寒冷
|
||
if temperature_value < 0.3:
|
||
processed_texture[y, x, 2] = min(255, processed_texture[y, x, 2] + int(50 * (0.3 - temperature_value) / 0.3))
|
||
|
||
return processed_texture
|
||
|
||
except Exception as e:
|
||
print(f"✗ 温度效果应用失败: {e}")
|
||
return texture_data
|
||
|
||
def generate_normal_map(self, heightmap: np.ndarray, strength: float = 1.0) -> np.ndarray:
|
||
"""
|
||
生成法线贴图
|
||
|
||
Args:
|
||
heightmap: 高度图数据
|
||
strength: 法线强度
|
||
|
||
Returns:
|
||
法线贴图数据 (RGB格式)
|
||
"""
|
||
try:
|
||
height, width = heightmap.shape
|
||
print(f"✓ 开始生成 {width}x{height} 法线贴图...")
|
||
|
||
# 创建法线贴图数据
|
||
normal_map = np.zeros((height, width, 3), dtype=np.uint8)
|
||
|
||
# 计算每个像素的法线
|
||
for y in range(height):
|
||
for x in range(width):
|
||
# 计算相邻点的高度
|
||
h_left = heightmap[y, max(0, x-1)]
|
||
h_right = heightmap[y, min(width-1, x+1)]
|
||
h_up = heightmap[max(0, y-1), x]
|
||
h_down = heightmap[min(height-1, y+1), x]
|
||
|
||
# 计算梯度
|
||
dx = (h_right - h_left) * strength
|
||
dy = (h_down - h_up) * strength
|
||
|
||
# 计算法线 (转换到0-1范围再转为0-255)
|
||
normal_x = (dx + 1.0) * 0.5
|
||
normal_y = (dy + 1.0) * 0.5
|
||
normal_z = 1.0 # 假设主要朝向摄像机
|
||
normal_z = normal_z / math.sqrt(normal_x*normal_x + normal_y*normal_y + normal_z*normal_z)
|
||
normal_z = (normal_z + 1.0) * 0.5
|
||
|
||
# 转换为8位值
|
||
normal_map[y, x] = [
|
||
int(normal_x * 255),
|
||
int(normal_y * 255),
|
||
int(normal_z * 255)
|
||
]
|
||
|
||
print("✓ 法线贴图生成完成")
|
||
return normal_map
|
||
|
||
except Exception as e:
|
||
print(f"✗ 法线贴图生成失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
# 返回默认法线贴图(正面法线)
|
||
height, width = heightmap.shape
|
||
return np.full((height, width, 3), [128, 128, 255], dtype=np.uint8)
|
||
|
||
def generate_splat_map(self, biome_map: np.ndarray, num_textures: int = 4) -> np.ndarray:
|
||
"""
|
||
生成混合贴图(用于多重纹理混合)
|
||
|
||
Args:
|
||
biome_map: 生物群落图数据
|
||
num_textures: 纹理数量
|
||
|
||
Returns:
|
||
混合贴图数据 (RGBA格式,每个通道代表一种纹理的权重)
|
||
"""
|
||
try:
|
||
height, width = biome_map.shape
|
||
print(f"✓ 开始生成 {width}x{height} 混合贴图...")
|
||
|
||
# 创建混合贴图数据
|
||
splat_map = np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
# 简化的混合贴图生成
|
||
# 在实际实现中,这里会根据生物群落类型和地形特征生成更复杂的混合
|
||
for y in range(height):
|
||
for x in range(width):
|
||
biome_id = biome_map[y, x]
|
||
|
||
# 根据生物群落ID分配到不同的纹理通道
|
||
channel = biome_id % 4
|
||
splat_map[y, x, channel] = 255
|
||
|
||
print("✓ 混合贴图生成完成")
|
||
return splat_map
|
||
|
||
except Exception as e:
|
||
print(f"✗ 混合贴图生成失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
# 返回默认混合贴图
|
||
height, width = biome_map.shape
|
||
return np.zeros((height, width, 4), dtype=np.uint8)
|
||
|
||
def update_chunk_mesh(self, chunk_x: int, chunk_y: int, lod_level: int,
|
||
heightmap: np.ndarray) -> bool:
|
||
"""
|
||
更新区块网格
|
||
|
||
Args:
|
||
chunk_x: 区块X坐标
|
||
chunk_y: 区块Y坐标
|
||
lod_level: LOD级别
|
||
heightmap: 高度图数据
|
||
|
||
Returns:
|
||
是否更新成功
|
||
"""
|
||
try:
|
||
chunk_key = f"{chunk_x},{chunk_y},{lod_level}"
|
||
|
||
# 生成区块网格数据
|
||
mesh_data = self.generate_terrain_mesh(heightmap)
|
||
|
||
# 存储到LOD区块中
|
||
if chunk_key not in self.lod_chunks:
|
||
self.lod_chunks[chunk_key] = {}
|
||
|
||
self.lod_chunks[chunk_key]['mesh_data'] = mesh_data
|
||
self.lod_chunks[chunk_key]['last_update'] = time.time()
|
||
|
||
print(f"✓ 区块 [{chunk_x}, {chunk_y}] LOD {lod_level} 网格已更新")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 区块网格更新失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def get_chunk_mesh(self, chunk_x: int, chunk_y: int, lod_level: int) -> Dict[str, np.ndarray]:
|
||
"""
|
||
获取区块网格数据
|
||
|
||
Args:
|
||
chunk_x: 区块X坐标
|
||
chunk_y: 区块Y坐标
|
||
lod_level: LOD级别
|
||
|
||
Returns:
|
||
网格数据字典
|
||
"""
|
||
try:
|
||
chunk_key = f"{chunk_x},{chunk_y},{lod_level}"
|
||
if chunk_key in self.lod_chunks:
|
||
return self.lod_chunks[chunk_key].get('mesh_data', {})
|
||
else:
|
||
return {}
|
||
|
||
except Exception as e:
|
||
print(f"✗ 区块网格获取失败: {e}")
|
||
return {}
|
||
|
||
def set_lod_distances(self, distances: List[float]):
|
||
"""
|
||
设置LOD距离
|
||
|
||
Args:
|
||
distances: LOD距离列表
|
||
"""
|
||
self.render_params['lod_distances'] = distances
|
||
print(f"✓ LOD距离已设置: {distances}")
|
||
|
||
def get_lod_level(self, distance: float) -> int:
|
||
"""
|
||
根据距离确定LOD级别
|
||
|
||
Args:
|
||
distance: 距离
|
||
|
||
Returns:
|
||
LOD级别
|
||
"""
|
||
try:
|
||
lod_distances = self.render_params.get('lod_distances', [])
|
||
if not lod_distances:
|
||
# 使用默认距离计算
|
||
base_distance = 100.0
|
||
for i in range(self.render_params['lod_levels']):
|
||
if distance < base_distance * (i + 1):
|
||
return i
|
||
return self.render_params['lod_levels'] - 1
|
||
else:
|
||
# 使用自定义距离
|
||
for i, lod_distance in enumerate(lod_distances):
|
||
if distance < lod_distance:
|
||
return i
|
||
return len(lod_distances) - 1
|
||
|
||
except Exception as e:
|
||
print(f"✗ LOD级别计算失败: {e}")
|
||
return 0
|
||
|
||
def frustum_cull(self, camera_position: List[float],
|
||
view_matrix: np.ndarray, projection_matrix: np.ndarray) -> List[Tuple[int, int]]:
|
||
"""
|
||
视锥体剔除
|
||
|
||
Args:
|
||
camera_position: 摄像机位置
|
||
view_matrix: 视图矩阵
|
||
projection_matrix: 投影矩阵
|
||
|
||
Returns:
|
||
可见区块列表 [(chunk_x, chunk_y), ...]
|
||
"""
|
||
try:
|
||
# 在实际实现中,这里会进行复杂的视锥体剔除计算
|
||
# 简化实现:返回所有区块
|
||
visible_chunks = []
|
||
|
||
if self.plugin.terrain_manager:
|
||
terrain_data = self.plugin.terrain_manager.get_terrain_data()
|
||
terrain_size = terrain_data.get('stats', {}).get('chunks_generated', 0)
|
||
if terrain_size > 0:
|
||
# 模拟一些可见区块
|
||
for i in range(min(10, terrain_size)):
|
||
visible_chunks.append((i, i))
|
||
|
||
return visible_chunks
|
||
|
||
except Exception as e:
|
||
print(f"✗ 视锥体剔除失败: {e}")
|
||
return []
|
||
|
||
def occlusion_cull(self, visible_chunks: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||
"""
|
||
遮挡剔除
|
||
|
||
Args:
|
||
visible_chunks: 可见区块列表
|
||
|
||
Returns:
|
||
未被遮挡的区块列表
|
||
"""
|
||
try:
|
||
# 在实际实现中,这里会进行遮挡剔除计算
|
||
# 简化实现:返回所有可见区块
|
||
return visible_chunks
|
||
|
||
except Exception as e:
|
||
print(f"✗ 遮挡剔除失败: {e}")
|
||
return visible_chunks
|
||
|
||
def generate_terrain_materials(self, biome_map: np.ndarray) -> Dict[str, Any]:
|
||
"""
|
||
生成地形材质
|
||
|
||
Args:
|
||
biome_map: 生物群落图数据
|
||
|
||
Returns:
|
||
材质数据字典
|
||
"""
|
||
try:
|
||
print("✓ 开始生成地形材质...")
|
||
|
||
# 在实际实现中,这里会生成多种材质纹理和属性
|
||
materials = {
|
||
'diffuse_textures': [],
|
||
'normal_textures': [],
|
||
'specular_textures': [],
|
||
'biome_mapping': {}
|
||
}
|
||
|
||
# 为每种生物群落生成材质ID
|
||
unique_biomes = np.unique(biome_map)
|
||
for biome_id in unique_biomes:
|
||
materials['biome_mapping'][biome_id] = f"material_{biome_id}"
|
||
|
||
print("✓ 地形材质生成完成")
|
||
return materials
|
||
|
||
except Exception as e:
|
||
print(f"✗ 地形材质生成失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return {}
|
||
|
||
def apply_tessellation(self, vertices: np.ndarray, tessellation_factor: float) -> np.ndarray:
|
||
"""
|
||
应用细分曲面
|
||
|
||
Args:
|
||
vertices: 顶点数据
|
||
tessellation_factor: 细分因子
|
||
|
||
Returns:
|
||
细分后的顶点数据
|
||
"""
|
||
try:
|
||
# 在实际实现中,这里会使用GPU细分着色器
|
||
# 简化实现:返回原始顶点
|
||
print(f"✓ 应用细分曲面 (因子: {tessellation_factor})")
|
||
return vertices
|
||
|
||
except Exception as e:
|
||
print(f"✗ 细分曲面应用失败: {e}")
|
||
return vertices
|
||
|
||
def apply_displacement_mapping(self, heightmap: np.ndarray, displacement_factor: float) -> np.ndarray:
|
||
"""
|
||
应用置换贴图
|
||
|
||
Args:
|
||
heightmap: 高度图数据
|
||
displacement_factor: 置换因子
|
||
|
||
Returns:
|
||
置换后的顶点数据
|
||
"""
|
||
try:
|
||
# 在实际实现中,这里会在细分着色器中应用置换贴图
|
||
# 简化实现:返回处理后的高度图
|
||
displaced_heightmap = heightmap * displacement_factor
|
||
print(f"✓ 应用置换贴图 (因子: {displacement_factor})")
|
||
return displaced_heightmap
|
||
|
||
except Exception as e:
|
||
print(f"✗ 置换贴图应用失败: {e}")
|
||
return heightmap
|
||
|
||
def calculate_shadow_map(self, light_direction: List[float],
|
||
heightmap: np.ndarray) -> np.ndarray:
|
||
"""
|
||
计算阴影贴图
|
||
|
||
Args:
|
||
light_direction: 光照方向
|
||
heightmap: 高度图数据
|
||
|
||
Returns:
|
||
阴影贴图数据
|
||
"""
|
||
try:
|
||
height, width = heightmap.shape
|
||
print(f"✓ 开始计算 {width}x{height} 阴影贴图...")
|
||
|
||
# 创建阴影贴图数据
|
||
shadow_map = np.ones((height, width), dtype=np.float32)
|
||
|
||
# 简化的阴影计算
|
||
light_x, light_y, light_z = light_direction
|
||
|
||
# 根据光照方向计算阴影
|
||
for y in range(height):
|
||
for x in range(width):
|
||
# 简单的高度遮挡计算
|
||
current_height = heightmap[y, x]
|
||
shadow_factor = 1.0
|
||
|
||
# 检查在光照方向上是否有更高的地形遮挡
|
||
step_x = int(light_x * 10)
|
||
step_y = int(light_y * 10)
|
||
|
||
check_x = x + step_x
|
||
check_y = y + step_y
|
||
|
||
if 0 <= check_x < width and 0 <= check_y < height:
|
||
check_height = heightmap[check_y, check_x]
|
||
if check_height > current_height:
|
||
shadow_factor = 0.5 # 简单的半阴影
|
||
|
||
shadow_map[y, x] = shadow_factor
|
||
|
||
print("✓ 阴影贴图计算完成")
|
||
return shadow_map
|
||
|
||
except Exception as e:
|
||
print(f"✗ 阴影贴图计算失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
# 返回默认阴影贴图(无阴影)
|
||
height, width = heightmap.shape
|
||
return np.ones((height, width), dtype=np.float32)
|
||
|
||
def apply_atmospheric_effects(self, color_data: np.ndarray,
|
||
camera_position: List[float],
|
||
sun_direction: List[float]) -> np.ndarray:
|
||
"""
|
||
应用大气效果
|
||
|
||
Args:
|
||
color_data: 颜色数据
|
||
camera_position: 摄像机位置
|
||
sun_direction: 太阳方向
|
||
|
||
Returns:
|
||
处理后的颜色数据
|
||
"""
|
||
try:
|
||
processed_colors = color_data.copy()
|
||
height, width = processed_colors.shape[:2]
|
||
|
||
print("✓ 应用大气效果...")
|
||
|
||
# 简化的雾效和光照计算
|
||
fog_density = 0.01
|
||
sun_r, sun_g, sun_b = sun_direction
|
||
|
||
for y in range(height):
|
||
for x in range(width):
|
||
# 计算到摄像机的距离(简化)
|
||
distance = math.sqrt((x - camera_position[0])**2 + (y - camera_position[1])**2)
|
||
|
||
# 应用雾效
|
||
fog_factor = math.exp(-distance * fog_density)
|
||
fog_factor = max(0.0, min(1.0, fog_factor))
|
||
|
||
# 混合雾颜色(白色)
|
||
processed_colors[y, x, 0] = int(processed_colors[y, x, 0] * fog_factor + 255 * (1 - fog_factor))
|
||
processed_colors[y, x, 1] = int(processed_colors[y, x, 1] * fog_factor + 255 * (1 - fog_factor))
|
||
processed_colors[y, x, 2] = int(processed_colors[y, x, 2] * fog_factor + 255 * (1 - fog_factor))
|
||
|
||
print("✓ 大气效果应用完成")
|
||
return processed_colors
|
||
|
||
except Exception as e:
|
||
print(f"✗ 大气效果应用失败: {e}")
|
||
return color_data
|
||
|
||
def optimize_rendering(self, target_fps: float = 60.0):
|
||
"""
|
||
优化渲染性能
|
||
|
||
Args:
|
||
target_fps: 目标帧率
|
||
"""
|
||
try:
|
||
print(f"✓ 开始优化渲染性能 (目标FPS: {target_fps})")
|
||
|
||
# 根据目标FPS调整渲染参数
|
||
current_fps = 1.0 / max(0.001, self.stats['average_render_time'])
|
||
|
||
if current_fps < target_fps * 0.8: # 如果FPS低于目标的80%
|
||
# 降低渲染质量以提高性能
|
||
self.render_params['lod_levels'] = max(1, self.render_params['lod_levels'] - 1)
|
||
self.render_params['chunk_size'] = min(64, self.render_params['chunk_size'] * 2)
|
||
print(" → 降低LOD级别和增加区块大小以提高性能")
|
||
|
||
elif current_fps > target_fps * 1.2: # 如果FPS高于目标的120%
|
||
# 提高渲染质量
|
||
self.render_params['lod_levels'] = min(10, self.render_params['lod_levels'] + 1)
|
||
self.render_params['chunk_size'] = max(8, self.render_params['chunk_size'] // 2)
|
||
print(" → 提高LOD级别和减小区块大小以提高质量")
|
||
|
||
print("✓ 渲染性能优化完成")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 渲染性能优化失败: {e}")
|
||
|
||
def export_render_settings(self, filename: str) -> bool:
|
||
"""
|
||
导出渲染设置
|
||
|
||
Args:
|
||
filename: 文件名
|
||
|
||
Returns:
|
||
是否导出成功
|
||
"""
|
||
try:
|
||
import json
|
||
|
||
settings = {
|
||
'render_params': self.render_params,
|
||
'lighting_params': self.lighting_params,
|
||
'material_params': self.material_params,
|
||
'shader_params': self.shader_params,
|
||
'timestamp': time.time()
|
||
}
|
||
|
||
with open(filename, 'w', encoding='utf-8') as f:
|
||
json.dump(settings, f, ensure_ascii=False, indent=2)
|
||
|
||
print(f"✓ 渲染设置已导出到: {filename}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 渲染设置导出失败: {e}")
|
||
return False
|
||
|
||
def import_render_settings(self, filename: str) -> bool:
|
||
"""
|
||
导入渲染设置
|
||
|
||
Args:
|
||
filename: 文件名
|
||
|
||
Returns:
|
||
是否导入成功
|
||
"""
|
||
try:
|
||
import json
|
||
|
||
with open(filename, 'r', encoding='utf-8') as f:
|
||
settings = json.load(f)
|
||
|
||
# 更新参数
|
||
if 'render_params' in settings:
|
||
self.render_params.update(settings['render_params'])
|
||
if 'lighting_params' in settings:
|
||
self.lighting_params.update(settings['lighting_params'])
|
||
if 'material_params' in settings:
|
||
self.material_params.update(settings['material_params'])
|
||
if 'shader_params' in settings:
|
||
self.shader_params.update(settings['shader_params'])
|
||
|
||
print(f"✓ 渲染设置已从 {filename} 导入")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 渲染设置导入失败: {e}")
|
||
return False
|
||
|
||
def get_render_capabilities(self) -> Dict[str, Any]:
|
||
"""
|
||
获取渲染能力信息
|
||
|
||
Returns:
|
||
渲染能力信息字典
|
||
"""
|
||
return {
|
||
'max_texture_size': 8192,
|
||
'max_lod_levels': 10,
|
||
'supported_shaders': ['vertex', 'fragment', 'geometry', 'tessellation'],
|
||
'supports_normal_mapping': True,
|
||
'supports_displacement_mapping': True,
|
||
'supports_tessellation': True,
|
||
'supports_shadow_mapping': True,
|
||
'max_anisotropy': 16.0
|
||
}
|
||
|
||
def set_render_quality(self, quality_level: str):
|
||
"""
|
||
设置渲染质量级别
|
||
|
||
Args:
|
||
quality_level: 质量级别 ('low', 'medium', 'high', 'ultra')
|
||
"""
|
||
quality_settings = {
|
||
'low': {
|
||
'lod_levels': 2,
|
||
'chunk_size': 64,
|
||
'normal_mapping': False,
|
||
'tessellation_factor': 0.5
|
||
},
|
||
'medium': {
|
||
'lod_levels': 4,
|
||
'chunk_size': 32,
|
||
'normal_mapping': True,
|
||
'tessellation_factor': 1.0
|
||
},
|
||
'high': {
|
||
'lod_levels': 6,
|
||
'chunk_size': 16,
|
||
'normal_mapping': True,
|
||
'tessellation_factor': 2.0
|
||
},
|
||
'ultra': {
|
||
'lod_levels': 8,
|
||
'chunk_size': 8,
|
||
'normal_mapping': True,
|
||
'tessellation_factor': 4.0
|
||
}
|
||
}
|
||
|
||
if quality_level in quality_settings:
|
||
settings = quality_settings[quality_level]
|
||
self.render_params['lod_levels'] = settings['lod_levels']
|
||
self.render_params['chunk_size'] = settings['chunk_size']
|
||
self.material_params['normal_mapping'] = settings['normal_mapping']
|
||
self.material_params['tessellation_factor'] = settings['tessellation_factor']
|
||
print(f"✓ 渲染质量已设置为: {quality_level}")
|
||
else:
|
||
print(f"✗ 无效的质量级别: {quality_level}") |