Files
render/obj_viewer_shader.cpp
2025-11-19 20:11:18 +05:00

532 lines
18 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
}