Игра запускается но чёрный экран. Возможно дело в opengl + Panda3D
# --- Start of game_objects.py ---
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
import numpy as np
from OpenGL.GL import *
from OpenGL.arrays import vbo

class Character:
    def __init__(self, model_path="characterMedium.fbx", position=(0, 0, -200), scale=1.0):
        self.position = list(position)
        self.scale = scale

        # Инициализация Panda3D
        self.base = ShowBase(windowType='offscreen')  # Отключаем создание окна
        self.base.disableMouse()  # Отключаем управление камерой мышью по умолчанию

        # Загрузка модели через Panda3D
        self.model = self.base.loader.loadModel(model_path)
        self.model.reparentTo(self.base.render)  # Добавляем модель в сцену
        self.model.setScale(self.scale)  # Масштабирование модели
        self.model.setPos(*self.position)  # Установка позиции модели

        # Извлечение данных вершин, нормалей и UV-координат
        vertices, normals, uvs = self.extract_vertices_from_model()  # Сохраняем все три значения
        self.vbo_data = self.generate_vbo_data(vertices, normals, uvs)  # Передаем все три значения
        self.vbo = vbo.VBO(np.array(self.vbo_data, dtype=np.float32))

    def extract_vertices_from_model(self):
        """Извлечение данных вершин из модели Panda3D."""
        geom_node = self.model.find("**/+GeomNode").node()
        geom = geom_node.getGeom(0)  # Убран лишний отступ
        vertex_data = geom.getVertexData()

        # Извлечение позиций вершин
        reader = GeomVertexReader(vertex_data, 'vertex')
        vertices = []
        while not reader.isAtEnd():
            vertex = reader.getData3f()
            vertices.extend([vertex.x, vertex.y, vertex.z])

        # Извлечение нормалей
        normal_reader = GeomVertexReader(vertex_data, 'normal')
        normals = []
        while not normal_reader.isAtEnd():
            normal = normal_reader.getData3f()
            normals.extend([normal.x, normal.y, normal.z])

        # Извлечение UV-координат
        uv_reader = GeomVertexReader(vertex_data, 'texcoord')
        uvs = []
        while not uv_reader.isAtEnd():
            uv = uv_reader.getData2f()
            uvs.extend([uv.x, uv.y])

        return vertices, normals, uvs


    def generate_vbo_data(self, vertices, normals, uvs):
        data = []
        for i in range(0, len(vertices), 3):  # Предполагаем формат [x, y, z]
            position = np.array(vertices[i:i+3]) * self.scale  # Преобразуем срез в numpy массив
            normal = normals[i:i+3]
            uv = uvs[i//3*2:i//3*2+2]  # UV-координаты для каждой вершины
            data.extend(position)
            data.extend(normal)
            data.extend(uv)
        return data

    def update_position(self, position):
        """Обновление позиции модели."""
        self.position = list(position)
        self.model.setPos(*self.position)

    def render(self):
        """Отрисовка модели через OpenGL."""
        glPushMatrix()
        glTranslatef(*self.position)  # Перемещение модели
        glScalef(self.scale, self.scale, self.scale)  # Масштабирование модели

        self.vbo.bind()
        glEnableClientState(GL_VERTEX_ARRAY)
        glEnableClientState(GL_NORMAL_ARRAY)
        glEnableClientState(GL_TEXTURE_COORD_ARRAY)

        # Указываем формат данных в VBO: позиция (3 float), нормаль (3 float), UV (2 float)
        stride = (3 + 3 + 2) * 4  # Размер одной вершины в байтах
        glVertexPointer(3, GL_FLOAT, stride, self.vbo + 0)  # Позиции вершин
        glNormalPointer(GL_FLOAT, stride, self.vbo + 12)  # Нормали
        glTexCoordPointer(2, GL_FLOAT, stride, self.vbo + 24)  # UV-координаты

        glDrawArrays(GL_TRIANGLES, 0, len(self.vbo_data) // 8)  # Отрисовка треугольников

        glDisableClientState(GL_VERTEX_ARRAY)
        glDisableClientState(GL_NORMAL_ARRAY)
        glDisableClientState(GL_TEXTURE_COORD_ARRAY)
        self.vbo.unbind()

        glPopMatrix()
# --- End of game_objects.py ---

# --- Start of interface_module.py ---
from PIL import Image
import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *

class GameInterface:
    def __init__(self, minimap_filename="mini_map.png", minimap_size=200):
        self.minimap_texture = self.load_minimap_texture(minimap_filename)
        self.minimap_size = minimap_size

    def load_minimap_texture(self, filename):
        """Загружает текстуру миникарты из файла PNG."""
        img = Image.open(filename).transpose(Image.FLIP_TOP_BOTTOM)
        if img.mode != "RGB":
            img = img.convert("RGB")
        img_data = np.array(list(img.getdata()), np.uint8)

        texture_id = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture_id)

        glEnable(GL_TEXTURE_2D)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
        return texture_id

    def render_minimap(self, player_x, player_z, plane_size):
        glPushMatrix()

        glDisable(GL_DEPTH_TEST)

        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        gluOrtho2D(0, 800, 0, 600)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        glEnable(GL_TEXTURE_2D)
        glBindTexture(GL_TEXTURE_2D, self.minimap_texture)
        glBegin(GL_QUADS)
        glTexCoord2f(0, 0)
        glVertex2f(800 - self.minimap_size, 600 - self.minimap_size)
        glTexCoord2f(1, 0)
        glVertex2f(800, 600 - self.minimap_size)
        glTexCoord2f(1, 1)
        glVertex2f(800, 600)
        glTexCoord2f(0, 1)
        glVertex2f(800 - self.minimap_size, 600)
        glEnd()
        glDisable(GL_TEXTURE_2D)

        glColor3f(1, 0, 0)
        glBegin(GL_QUADS)
        vertex_size = 2
        x = (player_x / plane_size) * self.minimap_size + (800 - self.minimap_size/2)
        z = (player_z / plane_size) * self.minimap_size + (600 - self.minimap_size/2)
        glVertex2f(x - vertex_size, z - vertex_size)
        glVertex2f(x + vertex_size, z - vertex_size)
        glVertex2f(x + vertex_size, z + vertex_size)
        glVertex2f(x - vertex_size, z + vertex_size)
        glEnd()

        glMatrixMode(GL_PROJECTION)
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)

        glEnable(GL_DEPTH_TEST)
        glPopMatrix()
