34 Commits

Author SHA1 Message Date
4266a0b570 add Console panel; fix the problem when cs set Rotation the direction not update; fix some little bug; add simple texture material Edit system;some treaks 2026-03-27 18:02:57 +08:00
ef4ea45edc add simple runtime; fix the issue of crashing when creating file and folder; some tweaks 2026-03-25 21:36:38 +08:00
f1de5df4de add bloom;some treaks 2026-03-17 16:20:22 +08:00
28d9a7dfb6 add Preetham sky; add point light and spot light; 2026-03-16 01:44:23 +08:00
3f56a6878d remove some useless include; add include <filesystem> for entrypoint 2026-03-15 00:49:10 +08:00
a6fb4bdcea add args for entry Application; using efsw to replace custom filewatcher; remove some useless code; move FileOpenSelector from Application to FileSystem;some tweaks 2026-03-15 00:46:47 +08:00
5cb9b04ab0 add compute shader to auto calculate screen exposure, add SSBO impl 2026-03-12 23:42:52 +08:00
8ba00467fd add README.md 2026-03-11 17:31:32 +08:00
79e6631c50 add Physics2D class; For Physics2D add DistanceJoint RevoluteJoint PrismaticJoint WeldJoint Component and serialize/deserialize code; use template to Copy Component from entity to entity; remove useless code, fix some error code 2026-03-11 16:13:38 +08:00
a265e71e1a add AnimationComponent, Skeleton, AnimationClip; add single Renderer3D class, remove some useless code; problem now renderer2D cannot blend with Renderer3D 2026-03-11 09:19:05 +08:00
eb0a463370 fix environment texture drag to viewport didn't create SkylightComponent; fix PhysicsWrappers::CreateConvexMesh collier worng scale;some tweaks 2026-03-09 01:03:35 +08:00
c1bb8f9fba readd Sprite Renderer render and panel edit; in renderer scene, add icon for camera and light; improve a user-friendly EditorCamera, is more like UE Editor Camera; fix woring build m_ProjectionMatrix for SceneCamera::Orthographic 2026-03-08 19:27:00 +08:00
79f56b60a0 replace mesh load texture from native create to AssetsManager::GetAsset; renderer.h add Submit::pFunc->~FuncT(); fix PhysicsWrappers::CreateTriamgleMesh() error collider collect; remove unused ChildrenComponent; AssetEditorPanel now will auto save(serialize) when the window closed 2026-03-08 00:26:39 +08:00
57e14bc3d2 add ImViewzmo, add floating window for QWER(select, translate, rotate, scale) , and add new debug button to floating (Experimental) 2026-03-02 17:10:03 +08:00
a0086020c1 add infiniteGrid, rewrote the shadow , now the shadow is a simple hardware shadow 2026-03-01 17:03:40 +08:00
56da5ebef7 add auto exposure 2026-02-26 18:17:12 +08:00
99bbf1eb5a fix the child position incorrect while parenting node; add camera focus func; treat icons to the asset and using AssetsManager::GetAsset to load it 2026-02-17 21:38:43 +08:00
2bbe332532 lots of asset manager changes. using meta to manage asset, add twoside material flag for grid 2026-02-15 16:17:23 +08:00
896d3c7f97 add file watcher using widows api for windows platform, using handles to import and manager assets, some little tweaks 2026-01-21 23:25:41 +08:00
cf3a46bae1 add trace for imgui.ini, add assets manager system and objects manager system, add some icons, add entity tree node for scene Hierachy's entities, add convert blend file process(but not actual use it, just implement) BUGS: file copy not work 2026-01-21 12:30:47 +08:00
2bfde2dfb0 add circle2d collider renderer, fixed a bug for renderer 2 value properties 2026-01-20 02:04:16 +08:00
323d646611 default mesh collider are generated by default mesh, add camera info serialization, using error info instead of assert to process missing filepath when deserializing scene, move cullface into internal renderer api, fixed dynamic vertex buffer bug 2026-01-19 13:05:24 +08:00
81b52abf0f update cursor load 2026-01-12 19:18:07 +08:00
bed57f18d3 add serialize for RigidbodyComponent linear/angular drag and gravity, change the physx API for c++ and c#, fixed rotate bug(using radians to storage), some little code tweaks 2026-01-12 18:58:11 +08:00
f857d8e791 move imgui property code and Pysics Actor code to a single file 2026-01-10 14:48:17 +08:00
9e1474e643 add directional light component and skylight component, add PCSS and hard shadow 2026-01-02 22:46:29 +08:00
abf0a65bd6 update transformComponent style, and change the position of button 'Add Component' 2026-01-01 00:57:23 +08:00
faecfe21ab fixed layer problem, remove 'default' layer to serialize, remove Transform class, using translation rotation scale instead 2025-12-31 23:56:49 +08:00
960eeaf94b add transform class, some code tweaks 2025-12-29 23:11:14 +08:00
ce41e348f8 add physX settings through the setting window for editor 2025-12-29 13:44:00 +08:00
9a44dd8d79 add overlapBox/Sphere/Capsule function, fixed OnWake/OnSleep not work 2025-12-28 22:40:48 +08:00
d0eed3a33d add c# debug (mono debug) 2025-12-25 17:39:50 +08:00
f747db4e27 add physX colliders ,add trigger, colliders now can visible, some rotation for cs and native cpp connection 2025-12-24 10:10:24 +08:00
00d3993a77 add PhysX SDK and some tweaks 2025-12-17 12:39:20 +08:00
216 changed files with 19135 additions and 3920 deletions

9
.gitmodules vendored
View File

@ -26,3 +26,12 @@
[submodule "Prism/vendor/Box2D"]
path = Prism/vendor/Box2D
url = https://github.com/erincatto/box2d.git
[submodule "Prism/vendor/PhysX"]
path = Prism/vendor/PhysX
url = https://github.com/NVIDIA-Omniverse/PhysX.git
[submodule "Prism/vendor/ImViewGuizmo"]
path = Prism/vendor/ImViewGuizmo
url = https://github.com/Ka1serM/ImViewGuizmo
[submodule "Prism/vendor/efsw"]
path = Prism/vendor/efsw
url = https://github.com/SpartanJ/efsw

View File

