From e034b97f4a0819a383c843674d6d48ea11cbb37d Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser Date: Sun, 1 Mar 2026 10:55:19 -0800 Subject: [PATCH] Add AS4 --- as4/CMakeLists.txt | 24 ++++ as4/CO.cpp | 242 +++++++++++++++++++++++++++++++++++++++ as4/README.md | 58 ++++++++++ as4/as3.cpp | 185 ++++++++++++++++++++++++++++++ as4/generated/cubemap.fs | 30 +++++ as4/generated/cubemap.vs | 20 ++++ as4/generated/skybox.fs | 30 +++++ as4/generated/skybox.vs | 24 ++++ as4/skybox.cpp | 168 +++++++++++++++++++++++++++ as4/skybox.hpp | 58 ++++++++++ 10 files changed, 839 insertions(+) create mode 100644 as4/CMakeLists.txt create mode 100644 as4/CO.cpp create mode 100644 as4/README.md create mode 100644 as4/as3.cpp create mode 100644 as4/generated/cubemap.fs create mode 100644 as4/generated/cubemap.vs create mode 100644 as4/generated/skybox.fs create mode 100644 as4/generated/skybox.vs create mode 100644 as4/skybox.cpp create mode 100644 as4/skybox.hpp diff --git a/as4/CMakeLists.txt b/as4/CMakeLists.txt new file mode 100644 index 0000000..3996146 --- /dev/null +++ b/as4/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.18) +project(as3 CXX) + +set(CMAKE_CXX_STANDARD 20) + +# adding this option to make clangd work +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_subdirectory(../raylib-cpp raylib) + +include(../assets/includeable.cmake) + +add_executable(as3 CO.cpp skybox.cpp) +target_link_libraries(as3 PUBLIC raylib raylib_cpp) + +make_includeable(../assets/shaders/skybox.vs generated/skybox.vs) +make_includeable(../assets/shaders/skybox.fs generated/skybox.fs) +make_includeable(../assets/shaders/cubemap.vs generated/cubemap.vs) +make_includeable(../assets/shaders/cubemap.fs generated/cubemap.fs) + +configure_file(../assets/models/penguin.glb models/penguin.glb COPYONLY) +configure_file(../assets/models/eagle.glb models/eagle.glb COPYONLY) +configure_file(../assets/textures/skybox.png textures/skybox.png COPYONLY) +configure_file(../assets/textures/snow.jpg textures/snow.jpg COPYONLY) diff --git a/as4/CO.cpp b/as4/CO.cpp new file mode 100644 index 0000000..463c025 --- /dev/null +++ b/as4/CO.cpp @@ -0,0 +1,242 @@ +#include "AudioDevice.hpp" +#include "Color.hpp" +#include "Keyboard.hpp" +#include "Matrix.hpp" +#include "Mesh.hpp" +#include "Model.hpp" +#include "RadiansDegrees.hpp" +#include "raylib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SKYBOX_IMPLEMENTATION +#include "skybox.hpp" + +void DrawBoundedModel(raylib::Model &model, auto transformer) { + // store the original transform to apply a different transform to the + // model without affecting the next time we draw + raylib::Matrix oldTransform = model.GetTransform(); + + // apply the transform that we get from whatever the transformer callback + // gives us + raylib::Matrix transform = transformer(model.GetTransform()); + + // apply the transform that we got from the transformer to the model + model.SetTransform(transform); + + // draw the model, passing the origin and default scale as arguments since + // the transform is already applied to the model + model.Draw({ 0, 0, 0 }, 1.0f, raylib::Color::White()); + + // get the bounding box of the model after applying the transform + auto box = model.GetTransformedBoundingBox(); + + // draw the bounding box of the model using raylib's built in function + DrawBoundingBox(box, raylib::Color::White()); + + // restore the model's transform to its original state so that the next time we + // draw the model, it doesn't have the previous transform applied to it + model.SetTransform(oldTransform); +} + +class Component { +public: + struct Entity *e; + virtual void Setup() = 0; + virtual void Update(float dt) = 0; + virtual void Cleanup() = 0; +}; + +struct Entity { + std::vector> components; + + template T> + T &AddComponent() { + auto out = components.emplace_back(std::make_shared()); + out->e = this; + return out; + } + + template T> + std::optional> GetComponent() { + for (auto &c : components) { + T *cast = dynamic_cast(c.get()); + if (cast) { + return *cast; + } + } + + return { }; + } +}; + +struct TransformComponent : public Component { + raylib::Transform t; + + void Setup() override { } + + void Update(float dt) override { } + + void Cleanup() override { } +}; + +struct DrawModelComponent : public Component { + raylib::Model *model; + + void Setup() override { } + + void Update(float dt) override { + //DrawBoundedModel(model, []()); + } + + void Cleanup() override { } +}; + +raylib::Degree angle_normalize(raylib::Degree angle) { + float decimal = float(angle) - int(angle); + int normalized = (int(angle) % 360 + 360) % 360; + return raylib::Degree(normalized + decimal); +} + +int main() { + raylib::Window window(800, 600, "CS381 - Assignment 3"); + window.SetState(FLAG_WINDOW_RESIZABLE); + raylib::AudioDevice audio; + + raylib::Model penguin("models/penguin.glb"); + + // behind and above the penguin (in penguin-local space) + const float CAM_DIST = 512.0f; + const float CAM_HEIGHT = 256.0f; + const float CAM_ANGULAR_VELOCITY = 2.0f; + const float CAM_PITCH_MIN = -0.5f; + const float CAM_PITCH_MAX = 1.5f; + + float camYaw = 3.14f; // offset by 90 deg so it faces in the proper direction + float camPitch = 0; + + raylib::Camera3D camera( + { 0, CAM_DIST * std::sin(camPitch), CAM_DIST * std::cos(camPitch) }, + { 0, 0, 0 }, + { 0, 1, 0 }, + 45.0f); + + raylib::Model ground = raylib::Mesh::Plane(10000, 10000, 50, 50, 25); + + raylib::Texture snowTexture("textures/snow.jpg"); + ground.GetMaterials()[0].maps[MATERIAL_MAP_DIFFUSE].texture = snowTexture; + + cs381::SkyBox skybox("textures/skybox.png"); + + std::vector entities; + + Entity &e = entities.emplace_back(); + e.AddComponent(); + e.AddComponent(); + + // penguin physics + raylib::Vector3 position = { 0, 0, 0 }; + raylib::Vector3 velocity = { 0, 0, 0 }; + float heading = 0.0f; + float speed = 0.0f; + + // units/s + const float ACCELERATION = 100.0f; + + // in radians + const float ANGULAR_VELOCITY = 3.14f; + + window.SetTargetFPS(60); // save cpu cycles + + while (!window.ShouldClose()) { + window.BeginDrawing(); + window.ClearBackground(raylib::Color::Gray()); + + float dt = window.GetFrameTime(); + + position += velocity * dt * 0.5f; + + // movement for penguin + if (IsKeyDown(KEY_W)) { + speed += ACCELERATION * dt; + } + + if (IsKeyDown(KEY_S)) { + speed -= ACCELERATION * dt; + } + + if (IsKeyDown(KEY_A)) { + heading += ANGULAR_VELOCITY * dt; + } + + if (IsKeyDown(KEY_D)) { + heading -= ANGULAR_VELOCITY * dt; + } + + if (IsKeyDown(KEY_SPACE)) { + speed = 0.0f; + } + + velocity = raylib::Vector3 { + std::sin(heading) * speed, + 0.0f, + std::cos(heading) * speed + }; + + // ds = 1/2 * (v0 + v1) * dt + position += velocity * dt * 0.5f; + + // movement for camera + if (IsKeyDown(KEY_LEFT)) { + camYaw += CAM_ANGULAR_VELOCITY * dt; + } + if (IsKeyDown(KEY_RIGHT)) { + camYaw -= CAM_ANGULAR_VELOCITY * dt; + } + if (IsKeyDown(KEY_UP)) { + camPitch += CAM_ANGULAR_VELOCITY * dt; + } + if (IsKeyDown(KEY_DOWN)) { + camPitch -= CAM_ANGULAR_VELOCITY * dt; + } + + // clamp the angle between + camPitch = std::clamp(camPitch, CAM_PITCH_MIN, CAM_PITCH_MAX); + + // x = cos(pitch) * sin(yaw) + // y = sin(pitch) + // z = cos(pitch) * cos(yaw) + float yaw = camYaw + heading; // follow penguin + raylib::Vector3 camOffset = { + CAM_DIST * std::cos(camPitch) * std::sin(yaw), + CAM_DIST * std::sin(camPitch) + CAM_HEIGHT, + CAM_DIST * std::cos(camPitch) * std::cos(yaw) + }; + camera.SetPosition(position + camOffset); + camera.SetTarget(position); + + camera.BeginMode(); + skybox.Draw(); + + ground.Draw({ 0, 0, 0 }, 1.0f, raylib::Color::White()); + + DrawBoundedModel(penguin, [&position, &heading](raylib::Matrix transform) { + return transform + .RotateY(heading) + .Scale(40, 40, 40) + .Translate(position); + }); + + camera.EndMode(); + window.EndDrawing(); + } + + return 0; +} diff --git a/as4/README.md b/as4/README.md new file mode 100644 index 0000000..e47914f --- /dev/null +++ b/as4/README.md @@ -0,0 +1,58 @@ +# Building and Running + +Clone the repository, navigate to the root of the project, and initialize the +submodules: + +```sh +git clone https://github.com/humanoidsandvichdispenser/cs381.git +cd cs381 +git submodule update --init --recursive +``` + +Navigate to the `as3` directory, create a build directory, and run CMake to +generate the build files: + +```sh +cd as3 +mkdir -p build +cd build +cmake .. +``` + +Compile the code using `make`: + +```sh +make +``` + +This should create an executable named `as3` in the `build` directory. You can +run the executable with the following command: + +```sh +./as3 +``` + +# Instructions on how to use the program + +Hold W and S to accelerate the penguin forward and backward. Use A and D to +change heading direction. This allows you to steer the penguin around the +environment. + +Use the arrow keys to change the camera's rotation (pitch and yaw) with respect +to the penguin, allowing you to look around while the penguin moves. + +# Readme Question + +`dt` is delta time, which is the time elapsed between each frame, which can be +found by taking the current time and subtracting the time from the previous +frame. Raylib provides a function `GetFrameTime()` that returns the time +elapsed since the last frame, which can be used to calculate `dt`. By using +`dt`, it ensures that physics calculations are independent of frame rate, since +otherwise the velocity would be in terms of units per frame rather than units +per second. By multiplying the velocity by `dt`, it converts the velocity to +units per second, allowing for consistent movement regardless of the frame +rate. + +# Extra Credit + +The camera moves with the penguin and can be rotated around the penguin using the arrow keys. diff --git a/as4/as3.cpp b/as4/as3.cpp new file mode 100644 index 0000000..7b3ca04 --- /dev/null +++ b/as4/as3.cpp @@ -0,0 +1,185 @@ +#include "AudioDevice.hpp" +#include "Color.hpp" +#include "Keyboard.hpp" +#include "Matrix.hpp" +#include "Mesh.hpp" +#include "Model.hpp" +#include "raylib.h" +#include +#include +#include +#include + +#define SKYBOX_IMPLEMENTATION +#include "skybox.hpp" + +void DrawBoundedModel(raylib::Model &model, auto transformer) { + // store the original transform to apply a different transform to the + // model without affecting the next time we draw + raylib::Matrix oldTransform = model.GetTransform(); + + // apply the transform that we get from whatever the transformer callback + // gives us + raylib::Matrix transform = transformer(model.GetTransform()); + + // apply the transform that we got from the transformer to the model + model.SetTransform(transform); + + // draw the model, passing the origin and default scale as arguments since + // the transform is already applied to the model + model.Draw({ 0, 0, 0 }, 1.0f, raylib::Color::White()); + + // get the bounding box of the model after applying the transform + auto box = model.GetTransformedBoundingBox(); + + // draw the bounding box of the model using raylib's built in function + DrawBoundingBox(box, raylib::Color::White()); + + // restore the model's transform to its original state so that the next time we + // draw the model, it doesn't have the previous transform applied to it + model.SetTransform(oldTransform); +} + +struct Entity { + enum Type { + Penguin = 0, + Eagle = 1, + } type; + raylib::Vector3 position; + raylib::Vector3 velocity; + raylib::Degree heading; +}; + +int main() { + raylib::Window window(800, 600, "CS381 - Assignment 3"); + window.SetState(FLAG_WINDOW_RESIZABLE); + raylib::AudioDevice audio; + + raylib::Model penguin("models/penguin.glb"); + + // behind and above the penguin (in penguin-local space) + const float CAM_DIST = 512.0f; + const float CAM_HEIGHT = 256.0f; + const float CAM_ANGULAR_VELOCITY = 2.0f; + const float CAM_PITCH_MIN = -0.5f; + const float CAM_PITCH_MAX = 1.5f; + + float camYaw = 3.14f; // offset by 90 deg so it faces in the proper direction + float camPitch = 0; + + raylib::Camera3D camera( + { 0, CAM_DIST * std::sin(camPitch), CAM_DIST * std::cos(camPitch) }, + { 0, 0, 0 }, + { 0, 1, 0 }, + 45.0f); + + raylib::Model ground = raylib::Mesh::Plane(10000, 10000, 50, 50, 25); + + raylib::Texture snowTexture("textures/snow.jpg"); + ground.GetMaterials()[0].maps[MATERIAL_MAP_DIFFUSE].texture = snowTexture; + + cs381::SkyBox skybox("textures/skybox.png"); + + std::vector entities; + + auto &e = entities.emplace_back(Entity::Penguin, raylib::Vector3 { 0, 0, 0 }, raylib::Vector3 { 0, 0, 0 }, 0); + + // penguin physics + raylib::Vector3 position = { 0, 0, 0 }; + raylib::Vector3 velocity = { 0, 0, 0 }; + float heading = 0.0f; + float speed = 0.0f; + + // units/s + const float ACCELERATION = 100.0f; + + // in radians + const float ANGULAR_VELOCITY = 3.14f; + + window.SetTargetFPS(60); // save cpu cycles + + while (!window.ShouldClose()) { + window.BeginDrawing(); + window.ClearBackground(raylib::Color::Gray()); + + float dt = window.GetFrameTime(); + + position += velocity * dt * 0.5f; + + // movement for penguin + if (IsKeyDown(KEY_W)) { + speed += ACCELERATION * dt; + } + + if (IsKeyDown(KEY_S)) { + speed -= ACCELERATION * dt; + } + + if (IsKeyDown(KEY_A)) { + heading += ANGULAR_VELOCITY * dt; + } + + if (IsKeyDown(KEY_D)) { + heading -= ANGULAR_VELOCITY * dt; + } + + if (IsKeyDown(KEY_SPACE)) { + speed = 0.0f; + } + + velocity = raylib::Vector3 { + std::sin(heading) * speed, + 0.0f, + std::cos(heading) * speed + }; + + // ds = 1/2 * (v0 + v1) * dt + position += velocity * dt * 0.5f; + + // movement for camera + if (IsKeyDown(KEY_LEFT)) { + camYaw += CAM_ANGULAR_VELOCITY * dt; + } + if (IsKeyDown(KEY_RIGHT)) { + camYaw -= CAM_ANGULAR_VELOCITY * dt; + } + if (IsKeyDown(KEY_UP)) { + camPitch += CAM_ANGULAR_VELOCITY * dt; + } + if (IsKeyDown(KEY_DOWN)) { + camPitch -= CAM_ANGULAR_VELOCITY * dt; + } + + // clamp the angle between + camPitch = std::clamp(camPitch, CAM_PITCH_MIN, CAM_PITCH_MAX); + + // x = cos(pitch) * sin(yaw) + // y = sin(pitch) + // z = cos(pitch) * cos(yaw) + float yaw = camYaw + heading; // follow penguin + raylib::Vector3 camOffset = { + CAM_DIST * std::cos(camPitch) * std::sin(yaw), + CAM_DIST * std::sin(camPitch) + CAM_HEIGHT, + CAM_DIST * std::cos(camPitch) * std::cos(yaw) + }; + camera.SetPosition(position + camOffset); + camera.SetTarget(position); + + camera.BeginMode(); + skybox.Draw(); + + ground.Draw({ 0, 0, 0 }, 1.0f, raylib::Color::White()); + + DrawBoundedModel(penguin, [&position, &heading](raylib::Matrix transform) { + return transform + .RotateY(heading) + .Scale(40, 40, 40) + .Translate(position); + }); + + camera.EndMode(); + window.EndDrawing(); + } + + return 0; +} diff --git a/as4/generated/cubemap.fs b/as4/generated/cubemap.fs new file mode 100644 index 0000000..54392d2 --- /dev/null +++ b/as4/generated/cubemap.fs @@ -0,0 +1,30 @@ +R"for_C++_include(#version 330 + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; + +// Input uniform values +uniform sampler2D equirectangularMap; + +// Output fragment color +out vec4 finalColor; + +vec2 SampleSphericalMap(vec3 v) +{ + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= vec2(0.1591, 0.3183); + uv += 0.5; + return uv; +} + +void main() +{ + // Normalize local position + vec2 uv = SampleSphericalMap(normalize(fragPosition)); + + // Fetch color from texture map + vec3 color = texture(equirectangularMap, uv).rgb; + + // Calculate final fragment color + finalColor = vec4(color, 1.0); +})for_C++_include" \ No newline at end of file diff --git a/as4/generated/cubemap.vs b/as4/generated/cubemap.vs new file mode 100644 index 0000000..7f5e04f --- /dev/null +++ b/as4/generated/cubemap.vs @@ -0,0 +1,20 @@ +R"for_C++_include(#version 330 + +// Input vertex attributes +in vec3 vertexPosition; + +// Input uniform values +uniform mat4 matProjection; +uniform mat4 matView; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; + +void main() +{ + // Calculate fragment position based on model transformations + fragPosition = vertexPosition; + + // Calculate final vertex position + gl_Position = matProjection*matView*vec4(vertexPosition, 1.0); +})for_C++_include" \ No newline at end of file diff --git a/as4/generated/skybox.fs b/as4/generated/skybox.fs new file mode 100644 index 0000000..c4cd6af --- /dev/null +++ b/as4/generated/skybox.fs @@ -0,0 +1,30 @@ +R"for_C++_include(#version 330 + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; + +// Input uniform values +uniform samplerCube environmentMap; +uniform bool vflipped; +uniform bool doGamma; + +// Output fragment color +out vec4 finalColor; + +void main() +{ + // Fetch color from texture map + vec3 color = vec3(0.0); + + if (vflipped) color = texture(environmentMap, vec3(fragPosition.x, -fragPosition.y, fragPosition.z)).rgb; + else color = texture(environmentMap, fragPosition).rgb; + + if (doGamma)// Apply gamma correction + { + color = color/(color + vec3(1.0)); + color = pow(color, vec3(1.0/2.2)); + } + + // Calculate final fragment color + finalColor = vec4(color, 1.0); +})for_C++_include" \ No newline at end of file diff --git a/as4/generated/skybox.vs b/as4/generated/skybox.vs new file mode 100644 index 0000000..00596e0 --- /dev/null +++ b/as4/generated/skybox.vs @@ -0,0 +1,24 @@ +R"for_C++_include(#version 330 + +// Input vertex attributes +in vec3 vertexPosition; + +// Input uniform values +uniform mat4 matProjection; +uniform mat4 matView; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; + +void main() +{ + // Calculate fragment position based on model transformations + fragPosition = vertexPosition; + + // Remove translation from the view matrix + mat4 rotView = mat4(mat3(matView)); + vec4 clipPos = matProjection*rotView*vec4(vertexPosition, 1.0); + + // Calculate final vertex position + gl_Position = clipPos; +})for_C++_include" \ No newline at end of file diff --git a/as4/skybox.cpp b/as4/skybox.cpp new file mode 100644 index 0000000..fafdf84 --- /dev/null +++ b/as4/skybox.cpp @@ -0,0 +1,168 @@ +/******************************************************************************************* +* +* raylib [models] example - Skybox loading and drawing +* +* Example originally created with raylib 1.8, last time updated with raylib 4.0 +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2017-2023 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "skybox.hpp" +#include + +#include "rlgl.h" + +namespace cs381 { + + SkyBox& SkyBox::Init() { + // Load skybox model + cube = raylib::Mesh::Cube(1.0f, 1.0f, 1.0f).LoadModelFrom(); + + // Load skybox shader and set required locations + // NOTE: Some locations are automatically set at shader loading + shader = raylib::Shader::LoadFromMemory(vertexShader, fragmentShader); + cube.materials[0].shader = shader; + shader.SetValue("environmentMap", (int)MATERIAL_MAP_CUBEMAP, SHADER_UNIFORM_INT); + + return *this; + } + + SkyBox& SkyBox::Load(const std::string_view filename, bool isEnviornment/* = false*/) { + if(shader.id == 0) Init(); + + shader.SetValue("doGamma", int(isEnviornment ? 1 : 0), SHADER_UNIFORM_INT); + shader.SetValue("vflipped", int(isEnviornment ? 1 : 0), SHADER_UNIFORM_INT); + + if(isEnviornment) { + if(cubemapShader.id == 0){ + cubemapShader = raylib::Shader::LoadFromMemory(cubemapVertexShader, cubemapFragmentShader); + cubemapShader.SetValue("equirectangularMap", int(0), SHADER_UNIFORM_INT); + } + + // Load HDR panorama (sphere) texture + texture.Load(filename); + // Make sure that things aren't sampled in a pixelated manor! + texture.SetFilter(TEXTURE_FILTER_BILINEAR); + + // Generate cubemap (texture with 6 quads-cube-mapping) from panorama HDR texture + // NOTE 1: New texture is generated rendering to texture, shader calculates the sphere->cube coordinates mapping + // NOTE 2: It seems on some Android devices WebGL, fbo does not properly support a FLOAT-based attachment, + // despite texture can be successfully created.. so using PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 instead of PIXELFORMAT_UNCOMPRESSED_R32G32B32A32 + cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = GenTextureCubemap(cubemapShader, texture, 1024, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); + } else { + raylib::Image img(filename); + texture.Load(img, CUBEMAP_LAYOUT_AUTO_DETECT); + // Make sure that things aren't sampled in a pixelated manor! + texture.SetFilter(TEXTURE_FILTER_BILINEAR); + cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = texture; // CUBEMAP_LAYOUT_PANORAMA + } + + return *this; + } + + SkyBox& SkyBox::Draw() { + // We are inside the cube, we need to disable backface culling! + rlDisableBackfaceCulling(); + rlDisableDepthMask(); + cube.Draw({}); + rlEnableBackfaceCulling(); + rlEnableDepthMask(); + + return *this; + } + + // Generate cubemap texture from HDR texture + TextureCubemap SkyBox::GenTextureCubemap(Shader shader, Texture2D panorama, int size, int format) { + TextureCubemap cubemap = { 0 }; + + rlDisableBackfaceCulling(); // Disable backface culling to render inside the cube + + // STEP 1: Setup framebuffer + //------------------------------------------------------------------------------------------ + unsigned int rbo = rlLoadTextureDepth(size, size, true); + cubemap.id = rlLoadTextureCubemap(0, size, format, 1); + + // unsigned int fbo = rlLoadFramebuffer(size, size); + unsigned int fbo = rlLoadFramebuffer(); + rlFramebufferAttach(fbo, rbo, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + rlFramebufferAttach(fbo, cubemap.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_CUBEMAP_POSITIVE_X, 0); + + // Check if framebuffer is complete with attachments (valid) + if (rlFramebufferComplete(fbo)) TraceLog(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", fbo); + //------------------------------------------------------------------------------------------ + + // STEP 2: Draw to framebuffer + //------------------------------------------------------------------------------------------ + // NOTE: Shader is used to convert HDR equirectangular environment map to cubemap equivalent (6 faces) + rlEnableShader(shader.id); + + // Define projection matrix and send it to shader + Matrix matFboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + rlSetUniformMatrix(shader.locs[SHADER_LOC_MATRIX_PROJECTION], matFboProjection); + + // Define view matrix for every side of the cubemap + Matrix fboViews[6] = { + MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 1.0f, 0.0f, 0.0f }, Vector3{ 0.0f, -1.0f, 0.0f }), + MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ -1.0f, 0.0f, 0.0f }, Vector3{ 0.0f, -1.0f, 0.0f }), + MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, 1.0f, 0.0f }, Vector3{ 0.0f, 0.0f, 1.0f }), + MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, -1.0f, 0.0f }, Vector3{ 0.0f, 0.0f, -1.0f }), + MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, 0.0f, 1.0f }, Vector3{ 0.0f, -1.0f, 0.0f }), + MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, 0.0f, -1.0f }, Vector3{ 0.0f, -1.0f, 0.0f }) + }; + + rlViewport(0, 0, size, size); // Set viewport to current fbo dimensions + + // Activate and enable texture for drawing to cubemap faces + rlActiveTextureSlot(0); + rlEnableTexture(panorama.id); + + for (int i = 0; i < 6; i++) { + // Set the view matrix for the current cube face + rlSetUniformMatrix(shader.locs[SHADER_LOC_MATRIX_VIEW], fboViews[i]); + + // Select the current cubemap face attachment for the fbo + // WARNING: This function by default enables->attach->disables fbo!!! + rlFramebufferAttach(fbo, cubemap.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_CUBEMAP_POSITIVE_X + i, 0); + rlEnableFramebuffer(fbo); + + // Load and draw a cube, it uses the current enabled texture + rlClearScreenBuffers(); + rlLoadDrawCube(); + + // ALTERNATIVE: Try to use internal batch system to draw the cube instead of rlLoadDrawCube + // for some reason this method does not work, maybe due to cube triangles definition? normals pointing out? + // TODO: Investigate this issue... + //rlSetTexture(panorama.id); // WARNING: It must be called after enabling current framebuffer if using internal batch system! + //rlClearScreenBuffers(); + //DrawCubeV(Vector3Zero(), Vector3One(), WHITE); + //rlDrawRenderBatchActive(); + } + //------------------------------------------------------------------------------------------ + + // STEP 3: Unload framebuffer and reset state + //------------------------------------------------------------------------------------------ + rlDisableShader(); // Unbind shader + rlDisableTexture(); // Unbind texture + rlDisableFramebuffer(); // Unbind framebuffer + rlUnloadFramebuffer(fbo); // Unload framebuffer (and automatically attached depth texture/renderbuffer) + + // Reset viewport dimensions to default + rlViewport(0, 0, rlGetFramebufferWidth(), rlGetFramebufferHeight()); + rlEnableBackfaceCulling(); + //------------------------------------------------------------------------------------------ + + cubemap.width = size; + cubemap.height = size; + cubemap.mipmaps = 1; + cubemap.format = format; + + return cubemap; + } + + raylib::Shader SkyBox::cubemapShader(0); + +} \ No newline at end of file diff --git a/as4/skybox.hpp b/as4/skybox.hpp new file mode 100644 index 0000000..522e5c3 --- /dev/null +++ b/as4/skybox.hpp @@ -0,0 +1,58 @@ +/******************************************************************************************* +* +* raylib [models] example - Skybox loading and drawing +* +* Example originally created with raylib 1.8, last time updated with raylib 4.0 +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2017-2023 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib-cpp.hpp" + +namespace cs381 { + struct SkyBox { + constexpr static std::string_view vertexShader = + #include "generated/skybox.vs" + ; + constexpr static std::string_view fragmentShader = + #include "generated/skybox.fs" + ; + constexpr static std::string_view cubemapVertexShader = + #include "generated/cubemap.vs" + ; + constexpr static std::string_view cubemapFragmentShader = + #include "generated/cubemap.fs" + ; + + static raylib::Shader cubemapShader; + + raylib::Texture texture; + raylib::Shader shader; + raylib::Model cube; + + SkyBox() : shader(0) {}; + SkyBox(SkyBox&) = delete; + SkyBox(SkyBox&&) = default; + SkyBox(const std::string_view filename, bool isEnviornment = false) : SkyBox() { + Load(filename, isEnviornment); + } + + ~SkyBox() { + if(cube.IsValid()) + UnloadTexture(cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture); + } + + SkyBox& Init(); + SkyBox& Load(const std::string_view filename, bool isEnviornment = false); + SkyBox& Draw(); + + + private: + // Generate cubemap texture from HDR texture + static TextureCubemap GenTextureCubemap(Shader shader, Texture2D panorama, int size, int format); + }; +} \ No newline at end of file