Этот программный код был разработан начиная с идеи - давай напишем игру Арканоид с нуля. Два дня мытарств и код был готов что бы вполне порадовать меня достижением.
Преимущество в том, что нужно владеть лишь базовыми знаниями, достаточными для того, что бы хоть как-то ориентироваться во всём этом информационном потоке, сленгах, терминах, понятиях.
При том уровне навыков которые я накопил за всю свою жизнь, я бы такого кода ни когда бы не написал, я и не мечтал об этом. Но вот пришла Алиса и я теперь могу реализовать свои идеи в программировании. Кстати, ИИ здесь не Алиса, а всего скорее её друг и соратник - назовём его условно Макс. :)
// Блокируем автоматическое включение OpenGL из GLFW
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad/glad.h>
#define _USE_MATH_DEFINES
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
// Константы мира
const float WORLD_LEFT = -10.0f;
const float WORLD_RIGHT = 10.0f;
const float WORLD_BOTTOM = -7.5f;
const float WORLD_TOP = 7.5f;
const float MAX_SPEED = 4.0f;
const int MAX_PARTICLES = 100;
struct Ball {
glm::vec3 position;
float radius;
glm::vec3 velocity;
glm::vec4 color; // Цвет шара
float colorChangeTimer; // Таймер смены цвета
};
struct Paddle {
glm::vec3 position;
glm::vec2 size;
glm::vec2 velocity;
};
// ИЗМЕНЕНО: добавлена halfWidth и halfHeight
struct Brick {
glm::vec3 position;
bool active;
float halfWidth; // 0.5 * ширина кирпича
float halfHeight; // 0.5 * высота кирпича
};
struct Particle {
glm::vec3 position;
glm::vec3 velocity;
glm::vec4 color;
float lifetime;
bool active;
};
struct GameState {
GLFWwindow* window;
GLuint shaderProgram;
GLuint ballVAO, ballVBO;
GLuint paddleVAO, paddleVBO;
GLuint brickVAO, brickVBO;
std::vector<Brick> bricks;
Ball ball;
Paddle paddle;
std::vector<Particle> particles;
bool gameOver;
int score;
float time;
};
// Вспомогательная функция: расчёт коэффициента отклонения
float calculateBounceFactor(float ballPos, float objPos, float objWidth) {
float offset = ballPos - objPos;
return glm::clamp(offset / (objWidth / 2.0f), -1.0f, 1.0f);
}
// Создание искр
void emitParticles(const glm::vec3& pos, const glm::vec3& normal, std::vector<Particle>& particles) {
static int particleIndex = 0;
for (int i = 0; i < 5; ++i) {
Particle& p = particles[particleIndex];
p.active = true;
p.position = pos;
glm::vec3 dir = normal + glm::vec3(
(static_cast<float>(rand()) / RAND_MAX - 0.5f) * 0.3f,
(static_cast<float>(rand()) / RAND_MAX - 0.5f) * 0.3f,
0.0f
);
p.velocity = glm::normalize(dir) * 0.3f;
p.color = glm::vec4(1.0f, 0.8f, 0.2f, 1.0f); // Золотой
p.lifetime = 0.5f + static_cast<float>(rand()) / RAND_MAX * 0.3f;
particleIndex = (particleIndex + 1) % MAX_PARTICLES;
}
}
// Компиляция шейдера
GLuint compileShader(GLuint type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, NULL, infoLog);
std::cerr << "Shader compilation failed: " << infoLog << std::endl;
}
return shader;
}
// Настройка шейдеров
GLuint setupShaders() {
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 u_projection;
uniform vec3 u_position;
uniform vec4 u_color;
out vec4 v_color;
void main() {
v_color = u_color;
vec4 worldPos = u_projection * vec4(aPos + u_position, 1.0);
gl_Position = worldPos;
}
)";
const char* fragmentShaderSource = R"(
#version 330 core
in vec4 v_color;
out vec4 FragColor;
void main() {
FragColor = v_color;
}
)";
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
// Настройка VAO/VBO для мяча
void setupBall(GameState& state) {
std::vector<glm::vec3> vertices;
vertices.push_back({0.0f, 0.0f, 0.0f});
const int segments = 36;
for (int i = 0; i <= segments; ++i) {
float angle = 2.0f * M_PI * i / segments;
vertices.push_back({cos(angle) * state.ball.radius, sin(angle) * state.ball.radius, 0.0f});
}
glGenVertexArrays(1, &state.ballVAO);
glGenBuffers(1, &state.ballVBO);
glBindVertexArray(state.ballVAO);
glBindBuffer(GL_ARRAY_BUFFER, state.ballVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// Настройка VAO/VBO для биты
void setupPaddle(GameState& state) {
std::vector<glm::vec3> vertices = {
{-state.paddle.size.x/2, -state.paddle.size.y/2, 0.0f},
{ state.paddle.size.x/2, -state.paddle.size.y/2, 0.0f},
{ state.paddle.size.x/2, state.paddle.size.y/2, 0.0f},
{-state.paddle.size.x/2, state.paddle.size.y/2, 0.0f}
};
glGenVertexArrays(1, &state.paddleVAO);
glGenBuffers(1, &state.paddleVBO);
glBindVertexArray(state.paddleVAO);
glBindBuffer(GL_ARRAY_BUFFER, state.paddleVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// Настройка VAO/VBO для кирпичей
void setupBrick(GameState& state) {
std::vector<glm::vec3> vertices = {
{-0.75f, -0.2f, 0.0f},
{ 0.75f, -0.2f, 0.0f},
{ 0.75f, 0.2f, 0.0f},
{-0.75f, 0.2f, 0.0f}
};
glGenVertexArrays(1, &state.brickVAO);
glGenBuffers(1, &state.brickVBO);
glBindVertexArray(state.brickVAO);
glBindBuffer(GL_ARRAY_BUFFER, state.brickVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
// Инициализация игры
bool init(GameState& state) {
srand(static_cast<unsigned int>(time(0))); // Для rand()
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, 0);
state.window = glfwCreateWindow(800, 600, "Arcanoid", NULL, NULL);
if (!state.window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return false;
}
glfwMakeContextCurrent(state.window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize Glad" << std::endl;
glfwTerminate();
return false;
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.6f, 0.1f, 0.1f, 1.0f);
state.shaderProgram = setupShaders();
// Инициализация объектов
state.ball.position = glm::vec3(0.0f, 0.0f, 0.0f);
state.ball.radius = 0.3f;
state.ball.velocity = glm::vec3(1.8f, 1.8f, 0.0f);
state.ball.color = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); // Чёрный
state.ball.colorChangeTimer = 0.0f;
state.paddle.position = glm::vec3(0.0f, WORLD_BOTTOM + 1.0f, 0.0f);
state.paddle.size = glm::vec2(2.0f, 0.2f);
state.paddle.velocity = glm::vec2(0.0f, 0.0f);
const int rows = 3, cols = 8;
const float brickWidth = 1.5f;
const float brickHeight = 0.4f;
for (int row = 0; row < rows; ++row) {
for (int col = 0; col < cols; ++col) {
Brick brick;
brick.position = glm::vec3(
WORLD_LEFT + 1.0f + col * (brickWidth + 0.3f),
WORLD_TOP - 1.0f - row * (brickHeight + 0.2f),
0.0f
);
brick.active = true;
brick.halfWidth = brickWidth / 2.0f; // Полуширина кирпича
brick.halfHeight = brickHeight / 2.0f; // Полувысота кирпича
state.bricks.push_back(brick);
}
}
setupBall(state);
setupPaddle(state);
setupBrick(state);
// Инициализация частиц
state.particles.resize(MAX_PARTICLES);
for (auto& p : state.particles) {
p.active = false;
}
state.gameOver = false;
state.score = 0;
state.time = 0.0f;
return true;
}
// Обновление игры
void updateGame(GameState& state, float deltaTime) {
if (state.gameOver) return;
state.time += deltaTime;
// Обновление позиции шара
state.ball.position += state.ball.velocity * deltaTime;
// Столкновение с левым краем
if (state.ball.position.x - state.ball.radius < WORLD_LEFT) {
state.ball.position.x = WORLD_LEFT + state.ball.radius;
state.ball.velocity.x *= -1.0f;
}
// Столкновение с правым краем
if (state.ball.position.x + state.ball.radius > WORLD_RIGHT) {
state.ball.position.x = WORLD_RIGHT - state.ball.radius;
state.ball.velocity.x *= -1.0f;
}
// Столкновение с верхним краем
if (state.ball.position.y + state.ball.radius > WORLD_TOP) {
state.ball.position.y = WORLD_TOP - state.ball.radius;
state.ball.velocity.y *= -1.0f;
}
// Потеря шара
if (state.ball.position.y - state.ball.radius < WORLD_BOTTOM) {
state.gameOver = true;
return;
}
// Столкновение с битой
if (state.ball.position.y - state.ball.radius <= state.paddle.position.y + state.paddle.size.y / 2.0f &&
state.ball.position.x >= state.paddle.position.x - state.paddle.size.x / 2.0f &&
state.ball.position.x <= state.paddle.position.x + state.paddle.size.x / 2.0f) {
float bounceFactor = calculateBounceFactor(
state.ball.position.x,
state.paddle.position.x,
state.paddle.size.x
);
// Эмиссия искр
glm::vec3 normal(0.0f, 1.0f, 0.0f);
emitParticles(state.ball.position, normal, state.particles);
state.ball.velocity.y *= -1.0f;
state.ball.velocity.x += bounceFactor * 0.7f;
// Смена цвета шара
state.ball.color = glm::vec4(1.0f, 0.8f, 0.0f, 1.0f); // Золотой
state.ball.colorChangeTimer = 0.2f;
// Ограничение скорости
float speed = glm::length(state.ball.velocity);
if (speed > MAX_SPEED) {
state.ball.velocity = glm::normalize(state.ball.velocity) * MAX_SPEED;
}
state.ball.position.y = state.paddle.position.y + state.paddle.size.y / 2.0f + state.ball.radius;
}
// Столкновение с кирпичами
for (auto& brick : state.bricks) {
if (!brick.active) continue;
// Проверка на пересечение с кирпичом (используем полуразмеры)
float dx = abs(state.ball.position.x - brick.position.x);
float dy = abs(state.ball.position.y - brick.position.y);
if (dx <= brick.halfWidth + state.ball.radius &&
dy <= brick.halfHeight + state.ball.radius) {
// Эмиссия искр в точке столкновения
glm::vec3 collisionPoint = state.ball.position;
glm::vec3 normal(0.0f, 1.0f, 0.0f); // Нормаль к поверхности кирпича (вертикальная)
emitParticles(collisionPoint, normal, state.particles);
// Определяем, с какой стороны произошло столкновение
float overlapX = brick.halfWidth + state.ball.radius - dx;
float overlapY = brick.halfHeight + state.ball.radius - dy;
if (overlapX < overlapY) {
// Столкновение по горизонтали (левый/правый край кирпича)
state.ball.velocity.x *= -1.0f;
// Корректировка позиции шара
if (state.ball.position.x < brick.position.x) {
state.ball.position.x = brick.position.x - brick.halfWidth - state.ball.radius;
} else {
state.ball.position.x = brick.position.x + brick.halfWidth + state.ball.radius;
}
} else {
// Столкновение по вертикали (верхний/нижний край кирпича)
state.ball.velocity.y *= -1.0f;
// Корректировка позиции шара
if (state.ball.position.y < brick.position.y) {
state.ball.position.y = brick.position.y - brick.halfHeight - state.ball.radius;
} else {
state.ball.position.y = brick.position.y + brick.halfHeight + state.ball.radius;
}
}
// Смена цвета шара на золотой
state.ball.color = glm::vec4(1.0f, 0.8f, 0.0f, 1.0f);
state.ball.colorChangeTimer = 0.2f;
// Ограничение максимальной скорости
float speed = glm::length(state.ball.velocity);
if (speed > MAX_SPEED) {
state.ball.velocity = glm::normalize(state.ball.velocity) * MAX_SPEED;
}
// Помечаем кирпич как разрушенный
brick.active = false;
state.score += 10; // Увеличиваем счёт
}
}
// Обновление таймера смены цвета шара
if (state.ball.colorChangeTimer > 0.0f) {
state.ball.colorChangeTimer -= deltaTime;
if (state.ball.colorChangeTimer <= 0.0f) {
state.ball.color = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); // Возвращаем чёрный цвет
}
}
}
// Отрисовка игры
void render(GameState& state) {
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(state.shaderProgram);
// Настройка проекции
glm::mat4 projection = glm::ortho(
WORLD_LEFT, WORLD_RIGHT,
WORLD_BOTTOM, WORLD_TOP,
-1.0f, 1.0f
);
glUniformMatrix4fv(
glGetUniformLocation(state.shaderProgram, "u_projection"),
1, GL_FALSE, glm::value_ptr(projection)
);
// Отрисовка шара
{
GLint posLoc = glGetUniformLocation(state.shaderProgram, "u_position");
if (posLoc != -1) {
glUniform3fv(posLoc, 1, glm::value_ptr(state.ball.position));
}
GLint colorLoc = glGetUniformLocation(state.shaderProgram, "u_color");
if (colorLoc != -1) {
glUniform4fv(colorLoc, 1, glm::value_ptr(state.ball.color));
}
glBindVertexArray(state.ballVAO);
glDrawArrays(GL_TRIANGLE_FAN, 0, 37); // 36 сегментов + центр
}
// Отрисовка биты
{
GLint posLoc = glGetUniformLocation(state.shaderProgram, "u_position");
if (posLoc != -1) {
glUniform3fv(posLoc, 1, glm::value_ptr(state.paddle.position));
}
GLint isPaddleLoc = glGetUniformLocation(state.shaderProgram, "u_isPaddle");
if (isPaddleLoc != -1) {
glUniform1i(isPaddleLoc, 1);
}
glBindVertexArray(state.paddleVAO);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
// Отрисовка кирпичей
{
GLint isPaddleLoc = glGetUniformLocation(state.shaderProgram, "u_isPaddle");
if (isPaddleLoc != -1) {
glUniform1i(isPaddleLoc, 0);
}
for (const auto& brick : state.bricks) {
if (!brick.active) continue;
GLint posLoc = glGetUniformLocation(state.shaderProgram, "u_position");
if (posLoc != -1) {
glUniform3fv(posLoc, 1, glm::value_ptr(brick.position));
}
glBindVertexArray(state.brickVAO);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
}
// Отрисовка частиц
{
glEnable(GL_POINT_SMOOTH);
glPointSize(3.0f);
GLint posLoc = glGetUniformLocation(state.shaderProgram, "u_position");
GLint colorLoc = glGetUniformLocation(state.shaderProgram, "u_color");
for (const auto& p : state.particles) {
if (!p.active) continue;
if (posLoc != -1) {
glUniform3fv(posLoc, 1, glm::value_ptr(p.position));
}
if (colorLoc != -1) {
glUniform4fv(colorLoc, 1, glm::value_ptr(p.color));
}
glBindVertexArray(state.ballVAO); // Используем VAO шара для точек
glDrawArrays(GL_POINTS, 0, 1);
}
glDisable(GL_POINT_SMOOTH);
}
glBindVertexArray(0);
glUseProgram(0);
}
// Обработка ввода
// Обработка ввода
void processInput(GameState& state, float deltaTime) {
if (glfwGetKey(state.window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(state.window, true);
}
if (state.gameOver) return;
float paddleSpeed = 5.0f * deltaTime;
// Движение биты влево
if (glfwGetKey(state.window, GLFW_KEY_LEFT) == GLFW_PRESS) {
state.paddle.position.x -= paddleSpeed;
// Ограничение по левой границе
if (state.paddle.position.x - state.paddle.size.x / 2.0f < WORLD_LEFT) {
state.paddle.position.x = WORLD_LEFT + state.paddle.size.x / 2.0f;
}
state.paddle.velocity.x = -paddleSpeed / deltaTime; // Обновляем скорость биты
}
// Движение биты вправо
else if (glfwGetKey(state.window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
state.paddle.position.x += paddleSpeed;
// Ограничение по правой границе
if (state.paddle.position.x + state.paddle.size.x / 2.0f > WORLD_RIGHT) {
state.paddle.position.x = WORLD_RIGHT - state.paddle.size.x / 2.0f;
}
state.paddle.velocity.x = paddleSpeed / deltaTime; // Обновляем скорость биты
}
else {
state.paddle.velocity.x = 0.0f; // Если нет ввода — скорость биты = 0
}
}
// Обновление состояния частиц
void updateParticles(GameState& state, float deltaTime) {
for (auto& p : state.particles) {
if (!p.active) continue;
p.position += p.velocity * deltaTime;
p.lifetime -= deltaTime;
if (p.lifetime <= 0.0f) {
p.active = false;
}
}
}
// Основной цикл игры
int main() {
GameState state = {};
if (!init(state)) {
return -1;
}
double lastFrame = glfwGetTime();
while (!glfwWindowShouldClose(state.window)) {
double currentFrame = glfwGetTime();
float deltaTime = static_cast<float>(currentFrame - lastFrame);
lastFrame = currentFrame;
// Обработка ввода
processInput(state, deltaTime);
// Обновление частиц
updateParticles(state, deltaTime);
// Обновление игрового состояния
updateGame(state, deltaTime);
// Отрисовка
render(state);
glfwSwapBuffers(state.window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
Что уже реализовано
Базовая механика арканоида
Движение шара, отскоки от границ и биты.
Столкновения с кирпичами (с учётом полуразмеров).
Разрушение кирпичей при попадании.Подсчёт очков.
Визуальные эффекты
Искры при столкновениях (частицы с физикой и затуханием).
Смена цвета шара при ударе о биту.
Управление
Бита управляется стрелками влево/вправо.
Выход по ESC.
Графика
Использование OpenGL (GLFW + GLAD).
Шейдеры для отрисовки шара, биты, кирпичей и частиц.
Ортографическая проекция.
Архитектура
Чёткое разделение логики: инициализация, обновление, отрисовка, ввод.
Структуры данных (Ball, Paddle, Brick, Particle, GameState).
Возможные улучшения (по приоритету)
Добавление уровней
Создать несколько макетов кирпичей (разные схемы расположения).
Переходить на следующий уровень после уничтожения всех кирпичей.
Сохранить счёт между уровнями.
Жизни игрока
Добавить 3 жизни: при падении шара ниже биты — минус жизнь.
Игра заканчивается, когда жизни = 0.
Отображать количество жизней на экране.
Улучшение физики шара
Добавить вращение шара (визуальный эффект).
Учитывать скорость биты при отскоке (если бита движется, шар получает дополнительный импульс).
Реализовать «липкий» режим (шар прилипает к бите по SPACE, затем запускается).
Звуковое сопровождение
Звук при ударе шара о биту/кирпичи.
Музыка на фоне.Использовать OpenAL или SDL_mixer.
Интерфейс пользователя
Отображать счёт, уровень, жизни в углу экрана.
Экран «Game Over» с итоговым счётом.
Кнопка «Restart» (по R).Меню старта (по Enter).
Оптимизация частиц
Использовать instanced rendering для частиц (если их много).
Добавить градиентное затухание цвета частиц.
Настройки сложности
Скорость шара растёт с каждым уровнем.
Кирпичи с разной прочностью (требуют 2–3 попаданий).
«Бонусные» кирпичи (дают +1 жизнь или ускоряют шар).
Сохранение рекордов
Записывать лучший счёт в файл (highscore.txt).
Отображать рекорд на экране.
Адаптивность
Поддержка разных разрешений экрана (масштабирование камеры).
Полноэкранный режим (F11).
Тестирование и отладка
Добавить логирование ошибок (например, через std::cerr).
Проверить утечки памяти (если используются динамические ресурсы).
Протестировать крайние случаи (например, шар застревает между кирпичами).
Заключение
Программа уже полностью работоспособна как базовый арканоид. Дальнейшие улучшения зависят от целей:
Для учебного проекта — достаточно текущего функционала.
Для публикации — добавить уровни, жизни, звук, интерфейс.
Для экспериментов — внедрить физику (трение, гравитация), шейдерные эффекты, мультиплеер.
Если нужны конкретные доработки — уточните, какой функционал приоритетен!
Следующий код был так же написан совместно с ИИ - главное идея и перспектива её дальнейшего развития.
Планетарная система - Солнце вокруг которого вращается планета.
// Блокируем автоматическое включение OpenGL из GLFW
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#define _USE_MATH_DEFINES
#include <cmath>
#include <chrono>
#include <iomanip>
#ifndef M_PI
#define PI 3.14159265358979323846
#else
#define PI M_PI
#endif
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// Шейдеры
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
)";
const char* fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture1;
void main() {
FragColor = texture(texture1, TexCoord);
}
)";
// Кэшированные uniform-локации
struct ShaderUniforms {
GLint modelLoc;
GLint viewLoc;
GLint projLoc;
GLint textureLoc;
};
// Загрузка текстуры
unsigned int loadTexture(const char* path) {
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char* data = stbi_load(path, &width, &height, &nrComponents, 0);
if (!data) {
std::cerr << "Failed to load texture: " << path << std::endl;
stbi_image_free(data);
return 0;
}
GLenum format;
if (nrComponents == 1) format = GL_RED;
else if (nrComponents == 3) format = GL_RGB;
else if (nrComponents == 4) format = GL_RGBA;
else {
std::cerr << "Unsupported number of components: " << nrComponents << " in " << path << std::endl;
stbi_image_free(data);
return 0;
}
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
return textureID;
}
// Компиляция шейдера
unsigned int compileShader(unsigned int type, const char* source) {
unsigned int shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
int success;
char infoLog[1024];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
std::cerr << "Shader compilation failed ("
<< (type == GL_VERTEX_SHADER ? "vertex" : "fragment")
<< "):\n" << infoLog << std::endl;
glDeleteShader(shader);
return 0;
}
return shader;
}
// Создание программы
unsigned int createShaderProgram(ShaderUniforms& uniforms) {
unsigned int vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
unsigned int fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
if (!vertexShader || !fragmentShader) return 0;
unsigned int program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
int success;
char infoLog[1024];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 1024, nullptr, infoLog);
std::cerr << "Shader program linking failed:\n" << infoLog << std::endl;
glDeleteProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return 0;
}
uniforms.modelLoc = glGetUniformLocation(program, "model");
uniforms.viewLoc = glGetUniformLocation(program, "view");
uniforms.projLoc = glGetUniformLocation(program, "projection");
uniforms.textureLoc = glGetUniformLocation(program, "texture1");
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
// Генерация сферы
void generateSphereVertices(std::vector<float>& vertices, std::vector<float>& texCoords, float radius, int slices = 32, int stacks = 16) {
for (int i = 0; i <= stacks; ++i) {
float lat0 = PI * (-0.5 + (float)(i - 1) / stacks);
float z0 = radius * sin(lat0);
float rz0 = radius * cos(lat0);
float lat1 = PI * (-0.5 + (float)i / stacks);
float z1 = radius * sin(lat1);
float rz1 = radius * cos(lat1);
for (int j = 0; j <= slices; ++j) {
float lng = 2 * PI * (float)(j - 1) / slices;
float x = cos(lng), y = sin(lng);
vertices.push_back(rz0 * x); vertices.push_back(rz0 * y); vertices.push_back(z0);
vertices.push_back(rz1 * x); vertices.push_back(rz1 * y); vertices.push_back(z1);
float u = (float)j / slices, v0 = (float)i / stacks, v1 = (float)(i + 1) / stacks;
texCoords.push_back(u); texCoords.push_back(v0);
texCoords.push_back(u); texCoords.push_back(v1);
}
}
}
// Глобальные переменные
float cameraDistance = 5.0f;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, cameraDistance);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
float sceneYaw = 0.0f, scenePitch = 0.0f;
float objectRotY = 0.0f, sunRotY = 0.0f;
float rotationSpeed = 0.5f;
float planetOrbitRadius = 3.0f;
float planetAngle = 0.0f;
// Для FPS-счётчика
auto lastFrameTime = std::chrono::high_resolution_clock::now();
int frameCount = 0;
float fps = 0.0f;
// Функция для обновления FPS
void updateFPS() {
auto currentTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(currentTime - lastFrameTime).count();
if (duration >= 1) {
fps = static_cast<float>(frameCount) / duration;
frameCount = 0;
lastFrameTime = currentTime;
}
frameCount++;
}
// Вывод FPS на консоль
void printFPS() {
static int printCounter = 0;
if (printCounter++ % 60 == 0) { // раз в ~60 кадров
std::cout << std::fixed << std::setprecision(1) << "FPS: " << fps << std::endl;
}
}
// Обработчик движения мыши
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
static bool firstMouse = true;
static float lastX = 400, lastY = 300;
const float sensitivity = 0.1f;
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = (xpos - lastX) * sensitivity;
float yoffset = (lastY - ypos) * sensitivity; // инвертируем Y
lastX = xpos;
lastY = ypos;
sceneYaw += xoffset;
scenePitch += yoffset;
// Ограничиваем наклон по вертикали
if (scenePitch > 89.0f) scenePitch = 89.0f;
if (scenePitch < -89.0f) scenePitch = -89.0f;
}
int main() {
const int WIDTH = 800, HEIGHT = 600;
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return -1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Solar System - Enhanced Control", nullptr, nullptr);
if (!window) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Захват курсора
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);
// Инициализация GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD" << std::endl;
glfwTerminate();
return -1;
}
glViewport(0, 0, WIDTH, HEIGHT);
glfwSetFramebufferSizeCallback(window, [](GLFWwindow*, int w, int h) {
glViewport(0, 0, w, h);
});
glEnable(GL_DEPTH_TEST);
// Создание шейдерной программы
ShaderUniforms uniforms;
unsigned int shaderProgram = createShaderProgram(uniforms);
if (!shaderProgram) {
std::cerr << "Shader program creation failed!" << std::endl;
glfwTerminate();
return -1;
}
glUseProgram(shaderProgram);
// Загрузка текстур
unsigned int sunTexture = loadTexture("sun.jpg");
unsigned int planetTexture = loadTexture("earth.jpg");
if (!sunTexture || !planetTexture) {
std::cerr << "Texture loading failed!" << std::endl;
glDeleteProgram(shaderProgram);
glfwTerminate();
return -1;
}
// Генерация геометрии
std::vector<float> sunVertices, sunTexCoords;
generateSphereVertices(sunVertices, sunTexCoords, 1.0f, 32, 16);
std::vector<float> planetVertices, planetTexCoords;
generateSphereVertices(planetVertices, planetTexCoords, 0.4f, 32, 16);
// Настройка VAO/VBO для Солнца
unsigned int sunVAO, sunVBO[2];
glGenVertexArrays(1, &sunVAO);
glGenBuffers(2, sunVBO);
glBindVertexArray(sunVAO);
glBindBuffer(GL_ARRAY_BUFFER, sunVBO[0]);
glBufferData(GL_ARRAY_BUFFER, sunVertices.size() * sizeof(float), sunVertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, sunVBO[1]);
glBufferData(GL_ARRAY_BUFFER, sunTexCoords.size() * sizeof(float), sunTexCoords.data(), GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// Настройка VAO/VBO для планеты
unsigned int planetVAO, planetVBO[2];
glGenVertexArrays(1, &planetVAO);
glGenBuffers(2, planetVBO);
glBindVertexArray(planetVAO);
glBindBuffer(GL_ARRAY_BUFFER, planetVBO[0]);
glBufferData(GL_ARRAY_BUFFER, planetVertices.size() * sizeof(float), planetVertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, planetVBO[1]);
glBufferData(GL_ARRAY_BUFFER, planetTexCoords.size() * sizeof(float), planetTexCoords.data(), GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// Матрица проекции (исправлено: perspective)
glm::mat4 projection = glm::perspective(
glm::radians(45.0f),
(float)WIDTH / (float)HEIGHT,
0.1f,
100.0f
);
glUniformMatrix4fv(uniforms.projLoc, 1, GL_FALSE, glm::value_ptr(projection));
// Основной цикл рендеринга
while (!glfwWindowShouldClose(window)) {
// Обработка ввода
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_EQUAL) == GLFW_PRESS) cameraDistance -= 0.01f;
if (glfwGetKey(window, GLFW_KEY_MINUS) == GLFW_PRESS) cameraDistance += 0.01f;
cameraDistance = glm::clamp(cameraDistance, 1.0f, 20.0f);
cameraPos = glm::vec3(0.0f, 0.0f, cameraDistance);
// Обновление углов вращения
objectRotY += rotationSpeed * 0.1f;
sunRotY += rotationSpeed * 0.05f;
planetAngle += rotationSpeed * 0.001f;
// Нормализация углов (устранение избыточных оборотов)
if (objectRotY >= 360.0f) objectRotY -= 360.0f;
if (sunRotY >= 360.0f) sunRotY -= 360.0f;
if (planetAngle > 2.0f * PI) planetAngle -= 2.0f * PI;
// Коррекция отрицательных углов (для устойчивости)
if (objectRotY < 0.0f) objectRotY += 360.0f;
if (sunRotY < 0.0f) sunRotY += 360.0f;
if (planetAngle < 0.0f) planetAngle += 2.0f * PI;
// Обновление FPS
updateFPS();
printFPS();
// Очистка буферов
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(shaderProgram);
// Матрица поворота сцены
glm::mat4 sceneRotation = glm::mat4(1.0f);
sceneRotation = glm::rotate(sceneRotation, glm::radians(sceneYaw), glm::vec3(0.0f, 1.0f, 0.0f));
sceneRotation = glm::rotate(sceneRotation, glm::radians(scenePitch), glm::vec3(1.0f, 0.0f, 0.0f));
// Обновление матрицы вида
glm::mat4 view = glm::lookAt(cameraPos, glm::vec3(0.0f), cameraUp);
glUniformMatrix4fv(uniforms.viewLoc, 1, GL_FALSE, glm::value_ptr(view));
// Рисование Солнца
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sunTexture);
glUniform1i(uniforms.textureLoc, 0);
glm::mat4 modelSun = glm::mat4(1.0f);
modelSun = glm::rotate(modelSun, glm::radians(sunRotY), glm::vec3(0.0f, 1.0f, 0.0f));
modelSun = sceneRotation * modelSun;
glUniformMatrix4fv(uniforms.modelLoc, 1, GL_FALSE, glm::value_ptr(modelSun));
glBindVertexArray(sunVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, sunVertices.size() / 3);
// Рисование планеты
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, planetTexture);
glUniform1i(uniforms.textureLoc, 0);
float planetX = planetOrbitRadius * cos(planetAngle);
float planetZ = planetOrbitRadius * sin(planetAngle);
glm::mat4 modelPlanet = glm::translate(glm::mat4(1.0f), glm::vec3(planetX, 0.0f, planetZ));
modelPlanet = glm::rotate(modelPlanet, glm::radians(objectRotY), glm::vec3(0.0f, 1.0f, 0.0f));
modelPlanet = sceneRotation * modelPlanet;
glUniformMatrix4fv(uniforms.modelLoc, 1, GL_FALSE, glm::value_ptr(modelPlanet));
glBindVertexArray(planetVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, planetVertices.size() / 3);
// Обмен буферами и обработка событий
glfwSwapBuffers(window);
glfwPollEvents();
}
// Освобождение ресурсов
glDeleteVertexArrays(1, &sunVAO);
glDeleteBuffers(2, sunVBO);
glDeleteVertexArrays(1, &planetVAO);
glDeleteBuffers(2, planetVBO);
glDeleteProgram(shaderProgram);
glDeleteTextures(1, &sunTexture);
glDeleteTextures(1, &planetTexture);
glfwTerminate();
return 0;
}
Обзор программы: 3D‑симуляция «Солнечная система»
Программа реализует интерактивную 3D‑сцену с моделью Солнца и планеты, вращающейся по орбите. Используются современные технологии графики (OpenGL 3.3+) с управлением камерой и визуализацией текстурированных сфер.
Ключевые технологии
Основной функционал
Архитектура кода
Ограничения и недочёты
Перспективы развития
Итог
Программа представляет собой базовую, но работоспособную 3D‑симуляцию солнечной системы с потенциалом для расширения. Она подходит для:
Для промышленного использования требуется доработка по пунктам, указанным в разделе «Перспективы».
П.С.
Код программы можно выделить, скопировать и вставить в строку чата для получения от ИИ рекомендаций для дальнейших действий. Можно написать - давай напишем простую 3D программу Планетарная система с нуля.
Возможно что у вас нет IDE среды программирования например на языке СИ, СИ++ и тому подобное, но у вас есть PYTHON, какая разница. Короче, дерзайте - дорогу осилит идущий.