@ -10,7 +10,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# set MSVC output directory
if(MSVC)
# config
string(REPLACE "/showIncludes" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
# temp config
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251")
@ -23,4 +23,5 @@ endif ()
add_subdirectory(Prism)
add_subdirectory(Sandbox)
add_subdirectory(Editor)
add_subdirectory(Editor)
add_subdirectory(PrismRuntime)

View File

@ -2,17 +2,21 @@ project(PrismEditor)
set(CMAKE_BINARY_DIR ${CMAKE_BINARY_DIR}/bin)
file(GLOB ASSETS assets)
file(GLOB ASSETS "assets")
file(COPY ${ASSETS} DESTINATION ${CMAKE_BINARY_DIR})
# imgui.ini file
file(GLOB IMGUI_INI imgui.ini)
file(COPY ${IMGUI_INI} DESTINATION ${CMAKE_BINARY_DIR})
file(GLOB DOTNET_LIBRARY library)
file(COPY ${DOTNET_LIBRARY} DESTINATION ${CMAKE_BINARY_DIR})
file(GLOB_RECURSE SRC_SOURCE ./**.cpp)
file(GLOB_RECURSE SRC_SOURCE ./Editor/**.cpp)
add_executable(${PROJECT_NAME} ${SRC_SOURCE})
target_link_libraries(${PROJECT_NAME} PRIVATE Prism-shared)
target_link_libraries(${PROJECT_NAME} PRIVATE Prism-static)
# Enable ImGui Docking space
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DOCKSPACE)

View File

@ -23,7 +23,7 @@ public:
};
Prism::Application* Prism::CreateApplication()
Prism::Application* Prism::CreateApplication(const CommandArgs args)
{
return new Editor({"hello wrold", 1920, 1080});
return new Editor({"hello world", 1920, 1080, args});
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,10 @@
#ifndef EDITORLAYER_H
#define EDITORLAYER_H
#include "Panels/ConsolePanel.h"
#include "Prism.h"
#include "Prism/Editor/ContentBrowserPanel.h"
#include "Prism/Editor/ObjectsPanel.h"
#include "Prism/Editor/SceneHierachyPanel.h"
namespace Prism
@ -26,7 +29,7 @@ namespace Prism
bool OnKeyPressedEvent(KeyPressedEvent& e);
bool OnMouseButtonPressedEvent(MouseButtonPressedEvent& e);
void ShowBoundingBoxes(bool show, bool onTop = false);
void ShowBoundingBoxes(bool show);
void SelectEntity(Entity entity);
void UpdateWindowTitle(const std::string& sceneName);
@ -44,18 +47,22 @@ namespace Prism
void OnEntityDeleted(Entity e);
Ray CastMouseRay();
void NewScene();
void OpenScene();
void OpenScene(const std::string& filepath);
void SaveScene();
void SaveSceneAs();
void OnScenePlay();
void OnSceneStop();
float GetSnapValue();
float GetSnapValue() const;
private:
Scope<SceneHierarchyPanel> m_SceneHierarchyPanel;
Scope<ContentBrowserPanel> m_ContentBrowserPanel;
Scope<ObjectsPanel> m_ObjectsPanel;
Ref<Scene> m_ActiveScene;
Ref<Scene> m_CurrentScene;
Ref<Scene> m_RuntimeScene, m_EditorScene;
std::string m_SceneFilePath;
@ -112,7 +119,7 @@ namespace Prism
struct RoughnessInput
{
float Value = 0.5f;
float Value = 1.0f;
Ref<Texture2D> TextureMap;
bool UseTexture = false;
};
@ -120,22 +127,14 @@ namespace Prism
// PBR params
bool m_RadiancePrefilter = false;
float m_EnvMapRotation = 0.0f;
// Editor resources
Ref<Texture2D> m_CheckerboardTex;
Ref<Texture2D> m_PlayButtonTex;
Ref<Texture2D> m_PlayButtonTex, m_StopButtonTex, m_PauseButtonTex;
// configure button
bool m_AllowViewportCameraEvents = false;
bool m_DrawOnTopBoundingBoxes = false;
bool m_UIShowBoundingBoxes = false;
bool m_UIShowBoundingBoxesOnTop = false;
enum class SceneType : uint32_t
{
@ -143,14 +142,18 @@ namespace Prism
};
SceneType m_SceneType;
bool m_ViewportPanelMouseOver = false;
bool m_ViewportPanelHovered = false;
bool m_ViewportPanelFocused = false;
bool m_ShowPhysicsSettings = false;
enum class SceneState
{
Edit = 0, Play = 1, Pause = 2
};
SceneState m_SceneState = SceneState::Edit;
private:
Scope<ConsolePanel> m_ConsolePanel;
};
}

View File

@ -0,0 +1,66 @@
//
// Created by Atdunbg on 2026/3/26.
//
#include "ConsolePanel.h"
namespace Prism
{
ConsolePanel::ConsolePanel()
{
// 预留一些空间
m_Messages.reserve(1000);
}
void ConsolePanel::OnImGuiRender()
{
ImGui::Begin("Console");
// 工具栏
if (ImGui::Button("Clear"))
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Messages.clear();
}
ImGui::SameLine();
ImGui::Checkbox("Auto-scroll", &m_AutoScroll);
ImGui::SameLine();
m_Filter.Draw("Filter", 200);
// 消息列表区域
ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar);
{
std::lock_guard<std::mutex> lock(m_Mutex);
for (const auto& [msg, color] : m_Messages)
{
if (m_Filter.PassFilter(msg.c_str()))
{
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::TextUnformatted(msg.c_str());
ImGui::PopStyleColor();
}
}
if (m_ScrollToBottom)
{
ImGui::SetScrollHereY(1.0f);
m_ScrollToBottom = false;
}
}
ImGui::EndChild();
ImGui::End();
}
void ConsolePanel::AddMessage(const std::string& message, ImVec4 color)
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Messages.emplace_back(message, color);
while (m_Messages.size() > 5000)
m_Messages.erase(m_Messages.begin());
if (m_AutoScroll && !m_ScrollToBottom)
m_ScrollToBottom = true;
}
}

View File

@ -0,0 +1,31 @@
//
// Created by Atdunbg on 2026/3/26.
//
#ifndef PRISM_CONSOLEPANEL_H
#define PRISM_CONSOLEPANEL_H
#include <mutex>
#include <vector>
#include "imgui.h"
namespace Prism
{
class ConsolePanel
{
public:
ConsolePanel();
void OnImGuiRender();
void AddMessage(const std::string& message, ImVec4 color = ImVec4(1,1,1,1));
private:
std::vector<std::pair<std::string, ImVec4>> m_Messages;
ImGuiTextFilter m_Filter;
bool m_AutoScroll = true;
bool m_ScrollToBottom = false;
std::mutex m_Mutex;
};
}
#endif //PRISM_CONSOLEPANEL_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,155 +0,0 @@
Scene: Scene Name
Environment:
AssetPath: assets/env/pink_sunrise_4k.hdr
Light:
Direction: [-0.787, -0.73299998, 1]
Radiance: [1, 1, 1]
Multiplier: 0.514999986
Entities:
- Entity: 12498244675852797835
TagComponent:
Tag: Box
TransformComponent:
Position: [-12.0348625, 6.59647179, 9.60061925e-07]
Rotation: [1, 0, 0, 0]
Scale: [3.00000024, 0.300000012, 1]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 0
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [1.5, 0.150000006]
Density: 1
Friction: 1
- Entity: 5178862374589434728
TagComponent:
Tag: Camera
TransformComponent:
Position: [-21.7406311, 9.70659542, 15]
Rotation: [0.999910355, -0.0133911213, 0, 0]
Scale: [1, 1, 1]
ScriptComponent:
ModuleName: Example.BasicController
StoredFields:
- Name: Speed
Type: 1
Data: 12
CameraComponent:
Camera: some camera data...
Primary: true
- Entity: 1289165777996378215
TagComponent:
Tag: Cube
TransformComponent:
Position: [500, 0, 0]
Rotation: [1, 0, 0, 0]
Scale: [1200, 1, 5]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 0
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [600, 0.5]
Density: 1
Friction: 2
- Entity: 14057422478420564497
TagComponent:
Tag: Player
TransformComponent:
Position: [-23.6932545, 1.59184527, -1.96369365e-06]
Rotation: [1, 0, 0, 0]
Scale: [1, 1, 1]
ScriptComponent:
ModuleName: Example.PlayerCube
StoredFields:
- Name: HorizontalForce
Type: 1
Data: 0.5
- Name: MaxSpeed
Type: 5
Data: [7, 10]
- Name: JumpForce
Type: 1
Data: 3
MeshComponent:
AssetPath: assets/meshes/Sphere1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 29.2000008
CircleCollider2DComponent:
Offset: [0, 0]
Radius: 0.5
Density: 1
Friction: 1
- Entity: 1352995477042327524
TagComponent:
Tag: Box
TransformComponent:
Position: [-29.6808929, 29.7597198, 0]
Rotation: [0.707106769, 0, 0, 0.707106769]
Scale: [58.4179001, 4.47999144, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 0
Mass: 3
BoxCollider2DComponent:
Offset: [0, 0]
Size: [29.7000008, 2.24000001]
Density: 1
Friction: 1
- Entity: 15223077898852293773
TagComponent:
Tag: Box
TransformComponent:
Position: [6.12674046, 45.5617676, 0]
Rotation: [0.977883637, 0, 0, -0.209149584]
Scale: [4.47999668, 4.47999668, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [2.24000001, 2.24000001]
Density: 1
Friction: 1
- Entity: 5421735812495444456
TagComponent:
Tag: Box
TransformComponent:
Position: [-20.766222, 2.29431438, 0]
Rotation: [1, 0, 0, 0]
Scale: [3.00000024, 0.300000012, 1]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 0
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [1.5, 0.150000006]
Density: 1
Friction: 1
- Entity: 2842299641876190180
TagComponent:
Tag: Box
TransformComponent:
Position: [-16.6143265, 4.39151001, 6.43359499e-09]
Rotation: [1, 0, 0, 0]
Scale: [3.00000024, 0.300000012, 1]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 0
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [1.5, 0.150000006]
Density: 1
Friction: 1

View File

@ -0,0 +1,336 @@
Scene: Scene Name
Environment:
AssetHandle: 5211537204242875091
Entities:
- Entity: 8293051279669100759
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [1.736814, 1.4724115, -4.2181306]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 5834225236589765516
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [-2.6417403, 1.4724115, -7.9285727]
Rotation: [0.52199936, 0, 0]
Scale: [1, 1.0000001, 1.0000001]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 8234256119181302872
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [1.736814, 1.4724115, -7.9285727]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 12935252585493481950
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [-1.5106764, 6.237644, -4.2181306]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 3328246672296261054
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [1.736814, 1.4724115, -0.88378817]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 4208267561919679628
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [-2.6417403, 1.4724115, -4.8956265]
Rotation: [-0.4034239, 0, 0]
Scale: [1, 0.99999994, 0.99999994]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: true
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 8114736924719261351
Parent: 0
Children:
[]
TagComponent:
Tag: Camera
TransformComponent:
Position: [0, 0.8097433, 4.573171]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
CameraComponent:
Camera:
ProjectionType: 0
PerspectiveFOV: 45
PerspectiveNear: 0.01
PerspectiveFar: 10000
OrthographicSize: 10
OrthographicNear: -1
OrthographicFar: 1
Primary: true
- Entity: 9267298328378270409
Parent: 0
Children:
[]
TagComponent:
Tag: Player
TransformComponent:
Position: [0, 0.70693016, 0]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
ScriptComponent:
ModuleName: FPSExample.FPSPlayer
StoredFields:
- Name: WalkingSpeed
Type: 1
Data: 2
- Name: RunSpeed
Type: 1
Data: 5
- Name: JumpForce
Type: 1
Data: 1
- Name: MouseSensitivity
Type: 1
Data: 10
- Name: CameraForwardOffset
Type: 1
Data: -2
- Name: CameraYOffset
Type: 1
Data: 2
MeshComponent:
AssetID: 3043502408333723884
AssetPath: assets/meshes/Default/Capsule.fbx
RigidBodyComponent:
BodyType: 1
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: true
LockRotationY: false
LockRotationZ: true
MeshColliderComponent:
IsConvex: true
IsTrigger: false
OverrideMesh: false
Material: 0
MaterialPath: ""
- Entity: 10732070446010033158
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [0, -2.6466873, 0]
Rotation: [0, 0, 0]
Scale: [100, 1, 100]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
RigidBodyComponent:
BodyType: 0
Mass: 1
LinearDrag: 0
AngularDrag: 0.05
DisableGravity: false
IsKinematic: false
Layer: 0
Constraints:
LockPositionX: false
LockPositionY: false
LockPositionZ: false
LockRotationX: false
LockRotationY: false
LockRotationZ: false
BoxColliderComponent:
Offset: [0, 0, 0]
Size: [2, 2, 2]
IsTrigger: false
Material: 0
MaterialPath: ""
- Entity: 5099152432245948441
Parent: 0
Children:
[]
TagComponent:
Tag: venice_dawn_1_4k
TransformComponent:
Position: [0, 0, 0]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
SkyLightComponent:
EnvironmentMap: 5211537204242875091
EnvironmentAssetPath: assets/env/venice_dawn_1_4k.hdr
Intensity: 1
Angle: 0
DynamicSky: false
TurbidityAzimuthInclination: [2, 0, 0]
PhysicsLayers:
[]

View File

@ -0,0 +1,64 @@
Scene: Scene Name
Environment:
AssetHandle: 6095149963749185931
Entities:
- Entity: 6421668200759325475
Parent: 0
Children:
[]
TagComponent:
Tag: M1911Materials
TransformComponent:
Position: [0, 3.159583, 0]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
MeshComponent:
AssetID: 7219694555758922702
AssetPath: assets/models/m1911/M1911Materials.fbx
- Entity: 16992665426857995732
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [0, 0, 0]
Rotation: [0, 0, 0]
Scale: [50, 1, 50]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
- Entity: 18182275256052989728
Parent: 0
Children:
[]
TagComponent:
Tag: Sky Light
TransformComponent:
Position: [0, 0, 0]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
SkyLightComponent:
EnvironmentMap: 6095149963749185931
EnvironmentAssetPath: ""
Intensity: 1
Angle: 0
DynamicSky: true
TurbidityAzimuthInclination: [2, 0.15, 0.71]
- Entity: 17803125207910630398
Parent: 0
Children:
[]
TagComponent:
Tag: Directional Light
TransformComponent:
Position: [0, 0, 0]
Rotation: [-0.4810984, -0.20606127, 2.9545484]
Scale: [1.0000023, 1.0000007, 0.9999998]
DirectionalLightComponent:
Radiance: [1, 1, 1]
CastShadows: true
SoftShadows: true
LightSize: 0.9
PhysicsLayers:
[]

View File

@ -1,174 +0,0 @@
Scene: Scene Name
Environment:
AssetPath: assets/env/pink_sunrise_4k.hdr
Light:
Direction: [-0.787, -0.73299998, 1]
Radiance: [1, 1, 1]
Multiplier: 0.514999986
Entities:
- Entity: 15861629587505754
TagComponent:
Tag: Box
TransformComponent:
Position: [-18.2095661, 39.2518234, 0]
Rotation: [0.967056513, 0, 0, -0.254561812]
Scale: [4.47999525, 4.47999525, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [2.24000001, 2.24000001]
- Entity: 15223077898852293773
TagComponent:
Tag: Box
TransformComponent:
Position: [5.37119865, 43.8762894, 0]
Rotation: [0.977883637, 0, 0, -0.209149718]
Scale: [4.47999668, 4.47999668, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [2.24000001, 2.24000001]
- Entity: 2157107598622182863
TagComponent:
Tag: Box
TransformComponent:
Position: [-7.60411549, 44.1442184, 0]
Rotation: [0.989285827, 0, 0, 0.145991713]
Scale: [4.47999287, 4.47999287, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 0.5
BoxCollider2DComponent:
Offset: [0, 0]
Size: [2.24000001, 2.24000001]
- Entity: 8080964283681139153
TagComponent:
Tag: Box
TransformComponent:
Position: [-0.739211679, 37.7653275, 0]
Rotation: [0.956475914, 0, 0, -0.291811317]
Scale: [5, 2, 2]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 0.25
BoxCollider2DComponent:
Offset: [0, 0]
Size: [2.5, 1]
- Entity: 1352995477042327524
TagComponent:
Tag: Box
TransformComponent:
Position: [-8.32969856, 30.4078159, 0]
Rotation: [0.781595349, 0, 0, 0.623785794]
Scale: [14.000001, 4.47999334, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 3
BoxCollider2DComponent:
Offset: [0, 0]
Size: [7, 2.24000001]
- Entity: 935615878363259513
TagComponent:
Tag: Box
TransformComponent:
Position: [6.88031197, 31.942337, 0]
Rotation: [0.986578286, 0, 0, 0.163288936]
Scale: [4.47999954, 4.47999954, 4.48000002]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [2.24000001, 2.24000001]
- Entity: 14057422478420564497
TagComponent:
Tag: Player
TransformComponent:
Position: [0, 22.774044, 0]
Rotation: [0.942591429, 0, 0, -0.333948225]
Scale: [6.00000048, 6.00000048, 4.48000002]
ScriptComponent:
ModuleName: Example.PlayerCube
StoredFields:
- Name: HorizontalForce
Type: 1
Data: 10
- Name: VerticalForce
Type: 1
Data: 10
MeshComponent:
AssetPath: assets/meshes/Sphere1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 1
CircleCollider2DComponent:
Offset: [0, 0]
Radius: 3
- Entity: 1289165777996378215
TagComponent:
Tag: Cube
TransformComponent:
Position: [0, 0, 0]
Rotation: [1, 0, 0, 0]
Scale: [50, 1, 50]
ScriptComponent:
ModuleName: Example.Sink
StoredFields:
- Name: SinkSpeed
Type: 1
Data: 0
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 0
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [25, 0.5]
- Entity: 5178862374589434728
TagComponent:
Tag: Camera
TransformComponent:
Position: [0, 25, 79.75]
Rotation: [0.995602965, -0.0936739072, 0, 0]
Scale: [1, 0.999999821, 0.999999821]
ScriptComponent:
ModuleName: Example.BasicController
StoredFields:
- Name: Speed
Type: 1
Data: 12
CameraComponent:
Camera: some camera data...
Primary: true
- Entity: 3948844418381294888
TagComponent:
Tag: Box
TransformComponent:
Position: [-1.48028564, 49.5945244, -2.38418579e-07]
Rotation: [0.977883637, 0, 0, -0.209149733]
Scale: [1.99999976, 1.99999976, 2]
MeshComponent:
AssetPath: assets/meshes/Cube1m.fbx
RigidBody2DComponent:
BodyType: 1
Mass: 1
BoxCollider2DComponent:
Offset: [0, 0]
Size: [1, 1]

View File

@ -1,66 +0,0 @@
Scene: Scene Name
Environment:
AssetPath: assets/env/birchwood_4k.hdr
Light:
Direction: [-0.5, -0.5, 1]
Radiance: [1, 1, 1]
Multiplier: 1
Entities:
- Entity: 1289165777996378215
TagComponent:
Tag: Sphere
TransformComponent:
Position: [0, 21.9805069, -1.64006281]
Rotation: [1, 0, 0, 0]
Scale: [0.100000024, 0.100000024, 0.100000024]
ScriptComponent:
ModuleName: Example.Sink
StoredFields:
- Name: SinkSpeed
Type: 1
Data: 5
MeshComponent:
AssetPath: assets/meshes/Sphere1m.fbx
- Entity: 5178862374589434728
TagComponent:
Tag: Camera
TransformComponent:
Position: [0, 14.75, 79.75]
Rotation: [0.995602965, -0.0936739072, 0, 0]
Scale: [1, 0.999999821, 0.999999821]
ScriptComponent:
ModuleName: Example.BasicController
StoredFields:
- Name: Speed
Type: 1
Data: 12
CameraComponent:
Camera: some camera data...
Primary: true
- Entity: 9095450049242347594
TagComponent:
Tag: Test Entity
TransformComponent:
Position: [0.248109579, -1.90734863e-06, -0.268640995]
Rotation: [1, 0, 0, 0]
Scale: [1, 1, 1]
ScriptComponent:
ModuleName: Example.Script
StoredFields:
- Name: VerticalSpeed
Type: 1
Data: 0
- Name: SinkRate
Type: 1
Data: 0
- Name: Speed
Type: 1
Data: 1
- Name: Rotation
Type: 1
Data: 0
- Name: Velocity
Type: 6
Data: [0, 0, 0]
MeshComponent:
AssetPath: assets/meshes/TestScene.fbx

View File

@ -0,0 +1,36 @@
Scene: Scene Name
Environment:
AssetHandle: 5211537204242875091
Entities:
- Entity: 15706224176559717512
Parent: 0
Children:
[]
TagComponent:
Tag: Cube
TransformComponent:
Position: [0, 0, 0]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
MeshComponent:
AssetID: 18328012085543462741
AssetPath: assets/meshes/Default/Cube.fbx
- Entity: 8041206185299282567
Parent: 0
Children:
[]
TagComponent:
Tag: venice_dawn_1_4k
TransformComponent:
Position: [0, 0, 0]
Rotation: [0, 0, 0]
Scale: [1, 1, 1]
SkyLightComponent:
EnvironmentMap: 5211537204242875091
EnvironmentAssetPath: assets/env/venice_dawn_1_4k.hdr
Intensity: 1
Angle: 0
DynamicSky: false
TurbidityAzimuthInclination: [2, 0, 0]
PhysicsLayers:
[]

View File

@ -0,0 +1,63 @@
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
out vec2 v_TexCoord;
void main()
{
vec4 position = vec4(a_Position.xy, 0.0, 1.0);
v_TexCoord = a_TexCoord;
gl_Position = position;
}
#type fragment
#version 430
layout(location = 0) out vec4 o_Color;
in vec2 v_TexCoord;
uniform sampler2D u_SceneTexture;
uniform sampler2D u_BloomTexture;
uniform float u_Exposure;
uniform bool u_EnableBloom;
void main()
{
#if 1
const float gamma = 2.2;
const float pureWhite = 1.0;
// Tonemapping
vec3 color = texture(u_SceneTexture, v_TexCoord).rgb;
if (u_EnableBloom)
{
vec3 bloomColor = texture(u_BloomTexture, v_TexCoord).rgb;
color += bloomColor;
}
// Reinhard tonemapping
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
float mappedLuminance = (luminance * (1.0 + luminance / (pureWhite * pureWhite))) / (1.0 + luminance);
// Scale color by ratio of average luminances.
vec3 mappedColor = (mappedLuminance / luminance) * color* u_Exposure;
// Gamma correction.
o_Color = vec4(mappedColor, 1.0);
#else
const float gamma = 2.2;
vec3 hdrColor = texture(u_SceneTexture, v_TexCoord).rgb;
vec3 bloomColor = texture(u_BloomTexture, v_TexCoord).rgb;
hdrColor += bloomColor; // additive blending
// tone mapping
vec3 result = vec3(1.0) - exp(-hdrColor * u_Exposure);
// also gamma correct while we're at it
result = pow(result, vec3(1.0 / gamma));
o_Color = vec4(result, 1.0);
#endif
}

View File

@ -0,0 +1,84 @@
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
out vec2 v_TexCoord;
void main()
{
vec4 position = vec4(a_Position.xy, 0.0, 1.0);
v_TexCoord = a_TexCoord;
gl_Position = position;
}
#type fragment
#version 430
layout(location = 0) out vec4 o_Color;
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
uniform bool u_Horizontal; // 未使用,可保留或移除
uniform bool u_FirstPass; // 是否进行阈值处理
uniform float u_Threshold; // 亮度阈值
void main()
{
float Pi = 6.28318530718; // 2*PI
float Directions = 32.0; // 模糊方向数
float Quality = 6.0; // 每个方向上的采样质量(采样次数)
float Size = 16.0; // 模糊半径
vec2 Radius = Size / textureSize(u_Texture, 0);
// 中心像素采样
vec3 centerColor = texture(u_Texture, v_TexCoord).rgb;
float centerLum = dot(centerColor, vec3(0.2126, 0.7152, 0.0722));
// 如果启用第一次处理且中心像素亮度低于阈值,则直接输出黑色(不进行模糊)
if (u_FirstPass && centerLum <= u_Threshold)
{
o_Color = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
vec3 result = centerColor; // 先累加中心像素
float totalSamples = 1.0; // 有效采样计数(中心像素已计入)
// 周围像素采样
for (float d = 0.0; d < Pi; d += Pi / Directions)
{
for (float i = 1.0 / Quality; i <= 1.0; i += 1.0 / Quality)
{
vec2 offset = vec2(cos(d), sin(d)) * Radius * i;
vec3 sampleColor = texture(u_Texture, v_TexCoord + offset).rgb;
if (u_FirstPass)
{
float lum = dot(sampleColor, vec3(0.2126, 0.7152, 0.0722));
if (lum <= u_Threshold)
{
// 低于阈值则贡献黑色,但采样点仍计入分母?这里选择不计入有效采样数
// 若希望保持模糊能量,可以 continue 跳过累加,但需调整分母
// 为简单起见,此处设为黑色并计入计数(分母不变),也可选择跳过
sampleColor = vec3(0.0);
// 如果希望忽略该采样点,可以 continue 并减少 totalSamples
// 但为了效果平滑,这里保留为黑色并计入计数
}
}
result += sampleColor;
totalSamples += 1.0;
}
}
// 归一化:除以总采样数(包括中心像素)
// 若之前选择忽略低于阈值的采样点continue则需相应调整 totalSamples
result /= totalSamples;
o_Color = vec4(result, 1.0);
}

View File

@ -0,0 +1,24 @@
// Collider Shader
#type vertex
#version 450
layout(location = 0) in vec3 a_Position;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
void main()
{
gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
}
#type fragment
#version 450
layout(location = 0) out vec4 color;
void main()
{
color = vec4(0.1, 1.0, 0.1, 1.0);
}

View File

@ -0,0 +1,63 @@
#type compute
#version 460 core
layout(local_size_x = 1, local_size_y = 1) in;
layout(binding = 0, std430) buffer Histogram {
uint bins[64];
};
layout(binding = 1, std430) buffer Exposure {
float exposure;
};
uniform float u_SpeedUp;
uniform float u_SpeedDown;
uniform float u_Key;
uniform float u_LowPercent;
uniform float u_HighPercent;
uniform float u_MinExposure;
uniform float u_MaxExposure;
uniform float u_DeltaTime;
uniform float u_LogMin;
uniform float u_LogMax;
void main() {
float currentExposure = exposure;
uint total = 0;
uint prefix[64];
for (int i = 0; i < 64; i++) {
total += bins[i];
prefix[i] = total;
}
float lowCount = u_LowPercent * 0.01 * total;
float highCount = u_HighPercent * 0.01 * total;
int lowBin = 0, highBin = 63;
for (int i = 0; i < 64; i++) {
if (prefix[i] < lowCount) lowBin = i + 1;
if (prefix[i] < highCount) highBin = i + 1;
}
lowBin = clamp(lowBin, 0, 63);
highBin = clamp(highBin, 0, 63);
float sumLum = 0.0;
uint count = 0;
for (int i = lowBin; i <= highBin; i++) {
float t = (float(i) + 0.5) / 64.0;
float logLum = u_LogMin + t * (u_LogMax - u_LogMin);
float lum = exp2(logLum);
sumLum += lum * float(bins[i]);
count += bins[i];
}
float avgLum = count > 0 ? sumLum / count : 0.18;
float targetExposure = u_Key / max(avgLum, 0.0001);
targetExposure = clamp(targetExposure, u_MinExposure, u_MaxExposure);
float speed = (targetExposure > currentExposure) ? u_SpeedUp : u_SpeedDown;
float adaptFactor = 1.0 - exp(-speed * u_DeltaTime);
float newExposure = mix(currentExposure, targetExposure, adaptFactor);
newExposure = clamp(newExposure, u_MinExposure, u_MaxExposure);
exposure = newExposure;
}

View File

@ -1,45 +0,0 @@
// Grid Shader
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
out vec2 v_TexCoord;
void main()
{
vec4 position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
gl_Position = position;
v_TexCoord = a_TexCoord;
}
#type fragment
#version 430
layout(location = 0) out vec4 color;
uniform float u_Scale;
uniform float u_Res;
in vec2 v_TexCoord;
float grid(vec2 st, float res)
{
vec2 grid = fract(st);
return step(res, grid.x) * step(res, grid.y);
}
void main()
{
float scale = u_Scale;
float resolution = u_Res;
float x = grid(v_TexCoord * scale, resolution);
color = vec4(vec3(0.2), 0.5) * (1.0 - x);
}

View File

@ -0,0 +1,26 @@
#type compute
#version 460 core
layout(local_size_x = 16, local_size_y = 16) in;
layout(binding = 0) uniform sampler2D u_SceneColor;
layout(binding = 1, std430) buffer Histogram {
uint bins[64];
};
uniform float u_LogMin;
uniform float u_LogMax;
void main() {
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = textureSize(u_SceneColor, 0);
if (texel.x >= size.x || texel.y >= size.y) return;
vec3 color = texelFetch(u_SceneColor, texel, 0).rgb;
float lum = max(dot(color, vec3(0.2126, 0.7152, 0.0722)), 0.0001);
float logLum = log2(lum);
float invLogRange = 1.0 / (u_LogMax - u_LogMin);
float t = (logLum - u_LogMin) * invLogRange;
int bin = int(clamp(t * 64.0, 0.0, 63.0));
atomicAdd(bins[bin], 1u);
}

View File

@ -0,0 +1,224 @@
// Infinite Grid Shader
// Based on "The Best Darn Grid Shader (Yet)" by Ben Golus
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
// camera
uniform mat4 u_View;
uniform mat4 u_Projection;
uniform vec3 u_CameraPosition;
out CameraData{
mat4 ViewProjection;
vec3 Position;
}CameraOutput;
out vec3 v_NearPoint;
out vec3 v_FarPoint;
vec3 unprojectPoint(float x, float y, float z) {
mat4 viewInv = inverse(u_View);
mat4 projInv = inverse(u_Projection);
vec4 unprojectedPoint = viewInv * projInv * vec4(x, y, z, 1.0);
return unprojectedPoint.xyz / unprojectedPoint.w;
}
void main() {
v_NearPoint = unprojectPoint(a_Position.x, a_Position.y, 0.0);
v_FarPoint = unprojectPoint(a_Position.x, a_Position.y, 1.0);
CameraOutput.ViewProjection = u_Projection * u_View;
CameraOutput.Position = u_CameraPosition;
gl_Position = vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
in vec3 v_NearPoint;
in vec3 v_FarPoint;
in CameraData{
mat4 ViewProjection;
vec3 Position;
}CameraInput;
// Grid plane: 0 = XZ (Y up), 1 = XY (Z forward), 2 = YZ (X right)
uniform int u_GridPlane;
uniform float u_GridScale;
uniform vec4 u_GridColorThin;
uniform vec4 u_GridColorThick;
uniform vec4 u_AxisColorX;
uniform vec4 u_AxisColorZ;
uniform float u_FadeDistance;
float computeDepth(vec3 pos) {
vec4 clipSpacePos = CameraInput.ViewProjection * vec4(pos, 1.0);
return clipSpacePos.z / clipSpacePos.w;
}
// Get the plane normal based on grid plane type
vec3 getPlaneNormal() {
if (u_GridPlane == 1) return vec3(0.0, 0.0, 1.0); // XY plane, Z normal
if (u_GridPlane == 2) return vec3(1.0, 0.0, 0.0); // YZ plane, X normal
return vec3(0.0, 1.0, 0.0); // XZ plane, Y normal (default)
}
// Get 2D coordinates on the plane
vec2 getPlaneCoords(vec3 pos) {
if (u_GridPlane == 1) return pos.xy; // XY plane
if (u_GridPlane == 2) return pos.yz; // YZ plane
return pos.xz; // XZ plane (default)
}
// Get the component perpendicular to the plane (for axis drawing)
vec2 getAxisCoords(vec3 pos) {
// Returns the two coordinates used for drawing axis lines
// First component -> first axis color, Second component -> second axis color
if (u_GridPlane == 1) return vec2(pos.x, pos.y); // XY: X-axis and Y-axis
if (u_GridPlane == 2) return vec2(pos.y, pos.z); // YZ: Y-axis and Z-axis
return vec2(pos.x, pos.z); // XZ: X-axis and Z-axis
}
// Calculate t for ray-plane intersection
float rayPlaneIntersection(vec3 nearPoint, vec3 farPoint) {
vec3 rayDir = farPoint - nearPoint;
if (u_GridPlane == 1) {
// XY plane (z = 0)
if (abs(rayDir.z) < 0.0001) return -1.0;
return -nearPoint.z / rayDir.z;
}
if (u_GridPlane == 2) {
// YZ plane (x = 0)
if (abs(rayDir.x) < 0.0001) return -1.0;
return -nearPoint.x / rayDir.x;
}
// XZ plane (y = 0) - default
if (abs(rayDir.y) < 0.0001) return -1.0;
return -nearPoint.y / rayDir.y;
}
// Get view angle component for normal fade
float getViewAngleComponent(vec3 viewDir) {
if (u_GridPlane == 1) return abs(viewDir.z); // XY plane
if (u_GridPlane == 2) return abs(viewDir.x); // YZ plane
return abs(viewDir.y); // XZ plane
}
// Pristine grid - single pixel line with proper AA
float pristineGridLine(vec2 uv) {
vec2 dudv = fwidth(uv);
vec2 uvMod = fract(uv);
vec2 uvDist = min(uvMod, 1.0 - uvMod);
vec2 distInPixels = uvDist / dudv;
vec2 lineAlpha = 1.0 - smoothstep(0.0, 1.0, distInPixels);
float alpha = max(lineAlpha.x, lineAlpha.y);
float density = max(dudv.x, dudv.y);
float densityFade = 1.0 - smoothstep(0.5, 1.0, density);
return alpha * densityFade;
}
// Axis line - single pixel wide
float axisLineAA(float coord, float dudv) {
float distInPixels = abs(coord) / dudv;
return 1.0 - smoothstep(0.0, 1.5, distInPixels);
}
void main() {
float t = rayPlaneIntersection(v_NearPoint, v_FarPoint);
if (t < 0.0) {
discard;
}
vec3 fragPos3D = v_NearPoint + t * (v_FarPoint - v_NearPoint);
float depth = computeDepth(fragPos3D);
if (depth > 1.0 || depth < -1.0) {
discard;
}
vec2 worldPos = getPlaneCoords(fragPos3D);
// === Fading ===
// Radial fade
float dist = length(fragPos3D - CameraInput.Position);
float radialFade = 1.0 - smoothstep(u_FadeDistance * 0.3, u_FadeDistance, dist);
// Normal fade (view angle)
vec3 viewDir = normalize(fragPos3D - CameraInput.Position);
float viewAngle = getViewAngleComponent(viewDir);
float normalFade = smoothstep(0.0, 0.15, viewAngle);
float fadeFactor = radialFade * normalFade;
if (fadeFactor < 0.001) {
discard;
}
// === Grid calculation ===
vec2 gridCoord1 = worldPos / u_GridScale;
vec2 gridCoord10 = worldPos / (u_GridScale * 10.0);
float grid1 = pristineGridLine(gridCoord1);
float grid10 = pristineGridLine(gridCoord10);
// LOD blend
vec2 deriv1 = fwidth(gridCoord1);
float lodFactor = smoothstep(20.0, 200.0, dist);
// float lodFactor = smoothstep(0.2, 0.5, max(deriv1.x, deriv1.y));
// Combine grids
float gridIntensity = mix(max(grid1, grid10 * 0.7), grid10, lodFactor);
// Grid color
vec3 gridColor = mix(u_GridColorThin.rgb, u_GridColorThick.rgb, lodFactor);
float baseAlpha = mix(u_GridColorThin.a, u_GridColorThick.a, lodFactor);
float gridAlpha = baseAlpha * gridIntensity * fadeFactor;
// === Axis lines ===
vec2 axisCoords = getAxisCoords(fragPos3D);
vec2 worldDeriv = fwidth(worldPos);
// First axis (uses AxisColorX - typically red)
float axis1Alpha = axisLineAA(axisCoords.y, worldDeriv.y) * fadeFactor;
// Second axis (uses AxisColorZ - typically blue)
float axis2Alpha = axisLineAA(axisCoords.x, worldDeriv.x) * fadeFactor;
// === Final composition ===
vec3 finalColor = gridColor;
float finalAlpha = gridAlpha;
// Blend axis colors
if (axis2Alpha > 0.001) {
float blend = axis2Alpha * u_AxisColorZ.a;
finalColor = mix(finalColor, u_AxisColorZ.rgb, blend);
finalAlpha = max(finalAlpha, blend);
}
if (axis1Alpha > 0.001) {
float blend = axis1Alpha * u_AxisColorX.a;
finalColor = mix(finalColor, u_AxisColorX.rgb, blend);
finalAlpha = max(finalAlpha, blend);
}
if (finalAlpha < 0.001) {
discard;
}
gl_FragDepth = depth * 0.5 + 0.5;
o_Color = vec4(finalColor, finalAlpha);
}

View File

@ -4,13 +4,10 @@
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
out vec2 v_TexCoord;
void main()
{
gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);

View File

@ -0,0 +1,36 @@
// Outline Shader
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 5) in ivec4 a_BoneIndices;
layout(location = 6) in vec4 a_BoneWeights;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
const int MAX_BONES = 100;
uniform mat4 u_BoneTransforms[100];
void main()
{
mat4 boneTransform = u_BoneTransforms[a_BoneIndices[0]] * a_BoneWeights[0];
boneTransform += u_BoneTransforms[a_BoneIndices[1]] * a_BoneWeights[1];
boneTransform += u_BoneTransforms[a_BoneIndices[2]] * a_BoneWeights[2];
boneTransform += u_BoneTransforms[a_BoneIndices[3]] * a_BoneWeights[3];
vec4 localPosition = boneTransform * vec4(a_Position, 1.0);
gl_Position = u_ViewProjection * u_Transform * localPosition;
}
#type fragment
#version 430
layout(location = 0) out vec4 color;
void main()
{
color = vec4(1.0, 0.5, 0.0, 1.0);
}

View File

@ -1,8 +1,8 @@
// -----------------------------
// -- From Hazel Engine PBR shader --
// -- Based on Hazel PBR shader --
// -----------------------------
// Note: this shader is still very much in progress. There are likely many bugs and future additions that will go in.
// Currently heavily updated.
// Currently heavily updated.
//
// References upon which this is based:
// - Unreal Engine 4 PBR notes (https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf)
@ -21,19 +21,25 @@ layout(location = 4) in vec2 a_TexCoord;
layout(location = 5) in ivec4 a_BoneIndices;
layout(location = 6) in vec4 a_BoneWeights;
uniform mat4 u_ViewProjectionMatrix;
uniform mat4 u_Transform;
const int MAX_BONES = 100;
uniform mat4 u_BoneTransforms[100];
uniform mat4 u_ViewProjectionMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_Transform;
uniform mat4 u_LightSpaceMatrix;
out VertexOutput
{
vec3 WorldPosition;
vec3 WorldPosition;
vec3 Normal;
vec2 TexCoord;
mat3 WorldNormals;
vec3 Binormal;
vec2 TexCoord;
mat3 WorldNormals;
mat3 WorldTransform;
vec3 Binormal;
vec3 ViewPosition;
vec4 FragPosLightSpace;
} vs_Output;
void main()
@ -43,15 +49,21 @@ void main()
boneTransform += u_BoneTransforms[a_BoneIndices[2]] * a_BoneWeights[2];
boneTransform += u_BoneTransforms[a_BoneIndices[3]] * a_BoneWeights[3];
vec4 localPosition = boneTransform * vec4(a_Position, 1.0);
vec4 localPosition = boneTransform * vec4(a_Position, 1.0);
vs_Output.WorldPosition = vec3(u_Transform * boneTransform * vec4(a_Position, 1.0));
vs_Output.WorldPosition = vec3(u_Transform * boneTransform * vec4(a_Position, 1.0));
vs_Output.Normal = mat3(u_Transform) * mat3(boneTransform) * a_Normal;
vs_Output.TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y);
vs_Output.WorldNormals = mat3(u_Transform) * mat3(a_Tangent, a_Binormal, a_Normal);
vs_Output.Binormal = mat3(boneTransform) * a_Binormal;
vs_Output.TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y);
vs_Output.WorldNormals = mat3(u_Transform) * mat3(a_Tangent, a_Binormal, a_Normal);
vs_Output.WorldTransform = mat3(u_Transform);
vs_Output.Binormal = a_Binormal;
gl_Position = u_ViewProjectionMatrix * u_Transform * localPosition;
vs_Output.FragPosLightSpace = u_LightSpaceMatrix * u_Transform * localPosition;
vs_Output.ViewPosition = vec3(u_ViewMatrix * vec4(vs_Output.WorldPosition, 1.0));
// gl_Position = u_ViewProjectionMatrix * u_Transform * vec4(a_Position, 1.0);
gl_Position = u_ViewProjectionMatrix * u_Transform * localPosition;
}
#type fragment
@ -61,102 +73,121 @@ const float PI = 3.141592;
const float Epsilon = 0.00001;
const int LightCount = 1;
// Constant normal incidence Fresnel factor for all dielectrics.
const vec3 Fdielectric = vec3(0.04);
struct Light {
vec3 Direction;
vec3 Radiance;
float Multiplier;
struct DirectionalLight {
vec3 Direction;
vec3 Radiance;
float Intensity;
bool CastShadows;
};
struct PointLight {
vec3 Position;
vec3 Radiance;
float Intensity;
float Range;
bool CastShadows;
};
struct SpotLight {
vec3 Position;
vec3 Direction;
vec3 Radiance;
float Intensity;
float Range;
float InnerConeCos;
float OuterConeCos;
bool CastShadows;
};
in VertexOutput
{
vec3 WorldPosition;
vec3 WorldPosition;
vec3 Normal;
vec2 TexCoord;
mat3 WorldNormals;
vec3 Binormal;
vec2 TexCoord;
mat3 WorldNormals;
mat3 WorldTransform;
vec3 Binormal;
vec3 ViewPosition;
vec4 FragPosLightSpace;
} vs_Input;
layout(location=0) out vec4 color;
layout(location = 0) out vec4 color;
layout(location = 1) out vec4 o_BloomColor;
uniform Light lights;
uniform DirectionalLight u_DirectionalLights;
uniform vec3 u_CameraPosition;
// PBR texture inputs
uniform int u_PointLightCount;
uniform PointLight u_PointLights;
uniform int u_SpotLightCount;
uniform SpotLight u_SpotLights;
// PBR
uniform sampler2D u_AlbedoTexture;
uniform sampler2D u_NormalTexture;
uniform sampler2D u_MetalnessTexture;
uniform sampler2D u_RoughnessTexture;
// Environment maps
// environment
uniform samplerCube u_EnvRadianceTex;
uniform samplerCube u_EnvIrradianceTex;
// BRDF LUT
uniform sampler2D u_BRDFLUTTexture;
uniform vec3 u_AlbedoColor;
uniform float u_IBLContribution;
uniform float u_BloomThreshold;
uniform float u_EnvMapRotation;
// baseColor
uniform vec3 u_AlbedoColor;
uniform float u_Metalness;
uniform float u_Roughness;
uniform float u_EnvMapRotation;
// Toggles
uniform float u_RadiancePrefilter;
// textureToggle
uniform float u_AlbedoTexToggle;
uniform float u_NormalTexToggle;
uniform float u_MetalnessTexToggle;
uniform float u_RoughnessTexToggle;
// shadow
uniform sampler2D u_ShadowMap;
uniform float u_ShadowBias;
uniform float u_ShadowSoftness;
uniform float u_ShadowIntensity;
uniform int u_ShadowEnabled;
struct PBRParameters
{
vec3 Albedo;
float Roughness;
float Metalness;
vec3 Normal;
vec3 View;
float NdotV;
vec3 Albedo;
float Roughness;
float Metalness;
vec3 Normal;
vec3 View;
float NdotV;
};
PBRParameters m_Params;
// GGX/Towbridge-Reitz normal distribution function.
// Uses Disney's reparametrization of alpha = roughness^2
// ---------- PBR param func ----------
float ndfGGX(float cosLh, float roughness)
{
float alpha = roughness * roughness;
float alphaSq = alpha * alpha;
float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0;
return alphaSq / (PI * denom * denom);
}
// Single term for separable Schlick-GGX below.
float gaSchlickG1(float cosTheta, float k)
{
return cosTheta / (cosTheta * (1.0 - k) + k);
}
// Schlick-GGX approximation of geometric attenuation function using Smith's method.
float gaSchlickGGX(float cosLi, float NdotV, float roughness)
{
float r = roughness + 1.0;
float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights.
return gaSchlickG1(cosLi, k) * gaSchlickG1(NdotV, k);
float alpha = roughness * roughness;
float alphaSq = alpha * alpha;
float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0;
return alphaSq / (PI * denom * denom);
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float k = (r * r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
@ -166,165 +197,275 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// Shlick's approximation of the Fresnel factor.
vec3 fresnelSchlick(vec3 F0, float cosTheta)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
vec3 fresnelSchlickRoughness(vec3 F0, float cosTheta, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
// ---------------------------------------------------------------------------------------------------
// The following code (from Unreal Engine 4's paper) shows how to filter the environment map
// for different roughnesses. This is mean to be computed offline and stored in cube map mips,
// so turning this on online will cause poor performance
float RadicalInverse_VdC(uint bits)
{
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}
vec2 Hammersley(uint i, uint N)
// ---------- direction light ----------
vec3 ComputeDirectionalLight(DirectionalLight light, vec3 F0, PBRParameters params)
{
return vec2(float(i)/float(N), RadicalInverse_VdC(i));
vec3 L = normalize(-light.Direction);
vec3 Lradiance = light.Radiance * light.Intensity;
vec3 Lh = normalize(L + params.View);
float cosLi = max(0.0, dot(params.Normal, L));
float cosLh = max(0.0, dot(params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, params.View)));
float D = ndfGGX(cosLh, params.Roughness);
float G = GeometrySmith(params.Normal, params.View, L, params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - params.Metalness);
vec3 diffuseBRDF = kd * params.Albedo;
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * params.NdotV);
return (diffuseBRDF + specularBRDF) * Lradiance * cosLi;
}
vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N)
vec3 ComputePointLight(PointLight light, vec3 F0, PBRParameters params, vec3 worldPos)
{
float a = Roughness * Roughness;
float Phi = 2 * PI * Xi.x;
float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) );
float SinTheta = sqrt( 1 - CosTheta * CosTheta );
vec3 H;
H.x = SinTheta * cos( Phi );
H.y = SinTheta * sin( Phi );
H.z = CosTheta;
vec3 UpVector = abs(N.z) < 0.999 ? vec3(0,0,1) : vec3(1,0,0);
vec3 TangentX = normalize( cross( UpVector, N ) );
vec3 TangentY = cross( N, TangentX );
// Tangent to world space
return TangentX * H.x + TangentY * H.y + N * H.z;
vec3 lightVec = light.Position - worldPos;
float dist = length(lightVec);
if (dist > light.Range) return vec3(0.0);
vec3 L = lightVec / dist;
vec3 Lradiance = light.Radiance * light.Intensity;
// 距离衰减:通常使用平方衰减,但为避免分母为零,加一个小值
float attenuation = 1.0 / (dist * dist + 0.0001);
// 可选:范围平滑衰减
float rangeFactor = clamp(1.0 - (dist / light.Range), 0.0, 1.0);
rangeFactor = rangeFactor * rangeFactor; // 平滑
attenuation *= rangeFactor;
vec3 Lh = normalize(L + params.View);
float cosLi = max(0.0, dot(params.Normal, L));
float cosLh = max(0.0, dot(params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, params.View)));
float D = ndfGGX(cosLh, params.Roughness);
float G = GeometrySmith(params.Normal, params.View, L, params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - params.Metalness);
vec3 diffuseBRDF = kd * params.Albedo;
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * params.NdotV);
return (diffuseBRDF + specularBRDF) * Lradiance * cosLi * attenuation;
}
float TotalWeight = 0.0;
vec3 PrefilterEnvMap(float Roughness, vec3 R)
vec3 ComputeSpotLight(SpotLight light, vec3 F0, PBRParameters params, vec3 worldPos)
{
vec3 N = R;
vec3 V = R;
vec3 PrefilteredColor = vec3(0.0);
int NumSamples = 1024;
for(int i = 0; i < NumSamples; i++)
{
vec2 Xi = Hammersley(i, NumSamples);
vec3 H = ImportanceSampleGGX(Xi, Roughness, N);
vec3 L = 2 * dot(V, H) * H - V;
float NoL = clamp(dot(N, L), 0.0, 1.0);
if (NoL > 0)
{
PrefilteredColor += texture(u_EnvRadianceTex, L).rgb * NoL;
TotalWeight += NoL;
}
}
return PrefilteredColor / TotalWeight;
vec3 lightVec = light.Position - worldPos;
float dist = length(lightVec);
if (dist > light.Range) return vec3(0.0);
vec3 L = lightVec / dist;
vec3 Lradiance = light.Radiance * light.Intensity;
// 距离衰减
float attenuation = 1.0 / (dist * dist + 0.0001);
float rangeFactor = clamp(1.0 - (dist / light.Range), 0.0, 1.0);
rangeFactor = rangeFactor * rangeFactor;
attenuation *= rangeFactor;
// 角度衰减(聚光锥)
float cosAngle = dot(-L, normalize(light.Direction)); // 光方向指向外,所以用 -L
if (cosAngle < light.OuterConeCos) return vec3(0.0);
float angleFalloff = (cosAngle - light.OuterConeCos) / (light.InnerConeCos - light.OuterConeCos);
angleFalloff = clamp(angleFalloff, 0.0, 1.0);
attenuation *= angleFalloff;
vec3 Lh = normalize(L + params.View);
float cosLi = max(0.0, dot(params.Normal, L));
float cosLh = max(0.0, dot(params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, params.View)));
float D = ndfGGX(cosLh, params.Roughness);
float G = GeometrySmith(params.Normal, params.View, L, params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - params.Metalness);
vec3 diffuseBRDF = kd * params.Albedo;
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * params.NdotV);
return (diffuseBRDF + specularBRDF) * Lradiance * cosLi * attenuation;
}
// ---------------------------------------------------------------------------------------------------
vec3 RotateVectorAboutY(float angle, vec3 vec)
{
angle = radians(angle);
mat3x3 rotationMatrix ={vec3(cos(angle),0.0,sin(angle)),
vec3(0.0,1.0,0.0),
vec3(-sin(angle),0.0,cos(angle))};
return rotationMatrix * vec;
}
vec3 Lighting(vec3 F0)
{
vec3 result = vec3(0.0);
for(int i = 0; i < LightCount; i++)
{
vec3 Li = -lights.Direction;
vec3 Lradiance = lights.Radiance * lights.Multiplier;
vec3 Lh = normalize(Li + m_Params.View);
vec3 result = vec3(0.0);
for(int i = 0; i < LightCount; i++)
{
vec3 Li = u_DirectionalLights.Direction;
vec3 Lradiance = u_DirectionalLights.Radiance * u_DirectionalLights.Intensity;
vec3 Lh = normalize(Li + m_Params.View);
// Calculate angles between surface normal and various light vectors.
float cosLi = max(0.0, dot(m_Params.Normal, Li));
float cosLh = max(0.0, dot(m_Params.Normal, Lh));
float cosLi = max(0.0, dot(m_Params.Normal, Li));
float cosLh = max(0.0, dot(m_Params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, m_Params.View)));
float D = ndfGGX(cosLh, m_Params.Roughness);
float G = gaSchlickGGX(cosLi, m_Params.NdotV, m_Params.Roughness);
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, m_Params.View)));
float D = ndfGGX(cosLh, m_Params.Roughness);
float G = GeometrySmith(m_Params.Normal, m_Params.View, Li, m_Params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness);
vec3 diffuseBRDF = kd * m_Params.Albedo;
vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness);
vec3 diffuseBRDF = kd * m_Params.Albedo;
// Cook-Torrance
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * m_Params.NdotV);
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * m_Params.NdotV);
result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi;
}
return result;
result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi;
}
return result;
}
// ---------- IBL ----------
vec3 RotateVectorAboutY(float angle, vec3 vec)
{
angle = radians(angle);
mat3 rotationMatrix = mat3(
vec3(cos(angle), 0.0, sin(angle)),
vec3(0.0, 1.0, 0.0),
vec3(-sin(angle), 0.0, cos(angle))
);
return rotationMatrix * vec;
}
vec3 IBL(vec3 F0, vec3 Lr)
{
vec3 irradiance = texture(u_EnvIrradianceTex, m_Params.Normal).rgb;
vec3 F = fresnelSchlickRoughness(F0, m_Params.NdotV, m_Params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness);
vec3 diffuseIBL = m_Params.Albedo * irradiance;
vec3 irradiance = texture(u_EnvIrradianceTex, m_Params.Normal).rgb;
vec3 F = fresnelSchlickRoughness(F0, m_Params.NdotV, m_Params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness);
vec3 diffuseIBL = m_Params.Albedo * irradiance;
int u_EnvRadianceTexLevels = textureQueryLevels(u_EnvRadianceTex);
float NoV = clamp(m_Params.NdotV, 0.0, 1.0);
vec3 R = 2.0 * dot(m_Params.View, m_Params.Normal) * m_Params.Normal - m_Params.View;
vec3 specularIrradiance = textureLod(u_EnvRadianceTex, RotateVectorAboutY(u_EnvMapRotation, Lr), (m_Params.Roughness) * u_EnvRadianceTexLevels).rgb;
int u_EnvRadianceTexLevels = textureQueryLevels(u_EnvRadianceTex);
vec3 specularIrradiance = textureLod(
u_EnvRadianceTex,
RotateVectorAboutY(u_EnvMapRotation, Lr),
m_Params.Roughness * u_EnvRadianceTexLevels
).rgb;
// Sample BRDF Lut, 1.0 - roughness for y-coord because texture was generated (in Sparky) for gloss model
vec2 specularBRDF = texture(u_BRDFLUTTexture, vec2(m_Params.NdotV, 1.0 - m_Params.Roughness)).rg;
vec3 specularIBL = specularIrradiance * (F * specularBRDF.x + specularBRDF.y);
vec2 specularBRDF = texture(u_BRDFLUTTexture, vec2(m_Params.NdotV, 1.0 - m_Params.Roughness)).rg;
vec3 specularIBL = specularIrradiance * (F * specularBRDF.x + specularBRDF.y);
return kd * diffuseIBL + specularIBL;
return kd * diffuseIBL + specularIBL;
}
// shadow
float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir)
{
// Perspective divide
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// Transform to [0,1] range
projCoords = projCoords * 0.5 + 0.5;
// If outside shadow map bounds, assume no shadow
if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0)
return 0.0;
// Get closest depth value from light's perspective
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
// Calculate bias based on surface angle
float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1);
// PCF (Percentage Closer Filtering) for soft shadows
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(u_ShadowMap, 0);
int pcfRange = int(u_ShadowSoftness);
int sampleCount = 0;
for(int x = -pcfRange; x <= pcfRange; ++x)
{
for(int y = -pcfRange; y <= pcfRange; ++y)
{
float pcfDepth = texture(u_ShadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
sampleCount++;
}
}
shadow /= float(sampleCount);
return shadow;
}
float ComputeShadow(vec4 fragPosLightSpace, float NdotL)
{
if (u_ShadowEnabled == 0) return 1.0;
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z > 1.0) return 1.0;
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
float bias = max(u_ShadowBias * (1.0 - NdotL), u_ShadowBias * 0.5);
float shadow = (currentDepth - bias) > closestDepth ? 1.0 : 0.0;
return mix(1.0, 1.0 - u_ShadowIntensity, shadow);
}
void main()
{
// Standard PBR inputs
m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor;
m_Params.Metalness = u_MetalnessTexToggle > 0.5 ? texture(u_MetalnessTexture, vs_Input.TexCoord).r : u_Metalness;
m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness;
m_Params.Roughness = max(m_Params.Roughness, 0.05); // Minimum roughness of 0.05 to keep specular highlight
m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor;
m_Params.Metalness = u_MetalnessTexToggle > 0.5 ? texture(u_MetalnessTexture, vs_Input.TexCoord).r : u_Metalness;
m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness;
m_Params.Roughness = max(m_Params.Roughness, 0.05);
// Normals (either from vertex or map)
m_Params.Normal = normalize(vs_Input.Normal);
if (u_NormalTexToggle > 0.5)
{
m_Params.Normal = normalize(2.0 * texture(u_NormalTexture, vs_Input.TexCoord).rgb - 1.0);
m_Params.Normal = normalize(vs_Input.WorldNormals * m_Params.Normal);
}
// normal
m_Params.Normal = normalize(vs_Input.Normal);
if (u_NormalTexToggle > 0.5)
{
m_Params.Normal = normalize(2.0 * texture(u_NormalTexture, vs_Input.TexCoord).rgb - 1.0);
m_Params.Normal = normalize(vs_Input.WorldNormals * m_Params.Normal);
}
m_Params.View = normalize(u_CameraPosition - vs_Input.WorldPosition);
m_Params.NdotV = max(dot(m_Params.Normal, m_Params.View), 0.0);
// Specular reflection vector
vec3 Lr = 2.0 * m_Params.NdotV * m_Params.Normal - m_Params.View;
m_Params.View = normalize(u_CameraPosition - vs_Input.WorldPosition);
m_Params.NdotV = max(dot(m_Params.Normal, m_Params.View), 0.0);
// Fresnel reflectance, metals use albedo
vec3 F0 = mix(Fdielectric, m_Params.Albedo, m_Params.Metalness);
vec3 Lr = 2.0 * m_Params.NdotV * m_Params.Normal - m_Params.View;
vec3 lightContribution = Lighting(F0);
vec3 iblContribution = IBL(F0, Lr);
vec3 F0 = mix(Fdielectric, m_Params.Albedo, m_Params.Metalness);
color = vec4(lightContribution + iblContribution, 1.0);
float shadowFactor = 1.0;
if (u_ShadowEnabled > 0.5) {
float shadow = calculateShadow(vs_Input.FragPosLightSpace, m_Params.Normal, u_DirectionalLights.Direction);
shadowFactor = 1.0 - shadow;
}
vec3 lightContribution = u_DirectionalLights.Intensity > 0.0 ? Lighting(F0) * shadowFactor : vec3(0.0);
if(u_PointLightCount > 0)
lightContribution += ComputePointLight(u_PointLights, F0, m_Params, vs_Input.WorldPosition);
if(u_SpotLightCount > 0)
lightContribution += ComputeSpotLight(u_SpotLights, F0, m_Params, vs_Input.WorldPosition);
vec3 iblContribution = IBL(F0, Lr) * u_IBLContribution;
color = vec4(lightContribution + iblContribution, 1.0);
// Bloom
float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
o_BloomColor = brightness > u_BloomThreshold ? color : vec4(0.0, 0.0, 0.0, 1.0);
}

View File

@ -1,8 +1,8 @@
// -----------------------------
// -- From Hazel Engine PBR shader --
// -- Based on Hazel PBR shader --
// -----------------------------
// Note: this shader is still very much in progress. There are likely many bugs and future additions that will go in.
// Currently heavily updated.
// Currently heavily updated.
//
// References upon which this is based:
// - Unreal Engine 4 PBR notes (https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf)
@ -19,27 +19,36 @@ layout(location = 3) in vec3 a_Binormal;
layout(location = 4) in vec2 a_TexCoord;
uniform mat4 u_ViewProjectionMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_Transform;
uniform mat4 u_LightSpaceMatrix;
out VertexOutput
{
vec3 WorldPosition;
vec3 Normal;
vec3 Normal;
vec2 TexCoord;
mat3 WorldNormals;
mat3 WorldTransform;
vec3 Binormal;
vec3 ViewPosition;
vec4 FragPosLightSpace;
} vs_Output;
void main()
{
vs_Output.WorldPosition = vec3(u_Transform * vec4(a_Position, 1.0));
vs_Output.Normal = mat3(u_Transform) * a_Normal;
vs_Output.Normal = mat3(u_Transform) * a_Normal;
vs_Output.TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y);
vs_Output.WorldNormals = mat3(u_Transform) * mat3(a_Tangent, a_Binormal, a_Normal);
vs_Output.WorldTransform = mat3(u_Transform);
vs_Output.Binormal = a_Binormal;
vs_Output.FragPosLightSpace = u_LightSpaceMatrix * u_Transform * vec4(a_Position, 1.0);
vs_Output.ViewPosition = vec3(u_ViewMatrix * vec4(vs_Output.WorldPosition, 1.0));
gl_Position = u_ViewProjectionMatrix * u_Transform * vec4(a_Position, 1.0);
}
@ -50,63 +59,101 @@ const float PI = 3.141592;
const float Epsilon = 0.00001;
const int LightCount = 1;
// Constant normal incidence Fresnel factor for all dielectrics.
const vec3 Fdielectric = vec3(0.04);
struct Light {
struct DirectionalLight {
vec3 Direction;
vec3 Radiance;
float Multiplier;
float Intensity;
bool CastShadows;
};
struct PointLight {
vec3 Position;
vec3 Radiance;
float Intensity;
float Range;
bool CastShadows;
};
struct SpotLight {
vec3 Position;
vec3 Direction;
vec3 Radiance;
float Intensity;
float Range;
float InnerConeCos;
float OuterConeCos;
bool CastShadows;
};
in VertexOutput
{
vec3 WorldPosition;
vec3 Normal;
vec3 Normal;
vec2 TexCoord;
mat3 WorldNormals;
mat3 WorldTransform;
vec3 Binormal;
vec3 ViewPosition;
vec4 FragPosLightSpace;
} vs_Input;
layout(location = 0) out vec4 color;
layout(location = 1) out vec4 o_BloomColor;
uniform DirectionalLight u_DirectionalLights;
uniform int u_PointLightCount;
uniform PointLight u_PointLights;
uniform int u_SpotLightCount;
uniform SpotLight u_SpotLights;
uniform Light lights;
uniform vec3 u_CameraPosition;
// PBR texture inputs
// PBR
uniform sampler2D u_AlbedoTexture;
uniform sampler2D u_NormalTexture;
uniform sampler2D u_MetalnessTexture;
uniform sampler2D u_RoughnessTexture;
// Environment maps
// environment
uniform samplerCube u_EnvRadianceTex;
uniform samplerCube u_EnvIrradianceTex;
// BRDF LUT
uniform sampler2D u_BRDFLUTTexture;
uniform vec3 u_AlbedoColor;
uniform float u_IBLContribution;
uniform float u_BloomThreshold;
uniform float u_EnvMapRotation;
// baseColor
uniform vec3 u_AlbedoColor;
uniform float u_Metalness;
uniform float u_Roughness;
uniform float u_EnvMapRotation;
// Toggles
uniform float u_RadiancePrefilter;
// textureToggle
uniform float u_AlbedoTexToggle;
uniform float u_NormalTexToggle;
uniform float u_MetalnessTexToggle;
uniform float u_RoughnessTexToggle;
// shadow
uniform sampler2D u_ShadowMap;
uniform float u_ShadowBias;
uniform float u_ShadowSoftness;
uniform float u_ShadowIntensity;
uniform int u_ShadowEnabled;
struct PBRParameters
{
vec3 Albedo;
float Roughness;
float Metalness;
vec3 Normal;
vec3 View;
float NdotV;
@ -114,53 +161,33 @@ struct PBRParameters
PBRParameters m_Params;
// GGX/Towbridge-Reitz normal distribution function.
// Uses Disney's reparametrization of alpha = roughness^2
// ---------- PBR param func ----------
float ndfGGX(float cosLh, float roughness)
{
float alpha = roughness * roughness;
float alphaSq = alpha * alpha;
float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0;
return alphaSq / (PI * denom * denom);
}
// Single term for separable Schlick-GGX below.
float gaSchlickG1(float cosTheta, float k)
{
return cosTheta / (cosTheta * (1.0 - k) + k);
}
// Schlick-GGX approximation of geometric attenuation function using Smith's method.
float gaSchlickGGX(float cosLi, float NdotV, float roughness)
{
float r = roughness + 1.0;
float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights.
return gaSchlickG1(cosLi, k) * gaSchlickG1(NdotV, k);
}
float GeometrySchlickGGX(float NdotV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
// Shlick's approximation of the Fresnel factor.
vec3 fresnelSchlick(vec3 F0, float cosTheta)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
@ -168,77 +195,97 @@ vec3 fresnelSchlick(vec3 F0, float cosTheta)
vec3 fresnelSchlickRoughness(vec3 F0, float cosTheta, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
// ---------------------------------------------------------------------------------------------------
// The following code (from Unreal Engine 4's paper) shows how to filter the environment map
// for different roughnesses. This is mean to be computed offline and stored in cube map mips,
// so turning this on online will cause poor performance
float RadicalInverse_VdC(uint bits)
{
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // / 0x100000000
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
vec2 Hammersley(uint i, uint N)
// ---------- direction light ----------
vec3 ComputeDirectionalLight(DirectionalLight light, vec3 F0, PBRParameters params)
{
return vec2(float(i)/float(N), RadicalInverse_VdC(i));
vec3 L = normalize(-light.Direction);
vec3 Lradiance = light.Radiance * light.Intensity;
vec3 Lh = normalize(L + params.View);
float cosLi = max(0.0, dot(params.Normal, L));
float cosLh = max(0.0, dot(params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, params.View)));
float D = ndfGGX(cosLh, params.Roughness);
float G = GeometrySmith(params.Normal, params.View, L, params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - params.Metalness);
vec3 diffuseBRDF = kd * params.Albedo;
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * params.NdotV);
return (diffuseBRDF + specularBRDF) * Lradiance * cosLi;
}
vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N)
vec3 ComputePointLight(PointLight light, vec3 F0, PBRParameters params, vec3 worldPos)
{
float a = Roughness * Roughness;
float Phi = 2 * PI * Xi.x;
float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) );
float SinTheta = sqrt( 1 - CosTheta * CosTheta );
vec3 H;
H.x = SinTheta * cos( Phi );
H.y = SinTheta * sin( Phi );
H.z = CosTheta;
vec3 UpVector = abs(N.z) < 0.999 ? vec3(0,0,1) : vec3(1,0,0);
vec3 TangentX = normalize( cross( UpVector, N ) );
vec3 TangentY = cross( N, TangentX );
// Tangent to world space
return TangentX * H.x + TangentY * H.y + N * H.z;
vec3 lightVec = light.Position - worldPos;
float dist = length(lightVec);
if (dist > light.Range) return vec3(0.0);
vec3 L = lightVec / dist;
vec3 Lradiance = light.Radiance * light.Intensity;
// 距离衰减:通常使用平方衰减,但为避免分母为零,加一个小值
float attenuation = 1.0 / (dist * dist + 0.0001);
// 可选:范围平滑衰减
float rangeFactor = clamp(1.0 - (dist / light.Range), 0.0, 1.0);
rangeFactor = rangeFactor * rangeFactor; // 平滑
attenuation *= rangeFactor;
vec3 Lh = normalize(L + params.View);
float cosLi = max(0.0, dot(params.Normal, L));
float cosLh = max(0.0, dot(params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, params.View)));
float D = ndfGGX(cosLh, params.Roughness);
float G = GeometrySmith(params.Normal, params.View, L, params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - params.Metalness);
vec3 diffuseBRDF = kd * params.Albedo;
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * params.NdotV);
return (diffuseBRDF + specularBRDF) * Lradiance * cosLi * attenuation;
}
float TotalWeight = 0.0;
vec3 PrefilterEnvMap(float Roughness, vec3 R)
vec3 ComputeSpotLight(SpotLight light, vec3 F0, PBRParameters params, vec3 worldPos)
{
vec3 N = R;
vec3 V = R;
vec3 PrefilteredColor = vec3(0.0);
int NumSamples = 1024;
for(int i = 0; i < NumSamples; i++)
{
vec2 Xi = Hammersley(i, NumSamples);
vec3 H = ImportanceSampleGGX(Xi, Roughness, N);
vec3 L = 2 * dot(V, H) * H - V;
float NoL = clamp(dot(N, L), 0.0, 1.0);
if (NoL > 0)
{
PrefilteredColor += texture(u_EnvRadianceTex, L).rgb * NoL;
TotalWeight += NoL;
}
}
return PrefilteredColor / TotalWeight;
}
vec3 lightVec = light.Position - worldPos;
float dist = length(lightVec);
if (dist > light.Range) return vec3(0.0);
// ---------------------------------------------------------------------------------------------------
vec3 L = lightVec / dist;
vec3 Lradiance = light.Radiance * light.Intensity;
vec3 RotateVectorAboutY(float angle, vec3 vec)
{
angle = radians(angle);
mat3x3 rotationMatrix ={vec3(cos(angle),0.0,sin(angle)),
vec3(0.0,1.0,0.0),
vec3(-sin(angle),0.0,cos(angle))};
return rotationMatrix * vec;
// 距离衰减
float attenuation = 1.0 / (dist * dist + 0.0001);
float rangeFactor = clamp(1.0 - (dist / light.Range), 0.0, 1.0);
rangeFactor = rangeFactor * rangeFactor;
attenuation *= rangeFactor;
// 角度衰减(聚光锥)
float cosAngle = dot(-L, normalize(light.Direction)); // 光方向指向外,所以用 -L
if (cosAngle < light.OuterConeCos) return vec3(0.0);
float angleFalloff = (cosAngle - light.OuterConeCos) / (light.InnerConeCos - light.OuterConeCos);
angleFalloff = clamp(angleFalloff, 0.0, 1.0);
attenuation *= angleFalloff;
vec3 Lh = normalize(L + params.View);
float cosLi = max(0.0, dot(params.Normal, L));
float cosLh = max(0.0, dot(params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, params.View)));
float D = ndfGGX(cosLh, params.Roughness);
float G = GeometrySmith(params.Normal, params.View, L, params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - params.Metalness);
vec3 diffuseBRDF = kd * params.Albedo;
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * params.NdotV);
return (diffuseBRDF + specularBRDF) * Lradiance * cosLi * attenuation;
}
vec3 Lighting(vec3 F0)
@ -246,22 +293,20 @@ vec3 Lighting(vec3 F0)
vec3 result = vec3(0.0);
for(int i = 0; i < LightCount; i++)
{
vec3 Li = -lights.Direction;
vec3 Lradiance = lights.Radiance * lights.Multiplier;
vec3 Li = u_DirectionalLights.Direction;
vec3 Lradiance = u_DirectionalLights.Radiance * u_DirectionalLights.Intensity;
vec3 Lh = normalize(Li + m_Params.View);
// Calculate angles between surface normal and various light vectors.
float cosLi = max(0.0, dot(m_Params.Normal, Li));
float cosLh = max(0.0, dot(m_Params.Normal, Lh));
vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, m_Params.View)));
float D = ndfGGX(cosLh, m_Params.Roughness);
float G = gaSchlickGGX(cosLi, m_Params.NdotV, m_Params.Roughness);
float G = GeometrySmith(m_Params.Normal, m_Params.View, Li, m_Params.Roughness);
vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness);
vec3 diffuseBRDF = kd * m_Params.Albedo;
// Cook-Torrance
vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * m_Params.NdotV);
result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi;
@ -269,35 +314,109 @@ vec3 Lighting(vec3 F0)
return result;
}
// ---------- IBL ----------
vec3 RotateVectorAboutY(float angle, vec3 vec)
{
angle = radians(angle);
mat3 rotationMatrix = mat3(
vec3(cos(angle), 0.0, sin(angle)),
vec3(0.0, 1.0, 0.0),
vec3(-sin(angle), 0.0, cos(angle))
);
return rotationMatrix * vec;
}
vec3 IBL(vec3 F0, vec3 Lr)
{
vec3 irradiance = texture(u_EnvIrradianceTex, m_Params.Normal).rgb;
vec3 F = fresnelSchlickRoughness(F0, m_Params.NdotV, m_Params.Roughness);
// vec3 F = fresnelSchlickR(F0, m_Params.NdotV);
vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness);
vec3 diffuseIBL = m_Params.Albedo * irradiance;
int u_EnvRadianceTexLevels = textureQueryLevels(u_EnvRadianceTex);
float NoV = clamp(m_Params.NdotV, 0.0, 1.0);
vec3 R = 2.0 * dot(m_Params.View, m_Params.Normal) * m_Params.Normal - m_Params.View;
vec3 specularIrradiance = textureLod(u_EnvRadianceTex, RotateVectorAboutY(u_EnvMapRotation, Lr), (m_Params.Roughness) * u_EnvRadianceTexLevels).rgb;
vec3 specularIrradiance = textureLod(
u_EnvRadianceTex,
RotateVectorAboutY(u_EnvMapRotation, Lr),
m_Params.Roughness * u_EnvRadianceTexLevels
).rgb;
// Sample BRDF Lut, 1.0 - roughness for y-coord because texture was generated (in Sparky) for gloss model
vec2 specularBRDF = texture(u_BRDFLUTTexture, vec2(m_Params.NdotV, 1.0 - m_Params.Roughness)).rg;
vec3 specularIBL = specularIrradiance * (F * specularBRDF.x + specularBRDF.y);
return kd * diffuseIBL + specularIBL;
}
// shadow
float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir)
{
// Perspective divide
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
// Transform to [0,1] range
projCoords = projCoords * 0.5 + 0.5;
// If outside shadow map bounds, assume no shadow
if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0)
return 0.0;
// Get closest depth value from light's perspective
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
// Calculate bias based on surface angle
float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1);
// PCF (Percentage Closer Filtering) for soft shadows
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(u_ShadowMap, 0);
int pcfRange = int(u_ShadowSoftness);
int sampleCount = 0;
for(int x = -pcfRange; x <= pcfRange; ++x)
{
for(int y = -pcfRange; y <= pcfRange; ++y)
{
float pcfDepth = texture(u_ShadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
sampleCount++;
}
}
shadow /= float(sampleCount);
return shadow;
}
float ComputeShadow(vec4 fragPosLightSpace, float NdotL)
{
if (u_ShadowEnabled == 0) return 1.0;
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z > 1.0) return 1.0;
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
float bias = max(u_ShadowBias * (1.0 - NdotL), u_ShadowBias * 0.5);
float shadow = (currentDepth - bias) > closestDepth ? 1.0 : 0.0;
return mix(1.0, 1.0 - u_ShadowIntensity, shadow);
}
void main()
{
// Standard PBR inputs
m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor;
m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor;
m_Params.Metalness = u_MetalnessTexToggle > 0.5 ? texture(u_MetalnessTexture, vs_Input.TexCoord).r : u_Metalness;
m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness;
m_Params.Roughness = max(m_Params.Roughness, 0.05); // Minimum roughness of 0.05 to keep specular highlight
m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness;
m_Params.Roughness = max(m_Params.Roughness, 0.05);
// Normals (either from vertex or map)
// normal
m_Params.Normal = normalize(vs_Input.Normal);
if (u_NormalTexToggle > 0.5)
{
@ -307,16 +426,32 @@ void main()
m_Params.View = normalize(u_CameraPosition - vs_Input.WorldPosition);
m_Params.NdotV = max(dot(m_Params.Normal, m_Params.View), 0.0);
// Specular reflection vector
vec3 Lr = 2.0 * m_Params.NdotV * m_Params.Normal - m_Params.View;
// Fresnel reflectance, metals use albedo
vec3 F0 = mix(Fdielectric, m_Params.Albedo, m_Params.Metalness);
vec3 lightContribution = Lighting(F0);
vec3 iblContribution = IBL(F0, Lr);
// Shadow
float shadowFactor = 1.0;
if (u_ShadowEnabled > 0.5) {
float shadow = calculateShadow(vs_Input.FragPosLightSpace, m_Params.Normal, u_DirectionalLights.Direction);
shadowFactor = 1.0 - shadow;
}
// directional light with with shadow
vec3 lightContribution = u_DirectionalLights.Intensity > 0.0 ? Lighting(F0) * shadowFactor : vec3(0.0);
if(u_PointLightCount > 0)
lightContribution += ComputePointLight(u_PointLights, F0, m_Params, vs_Input.WorldPosition);
if(u_SpotLightCount > 0)
lightContribution += ComputeSpotLight(u_SpotLights, F0, m_Params, vs_Input.WorldPosition);
vec3 iblContribution = IBL(F0, Lr) * u_IBLContribution;
color = vec4(lightContribution + iblContribution, 1.0);
// color = vec4(iblContribution, 1.0);
// Bloom
float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
o_BloomColor = brightness > u_BloomThreshold ? color : vec4(0.0, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,159 @@
#type compute
#version 450 core
const float PI = 3.141592;
layout(binding = 0, rgba32f) restrict writeonly uniform imageCube o_CubeMap;
uniform vec3 u_TurbidityAzimuthInclination;
#define PI 3.14159265359
vec3 GetCubeMapTexCoord()
{
vec2 st = gl_GlobalInvocationID.xy / vec2(imageSize(o_CubeMap));
vec2 uv = 2.0 * vec2(st.x, 1.0 - st.y) - vec2(1.0);
vec3 ret;
if (gl_GlobalInvocationID.z == 0) ret = vec3( 1.0, uv.y, -uv.x);
else if (gl_GlobalInvocationID.z == 1) ret = vec3( -1.0, uv.y, uv.x);
else if (gl_GlobalInvocationID.z == 2) ret = vec3( uv.x, 1.0, -uv.y);
else if (gl_GlobalInvocationID.z == 3) ret = vec3( uv.x, -1.0, uv.y);
else if (gl_GlobalInvocationID.z == 4) ret = vec3( uv.x, uv.y, 1.0);
else if (gl_GlobalInvocationID.z == 5) ret = vec3(-uv.x, uv.y, -1.0);
return normalize(ret);
}
float saturatedDot( in vec3 a, in vec3 b )
{
return clamp(dot(a, b), 0.0, 1.0);
}
vec3 YxyToXYZ( in vec3 Yxy )
{
float Y = Yxy.r;
float x = Yxy.g;
float y = Yxy.b;
float X = x * ( Y / y );
float Z = ( 1.0 - x - y ) * ( Y / y );
return vec3(X,Y,Z);
}
vec3 XYZToRGB( in vec3 XYZ )
{
// CIE/E
mat3 M = mat3
(
2.3706743, -0.9000405, -0.4706338,
-0.5138850, 1.4253036, 0.0885814,
0.0052982, -0.0146949, 1.0093968
);
return XYZ * M;
}
vec3 YxyToRGB( in vec3 Yxy )
{
vec3 XYZ = YxyToXYZ( Yxy );
vec3 RGB = XYZToRGB( XYZ );
return RGB;
}
void calculatePerezDistribution( in float t, out vec3 A, out vec3 B, out vec3 C, out vec3 D, out vec3 E )
{
A = vec3( 0.1787 * t - 1.4630, -0.0193 * t - 0.2592, -0.0167 * t - 0.2608 );
B = vec3( -0.3554 * t + 0.4275, -0.0665 * t + 0.0008, -0.0950 * t + 0.0092 );
C = vec3( -0.0227 * t + 5.3251, -0.0004 * t + 0.2125, -0.0079 * t + 0.2102 );
D = vec3( 0.1206 * t - 2.5771, -0.0641 * t - 0.8989, -0.0441 * t - 1.6537 );
E = vec3( -0.0670 * t + 0.3703, -0.0033 * t + 0.0452, -0.0109 * t + 0.0529 );
}
vec3 calculateZenithLuminanceYxy( in float t, in float thetaS )
{
float chi = ( 4.0 / 9.0 - t / 120.0 ) * ( PI - 2.0 * thetaS );
float Yz = ( 4.0453 * t - 4.9710 ) * tan( chi ) - 0.2155 * t + 2.4192;
float theta2 = thetaS * thetaS;
float theta3 = theta2 * thetaS;
float T = t;
float T2 = t * t;
float xz =
( 0.00165 * theta3 - 0.00375 * theta2 + 0.00209 * thetaS + 0.0) * T2 +
(-0.02903 * theta3 + 0.06377 * theta2 - 0.03202 * thetaS + 0.00394) * T +
( 0.11693 * theta3 - 0.21196 * theta2 + 0.06052 * thetaS + 0.25886);
float yz =
( 0.00275 * theta3 - 0.00610 * theta2 + 0.00317 * thetaS + 0.0) * T2 +
(-0.04214 * theta3 + 0.08970 * theta2 - 0.04153 * thetaS + 0.00516) * T +
( 0.15346 * theta3 - 0.26756 * theta2 + 0.06670 * thetaS + 0.26688);
return vec3( Yz, xz, yz );
}
vec3 calculatePerezLuminanceYxy( in float theta, in float gamma, in vec3 A, in vec3 B, in vec3 C, in vec3 D, in vec3 E )
{
float cosTheta = max(cos(theta), 1e-6);
return ( 1.0 + A * exp( B / cos( theta ) ) ) * ( 1.0 + C * exp( D * gamma ) + E * cos( gamma ) * cos( gamma ) );
}
vec3 calculateSkyLuminanceRGB( in vec3 s, in vec3 e, in float t )
{
vec3 A, B, C, D, E;
calculatePerezDistribution( t, A, B, C, D, E );
float thetaS = acos(clamp(dot(s, vec3(0,1,0)), 0.0, 1.0));
float thetaE = acos(clamp(dot(e, vec3(0,1,0)), 0.0, 1.0));
float gammaE = acos( saturatedDot( s, e ) );
vec3 Yz = calculateZenithLuminanceYxy( t, thetaS );
vec3 fThetaGamma = calculatePerezLuminanceYxy( thetaE, gammaE, A, B, C, D, E );
vec3 fZeroThetaS = calculatePerezLuminanceYxy( 0.0, thetaS, A, B, C, D, E );
vec3 Yp = Yz * ( fThetaGamma / fZeroThetaS );
return YxyToRGB( Yp );
}
layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
void main()
{
vec3 cubeTC = GetCubeMapTexCoord();
float turbidity = u_TurbidityAzimuthInclination.x;
float azimuth = u_TurbidityAzimuthInclination.y;;
float inclination = u_TurbidityAzimuthInclination.z;
vec3 sunDir = normalize( vec3( sin(inclination) * cos(azimuth), cos(inclination), sin(inclination) * sin(azimuth) ) );
vec3 viewDir = cubeTC;
const float SUN_ANGULAR_RADIUS = 0.03465;
const float SUN_INTENSITY = 100.0;
vec3 skyLuminance;
if (viewDir.y < 0.0) {
skyLuminance = vec3(0.02);
} else {
skyLuminance = calculateSkyLuminanceRGB(sunDir, viewDir, turbidity);
}
float cosAngle = dot(viewDir, sunDir);
float angle = acos(cosAngle);
if (angle < SUN_ANGULAR_RADIUS) {
skyLuminance = vec3(SUN_INTENSITY);
} else {
float haloWidth = 0.1;
if (angle < SUN_ANGULAR_RADIUS + haloWidth) {
float t = (angle - SUN_ANGULAR_RADIUS) / haloWidth;
float haloFactor = 1.0 - smoothstep(0.0, 1.0, t);
skyLuminance += vec3(SUN_INTENSITY * 0.1 * haloFactor);
}
}
vec4 color = vec4(skyLuminance * 0.05, 1.0);
imageStore(o_CubeMap, ivec3(gl_GlobalInvocationID), color);
}

View File

@ -6,14 +6,14 @@
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec4 a_Color;
layout(location = 2) in vec2 a_TexCoord;
layout(location = 3) in float a_TexIndex;
layout(location = 3) in int a_TexIndex;
layout(location = 4) in float a_TilingFactor;
uniform mat4 u_ViewProjection;
out vec4 v_Color;
out vec2 v_TexCoord;
out float v_TexIndex;
flat out int v_TexIndex;
out float v_TilingFactor;
void main()
@ -32,12 +32,17 @@ layout(location = 0) out vec4 color;
in vec4 v_Color;
in vec2 v_TexCoord;
in float v_TexIndex;
flat in int v_TexIndex;
in float v_TilingFactor;
uniform sampler2D u_Textures[32];
void main()
{
color = texture(u_Textures[int(v_TexIndex)], v_TexCoord * v_TilingFactor) * v_Color;
vec4 texColor = texture(u_Textures[v_TexIndex], v_TexCoord * v_TilingFactor) * v_Color;
if(texColor.a < 0.1)
discard;
color = texColor;
}

View File

@ -0,0 +1,44 @@
// Basic Texture Shader
#type vertex
#version 430 core
layout(location = 0) in vec3 a_WorldPosition;
layout(location = 1) in float a_Thickness;
layout(location = 2) in vec2 a_LocalPosition;
layout(location = 3) in vec4 a_Color;
uniform mat4 u_ViewProjection;
out vec2 v_LocalPosition;
out float v_Thickness;
out vec4 v_Color;
void main()
{
v_LocalPosition = a_LocalPosition;
v_Thickness = a_Thickness;
v_Color = a_Color;
gl_Position = u_ViewProjection * vec4(a_WorldPosition, 1.0);
}
#type fragment
#version 430 core
layout(location = 0) out vec4 color;
in vec2 v_LocalPosition;
in float v_Thickness;
in vec4 v_Color;
void main()
{
float fade = 0.01;
float dist = sqrt(dot(v_LocalPosition, v_LocalPosition));
if (dist > 1.0 || dist < 1.0 - v_Thickness - fade)
discard;
float alpha = 1.0 - smoothstep(1.0f - fade, 1.0f, dist);
alpha *= smoothstep(1.0 - v_Thickness - fade, 1.0 - v_Thickness, dist);
color = v_Color;
color.a = alpha;
}

View File

@ -17,20 +17,53 @@ void main()
#version 430
layout(location = 0) out vec4 o_Color;
layout(location = 1) out vec4 o_BloomTexture;
in vec2 v_TexCoord;
uniform sampler2DMS u_Texture;
uniform float u_Exposure;
uniform sampler2D u_BloomTexture;
uniform bool u_EnableAutoExposure;
uniform float u_ManualExposure;
layout(std430, binding = 2) buffer Exposure
{
float u_Exposure;
};
uniform int u_TextureSamples;
vec4 MultiSampleTexture(sampler2DMS tex, ivec2 texCoord, int samples)
uniform bool u_EnableBloom;
uniform float u_BloomThreshold;
const float uFar = 1.0;
vec4 SampleTexture(sampler2D tex, vec2 texCoord)
{
return texture(tex, texCoord);
}
vec4 MultiSampleTexture(sampler2DMS tex, vec2 tc)
{
ivec2 texSize = textureSize(tex);
ivec2 texCoord = ivec2(tc * texSize);
vec4 result = vec4(0.0);
for (int i = 0; i < samples; i++)
for (int i = 0; i < u_TextureSamples; i++)
result += texelFetch(tex, texCoord, i);
result /= float(samples);
result /= float(u_TextureSamples);
return result;
}
float MultiSampleDepth(sampler2DMS tex, vec2 tc)
{
ivec2 texSize = textureSize(tex);
ivec2 texCoord = ivec2(tc * texSize);
float result = 0.0;
for (int i = 0; i < u_TextureSamples; i++)
result += texelFetch(tex, texCoord, i).r;
result /= float(u_TextureSamples);
return result;
}
@ -39,10 +72,20 @@ void main()
const float gamma = 2.2;
const float pureWhite = 1.0;
ivec2 texSize = textureSize(u_Texture);
ivec2 texCoord = ivec2(v_TexCoord * texSize);
vec4 msColor = MultiSampleTexture(u_Texture, texCoord, u_TextureSamples);
vec3 color = msColor.rgb * u_Exposure;//texture(u_Texture, v_TexCoord).rgb * u_Exposure;
// Tonemapping
vec4 msColor = MultiSampleTexture(u_Texture, v_TexCoord);
vec3 color = msColor.rgb;
if (u_EnableBloom)
{
vec3 bloomColor = texture(u_BloomTexture, v_TexCoord).rgb;
color += bloomColor;
}
if(u_EnableAutoExposure)
color *= u_Exposure;
else
color *= u_ManualExposure;
// Reinhard tonemapping operator.
// see: "Photographic Tone Reproduction for Digital Images", eq. 4
@ -54,4 +97,8 @@ void main()
// Gamma correction.
o_Color = vec4(pow(mappedColor, vec3(1.0 / gamma)), 1.0);
}
// Show over-exposed areas
// if (o_Color.r > 1.0 || o_Color.g > 1.0 || o_Color.b > 1.0)
// o_Color.rgb *= vec3(1.0, 0.25, 0.25);
}

View File

@ -0,0 +1,23 @@
// Shadow Map shader
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
uniform mat4 u_LightViewProjection;
uniform mat4 u_Transform;
void main()
{
gl_Position = u_LightViewProjection * u_Transform * vec4(a_Position, 1.0);
}
#type fragment
#version 430
layout(location = 0) out vec4 o_Color;
void main()
{
}

View File

@ -0,0 +1,35 @@
// Shadow Map shader
#type vertex
#version 430
layout(location = 0) in vec3 a_Position;
layout(location = 5) in ivec4 a_BoneIndices;
layout(location = 6) in vec4 a_BoneWeights;
uniform mat4 u_LightViewProjection;
uniform mat4 u_Transform;
const int MAX_BONES = 100;
uniform mat4 u_BoneTransforms[100];
void main()
{
mat4 boneTransform = u_BoneTransforms[a_BoneIndices[0]] * a_BoneWeights[0];
boneTransform += u_BoneTransforms[a_BoneIndices[1]] * a_BoneWeights[1];
boneTransform += u_BoneTransforms[a_BoneIndices[2]] * a_BoneWeights[2];
boneTransform += u_BoneTransforms[a_BoneIndices[3]] * a_BoneWeights[3];
vec4 localPosition = boneTransform * vec4(a_Position, 1.0);
gl_Position = u_LightViewProjection * u_Transform * localPosition;
}
#type fragment
#version 430
layout(location = 0) out vec4 o_Color;
void main()
{
}

View File

@ -24,10 +24,13 @@ layout(location = 0) out vec4 finalColor;
uniform samplerCube u_Texture;
uniform float u_TextureLod;
uniform float u_SkyIntensity;
in vec3 v_Position;
void main()
{
finalColor = textureLod(u_Texture, v_Position, u_TextureLod);
vec3 color = textureLod(u_Texture, v_Position, u_TextureLod).rgb * u_SkyIntensity;
finalColor = vec4(color, 1.0);
}

108
Editor/imgui.ini Normal file
View File

@ -0,0 +1,108 @@
[Window][DockSpace Demo]
Pos=0,0
Size=1920,1080
Collapsed=0
[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0
[Window][Scene Hierarchy]
Pos=1449,24
Size=471,385
Collapsed=0
DockId=0x00000009,0
[Window][Properties]
Pos=1449,411
Size=471,669
Collapsed=0
DockId=0x0000000A,0
[Window][Scene Renderer]
Pos=0,585
Size=481,495
Collapsed=0
DockId=0x00000006,0
[Window][Materials]
Pos=0,24
Size=481,559
Collapsed=0
DockId=0x00000005,0
[Window][Script Engine Debug]
Pos=1449,411
Size=471,669
Collapsed=0
DockId=0x0000000A,2
[Window][Model]
Pos=1595,454
Size=471,744
Collapsed=0
DockId=0x0000000A,3
[Window][Toolbar]
Pos=483,24
Size=1110,32
Collapsed=0
DockId=0x00000001,0
[Window][Viewport]
Pos=483,58
Size=964,647
Collapsed=0
DockId=0x0000000B,0
[Window][Environment]
Pos=1449,411
Size=471,669
Collapsed=0
DockId=0x0000000A,1
[Window][Project]
Pos=483,707
Size=964,373
Collapsed=0
DockId=0x0000000C,0
[Window][Objects]
Pos=483,707
Size=964,373
Collapsed=0
DockId=0x0000000C,1
[Window][Physics]
Pos=189,113
Size=468,371
Collapsed=0
[Window][##tool_bar]
Pos=483,24
Size=964,32
Collapsed=0
DockId=0x00000001,0
[Window][Console]
Pos=483,707
Size=964,373
Collapsed=0
DockId=0x0000000C,2
[Docking][Data]
DockSpace ID=0xC0DFADC4 Window=0xD0388BC8 Pos=268,189 Size=1920,1056 Split=X Selected=0x0C01D6D5
DockNode ID=0x00000007 Parent=0xC0DFADC4 SizeRef=1557,1542 Split=X
DockNode ID=0x00000003 Parent=0x00000007 SizeRef=481,1542 Split=Y Selected=0x5D711C2C
DockNode ID=0x00000005 Parent=0x00000003 SizeRef=481,817 Selected=0x5D711C2C
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=481,723 Selected=0x68D924E0
DockNode ID=0x00000004 Parent=0x00000007 SizeRef=1074,1542 Split=Y
DockNode ID=0x00000001 Parent=0x00000004 SizeRef=2560,32 CentralNode=1 HiddenTabBar=1 Selected=0xE8CD5B84
DockNode ID=0x00000002 Parent=0x00000004 SizeRef=2560,1508 Split=Y Selected=0xC450F867
DockNode ID=0x0000000B Parent=0x00000002 SizeRef=1401,955 HiddenTabBar=1 Selected=0xC450F867
DockNode ID=0x0000000C Parent=0x00000002 SizeRef=1401,551 Selected=0x9C21DE82
DockNode ID=0x00000008 Parent=0xC0DFADC4 SizeRef=471,1542 Split=Y Selected=0x8C72BEA8
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=315,563 Selected=0xB8729153
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=315,977 Selected=0x73E3D51F

View File

@ -4,6 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>

View File

@ -5,6 +5,7 @@ namespace Example
public class BasicController : Entity
{
public float Speed;
public float DistanceFromPlayer = 20.0F;
private Entity m_PlayerEntity;
@ -15,13 +16,17 @@ namespace Example
public void OnUpdate(float ts)
{
/*
Mat4 transform = GetTransform();
Vec3 playerTranstation = m_PlayerEntity.GetTransform().Translation;
Vec3 translation = transform.Translation;
translation.XY = m_PlayerEntity.GetTransform().Translation.XY;
translation.XY = playerTranstation.XY;
translation.Z = playerTranstation.Z + DistanceFromPlayer;
translation.Y = Math.Max(translation.Y, 4.5f);
transform.Translation = translation;
SetTransform(transform);
*/
}

223
ExampleApp/Src/FPSPlayer.cs Normal file
View File

@ -0,0 +1,223 @@
using System.Runtime.InteropServices;
using Prism;
namespace FPSExample
{
public class FPSPlayer : Entity
{
public float WalkingSpeed = 10.0f;
public float RunSpeed = 20.0f;
public float JumpForce = 50.0f;
public float m_Radius = 0.5f;
public float TorqueStrength = 10.0f;
[NonSerialized]
public float MouseSensitivity = 10.0f;
public float CameraForwardOffset = 0.5f;
public float CameraYOffset = 0.5f;
private bool m_Colliding = false;
private float m_CurrentSpeed;
private RigidBodyComponent m_RigidBody;
private TransformComponent m_Transform;
private TransformComponent m_CameraTransform;
private Entity m_CameraEntity;
private Vec2 m_LastMousePosition;
private float m_CurrentYMovement = 0.0f;
private Vec2 m_MovementDirection = new Vec2(0.0f);
private bool m_ShouldJump = false;
void OnCreate()
{
m_Transform = GetComponent<TransformComponent>();
m_RigidBody = GetComponent<RigidBodyComponent>();
m_CurrentSpeed = WalkingSpeed;
AddCollisionBeginCallback((n) => { m_Colliding = true; });
AddCollisionEndCallback((n) => { m_Colliding = false; });
m_CameraEntity = FindEntityByTag("Camera");
m_CameraTransform = m_CameraEntity.GetComponent<TransformComponent>();
m_LastMousePosition = Input.GetMousePosition();
Input.SetCursorMode(Input.CursorMode.Locked);
int size = Marshal.SizeOf<Transform>();
Console.WriteLine($"C# size of Transform: {size}");
}
void OnUpdate(float ts)
{
if (Input.IsKeyPressed(KeyCode.Escape) && Input.GetCursorMode() == Input.CursorMode.Locked)
Input.SetCursorMode(Input.CursorMode.Normal);
if(Input.IsMouseButtonPressed(Input.MouseButton.Left) && Input.GetCursorMode() == Input.CursorMode.Normal)
Input.SetCursorMode(Input.CursorMode.Locked);
m_CurrentSpeed = Input.IsKeyPressed(KeyCode.LeftControl) ? RunSpeed : WalkingSpeed;
UpdateRayCasting();
UpdateMovementInput();
UpdateRotation(ts);
UpdateCameraTransform();
}
void OnPhysicsUpdate(float fixedTimeStep)
{
UpdateMovement();
}
private void UpdateRotation(float ts)
{
Vec2 currentMousePosition = Input.GetMousePosition();
Vec2 delta = m_LastMousePosition - currentMousePosition;
m_CurrentYMovement = delta.X * MouseSensitivity * ts;
float xRotation = delta.Y * MouseSensitivity * ts;
// m_RigidBody.Rotate(new Vec3(0.0f, m_CurrentYMovement, 0.0f));
if (delta.X != 0 || delta.Y != 0)
{
m_CameraTransform.Rotation += new Vec3(xRotation, m_CurrentYMovement, 0.0f);
}
m_CameraTransform.Rotation = new Vec3(Mathf.Clamp(m_CameraTransform.Rotation.X, -89.0f, 89.0f), m_CameraTransform.Rotation.YZ);
m_LastMousePosition = currentMousePosition;
}
private void UpdateMovementInput()
{
if (Input.IsKeyPressed(KeyCode.W)){
m_MovementDirection.Y -= 1.0f;
Debug.Log("KeyPressed: W");
}
else if (Input.IsKeyPressed(KeyCode.S)){
m_MovementDirection.Y += 1.0f;
Debug.Log("KeyPressed: S");
}
else
m_MovementDirection.Y = 0.0f;
if(Input.IsKeyPressed(KeyCode.A)){
m_MovementDirection.X += 1.0f;
Debug.Log("KeyPressed: A");
}
else if (Input.IsKeyPressed(KeyCode.D)){
m_MovementDirection.X -= 1.0f;
Debug.Log("KeyPressed: D");
}
else
m_MovementDirection.X = 0.0f;
m_ShouldJump = Input.IsKeyPressed(KeyCode.Space) && !m_ShouldJump;
}
Collider[] colliders = new Collider[10];
private void UpdateRayCasting()
{
RaycastHit hitInfo;
if (Input.IsKeyPressed(KeyCode.H) &&
Physics.Raycast(m_CameraTransform.Translation + (m_CameraTransform.Transform.Forward),
m_CameraTransform.Transform.Forward, 20.0f, out hitInfo))
{
FindEntityByID(hitInfo.EntityID).GetComponent<MeshComponent>().Mesh.GetMaterial(0).Set("u_AlbedoColor", new Vec3(1.0f ,0.0f, 0.0f));
}
if (Input.IsKeyPressed(KeyCode.I))
{
// NOTE: The NonAlloc version of Overlap functions should be used when possible since it doesn't allocate a new array
// whenever you call it. The normal versions allocates a brand new array every time.
int numColliders = Physics.OverlapBoxNonAlloc(m_Transform.Translation, new Vec3(1.0f), colliders);
Console.WriteLine("Colliders: {0}", numColliders);
// When using NonAlloc it's not safe to use a foreach loop since some of the colliders may not exist
for (int i = 0; i < numColliders; i++)
{
Console.WriteLine(colliders[i]);
}
}
}
private void UpdateMovement()
{
// 2. 计算期望的移动方向(世界坐标系)
Vec3 desiredDirection = Vec3.Zero;
if (m_MovementDirection.LengthSquared() != 0.0f)
{
Vec3 right = m_CameraTransform.Transform.Right;
Vec3 forward = m_CameraTransform.Transform.Forward;
right.Y = 0; forward.Y = 0;
right.Normalize(); forward.Normalize();
desiredDirection = right * m_MovementDirection.X + forward * m_MovementDirection.Y;
desiredDirection.Normalize();
/*
Vec3 movement = right * m_MovementDirection.X + forward * m_MovementDirection.Y;
movement.Normalize();
Vec3 velocity = movement * m_CurrentSpeed;
velocity.Y = m_RigidBody.GetLinearVelocity().Y;
m_RigidBody.SetLinearVelocity(velocity);
*/
}
Vec3 targetAngularVelocity = Vec3.Zero;
if (desiredDirection.LengthSquared() > 0.01f)
{
Vec3 up = Vec3.Up;
Vec3 rotationAxis = Vec3.Cross(desiredDirection, up).Normalized();
float angularSpeed = 2 * (m_CurrentSpeed / m_Radius);
targetAngularVelocity = rotationAxis * angularSpeed;
Vec3 currentAngular = m_RigidBody.GetAngularVelocity();
Vec3 angularDiff = targetAngularVelocity - currentAngular;
float inertia = 0.4f * m_RigidBody.Mass * m_Radius * m_Radius;
Vec3 torque = angularDiff * inertia * TorqueStrength;
m_RigidBody.AddTorque(torque, RigidBodyComponent.ForceMode.Force);
}
/*
// 3. 获取当前角速度,计算需要调整的差值
Vec3 currentAngular = m_RigidBody.GetAngularVelocity();
Vec3 angularDiff = targetAngularVelocity - currentAngular;
// 4. 施加扭矩:扭矩 = 转动惯量 * 角加速度
// 球体的转动惯量 I = (2/5) * m * r^2 (实心球)
float inertia = TorqueStrength * m_RigidBody.Mass * m_Radius * m_Radius;
// 设定一个系数控制响应速度(可调)
float torqueStrength = 5.0f;
Vec3 torque = angularDiff * inertia * torqueStrength;
m_RigidBody.AddTorque(-torque, RigidBodyComponent.ForceMode.Force);
*/
if (m_ShouldJump && m_Colliding)
{
Debug.Log("Jump");
m_RigidBody.AddForce(Vec3.Up * JumpForce, RigidBodyComponent.ForceMode.Impulse);
m_ShouldJump = false;
}
}
private void UpdateCameraTransform(){
Vec3 position = m_Transform.Translation + m_CameraTransform.Transform.Forward * CameraForwardOffset;
position.Y += CameraYOffset;
m_CameraTransform.Translation = position;
}
}
}

View File

@ -78,6 +78,7 @@ namespace Example
void OnUpdate(float ts)
{
/*
Mat4 transform = GetTransform();
Vec3 translation = transform.Translation;
translation.Y += ts * speed;
@ -91,6 +92,7 @@ namespace Example
transform.Translation = translation;
SetTransform(transform);
*/
}

View File

@ -72,9 +72,11 @@ namespace Example
if (Input.IsKeyPressed(KeyCode.R))
{
/*
Mat4 transform = GetTransform();
transform.Translation = new Vec3(0.0f);
SetTransform(transform);
*/
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Prism;
namespace Example
{
class PlayerSphere : Entity
{
public float HorizontalForce = 10.0f;
public float JumpForce = 10.0f;
private RigidBodyComponent m_PhysicsBody;
private MaterialInstance m_MeshMaterial;
public int m_CollisionCounter = 0;
public Vec3 MaxSpeed = new Vec3();
private bool Colliding => m_CollisionCounter > 0;
private TransformComponent m_Transform;
void OnCreate()
{
m_PhysicsBody = GetComponent<RigidBodyComponent>();
m_Transform = GetComponent<TransformComponent>();
MeshComponent meshComponent = GetComponent<MeshComponent>();
m_MeshMaterial = meshComponent.Mesh.GetMaterial(0);
m_MeshMaterial.Set("u_Metalness", 0.0f);
AddCollisionBeginCallback(OnPlayerCollisionBegin);
AddCollisionEndCallback(OnPlayerCollisionEnd);
AddTriggerBeginCallback(OnPlayerTriggerBegin);
AddTriggerEndCallback(OnPlayerTriggerEnd);
}
void OnPlayerCollisionBegin(float value)
{
m_CollisionCounter++;
}
void OnPlayerCollisionEnd(float value)
{
m_CollisionCounter--;
}
void OnPlayerTriggerBegin(float value)
{
Console.WriteLine("Player trigger begin");
}
void OnPlayerTriggerEnd(float value)
{
Console.WriteLine("Player trigger end");
}
void OnUpdate(float ts)
{
float movementForce = HorizontalForce;
if (!Colliding)
{
movementForce *= 0.4f;
}
if (Input.IsKeyPressed(KeyCode.W))
m_PhysicsBody.AddForce(m_Transform.Transform.Forward * movementForce);
else if (Input.IsKeyPressed(KeyCode.S))
m_PhysicsBody.AddForce(m_Transform.Transform.Forward * -movementForce);
if (Input.IsKeyPressed(KeyCode.D))
m_PhysicsBody.AddForce(m_Transform.Transform.Right * movementForce);
else if (Input.IsKeyPressed(KeyCode.A))
m_PhysicsBody.AddForce(m_Transform.Transform.Right * -movementForce);
if (Colliding && Input.IsKeyPressed(KeyCode.Space))
m_PhysicsBody.AddForce(m_Transform.Transform.Up * JumpForce);
if (Colliding)
m_MeshMaterial.Set("u_AlbedoColor", new Vec3(1.0f, 0.0f, 0.0f));
else
m_MeshMaterial.Set("u_AlbedoColor", new Vec3(0.8f, 0.8f, 0.8f));
Vec3 linearVelocity = m_PhysicsBody.GetLinearVelocity();
linearVelocity.Clamp(new Vec3(-MaxSpeed.X, -1000, -MaxSpeed.Z), MaxSpeed);
m_PhysicsBody.SetLinearVelocity(linearVelocity);
if (Input.IsKeyPressed(KeyCode.R))
{
/*
Mat4 transform = GetTransform();
transform.Translation = new Vec3(0.0f);
SetTransform(transform);
*/
}
}
}
}

View File

@ -21,6 +21,7 @@ namespace Example
public void OnUpdate(float ts)
{
/*
Rotation += ts;
@ -34,19 +35,10 @@ namespace Example
translation.Z += Velocity.Z * ts;
translation.Y -= SinkRate * ts;
/*
if (Input.IsKeyPressed(KeyCode.Up))
translation.Y += speed;
else if (Input.IsKeyPressed(KeyCode.Down))
translation.Y -= speed;
if (Input.IsKeyPressed(KeyCode.Right))
translation.X += speed;
else if (Input.IsKeyPressed(KeyCode.Left))
translation.X -= speed;
*/
transform.Translation = translation;
SetTransform(transform);
*/
}
}

View File

@ -14,6 +14,7 @@ namespace Example
void OnUpdate(float ts)
{
/*
Mat4 transform = GetTransform();
Vec3 translation = transform.Translation;
@ -21,6 +22,7 @@ namespace Example
transform.Translation = translation;
SetTransform(transform);
*/
}
}

