|
|
|
|
@@ -3,9 +3,9 @@
|
|
|
|
|
#include <cfloat>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <random>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <glm/glm.hpp>
|
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
|
|
|
#include <imgui.h>
|
|
|
|
|
#include <imgui_internal.h>
|
|
|
|
|
@@ -14,6 +14,20 @@
|
|
|
|
|
|
|
|
|
|
using namespace Fan;
|
|
|
|
|
|
|
|
|
|
/******************** Constants ********************/
|
|
|
|
|
|
|
|
|
|
// Random lights are scattered inside this box in every scene
|
|
|
|
|
const glm::vec3 LIGHT_BOX_MIN{-10.0f, 1.0f, -3.0f};
|
|
|
|
|
const glm::vec3 LIGHT_BOX_MAX{10.0f, 5.0f, 3.0f};
|
|
|
|
|
|
|
|
|
|
// Overlay text in the top-left corner of the viewport
|
|
|
|
|
const ImVec4 OVERLAY_COLOR{0.0f, 1.0f, 0.0f, 1.0f};
|
|
|
|
|
constexpr float OVERLAY_X = 20.0f;
|
|
|
|
|
constexpr float OVERLAY_Y = 35.0f;
|
|
|
|
|
constexpr float OVERLAY_LINE_HEIGHT = 17.0f;
|
|
|
|
|
|
|
|
|
|
/******************** Application state ********************/
|
|
|
|
|
|
|
|
|
|
// Ring buffer of per-frame measurements, for plotting and stable averages
|
|
|
|
|
struct MetricHistory
|
|
|
|
|
{
|
|
|
|
|
@@ -42,47 +56,72 @@ struct MetricHistory
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct UserData
|
|
|
|
|
// Free-fly camera controls, active while the mouse is grabbed
|
|
|
|
|
struct CameraInput
|
|
|
|
|
{
|
|
|
|
|
Window& window;
|
|
|
|
|
Camera& camera;
|
|
|
|
|
float delta_time{};
|
|
|
|
|
bool mouse_grab{};
|
|
|
|
|
|
|
|
|
|
float speed{CAMERA_SPEED};
|
|
|
|
|
float sensitivity{CAMERA_SENSITIVITY};
|
|
|
|
|
|
|
|
|
|
bool mouse_grab{};
|
|
|
|
|
bool first_mouse{true}; // Skips the jump from the cursor's position before grabbing
|
|
|
|
|
float last_mouse_x{};
|
|
|
|
|
float last_mouse_y{};
|
|
|
|
|
bool first_mouse{true};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool vsync{true};
|
|
|
|
|
bool show_debug{};
|
|
|
|
|
// Timings of the scene pass (gpu_timer is null if timer queries are unsupported)
|
|
|
|
|
struct PerformanceStats
|
|
|
|
|
{
|
|
|
|
|
CpuTimer cpu_timer{};
|
|
|
|
|
Rc<GpuTimer> gpu_timer{};
|
|
|
|
|
|
|
|
|
|
Rc<Texture> logo{};
|
|
|
|
|
|
|
|
|
|
// Performance measurements of the scene pass (GPU timer is null if unsupported)
|
|
|
|
|
CpuTimer scene_cpu_timer{};
|
|
|
|
|
Rc<GpuTimer> scene_gpu_timer{};
|
|
|
|
|
MetricHistory frame_times{};
|
|
|
|
|
MetricHistory scene_cpu_times{};
|
|
|
|
|
MetricHistory scene_gpu_times{};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct AppState
|
|
|
|
|
{
|
|
|
|
|
Window& window;
|
|
|
|
|
Camera& camera;
|
|
|
|
|
|
|
|
|
|
CameraInput input{};
|
|
|
|
|
PerformanceStats stats{};
|
|
|
|
|
|
|
|
|
|
bool vsync{true};
|
|
|
|
|
bool show_debug{};
|
|
|
|
|
Rc<Texture> logo{};
|
|
|
|
|
|
|
|
|
|
std::vector<Rc<Scene>> scenes{};
|
|
|
|
|
size_t selected_scene_index{};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void process_input(Window& window, Camera& camera, UserData& user_data);
|
|
|
|
|
/******************** Declarations ********************/
|
|
|
|
|
|
|
|
|
|
int run(Window& window);
|
|
|
|
|
|
|
|
|
|
// Scenes
|
|
|
|
|
std::vector<Rc<Scene>> create_scenes(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh);
|
|
|
|
|
Rc<Scene> create_sponza_scene(const Rc<Shader>& shader);
|
|
|
|
|
Rc<Scene> create_car_scene(const Rc<Shader>& shader);
|
|
|
|
|
Rc<Scene> create_backpack_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh);
|
|
|
|
|
Rc<Scene> create_cubes_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh);
|
|
|
|
|
Rc<Material> create_textured_material(const Rc<Shader>& shader, const std::string& texture_path);
|
|
|
|
|
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count, unsigned int seed);
|
|
|
|
|
|
|
|
|
|
// Frame
|
|
|
|
|
void process_input(AppState& app);
|
|
|
|
|
void mouse_callback(double pos_x, double pos_y, void* user_data);
|
|
|
|
|
void render_scene(AppState& app, FrameBuffer& framebuffer, const Rc<Shader>& debug_shader);
|
|
|
|
|
|
|
|
|
|
// UI
|
|
|
|
|
void setup_dockspace();
|
|
|
|
|
void draw_panel_window(UserData& user_data);
|
|
|
|
|
void draw_viewport_window(FrameBuffer& framebuffer, UserData& user_data);
|
|
|
|
|
void draw_panel_window(AppState& app);
|
|
|
|
|
void draw_viewport_window(AppState& app, FrameBuffer& framebuffer);
|
|
|
|
|
void draw_overlay(const AppState& app);
|
|
|
|
|
void plot_metric(const char* label, const MetricHistory& history);
|
|
|
|
|
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count,
|
|
|
|
|
const glm::vec3& box_min, const glm::vec3& box_max,
|
|
|
|
|
unsigned int seed = std::random_device{}());
|
|
|
|
|
|
|
|
|
|
template<typename... Args>
|
|
|
|
|
void overlay_text(float offset_x, float offset_y, ImVec4 color, const char* format, Args... args)
|
|
|
|
|
void overlay_text(float offset_x, float offset_y, const char* format, Args... args)
|
|
|
|
|
{
|
|
|
|
|
char buffer[256];
|
|
|
|
|
std::snprintf(buffer, sizeof(buffer), format, args...);
|
|
|
|
|
@@ -92,102 +131,132 @@ void overlay_text(float offset_x, float offset_y, ImVec4 color, const char* form
|
|
|
|
|
ImVec2 text_size = ImGui::CalcTextSize(buffer);
|
|
|
|
|
|
|
|
|
|
draw_list->AddRectFilled(pos - ImVec2{4, 2}, pos + text_size + ImVec2{4, 2}, IM_COL32(0, 0, 0, 160));
|
|
|
|
|
draw_list->AddText(pos, ImGui::ColorConvertFloat4ToU32(color), buffer);
|
|
|
|
|
draw_list->AddText(pos, ImGui::ColorConvertFloat4ToU32(OVERLAY_COLOR), buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************** Entry point ********************/
|
|
|
|
|
|
|
|
|
|
int main()
|
|
|
|
|
{
|
|
|
|
|
/******************** Initialization ********************/
|
|
|
|
|
|
|
|
|
|
auto window = Window::create(WINDOW_WIDTH, WINDOW_HEIGHT, VERSION_STRING);
|
|
|
|
|
if (!window) return EXIT_FAILURE;
|
|
|
|
|
window->set_vsync(true);
|
|
|
|
|
|
|
|
|
|
if (!Renderer::init()) return EXIT_FAILURE;
|
|
|
|
|
|
|
|
|
|
// Every GL resource created by run() is released when it returns,
|
|
|
|
|
// before the renderer and the window shut down
|
|
|
|
|
int exit_code = run(*window);
|
|
|
|
|
|
|
|
|
|
Renderer::shutdown();
|
|
|
|
|
return exit_code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int run(Window& window)
|
|
|
|
|
{
|
|
|
|
|
Camera camera{{6.0f, 3.0f, 1.0f}};
|
|
|
|
|
camera.fov = 60.0f;
|
|
|
|
|
camera.look_at({0.0f, 1.0f, 0.0f});
|
|
|
|
|
|
|
|
|
|
UserData user_data{*window, camera};
|
|
|
|
|
window->set_callback_user_data(&user_data);
|
|
|
|
|
window->set_mouse_callback(mouse_callback);
|
|
|
|
|
|
|
|
|
|
/******************** Resource loading ********************/
|
|
|
|
|
AppState app{window, camera};
|
|
|
|
|
window.set_callback_user_data(&app);
|
|
|
|
|
window.set_mouse_callback(mouse_callback);
|
|
|
|
|
|
|
|
|
|
// Assets never fail to load: missing ones are replaced by fallbacks
|
|
|
|
|
auto shader = ResourceCache::load_shader("./src/shaders/default.vert", "./src/shaders/default.frag");
|
|
|
|
|
auto debug_shader = ResourceCache::load_shader("./src/shaders/debug.vert", "./src/shaders/debug.frag", false);
|
|
|
|
|
|
|
|
|
|
Entity sponza = load_obj("./assets/sponza/sponza.obj", shader);
|
|
|
|
|
Entity car = load_obj("./assets/audi_r8/Audi_R8_2017.obj", shader);
|
|
|
|
|
Entity backpack = load_obj("./assets/backpack/backpack.obj", shader, true);
|
|
|
|
|
|
|
|
|
|
auto cube_texture = ResourceCache::load_texture("./assets/textures/container.jpg");
|
|
|
|
|
auto default_texture = ResourceCache::load_texture("./assets/textures/wall.jpg");
|
|
|
|
|
user_data.logo = ResourceCache::load_texture("assets/logo/logo.png");
|
|
|
|
|
|
|
|
|
|
user_data.scene_gpu_timer = GpuTimer::create();
|
|
|
|
|
|
|
|
|
|
auto cube_mesh = Mesh::create_cube();
|
|
|
|
|
auto framebuffer = FrameBuffer::create(window->get_width(), window->get_height());
|
|
|
|
|
app.logo = ResourceCache::load_texture("assets/logo/logo.png");
|
|
|
|
|
app.stats.gpu_timer = GpuTimer::create();
|
|
|
|
|
|
|
|
|
|
// Built from memory or sized from the window, failing here means OpenGL is broken
|
|
|
|
|
if (!cube_mesh || !framebuffer) {
|
|
|
|
|
Renderer::shutdown();
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
auto cube_mesh = Mesh::create_cube();
|
|
|
|
|
auto framebuffer = FrameBuffer::create(window.get_width(), window.get_height());
|
|
|
|
|
if (!cube_mesh || !framebuffer) return EXIT_FAILURE;
|
|
|
|
|
|
|
|
|
|
app.scenes = create_scenes(shader, cube_mesh);
|
|
|
|
|
|
|
|
|
|
while (!window.should_close()) {
|
|
|
|
|
window.begin();
|
|
|
|
|
window.update();
|
|
|
|
|
|
|
|
|
|
app.stats.frame_times.push(window.get_delta_time() * 1000.0f);
|
|
|
|
|
process_input(app);
|
|
|
|
|
|
|
|
|
|
Renderer::set_clear_color({0.0f, 0.0f, 0.0f, 1.0f});
|
|
|
|
|
Renderer::clear();
|
|
|
|
|
|
|
|
|
|
// UI layer
|
|
|
|
|
setup_dockspace();
|
|
|
|
|
draw_panel_window(app);
|
|
|
|
|
draw_viewport_window(app, *framebuffer);
|
|
|
|
|
|
|
|
|
|
// Scene layer, shown by the viewport window
|
|
|
|
|
render_scene(app, *framebuffer, debug_shader);
|
|
|
|
|
|
|
|
|
|
window.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************** Scene construction ********************/
|
|
|
|
|
// app is about to be destroyed, don't let the window call back into it
|
|
|
|
|
window.set_mouse_callback(nullptr);
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sponza scene
|
|
|
|
|
Rc<Scene> sponza_scene = make_rc<Scene>();
|
|
|
|
|
user_data.scenes.push_back(sponza_scene);
|
|
|
|
|
/******************** Scenes ********************/
|
|
|
|
|
|
|
|
|
|
std::vector<Rc<Scene>> create_scenes(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh)
|
|
|
|
|
{
|
|
|
|
|
return {
|
|
|
|
|
create_sponza_scene(shader),
|
|
|
|
|
create_car_scene(shader),
|
|
|
|
|
create_backpack_scene(shader, cube_mesh),
|
|
|
|
|
create_cubes_scene(shader, cube_mesh),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Rc<Scene> create_sponza_scene(const Rc<Shader>& shader)
|
|
|
|
|
{
|
|
|
|
|
auto scene = make_rc<Scene>();
|
|
|
|
|
|
|
|
|
|
Entity sponza = load_obj("./assets/sponza/sponza.obj", shader);
|
|
|
|
|
sponza.transform.position = {0.0f, -0.5f, 0.0f};
|
|
|
|
|
sponza.transform.scale = {0.008f, 0.008f, 0.008f};
|
|
|
|
|
sponza_scene->add_entity(sponza);
|
|
|
|
|
scene->add_entity(sponza);
|
|
|
|
|
|
|
|
|
|
add_random_lights(*sponza_scene, 4, 4,
|
|
|
|
|
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
|
|
|
|
69420);
|
|
|
|
|
add_random_lights(*scene, 4, 4, 69420);
|
|
|
|
|
return scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Car scene
|
|
|
|
|
Rc<Scene> car_scene = make_rc<Scene>();
|
|
|
|
|
user_data.scenes.push_back(car_scene);
|
|
|
|
|
Rc<Scene> create_car_scene(const Rc<Shader>& shader)
|
|
|
|
|
{
|
|
|
|
|
auto scene = make_rc<Scene>();
|
|
|
|
|
|
|
|
|
|
car_scene->add_entity(car);
|
|
|
|
|
scene->add_entity(load_obj("./assets/audi_r8/Audi_R8_2017.obj", shader));
|
|
|
|
|
|
|
|
|
|
add_random_lights(*car_scene, 4, 4,
|
|
|
|
|
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
|
|
|
|
42);
|
|
|
|
|
add_random_lights(*scene, 4, 4, 42);
|
|
|
|
|
return scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Backpack scene
|
|
|
|
|
Rc<Scene> backpack_scene = make_rc<Scene>();
|
|
|
|
|
user_data.scenes.push_back(backpack_scene);
|
|
|
|
|
Rc<Scene> create_backpack_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh)
|
|
|
|
|
{
|
|
|
|
|
auto scene = make_rc<Scene>();
|
|
|
|
|
|
|
|
|
|
backpack_scene->add_entity(backpack);
|
|
|
|
|
scene->add_entity(load_obj("./assets/backpack/backpack.obj", shader, true));
|
|
|
|
|
|
|
|
|
|
auto default_material = make_rc<Material>();
|
|
|
|
|
default_material->shader = shader;
|
|
|
|
|
default_material->diffuse_map = default_texture;
|
|
|
|
|
Entity floor{cube_mesh, default_material};
|
|
|
|
|
Entity floor{cube_mesh, create_textured_material(shader, "./assets/textures/wall.jpg")};
|
|
|
|
|
floor.transform.position = {0.0f, -3.0f, 0.0f};
|
|
|
|
|
floor.transform.scale = {20.0f, 1.0f, 20.0f};
|
|
|
|
|
backpack_scene->add_entity(floor);
|
|
|
|
|
scene->add_entity(floor);
|
|
|
|
|
|
|
|
|
|
add_random_lights(*backpack_scene, 4, 4,
|
|
|
|
|
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
|
|
|
|
37);
|
|
|
|
|
add_random_lights(*scene, 4, 4, 37);
|
|
|
|
|
return scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cubes scene
|
|
|
|
|
Rc<Scene> cubes_scene = make_rc<Scene>();
|
|
|
|
|
user_data.scenes.push_back(cubes_scene);
|
|
|
|
|
Rc<Scene> create_cubes_scene(const Rc<Shader>& shader, const Rc<Mesh>& cube_mesh)
|
|
|
|
|
{
|
|
|
|
|
auto scene = make_rc<Scene>();
|
|
|
|
|
auto material = create_textured_material(shader, "./assets/textures/container.jpg");
|
|
|
|
|
|
|
|
|
|
auto cube_material = make_rc<Material>();
|
|
|
|
|
cube_material->shader = shader;
|
|
|
|
|
cube_material->diffuse_map = cube_texture;
|
|
|
|
|
|
|
|
|
|
std::vector<glm::vec3> positions{
|
|
|
|
|
const std::vector<glm::vec3> positions{
|
|
|
|
|
{ 0.0f, 0.0f, 0.0f}, { 2.0f, 5.0f, -15.0f}, {-1.5f, -2.2f, -2.5f},
|
|
|
|
|
{-3.8f, -2.0f, -12.3f}, { 2.4f, -0.4f, -3.5f}, {-1.7f, 3.0f, -7.5f},
|
|
|
|
|
{ 1.3f, -2.0f, -2.5f}, { 1.5f, 2.0f, -2.5f}, { 1.5f, 0.2f, -1.5f},
|
|
|
|
|
@@ -195,92 +264,110 @@ int main()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < positions.size(); ++i) {
|
|
|
|
|
Entity entity{cube_mesh, cube_material};
|
|
|
|
|
entity.transform.position = positions[i];
|
|
|
|
|
entity.transform.set_rotation(glm::radians(20.0f * i), {1.0f, 0.3f, 0.5f});
|
|
|
|
|
cubes_scene->add_entity(entity);
|
|
|
|
|
Entity cube{cube_mesh, material};
|
|
|
|
|
cube.transform.position = positions[i];
|
|
|
|
|
cube.transform.set_rotation(glm::radians(20.0f * i), {1.0f, 0.3f, 0.5f});
|
|
|
|
|
scene->add_entity(cube);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_random_lights(*cubes_scene, 4, 4,
|
|
|
|
|
glm::vec3(-10.0f, 1.0f, -3.0f), glm::vec3(10.0f, 5.0f, 3.0f),
|
|
|
|
|
152533);
|
|
|
|
|
|
|
|
|
|
/******************** Main loop ********************/
|
|
|
|
|
|
|
|
|
|
while (!window->should_close()) {
|
|
|
|
|
window->begin();
|
|
|
|
|
window->update();
|
|
|
|
|
|
|
|
|
|
user_data.delta_time = window->get_delta_time();
|
|
|
|
|
user_data.frame_times.push(user_data.delta_time * 1000.0f);
|
|
|
|
|
process_input(*window, camera, user_data);
|
|
|
|
|
|
|
|
|
|
Renderer::set_clear_color({0.0f, 0.0f, 0.0f, 1.0f});
|
|
|
|
|
Renderer::clear();
|
|
|
|
|
|
|
|
|
|
// UI Layer
|
|
|
|
|
setup_dockspace();
|
|
|
|
|
draw_panel_window(user_data);
|
|
|
|
|
draw_viewport_window(*framebuffer, user_data);
|
|
|
|
|
|
|
|
|
|
// Scene Rendering Layer
|
|
|
|
|
const Scene& scene = *user_data.scenes[user_data.selected_scene_index];
|
|
|
|
|
|
|
|
|
|
int width = framebuffer->get_width();
|
|
|
|
|
int height = framebuffer->get_height();
|
|
|
|
|
framebuffer->bind();
|
|
|
|
|
user_data.scene_cpu_timer.begin();
|
|
|
|
|
if (user_data.scene_gpu_timer) { user_data.scene_gpu_timer->begin(); }
|
|
|
|
|
|
|
|
|
|
Renderer::set_viewport(0, 0, width, height);
|
|
|
|
|
Renderer::clear();
|
|
|
|
|
Renderer::draw_scene(scene, camera, width, height);
|
|
|
|
|
if (user_data.show_debug) {
|
|
|
|
|
Renderer::draw_debug_wireframes(scene, camera, width, height,
|
|
|
|
|
debug_shader, ResourceCache::get_fallback_mesh());
|
|
|
|
|
add_random_lights(*scene, 4, 4, 152533);
|
|
|
|
|
return scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (user_data.scene_gpu_timer) { user_data.scene_gpu_timer->end(); }
|
|
|
|
|
user_data.scene_cpu_timer.end();
|
|
|
|
|
framebuffer->unbind();
|
|
|
|
|
|
|
|
|
|
user_data.scene_cpu_times.push(user_data.scene_cpu_timer.get_elapsed_ms());
|
|
|
|
|
if (user_data.scene_gpu_timer) {
|
|
|
|
|
user_data.scene_gpu_times.push(user_data.scene_gpu_timer->get_elapsed_ms());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window->end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Renderer::shutdown();
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void process_input(Window& window, Camera& camera, UserData& user_data)
|
|
|
|
|
Rc<Material> create_textured_material(const Rc<Shader>& shader, const std::string& texture_path)
|
|
|
|
|
{
|
|
|
|
|
auto material = make_rc<Material>();
|
|
|
|
|
material->shader = shader;
|
|
|
|
|
material->diffuse_map = ResourceCache::load_texture(texture_path);
|
|
|
|
|
return material;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count, unsigned int seed)
|
|
|
|
|
{
|
|
|
|
|
std::mt19937 rng(seed);
|
|
|
|
|
|
|
|
|
|
std::uniform_real_distribution<float> random_x(LIGHT_BOX_MIN.x, LIGHT_BOX_MAX.x);
|
|
|
|
|
std::uniform_real_distribution<float> random_y(LIGHT_BOX_MIN.y, LIGHT_BOX_MAX.y);
|
|
|
|
|
std::uniform_real_distribution<float> random_z(LIGHT_BOX_MIN.z, LIGHT_BOX_MAX.z);
|
|
|
|
|
std::uniform_real_distribution<float> random_axis(-1.0f, 1.0f);
|
|
|
|
|
|
|
|
|
|
const glm::vec3 light_colors[] = {
|
|
|
|
|
glm::vec3(1.0f, 1.0f, 1.0f), // White
|
|
|
|
|
glm::vec3(1.0f, 0.6f, 0.2f), // Warm orange
|
|
|
|
|
glm::vec3(0.2f, 0.5f, 1.0f), // Cool blue
|
|
|
|
|
glm::vec3(1.0f, 0.2f, 0.2f), // Red
|
|
|
|
|
glm::vec3(0.3f, 1.0f, 0.4f), // Green
|
|
|
|
|
glm::vec3(0.8f, 0.3f, 1.0f) // Purple
|
|
|
|
|
};
|
|
|
|
|
std::uniform_int_distribution<size_t> random_color_index(0, std::size(light_colors) - 1);
|
|
|
|
|
|
|
|
|
|
auto random_position = [&]() {
|
|
|
|
|
return glm::vec3(random_x(rng), random_y(rng), random_z(rng));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto random_direction = [&]() {
|
|
|
|
|
glm::vec3 direction;
|
|
|
|
|
|
|
|
|
|
// Avoid generating a vector that is too close to zero
|
|
|
|
|
do {
|
|
|
|
|
direction = glm::vec3(random_axis(rng), random_axis(rng), random_axis(rng));
|
|
|
|
|
} while (glm::dot(direction, direction) < 0.001f);
|
|
|
|
|
|
|
|
|
|
return glm::normalize(direction);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto random_color = [&]() { return light_colors[random_color_index(rng)]; };
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < point_light_count; ++i) {
|
|
|
|
|
PointLight light;
|
|
|
|
|
light.position = random_position();
|
|
|
|
|
|
|
|
|
|
glm::vec3 color = random_color();
|
|
|
|
|
light.diffuse = color;
|
|
|
|
|
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
|
|
|
|
|
|
|
|
|
scene.light_environment.point_lights.push_back(light);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < spot_light_count; ++i) {
|
|
|
|
|
SpotLight light;
|
|
|
|
|
light.position = random_position();
|
|
|
|
|
light.direction = random_direction();
|
|
|
|
|
|
|
|
|
|
glm::vec3 color = random_color();
|
|
|
|
|
light.diffuse = color;
|
|
|
|
|
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
|
|
|
|
|
|
|
|
|
// Slightly wider soft outer cone than the default
|
|
|
|
|
light.outer_cut_off = glm::cos(glm::radians(17.5f));
|
|
|
|
|
|
|
|
|
|
scene.light_environment.spot_lights.push_back(light);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************** Frame ********************/
|
|
|
|
|
|
|
|
|
|
void process_input(AppState& app)
|
|
|
|
|
{
|
|
|
|
|
Window& window = app.window;
|
|
|
|
|
CameraInput& input = app.input;
|
|
|
|
|
|
|
|
|
|
if (window.is_key_pressed(GLFW_KEY_ESCAPE)) window.set_should_close(true);
|
|
|
|
|
|
|
|
|
|
static bool wireframe = false;
|
|
|
|
|
if (window.is_key_pressed(GLFW_KEY_H)) {
|
|
|
|
|
wireframe = !wireframe;
|
|
|
|
|
Renderer::set_wireframe(wireframe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool fullscreen = false;
|
|
|
|
|
if (window.is_key_pressed(GLFW_KEY_F11)) {
|
|
|
|
|
fullscreen = !fullscreen;
|
|
|
|
|
window.set_fullscreen(fullscreen);
|
|
|
|
|
}
|
|
|
|
|
if (window.is_key_pressed(GLFW_KEY_H)) Renderer::set_wireframe(!Renderer::is_wireframe());
|
|
|
|
|
if (window.is_key_pressed(GLFW_KEY_F11)) window.set_fullscreen(!window.is_fullscreen());
|
|
|
|
|
|
|
|
|
|
if (window.is_key_pressed(GLFW_KEY_INSERT)) {
|
|
|
|
|
user_data.mouse_grab = !user_data.mouse_grab;
|
|
|
|
|
user_data.first_mouse = true;
|
|
|
|
|
window.set_mouse_grab(user_data.mouse_grab);
|
|
|
|
|
input.mouse_grab = !input.mouse_grab;
|
|
|
|
|
input.first_mouse = true;
|
|
|
|
|
window.set_mouse_grab(input.mouse_grab);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!user_data.mouse_grab) return;
|
|
|
|
|
if (!input.mouse_grab) return;
|
|
|
|
|
|
|
|
|
|
Camera& camera = app.camera;
|
|
|
|
|
float boost = window.is_key_down(GLFW_KEY_LEFT_SHIFT) ? 2.0f : 1.0f;
|
|
|
|
|
float velocity = input.speed * window.get_delta_time() * boost;
|
|
|
|
|
|
|
|
|
|
float velocity = user_data.speed * user_data.delta_time * (window.is_key_down(GLFW_KEY_LEFT_SHIFT) ? 2.0f : 1.0f);
|
|
|
|
|
if (window.is_key_down(GLFW_KEY_W)) camera.position += camera.get_front() * velocity;
|
|
|
|
|
if (window.is_key_down(GLFW_KEY_S)) camera.position -= camera.get_front() * velocity;
|
|
|
|
|
if (window.is_key_down(GLFW_KEY_A)) camera.position -= camera.get_right() * velocity;
|
|
|
|
|
@@ -289,28 +376,60 @@ void process_input(Window& window, Camera& camera, UserData& user_data)
|
|
|
|
|
|
|
|
|
|
void mouse_callback(double pos_x, double pos_y, void* user_data)
|
|
|
|
|
{
|
|
|
|
|
auto* data = static_cast<UserData*>(user_data);
|
|
|
|
|
if (!data->mouse_grab) return;
|
|
|
|
|
AppState& app = *static_cast<AppState*>(user_data);
|
|
|
|
|
CameraInput& input = app.input;
|
|
|
|
|
if (!input.mouse_grab) return;
|
|
|
|
|
|
|
|
|
|
if (data->first_mouse) {
|
|
|
|
|
data->last_mouse_x = pos_x;
|
|
|
|
|
data->last_mouse_y = pos_y;
|
|
|
|
|
data->first_mouse = false;
|
|
|
|
|
if (input.first_mouse) {
|
|
|
|
|
input.last_mouse_x = pos_x;
|
|
|
|
|
input.last_mouse_y = pos_y;
|
|
|
|
|
input.first_mouse = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float delta_x = (pos_x - data->last_mouse_x) * data->sensitivity;
|
|
|
|
|
float delta_y = (data->last_mouse_y - pos_y) * data->sensitivity;
|
|
|
|
|
float delta_x = (pos_x - input.last_mouse_x) * input.sensitivity;
|
|
|
|
|
float delta_y = (input.last_mouse_y - pos_y) * input.sensitivity;
|
|
|
|
|
|
|
|
|
|
data->last_mouse_x = pos_x;
|
|
|
|
|
data->last_mouse_y = pos_y;
|
|
|
|
|
data->camera.rotate(delta_x, delta_y);
|
|
|
|
|
input.last_mouse_x = pos_x;
|
|
|
|
|
input.last_mouse_y = pos_y;
|
|
|
|
|
app.camera.rotate(delta_x, delta_y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void render_scene(AppState& app, FrameBuffer& framebuffer, const Rc<Shader>& debug_shader)
|
|
|
|
|
{
|
|
|
|
|
const Scene& scene = *app.scenes[app.selected_scene_index];
|
|
|
|
|
PerformanceStats& stats = app.stats;
|
|
|
|
|
|
|
|
|
|
int width = framebuffer.get_width();
|
|
|
|
|
int height = framebuffer.get_height();
|
|
|
|
|
|
|
|
|
|
framebuffer.bind();
|
|
|
|
|
stats.cpu_timer.begin();
|
|
|
|
|
if (stats.gpu_timer) { stats.gpu_timer->begin(); }
|
|
|
|
|
|
|
|
|
|
Renderer::set_viewport(0, 0, width, height);
|
|
|
|
|
Renderer::clear();
|
|
|
|
|
Renderer::draw_scene(scene, app.camera, width, height);
|
|
|
|
|
if (app.show_debug) {
|
|
|
|
|
Renderer::draw_debug_wireframes(scene, app.camera, width, height,
|
|
|
|
|
debug_shader, ResourceCache::get_fallback_mesh());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stats.gpu_timer) { stats.gpu_timer->end(); }
|
|
|
|
|
stats.cpu_timer.end();
|
|
|
|
|
framebuffer.unbind();
|
|
|
|
|
|
|
|
|
|
stats.scene_cpu_times.push(stats.cpu_timer.get_elapsed_ms());
|
|
|
|
|
if (stats.gpu_timer) { stats.scene_gpu_times.push(stats.gpu_timer->get_elapsed_ms()); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************** UI ********************/
|
|
|
|
|
|
|
|
|
|
void setup_dockspace()
|
|
|
|
|
{
|
|
|
|
|
ImGuiID dockspace_id = ImGui::GetID("Dockspace");
|
|
|
|
|
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
|
|
|
|
|
|
|
|
// Build the default layout once: panel on the left, viewport on the right
|
|
|
|
|
static bool first_time = true;
|
|
|
|
|
if (first_time) {
|
|
|
|
|
first_time = false;
|
|
|
|
|
@@ -329,44 +448,45 @@ void setup_dockspace()
|
|
|
|
|
ImGui::DockSpaceOverViewport(dockspace_id, viewport, ImGuiDockNodeFlags_None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_panel_window(UserData& user_data)
|
|
|
|
|
void draw_panel_window(AppState& app)
|
|
|
|
|
{
|
|
|
|
|
ImGui::Begin("Panel");
|
|
|
|
|
|
|
|
|
|
if (user_data.logo) ImGui::Image(user_data.logo->get_id(), {50.0f, 50.0f}, {0.0f, 1.0f}, {1.0f, 0.0f});
|
|
|
|
|
// Header: logo with the title vertically centered next to it
|
|
|
|
|
if (app.logo) ImGui::Image(app.logo->get_id(), {50.0f, 50.0f}, {0.0f, 1.0f}, {1.0f, 0.0f});
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (50.0f - ImGui::GetTextLineHeight()) * 0.5f);
|
|
|
|
|
ImGui::Text("Fan Engine");
|
|
|
|
|
|
|
|
|
|
ImGui::SeparatorText("General Settings");
|
|
|
|
|
|
|
|
|
|
if (ImGui::Checkbox("VSync", &user_data.vsync)) {
|
|
|
|
|
user_data.window.set_vsync(user_data.vsync);
|
|
|
|
|
if (ImGui::Checkbox("VSync", &app.vsync)) {
|
|
|
|
|
app.window.set_vsync(app.vsync);
|
|
|
|
|
}
|
|
|
|
|
ImGui::Checkbox("Debug Wireframes", &user_data.show_debug);
|
|
|
|
|
ImGui::Checkbox("Debug Wireframes", &app.show_debug);
|
|
|
|
|
|
|
|
|
|
ImGui::SeparatorText("Scene Selector");
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < user_data.scenes.size(); ++i) {
|
|
|
|
|
for (size_t i = 0; i < app.scenes.size(); ++i) {
|
|
|
|
|
char label[64];
|
|
|
|
|
ImFormatString(label, IM_ARRAYSIZE(label), "Scene %zu", i);
|
|
|
|
|
if (ImGui::RadioButton(label, user_data.selected_scene_index == i)) {
|
|
|
|
|
user_data.selected_scene_index = i;
|
|
|
|
|
if (ImGui::RadioButton(label, app.selected_scene_index == i)) {
|
|
|
|
|
app.selected_scene_index = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::SeparatorText("Camera Settings");
|
|
|
|
|
|
|
|
|
|
ImGui::DragFloat3("Position", &user_data.camera.position.x, 0.1f, -20.0f, 20.0f, "%.1f");
|
|
|
|
|
ImGui::SliderFloat("FOV", &user_data.camera.fov, 30.0f, 100.0f, "%.1f");
|
|
|
|
|
ImGui::DragFloat3("Position", &app.camera.position.x, 0.1f, -20.0f, 20.0f, "%.1f");
|
|
|
|
|
ImGui::SliderFloat("FOV", &app.camera.fov, 30.0f, 100.0f, "%.1f");
|
|
|
|
|
|
|
|
|
|
ImGui::SeparatorText("Performance");
|
|
|
|
|
|
|
|
|
|
ImGui::TextDisabled("Disable VSync for accurate frame times");
|
|
|
|
|
plot_metric("Frame", user_data.frame_times);
|
|
|
|
|
plot_metric("Scene CPU", user_data.scene_cpu_times);
|
|
|
|
|
if (user_data.scene_gpu_timer) {
|
|
|
|
|
plot_metric("Scene GPU", user_data.scene_gpu_times);
|
|
|
|
|
plot_metric("Frame", app.stats.frame_times);
|
|
|
|
|
plot_metric("Scene CPU", app.stats.scene_cpu_times);
|
|
|
|
|
if (app.stats.gpu_timer) {
|
|
|
|
|
plot_metric("Scene GPU", app.stats.scene_gpu_times);
|
|
|
|
|
} else {
|
|
|
|
|
ImGui::TextDisabled("Scene GPU: timer queries not supported");
|
|
|
|
|
}
|
|
|
|
|
@@ -382,7 +502,7 @@ void draw_panel_window(UserData& user_data)
|
|
|
|
|
ImGui::End();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_viewport_window(FrameBuffer& framebuffer, UserData& user_data)
|
|
|
|
|
void draw_viewport_window(AppState& app, FrameBuffer& framebuffer)
|
|
|
|
|
{
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f, 0.0f});
|
|
|
|
|
ImGui::Begin("Viewport");
|
|
|
|
|
@@ -391,32 +511,48 @@ void draw_viewport_window(FrameBuffer& framebuffer, UserData& user_data)
|
|
|
|
|
if (size.x > 0 && size.y > 0) {
|
|
|
|
|
// Size the framebuffer in physical pixels so HiDPI displays render at full resolution
|
|
|
|
|
ImVec2 scale = ImGui::GetIO().DisplayFramebufferScale;
|
|
|
|
|
GLsizei fb_width = static_cast<GLsizei>(size.x * scale.x);
|
|
|
|
|
GLsizei fb_height = static_cast<GLsizei>(size.y * scale.y);
|
|
|
|
|
if (fb_width != framebuffer.get_width() || fb_height != framebuffer.get_height()) {
|
|
|
|
|
framebuffer.resize(fb_width, fb_height);
|
|
|
|
|
GLsizei width = static_cast<GLsizei>(size.x * scale.x);
|
|
|
|
|
GLsizei height = static_cast<GLsizei>(size.y * scale.y);
|
|
|
|
|
if (width != framebuffer.get_width() || height != framebuffer.get_height()) {
|
|
|
|
|
framebuffer.resize(width, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flip vertically: OpenGL textures start at the bottom row
|
|
|
|
|
ImGui::Image(framebuffer.get_texture_id(), size, ImVec2{0.0f, 1.0f}, ImVec2{1.0f, 0.0f});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Overlays (averaged over the last MetricHistory::SIZE frames so they're readable)
|
|
|
|
|
float frame_ms = user_data.frame_times.average();
|
|
|
|
|
float fps = frame_ms > 0.0f ? 1000.0f / frame_ms : 0.0f;
|
|
|
|
|
overlay_text(20.0f, 35.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", VERSION_STRING);
|
|
|
|
|
overlay_text(20.0f, 52.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "FPS: %.1f", fps);
|
|
|
|
|
overlay_text(20.0f, 69.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Frame time: %.2f ms", frame_ms);
|
|
|
|
|
overlay_text(20.0f, 86.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Scene CPU: %.2f ms", user_data.scene_cpu_times.average());
|
|
|
|
|
if (user_data.scene_gpu_timer) {
|
|
|
|
|
overlay_text(20.0f, 103.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Scene GPU: %.2f ms", user_data.scene_gpu_times.average());
|
|
|
|
|
} else {
|
|
|
|
|
overlay_text(20.0f, 103.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", "Scene GPU: n/a");
|
|
|
|
|
}
|
|
|
|
|
if (user_data.mouse_grab) { overlay_text(20.0f, 120.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", "INPUT LOCKED"); }
|
|
|
|
|
draw_overlay(app);
|
|
|
|
|
|
|
|
|
|
ImGui::End();
|
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void draw_overlay(const AppState& app)
|
|
|
|
|
{
|
|
|
|
|
const PerformanceStats& stats = app.stats;
|
|
|
|
|
|
|
|
|
|
// Averaged over the last MetricHistory::SIZE frames so the numbers are readable
|
|
|
|
|
float frame_ms = stats.frame_times.average();
|
|
|
|
|
float fps = frame_ms > 0.0f ? 1000.0f / frame_ms : 0.0f;
|
|
|
|
|
|
|
|
|
|
float y = OVERLAY_Y;
|
|
|
|
|
auto line = [&y](const char* format, auto... args) {
|
|
|
|
|
overlay_text(OVERLAY_X, y, format, args...);
|
|
|
|
|
y += OVERLAY_LINE_HEIGHT;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
line("%s", VERSION_STRING);
|
|
|
|
|
line("FPS: %.1f", fps);
|
|
|
|
|
line("Frame time: %.2f ms", frame_ms);
|
|
|
|
|
line("Scene CPU: %.2f ms", stats.scene_cpu_times.average());
|
|
|
|
|
if (stats.gpu_timer) {
|
|
|
|
|
line("Scene GPU: %.2f ms", stats.scene_gpu_times.average());
|
|
|
|
|
} else {
|
|
|
|
|
line("%s", "Scene GPU: n/a");
|
|
|
|
|
}
|
|
|
|
|
if (app.input.mouse_grab) { line("%s", "INPUT LOCKED"); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void plot_metric(const char* label, const MetricHistory& history)
|
|
|
|
|
{
|
|
|
|
|
char overlay[32];
|
|
|
|
|
@@ -426,74 +562,3 @@ void plot_metric(const char* label, const MetricHistory& history)
|
|
|
|
|
ImGui::PlotLines(label, history.values.data(), static_cast<int>(history.values.size()),
|
|
|
|
|
static_cast<int>(history.offset), overlay, 0.0f, FLT_MAX, ImVec2{0.0f, 40.0f});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_random_lights(Scene& scene, int point_light_count, int spot_light_count,
|
|
|
|
|
const glm::vec3& box_min, const glm::vec3& box_max,
|
|
|
|
|
unsigned int seed/* = std::random_device{}()*/)
|
|
|
|
|
{
|
|
|
|
|
std::mt19937 rng(seed);
|
|
|
|
|
|
|
|
|
|
std::uniform_real_distribution<float> random_x(box_min.x, box_max.x);
|
|
|
|
|
std::uniform_real_distribution<float> random_y(box_min.y, box_max.y);
|
|
|
|
|
std::uniform_real_distribution<float> random_z(box_min.z, box_max.z);
|
|
|
|
|
|
|
|
|
|
std::uniform_real_distribution<float> random_direction(-1.0f, 1.0f);
|
|
|
|
|
std::uniform_real_distribution<float> random_intensity(0.5f, 1.5f);
|
|
|
|
|
|
|
|
|
|
const glm::vec3 light_colors[] = {
|
|
|
|
|
glm::vec3(1.0f, 1.0f, 1.0f), // White
|
|
|
|
|
glm::vec3(1.0f, 0.6f, 0.2f), // Warm orange
|
|
|
|
|
glm::vec3(0.2f, 0.5f, 1.0f), // Cool blue
|
|
|
|
|
glm::vec3(1.0f, 0.2f, 0.2f), // Red
|
|
|
|
|
glm::vec3(0.3f, 1.0f, 0.4f), // Green
|
|
|
|
|
glm::vec3(0.8f, 0.3f, 1.0f) // Purple
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::uniform_int_distribution<size_t> randomColor(0, std::size(light_colors) - 1);
|
|
|
|
|
|
|
|
|
|
auto generateDirection = [&]() {
|
|
|
|
|
glm::vec3 direction;
|
|
|
|
|
|
|
|
|
|
// Avoid generating a vector that is too close to zero.
|
|
|
|
|
do {
|
|
|
|
|
direction = glm::vec3(random_direction(rng), random_direction(rng), random_direction(rng));
|
|
|
|
|
} while (glm::dot(direction, direction) < 0.001f);
|
|
|
|
|
|
|
|
|
|
return glm::normalize(direction);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto generateColor = [&]() { return light_colors[randomColor(rng)]; };
|
|
|
|
|
|
|
|
|
|
// Generate point lights
|
|
|
|
|
for (int i = 0; i < point_light_count; ++i) {
|
|
|
|
|
PointLight light;
|
|
|
|
|
|
|
|
|
|
light.position = glm::vec3(random_x(rng), random_y(rng), random_z(rng));
|
|
|
|
|
glm::vec3 color = generateColor();
|
|
|
|
|
light.diffuse = color;
|
|
|
|
|
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
|
|
|
|
|
|
|
|
|
scene.light_environment.point_lights.push_back(light);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate spot lights
|
|
|
|
|
for (int i = 0; i < spot_light_count; ++i) {
|
|
|
|
|
SpotLight light;
|
|
|
|
|
|
|
|
|
|
light.position = glm::vec3(random_x(rng), random_y(rng), random_z(rng));
|
|
|
|
|
light.direction = generateDirection();
|
|
|
|
|
glm::vec3 color = generateColor();
|
|
|
|
|
light.ambient = glm::vec3(0.0f);
|
|
|
|
|
light.diffuse = color;
|
|
|
|
|
light.specular = glm::mix(color, glm::vec3(1.0f), 0.25f);
|
|
|
|
|
|
|
|
|
|
// Narrow inner cone and slightly wider soft outer cone
|
|
|
|
|
light.cut_off = glm::cos(glm::radians(12.5f));
|
|
|
|
|
light.outer_cut_off = glm::cos(glm::radians(17.5f));
|
|
|
|
|
light.constant = 1.0f;
|
|
|
|
|
light.linear = 0.09f;
|
|
|
|
|
light.quadratic = 0.032f;
|
|
|
|
|
|
|
|
|
|
scene.light_environment.spot_lights.push_back(light);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|