Третья лабораторная работа (lab3) расширяет функциональность второй (lab2), внедряя реалистичные тени с использованием теневых карт и шейдеров. Основные улучшения включают:
- Тени: Добавление реалистичных теней объектов в сцене.
- Теневые Карты (Shadow Maps): Использование буфера глубины для хранения информации о глубине от источника света.
- Шейдеры: Создание и использование вершинных и фрагментных шейдеров для рендеринга сцены с тенями.
Этот гайд фокусируется на нововведениях и обновлениях, основываясь на предыдущих лабораторных работах.
Тени играют ключевую роль в повышении реализма сцены, показывая взаимное расположение объектов и источников света. Основные типы теней:
- Реалистичные (Hard) Тени: Четкие границы теней.
- Мягкие (Soft) Тени: Размытые границы, имитирующие реальные источники света.
Теневые карты — это популярный метод создания теней в реальном времени. Процесс включает два прохода рендеринга:
-
Depth Pass (Проход глубины):
- Рендеринг сцены с точки зрения источника света.
- Запись глубины каждого фрагмента в текстуру глубины (теневую карту).
-
Render Pass (Основной проход):
- Рендеринг сцены с точки зрения камеры.
- Для каждого фрагмента проверяется, находится ли он в тени, сравнивая его глубину с глубиной, записанной в теневую карту.
Преимущества теневых карт:
- Относительная простота реализации.
- Поддержка динамических сцен и анимаций.
Недостатки:
- Возможны артефакты, такие как зебра-эффект (aliasing).
- Ограниченная точность из-за разрешения текстуры глубины.
Шейдеры — это программы, выполняемые на GPU, которые управляют процессом рендеринга графических объектов. Основные типы шейдеров:
- Вершинный шейдер (Vertex Shader): Обрабатывает вершины объектов.
- Фрагментный шейдер (Fragment Shader): Определяет цвет пикселей.
В lab3 используются шейдеры с поддержкой теней, которые учитывают информацию из теневых карт при расчете освещения.
Вторая лабораторная работа (lab2) фокусировалась на:
- Материалах: Определение свойств поверхностей объектов (цвет, блеск, текстуры).
- Освещении: Добавление источников света и управление их параметрами.
- Текстурировании: Применение текстур к объектам.
- Нормалях: Добавление нормальных векторов для корректного освещения.
Третья лабораторная работа (lab3) добавляет следующие концептуальные элементы:
- Тени: Реализация теней для объектов.
- Теневые Карты: Использование буфера глубины для создания теней.
- Шейдеры для Теней: Создание специализированных шейдеров для обработки теней.
- Множественные Проходы Рендеринга: Введение Depth Pass и Render Pass для создания теней.
Вершинный Шейдер (depth.vert):
- Преобразует вершины сцены в пространство источника света.
- Передает глубину фрагментов в текстуру глубины.
Фрагментный Шейдер (depth.frag):
- Вычисляет глубину каждого фрагмента и записывает его в теневую карту.
Вершинный Шейдер для Основного Прохода (shading.vert):
- Преобразует вершины сцены в пространство камеры и источника света.
- Передает необходимые данные в фрагментный шейдер.
Фрагментный Шейдер для Основного Прохода (shading.frag):
- Использует теневую карту для определения, находится ли фрагмент в тени.
- Вычисляет финальный цвет фрагмента с учетом освещения и теней.
Пример Фрагментного Шейдера для Теней (shading.frag):
#version 330 core
out vec4 FragColor;
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;
in vec4 FragPosLightSpace;
uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;
uniform vec3 viewPos;
// Структура для направленного света
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
// Функция для расчета теней
float ShadowCalculation(vec4 fragPosLightSpace)
{
// Преобразование из пространств NDC
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// Преобразование в [0,1] диапазон
projCoords = projCoords * 0.5 + 0.5;
// Получение глубины из карты теней
float closestDepth = texture(shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
// Порог для смещения (Bias) для предотвращения артефактов
float bias = max(0.05 * (1.0 - dot(Normal, -dirLight.direction)), 0.005);
// Проверка, находится ли текущий фрагмент в тени
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
// Если фрагмент за пределами карты теней, он не в тени
if(projCoords.z > 1.0)
shadow = 0.0;
return shadow;
}
void main()
{
// Получение текстуры
vec3 color = texture(diffuseTexture, TexCoords).rgb;
// Нормализация нормали
vec3 norm = normalize(Normal);
// Направление света
vec3 lightDir = normalize(-dirLight.direction);
// Расчет диффузного компонента
float diff = max(dot(norm, lightDir), 0.0);
// Расчет зеркального компонента
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
// Расчет тени
float shadow = ShadowCalculation(FragPosLightSpace);
// Итоговое освещение
vec3 ambient = dirLight.ambient * color;
vec3 diffuse = dirLight.diffuse * diff * color;
vec3 specular = dirLight.specular * spec;
// Применение тени
vec3 lighting = ambient + (1.0 - shadow) * (diffuse + specular);
FragColor = vec4(lighting, 1.0);
}Класс DepthMap (materials/depth_map.py):
- Создает и управляет буфером глубины (теневой картой).
- Инициализирует Framebuffer Object (FBO) для рендеринга глубины.
- Предоставляет методы для привязки карты глубины для записи и чтения.
Пример Инициализации Теневой Карты:
class DepthMap:
def __init__(self, width=1024, height=1024):
self.width = width
self.height = height
self.FBO = glGenFramebuffers(1)
# Создание текстуры для карты глубины
self.depth_map = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, self.depth_map)
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, self.width, self.height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, None)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
border_color = [1.0, 1.0, 1.0, 1.0]
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color)
# Привязка текстуры к FBO
glBindFramebuffer(GL_FRAMEBUFFER, self.FBO)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, self.depth_map, 0)
glDrawBuffer(GL_NONE)
glReadBuffer(GL_NONE)
glBindFramebuffer(GL_FRAMEBUFFER, 0)Добавление Метода render_depth_map:
- Выполняет Depth Pass: рендеринг сцены с точки зрения источника света для создания теневой карты.
- Настраивает матрицу пространства света (
lightSpaceMatrix).
Добавление Метода render_scene:
- Выполняет Render Pass: основной рендеринг сцены с использованием теневой карты для определения теней.
- Передает параметры света и камеры в шейдеры.
Пример Реализации Метода render_depth_map:
def render_depth_map(self, depth_shader: Shader):
"""Рендеринг сцены для создания карты глубины."""
self.depth_map.bind_for_writing()
glClear(GL_DEPTH_BUFFER_BIT)
depth_shader.use()
if not self.lights:
print("No lights in the scene for depth map.")
return
# Предполагаем, что у нас один направленный свет
light = self.lights[0]
# Настраиваем lightSpaceMatrix на основе направления света.
light_projection = glm.ortho(-10.0, 10.0, -10.0, 10.0, 1.0, 20.0)
light_dir = glm.normalize(glm.vec3(*light.direction))
light_position = glm.vec3(-light_dir.x * 10.0, -light_dir.y * 10.0, -light_dir.z * 10.0)
light_view = glm.lookAt(light_position, glm.vec3(0.0, 0.0, 0.0), glm.vec3(0.0, 1.0, 0.0))
light_space_matrix = light_projection * light_view
depth_shader.set_mat4("lightSpaceMatrix", light_space_matrix)
# Рендерим все объекты
for obj in self.objects:
obj.render(depth_shader)
glBindFramebuffer(GL_FRAMEBUFFER, 0)Добавление Проходов Рендеринга:
- Depth Pass: Вызов
render_depth_mapдля создания теневой карты. - Render Pass: Вызов
render_sceneдля основного рендеринга с тенями.
Изменение Метода render:
def render(self):
if not self.scene or not self.shader or not self.depth_shader:
return
# Обновляем анимации
self.scene.update_animations()
# Рисуем оси координат и сетку
self.scene.draw_grid()
self.scene.draw_axes()
# Depth Pass
self.scene.render_depth_map(self.depth_shader)
# Render Pass
self.shader.use()
self.scene.render_scene(self.shader)
glutSwapBuffers()Управление Шейдерами:
- Загрузка, компиляция и связывание вершинных и фрагментных шейдеров.
- Методы для установки uniform-переменных (матрицы, цвета, текстур и т.д.).
Пример Инициализации Шейдера:
class Shader:
def __init__(self, vertex_source_path: str, fragment_source_path: str):
# Чтение исходников шейдеров
with open(vertex_source_path, 'r') as file:
vertex_source = file.read()
with open(fragment_source_path, 'r') as file:
fragment_source = file.read()
# Создание и компиляция вершинного шейдера
vertex_shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
gl.glShaderSource(vertex_shader, vertex_source)
gl.glCompileShader(vertex_shader)
self._check_compile_errors(vertex_shader, "VERTEX")
# Создание и компиляция фрагментного шейдера
fragment_shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
gl.glShaderSource(fragment_shader, fragment_source)
gl.glCompileShader(fragment_shader)
self._check_compile_errors(fragment_shader, "FRAGMENT")
# Создание шейдерной программы и связывание шейдеров
self._program = gl.glCreateProgram()
gl.glAttachShader(self._program, vertex_shader)
gl.glAttachShader(self._program, fragment_shader)
gl.glLinkProgram(self._program)
self._check_compile_errors(self._program, "PROGRAM")
# Удаление шейдеров после связывания
gl.glDeleteShader(vertex_shader)
gl.glDeleteShader(fragment_shader)
def use(self):
gl.glUseProgram(self._program)
# Методы для установки uniform-переменных...Рендеринг с Шейдерами:
- Каждый объект вызывает метод
render, который устанавливает матрицу модели, применяет материал и вызывает методdraw_meshс текущим шейдером.
Пример Реализации Метода render в Shape:
def render(self, shader: Shader):
"""Отрисовка фигуры с применением трансформаций и материала."""
# Создаем матрицу модели
model = glm.mat4(1.0)
model = glm.translate(model, glm.vec3(*self.position))
model = glm.rotate(model, glm.radians(self.rotation[0]), glm.vec3(1.0, 0.0, 0.0))
model = glm.rotate(model, glm.radians(self.rotation[1]), glm.vec3(0.0, 1.0, 0.0))
model = glm.rotate(model, glm.radians(self.rotation[2]), glm.vec3(0.0, 0.0, 1.0))
model = glm.scale(model, glm.vec3(self.scale))
shader.set_mat4("model", model)
# Применяем материал
self.material.apply(shader)
# Отрисовка геометрии
self.draw_mesh(shader)
# Очистка материала
self.material.cleanup(shader)Обновления в Подклассах:
- Подклассы, такие как
Cube,Sphere,Teapot, и другие, реализуют методdraw_mesh, который выполняет рендеринг геометрии с использованием текущего шейдера.
-
Теневые Карты:
- Введение класса
DepthMapдля управления буфером глубины. - Реализация Depth Pass и Render Pass в классе
SceneиRenderWindow.
- Введение класса
-
Шейдеры с Поддержкой Теней:
- Создание и использование специализированных шейдеров (
depth.vert,depth.frag,shading.vert,shading.frag).
- Создание и использование специализированных шейдеров (
-
Изменение Класса
Light:- Введение класса
DirectionalLightс поддержкой направления и параметров освещения. - Добавление методов для передачи параметров света в шейдеры.
- Введение класса
-
Обновление Класса
Scene:- Добавление методов
render_depth_mapиrender_sceneдля управления проходами рендеринга. - Обновление матриц для пространства света (
lightSpaceMatrix).
- Добавление методов
-
Рендеринг Объектов:
- Обновление методов рендеринга объектов для использования шейдеров и теневых карт.
-
Анимации:
- Добавление новых типов анимаций, таких как
DirectionalLightRotationAnimation, для динамического изменения направления света и создания движущихся теней.
- Добавление новых типов анимаций, таких как
Третья лабораторная работа (lab3) значительно расширяет функциональность предыдущих, внедряя реалистичные тени с использованием теневых карт и шейдеров. Это позволяет создавать более глубоко проработанные и визуально привлекательные 3D-сцены.
Ключевые моменты:
- Теневые Карты: Позволяют создавать реалистичные тени путем записи глубины сцены с точки зрения источника света.
- Шейдеры: Обеспечивают гибкость и контроль над процессом рендеринга, позволяя учитывать тени и освещение.
- Множественные Проходы Рендеринга: Позволяют разделить процесс рендеринга на Depth Pass и Render Pass для эффективного создания теней.
- Динамические Тени: Анимации света позволяют создавать изменяющиеся тени, добавляя реализма сцене.
Рекомендации для дальнейшего изучения:
- Улучшение Качества Теней: Реализация техник PCF (Percentage Closer Filtering) для смягчения теней.
- Множественные Источники Света: Поддержка нескольких источников света и их теней.
- Каскадные Теневые Карты: Для улучшения качества теней в различных частях сцены.
- Использование Шейдеров GLSL: Переход на более сложные шейдеры для расширения возможностей рендеринга.
Этот гайд предоставляет основы для понимания и дальнейшего развития проекта по теням и теневым картам в OpenGL на Python.