View File

@ -5,6 +5,17 @@
<RootNamespace>Prism_ScriptCore</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<Content Include="bin\Debug\net9.0\Prism-ScriptCore.deps.json" />
<Content Include="bin\Debug\net9.0\Prism-ScriptCore.dll" />
<Content Include="bin\Debug\net9.0\Prism-ScriptCore.pdb" />
<Content Include="bin\Release\net9.0\Prism-ScriptCore.deps.json" />
<Content Include="bin\Release\net9.0\Prism-ScriptCore.dll" />
<Content Include="bin\Release\net9.0\Prism-ScriptCore.pdb" />
<Content Include="Prism-ScriptCore.sln" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,8 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.1.11312.151 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Prism-ScriptCore", "Prism-ScriptCore.csproj", "{B94EF710-0487-4388-97E3-B650761A849C}"
EndProject
Global
@ -13,4 +16,10 @@ Global
{B94EF710-0487-4388-97E3-B650761A849C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B94EF710-0487-4388-97E3-B650761A849C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BA69725C-3CFE-4882-9EDF-2A8E92423E8F}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,15 @@
using System.Runtime.CompilerServices;
namespace Prism
{
public class Debug
{
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void Log_Native(string message);
public static void Log(string message)
{
Log_Native(message);
}
}
}

