aether-engine 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. aether/__init__.py +54 -0
  2. aether/audio/__init__.py +0 -0
  3. aether/audio/audio_manager.py +211 -0
  4. aether/audio/music.py +6 -0
  5. aether/audio/sound.py +6 -0
  6. aether/core/__init__.py +211 -0
  7. aether/core/achievement_system.py +304 -0
  8. aether/core/application.py +19 -0
  9. aether/core/config.py +75 -0
  10. aether/core/console.py +366 -0
  11. aether/core/engine.py +50 -0
  12. aether/core/event_bus.py +24 -0
  13. aether/core/input.py +58 -0
  14. aether/core/plugin_system.py +93 -0
  15. aether/core/save_system.py +409 -0
  16. aether/core/time_manager.py +25 -0
  17. aether/core/window.py +57 -0
  18. aether/graphics/__init__.py +0 -0
  19. aether/graphics/camera.py +69 -0
  20. aether/graphics/framebuffer.py +119 -0
  21. aether/graphics/light.py +32 -0
  22. aether/graphics/lod_system.py +170 -0
  23. aether/graphics/material.py +25 -0
  24. aether/graphics/mesh.py +83 -0
  25. aether/graphics/mesh_factory.py +67 -0
  26. aether/graphics/particle_system.py +268 -0
  27. aether/graphics/post_process.py +279 -0
  28. aether/graphics/renderer.py +135 -0
  29. aether/graphics/shader.py +61 -0
  30. aether/graphics/shader_library.py +157 -0
  31. aether/graphics/sky_atmosphere.py +309 -0
  32. aether/graphics/terrain.py +408 -0
  33. aether/graphics/texture.py +34 -0
  34. aether/graphics/water.py +432 -0
  35. aether/physics/__init__.py +0 -0
  36. aether/physics/bullet_backend.py +395 -0
  37. aether/physics/collider.py +22 -0
  38. aether/physics/physics_world.py +436 -0
  39. aether/physics/raycast.py +25 -0
  40. aether/physics/rigidbody.py +7 -0
  41. aether/resources/__init__.py +0 -0
  42. aether/resources/asset_database.py +76 -0
  43. aether/resources/resource_manager.py +147 -0
  44. aether/resources/serialization.py +114 -0
  45. aether/scene/__init__.py +0 -0
  46. aether/scene/component.py +368 -0
  47. aether/scene/entity.py +46 -0
  48. aether/scene/scene.py +33 -0
  49. aether/scene/scene_graph.py +252 -0
  50. aether/scene/scene_manager.py +191 -0
  51. aether/ui/__init__.py +0 -0
  52. aether/ui/button.py +297 -0
  53. aether/ui/canvas.py +11 -0
  54. aether/ui/text.py +286 -0
  55. aether/utils/__init__.py +0 -0
  56. aether/utils/logger.py +16 -0
  57. aether/utils/math_utils.py +31 -0
  58. aether/utils/object_pool.py +137 -0
  59. aether/utils/profiler.py +187 -0
  60. aether_engine-1.0.0.dist-info/METADATA +71 -0
  61. aether_engine-1.0.0.dist-info/RECORD +64 -0
  62. aether_engine-1.0.0.dist-info/WHEEL +5 -0
  63. aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
  64. aether_engine-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,279 @@
