#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; }