From ec9e591040943a58e4f7defbe071d16d869d5d95 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 19 Nov 2025 20:11:18 +0500 Subject: [PATCH] start --- README_WINDOWS.md | 182 ++++++++++++++ fragment_shader.glsl | 34 +++ model.obj | 40 +++ normal_fragment.glsl | 13 + obj_viewer_shader | Bin 0 -> 144000 bytes obj_viewer_shader.cpp | 531 ++++++++++++++++++++++++++++++++++++++++ sky.frag | 45 ++++ sky_vertex.glsl | 12 + toon_fragment.glsl | 44 ++++ vertex_shader.glsl | 19 ++ wireframe_fragment.glsl | 23 ++ 11 files changed, 943 insertions(+) create mode 100644 README_WINDOWS.md create mode 100644 fragment_shader.glsl create mode 100644 model.obj create mode 100644 normal_fragment.glsl create mode 100755 obj_viewer_shader create mode 100644 obj_viewer_shader.cpp create mode 100644 sky.frag create mode 100644 sky_vertex.glsl create mode 100644 toon_fragment.glsl create mode 100644 vertex_shader.glsl create mode 100644 wireframe_fragment.glsl diff --git a/README_WINDOWS.md b/README_WINDOWS.md new file mode 100644 index 0000000..d1fc2bc --- /dev/null +++ b/README_WINDOWS.md @@ -0,0 +1,182 @@ +# OBJ Viewer с Шейдерами + +Программа для просмотра 3D моделей в формате OBJ с поддержкой различных шейдеров и интерактивного управления. + +## Возможности + +- Загрузка и отображение 3D моделей в формате OBJ +- 5 встроенных шейдеров с переключением в реальном времени +- Интерактивное управление камерой (вращение, масштабирование) +- Визуализация неба с настраиваемыми параметрами +- Каркасный режим (wireframe) +- Вывод цветовых значений в консоль + +## Шейдеры + +1. **Phong** - классическое освещение с ambient, diffuse и specular компонентами +2. **Toon** - мультяшный стиль с дискретными уровнями освещения +3. **Normal** - визуализация нормалей как RGB цветов +4. **Wireframe** - зеленоватый каркасный стиль +5. **Sky** - визуализация неба с параметрами высоты, угла солнца и интенсивности + +## Установка для Windows + +### Требования + +- MinGW-w64 или Visual Studio +- GLEW +- GLUT (freeglut) +- GLM + +### Установка зависимостей (MinGW) + +1. Скачайте и установите [MinGW-w64](https://www.mingw-w64.org/) + +2. Скачайте библиотеки: + - [GLEW](http://glew.sourceforge.net/) - OpenGL Extension Wrangler + - [freeglut](http://freeglut.sourceforge.net/) - GLUT для Windows + - [GLM](https://github.com/g-truc/glm) - OpenGL Mathematics (header-only) + +3. Распакуйте библиотеки и скопируйте файлы: + ``` + GLEW: + - glew32.dll -> C:\Windows\System32\ + - glew32.lib -> C:\mingw64\lib\ + - include\GL\* -> C:\mingw64\include\GL\ + + freeglut: + - freeglut.dll -> C:\Windows\System32\ + - freeglut.lib -> C:\mingw64\lib\ + - include\GL\* -> C:\mingw64\include\GL\ + + GLM: + - glm\* -> C:\mingw64\include\glm\ + ``` + +### Компиляция + +Откройте командную строку (cmd) в папке с проектом и выполните: + +```bash +g++ obj_viewer_shader.cpp -o obj_viewer_shader.exe -lopengl32 -lglu32 -lfreeglut -lglew32 -std=c++11 +``` + +Или используйте MinGW-w64: + +```bash +g++ obj_viewer_shader.cpp -o obj_viewer_shader.exe -lglu32 -lglew32 -lfreeglut -lopengl32 -std=c++11 +``` + +### Запуск + +```bash +obj_viewer_shader.exe model.obj +``` + +Или просто запустите без параметров - программа попросит ввести путь к файлу: + +```bash +obj_viewer_shader.exe +``` + +## Управление + +### Мышь +- **Левая кнопка** - вращение модели +- **Правая кнопка** - приближение/отдаление + +### Клавиатура + +#### Переключение шейдеров +- **1** - Phong (классическое освещение) +- **2** - Toon (мультяшный стиль) +- **3** - Normal (нормали) +- **4** - Wireframe +- **5** - Sky (небо) + +#### Параметры неба (для шейдера Sky) +- **h/H** - уменьшить/увеличить высоту (±500м) +- **e/E** - уменьшить/увеличить угол солнца (±0.05 рад) +- **i/I** - уменьшить/увеличить интенсивность света (±0.05) +- **P** - вывести текущий цвет неба в консоль + +#### Прочее +- **W** - включить/выключить каркасный режим +- **ESC** - выход из программы + +## Структура проекта + +``` +project/ +├── obj_viewer_shader.cpp # Основной файл программы +├── vertex_shader.glsl # Вершинный шейдер (общий) +├── fragment_shader.glsl # Phong шейдер +├── toon_fragment.glsl # Toon шейдер +├── normal_fragment.glsl # Normal шейдер +├── wireframe_fragment.glsl # Wireframe шейдер +├── sky_vertex.glsl # Вершинный шейдер для неба +├── sky.frag # Фрагментный шейдер неба +└── model.obj # Ваша 3D модель +``` + +## Формат OBJ + +Программа поддерживает стандартные OBJ файлы с: +- Вершинами (v) +- Нормалями (vn) +- Текстурными координатами (vt) +- Гранями (f) - треугольники и квады + +## Пример вывода цвета неба + +``` +=== ЦВЕТ НЕБА === +Параметры: + height: 5000 + sunElevation: 0.5 + sunIntensity: 0.8 +Результат (RGB): + 0.51 0.64 0.82 +``` + +## Создание своих шейдеров + +Чтобы добавить собственный шейдер: + +1. Создайте файл фрагментного шейдера (например, `my_shader.glsl`) +2. Откройте `obj_viewer_shader.cpp` +3. Найдите функцию `init()` и добавьте в массив `shaders`: + ```cpp + {"Мой шейдер", "vertex_shader.glsl", "my_shader.glsl"} + ``` +4. Перекомпилируйте программу + +## Troubleshooting + +### Ошибка "не удалось найти DLL" +Убедитесь, что `glew32.dll` и `freeglut.dll` находятся в `C:\Windows\System32\` или в папке с программой. + +### Ошибка компиляции "cannot find -lglew32" +Проверьте, что библиотеки установлены в `C:\mingw64\lib\` + +### Черный экран +Проверьте, что файлы шейдеров (`.glsl`, `.frag`) находятся в той же папке, что и исполняемый файл. + +### Модель не загружается +- Проверьте путь к OBJ файлу +- Убедитесь, что файл в правильном формате OBJ +- Используйте относительные пути или полные пути с двойными слешами: `C:\\path\\to\\model.obj` + +## Системные требования + +- Windows 7/8/10/11 +- OpenGL 3.3+ +- Видеокарта с поддержкой шейдеров GLSL 330 + +## Лицензия + +Свободное использование в образовательных целях. + +## Автор + +Создано для работы с 3D моделями и изучения шейдеров GLSL. diff --git a/fragment_shader.glsl b/fragment_shader.glsl new file mode 100644 index 0000000..a0d65cf --- /dev/null +++ b/fragment_shader.glsl @@ -0,0 +1,34 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; + +uniform vec3 lightPos; +uniform vec3 viewPos; +uniform vec3 lightColor; +uniform vec3 objectColor; + +void main() +{ + // Ambient + float ambientStrength = 0.3; + vec3 ambient = ambientStrength * lightColor; + + // Diffuse + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(lightPos - FragPos); + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * lightColor; + + // Specular + float specularStrength = 0.5; + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); + vec3 specular = specularStrength * spec * lightColor; + + vec3 result = (ambient + diffuse + specular) * objectColor; + FragColor = vec4(result, 1.0); +} diff --git a/model.obj b/model.obj new file mode 100644 index 0000000..a1132cc --- /dev/null +++ b/model.obj @@ -0,0 +1,40 @@ +# Blender 4.4.3 +# www.blender.org +mtllib Без имени.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vn -0.0000 1.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vt 0.625000 0.500000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.125000 0.500000 +vt 0.375000 0.500000 +vt 0.125000 0.750000 +s 0 +usemtl Material +f 1/1/1 5/2/1 7/3/1 3/4/1 +f 4/5/2 3/4/2 7/6/2 8/7/2 +f 8/8/3 7/9/3 5/10/3 6/11/3 +f 6/12/4 2/13/4 4/5/4 8/14/4 +f 2/13/5 1/1/5 3/4/5 4/5/5 +f 6/11/6 5/10/6 1/1/6 2/13/6 diff --git a/normal_fragment.glsl b/normal_fragment.glsl new file mode 100644 index 0000000..885b933 --- /dev/null +++ b/normal_fragment.glsl @@ -0,0 +1,13 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; + +void main() +{ + // Отображение нормалей как цветов + vec3 normalColor = normalize(Normal) * 0.5 + 0.5; + FragColor = vec4(normalColor, 1.0); +} diff --git a/obj_viewer_shader b/obj_viewer_shader new file mode 100755 index 0000000000000000000000000000000000000000..a57025b30350d5f9483ef5990dc7749ea3dac351 GIT binary patch literal 144000 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Vec3 { + float x, y, z; + Vec3(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {} +}; + +struct Face { + std::vector vertices; + std::vector textures; + std::vector normals; +}; + +class OBJModel { +public: + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector faces; + bool loaded; + + GLuint VAO, VBO, NBO; + std::vector vertexData; + std::vector normalData; + + OBJModel() : loaded(false), VAO(0), VBO(0), NBO(0) {} + + bool loadOBJ(const std::string& filename) { + std::ifstream file(filename); + if (!file.is_open()) { + std::cout << "Модель '" << filename << "' не найдена!" << std::endl; + return false; + } + + std::string line; + while (std::getline(file, line)) { + std::istringstream iss(line); + std::string prefix; + iss >> prefix; + + if (prefix == "v") { + float x, y, z; + iss >> x >> y >> z; + vertices.push_back(Vec3(x, y, z)); + } + else if (prefix == "vn") { + float x, y, z; + iss >> x >> y >> z; + normals.push_back(Vec3(x, y, z)); + } + else if (prefix == "vt") { + float u, v; + iss >> u >> v; + texcoords.push_back(Vec3(u, v, 0)); + } + else if (prefix == "f") { + Face face; + std::string vertex; + while (iss >> vertex) { + std::istringstream vss(vertex); + std::string index; + int idx = 0; + + while (std::getline(vss, index, '/')) { + if (!index.empty()) { + int i = std::stoi(index) - 1; + if (idx == 0) face.vertices.push_back(i); + else if (idx == 1) face.textures.push_back(i); + else if (idx == 2) face.normals.push_back(i); + } + idx++; + } + } + faces.push_back(face); + } + } + + file.close(); + loaded = true; + std::cout << "Модель загружена успешно!" << std::endl; + std::cout << "Вершин: " << vertices.size() << std::endl; + std::cout << "Граней: " << faces.size() << std::endl; + + setupBuffers(); + return true; + } + + void setupBuffers() { + // Преобразуем грани в массив вершин (триангуляция) + for (const auto& face : faces) { + // Для граней с 3+ вершинами делаем триангуляцию веером + for (size_t i = 1; i < face.vertices.size() - 1; ++i) { + // Первая вершина треугольника (общая для всех) + int v0 = face.vertices[0]; + int v1 = face.vertices[i]; + int v2 = face.vertices[i + 1]; + + // Добавляем вершины треугольника + for (int vIdx : {v0, v1, v2}) { + if (vIdx < vertices.size()) { + vertexData.push_back(vertices[vIdx].x); + vertexData.push_back(vertices[vIdx].y); + vertexData.push_back(vertices[vIdx].z); + } + } + + // Добавляем нормали для треугольника + std::vector indices = {0, i, i + 1}; + for (size_t j : indices) { + if (j < face.normals.size() && face.normals[j] < normals.size()) { + normalData.push_back(normals[face.normals[j]].x); + normalData.push_back(normals[face.normals[j]].y); + normalData.push_back(normals[face.normals[j]].z); + } else { + // Если нормалей нет, вычисляем плоскую нормаль + normalData.push_back(0.0f); + normalData.push_back(0.0f); + normalData.push_back(1.0f); + } + } + } + } + + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &NBO); + + glBindVertexArray(VAO); + + // Вершины + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), + vertexData.data(), GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + // Нормали + glBindBuffer(GL_ARRAY_BUFFER, NBO); + glBufferData(GL_ARRAY_BUFFER, normalData.size() * sizeof(float), + normalData.data(), GL_STATIC_DRAW); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + + glBindVertexArray(0); + } + + void render() { + if (!loaded || VAO == 0) return; + + glBindVertexArray(VAO); + glDrawArrays(GL_TRIANGLES, 0, vertexData.size() / 3); + glBindVertexArray(0); + } + + ~OBJModel() { + if (VAO) glDeleteVertexArrays(1, &VAO); + if (VBO) glDeleteBuffers(1, &VBO); + if (NBO) glDeleteBuffers(1, &NBO); + } +}; + +// Функция для загрузки шейдера +std::string loadShaderSource(const std::string& filepath) { + std::ifstream file(filepath); + if (!file.is_open()) { + std::cerr << "Не удалось открыть файл шейдера: " << filepath << std::endl; + return ""; + } + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +// Компиляция шейдера +GLuint compileShader(GLenum type, const std::string& source) { + GLuint shader = glCreateShader(type); + const char* src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char infoLog[512]; + glGetShaderInfoLog(shader, 512, nullptr, infoLog); + std::cerr << "Ошибка компиляции шейдера:\n" << infoLog << std::endl; + return 0; + } + return shader; +} + +// Создание шейдерной программы +GLuint createShaderProgram(const std::string& vertexPath, const std::string& fragmentPath) { + std::string vertexSource = loadShaderSource(vertexPath); + std::string fragmentSource = loadShaderSource(fragmentPath); + + if (vertexSource.empty() || fragmentSource.empty()) { + return 0; + } + + GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource); + GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource); + + if (!vertexShader || !fragmentShader) { + return 0; + } + + GLuint program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + + GLint success; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (!success) { + char infoLog[512]; + glGetProgramInfoLog(program, 512, nullptr, infoLog); + std::cerr << "Ошибка линковки шейдерной программы:\n" << infoLog << std::endl; + return 0; + } + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + return program; +} + +// Глобальные переменные +OBJModel model; +GLuint shaderProgram; +std::vector shaderPrograms; +std::vector shaderNames; +int currentShader = 0; +float rotationX = 0.0f; +float rotationY = 0.0f; +float zoom = 5.0f; +int lastMouseX = 0; +int lastMouseY = 0; +bool mouseLeftDown = false; +bool mouseRightDown = false; +bool showWireframe = false; + +// Параметры для шейдера неба +float skyHeight = 5000.0f; +float sunElevation = 0.5f; +float sunIntensity = 0.8f; + +void display() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Используем текущий шейдер + GLuint activeShader = shaderPrograms[currentShader]; + glUseProgram(activeShader); + + // Матрицы трансформации + glm::mat4 model_matrix = glm::mat4(1.0f); + model_matrix = glm::rotate(model_matrix, glm::radians(rotationX), glm::vec3(1.0f, 0.0f, 0.0f)); + model_matrix = glm::rotate(model_matrix, glm::radians(rotationY), glm::vec3(0.0f, 1.0f, 0.0f)); + + glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -zoom)); + glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f); + + // Передаем матрицы в шейдер + glUniformMatrix4fv(glGetUniformLocation(activeShader, "model"), 1, GL_FALSE, glm::value_ptr(model_matrix)); + glUniformMatrix4fv(glGetUniformLocation(activeShader, "view"), 1, GL_FALSE, glm::value_ptr(view)); + glUniformMatrix4fv(glGetUniformLocation(activeShader, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + + // Параметры освещения (не все шейдеры их используют, но это не проблема) + glm::vec3 lightPos(5.0f, 5.0f, 5.0f); + glm::vec3 viewPos(0.0f, 0.0f, zoom); + glm::vec3 lightColor(1.0f, 1.0f, 1.0f); + glm::vec3 objectColor(0.8f, 0.8f, 0.8f); + + glUniform3fv(glGetUniformLocation(activeShader, "lightPos"), 1, glm::value_ptr(lightPos)); + glUniform3fv(glGetUniformLocation(activeShader, "viewPos"), 1, glm::value_ptr(viewPos)); + glUniform3fv(glGetUniformLocation(activeShader, "lightColor"), 1, glm::value_ptr(lightColor)); + glUniform3fv(glGetUniformLocation(activeShader, "objectColor"), 1, glm::value_ptr(objectColor)); + + // Параметры для шейдера неба + glUniform1f(glGetUniformLocation(activeShader, "height"), skyHeight); + glUniform1f(glGetUniformLocation(activeShader, "sunElevation"), sunElevation); + glUniform1f(glGetUniformLocation(activeShader, "sunIntensity"), sunIntensity); + + // Wireframe режим + if (showWireframe) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } else { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + model.render(); + + glutSwapBuffers(); +} + +void reshape(int w, int h) { + glViewport(0, 0, w, h); +} + +void mouse(int button, int state, int x, int y) { + if (button == GLUT_LEFT_BUTTON) { + mouseLeftDown = (state == GLUT_DOWN); + lastMouseX = x; + lastMouseY = y; + } + else if (button == GLUT_RIGHT_BUTTON) { + mouseRightDown = (state == GLUT_DOWN); + lastMouseY = y; + } +} + +void mouseMotion(int x, int y) { + if (mouseLeftDown) { + rotationY += (x - lastMouseX) * 0.5f; + rotationX += (y - lastMouseY) * 0.5f; + lastMouseX = x; + lastMouseY = y; + glutPostRedisplay(); + } + else if (mouseRightDown) { + zoom += (y - lastMouseY) * 0.05f; + if (zoom < 1.0f) zoom = 1.0f; + lastMouseY = y; + glutPostRedisplay(); + } +} + +void printSkyColor() { + // Константы - цвета неба + glm::vec3 C_strato = glm::vec3(0x05, 0x0F, 0x1F) / 255.0f; + glm::vec3 C_sea = glm::vec3(0x87, 0xBD, 0xFF) / 255.0f; + glm::vec3 C_warm = glm::vec3(0xFF, 0xF2, 0xD9) / 255.0f; + glm::vec3 C_sunset = glm::vec3(0xFF, 0x8C, 0x4D) / 255.0f; + + // 1. Зависимость от высоты + float H = 8000.0f; + float t = glm::clamp(expf(-skyHeight / H), 0.0f, 1.0f); + glm::vec3 C_base = glm::mix(C_strato, C_sea, t); + + // 2. Параметры солнца + float d = glm::smoothstep(0.0f, 0.6f, sunElevation); + float s = glm::clamp(sunIntensity, 0.0f, 1.0f); + + // 3. Тёплая добавка + glm::vec3 C_tint = glm::mix(C_sunset, C_warm, d); + float alpha = 0.6f * s * (1.0f - d) * t; + + // 4. Итоговый цвет + glm::vec3 C_lit = C_base * (0.55f + 0.45f * s * d) + alpha * C_tint; + glm::vec3 C_out = glm::clamp(C_lit, 0.0f, 1.0f); + + std::cout << "\n=== ЦВЕТ НЕБА ===" << std::endl; + std::cout << "Параметры:" << std::endl; + std::cout << " height: " << skyHeight << std::endl; + std::cout << " sunElevation: " << sunElevation << std::endl; + std::cout << " sunIntensity: " << sunIntensity << std::endl; + std::cout << "Результат (RGB):" << std::endl; + printf(" %.2f %.2f %.2f\n", C_out.r, C_out.g, C_out.b); +} + +void keyboard(unsigned char key, int x, int y) { + bool needUpdate = false; + + if (key == 27) { // ESC + exit(0); + } + else if (key == '1' || key == '2' || key == '3' || key == '4' || key == '5') { + int shader = key - '1'; + if (shader < shaderPrograms.size()) { + currentShader = shader; + std::cout << "Активирован шейдер: " << shaderNames[currentShader] << std::endl; + if (currentShader == 4) { // Sky shader + printSkyColor(); + } + glutPostRedisplay(); + } + } + else if (key == 'w' || key == 'W') { + showWireframe = !showWireframe; + std::cout << "Wireframe: " << (showWireframe ? "ВКЛ" : "ВЫКЛ") << std::endl; + glutPostRedisplay(); + } + else if (key == 'p' || key == 'P') { + printSkyColor(); + } + // Управление высотой + else if (key == 'h') { + skyHeight -= 500.0f; + if (skyHeight < 0) skyHeight = 0; + needUpdate = true; + } + else if (key == 'H') { + skyHeight += 500.0f; + needUpdate = true; + } + // Управление углом солнца + else if (key == 'e') { + sunElevation -= 0.05f; + if (sunElevation < 0) sunElevation = 0; + needUpdate = true; + } + else if (key == 'E') { + sunElevation += 0.05f; + needUpdate = true; + } + // Управление интенсивностью + else if (key == 'i') { + sunIntensity -= 0.05f; + if (sunIntensity < 0) sunIntensity = 0; + needUpdate = true; + } + else if (key == 'I') { + sunIntensity += 0.05f; + if (sunIntensity > 1.0f) sunIntensity = 1.0f; + needUpdate = true; + } + + if (needUpdate) { + printSkyColor(); + glutPostRedisplay(); + } +} + +void init() { + glewExperimental = GL_TRUE; + GLenum err = glewInit(); + if (err != GLEW_OK) { + std::cerr << "Ошибка инициализации GLEW: " << glewGetErrorString(err) << std::endl; + exit(1); + } + + glEnable(GL_DEPTH_TEST); + glClearColor(0.1f, 0.1f, 0.15f, 1.0f); + + // Загрузка всех шейдеров + struct ShaderPair { + std::string name; + std::string vertex; + std::string fragment; + }; + + std::vector shaders = { + {"Phong (освещение)", "vertex_shader.glsl", "fragment_shader.glsl"}, + {"Toon (мультяшный)", "vertex_shader.glsl", "toon_fragment.glsl"}, + {"Normal (нормали)", "vertex_shader.glsl", "normal_fragment.glsl"}, + {"Wireframe Style", "vertex_shader.glsl", "wireframe_fragment.glsl"}, + {"Sky (небо)", "sky_vertex.glsl", "sky.frag"} + }; + + for (const auto& shader : shaders) { + GLuint program = createShaderProgram(shader.vertex, shader.fragment); + if (program) { + shaderPrograms.push_back(program); + shaderNames.push_back(shader.name); + std::cout << "✓ Загружен: " << shader.name << std::endl; + } else { + std::cout << "✗ Ошибка: " << shader.name << std::endl; + } + } + + if (shaderPrograms.empty()) { + std::cerr << "Не удалось загрузить ни один шейдер!" << std::endl; + exit(1); + } + + std::cout << "\nДоступно шейдеров: " << shaderPrograms.size() << std::endl; +} + +int main(int argc, char** argv) { + std::string filename; + + if (argc > 1) { + filename = argv[1]; + } else { + std::cout << "Введите путь к OBJ файлу: "; + std::getline(std::cin, filename); + } + + // Инициализация GLUT + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); + glutInitWindowSize(800, 600); + glutCreateWindow("OBJ Viewer с шейдерами - Используйте мышь для вращения"); + + init(); + + // Загрузка модели + if (!model.loadOBJ(filename)) { + std::cout << "Не удалось загрузить модель." << std::endl; + return 1; + } + + glutDisplayFunc(display); + glutReshapeFunc(reshape); + glutMouseFunc(mouse); + glutMotionFunc(mouseMotion); + glutKeyboardFunc(keyboard); + + std::cout << "\n=== УПРАВЛЕНИЕ ===" << std::endl; + std::cout << "Мышь:" << std::endl; + std::cout << " - Левая кнопка: вращение модели" << std::endl; + std::cout << " - Правая кнопка: приближение/отдаление" << std::endl; + std::cout << "\nШейдеры:" << std::endl; + std::cout << " - 1: Phong (классическое освещение)" << std::endl; + std::cout << " - 2: Toon (мультяшный)" << std::endl; + std::cout << " - 3: Normal (нормали)" << std::endl; + std::cout << " - 4: Wireframe" << std::endl; + std::cout << " - 5: Sky (небо)" << std::endl; + std::cout << "\nПараметры неба (шейдер Sky):" << std::endl; + std::cout << " - h/H: уменьшить/увеличить высоту" << std::endl; + std::cout << " - e/E: уменьшить/увеличить угол солнца" << std::endl; + std::cout << " - i/I: уменьшить/увеличить интенсивность" << std::endl; + std::cout << " - P: вывести цвет неба в консоль" << std::endl; + std::cout << "\nПрочее:" << std::endl; + std::cout << " - W: каркасный режим" << std::endl; + std::cout << " - ESC: выход" << std::endl; + std::cout << "\nТекущий шейдер: " << shaderNames[currentShader] << std::endl; + + glutMainLoop(); + return 0; +} diff --git a/sky.frag b/sky.frag new file mode 100644 index 0000000..354baad --- /dev/null +++ b/sky.frag @@ -0,0 +1,45 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; + +uniform float height; +uniform float sunElevation; +uniform float sunIntensity; + +void main() +{ + // Константы - цвета неба + vec3 C_strato = vec3(0x05, 0x0F, 0x1F) / 255.0; // #050F1F + vec3 C_sea = vec3(0x87, 0xBD, 0xFF) / 255.0; // #87BDFF + vec3 C_warm = vec3(0xFF, 0xF2, 0xD9) / 255.0; // #FFF2D9 + vec3 C_sunset = vec3(0xFF, 0x8C, 0x4D) / 255.0; // #FF8C4D + + // 1. Зависимость цвета от высоты наблюдателя + float H = 8000.0; + float t = clamp(exp(-height / H), 0.0, 1.0); + + // C_base - линейная интерполяция от C_strato до C_sea с параметром t + vec3 C_base = mix(C_strato, C_sea, t); + + // 2. Параметры положения Солнца + float d = smoothstep(0.0, 0.6, sunElevation); + float s = clamp(sunIntensity, 0.0, 1.0); + + // 3. Тёплая добавка при низком Солнце + // C_tint - линейная интерполяция от C_sunset до C_warm с параметром d + vec3 C_tint = mix(C_sunset, C_warm, d); + + // Коэффициент тёплой добавки + float alpha = 0.6 * s * (1.0 - d) * t; + + // 4. Итоговый цвет неба + vec3 C_lit = C_base * (0.55 + 0.45 * s * d) + alpha * C_tint; + + // Финальный цвет с ограничением + vec3 C_out = clamp(C_lit, 0.0, 1.0); + + FragColor = vec4(C_out, 1.0); +} diff --git a/sky_vertex.glsl b/sky_vertex.glsl new file mode 100644 index 0000000..bbd690b --- /dev/null +++ b/sky_vertex.glsl @@ -0,0 +1,12 @@ +#version 330 core + +layout(location = 0) in vec3 aPos; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(aPos, 1.0); +} diff --git a/toon_fragment.glsl b/toon_fragment.glsl new file mode 100644 index 0000000..10b10a5 --- /dev/null +++ b/toon_fragment.glsl @@ -0,0 +1,44 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; + +uniform vec3 lightPos; +uniform vec3 viewPos; + +void main() +{ + // Toon shader (мультяшный стиль) + vec3 lightColor = vec3(1.0, 1.0, 1.0); + vec3 objectColor = vec3(0.3, 0.6, 0.9); + + // Ambient + float ambientStrength = 0.3; + vec3 ambient = ambientStrength * lightColor; + + // Diffuse с дискретными уровнями + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(lightPos - FragPos); + float diff = max(dot(norm, lightDir), 0.0); + + // Квантуем освещение на 4 уровня + if (diff > 0.95) diff = 1.0; + else if (diff > 0.5) diff = 0.7; + else if (diff > 0.25) diff = 0.4; + else diff = 0.2; + + vec3 diffuse = diff * lightColor; + + // Specular с четким бликом + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); + + vec3 specular = vec3(0.0); + if (spec > 0.5) specular = vec3(1.0); // Четкий блик + + vec3 result = (ambient + diffuse + specular * 0.3) * objectColor; + FragColor = vec4(result, 1.0); +} diff --git a/vertex_shader.glsl b/vertex_shader.glsl new file mode 100644 index 0000000..42e1a00 --- /dev/null +++ b/vertex_shader.glsl @@ -0,0 +1,19 @@ +#version 330 core + +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aNormal; + +out vec3 FragPos; +out vec3 Normal; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + FragPos = vec3(model * vec4(aPos, 1.0)); + Normal = mat3(transpose(inverse(model))) * aNormal; + + gl_Position = projection * view * vec4(FragPos, 1.0); +} diff --git a/wireframe_fragment.glsl b/wireframe_fragment.glsl new file mode 100644 index 0000000..0b88ebe --- /dev/null +++ b/wireframe_fragment.glsl @@ -0,0 +1,23 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; + +uniform vec3 lightPos; +uniform vec3 viewPos; + +void main() +{ + // Wireframe стиль + vec3 lightColor = vec3(0.0, 1.0, 0.5); + vec3 objectColor = vec3(0.1, 0.1, 0.1); + + vec3 norm = normalize(Normal); + vec3 lightDir = normalize(lightPos - FragPos); + float diff = max(dot(norm, lightDir), 0.0); + + vec3 result = objectColor + lightColor * diff * 0.3; + FragColor = vec4(result, 1.0); +}