# --- End of interface_module.py ---

# --- Start of main.py ---
import pygame
import math
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from plane_module import Plane
from game_objects import Character
from interface_module import GameInterface

pygame.init()
screen = pygame.display.set_mode((800, 600), DOUBLEBUF | OPENGL)
pygame.display.set_caption("3D Shooter with OpenGL")

glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)

glLightfv(GL_LIGHT0, GL_POSITION, (0, 100, 0, 1))
glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.8, 0.8, 0.8, 1))

player_x, player_y, player_z = 0, 0, 0
move_speed = 0.8  # Скорость движения в обычном режиме
jump_force = 1.5
gravity = 0.08

camera_distance = 20
camera_height = 15
camera_pitch = 0
camera_yaw = 0
mouse_sensitivity = 0.2
min_pitch, max_pitch = -89, 89

# Указываем height_scale для более выраженных перепадов высот
plane = Plane(size=20000, grid=50, heightmap="Height_Map.exr", diffusemap="Diffuse_Map.exr", height_scale=20000)
character = Character(model_path="characterMedium.fbx", position=[player_x, player_y, player_z], scale=5.0)
interface = GameInterface(minimap_filename="mini_map.png", minimap_size=200)

# Флаг для режима noclip
noclip_mode = False

def set_camera(player_x, player_y, player_z, yaw, pitch, distance):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(60, (800 / 600), 0.1, 10000.0)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    look_x = player_x + math.sin(math.radians(yaw)) * math.cos(math.radians(pitch))
    look_y = player_y + math.sin(math.radians(pitch))
    look_z = player_z - math.cos(math.radians(yaw)) * math.cos(math.radians(pitch))

    gluLookAt(
        player_x, player_y + camera_height, player_z,
        look_x, look_y + camera_height, look_z,
        0, 1, 0
    )

clock = pygame.time.Clock()
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
vertical_momentum = 0
is_grounded = True
running = True