1
+ """Эффекты пост-обработки: Bloom, SSAO, HDR, DOF"""
2
+ import numpy as np
3
+ from OpenGL.GL import *
4
+ from aether.graphics.shader import Shader
5
+ from aether.graphics.framebuffer import Framebuffer
6
+
7
+
8
+ class PostProcessEffect:
9
+ """Базовый класс эффекта пост-обработки"""
10
+
11
+ def __init__(self, name: str):
12
+ self.name = name
13
+ self.enabled = True
14
+
15
+ def apply(self, input_texture: int, output_fbo: Framebuffer,
16
+ width: int, height: int):
17
+ """Применяет эффект"""
18
+ pass
19
+
20
+
21
+ class BloomEffect(PostProcessEffect):
22
+ """Эффект Bloom (свечение)"""
23
+
24
+ def __init__(self, threshold: float = 1.0, intensity: float = 0.5,
25
+ blur_iterations: int = 5):
26
+ super().__init__("Bloom")
27
+ self.threshold = threshold
28
+ self.intensity = intensity
29
+ self.blur_iterations = blur_iterations
30
+
31
+ self._setup_shaders()
32
+ self._setup_buffers()
33
+
34
+ def _setup_shaders(self):
35
+ """Настройка шейдеров"""
36
+ threshold_vert = """
37
+ #version 460 core
38
+ layout(location = 0) in vec2 aPos;
39
+ layout(location = 1) in vec2 aTexCoord;
40
+ out vec2 TexCoord;
41
+ void main() {
42
+ gl_Position = vec4(aPos, 0.0, 1.0);
43
+ TexCoord = aTexCoord;
44
+ }
45
+ """
46
+
47
+ threshold_frag = """
48
+ #version 460 core
49
+ in vec2 TexCoord;
50
+ out vec4 FragColor;
51
+ uniform sampler2D sceneTexture;
52
+ uniform float threshold;
53
+ void main() {
54
+ vec3 color = texture(sceneTexture, TexCoord).rgb;
55
+ float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));
56
+ if (brightness > threshold) {
57
+ FragColor = vec4(color, 1.0);
58
+ } else {
59
+ FragColor = vec4(0.0, 0.0, 0.0, 1.0);
60
+ }
61
+ }
62
+ """
63
+
64
+ blur_vert = threshold_vert
65
+
66
+ blur_frag = """
67
+ #version 460 core
68
+ in vec2 TexCoord;
69
+ out vec4 FragColor;
70
+ uniform sampler2D image;
71
+ uniform bool horizontal;
72
+ uniform float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
73
+ void main() {
74
+ vec2 tex_offset = 1.0 / textureSize(image, 0);
75
+ vec3 result = texture(image, TexCoord).rgb * weight[0];
76
+ if (horizontal) {
77
+ for (int i = 1; i < 5; i++) {
78
+ result += texture(image, TexCoord + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
79
+ result += texture(image, TexCoord - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
80
+ }
81
+ } else {
82
+ for (int i = 1; i < 5; i++) {
83
+ result += texture(image, TexCoord + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
84
+ result += texture(image, TexCoord - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
85
+ }
86
+ }
87
+ FragColor = vec4(result, 1.0);
88
+ }
89
+ """
90
+
91
+ final_vert = threshold_vert
92
+
93
+ final_frag = """
94
+ #version 460 core
95
+ in vec2 TexCoord;
96
+ out vec4 FragColor;
97
+ uniform sampler2D sceneTexture;
98
+ uniform sampler2D bloomTexture;
99
+ uniform float bloomIntensity;
100
+ void main() {
101
+ vec3 sceneColor = texture(sceneTexture, TexCoord).rgb;
102
+ vec3 bloomColor = texture(bloomTexture, TexCoord).rgb;
103
+ vec3 result = sceneColor + bloomColor * bloomIntensity;
104
+
105
+ // HDR tonemapping
106
+ result = result / (result + vec3(1.0));
107
+
108
+ // Gamma correction
109
+ result = pow(result, vec3(1.0 / 2.2));
110
+
111
+ FragColor = vec4(result, 1.0);
112
+ }
113
+ """
114
+
115
+ from aether.graphics.shader import Shader
116
+ self.threshold_shader = Shader(threshold_vert, threshold_frag)
117
+ self.blur_shader = Shader(blur_vert, blur_frag)
118
+ self.final_shader = Shader(final_vert, final_frag)
119
+
120
+ def _setup_buffers(self):
121
+ """Создаёт буферы для Bloom"""
122
+ self.ping_pong_fbos = [Framebuffer(1920, 1080) for _ in range(2)]
123
+ self.threshold_fbo = Framebuffer(1920, 1080)
124
+
125
+ def apply(self, input_texture: int, output_fbo: Framebuffer,
126
+ width: int, height: int):
127
+ """Применяет Bloom эффект"""
128
+ if not self.enabled:
129
+ return
130
+
131
+ # 1. Выделяем яркие области
132
+ self.threshold_fbo.bind()
133
+ glClear(GL_COLOR_BUFFER_BIT)
134
+ self.threshold_shader.use()
135
+ self.threshold_shader.set_float("threshold", self.threshold)
136
+ glBindTexture(GL_TEXTURE_2D, input_texture)
137
+ self._render_quad()
138
+
139
+ # 2. Размытие (ping-pong)
140
+ horizontal = True
141
+ first_iteration = True
142
+
143
+ for i in range(self.blur_iterations):
144
+ self.ping_pong_fbos[1 if horizontal else 0].bind()
145
+ glClear(GL_COLOR_BUFFER_BIT)
146
+ self.blur_shader.use()
147
+ self.blur_shader.set_bool("horizontal", horizontal)
148
+
149
+ if first_iteration:
150
+ glBindTexture(GL_TEXTURE_2D, self.threshold_fbo.color_texture)
151
+ first_iteration = False
152
+ else:
153
+ glBindTexture(GL_TEXTURE_2D,
154
+ self.ping_pong_fbos[0 if horizontal else 1].color_texture)
155
+
156
+ self._render_quad()
157
+ horizontal = not horizontal
158
+
159
+ # 3. Комбинируем с исходным изображением
160
+ output_fbo.bind()
161
+ glClear(GL_COLOR_BUFFER_BIT)
162
+ self.final_shader.use()
163
+ self.final_shader.set_float("bloomIntensity", self.intensity)
164
+
165
+ glActiveTexture(GL_TEXTURE0)
166
+ glBindTexture(GL_TEXTURE_2D, input_texture)
167
+ self.final_shader.set_int("sceneTexture", 0)
168
+
169
+ glActiveTexture(GL_TEXTURE1)
170
+ glBindTexture(GL_TEXTURE_2D, self.ping_pong_fbos[0].color_texture)
171
+ self.final_shader.set_int("bloomTexture", 1)
172
+
173
+ self._render_quad()
174
+
175
+ def _render_quad(self):
176
+ """Рендерит полноэкранный quad"""
177
+ vertices = np.array([
178
+ -1, -1, 0, 0,
179
+ 1, -1, 1, 0,
180
+ 1, 1, 1, 1,
181
+ -1, 1, 0, 1,
182
+ ], dtype=np.float32)
183
+
184
+ # Простой рендеринг quad (можно вынести в общий метод)
185
+ vao = glGenVertexArrays(1)
186
+ vbo = glGenBuffers(1)
187
+ glBindVertexArray(vao)
188
+ glBindBuffer(GL_ARRAY_BUFFER, vbo)
189
+ glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
190
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, ctypes.c_void_p(0))
191
+ glEnableVertexAttribArray(0)
192
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, ctypes.c_void_p(8))
193
+ glEnableVertexAttribArray(1)
194
+
195
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
196
+
197
+ glDeleteVertexArrays(1, [vao])
198
+ glDeleteBuffers(1, [vbo])
199
+
200
+
201
+ class SSAOEffect(PostProcessEffect):
202
+ """Screen Space Ambient Occlusion"""
203
+
204
+ def __init__(self, radius: float = 0.5, bias: float = 0.025,
205
+ sample_count: int = 64):
206
+ super().__init__("SSAO")
207
+ self.radius = radius
208
+ self.bias = bias
209
+ self.sample_count = sample_count
210
+
211
+ self._generate_samples()
212
+ self._generate_noise()
213
+
214
+ def _generate_samples(self):
215
+ """Генерирует сэмплы в полусфере"""
216
+ self.samples = []
217
+ for _ in range(self.sample_count):
218
+ sample = np.array([
219
+ np.random.uniform(-1, 1),
220
+ np.random.uniform(-1, 1),
221
+ np.random.uniform(0, 1)
222
+ ], dtype=np.float32)
223
+ sample = sample / np.linalg.norm(sample)
224
+ sample *= np.random.uniform(0, 1)
225
+
226
+ # Больше сэмплов ближе к центру
227
+ scale = _ / self.sample_count
228
+ scale = 0.1 + 0.9 * scale * scale
229
+ sample *= scale
230
+
231
+ self.samples.append(sample)
232
+
233
+ self.samples = np.array(self.samples, dtype=np.float32)
234
+
235
+ def _generate_noise(self):
236
+ """Генерирует шум для поворота сэмплов"""
237
+ self.noise = np.random.randn(16, 3).astype(np.float32)
238
+
239
+ def apply(self, input_texture: int, output_fbo: Framebuffer,
240
+ width: int, height: int):
241
+ """Применяет SSAO"""
242
+ # TODO: реализовать SSAO шейдер
243
+ pass
244
+
245
+
246
+ class PostProcessStack:
247
+ """Стек пост-обработки"""
248
+
249
+ def __init__(self, width: int, height: int):
250
+ self.width = width
251
+ self.height = height
252
+ self.effects = []
253
+
254
+ # Основные буферы
255
+ self.scene_fbo = Framebuffer(width, height)
256
+ self.temp_fbo = Framebuffer(width, height)
257
+
258
+ def add_effect(self, effect: PostProcessEffect):
259
+ """Добавляет эффект"""
260
+ self.effects.append(effect)
261
+
262
+ def process(self, input_texture: int) -> int:
263
+ """Применяет все эффекты по очереди"""
264
+ current_input = input_texture
265
+ current_output = self.temp_fbo
266
+
267
+ for effect in self.effects:
268
+ if effect.enabled:
269
+ effect.apply(current_input, current_output, self.width, self.height)
270
+ current_input, current_output = current_output, current_input
271
+
272
+ return current_input
273
+
274
+ def resize(self, width: int, height: int):
275
+ """Изменяет размер буферов"""
276
+ self.width = width
277
+ self.height = height
278
+ self.scene_fbo = Framebuffer(width, height)
279
+ self.temp_fbo = Framebuffer(width, height)
@@ -0,0 +1,135 @@
1
+ """Главный рендерер с системой Render Pass"""
2
+ from OpenGL.GL import *
3
+ import numpy as np
4
+ from typing import List, Optional
5
+ from aether.graphics.shader import Shader
6
+ from aether.graphics.camera import Camera
7
+ from aether.graphics.shader_library import ShaderLibrary
8
+
9
+
10
+ class RenderPass:
11
+ """Один проход рендеринга"""
12
+
13
+ def __init__(self, name: str, shader: Shader):
14
+ self.name = name
15
+ self.shader = shader
16
+ self.enabled = True
17
+ self.clear_color = (0.0, 0.0, 0.0, 1.0)
18
+ self.clear_flags = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
19
+
20
+ def begin(self):
21
+ """Начало прохода"""
22
+ if self.enabled:
23
+ self.shader.use()
24
+
25
+ def end(self):
26
+ """Конец прохода"""
27
+ pass
28
+
29
+
30
+ class Renderer:
31
+ """Главный рендерер движка"""
32
+
33
+ def __init__(self, width: int, height: int):
34
+ self.width = width
35
+ self.height = height
36
+ self.clear_color = (0.05, 0.05, 0.1, 1.0)
37
+
38
+ # Статистика
39
+ self.draw_calls = 0
40
+ self.triangles = 0
41
+
42
+ # Render Passes
43
+ self.passes: List[RenderPass] = []
44
+
45
+ # Настройка OpenGL
46
+ self._setup_opengl()
47
+
48
+ # Создаём стандартные проходы
49
+ self._create_default_passes()
50
+
51
+ print(f"[Renderer] Initialized ({width}x{height})")
52
+
53
+ def _setup_opengl(self):
54
+ """Настройка состояния OpenGL"""
55
+ glClearColor(*self.clear_color)
56
+ glEnable(GL_DEPTH_TEST)
57
+ glDepthFunc(GL_LESS)
58
+ glEnable(GL_CULL_FACE)
59
+ glCullFace(GL_BACK)
60
+ glEnable(GL_MULTISAMPLE)
61
+ glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS)
62
+
63
+ # Настройка для PBR
64
+ glEnable(GL_FRAMEBUFFER_SRGB)
65
+
66
+ def _create_default_passes(self):
67
+ """Создаёт стандартные проходы рендеринга"""
68
+ shader_lib = ShaderLibrary()
69
+
70
+ # Основной проход (PBR)
71
+ pbr_pass = RenderPass("PBR", shader_lib.get_pbr_shader())
72
+ self.passes.append(pbr_pass)
73
+
74
+ # Можно добавить: Shadow Pass, UI Pass, Post-process Pass
75
+ print(f"[Renderer] Created {len(self.passes)} render passes")
76
+
77
+ def begin_frame(self):
78
+ """Начало кадра"""
79
+ self.draw_calls = 0
80
+ self.triangles = 0
81
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
82
+ glClearColor(*self.clear_color)
83
+
84
+ def render_scene(self, scene):
85
+ """Рендерит сцену"""
86
+ if not scene:
87
+ return
88
+
89
+ camera = scene.main_camera
90
+ if not camera:
91
+ return
92
+
93
+ # Обновляем матрицы
94
+ projection = camera.get_projection_matrix(self.width / self.height)
95
+ view = camera.get_view_matrix()
96
+ view_pos = camera.position
97
+
98
+ for render_pass in self.passes:
99
+ if not render_pass.enabled:
100
+ continue
101
+
102
+ render_pass.begin()
103
+ shader = render_pass.shader
104
+
105
+ # Устанавливаем общие uniform-ы
106
+ shader.set_mat4("projection", projection)
107
+ shader.set_mat4("view", view)
108
+ shader.set_vec3("viewPos", view_pos)
109
+
110
+ # Рендерим все объекты
111
+ for entity in scene.entities:
112
+ if not entity.enabled:
113
+ continue
114
+
115
+ mesh_renderer = entity.get_component("MeshRenderer")
116
+ if mesh_renderer and mesh_renderer.enabled and mesh_renderer.visible:
117
+ mesh_renderer.render(view, projection)
118
+ self.draw_calls += 1
119
+
120
+ render_pass.end()
121
+
122
+ def end_frame(self):
123
+ """Конец кадра"""
124
+ pass
125
+
126
+ def resize(self, width: int, height: int):
127
+ """Обработчик изменения размера окна"""
128
+ self.width = width
129
+ self.height = height
130
+ glViewport(0, 0, width, height)
131
+ print(f"[Renderer] Resized to {width}x{height}")
132
+
133
+ def cleanup(self):
134
+ """Очистка ресурсов"""
135
+ print("[Renderer] Cleanup complete")
@@ -0,0 +1,61 @@
1
+ """Загрузка и компиляция шейдеров"""
2
+ from OpenGL.GL import *
3
+ from OpenGL.GL.shaders import compileProgram, compileShader
4
+ import numpy as np
5
+
6
+
7
+ class Shader:
8
+ _cache = {}
9
+
10
+ def __init__(self, vertex_source: str, fragment_source: str):
11
+ self.program = compileProgram(
12
+ compileShader(vertex_source, GL_VERTEX_SHADER),
13
+ compileShader(fragment_source, GL_FRAGMENT_SHADER)
14
+ )
15
+ self._uniform_locations = {}
16
+
17
+ @classmethod
18
+ def from_files(cls, vertex_path: str, fragment_path: str) -> 'Shader':
19
+ cache_key = (vertex_path, fragment_path)
20
+ if cache_key in cls._cache:
21
+ return cls._cache[cache_key]
22
+
23
+ with open(vertex_path, 'r') as f:
24
+ vert = f.read()
25
+ with open(fragment_path, 'r') as f:
26
+ frag = f.read()
27
+
28
+ shader = cls(vert, frag)
29
+ cls._cache[cache_key] = shader
30
+ return shader
31
+
32
+ def use(self):
33
+ glUseProgram(self.program)
34
+
35
+ def _get_location(self, name: str):
36
+ if name not in self._uniform_locations:
37
+ self._uniform_locations[name] = glGetUniformLocation(self.program, name)
38
+ return self._uniform_locations[name]
39
+
40
+ def set_mat4(self, name: str, matrix: np.ndarray):
41
+ loc = self._get_location(name)
42
+ glUniformMatrix4fv(loc, 1, GL_TRUE, matrix)
43
+
44
+ def set_vec3(self, name: str, vec):
45
+ loc = self._get_location(name)
46
+ glUniform3fv(loc, 1, vec)
47
+
48
+ def set_vec4(self, name: str, vec):
49
+ loc = self._get_location(name)
50
+ glUniform4fv(loc, 1, vec)
51
+
52
+ def set_float(self, name: str, value: float):
53
+ loc = self._get_location(name)
54
+ glUniform1f(loc, value)
55
+
56
+ def set_int(self, name: str, value: int):
57
+ loc = self._get_location(name)
58
+ glUniform1i(loc, value)
59
+
60
+ def set_bool(self, name: str, value: bool):
61
+ self.set_int(name, 1 if value else 0)
@@ -0,0 +1,157 @@
1
+ """Библиотека встроенных шейдеров"""
2
+ from aether.graphics.shader import Shader
3
+
4
+
5
+ class ShaderLibrary:
6
+ """Хранит и кэширует встроенные шейдеры"""
7
+
8
+ # PBR шейдер (Physically Based Rendering)
9
+ PBR_VERTEX = """
10
+ #version 460 core
11
+ layout(location = 0) in vec3 aPos;
12
+ layout(location = 1) in vec3 aNormal;
13
+ layout(location = 2) in vec2 aTexCoord;
14
+
15
+ out vec3 FragPos;
16
+ out vec3 Normal;
17
+ out vec2 TexCoord;
18
+
19
+ uniform mat4 model;
20
+ uniform mat4 view;
21
+ uniform mat4 projection;
22
+
23
+ void main() {
24
+ FragPos = vec3(model * vec4(aPos, 1.0));
25
+ Normal = mat3(transpose(inverse(model))) * aNormal;
26
+ TexCoord = aTexCoord;
27
+
28
+ gl_Position = projection * view * vec4(FragPos, 1.0);
29
+ }
30
+ """
31
+
32
+ PBR_FRAGMENT = """
33
+ #version 460 core
34
+ in vec3 FragPos;
35
+ in vec3 Normal;
36
+ in vec2 TexCoord;
37
+
38
+ out vec4 FragColor;
39
+
40
+ uniform vec3 viewPos;
41
+ uniform vec3 albedo;
42
+ uniform float metallic;
43
+ uniform float roughness;
44
+ uniform float ao;
45
+
46
+ // Источники света
47
+ struct DirectionalLight {
48
+ vec3 direction;
49
+ vec3 color;
50
+ float intensity;
51
+ };
52
+
53
+ uniform DirectionalLight sunLight;
54
+
55
+ const float PI = 3.14159265359;
56
+
57
+ float DistributionGGX(vec3 N, vec3 H, float roughness) {
58
+ float a = roughness * roughness;
59
+ float a2 = a * a;
60
+ float NdotH = max(dot(N, H), 0.0);
61
+ float NdotH2 = NdotH * NdotH;
62
+
63
+ float nom = a2;
64
+ float denom = (NdotH2 * (a2 - 1.0) + 1.0);
65
+ denom = PI * denom * denom;
66
+
67
+ return nom / denom;
68
+ }
69
+
70
+ float GeometrySchlickGGX(float NdotV, float roughness) {
71
+ float r = roughness + 1.0;
72
+ float k = (r * r) / 8.0;
73
+
74
+ float nom = NdotV;
75
+ float denom = NdotV * (1.0 - k) + k;
76
+
77
+ return nom / denom;
78
+ }
79
+
80
+ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
81
+ float NdotV = max(dot(N, V), 0.0);
82
+ float NdotL = max(dot(N, L), 0.0);
83
+ float ggx2 = GeometrySchlickGGX(NdotV, roughness);
84
+ float ggx1 = GeometrySchlickGGX(NdotL, roughness);
85
+
86
+ return ggx1 * ggx2;
87
+ }
88
+
89
+ vec3 FresnelSchlick(float cosTheta, vec3 F0) {
90
+ return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
91
+ }
92
+
93
+ void main() {
94
+ vec3 N = normalize(Normal);
95
+ vec3 V = normalize(viewPos - FragPos);
96
+
97
+ vec3 F0 = vec3(0.04);
98
+ F0 = mix(F0, albedo, metallic);
99
+
100
+ // Освещение от солнца
101
+ vec3 L = normalize(-sunLight.direction);
102
+ vec3 H = normalize(V + L);
103
+
104
+ // Радианс источника
105
+ vec3 radiance = sunLight.color * sunLight.intensity;
106
+
107
+ // Cook-Torrance BRDF
108
+ float NDF = DistributionGGX(N, H, roughness);
109
+ float G = GeometrySmith(N, V, L, roughness);
110
+ vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
111
+
112
+ vec3 kS = F;
113
+ vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
114
+
115
+ vec3 numerator = NDF * G * F;
116
+ float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
117
+ vec3 specular = numerator / denominator;
118
+
119
+ float NdotL = max(dot(N, L), 0.0);
120
+ vec3 Lo = (kD * albedo / PI + specular) * radiance * NdotL;
121
+
122
+ // Ambient
123
+ vec3 ambient = vec3(0.03) * albedo * ao;
124
+ vec3 color = ambient + Lo;
125
+
126
+ // HDR tonemapping
127
+ color = color / (color + vec3(1.0));
128
+
129
+ // Gamma correction
130
+ color = pow(color, vec3(1.0/2.2));
131
+
132
+ FragColor = vec4(color, 1.0);
133
+ }
134
+ """
135
+
136
+ _instance = None
137
+ _shaders = {}
138
+
139
+ def __new__(cls):
140
+ if cls._instance is None:
141
+ cls._instance = super().__new__(cls)
142
+ return cls._instance
143
+
144
+ def get_pbr_shader(self) -> Shader:
145
+ """Возвращает PBR шейдер"""
146
+ if 'pbr' not in self._shaders:
147
+ self._shaders['pbr'] = Shader(self.PBR_VERTEX, self.PBR_FRAGMENT)
148
+ return self._shaders['pbr']
149
+
150
+ def get_shader(self, name: str) -> Shader:
151
+ """Получает шейдер по имени"""
152
+ return self._shaders.get(name)
153
+
154
+ def preload_all(self):
155
+ """Предзагружает все встроенные шейдеры"""
156
+ self.get_pbr_shader()
157
+ print("[ShaderLibrary] All shaders preloaded")