View File

@ -5,14 +5,54 @@ namespace Prism
{
public class Entity
{
private Action<float>? m_CollisionBeginCallbacks;
private Action<float>? m_CollisionEndCallbacks;
private Action<float>? m_Collision2DBeginCallbacks;
private Action<float>? m_Collision2DEndCallbacks;
private Action<float>? m_TriggerBeginCallbacks;
private Action<float>? m_TriggerEndCallbacks;
public ulong ID { get; private set; }
~Entity()
{
}
private List<Action<float>> m_Collision2DBeginCallbacks = new List<Action<float>>();
private List<Action<float>> m_Collision2DEndCallbacks = new List<Action<float>>();
public Vec3 Translation
{
get
{
return GetComponent<TransformComponent>().Translation;
}
set
{
GetComponent<TransformComponent>().Translation = value;
}
}
public Vec3 Rotation
{
get
{
return GetComponent<TransformComponent>().Rotation;
}
set
{
GetComponent<TransformComponent>().Rotation = value;
}
}
public Vec3 Scale
{
get
{
return GetComponent<TransformComponent>().Scale;
}
set
{
GetComponent<TransformComponent>().Scale = value;
}
}
protected Entity() { ID = 0; }
@ -51,50 +91,86 @@ namespace Prism
return new Entity(entityID);
}
public Mat4 GetTransform()
public Entity FindEntityByID(ulong entityID)
{
Mat4 mat4Instance;
GetTransform_Native(ID, out mat4Instance);
return mat4Instance;
}
public void SetTransform(Mat4 transform)
{
SetTransform_Native(ID, ref transform);
// TODO: to verify it
return new Entity(entityID);
}
public void AddCollision2DBeginCallback(Action<float> callback)
{
m_Collision2DBeginCallbacks.Add(callback);
m_Collision2DBeginCallbacks += callback;
}
public void AddCollision2DEndCallback(Action<float> callback)
{
m_Collision2DEndCallbacks.Add(callback);
m_Collision2DEndCallbacks += callback;
}
public void AddCollisionBeginCallback(Action<float> callback)
{
m_CollisionBeginCallbacks += callback;
}
public void AddCollisionEndCallback(Action<float> callback)
{
m_CollisionEndCallbacks += callback;
}
public void AddTriggerBeginCallback(Action<float> callback)
{
m_TriggerBeginCallbacks += callback;
}
public void AddTriggerEndCallback(Action<float> callback)
{
m_TriggerEndCallbacks += callback;
}
private void OnCollisionBegin(float data)
{
if (m_CollisionBeginCallbacks != null)
m_CollisionBeginCallbacks.Invoke(data);
}
private void OnCollisionEnd(float data)
{
if (m_CollisionEndCallbacks != null)
m_CollisionEndCallbacks.Invoke(data);
}
private void OnCollision2DBegin(float data)
{
foreach (var callback in m_Collision2DBeginCallbacks)
callback.Invoke(data);
if(m_Collision2DBeginCallbacks != null)
m_Collision2DBeginCallbacks.Invoke(data);
}
private void OnCollision2DEnd(float data)
{
foreach (var callback in m_Collision2DEndCallbacks)
callback.Invoke(data);
if(m_Collision2DEndCallbacks != null)
m_Collision2DEndCallbacks.Invoke(data);
}
private void OnTriggerBegin(float data)
{
if (m_TriggerBeginCallbacks != null)
m_TriggerBeginCallbacks.Invoke(data);
}
private void OnTriggerEnd(float data)
{
if (m_TriggerEndCallbacks != null)
m_TriggerEndCallbacks.Invoke(data);
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void CreateComponent_Native(ulong entityID, Type type);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool HasComponent_Native(ulong entityID, Type type);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void GetTransform_Native(ulong entityID, out Mat4 matrix);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void SetTransform_Native(ulong entityID, ref Mat4 matrix);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong FindEntityByTag_Native(string tag);
}
}

