Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
BasedOnStyle: Google
IndentWidth: 4
AccessModifierOffset: -4
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
DerivePointerAlignment: true
KeepEmptyLinesAtTheStartOfBlocks: true
SeparateDefinitionBlocks: Always
EmptyLineBeforeAccessModifier: LogicalBlock
SortIncludes: false
InsertNewlineAtEOF : true
41 changes: 41 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
Checks: '-*,cppcoreguidelines-avoid-goto,cppcoreguidelines-pro-type-const-cast, google-readability-casting, modernize-replace-random-shuffle, readability-braces-around-statements, readability-container-size-empty, readability-redundant-control-flow, readability-redundant-string-init, modernize-use-nullptr, readability-identifier-naming, google-build-using-namespace'
HeaderFilterRegex: '\.h$'
WarningsAsErrors: '*'
CheckOptions:
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.TypedefCase
value: CamelCase
- key: readability-identifier-naming.TypedefIgnoredRegexp
value: (allocator_type|size_type|key_type|value_type|mapped_type|difference_type|pointer|const_pointer|reference|const_reference|iterator_category|const_iterator|iterator|reverse_iterator|const_reverse_iterator|key_compare)
- key: readability-identifier-naming.TypeAliasCase
value: CamelCase
- key: readability-identifier-naming.PrivateMemberSuffix
value: '_'
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: CamelCase
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.PrivateMemberCase
value: lower_case
- key: readability-identifier-naming.ParameterCase
value: lower_case
- key: readability-identifier-naming.GlobalConstantPrefix
value: k
- key: readability-identifier-naming.GlobalConstantCase
value: CamelCase
- key: readability-identifier-naming.StaticConstantPrefix
value: k
- key: readability-identifier-naming.StaticConstantCase
value: CamelCase
- key: readability-identifier-naming.ConstexprVariableCase
value: CamelCase
- key: readability-identifier-naming.ConstexprVariablePrefix
value: k
- key: google-runtime-int.TypeSuffix
value: _t
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@
*.exe
*.out
*.app

# Build files
/build/
/build_debug/
compile_commands.json
/.cache/

# 3D Models
/data/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "submodules/stb"]
path = submodules/stb
url = https://github.com/nothings/stb.git
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.5)
project(3d-renderer)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавь строчку

set(CMAKE_CXX_STANDARD_REQUIRED True)

А то cmake разрешено откатиться на более низкий стандарт.

add_subdirectory(src)
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 3D Renderer
Курсовой проект по имплементации 3D рендерера с нуля.
## Скачивание
```shell
git clone --recurse-submodules git@github.com:rualss/3d-renderer.git
git checkout dev
git submodule update --init --recursive
```
## Сборка и запуск
Перед сборкой нужно установить зависимости SFML. Все остальные библиотеки подтянутся сами
```shell
sudo apt update && sudo apt install \
libxrandr-dev \
libxcursor-dev \
libxi-dev \
libudev-dev \
libflac-dev \
libvorbis-dev \
libgl1-mesa-dev \
libegl1-mesa-dev \
libdrm-dev \
libgbm-dev
```
Далее в корне репозитория нужно выполнить
```shell
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --parallel <количество потоков у процессора>
./renderer
```
## Управление
+ **W** - переместить камеру вперёд
+ **A** - переместить камеру влево
+ **S** - переместить камеру назад
+ **D** - переместить камеру вправо
+ **Up** - повернуть камеру вверх
+ **Down** - повернуть камеру вниз
+ **Right** - повернуть камеру вправо
+ **Left** - повернуть камеру влево
+ **Q** - наклонить камеру влево
+ **E** - наклонить камеру вправо

## Пример работы
![floppa](pictures/floppa.png)
Binary file added pictures/floppa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
project(3d-renderer)

include(sources.cmake)

add_subdirectory(app)