while running:
    dt = clock.tick(60) * 0.1

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glClearColor(0.53, 0.81, 0.98, 1)

    set_camera(*character.position, camera_yaw, camera_pitch, camera_distance)

    glPushMatrix()
    plane.render()
    character.render()  # Рендеринг персонажа
    glPopMatrix()

    glDisable(GL_DEPTH_TEST)
    interface.render_minimap(character.position[0], character.position[2], plane_size=20000)
    glEnable(GL_DEPTH_TEST)

    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_n:  # Переключение режима noclip
            noclip_mode = not noclip_mode

    dx, dy = pygame.mouse.get_rel()
    camera_yaw += dx * mouse_sensitivity
    camera_pitch -= dy * mouse_sensitivity
    camera_pitch = max(min_pitch, min(max_pitch, camera_pitch))

    keys = pygame.key.get_pressed()

    # Определяем текущую скорость в зависимости от режима
    current_move_speed = move_speed * 10 if noclip_mode else move_speed

    move_forward = (keys[pygame.K_w] - keys[pygame.K_s]) * current_move_speed * dt
    move_strafe = (keys[pygame.K_d] - keys[pygame.K_a]) * current_move_speed * dt

    angle = math.radians(camera_yaw)
    forward_x = math.sin(angle)
    forward_z = -math.cos(angle)
    strafe_x = math.cos(angle)
    strafe_z = math.sin(angle)

    old_character_position = character.position.copy()

    if noclip_mode:
        # В режиме noclip игнорируем высоту местности и гравитацию
        new_x = character.position[0] + forward_x * move_forward + strafe_x * move_strafe
        new_z = character.position[2] + forward_z * move_forward + strafe_z * move_strafe
        vertical_momentum = 0
        new_y = character.position[1] + (keys[pygame.K_SPACE] - keys[pygame.K_LCTRL]) * current_move_speed * dt
        character.update_position([new_x, new_y, new_z])
    else:
        # В обычном режиме сохраняем логику столкновений и гравитации
        new_x = character.position[0] + forward_x * move_forward + strafe_x * move_strafe
        new_z = character.position[2] + forward_z * move_forward + strafe_z * move_strafe

        terrain_height = plane.get_height(new_x, new_z)
        if character.position[1] < terrain_height:
            character.position[1] = terrain_height
            vertical_momentum = 0
            is_grounded = True

        if not is_grounded:
            vertical_momentum -= gravity * dt
            character.position[1] += vertical_momentum
        else:
            vertical_momentum = 0

        if is_grounded and keys[pygame.K_SPACE]:
            vertical_momentum = jump_force
            is_grounded = False

        character.update_position([new_x, character.position[1], new_z])

    pygame.display.flip()

pygame.quit()
# --- End of main.py ---

# --- Start of plane_module.py ---
import OpenEXR
import Imath
import numpy as np
from PIL import Image
import math
from OpenGL.GL import *
from OpenGL.arrays import vbo