View File

@ -4,14 +4,61 @@ namespace Prism
{
public class Input
{
public enum CursorMode
{
Normal = 0,
Hidden = 1,
Locked = 2,
}
public enum MouseButton
{
Button0 = 0,
Button1 = 1,
Button2 = 2,
Button3 = 3,
Button4 = 4,
Button5 = 5,
Left = Button0,
Right = Button1,
Middle = Button2
}
public static bool IsKeyPressed(KeyCode keycode)
{
return IsKeyPressed_Native(keycode);
}
public static bool IsMouseButtonPressed(MouseButton button)
{
return IsMouseButtonPressed_Native(button);
}
public static Vec2 GetMousePosition()
{
GetMousePosition_Native(out Vec2 position);
return position;
}
public static void SetCursorMode(CursorMode mode) => SetCursorMode_Native(mode);
public static CursorMode GetCursorMode() => GetCursorMode_Native();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool IsKeyPressed_Native(KeyCode keycode);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool IsMouseButtonPressed_Native(MouseButton button);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void GetMousePosition_Native(out Vec2 position);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void SetCursorMode_Native(CursorMode mode);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern CursorMode GetCursorMode_Native();
}
}

View File

@ -0,0 +1,16 @@
namespace Prism
{
public static class Mathf
{
public const float DegreeToRadians = (float)Math.PI * 2.0f / 360.0f;
public const float RadiansToDegrees = 360.0f / ((float)Math.PI * 2.0f);
public static float Clamp(float value, float min, float max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
}
}

View File

@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace Prism
{
[StructLayout(LayoutKind.Sequential)]
public struct Transform
{
public Vec3 Position;
public Vec3 Rotation;
public Vec3 Scale;
public Vec3 Up;
public Vec3 Right;
public Vec3 Forward;
}
}

View File

@ -8,6 +8,8 @@ namespace Prism
public float X;
public float Y;
public static Vec2 Zero = new Vec2(0.0f, 0.0f);
public Vec2(float scalar)
{
X = Y = scalar;
@ -25,20 +27,68 @@ namespace Prism
}
public void Clamp(Vec2 min, Vec2 max) {
if (X < min.X)
X = min.X;
if (X > max.X)
X = max.X;
if (Y < min.Y)
Y = min.Y;
if (Y > max.Y)
Y = max.Y;
X = Mathf.Clamp(X, min.X, max.X);
Y = Mathf.Clamp(Y, min.Y, max.Y);
}
public float LengthSquared()
{
return X * X + Y * Y;
}
public float Length()
{
return (float)Math.Sqrt(X * X + Y * Y);
}
public Vec2 SafeNormalized()
{
float length = Length();
if (length > float.Epsilon)
{
return new Vec2(X / length, Y / length);
}
else
{
return Zero;
}
}
public Vec2 Normalized()
{
float length = Length();
return new Vec2(X / length, Y / length);
}
public void SafeNormalize()
{
float length = Length();
if (length > float.Epsilon)
{
X = X / length;
Y = Y / length;
}
}
public void Normalize()
{
float length = Length();
X = X / length;
Y = Y / length;
}
public static Vec2 operator -(Vec2 l, Vec2 r)
{
return new Vec2(l.X - r.X, l.Y - r.Y);
}
public static Vec2 operator -(Vec2 vector)
{
return new Vec2(-vector.X, -vector.Y);
}
public override string ToString()
{
return $"({X}, {Y})";
}
}
}