include(FetchContent)
FetchContent_Declare(
assimp
GIT_REPOSITORY https://github.com/assimp/assimp.git
GIT_TAG master
)
FetchContent_MakeAvailable(assimp)

FetchContent_Declare(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG master
)
FetchContent_MakeAvailable(glm)

target_include_directories(3d_pipeline_lib PRIVATE ${CMAKE_SOURCE_DIR}/submodules/stb)
target_link_libraries(3d_pipeline_lib PUBLIC glm::glm assimp::assimp)
11 changes: 11 additions & 0 deletions src/alias.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <cstdint>
namespace renderer {

enum class Axis { X, Y, Z };

enum Height : int32_t;
enum Width : int32_t;

} // namespace renderer
20 changes: 20 additions & 0 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
project(3d-renderer)

include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 3.0.0
GIT_SHALLOW ON
EXCLUDE_FROM_ALL
SYSTEM)
FetchContent_MakeAvailable(SFML)

add_executable(
renderer
main.cpp
application.cpp
timer.cpp
)

target_include_directories(renderer PUBLIC ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(renderer PRIVATE 3d_pipeline_lib SFML::Graphics)
116 changes: 116 additions & 0 deletions src/app/application.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "application.h"
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Window/Keyboard.hpp>
#include "camera.h"
#include "color.h"
#include "light.h"
#include "linalg.h"
#include "material.h"
#include "model_loader.h"
#include "object_3d.h"
#include "picture.h"
#include "polygon.h"
#include "world.h"
#include <SFML/Window/VideoMode.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/trigonometric.hpp>

namespace renderer {

namespace {

World ExampleScene() {
ModelLoader loader;
loader.Open("../data/floppa2.fbx");
Object3D floppa = loader.GetObject();
floppa.ApplyMatrix(glm::scale(Mat4(1.), {0.01, 0.01, 0.01}));
World world;
world.AddObject(floppa);
world.AddLight(DirectionalLight{});
return world;
}

} // namespace

Application::Application()
: world_(ExampleScene()),
window_(sf::VideoMode({static_cast<Index>(picture_.GetWidth()),
static_cast<Index>(picture_.GetHeight())}),
kDefaultName) {
}

void Application::Run() {
while (window_.isOpen()) {
window_.clear();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нарушение уровня абстракции. Это низкоуровневая фигня, которая является частью отрисовки фрейма, непонятно зачем это тут зовется. Это должно быть внутри функции draw frame.

HandleEvents();
HandleKeyboard();
RenderFrame();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Слишком специфичное название. У тебя выше две функции общего назначения, которые обслуживают run-time loop. А тут почему-то ты делаешь такую мелкую специфическую работу, как отрендерить фрейм. Это скорее всего должно быть частью какой-то другой общей работы.
  • Название метода врет, ты не просто рендеришь фрейм, ты его сначала создаешь, а потом отрисовываешь на экране. Не ври в названии методов.

Comment on lines +45 to +47
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну это просто феерия черных ящиков. Прям три закрытые шкатулки, угадайте где лежит миллион рублей. Блять, ты что в Поле Чудес переиграл? Как вообще понять, какой тут поток данных? Ты просто манипулируешь скрыто состоянием объекта Application и все. Не делай так.

}
}

void Application::HandleEvents() {
while (const std::optional event = window_.pollEvent()) {
if (event->is<sf::Event::Closed>()) {
window_.close();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Странно, что после этого ты не останавливаешь run-time loop, а делаешь вызовы к двух других функций.

}
}
}

void Application::HandleKeyboard() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Плохая функция, ты в ней смешиваешь две совершенно разные задачи:

  1. считать ввод пользователя
  2. отреагировать на ввод пользователя
    В итоге у тебя все в перемешку. Я бы видел это в таком ключе
Time elapsed = timer_.passed();
auto motion = read_input(); // это что-то, что понимает как двигать камеру
// теперь подвинуть камеру либо так
// camera_ = moveCamera(motion * elapsed, movement_speed, rotation_speed_, std::move(camera));
// либо так
camera_.move(motion * elapsed, movement_speed_, rotation_speed_);
// оба варианта так себе, но иначе нужен большой редизайн.
frame = renderer_.render(scnene_, camera_, std::move(frame));
drawFrame(frame);

И вот таким макаром у тебя понятно какие данные как текут, какие объекты какую работу делают и взаимодействие с пользователям отделено от обработки логики ядра приложения.

Time elapsed = timer_.Elapsed();
CoordType move_distance = movement_speed_ * elapsed.ToSeconds();
CoordType rotate_angle = rotation_speed_ * elapsed.ToSeconds();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У тебя тут магические константы -- имена клавиш. По-хорошему надо хранить таблицу с биндами имен клавишь на действия и тут транслировать эти действия. За это должен отвечать отдельный объект. Но этим НЕ надо сейчас заниматься.

camera_.Move(camera_.GetForwardDirection() * move_distance);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A)) {
camera_.Move(-camera_.GetRightDirecton() * move_distance);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S)) {
camera_.Move(-camera_.GetForwardDirection() * move_distance);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D)) {
camera_.Move(camera_.GetRightDirecton() * move_distance);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Space)) {
camera_.Move(camera_.GetUpDirection() * move_distance);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Z)) {
camera_.Move(-camera_.GetUpDirection() * move_distance);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up)) {
camera_.Rotate(Axis::X, rotate_angle);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down)) {
camera_.Rotate(Axis::X, -rotate_angle);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left)) {
camera_.Rotate(Axis::Y, rotate_angle);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right)) {
camera_.Rotate(Axis::Y, -rotate_angle);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::E)) {
camera_.Rotate(Axis::Z, rotate_angle);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Q)) {
camera_.Rotate(Axis::Z, -rotate_angle);
}
}

