532 lines
18 KiB
C++
532 lines
18 KiB
C++
#include <iostream>
|
||
#include <fstream>
|
||
#include <vector>
|
||
#include <string>
|
||
#include <sstream>
|
||
#include <cmath>
|
||
#include <GL/glew.h>
|
||
#include <GL/glut.h>
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#include <glm/gtc/type_ptr.hpp>
|
||
|
||
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<int> vertices;
|
||
std::vector<int> textures;
|
||
std::vector<int> normals;
|
||
};
|
||
|
||
class OBJModel {
|
||
public:
|
||
std::vector<Vec3> vertices;
|
||
std::vector<Vec3> normals;
|
||
std::vector<Vec3> texcoords;
|
||
std::vector<Face> faces;
|
||
bool loaded;
|
||
|
||
GLuint VAO, VBO, NBO;
|
||
std::vector<float> vertexData;
|
||
std::vector<float> 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<size_t> 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<GLuint> shaderPrograms;
|
||
std::vector<std::string> 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<ShaderPair> 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;
|
||
}
|