View File

@ -5,6 +5,12 @@ namespace Prism
[StructLayout(LayoutKind.Sequential)]
public struct Vec3
{
public static Vec3 Zero = new Vec3(0.0f, 0.0f, 0.0f);
public static Vec3 Forward = new Vec3(0.0f, 0.0f, -1.0f);
public static Vec3 Right = new Vec3(1.0f, 0.0f, 0.0f);
public static Vec3 Up = new Vec3(0.0f, 1.0f, 0.0f);
public float X;
public float Y;
public float Z;
@ -14,11 +20,24 @@ namespace Prism
X = Y = Z = scalar;
}
public Vec3(Vec2 vec) {
X = vec.X;
Y = vec.Y;
public Vec3(Vec2 vec2) {
X = vec2.X;
Y = vec2.Y;
Z = 0.0f;
}
public Vec3(Vec2 vec2, float z) {
X = vec2.X;
Y = vec2.Y;
Z = z;
}
public Vec3(float x, Vec2 vec2)
{
X = x;
Y = vec2.X;
Z = vec2.Y;
}
public Vec3(float x, float y, float z)
{
@ -26,12 +45,65 @@ namespace Prism
Y = y;
Z = z;
}
public Vec3(Vec4 vec) {
X = vec.X;
Y = vec.Y;
Z = vec.Z;
}
public static Vec3 Cross(Vec3 a, Vec3 b)
{
return new Vec3(
a.Y * b.Z - a.Z * b.Y,
a.Z * b.X - a.X * b.Z,
a.X * b.Y - a.Y * b.X
);
}
public void Clamp(Vec3 min, Vec3 max)
{
X = Mathf.Clamp(X, min.X, max.X);
Y = Mathf.Clamp(Y, min.Y, max.Y);
Z = Mathf.Clamp(Z, min.Z, max.Z);
}
public float LengthSquared()
{
return X * X + Y * Y + Z * Z;
}
public float Length()
{
return (float)Math.Sqrt(X * X + Y * Y + Z * Z);
}
public Vec3 Normalized()
{
float length = Length();
if (length > float.Epsilon)
{
return new Vec3(X / length, Y / length, Z / length);
}
else
{
return Zero;
}
}
public void Normalize()
{
float length = Length();
if (length > float.Epsilon)
{
X = X / length;
Y = Y / length;
Z = Z / length;
}
}
public Vec2 XY {
get { return new Vec2(X, Y); }
@ -49,5 +121,61 @@ namespace Prism
set { Y = value.X; Z = value.Y; }
}
public static Vec3 operator *(Vec3 left, float scalar)
{
return new Vec3(left.X * scalar, left.Y * scalar, left.Z * scalar);
}
public static Vec3 operator *(float scalar, Vec3 right)
{
return new Vec3(scalar * right.X, scalar * right.Y, scalar * right.Z);
}
public static Vec3 operator +(Vec3 left, Vec3 right)
{
return new Vec3(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
}
public static Vec3 operator +(Vec3 left, float right)
{
return new Vec3(left.X + right, left.Y + right, left.Z + right);
}
public static Vec3 operator -(Vec3 left, Vec3 right)
{
return new Vec3(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
}
public static Vec3 operator /(Vec3 left, Vec3 right)
{
return new Vec3(left.X / right.X, left.Y / right.Y, left.Z / right.Z);
}
public static Vec3 operator /(Vec3 left, float scalar)
{
return new Vec3(left.X / scalar, left.Y / scalar, left.Z / scalar);
}
public static Vec3 operator-(Vec3 vector)
{
return new Vec3(-vector.X, -vector.Y, -vector.Z);
}
public static Vec3 Cos(Vec3 vector)
{
return new Vec3((float)Math.Cos(vector.X), (float)Math.Cos(vector.Y), (float)Math.Cos(vector.Z));
}
public static Vec3 Sin(Vec3 vector)
{
return new Vec3((float)Math.Sin(vector.X), (float)Math.Sin(vector.Y), (float)Math.Sin(vector.Z));
}
public override string ToString()
{
return $"({X}, {Y}, {Z})";
}
}
}

View File

@ -2,13 +2,13 @@ using System.Runtime.InteropServices;
namespace Prism
{
[StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Sequential)]
public struct Vec4
{
[FieldOffset(0)] public float X;
[FieldOffset(4)] public float Y;
[FieldOffset(8)] public float Z;
[FieldOffset(12)] public float W;
public float X;
public float Y;
public float Z;
public float W;
public Vec4(float scalar)
{

View File

@ -0,0 +1,100 @@
using System;
namespace Prism
{
public class Collider
{
public ulong EntityID { get; protected set; }
public bool IsTrigger { get; protected set; }
private Entity entity;
private RigidBodyComponent _rigidBodyComponent;
public Entity Entity
{
get
{
if (entity == null)
entity = new Entity(EntityID);
return entity;
}
}
public RigidBodyComponent RigidBody
{
get
{
if (_rigidBodyComponent == null)
_rigidBodyComponent = Entity.GetComponent<RigidBodyComponent>();
return _rigidBodyComponent;
}
}
public override string ToString()
{
string type = "Collider";
if (this is BoxCollider) type = "BoxCollider";
else if (this is SphereCollider) type = "SphereCollider";
else if (this is CapsuleCollider) type = "CapsuleCollider";
else if (this is MeshCollider) type = "MeshCollider";
return "Collider(" + type + ", " + EntityID + ", " + IsTrigger + ")";
}
}
public class BoxCollider : Collider
{
public Vec3 Size { get; protected set; }
public Vec3 Offset { get; protected set; }
internal BoxCollider(ulong entityID, bool isTrigger, Vec3 size, Vec3 offset)
{
EntityID = entityID;
Size = size;
Offset = offset;
IsTrigger = isTrigger;
}
}
public class SphereCollider : Collider
{
public float Radius { get; protected set; }
internal SphereCollider(ulong entityID, bool isTrigger, float radius)
{
EntityID = entityID;
Radius = radius;
IsTrigger = isTrigger;
}
}
public class CapsuleCollider : Collider
{
public float Radius { get; protected set; }
public float Height { get; protected set; }
internal CapsuleCollider(ulong entityID, bool isTrigger, float radius, float height)
{
EntityID = entityID;
Radius = radius;
Height = height;
IsTrigger = isTrigger;
}
}
public class MeshCollider : Collider
{
public Mesh Mesh { get; protected set; }
internal MeshCollider(ulong entityID, bool isTrigger, IntPtr filepath)
{
EntityID = entityID;
Mesh = new Mesh(filepath);
IsTrigger = isTrigger;
}
}
}

View File

@ -0,0 +1,74 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Prism
{
[StructLayout(LayoutKind.Sequential)]
public struct RaycastHit
{
public ulong EntityID { get; private set; }
public Vec3 Position { get; private set; }
public Vec3 Normal { get; private set; }
public float Distance { get; private set; }
}
public static class Physics
{
public static bool Raycast(Vec3 origin, Vec3 direction, float maxDistance, out RaycastHit hit)
{
return Raycast_Native(ref origin, ref direction, maxDistance, out hit);
}
public static Collider[] OverlapBox(Vec3 origin, Vec3 halfSize)
{
return OverlapBox_Native(ref origin, ref halfSize);
}
public static Collider[] OverlapCapsule(Vec3 origin, float radius, float halfHeight)
{
return OverlapCapsule_Native(ref origin, radius, halfHeight);
}
public static Collider[] OverlapSphere(Vec3 origin, float radius)
{
return OverlapSphere_Native(ref origin, radius);
}
public static int OverlapBoxNonAlloc(Vec3 origin, Vec3 halfSize, Collider[] colliders)
{
return OverlapBoxNonAlloc_Native(ref origin, ref halfSize, colliders);
}
public static int OverlapCapsuleNonAlloc(Vec3 origin, float radius, float halfHeight, Collider[] colliders)
{
return OverlapCapsuleNonAlloc_Native(ref origin, radius, halfHeight, colliders);
}
public static int OverlapSphereNonAlloc(Vec3 origin, float radius, Collider[] colliders)
{
return OverlapSphereNonAlloc_Native(ref origin, radius, colliders);
}
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Raycast_Native(ref Vec3 origin, ref Vec3 direction, float maxDistance, out RaycastHit hit);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern Collider[] OverlapBox_Native(ref Vec3 origin, ref Vec3 halfSize);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern Collider[] OverlapCapsule_Native(ref Vec3 origin, float radius, float halfHeight);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern Collider[] OverlapSphere_Native(ref Vec3 origin, float radius);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int OverlapBoxNonAlloc_Native(ref Vec3 origin, ref Vec3 halfSize, Collider[] colliders);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int OverlapCapsuleNonAlloc_Native(ref Vec3 origin, float radius, float halfHeight, Collider[] colliders);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int OverlapSphereNonAlloc_Native(ref Vec3 origin, float radius, Collider[] colliders);
}
}

View File

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Prism
{
@ -31,13 +32,12 @@ namespace Prism
}
public class TransformComponent : Component
{
public Mat4 Transform
{
public Transform Transform
{
get
{
Mat4 result;
GetTransform_Native(Entity.ID, out result);
GetTransform_Native(Entity.ID, out Transform result);
return result;
}
set
@ -46,12 +46,68 @@ namespace Prism
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void GetTransform_Native(ulong entityID, out Mat4 result);
public Vec3 Translation
{
get
{
GetTranslation_Native(Entity.ID, out Vec3 result);
return result;
}
set
{
SetTranslation_Native(Entity.ID, ref value);
}
}
public Vec3 Rotation
{
get
{
GetRotation_Native(Entity.ID, out Vec3 result);
return result;
}
set
{
SetRotation_Native(Entity.ID, ref value);
}
}
public Vec3 Scale
{
get
{
GetScale_Native(Entity.ID, out Vec3 result);
return result;
}
set
{
SetScale_Native(Entity.ID, ref value);
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void SetTransform_Native(ulong entityID, ref Mat4 result);
internal static extern void GetTransform_Native(ulong entityID, out Transform inTransform);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void SetTransform_Native(ulong entityID, ref Transform outTransform);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GetTranslation_Native(ulong entityID, out Vec3 outTranslation);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void SetTranslation_Native(ulong entityID, ref Vec3 inTranslation);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GetRotation_Native(ulong entityID, out Vec3 outRotation);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void SetRotation_Native(ulong entityID, ref Vec3 inRotation);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GetScale_Native(ulong entityID, out Vec3 outScale);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void SetScale_Native(ulong entityID, ref Vec3 inScale);
}
public class MeshComponent : Component
@ -123,4 +179,121 @@ namespace Prism
public class BoxCollider2DComponent : Component
{
}
public class BoxColliderComponent : Component
{
}
public class SphereColliderComponent : Component
{
}
public class RigidBodyComponent : Component
{
public enum Type
{
Static,
Dynamic
}
public enum ForceMode
{
Force = 0,
Impulse,
VelocityChange,
Acceleration
}
public Type BodyType
{
get
{
return GetBodyType_Native(Entity.ID);
}
}
public float Mass
{
get { return GetMass_Native(Entity.ID); }
set { SetMass_Native(Entity.ID, value); }
}
public uint Layer
{
get { return GetLayer_Native(Entity.ID); }
}
public void AddForce(Vec3 force, ForceMode forceMode = ForceMode.Force)
{
AddForce_Native(Entity.ID, ref force, forceMode);
}
public void AddTorque(Vec3 torque, ForceMode forceMode = ForceMode.Force)
{
AddTorque_Native(Entity.ID, ref torque, forceMode);
}
public Vec3 GetLinearVelocity()
{
GetLinearVelocity_Native(Entity.ID, out Vec3 velocity);
return velocity;
}
public void SetLinearVelocity(Vec3 velocity)
{
SetLinearVelocity_Native(Entity.ID, ref velocity);
}
public Vec3 GetAngularVelocity()
{
GetAngularVelocity_Native(Entity.ID, out Vec3 velocity);
return velocity;
}
public void SetAngularVelocity(Vec3 velocity)
{
SetAngularVelocity_Native(Entity.ID, ref velocity);
}
public void Rotate(Vec3 rotation)
{
Rotate_Native(Entity.ID, ref rotation);
}
// TODO: Add SetMaxLinearVelocity() as well
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void AddForce_Native(ulong entityID, ref Vec3 force, ForceMode forceMode);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void AddTorque_Native(ulong entityID, ref Vec3 torque, ForceMode forceMode);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GetLinearVelocity_Native(ulong entityID, out Vec3 velocity);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void SetLinearVelocity_Native(ulong entityID, ref Vec3 velocity);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void GetAngularVelocity_Native(ulong entityID, out Vec3 velocity);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void SetAngularVelocity_Native(ulong entityID, ref Vec3 velocity);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Rotate_Native(ulong entityID, ref Vec3 rotation);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern uint GetLayer_Native(ulong entityID);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern float GetMass_Native(ulong entityID);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern float SetMass_Native(ulong entityID, float mass);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern Type GetBodyType_Native(ulong entityID);
}
}

View File

@ -18,6 +18,7 @@ add_subdirectory(vendor/mono EXCLUDE_FROM_ALL)
add_subdirectory(vendor/FastNoise EXCLUDE_FROM_ALL)
add_subdirectory(vendor/yaml-cpp EXCLUDE_FROM_ALL)
add_subdirectory(vendor/Box2D EXCLUDE_FROM_ALL)
add_subdirectory(vendor/efsw EXCLUDE_FROM_ALL)
# ------------- imgui -------------
@ -46,6 +47,42 @@ list(APPEND SRC_SOURCE ${IMGUIZMO_SOURCE})
# ----------- ImViewGuizmo -------------
set(IMVIEWGUIZMO_DIR vendor/ImViewGuizmo)
# ------------- NVIDIA PhysX -------------
# PhysX/physx/buildtools/presets/*.xml
# PX_GENERATE_STATIC_LIBRARIES=True
# NV_USE_STATIC_WINCRT=False
set(PHYSX_BUILD_TYPE "checked" CACHE STRING "The build type of PhysX")
set_property(CACHE PHYSX_BUILD_TYPE PROPERTY STRINGS debug checked profile release)
if(NOT CMAKE_BUILD_TYPE)
if(PHYSX_BUILD_TYPE STREQUAL "debug" OR PHYSX_BUILD_TYPE STREQUAL "checked")
set(CMAKE_BUILD_TYPE "Debug")
endif()
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set(PHYSX_BUILD_TYPE "release")
elseif (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(PHYSX_BUILD_TYPE "debug")
endif ()
include_directories(vendor/PhysX/physx/include)
set(PHYSX_LIB_DIR "vendor/PhysX/physx/bin/win.x86_64.vc143.md/${PHYSX_BUILD_TYPE}") # This is the path where PhysX libraries are installed
link_directories(${PHYSX_LIB_DIR})
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Building snippet in debug with PhysX ${PHYSX_BUILD_TYPE} configuration")
add_compile_definitions(_DEBUG)
else()
message("Building snippet in release configuration with PhysX ${PHYSX_BUILD_TYPE} configuration")
add_compile_definitions(NDEBUG)
endif()
# ------------- link libraries -------------
set(LINK_LIBRARIES_PRIVATE
glfw
@ -55,6 +92,14 @@ set(LINK_LIBRARIES_PRIVATE
tinyFileDialogs
FastNoise
yaml-cpp
efsw
PhysXExtensions_static_64
PhysX_static_64
PhysXPvdSDK_static_64
PhysXCommon_static_64
PhysXFoundation_static_64
PhysXCooking_static_64
)
set(LINK_LIBRARIES_PUBLIC
@ -79,15 +124,17 @@ set(TARGET_INCLUDE_DIR
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
${IMGUI_DIR}
${IMGUIZMO_DIR}
${IMVIEWGUIZMO_DIR}
)
# ------------- debug Defines -------------
set(DEBUG_DEFINITIONS
$<$<CONFIG:Debug>:PM_ENABLE_ASSERTS>
$<$<CONFIG:RelWithDebInfo>:PM_ENABLE_ASSERTS>
)
# static library
# static library
set(STATIC_LIBRARY ${PROJECT_NAME}-static)
add_library(${STATIC_LIBRARY} STATIC ${SRC_SOURCE})
@ -101,6 +148,11 @@ target_compile_definitions(${STATIC_LIBRARY} PRIVATE
target_include_directories(${STATIC_LIBRARY} PUBLIC
${TARGET_INCLUDE_DIR}
)
target_link_directories(${STATIC_LIBRARY} INTERFACE
${PHYSX_LIB_DIR}
)
target_link_libraries(${STATIC_LIBRARY}
PRIVATE
${LINK_LIBRARIES_PRIVATE}
@ -145,7 +197,45 @@ set_target_properties(${SHARED_LIBRARY} PROPERTIES
)
# static runtime library
set(RUNTIME_LIBRARY ${PROJECT_NAME}-Runtime)
add_library(${RUNTIME_LIBRARY} STATIC ${SRC_SOURCE})
target_compile_definitions(${RUNTIME_LIBRARY} PRIVATE
PRISM_RUNTIME
INTERFACE
PRISM_STATIC
)
target_include_directories(${RUNTIME_LIBRARY} PUBLIC
${TARGET_INCLUDE_DIR}
)
target_link_directories(${RUNTIME_LIBRARY} INTERFACE
${PHYSX_LIB_DIR}
)
target_link_libraries(${RUNTIME_LIBRARY}
PRIVATE
${LINK_LIBRARIES_PRIVATE}
PUBLIC
${LINK_LIBRARIES_PUBLIC}
)
target_precompile_headers(${RUNTIME_LIBRARY} PRIVATE
src/pmpch.h
)
set_target_properties(${RUNTIME_LIBRARY} PROPERTIES
OUTPUT_NAME ${PROJECT_NAME}
ARCHIVE_OUTPUT_NAME ${PROJECT_NAME}rd
)
# alias
add_library(Prism::static ALIAS Prism-static)
add_library(Prism::shared ALIAS Prism-shared)
add_library(Prism ALIAS Prism-shared)
add_library(Prism::Runtime ALIAS Prism-Runtime)

View File

@ -0,0 +1,79 @@
//
// Created by Atdunbg on 2026/3/9.
//
#include "AnimationClip.h"
#include "assimp/scene.h"
struct aiAnimation;
namespace Prism
{
AnimationClip::AnimationClip(const aiScene* scene)
{
const aiAnimation* anim = scene->mAnimations[0]; // 先只处理第一个动画
m_Duration = (float)anim->mDuration;
m_TicksPerSecond = (float)(anim->mTicksPerSecond != 0 ? anim->mTicksPerSecond : 25.0);
for (uint32_t i = 0; i < anim->mNumChannels; i++)
{
aiNodeAnim* nodeAnim = anim->mChannels[i];
BoneChannel channel;
channel.BoneName = nodeAnim->mNodeName.C_Str();
// 位置关键帧
for (uint32_t j = 0; j < nodeAnim->mNumPositionKeys; j++)
{
const auto& key = nodeAnim->mPositionKeys[j];
channel.Positions.emplace_back((float)key.mTime,
glm::vec3(key.mValue.x, key.mValue.y, key.mValue.z));
}
// 旋转关键帧
for (uint32_t j = 0; j < nodeAnim->mNumRotationKeys; j++)
{
const auto& key = nodeAnim->mRotationKeys[j];
channel.Rotations.emplace_back((float)key.mTime,
glm::quat(key.mValue.w, key.mValue.x, key.mValue.y, key.mValue.z));
}
// 缩放关键帧
for (uint32_t j = 0; j < nodeAnim->mNumScalingKeys; j++)
{
const auto& key = nodeAnim->mScalingKeys[j];
channel.Scalings.emplace_back((float)key.mTime,
glm::vec3(key.mValue.x, key.mValue.y, key.mValue.z));
}
m_BoneChannels.push_back(channel);
}
}
glm::mat4 AnimationClip::SampleAtTime(const float timeTicks, const std::string& boneName, const glm::mat4& defaultTransform) const
{
for (const auto& channel : m_BoneChannels) {
if (channel.BoneName == boneName) {
const glm::vec3 pos = SampleChannel(channel.Positions, timeTicks, glm::vec3(0.0f));
const glm::quat rot = SampleChannel(channel.Rotations, timeTicks, glm::quat(1.0f, 0.0f, 0.0f, 0.0f));
const glm::vec3 scl = SampleChannel(channel.Scalings, timeTicks, glm::vec3(1.0f));
return glm::translate(glm::mat4(1.0f), pos) * glm::toMat4(rot) * glm::scale(glm::mat4(1.0f), scl);
}
}
return defaultTransform;
}
template <typename T>
T AnimationClip::SampleChannel(const std::vector<KeyFrame<T>>& keys, float time, const T& defaultValue) const
{
if (keys.empty()) return defaultValue;
if (time <= keys.front().Time) return keys.front().Value;
if (time >= keys.back().Time) return keys.back().Value;
// 找到当前时间所在的关键帧区间
for (size_t i = 0; i < keys.size() - 1; ++i) {
if (time >= keys[i].Time && time <= keys[i+1].Time) {
float t = (time - keys[i].Time) / (keys[i+1].Time - keys[i].Time);
return Interpolate(keys[i].Value, keys[i+1].Value, t);
}
}
return defaultValue;
}
}

View File

@ -0,0 +1,63 @@
//
// Created by Atdunbg on 2026/3/9.
//
#ifndef PRISM_ANIMATIONCLIP_H
#define PRISM_ANIMATIONCLIP_H
#include "Prism/Asset/Asset.h"
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
struct aiScene;
namespace Prism
{
template<typename T>
struct KeyFrame
{
float Time = 0.0f;
T Value;
KeyFrame() = default;
KeyFrame(const float t, const T& v) : Time(t), Value(v) {}
};
struct BoneChannel {
std::string BoneName;
std::vector<KeyFrame<glm::vec3>> Positions;
std::vector<KeyFrame<glm::quat>> Rotations;
std::vector<KeyFrame<glm::vec3>> Scalings;
};
class PRISM_API AnimationClip : public Asset
{
public:
AnimationClip(const aiScene* scene);
glm::mat4 SampleAtTime(float timeTicks, const std::string& boneName, const glm::mat4& defaultTransform) const;
private:
// 辅助采样模板函数
template<typename T>
T SampleChannel(const std::vector<KeyFrame<T>>& keys, float time, const T& defaultValue) const;
glm::vec3 Interpolate(const glm::vec3& a, const glm::vec3& b, const float t) const {
return glm::mix(a, b, t);
}
glm::quat Interpolate(const glm::quat& a, const glm::quat& b, const float t) const {
return glm::slerp(a, b, t);
}
public:
float m_Duration = 0.0f;
float m_TicksPerSecond;
std::vector<BoneChannel> m_BoneChannels;
};
}
#endif //PRISM_ANIMATIONCLIP_H

View File

@ -0,0 +1,124 @@
//
// Created by Atdunbg on 2026/3/9.
//
#include "AnimatorController.h"
namespace Prism
{
AnimationController::~AnimationController()
{
}
void AnimationController::Update(const float deltaTime)
{
if (m_State != PLAY || !m_currentClip || !m_skeleton)
return;
const float rawTime = m_AnimationCurrentTime + deltaTime * m_currentClip->m_TicksPerSecond * m_TimeMultiplier;
const float duration = m_currentClip->m_Duration;
if (!m_Loop && rawTime >= duration)
{
m_AnimationCurrentTime = 0.0f;
m_State = STOP;
}
else
{
m_AnimationCurrentTime = fmod(rawTime, duration);
}
ComputeBoneTransforms(m_AnimationCurrentTime);
}
void AnimationController::SetSkeleton(const Ref<Skeleton>& skeleton)
{
m_skeleton = skeleton;
if (m_skeleton)
{
ComputeBindPoseTransforms();
}
}
void AnimationController::ComputeBoneTransforms(float time)
{
const auto boneCount = (uint32_t)m_skeleton->m_Bones.size();
m_FinalBoneTransforms.resize(boneCount);
std::vector<glm::mat4> localTransforms(boneCount, glm::mat4(1.0f));
for (uint32_t i = 0; i < boneCount; i++)
{
const BoneInfo& bone = m_skeleton->m_Bones[i];
if (m_currentClip)
{
localTransforms[i] = m_currentClip->SampleAtTime(time, bone.Name, bone.LocalBindPose);
}
else
{
localTransforms[i] = bone.LocalBindPose;
}
}
// 2. 递归计算全局变换(从根骨骼开始)
std::function<void(uint32_t, const glm::mat4&)> computeGlobal = [&](const uint32_t boneIdx, const glm::mat4& parentGlobal)
{
const glm::mat4 global = parentGlobal * localTransforms[boneIdx];
glm::mat4 final = global * m_skeleton->m_Bones[boneIdx].InverseTransform;
final = m_GlobalInverseTransform * final;
m_FinalBoneTransforms[boneIdx] = final;
for (uint32_t childIdx = 0; childIdx < boneCount; childIdx++)
{
if (m_skeleton->m_Bones[childIdx].ParentIndex == (int)boneIdx)
{
computeGlobal(childIdx, global);
}
}
};
for (uint32_t i = 0; i < boneCount; i++)
{
if (m_skeleton->m_Bones[i].ParentIndex == -1)
{
computeGlobal(i, glm::mat4(1.0f));
}
}
}
void AnimationController::ComputeBindPoseTransforms()
{
if (!m_skeleton) return;
uint32_t boneCount = (uint32_t)m_skeleton->m_Bones.size();
m_FinalBoneTransforms.resize(boneCount);
// 1. 收集每个骨骼的绑定姿势局部变换
std::vector<glm::mat4> localTransforms(boneCount);
for (uint32_t i = 0; i < boneCount; i++)
localTransforms[i] = m_skeleton->m_Bones[i].LocalBindPose;
// 2. 递归计算全局变换,并应用逆绑定矩阵和全局逆矩阵
std::function<void(uint32_t, const glm::mat4&)> computeGlobal =
[&](uint32_t idx, const glm::mat4& parentGlobal)
{
glm::mat4 global = parentGlobal * localTransforms[idx];
glm::mat4 final = m_GlobalInverseTransform * global * m_skeleton->m_Bones[idx].InverseTransform;
m_FinalBoneTransforms[idx] = final;
// 处理子骨骼
for (uint32_t child = 0; child < boneCount; child++)
{
if (m_skeleton->m_Bones[child].ParentIndex == (int)idx)
computeGlobal(child, global);
}
};
// 从所有根骨骼开始递归
for (uint32_t i = 0; i < boneCount; i++)
{
if (m_skeleton->m_Bones[i].ParentIndex == -1)
computeGlobal(i, glm::mat4(1.0f));
}
}
}

View File

@ -0,0 +1,60 @@
//
// Created by Atdunbg on 2026/3/9.
//
#ifndef PRISM_ANIMATORCONTROLLER_H
#define PRISM_ANIMATORCONTROLLER_H
#include "AnimationClip.h"
#include "Skeleton.h"
#include "Prism/Asset/Asset.h"
namespace Prism
{
class PRISM_API AnimationController : public Asset
{
public:
enum AnimationState : uint8_t
{
STOP = 0,
PLAY,
PAUSE
};
public:
~AnimationController();
void Update(const float deltaTime);
const std::vector<glm::mat4>& GetFinalBoneTransforms() const { return m_FinalBoneTransforms; }
void SetSkeleton(const Ref<Skeleton>& skeleton);
void SetAnimationClip(const Ref<AnimationClip>& clip) { m_currentClip = clip; }
void SetGlobalInverseTransform(const glm::mat4& inv) { m_GlobalInverseTransform = inv; }
void Play() { m_State = PLAY; }
void Pause() { m_State = PAUSE; }
void Stop() { if (m_State == STOP) return; m_State = STOP; m_AnimationCurrentTime = 0.0f; ComputeBindPoseTransforms(); }
AnimationState GetAnimationState() const { return m_State; }
float& GetCurrentTime() { return m_AnimationCurrentTime; }
float& GetMultiplierTime() { return m_TimeMultiplier; }
bool& GetLoop() { return m_Loop; }
private:
void ComputeBoneTransforms(float time);
void ComputeBindPoseTransforms();
Ref<Skeleton> m_skeleton = nullptr;
Ref<AnimationClip> m_currentClip = nullptr;
AnimationState m_State = STOP;
float m_AnimationCurrentTime = 0.0f;
float m_TimeMultiplier = 1.0f;
bool m_Loop = false;
std::vector<glm::mat4> m_FinalBoneTransforms;
glm::mat4 m_GlobalInverseTransform = glm::mat4(1.0f);
};
}
#endif //PRISM_ANIMATORCONTROLLER_H

View File

@ -0,0 +1,68 @@
//
// Created by Atdunbg on 2026/3/9.
//
#include "Skeleton.h"
#include "assimp/scene.h"
namespace Prism
{
// TODO: this maybe move Utils
extern glm::mat4 Mat4FromAssimpMat4(const aiMatrix4x4& matrix);
Skeleton::Skeleton(const aiScene* scene)
{
// 收集所有骨骼(通过所有 Mesh 中的 aiBone
for (uint32_t am = 0; am < scene->mNumMeshes; am++)
{
const aiMesh* aMesh = scene->mMeshes[am];
for (uint32_t i = 0; i < aMesh->mNumBones; i++)
{
const aiBone* bone = aMesh->mBones[i];
if (std::string boneName = bone->mName.C_Str(); m_NameToIndex.find(boneName) == m_NameToIndex.end())
{
const auto boneIndex = (uint32_t)m_Bones.size();
BoneInfo bi;
bi.Name = boneName;
bi.InverseTransform = Mat4FromAssimpMat4(bone->mOffsetMatrix);
bi.ParentIndex = -1; // 稍后设置
m_Bones.push_back(bi);
m_NameToIndex[boneName] = boneIndex;
}
}
}
// 设置骨骼的父子关系及默认局部变换(遍历节点树)
std::unordered_map<std::string, uint32_t>& nameToIdx = m_NameToIndex;
std::function<void(aiNode*, int)> traverseNodes = [&](const aiNode* node, const int parentBoneIdx)
{
const std::string nodeName = node->mName.C_Str();
const auto it = nameToIdx.find(nodeName);
int currentBoneIdx = -1;
if (it != nameToIdx.end())
{
currentBoneIdx = (int)it->second;
BoneInfo& bone = m_Bones[currentBoneIdx];
bone.ParentIndex = parentBoneIdx;
// 存储默认局部变换(绑定姿势下相对于父节点的变换)
bone.LocalBindPose = Mat4FromAssimpMat4(node->mTransformation);
}
for (uint32_t i = 0; i < node->mNumChildren; i++)
{
traverseNodes(node->mChildren[i], currentBoneIdx);
}
};
traverseNodes(scene->mRootNode, -1);
}
uint32_t Skeleton::GetBoneIndex(const std::string& name) const
{
const auto it = m_NameToIndex.find(name);
return (it != m_NameToIndex.end()) ? it->second : static_cast<uint32_t>(-1);
}
}

View File

@ -0,0 +1,35 @@
//
// Created by Atdunbg on 2026/3/9.
//
#ifndef PRISM_SKELETON_H
#define PRISM_SKELETON_H
#include "glm/glm.hpp"
#include "Prism/Asset/Asset.h"
struct aiScene;
namespace Prism
{
struct BoneInfo
{
std::string Name;
int ParentIndex; // -1 is Root
glm::mat4 LocalBindPose;
glm::mat4 InverseTransform; // aiScene::mOffsetMatrix
};
class PRISM_API Skeleton : public Asset
{
public:
Skeleton(const aiScene* scene);
uint32_t GetBoneIndex(const std::string& name) const;
public:
std::vector<BoneInfo> m_Bones;
std::unordered_map<std::string, uint32_t> m_NameToIndex;
};
}
#endif //PRISM_SKELETON_H

View File

@ -0,0 +1,25 @@
//
// Created by Atdunbg on 2026/3/26.
//
#include "Asset.h"
#include "Prism/Renderer/Texture.h"
namespace Prism
{
PhysicsMaterialAsset::PhysicsMaterialAsset(): StaticFriction(0), DynamicFriction(0), Bounciness(0)
{
Type = AssetType::PhysicsMaterial;
}
PhysicsMaterialAsset::PhysicsMaterialAsset(const float staticFriction, const float dynamicFriction,
const float bounciness): StaticFriction(staticFriction), DynamicFriction(dynamicFriction), Bounciness(bounciness)
{
Type = AssetType::PhysicsMaterial;
}
PBRMaterialAsset::PBRMaterialAsset()
{
Type = AssetType::Material;
}
}

View File

@ -0,0 +1,99 @@
//
// Created by Atdunbg on 2026/2/3.
//
#ifndef PRISM_ASSET_H
#define PRISM_ASSET_H
#include <glm/glm.hpp>
#include "Prism/Core/UUID.h"
#include "Prism/Core/Ref.h"
namespace Prism
{
class Texture2D;
class Shader;
class MaterialInstance;
enum class AssetType
{
Scene = 0, Mesh, Texture, EnvMap, Audio, Script, Material, PhysicsMaterial, Directory, Other, None
};
using AssetHandle = UUID;
class PRISM_API Asset : public RefCounted
{
public:
AssetHandle Handle;
AssetType Type = AssetType::None;
std::string FilePath;
std::string FileName;
std::string Extension;
AssetHandle ParentDirectory;
bool IsDataLoaded = false;
virtual ~Asset()
{
}
};
class PRISM_API PhysicsMaterialAsset : public Asset
{
public:
float StaticFriction;
float DynamicFriction;
float Bounciness;
PhysicsMaterialAsset();
PhysicsMaterialAsset(const float staticFriction, const float dynamicFriction, const float bounciness);
};
// Treating directories as assets simplifies the asset manager window rendering by a lot
class Directory : public Asset
{
public:
std::vector<AssetHandle> ChildDirectories;
Directory()
{
Type = AssetType::Directory;
}
};
class PBRMaterialAsset : public Asset
{
public:
PBRMaterialAsset();
virtual ~PBRMaterialAsset() = default;
// Albedo
glm::vec3 AlbedoColor = glm::vec3(1.0f);
float AlbedoTexToggle = 0.0f;
Ref<Texture2D> AlbedoTexture;
// Normal
float NormalTexToggle = 0.0f;
Ref<Texture2D> NormalTexture;
// Metalness
float Metalness = 0.8f;
float MetalnessTexToggle = 0.0f;
Ref<Texture2D> MetalnessTexture;
// Roughness
float Roughness = 0.1f;
float RoughnessTexToggle = 0.0f;
Ref<Texture2D> RoughnessTexture;
bool IsDirty = true;
};
}
#endif //PRISM_ASSET_H

View File

@ -0,0 +1,440 @@
//
// Created by Atdunbg on 2026/2/13.
//
#include "AssetSerializer.h"
#include "AssetsManager.h"
#include "Prism/Utilities/StringUtils.h"
#include "Prism/Utilities/FileSystem.h"
#include "Prism/Renderer/Mesh.h"
#include "Prism/Renderer/SceneEnvironment.h"
#include "Prism/Renderer/SceneRenderer.h"
#include "yaml-cpp/yaml.h"
#include "Prism/Utilities/SerializeUtils.h"
namespace Prism
{
void AssetSerializer::SerializeAsset(const Ref<Asset>& asset, AssetType type)
{
YAML::Emitter out;
switch (type)
{
case AssetType::Texture:
case AssetType::EnvMap:
case AssetType::Audio:
case AssetType::Script:
case AssetType::Directory:
case AssetType::Other:
case AssetType::None:
return;
}
out << YAML::BeginMap;
switch (type)
{
case Prism::AssetType::PhysicsMaterial:
{
Ref<PhysicsMaterialAsset> physicsMaterial = Ref<PhysicsMaterialAsset>(asset);
out << YAML::Key << "StaticFriction" << physicsMaterial->StaticFriction;
out << YAML::Key << "DynamicFriction" << physicsMaterial->DynamicFriction;
out << YAML::Key << "Bounciness" << physicsMaterial->Bounciness;
break;
}
case AssetType::Material:
{
const auto& material = Ref<PBRMaterialAsset>(asset);
// Albedo
out << YAML::Key << "Albedo";
out << YAML::BeginMap;
{
out << YAML::Key << "Color" << YAML::Value << material->AlbedoColor;
out << YAML::Key << "TexToggle" << YAML::Value << material->AlbedoTexToggle;
out << YAML::Key << "Texture";
out << YAML::BeginMap;
if (material->AlbedoTexture)
{
out << YAML::Key << "AssetHandle" << YAML::Value << material->AlbedoTexture->Handle;
out << YAML::Key << "AssetPath" << YAML::Value << material->AlbedoTexture->FilePath;
}
else
{
out << YAML::Key << "AssetHandle" << YAML::Value << 0;
out << YAML::Key << "AssetPath" << YAML::Value << "";
}
out << YAML::EndMap;
}
out << YAML::EndMap;
// Normal
out << YAML::Key << "Normal";
out << YAML::BeginMap;
{
out << YAML::Key << "TexToggle" << YAML::Value << material->NormalTexToggle;
out << YAML::Key << "Texture";
out << YAML::BeginMap;
if (material->NormalTexture)
{
out << YAML::Key << "AssetHandle" << YAML::Value << material->NormalTexture->Handle;
out << YAML::Key << "AssetPath" << YAML::Value << material->NormalTexture->FilePath;
}
else
{
out << YAML::Key << "AssetHandle" << YAML::Value << 0;
out << YAML::Key << "AssetPath" << YAML::Value << "";
}
out << YAML::EndMap;
}
out << YAML::EndMap;
// Metalness
out << YAML::Key << "Metalness";
out << YAML::BeginMap;
{
out << YAML::Key << "Value" << YAML::Value << material->Metalness; // 金属度数值
out << YAML::Key << "TexToggle" << YAML::Value << material->MetalnessTexToggle;
out << YAML::Key << "Texture";
out << YAML::BeginMap;
if (material->MetalnessTexture)
{
out << YAML::Key << "AssetHandle" << YAML::Value << material->MetalnessTexture->Handle;
out << YAML::Key << "AssetPath" << YAML::Value << material->MetalnessTexture->FilePath;
}
else
{
out << YAML::Key << "AssetHandle" << YAML::Value << 0;
out << YAML::Key << "AssetPath" << YAML::Value << "";
}
out << YAML::EndMap;
}
out << YAML::EndMap;
// Roughness
out << YAML::Key << "Roughness";
out << YAML::BeginMap;
{
out << YAML::Key << "Value" << YAML::Value << material->Roughness; // 粗糙度数值
out << YAML::Key << "TexToggle" << YAML::Value << material->RoughnessTexToggle;
out << YAML::Key << "Texture";
out << YAML::BeginMap;
if (material->RoughnessTexture)
{
out << YAML::Key << "AssetHandle" << YAML::Value << material->RoughnessTexture->Handle;
out << YAML::Key << "AssetPath" << YAML::Value << material->RoughnessTexture->FilePath;
}
else
{
out << YAML::Key << "AssetHandle" << YAML::Value << 0;
out << YAML::Key << "AssetPath" << YAML::Value << "";
}
out << YAML::EndMap;
}
out << YAML::EndMap;
break;
}
}
out << YAML::EndMap;
PM_CORE_INFO("Save Asset change...");
std::ofstream fout(asset->FilePath);
fout << out.c_str();
}
Ref<Asset> AssetSerializer::DeserializeYAML(const Ref<Asset>& asset)
{
const std::ifstream stream(asset->FilePath);
std::stringstream strStream;
strStream << stream.rdbuf();
YAML::Node data = YAML::Load(strStream.str());
switch (asset->Type)
{
case AssetType::PhysicsMaterial:
{
float staticFriction = data["StaticFriction"].as<float>();
float dynamicFriction = data["DynamicFriction"].as<float>();
float bounciness = data["Bounciness"].as<float>();
return Ref<PhysicsMaterialAsset>::Create(staticFriction, dynamicFriction, bounciness);
}
case AssetType::Material:
{
Ref<PBRMaterialAsset> material = Ref<PBRMaterialAsset>::Create();
// ==================== Albedo ====================
if (data["Albedo"])
{
auto albedoNode = data["Albedo"];
material->AlbedoColor = albedoNode["Color"].as<glm::vec3>();
material->AlbedoTexToggle = albedoNode["TexToggle"].as<float>();
if (albedoNode["Texture"])
{
auto texNode = albedoNode["Texture"];
UUID texHandle = 0;
std::string texPath;
if (texNode["AssetHandle"])
texHandle = texNode["AssetHandle"].as<uint64_t>();
if (texNode["AssetPath"])
texPath = texNode["AssetPath"].as<std::string>();
if (texHandle != 0 && AssetsManager::IsAssetHandleValid(texHandle))
material->AlbedoTexture = AssetsManager::GetAsset<Texture2D>(texHandle);
else if (!texPath.empty())
material->AlbedoTexture = Texture2D::Create(texPath);
else
material->AlbedoTexture = nullptr;
}
}
// ==================== Normal ====================
if (data["Normal"])
{
auto normalNode = data["Normal"];
material->NormalTexToggle = normalNode["TexToggle"].as<float>();
if (normalNode["Texture"])
{
auto texNode = normalNode["Texture"];
UUID texHandle = 0;
std::string texPath;
if (texNode["AssetHandle"])
texHandle = texNode["AssetHandle"].as<uint64_t>();
if (texNode["AssetPath"])
texPath = texNode["AssetPath"].as<std::string>();
if (texHandle != 0 && AssetsManager::IsAssetHandleValid(texHandle))
material->NormalTexture = AssetsManager::GetAsset<Texture2D>(texHandle);
else if (!texPath.empty())
material->NormalTexture = Texture2D::Create(texPath);
else
material->NormalTexture = nullptr;
}
}
// ==================== Metalness ====================
if (data["Metalness"])
{
auto metalNode = data["Metalness"];
material->Metalness = metalNode["Value"].as<float>();
material->MetalnessTexToggle = metalNode["TexToggle"].as<float>();
if (metalNode["Texture"])
{
auto texNode = metalNode["Texture"];
UUID texHandle = 0;
std::string texPath;
if (texNode["AssetHandle"])
texHandle = texNode["AssetHandle"].as<uint64_t>();
if (texNode["AssetPath"])
texPath = texNode["AssetPath"].as<std::string>();
if (texHandle != 0 && AssetsManager::IsAssetHandleValid(texHandle))
material->MetalnessTexture = AssetsManager::GetAsset<Texture2D>(texHandle);
else if (!texPath.empty())
material->MetalnessTexture = Texture2D::Create(texPath);
else
material->MetalnessTexture = nullptr;
}
}
// ==================== Roughness ====================
if (data["Roughness"])
{
auto roughNode = data["Roughness"];
material->Roughness = roughNode["Value"].as<float>();
material->RoughnessTexToggle = roughNode["TexToggle"].as<float>();
if (roughNode["Texture"])
{
auto texNode = roughNode["Texture"];
UUID texHandle = 0;
std::string texPath;
if (texNode["AssetHandle"])
texHandle = texNode["AssetHandle"].as<uint64_t>();
if (texNode["AssetPath"])
texPath = texNode["AssetPath"].as<std::string>();
if (texHandle != 0 && AssetsManager::IsAssetHandleValid(texHandle))
material->RoughnessTexture = AssetsManager::GetAsset<Texture2D>(texHandle);
else if (!texPath.empty())
material->RoughnessTexture = Texture2D::Create(texPath);
else
material->RoughnessTexture = nullptr;
}
}
return material;
}
}
return nullptr;
}
Ref<Asset> AssetSerializer::LoadAssetInfo(const std::string& filepath, AssetHandle parentHandle, AssetType type)
{
Ref<Asset> asset;
if (type == AssetType::Directory)
asset = Ref<Directory>::Create();
else
asset = Ref<Asset>::Create();
const std::string extension = Utils::GetExtension(filepath);
asset->FilePath = filepath;
asset->Type = type;
std::replace(asset->FilePath.begin(), asset->FilePath.end(), '\\', '/');
const bool hasMeta = FileSystem::Exists(asset->FilePath + ".meta");
if (hasMeta)
{
LoadMetaData(asset);
}
else
{
asset->Handle = AssetHandle();
}
// TODO: file or directory Type to fix
asset->Extension = extension;
asset->FileName = Utils::RemoveExtension(Utils::GetFilename(filepath));
asset->ParentDirectory = parentHandle;
asset->IsDataLoaded = false;
#ifndef PRISM_RUNTIME
if (!hasMeta)
CreateMetaFile(asset);
#endif
return asset;
}
Ref<Asset> AssetSerializer::LoadAssetData(Ref<Asset>& asset)
{
if (asset->Type == AssetType::Directory)
return asset;
Ref<Asset> temp = asset;
bool loadYAMLData = true;
switch (asset->Type)
{
case AssetType::Mesh:
{
if (asset->Extension != "blend")
asset = Ref<Mesh>::Create(asset->FilePath);
loadYAMLData = false;
break;
}
case AssetType::Texture:
{
asset = Texture2D::Create(asset->FilePath);
loadYAMLData = false;
break;
}
case AssetType::EnvMap:
{
auto [radiance, irradiance] = SceneRenderer::CreateEnvironmentMap(asset->FilePath);
asset = Ref<Environment>::Create(radiance, irradiance);
loadYAMLData = false;
break;
}
case AssetType::Scene:
case AssetType::Audio:
case AssetType::Script:
case AssetType::Other:
{
loadYAMLData = false;
break;
}
case AssetType::PhysicsMaterial:
break;
}
if (loadYAMLData)
{
asset = DeserializeYAML(asset);
PM_CORE_ASSERT(asset, "Failed to load asset");
}
asset->Handle = temp->Handle;
asset->FilePath = temp->FilePath;
asset->FileName = temp->FileName;
asset->Extension = temp->Extension;
asset->ParentDirectory = temp->ParentDirectory;
asset->Type = temp->Type;
asset->IsDataLoaded = true;
return asset;
}
void AssetSerializer::LoadMetaData(Ref<Asset>& asset)
{
std::ifstream stream(asset->FilePath + ".meta");
std::stringstream strStream;
strStream << stream.rdbuf();
YAML::Node data = YAML::Load(strStream.str());
if (!data["Asset"])
{
PM_CORE_ASSERT("Invalid File Format");
}
asset->Handle = data["Asset"].as<uint64_t>();
asset->FilePath = data["FilePath"].as<std::string>();
asset->Type = (AssetType)data["Type"].as<int>();
if (asset->FileName == "assets" && asset->Handle == 0)
{
asset->Handle = AssetHandle();
#ifndef PRISM_RUNTIME
CreateMetaFile(asset);
#endif
}
}
void AssetSerializer::CreateMetaFile(const Ref<Asset>& asset)
{
YAML::Emitter out;
out << YAML::BeginMap;
out << YAML::Key << "Asset" << YAML::Value << asset->Handle;
out << YAML::Key << "FilePath" << YAML::Value << asset->FilePath;
out << YAML::Key << "Type" << YAML::Value << (int)asset->Type;
out << YAML::EndMap;
std::ofstream fout(asset->FilePath + ".meta");
fout << out.c_str();
}
void AssetSerializer::UpdateMetaFile(const Ref<Asset>& asset)
{
YAML::Emitter out;
out << YAML::BeginMap;
out << YAML::Key << "Asset" << YAML::Value << asset->Handle;
out << YAML::Key << "FilePath" << YAML::Value << asset->FilePath;
out << YAML::Key << "Type" << YAML::Value << (int)asset->Type;
out << YAML::EndMap;
std::ofstream fout(asset->FilePath + ".meta");
fout << out.c_str();
}
}

View File

@ -0,0 +1,39 @@
//
// Created by Atdunbg on 2026/2/13.
//
#ifndef PRISM_ASSETSERIALIZER_H
#define PRISM_ASSETSERIALIZER_H
#include "Asset.h"
#include "Prism/Core/Ref.h"
namespace Prism
{
class PRISM_API AssetSerializer
{
public:
template<typename T>
static void SerializeAsset(const Ref<T>& asset)
{
static_assert(std::is_base_of<Asset, T>::value, "SerializeAsset only accepts types that inherit from Asset");
SerializeAsset(asset, asset->Type);
}
static Ref<Asset> LoadAssetInfo(const std::string& filepath, AssetHandle parentHandle, AssetType type);
static Ref<Asset> LoadAssetData(Ref<Asset>& asset);
private:
static void SerializeAsset(const Ref<Asset>& asset, AssetType type);
static Ref<Asset> DeserializeYAML(const Ref<Asset>& asset);
static void LoadMetaData(Ref<Asset>& asset);
static void CreateMetaFile(const Ref<Asset>& asset);
static void UpdateMetaFile(const Ref<Asset>& asset);
private:
friend class AssetsManager;
};
}
#endif //PRISM_ASSETSERIALIZER_H

View File

@ -0,0 +1,449 @@
//
// Created by Atdunbg on 2026/1/20.
//
#include "AssetsManager.h"
#include <filesystem>
#include <utility>
#include "Prism/Core/Log.h"
#include "Prism/Renderer/Mesh.h"
#include <yaml-cpp/yaml.h>
#include "AssetSerializer.h"
#include "Prism/Core/Application.h"
#include "Prism/Renderer/SceneEnvironment.h"
#include "Prism/Utilities/StringUtils.h"
namespace Prism
{
void AssetTypes::Init()
{
s_Types["scene"] = AssetType::Scene;
s_Types["pmx"] = AssetType::Mesh;
s_Types["fbx"] = AssetType::Mesh;
s_Types["dae"] = AssetType::Mesh;
s_Types["obj"] = AssetType::Mesh;
s_Types["png"] = AssetType::Texture;
s_Types["jpg"] = AssetType::Texture;
s_Types["jpeg"] = AssetType::Texture;
s_Types["bmp"] = AssetType::Texture;
s_Types["tga"] = AssetType::Texture;
s_Types["hdr"] = AssetType::EnvMap;
s_Types["blend"] = AssetType::Mesh;
s_Types["pmat"] = AssetType::Material;
s_Types["hpm"] = AssetType::PhysicsMaterial;
s_Types["wav"] = AssetType::Audio;
s_Types["ogg"] = AssetType::Audio;
s_Types["cs"] = AssetType::Script;
}
AssetType AssetTypes::GetAssetTypeFromExtension(const std::string& extension)
{
return s_Types.find(extension) != s_Types.end() ? s_Types[extension] : AssetType::Other;
}
std::map<std::string, AssetType> AssetTypes::s_Types;
AssetsManager::AssetsChangeEventFn AssetsManager::s_AssetsChangeCallback = nullptr;
std::unordered_map<AssetHandle, Ref<Asset>> AssetsManager::s_LoadedAssets;
void AssetsManager::Init()
{
FileSystem::SetChangeCallback(OnFileSystemChanged);
ReloadAssets();
}
void AssetsManager::SetAssetChangeCallback(const AssetsChangeEventFn& callback)
{
s_AssetsChangeCallback = callback;
}
void AssetsManager::Shutdown()
{
s_LoadedAssets.clear();
}
std::vector<Ref<Asset>> AssetsManager::GetAssetsInDirectory(AssetHandle directoryHandle)
{
std::vector<Ref<Asset>> results;
for (const auto& asset : s_LoadedAssets)
{
if (asset.second && asset.second->ParentDirectory == directoryHandle && asset.second->Handle != directoryHandle)
results.push_back(asset.second);
}
return results;
}
std::vector<Ref<Asset>> AssetsManager::SearchFiles(const std::string& query, const std::string& searchPath)
{
std::vector<Ref<Asset>> results;
if (!searchPath.empty())
{
for (const auto&[key, asset] : s_LoadedAssets)
{
if (asset->FileName.find(query) != std::string::npos && asset->FilePath.find(searchPath) != std::string::npos)
{
results.push_back(asset);
}
}
}
return results;
}
std::string AssetsManager::GetParentPath(const std::string& path)
{
return std::filesystem::path(path).parent_path().string();
}
bool AssetsManager::IsDirectory(const std::string& filepath)
{
for (auto&[handle, asset] : s_LoadedAssets)
{
if (asset->Type == AssetType::Directory && asset->FilePath == filepath)
return true;
}
return false;
}
AssetHandle AssetsManager::GetAssetHandleFromFilePath(const std::string& filepath)
{
const std::string normalizedPath = Utils::NormalizePath(filepath);
for (auto&[id, asset] : s_LoadedAssets)
{
if (asset->FilePath == normalizedPath)
return id;
const std::string normalizedPathToLower = Utils::StringToLower(normalizedPath);
const std::string assetFilePath = Utils::StringToLower(asset->FilePath);
if (assetFilePath == normalizedPathToLower)
return id;
}
return 0;
}
bool AssetsManager::IsAssetHandleValid(const AssetHandle& assetHandle)
{
return assetHandle != 0 && s_LoadedAssets.find(assetHandle) != s_LoadedAssets.end();
}
template <typename T>
Ref<T> AssetsManager::GetAsset(AssetHandle assetHandle, bool loadData)
{
PM_CORE_ASSERT(s_LoadedAssets.find(assetHandle) != s_LoadedAssets.end());
Ref<Asset> asset = s_LoadedAssets[assetHandle];
if (!asset->IsDataLoaded && loadData)
{
asset = AssetSerializer::LoadAssetData(asset);
s_LoadedAssets[assetHandle] = asset;
}
return asset.As<T>();
}
template PRISM_API Ref<Asset> AssetsManager::GetAsset(AssetHandle, bool);
template PRISM_API Ref<Mesh> AssetsManager::GetAsset(AssetHandle, bool);
template PRISM_API Ref<PhysicsMaterialAsset> AssetsManager::GetAsset(AssetHandle, bool);
template PRISM_API Ref<Environment> AssetsManager::GetAsset(AssetHandle, bool);
template PRISM_API Ref<Directory> AssetsManager::GetAsset(AssetHandle, bool);
template PRISM_API Ref<Texture2D> AssetsManager::GetAsset(AssetHandle, bool);
template <typename T>
Ref<T> AssetsManager::GetAsset(const std::string& filepath, const bool loadData)
{
return GetAsset<T>(GetAssetHandleFromFilePath(filepath), loadData);
}
template PRISM_API Ref<Asset> AssetsManager::GetAsset(const std::string&, bool);
template PRISM_API Ref<Mesh> AssetsManager::GetAsset(const std::string&, bool);
template PRISM_API Ref<PhysicsMaterialAsset> AssetsManager::GetAsset(const std::string&, bool);
template PRISM_API Ref<PBRMaterialAsset> AssetsManager::GetAsset(const std::string&, bool);
template PRISM_API Ref<Environment> AssetsManager::GetAsset(const std::string&, bool);
template PRISM_API Ref<Directory> AssetsManager::GetAsset(const std::string&, bool);
template PRISM_API Ref<Texture2D> AssetsManager::GetAsset(const std::string&, bool);
template <typename T>
Ref<T> AssetsManager::TryGetAsset(const std::string& filepath, const bool loadData)
{
AssetHandle assetHandle = GetAssetHandleFromFilePath(filepath);
if (!assetHandle) return Ref<T>();
return GetAsset<T>(assetHandle, loadData);
}
template PRISM_API Ref<Asset> AssetsManager::TryGetAsset(const std::string&, bool);
template PRISM_API Ref<Mesh> AssetsManager::TryGetAsset(const std::string&, bool);
template PRISM_API Ref<PhysicsMaterialAsset> AssetsManager::TryGetAsset(const std::string&, bool);
template PRISM_API Ref<PBRMaterialAsset> AssetsManager::TryGetAsset(const std::string&, bool);
template PRISM_API Ref<Environment> AssetsManager::TryGetAsset(const std::string&, bool);
template PRISM_API Ref<Directory> AssetsManager::TryGetAsset(const std::string&, bool);
template PRISM_API Ref<Texture2D> AssetsManager::TryGetAsset(const std::string&, bool);
void AssetsManager::RemoveAsset(AssetHandle assetHandle)
{
Ref<Asset> asset = s_LoadedAssets[assetHandle];
if (asset->Type == AssetType::Directory)
{
if (IsAssetHandleValid(asset->ParentDirectory))
{
auto& childList = s_LoadedAssets[asset->ParentDirectory].As<Directory>()->ChildDirectories;
childList.erase(std::remove(childList.begin(), childList.end(), assetHandle), childList.end());
}
for (const auto child : asset.As<Directory>()->ChildDirectories)
RemoveAsset(child);
for (auto it = s_LoadedAssets.begin(); it != s_LoadedAssets.end(); )
{
if (it->second->ParentDirectory != assetHandle)
{
++it;
continue;
}
it = s_LoadedAssets.erase(it);
}
}
s_LoadedAssets.erase(assetHandle);
}
template<typename T, typename... Args>
Ref<T> AssetsManager::CreateAsset(const std::string& filename, AssetType type, AssetHandle directoryHandle, Args&&... args)
{
static_assert(std::is_base_of_v<Asset, T>, "CreateAsset only works for types derived from Asset");
const auto& directory = GetAsset<Directory>(directoryHandle);
Ref<T> asset = Ref<T>::Create(std::forward<Args>(args)...);
asset->Type = type;
asset->FilePath = directory->FilePath + "/" + filename;
asset->FileName = Utils::RemoveExtension(Utils::GetFilename(asset->FilePath));
asset->Extension = Utils::GetFilename(filename);
asset->ParentDirectory = directoryHandle;
asset->Handle = std::hash<std::string>()(asset->FilePath + std::to_string(Application::Get().GetTime()));
asset->IsDataLoaded = true;
s_LoadedAssets[asset->Handle] = asset;
AssetSerializer::SerializeAsset(asset);
AssetSerializer::CreateMetaFile(asset);
return asset;
}
template PRISM_API Ref<PhysicsMaterialAsset> AssetsManager::CreateAsset<PhysicsMaterialAsset, float, float, float>(const std::string&, AssetType, AssetHandle, float&&, float&&, float&&);
template PRISM_API Ref<PhysicsMaterialAsset> AssetsManager::CreateAsset<PhysicsMaterialAsset>( const std::string&, AssetType, AssetHandle);
template PRISM_API Ref<PBRMaterialAsset> AssetsManager::CreateAsset<PBRMaterialAsset>( const std::string&, AssetType, AssetHandle);
bool AssetsManager::IsAssetType(const AssetHandle assetHandle, const AssetType type)
{
return s_LoadedAssets.find(assetHandle) != s_LoadedAssets.end() && s_LoadedAssets[assetHandle]->Type == type;
}
std::string AssetsManager::StripExtras(const std::string& filename)
{
std::vector<std::string> out;
size_t start;
size_t end = 0;
while ((start = filename.find_first_not_of('.', end)) != std::string::npos)
{
end = filename.find('.', start);
out.push_back(filename.substr(start, end - start));
}
if (out[0].length() >= 10)
{
auto cutFilename = out[0].substr(0, 9) + "...";
return cutFilename;
}
const auto filenameLength = out[0].length();
const auto paddingToAdd = 9 - filenameLength;
std::string newFileName;
for (int i = 0; i <= paddingToAdd; i++)
{
newFileName += " ";
}
newFileName += out[0];
return newFileName;
}
Ref<Asset> AssetsManager::ImportAsset(const std::string& filepath, AssetHandle parentHandle)
{
const std::string extension = Utils::GetExtension(filepath);
if (extension == "meta")
return {};
const AssetType type = AssetTypes::GetAssetTypeFromExtension(extension);
Ref<Asset> asset = AssetSerializer::LoadAssetInfo(filepath, parentHandle, type);
if (s_LoadedAssets.find(asset->Handle) != s_LoadedAssets.end())
{
if (s_LoadedAssets[asset->Handle]->IsDataLoaded)
{
asset = AssetSerializer::LoadAssetData(asset);
}
}
s_LoadedAssets[asset->Handle] = asset;
return asset;
}
Ref<Asset> AssetsManager::ProcessDirectory(const std::string& directoryPath, AssetHandle parentHandle)
{
Ref<Directory> dirInfo = AssetSerializer::LoadAssetInfo(directoryPath, parentHandle, AssetType::Directory).As<Directory>();
s_LoadedAssets[dirInfo->Handle] = dirInfo;
if (IsAssetHandleValid(parentHandle))
s_LoadedAssets[parentHandle].As<Directory>()->ChildDirectories.push_back(dirInfo->Handle);
for (const auto& entry : std::filesystem::directory_iterator(directoryPath))
{
if (entry.is_directory())
ProcessDirectory(entry.path().string(), dirInfo->Handle);
else
{
Ref<Asset> asset = ImportAsset(entry.path().string(), dirInfo->Handle);
}
}
return dirInfo;
}
Ref<Asset> AssetsManager::ReloadAssets()
{
return ProcessDirectory("assets", 0);
}
void AssetsManager::OnFileSystemChanged(FileSystemChangedEvent e)
{
e.NewName = Utils::RemoveExtension(e.NewName);
e.OldName = Utils::RemoveExtension(e.OldName);
const AssetHandle parentHandle = FindParentHandle(e.FilePath);
if (std::filesystem::path(e.FilePath).extension() != ".meta")
{
switch (e.Action)
{
case FileSystemAction::Added:
{
if (e.IsDirectory)
ProcessDirectory(e.FilePath, parentHandle);
else
{
AssetHandle assetHandle = GetAssetHandleFromFilePath(e.FilePath);
if (!assetHandle)
ImportAsset(e.FilePath, parentHandle);
}
}
break;
case FileSystemAction::Modified:
{
if (!e.IsDirectory)
{
Ref<Asset> asset = ImportAsset(e.FilePath, parentHandle);
}
}
break;
case FileSystemAction::Rename:
{
Ref<Asset> asset = nullptr;
{
const std::string oldFilePath = Utils::ReplaceFilePathName(e.FilePath, e.OldName);
for (auto& [handle, existingAsset] : s_LoadedAssets)
{
if (existingAsset->FilePath == oldFilePath)
{
asset = existingAsset;
break;
}
}
}
if (asset)
{
std::string extension = Utils::GetExtension(asset->FilePath);
std::string oldMetaPath = asset->FilePath + ".meta";
FileSystem::Rename(oldMetaPath, e.NewName + "." + extension);
asset->FilePath = e.FilePath;
asset->FileName = e.NewName;
asset->Extension = extension;
AssetSerializer::UpdateMetaFile(asset);
}
}
break;
case FileSystemAction::Delete:
{
for (auto it = s_LoadedAssets.begin(); it != s_LoadedAssets.end(); it++)
{
std::string filePath = Utils::NormalizePath(it->second->FilePath);
std::string eFilePath = Utils::NormalizePath(e.FilePath);
if (filePath != eFilePath)
continue;
RemoveAsset(it->first);
FileSystem::PrismDeleteFile(eFilePath + ".meta");
break;
}
}
break;
}
}
if (s_AssetsChangeCallback)
s_AssetsChangeCallback();
}
AssetHandle AssetsManager::FindParentHandleInChildren(Ref<Directory>& dir, const std::string& dirName)
{
if (dir->FileName == dirName)
return dir->Handle;
for (const AssetHandle& childHandle : dir->ChildDirectories)
{
Ref<Directory> child = GetAsset<Directory>(childHandle);
AssetHandle dirHandle = FindParentHandleInChildren(child, dirName);
if (IsAssetHandleValid(dirHandle))
return dirHandle;
}
return 0;
}
AssetHandle AssetsManager::FindParentHandle(const std::string& filepath)
{
const std::vector<std::string> parts = Utils::SplitString(filepath, "/\\");
const std::string& parentFolder = parts[parts.size() - 2];
Ref<Directory> assetsDirectory = GetAsset<Directory>(GetAssetHandleFromFilePath("assets"));
return FindParentHandleInChildren(assetsDirectory, parentFolder);
}
}

View File

@ -0,0 +1,93 @@
//
// Created by Atdunbg on 2026/1/20.
//
#ifndef PRISM_ASSETSMANAGER_H
#define PRISM_ASSETSMANAGER_H
#include <map>
#include "Asset.h"
#include "Prism/Utilities/FileSystem.h"
#include "Prism/Core/Ref.h"
namespace Prism
{
class AssetTypes
{
public:
static void Init();
static AssetType GetAssetTypeFromExtension(const std::string& extension);
private:
static std::map<std::string, AssetType> s_Types;
};
class PRISM_API AssetsManager
{
public:
using AssetsChangeEventFn = std::function<void()>;
public:
static void Init();
static void SetAssetChangeCallback(const AssetsChangeEventFn& callback);
static void Shutdown();
static std::vector<Ref<Asset>> GetAssetsInDirectory(AssetHandle directoryHandle);
static std::vector<Ref<Asset>> SearchFiles(const std::string& query, const std::string& searchPath);
static std::string GetParentPath(const std::string& path);
static bool IsDirectory(const std::string& filepath);
static AssetHandle GetAssetHandleFromFilePath(const std::string& filepath);
static bool IsAssetHandleValid(const AssetHandle& assetHandle);
static Ref<PhysicsMaterialAsset> CreateAssetPhysicsMaterial(const std::string& filename, AssetType type, const AssetHandle& directoryHandle, float v1, float v2, float v3);
static void RemoveAsset(AssetHandle assetHandle);
template<typename T, typename... Args>
static Ref<T> CreateAsset(const std::string& filename, AssetType type, AssetHandle directoryHandle, Args&&... args);
template<typename T>
static Ref<T> GetAsset(AssetHandle assetHandle, bool loadData = true);
template<typename T>
static Ref<T> GetAsset(const std::string& filepath, bool loadData = true);
template <class T>
static Ref<T> TryGetAsset(const std::string& filepath, bool loadData = true);
static bool IsAssetType(AssetHandle assetHandle, AssetType type);
static std::string StripExtras(const std::string& filename);
private:
static Ref<Asset> ImportAsset(const std::string& filepath, AssetHandle parentHandle);
static Ref<Asset> ProcessDirectory(const std::string& directoryPath, AssetHandle parentHandle);
/**
* this will load from projectPath all assets, and return the root Asset Ref
* @param projectPath assets path
* @return the root asset Ref
*/
static Ref<Asset> ReloadAssets();
static void OnFileSystemChanged(FileSystemChangedEvent e);
static AssetHandle FindParentHandleInChildren(Ref<Directory>& dir, const std::string& dirName);
static AssetHandle FindParentHandle(const std::string& filepath);
private:
static std::unordered_map<AssetHandle, Ref<Asset>> s_LoadedAssets;
static AssetsChangeEventFn s_AssetsChangeCallback;
};
}
#endif //PRISM_ASSETSMANAGER_H

Some files were not shown because too many files have changed in this diff Show More