void Application::RenderFrame() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Название метода врет.

renderer_.Render(world_, camera_, &picture_);
pixels_.clear();
for (size_t x = 0; x < picture_.GetWidth(); ++x) {
for (size_t y = 0; y < picture_.GetHeight(); ++y) {
DiscreteColor color = picture_(x, y);
if (color != kBlackDiscrete) {
pixels_.emplace_back(sf::Vector2f(x, y), sf::Color(color.x, color.y, color.z));
}
}
}
window_.draw(pixels_.data(), pixels_.size(), sf::PrimitiveType::Points);
window_.display();
Comment on lines +112 to +113
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот это вместе с windows_.clear() является одним вызовом метода draw. Так же конвертация picture в pixels является деталью метода drawFrame.

}

} // namespace renderer
42 changes: 42 additions & 0 deletions src/app/application.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include "camera.h"
#include "linalg.h"
#include "picture.h"
#include "renderer.h"
#include "world.h"
#include "timer.h"

namespace renderer {

class Application {
using Window = sf::RenderWindow;
using Index = uint32_t; // SFML default size type, there is no sf::size_type

public:
Application();
void Run();

private:
void HandleEvents();
void HandleKeyboard();
void RenderFrame();

static constexpr CoordType kDefaultMovementSpeed = 50.;
static constexpr CoordType kDefaultRotationSpeed = 80.;
static constexpr std::string kDefaultName = "3D renderer";

Renderer renderer_;
Camera camera_;
World world_;
Picture picture_;
Window window_;
CoordType movement_speed_ = kDefaultMovementSpeed;
CoordType rotation_speed_ = kDefaultRotationSpeed;
std::vector<sf::Vertex> pixels_;
Timer timer_;
};

} // namespace renderer
16 changes: 16 additions & 0 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <exception>
#include <iostream>

#include "application.h"

using namespace renderer;

int main() {
try {
Application app;
app.Run();
} catch (std::exception& e) {
std::cout << e.what() << '\n';
} catch (...) {
}
}
Loading