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.
- aether/__init__.py +54 -0
- aether/audio/__init__.py +0 -0
- aether/audio/audio_manager.py +211 -0
- aether/audio/music.py +6 -0
- aether/audio/sound.py +6 -0
- aether/core/__init__.py +211 -0
- aether/core/achievement_system.py +304 -0
- aether/core/application.py +19 -0
- aether/core/config.py +75 -0
- aether/core/console.py +366 -0
- aether/core/engine.py +50 -0
- aether/core/event_bus.py +24 -0
- aether/core/input.py +58 -0
- aether/core/plugin_system.py +93 -0
- aether/core/save_system.py +409 -0
- aether/core/time_manager.py +25 -0
- aether/core/window.py +57 -0
- aether/graphics/__init__.py +0 -0
- aether/graphics/camera.py +69 -0
- aether/graphics/framebuffer.py +119 -0
- aether/graphics/light.py +32 -0
- aether/graphics/lod_system.py +170 -0
- aether/graphics/material.py +25 -0
- aether/graphics/mesh.py +83 -0
- aether/graphics/mesh_factory.py +67 -0
- aether/graphics/particle_system.py +268 -0
- aether/graphics/post_process.py +279 -0
- aether/graphics/renderer.py +135 -0
- aether/graphics/shader.py +61 -0
- aether/graphics/shader_library.py +157 -0
- aether/graphics/sky_atmosphere.py +309 -0
- aether/graphics/terrain.py +408 -0
- aether/graphics/texture.py +34 -0
- aether/graphics/water.py +432 -0
- aether/physics/__init__.py +0 -0
- aether/physics/bullet_backend.py +395 -0
- aether/physics/collider.py +22 -0
- aether/physics/physics_world.py +436 -0
- aether/physics/raycast.py +25 -0
- aether/physics/rigidbody.py +7 -0
- aether/resources/__init__.py +0 -0
- aether/resources/asset_database.py +76 -0
- aether/resources/resource_manager.py +147 -0
- aether/resources/serialization.py +114 -0
- aether/scene/__init__.py +0 -0
- aether/scene/component.py +368 -0
- aether/scene/entity.py +46 -0
- aether/scene/scene.py +33 -0
- aether/scene/scene_graph.py +252 -0
- aether/scene/scene_manager.py +191 -0
- aether/ui/__init__.py +0 -0
- aether/ui/button.py +297 -0
- aether/ui/canvas.py +11 -0
- aether/ui/text.py +286 -0
- aether/utils/__init__.py +0 -0
- aether/utils/logger.py +16 -0
- aether/utils/math_utils.py +31 -0
- aether/utils/object_pool.py +137 -0
- aether/utils/profiler.py +187 -0
- aether_engine-1.0.0.dist-info/METADATA +71 -0
- aether_engine-1.0.0.dist-info/RECORD +64 -0
- aether_engine-1.0.0.dist-info/WHEEL +5 -0
- aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
- 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")
|