commit 88d329637146b579ab4f604f4c4827358a4dbff4 Author: StepanovPlaton Date: Sun Oct 5 07:51:25 2025 +0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ff9a75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +main.exe +*.o \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bc9395b --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +CC = gcc +CFLAGS = -std=gnu23 -Wall -Wextra +LIBS = -lgdi32 + +TARGET = main.exe +SOURCES = main.c utils/utils.c +OBJECTS = $(SOURCES:.c=.o) + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) -o $@ $^ $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + @if command -v rm >/dev/null 2>&1; then \ + rm -f $(TARGET) main.o utils/utils.o; \ + else \ + powershell -Command "Remove-Item -ErrorAction SilentlyContinue '$(TARGET)', 'main.o', 'utils/utils.o'"; \ + fi + +rebuild: clean all + +run: $(TARGET) + .\$(TARGET) + +.PHONY: all clean rebuild run \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c400f5b --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# C 3D Graphic Engine +> **C3DGraphicEngine** - это графический 3D движок, написанный с полного нуля на чистом С! +### Скачайте и попробуйте сами! + +![](./screenshots/demo_render.png) + + +## Стек: +- [C23](https://ru.wikipedia.org/wiki/C23) +- [windows.h](https://ru.wikipedia.org/wiki/Windows.h) +- ВСЁ! :smirk: + +## О проекте: +- Графический 3D движок в 75 Kb! +- Первая версия написана за 8 часов +- Рендерит произвольные объекты, заданные как набор вершин и ребер +- Реализует сцену с произвольным позиционированием камеры +- Использует перспективную проекцию +- Создан с полного нуля: + - Не использует продвинутые графические библиотеки + - Основан на [Windows API](https://ru.wikipedia.org/wiki/Windows_API) (использует функции создания окон, рисования точек, линий) + +## Запуск: +- Make: + ```bash + make + make run + ``` +- GCC: + ```bash + gcc -o main.exe .\main.c .\utils\utils.c -lgdi32 + .\main.exe + ``` + +## Идеи: +- [ ] Добавить непрозрачность (скрыть невидимые грани) +- [ ] Добавить алгоритмы создания сложных фигур (додекаэдр, шар и тд.) +- [ ] Добавить физику +- [ ] Have fun! + +### Над проектом работали [StepanovPlaton](https://github.com/StepanovPlaton) и [Fluorouacil](https://github.com/Fluorouacil)! \ No newline at end of file diff --git a/builds/v0.1.exe b/builds/v0.1.exe new file mode 100644 index 0000000..983f82b Binary files /dev/null and b/builds/v0.1.exe differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..6e9bb4e --- /dev/null +++ b/main.c @@ -0,0 +1,176 @@ +#include +#include +#include + +#include "utils/utils.h" + +#define WINDOW_WIDTH 800.0f +#define WINDOW_HEIGHT 600.0f + +#define BG_COLOR BLACK_BRUSH +#define COLOR RGB(255, 255, 255) + +Point3D cube_points[] = {{0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, + {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 1.0f}, + {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 1.0f}}; +int cube_edges[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, + {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}}; +Point3D cube_position = {.coordinates = {3.0f, 6.0f, 5.0f}}; +float cube_speed[] = {0.03f, 0.0f, 0.0f}; + +const Object cube = {.points = cube_points, + .edges = cube_edges, + .number_of_points = 8, + .number_of_edges = 12, + .position = &cube_position, + .rotate_speed = &cube_speed}; + +Point3D pyramid_points[] = {{-1.0f, 0.0f, -1.0f}, + {1.0f, 0.0f, -1.0f}, + {1.0f, 0.0f, 1.0f}, + {-1.0f, 0.0f, 1.0f}, + {0.0f, 1.5f, 0.0f}}; +int pyramid_edges[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, + {0, 4}, {1, 4}, {2, 4}, {3, 4}}; +Point3D pyramid_position = {.coordinates = {6.0f, 3.0f, 5.0f}}; +float pyramid_speed[] = {0.0f, 0.0f, 0.03f}; + +const Object pyramid = {.points = pyramid_points, + .edges = pyramid_edges, + .number_of_points = 5, + .number_of_edges = 8, + .position = &pyramid_position, + .rotate_speed = &pyramid_speed}; + +const int number_of_objects = 2; +Object objects[] = {cube, pyramid}; + +Camera camera = {.position = {0.0f, 0.0f, 0.0f}, + .target = {1.0f, 1.0f, 1.0f}, + .up = {-1.0f, -1.0f, 1.0f}, + .fov = 120.0f, + .max_distance = 20.0f, + .min_distance = .0f, + .aspect_ratio = WINDOW_WIDTH / WINDOW_HEIGHT}; + +float render_matrix[4][4]; + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + switch (uMsg) { + case WM_CREATE: + SetTimer(hwnd, 1, 50, NULL); + return 0; + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + + RECT rect; + GetClientRect(hwnd, &rect); + FillRect(hdc, &rect, (HBRUSH)GetStockObject(BG_COLOR)); + float width = rect.right - rect.left; + float height = rect.bottom - rect.top; + + Screen screen = {.width = width, + .height = height, + .hdc = &hdc, + .render_matrix = &render_matrix}; + + HPEN hPen = CreatePen(PS_SOLID, 1, COLOR); + HPEN hOldPen = (HPEN)SelectObject(hdc, hPen); + + for (int i = 0; i < number_of_objects; i++) { + draw_object(&(objects[i]), &screen); + } + + SelectObject(hdc, hOldPen); + DeleteObject(hPen); + + EndPaint(hwnd, &ps); + return 0; + } + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_TIMER: + for (int i = 0; i < number_of_objects; i++) { + float reverse_translate_matrix[4][4]; + create_translate_matrix(objects[i].position, reverse_translate_matrix, + -1); + transform_object(&(objects[i]), 4, reverse_translate_matrix); + + Point3D center_of_object = calculate_centroid(&(objects[i])); + float reverse_center_translate_matrix[4][4]; + create_translate_matrix(¢er_of_object, + reverse_center_translate_matrix, -1); + transform_object(&(objects[i]), 4, reverse_center_translate_matrix); + + float rotate_matrix[3][3]; + create_rotate_matrix(*(objects[i].rotate_speed), rotate_matrix); + transform_object(&(objects[i]), 3, rotate_matrix); + + float center_translate_matrix[4][4]; + create_translate_matrix(¢er_of_object, center_translate_matrix, 1); + transform_object(&(objects[i]), 4, center_translate_matrix); + + float translate_matrix[4][4]; + create_translate_matrix(objects[i].position, translate_matrix, 1); + transform_object(&(objects[i]), 4, translate_matrix); + } + + InvalidateRect(hwnd, NULL, TRUE); + return 0; + + case WM_KEYDOWN: + return 0; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) { + const char CLASS_NAME[] = "C3DGraphicEngine"; + + WNDCLASS wc = {0}; + wc.lpfnWndProc = WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + RegisterClass(&wc); + + HWND hwnd = CreateWindowEx(0, CLASS_NAME, CLASS_NAME, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, + WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); + + if (hwnd == NULL) { + return 0; + } + + float view_matrix[4][4]; + create_view_matrix(&camera, view_matrix); + float projection_matrix[4][4]; + create_projection_matrix(&camera, projection_matrix); + matrix_mult_matrix(4, projection_matrix, view_matrix, render_matrix); + + for (int i = 0; i < number_of_objects; i++) { + float translate_matrix[4][4]; + create_translate_matrix(objects[i].position, translate_matrix, 1); + transform_object(&(objects[i]), 4, translate_matrix); + } + + ShowWindow(hwnd, nCmdShow); + UpdateWindow(hwnd); + + MSG msg = {0}; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} diff --git a/screenshots/demo_render.png b/screenshots/demo_render.png new file mode 100644 index 0000000..5d1cca7 Binary files /dev/null and b/screenshots/demo_render.png differ diff --git a/utils/utils.c b/utils/utils.c new file mode 100644 index 0000000..a104d5a --- /dev/null +++ b/utils/utils.c @@ -0,0 +1,246 @@ +#include "utils.h" + +#include +#include +#include + +// === Vectors === +void vector_substruct(int size, float v1[size], float v2[size], float r[size]) { + for (int i = 0; i < size; i++) + r[i] = v1[i] - v2[i]; +} +void cross_product_vectors(float v1[3], float v2[3], float r[3]) { + r[0] = v1[1] * v2[2] - v1[2] * v2[1]; + r[1] = v1[2] * v2[0] - v1[0] * v2[2]; + r[2] = v1[0] * v2[1] - v1[1] * v2[0]; +} +float dot_product_vectors(float v1[3], float v2[3]) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} +void vector_normalize(int size, float coordinates[size]) { + float divider = 0; + for (int i = 0; i < size; i++) + divider += coordinates[i] * coordinates[i]; + divider = sqrt(divider); + float new_coordinates[size]; + for (int i = 0; i < size; i++) + coordinates[i] /= divider; +} +void print_vector(int size, float vector[size]) { + printf("("); + for (int i = 0; i < size; i++) { + printf("%f", vector[i]); + if (i != size - 1) + printf(",\t"); + } + printf(")\n"); +} + +// === Matrices === +void print_matrix(int size, float matrix[size][size]) { + for (int i = 0; i < size; i++) { + printf("("); + for (int j = 0; j < size; j++) { + printf("%f", matrix[i][j]); + if (j != size - 1) + printf(",\t"); + } + printf(")\n"); + } +} +void matrix_mult_matrix(int size, float matrix1[size][size], + float matrix2[size][size], float result[size][size]) { + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + result[i][j] = 0; + } + } + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + for (int k = 0; k < size; k++) { + result[i][j] += matrix1[i][k] * matrix2[k][j]; + } + } + } +} +void matrix_mult_vector(int size, float matrix[size][size], float vector[size], + float result[size]) { + for (int i = 0; i < size; i++) + result[i] = 0; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + result[i] += matrix[i][j] * vector[j]; + } + } +} +void create_axis_rotate_matrix(int axis, float angle, + float rotate_matrix[3][3]) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + rotate_matrix[i][j] = 0; + } + } + + float cos_angle = cosf(angle); + float sin_angle = sinf(angle); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == axis || j == axis) { + if (i == j) + rotate_matrix[i][j] = 1; + else + rotate_matrix[i][j] = 0; + } else { + if (i == j) { + rotate_matrix[i][j] = cos_angle; + } else { + if ((i < j && axis == 2) || (i > j && axis != 2)) { + rotate_matrix[i][j] = sin_angle; + } else { + rotate_matrix[i][j] = -sin_angle; + } + } + } + } + } +} + +void create_rotate_matrix(float rotate_speed[3], float rotate_matrix[3][3]) { + float x_rotate[3][3]; + create_axis_rotate_matrix(0, rotate_speed[0], x_rotate); + float y_rotate[3][3]; + create_axis_rotate_matrix(1, rotate_speed[1], y_rotate); + float z_rotate[3][3]; + create_axis_rotate_matrix(2, rotate_speed[2], z_rotate); + float xy_rotate[3][3]; + matrix_mult_matrix(3, x_rotate, y_rotate, xy_rotate); + matrix_mult_matrix(3, xy_rotate, z_rotate, rotate_matrix); +} + +// === Points in scene === +void transform_point(Point3D *point, int size, + float translate_matrix[size][size]) { + float new_coordinates[3]; + float old_coordinates[] = {point->coordinates[0], point->coordinates[1], + point->coordinates[2], 1.0f}; + matrix_mult_vector(size, translate_matrix, old_coordinates, new_coordinates); + for (int i = 0; i < 3; i++) + point->coordinates[i] = new_coordinates[i]; +} +void create_translate_matrix(Point3D *position, float translate_matrix[4][4], + int k) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + translate_matrix[i][j] = 0; + if (i == j) + translate_matrix[i][j] = 1; + else if (j == 3) + translate_matrix[i][j] = position->coordinates[i] * k; + } + } +} + +// === Points on screen === +ScreenPoint convert_to_screen_point(Point3D *point, Screen *screen) { + float tmp[] = {point->coordinates[0], point->coordinates[1], + point->coordinates[2], 1.0f}; + float point_projection_view[4]; + matrix_mult_vector(4, *(screen->render_matrix), tmp, point_projection_view); + + float perspective_coordinates[3] = { + point_projection_view[0] / point_projection_view[3], + point_projection_view[1] / point_projection_view[3], + point_projection_view[2] / point_projection_view[3], + }; + ScreenPoint spoint = { + .coordinates = {((perspective_coordinates[0] + 1.0f) / 2.0f) * + (float)screen->width, + ((perspective_coordinates[1] + 1.0f) / 2.0f) * + (float)screen->height}}; + return spoint; +} + +void draw_line(Screen *screen, ScreenPoint *sp1, ScreenPoint *sp2) { + MoveToEx(*(screen->hdc), sp1->coordinates[0], sp1->coordinates[1], NULL); + LineTo(*(screen->hdc), sp2->coordinates[0], sp2->coordinates[1]); +} + +// === Camera === +void create_view_matrix(Camera *camera, float view_matrix[4][4]) { + float forward[3]; + vector_substruct(3, camera->position.coordinates, camera->target.coordinates, + forward); + vector_normalize(3, forward); + float right[3]; + cross_product_vectors(camera->up, forward, right); + vector_normalize(3, right); + + float up[3]; + cross_product_vectors(forward, right, up); + vector_normalize(3, up); + + float *vectors[] = {right, up, forward}; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + view_matrix[i][j] = 0; + if (i == 3) { + if (j == 3) + view_matrix[i][j] = 1; + } else if (j == 3) { + view_matrix[i][j] = + -1 * dot_product_vectors(vectors[i], camera->position.coordinates); + } else { + view_matrix[i][j] = vectors[i][j]; + } + } + } +} +void create_projection_matrix(Camera *camera, float projection_matrix[4][4]) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + projection_matrix[i][j] = 0; + } + } + float f = 1 / tan(camera->fov / 2); + projection_matrix[0][0] = f / camera->aspect_ratio; + projection_matrix[1][1] = f; + projection_matrix[2][2] = (camera->max_distance + camera->min_distance) / + (camera->min_distance - camera->max_distance); + projection_matrix[3][2] = -1; + projection_matrix[2][3] = (2 * camera->max_distance * camera->min_distance) / + (camera->min_distance - camera->max_distance); +} + +// === Composite objects === +void transform_object(const Object *object, int size, + float translate_matrix[size][size]) { + for (int i = 0; i < object->number_of_points; i++) { + transform_point(&(object->points[i]), size, translate_matrix); + } +} +void draw_object(const Object *object, Screen *screen) { + for (int i = 0; i < object->number_of_edges; i++) { + Point3D point1 = object->points[object->edges[i][0]]; + ScreenPoint screen_point1 = convert_to_screen_point(&point1, screen); + Point3D point2 = object->points[object->edges[i][1]]; + ScreenPoint screen_point2 = convert_to_screen_point(&point2, screen); + draw_line(screen, &screen_point1, &screen_point2); + } +} +Point3D calculate_centroid(const Object *object) { + Point3D centroid = {{0.0f, 0.0f, 0.0f}}; + + for (int i = 0; i < object->number_of_points; i++) { + centroid.coordinates[0] += object->points[i].coordinates[0]; + centroid.coordinates[1] += object->points[i].coordinates[1]; + centroid.coordinates[2] += object->points[i].coordinates[2]; + } + + float inv_n = 1.0f / (float)object->number_of_points; + centroid.coordinates[0] *= inv_n; + centroid.coordinates[1] *= inv_n; + centroid.coordinates[2] *= inv_n; + + return centroid; +} \ No newline at end of file diff --git a/utils/utils.h b/utils/utils.h new file mode 100644 index 0000000..95c2f4f --- /dev/null +++ b/utils/utils.h @@ -0,0 +1,80 @@ +#ifndef OBJECTS_H +#define OBJECTS_H + +#include + +// === Vectors === +void vector_substruct(int size, float v1[size], float v2[size], float r[size]); +void cross_product_vectors(float v1[3], float v2[3], float r[3]); +float dot_product_vectors(float v1[3], float v2[3]); +void vector_normalize(int size, float coordinates[size]); +void print_vector(int size, float vector[size]); + +// === Matrix === +void print_matrix(int size, float matrix[size][size]); +void matrix_mult_matrix(int size, float matrix1[size][size], + float matrix2[size][size], float result[size][size]); +void matrix_mult_vector(int size, float matrix[size][size], float vector[size], + float result[size]); + +void create_axis_rotate_matrix(int axis, float angle, + float rotate_matrix[3][3]); +void create_rotate_matrix(float rotate_speed[3], float rotate_matrix[3][3]); + +// === Point in scene === +struct Point3D { + float coordinates[3]; +}; +typedef struct Point3D Point3D; +void transform_point(Point3D *point, int size, + float translate_matrix[size][size]); +void create_translate_matrix(Point3D *position, float translate_matrix[4][4], + int k); + +// === Screen summary === +struct Screen { + int width; + int height; + HDC *hdc; + float (*render_matrix)[4][4]; +}; +typedef struct Screen Screen; + +// === Point on screen === +struct ScreenPoint { + int coordinates[2]; +}; +typedef struct ScreenPoint ScreenPoint; +ScreenPoint convert_to_screen_point(Point3D *point, Screen *screen); +void draw_line(Screen *screen, ScreenPoint *sp1, ScreenPoint *sp2); + +// === Camera in scene === +struct Camera { + Point3D position; + Point3D target; + float up[3]; + float fov; + float max_distance; + float min_distance; + float aspect_ratio; +}; +typedef struct Camera Camera; +void create_view_matrix(Camera *camera, float view_matrix[4][4]); +void create_projection_matrix(Camera *camera, float projection_matrix[4][4]); + +// === Composite object in scene === +struct Object { + Point3D *points; + int (*edges)[2]; + int number_of_points; + int number_of_edges; + Point3D *position; + float (*rotate_speed)[3]; +}; +typedef struct Object Object; +void transform_object(const Object *object, int size, + float translate_matrix[size][size]); +void draw_object(const Object *object, Screen *screen); +Point3D calculate_centroid(const Object *object); + +#endif \ No newline at end of file