class Plane:
    def __init__(self, size=20000, grid=50, heightmap="Height_Map.exr", diffusemap="Diffuse_Map.exr", height_scale=4000):
        self.size = size  # Увеличен базовый размер в 20 раз (1000 -> 20000)
        self.grid = grid
        self.height_scale = height_scale  # Новый параметр для масштабирования высот
        self.heightmap = self.load_heightmap(heightmap)
        self.diffuse_texture = self.load_diffuse_texture(diffusemap)
        self.points = self.generate_points()
        self.triangles = self.generate_triangles()
        self.vbo_data = self.generate_vbo_data()
        self.vbo = vbo.VBO(np.array(self.vbo_data, dtype=np.float32))

    def load_heightmap(self, filename):
        """Загружает карту высот из файла EXR."""
        exr_file = OpenEXR.InputFile(filename)
        dw = exr_file.header()['dataWindow']
        width, height = dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1
        red_channel = np.frombuffer(exr_file.channel('R', Imath.PixelType(Imath.PixelType.FLOAT)), dtype=np.float32)
        red_channel.shape = (height, width)
        return red_channel

    def load_diffuse_texture(self, filename):
        """Загружает текстуру из файла EXR."""
        exr_file = OpenEXR.InputFile(filename)
        dw = exr_file.header()['dataWindow']
        size = (dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1)

        # Получаем каналы R, G, B
        channels = exr_file.channels(['R', 'G', 'B'], Imath.PixelType(Imath.PixelType.FLOAT))
        red_channel = np.frombuffer(channels[0], dtype=np.float32).reshape(size[1], size[0])
        green_channel = np.frombuffer(channels[1], dtype=np.float32).reshape(size[1], size[0])
        blue_channel = np.frombuffer(channels[2], dtype=np.float32).reshape(size[1], size[0])

        # Собираем данные в RGB-текстуру
        img_data = np.stack([red_channel, green_channel, blue_channel], axis=-1)

        # Создаем текстуру в OpenGL
        texture_id = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture_id)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, size[0], size[1], 0, GL_RGB, GL_FLOAT, img_data)
        glGenerateMipmap(GL_TEXTURE_2D)

        # Устанавливаем параметры текстуры
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

        return texture_id

    def get_height(self, x, z):
        width, height = self.heightmap.shape
        # Масштабируем координаты согласно новому размеру
        x_index = int((x + self.size / 2) / self.size * (width - 1))
        z_index = int((z + self.size / 2) / self.size * (height - 1))
        if 0 <= x_index < width and 0 <= z_index < height:
            return self.heightmap[z_index, x_index] * self.height_scale  # Используем height_scale
        else:
            return 0

    def generate_points(self):
        points = []
        for x in range(-self.size, self.size + 1, self.grid):
            for z in range(-self.size, self.size + 1, self.grid):
                y = self.get_height(x, z)
                points.append((x, y, z))
        return points

    def generate_triangles(self):
        triangles = []
        width = self.size * 2 // self.grid + 1
        for i in range(len(self.points) - width - 1):
            if (i + 1) % width == 0:
                continue
            triangles.append([self.points[i], self.points[i + 1], self.points[i + width]])
            triangles.append([self.points[i + 1], self.points[i + width + 1], self.points[i + width]])
        return triangles

    def calculate_normal(self, triangle):
        v1 = (
            triangle[1][0] - triangle[0][0],
            triangle[1][1] - triangle[0][1],
            triangle[1][2] - triangle[0][2],
        )
        v2 = (
            triangle[2][0] - triangle[0][0],
            triangle[2][1] - triangle[0][1],
            triangle[2][2] - triangle[0][2],
        )
        normal = (
            v1[1] * v2[2] - v1[2] * v2[1],
            v1[2] * v2[0] - v1[0] * v2[2],
            v1[0] * v2[1] - v1[1] * v2[0],
        )
        length = math.sqrt(normal[0] ** 2 + normal[1] ** 2 + normal[2] ** 2)
        return (
            normal[0] / length,
            normal[1] / length,
            normal[2] / length,
        )

    def generate_vbo_data(self):
        data = []
        for triangle in self.triangles:
            normal = self.calculate_normal(triangle)
            for vertex in triangle:
                data.extend(normal)
                data.extend(vertex)
                # Текстурные координаты
                tex_u = (vertex[0] + self.size) / (2 * self.size)
                tex_v = (vertex[2] + self.size) / (2 * self.size)
                data.extend([tex_u, tex_v])
        return data

    def render(self):
        glEnable(GL_TEXTURE_2D)
        glBindTexture(GL_TEXTURE_2D, self.diffuse_texture)
        glColor3f(1, 1, 1)  # Уберите цвет, чтобы текстура не смешивалась с материалом

        self.vbo.bind()
        glEnableClientState(GL_VERTEX_ARRAY)
        glEnableClientState(GL_NORMAL_ARRAY)
        glEnableClientState(GL_TEXTURE_COORD_ARRAY)

        stride = 32  # 12 (нормаль) + 12 (вершина) + 8 (текстура)
        glNormalPointer(GL_FLOAT, stride, self.vbo)
        glVertexPointer(3, GL_FLOAT, stride, self.vbo + 12)
        glTexCoordPointer(2, GL_FLOAT, stride, self.vbo + 24)

        glDrawArrays(GL_TRIANGLES, 0, len(self.vbo_data) // 8)

        glDisableClientState(GL_VERTEX_ARRAY)
        glDisableClientState(GL_NORMAL_ARRAY)
        glDisableClientState(GL_TEXTURE_COORD_ARRAY)
        self.vbo.unbind()
        glDisable(GL_TEXTURE_2D)
# --- End of plane_module.py ---