enigine 3D Game Engine

repository·master·Indexed 42 days ago

https://github.com/furkansarihan/enigine

A C++ 3D game engine featuring a PBR rendering pipeline, skeletal animation system, and spatial culling. It includes a RenderManager for orchestrating depth, SSAO, and deferred shading, a CullingManager for frustum and AABB intersection tests using Bullet Physics, and a Model class for loading geometry via Assimp.

Tokens
5.9K
Snippets
21
Records
31
Agent score
50%

What's inside enigine

  1. Install and build enigine

    master

    enigine is a 3D game engine that requires a C++ compiler, CMake as the build system, and Conan as the package manager.

    To build and run the engine, navigate to the dev directory and execute the provided build script with the --run flag.

    cd dev
    ./build.sh --run
  2. Manage scene visibility and object culling with CullingManager

    master
    The CullingManager class provides an interface for managing object visibility and performing spatial queries (like raycasting and AABB intersection) using the Bullet Physics library. It supports frustum culling to determine if objects are within the camera's view and allows registering objects with specific collision shapes (spheres or boxes) for intersection testing.
  3. Understand AnimatorState and animation types

    master

    The Animator manages two distinct types of animation collections via the AnimatorState struct:

    1. State Animations (animations): Typically used for continuous playback (e.g., walking, running, idling).
    2. Pose Animations (poses): Typically used for specific, often one-off, pose overrides or blending targets.

    Both collections store pointers to Anim objects, which wrap the underlying Animation data with playback state.

  4. Handle skeletal animation with BoneInfo

    master

    For models with skeletal animation, the Model class manages bone transformations using the BoneInfo struct and a bone map.

    BoneInfo Struct

    • id: The index of the bone in the final bone matrices array.
    • offset: A glm::mat4 offset matrix that transforms a vertex from model space to bone space.

    Bone Mapping

    • m_boneInfoMap: A std::map<std::string, BoneInfo> that maps bone names to their respective BoneInfo data.
  5. Manage engine resources with ResourceManager

    master

    The ResourceManager class is responsible for loading, caching, and managing the lifecycle of engine resources such as Model, Texture, and Material objects. It uses an internal cache to prevent redundant loading of the same resource. When requesting a resource, you can specify if you want a copy of the resource or a pointer to the cached instance.

    // Initialize the manager with the path to the executable
    ResourceManager resourceManager("/path/to/executable");
    
    // Retrieve a cached model or a copy
    Model* myModel = resourceManager.getModel("assets/models/hero.obj", false);
  6. Manage the rendering pipeline with RenderManager

    master

    The RenderManager is the central orchestrator for the rendering process. It manages PBR (Physically Based Rendering), shadows, SSAO, post-processing, and culling.

    To use it, you typically follow a frame lifecycle:

    1. Setup: Call setupFrame(window) at the start of a frame.
    2. Execution: Call the sequential rendering stages:
      • renderDepth()
      • renderOpaque()
      • renderSSAO()
      • renderDeferredShading()
      • renderBlend()
      • renderTransmission()
      • renderPostProcess()

    You can add objects to the pipeline using addSource(RenderSource*), addParticleSource(RenderParticleSource*), or by implementing the Renderable interface for custom rendering logic.

    // Typical frame loop usage
    renderManager->setupFrame(window);
    
    renderManager->renderDepth();
    renderManager->renderOpaque();
    renderManager->renderSSAO();
    renderManager->renderDeferredShading();
    renderManager->renderBlend();
    renderManager->renderTransmission();
    renderManager->renderPostProcess();
  7. Handle input events in RenderUI

    master

    To ensure the UI responds to user interactions, you must route GLFW input callbacks to the corresponding listener methods in RenderUI. Supported listeners include:

    • keyListener(GLFWwindow *window, int key, int scancode, int action, int mods)
    • mouseButtonListener(GLFWwindow *window, int button, int action, int mods)
    • mouseScrollListener(GLFWwindow *window, double xoffset, double yoffset)
    • fileDropListener(GLFWwindow *window, int count, const char **paths)
  8. Register objects for culling and intersection tests

    master

    To include an object in the culling system, use addObject. You must provide a userPointer (a unique identifier, typically a pointer to your game object) to manage the object later. You can add objects as spheres (using radius) or as boxes (using size).

    To update an object's position or orientation, call updateObject with its original userPointer and a new modelMatrix.

    // Add a sphere object
    void* myObjPtr = static_cast<void*>(myGameObject);
    btCollisionObject* colObj = cullingManager.addObject(myObjPtr, 1.0f, modelMatrix);
    
    // Add a box object
    btCollisionObject* boxColObj = cullingManager.addObject(myObjPtr, glm::vec3(1.0f, 2.0f, 1.0f), modelMatrix);
    
    // Update position
    cullingManager.updateObject(myObjPtr, newModelMatrix);
    
    // Remove object
    cullingManager.removeObject(myObjPtr);
  9. Use the Animator class to manage skeletal animations

    master

    The Animator class is the primary interface for managing and playing back multiple animations on a skeletal mesh. You initialize it with a list of available Animation objects. The animator maintains a state (AnimatorState) containing active animations and poses, and calculates the final bone matrices required for skinning.

    Key operations:

    • update(float deltaTime): Advances the animation timers and recalculates bone transforms.
    • addStateAnimation(Animation *animation): Adds an animation to the active state animations list.
    • addPoseAnimation(Animation *animation): Adds an animation to the active poses list.
    • calculateBoneTransform(...): Internal logic used during update to traverse the node hierarchy and compute global transforms.
    // Example initialization and update loop
    std::vector<Animation*> myAnimations = { anim1, anim2 };
    Animator animator(myAnimations);
    
    // In the main loop:
    animator.update(deltaTime);
    
    // Access the matrices for the shader:
    const std::vector<glm::mat4>& boneMatrices = animator.m_finalBoneMatrices;
  10. Load and manage textures

    master

    The ResourceManager provides several ways to handle textures, including loading from files, memory buffers, or creating texture arrays.

    • textureFromFile(const TextureParams &params, const std::string &key, const std::string &path): Loads a texture from a file and stores it under the provided key.
    • textureFromMemory(const TextureParams &params, const std::string &key, void *buffer, unsigned int bufferSize): Creates a texture from a raw memory buffer.
    • getTextureArray(std::vector<std::string> texturePaths, bool anisotropicFiltering = false): Loads multiple textures from a list of paths into a single texture array.
    • loadTexture(Texture &texture, const TextureParams &params, void *data): Directly loads data into an existing Texture object.
    • updateTexture(Texture &texture, const TextureParams &params): Updates an existing texture's parameters.
    // Load a texture from a file
    Texture* tex = resourceManager.textureFromFile(params, "grass_tex", "assets/textures/grass.png");
    
    // Load a texture array
    std::vector<std::string> paths = {"tex1.png", "tex2.png"};
    Texture* texArray = resourceManager.getTextureArray(paths, true);
  11. Perform frustum culling

    master

    To enable frustum culling, you must first update the manager's internal planes using the camera's view-projection matrix via calculatePlanes. Once updated, you can check if a specific AABB is within the camera's view using inFrustum.

    // 1. Update planes every frame with the new view-projection matrix
    cullingManager.calculatePlanes(viewProjectionMatrix);
    
    // 2. Check if an object's AABB is visible
    bool visible = cullingManager.inFrustum(objAabbMin, objAabbMax, cameraPosition);