commit ec9e591040943a58e4f7defbe071d16d869d5d95 Author: root Date: Wed Nov 19 20:11:18 2025 +0500 start 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 0000000..a57025b Binary files /dev/null and b/obj_viewer_shader differ diff --git a/obj_viewer_shader.cpp b/obj_viewer_shader.cpp new file mode 100644 index 0000000..5df28ba --- /dev/null +++ b/obj_viewer_shader.cpp @@ -0,0 +1,531 @@ +#include +#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); +}