refactor: rename ui_editor to editor for consistency

This commit is contained in:
2026-03-24 16:23:04 +08:00
parent d575532966
commit ac5c98584a
969 changed files with 88954 additions and 0 deletions

View File

@@ -0,0 +1,458 @@
# 编辑器与运行时分层架构设计
## 1. 背景与目的
### 1.1 参考案例Unity 的 Editor/Player 分离
Unity 是目前最成熟的游戏引擎之一其架构设计经过多年迭代验证。Unity 的核心设计理念是**将游戏开发工具(编辑器)与游戏运行载体(播放器)清晰分离**,同时共享同一套引擎核心。
Unity 的整体架构如下:
```
┌─────────────────────────────────────────────────────────────┐
│ Unity Hub │
│ (项目浏览器、启动器) │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Unity Editor │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │Hierarchy │ │Inspector │ │ Scene │ │ Project │ │
│ │ Panel │ │ Panel │ │ View │ │ Browser │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Unity Engine Core (C++) │ │
│ │ Scene System, ECS, Renderer, Physics, Audio, etc. │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ (Build)
┌─────────────────────────────────────────────────────────────┐
│ Unity Player │
│ (独立可执行文件,不含编辑器组件) │
│ - IL2CPP 虚拟机运行 C# 脚本 │
│ - 裁剪过的引擎子集 │
└─────────────────────────────────────────────────────────────┘
```
这一架构的核心思想是:
- **Editor 是开发工具**,面向游戏开发者,提供场景编辑、参数调整、资源管理等功能
- **Player 是运行载体**,面向最终用户,运行游戏逻辑,不包含任何编辑器 UI
- **Engine Core 是共享层**Editor 和 Player 都依赖同一套核心逻辑
### 1.2 分层架构的目标与收益
采用分层架构设计编辑器与运行时,带来以下收益:
| 目标 | 说明 |
|------|------|
| **职责分离** | 编辑器和运行时各司其职,代码边界清晰 |
| **核心复用** | 场景系统、渲染器、物理等核心逻辑只需维护一份 |
| **按需发布** | 发行游戏时只需包含运行时,体积更小 |
| **独立迭代** | 编辑器改进不影响运行时稳定性 |
| **团队协作** | 程序员专注核心,设计师专注编辑器操作 |
---
## 2. 整体架构设计
### 2.1 分层原则
编辑器与运行时的分层应遵循以下原则:
1. **核心不可依赖编辑器**Engine Core 不应包含任何 UI 相关的代码,确保可以在无界面环境下运行
2. **运行时独立最小化**:运行时只包含游戏运行必需的组件,不包含编辑器特有功能
3. **接口抽象分离**:通过接口隔离变化,便于未来替换实现
4. **数据驱动**:场景、配置等数据与代码逻辑分离
### 2.2 模块划分
整体架构分为五个主要模块:
| 模块 | 英文名 | 说明 |
|------|--------|------|
| 引擎核心 | Engine Core | 游戏运行时逻辑的核心库 |
| 编辑器应用 | Editor Application | 游戏开发工具 |
| 运行时应用 | Runtime Application | 游戏运行载体 |
| 启动器 | Launcher | 项目浏览器与启动器 |
| 脚本系统 | Script System | 游戏逻辑脚本 |
### 2.3 整体架构图
```
┌─────────────────────────────┐
│ <Launcher> │
│ (启动器) │
│ 扫描项目,启动应用 │
└─────────────┬───────────────┘
┌──────────────────┴──────────────────┐
│ │
┌─────────▼─────────┐ ┌──────────▼──────────┐
│ <Editor>.exe │ │ <Runtime>.exe │
│ (编辑器) │ │ (运行时) │
│ │ │ │
│ ┌─────────────┐ │ │ ┌───────────────┐ │
│ │ <Editor>Layer │ │ │ │ <Runtime>Layer │ │
│ │ (UI面板) │ │ │ │ (游戏逻辑) │ │
│ └──────┬──────┘ │ │ └───────┬───────┘ │
└─────────┼─────────┘ └──────────┼──────────┘
│ │
┌──────────┴───────────────────────────────────┴──────────┐
│ │
│ <Core> │
│ (引擎核心静态库) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Scene │ │ Renderer │ │ Physics │ │ Script │ │
│ │ (ECS) │ │ │ │ 2D/3D │ │ (C# CLR) │ │
│ └──────────┘ └──────────┘ └──────────┘ └────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────────┐ │
│ │ Asset │ │ Event │ │ Project/Settings │ │
│ │ Manager │ │ System │ │ │ │
│ └──────────┘ └──────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.4 模块间依赖关系
```
<Launcher> ──启动──> <Editor> / <Runtime>
<Editor> ──链接──> Engine Core
<Runtime> ──链接──> Engine Core
Engine Core不依赖任何其他模块
├── Scene System
├── Renderer
├── Physics
├── Script Engine
├── Asset Manager
└── Event System
<Script>C#项目)──编译输出──> .dll 供 <Core> 加载
```
依赖规则:
- **Engine Core 是底层**,不被任何其他模块依赖(它依赖谁?)
- **Editor 和 Runtime 都依赖 Engine Core**,不直接依赖彼此
- **Launcher 只依赖文件系统**,不依赖 Engine Core
---
## 3. 核心模块设计
### 3.1 Engine Core引擎核心
#### 职责
引擎核心是整个架构的基石,负责游戏运行时的一切逻辑:
- **场景管理**Entity-Component-System 架构,场景的创建、销毁、序列化
- **渲染系统**:渲染管线、材质、光照、后处理
- **物理系统**2D/3D 物理模拟
- **脚本系统**:脚本引擎、脚本绑定
- **资源管理**:资源加载、缓存、卸载
- **事件系统**:事件分发与响应
- **输入系统**:输入事件捕获与分发
- **音频系统**:音频播放、音效处理
#### 边界
Engine Core **不包含**
- 任何 UI 逻辑ImGui 代码、面板布局等)
- 编辑器特有功能场景大纲、Gizmo 选择等)
- 启动器相关逻辑
#### 设计要点
Engine Core 编译为**静态库**`.lib`),被 Editor 和 Runtime 两个可执行文件链接。这种方式确保:
- 代码真正共享,而非运行时 DLL 加载
- 两个应用使用完全相同的核心逻辑版本
---
### 3.2 Editor Application编辑器应用
#### 职责
编辑器是游戏开发者的主要工具,提供:
- **场景编辑**:场景视图、层级面板、检视面板
- **资源管理**:资源导入、浏览、配置
- **游戏预览**Play/Simulate 模式切换
- **项目配置**:项目设置、构建选项
#### 与 Engine Core 的关系
编辑器**链接** Engine Core并在此基础上添加编辑器的 UI 层:
```
Editor Application
├── Editor UI LayerImGui 面板)
│ │
│ ├── ViewportPanel
│ ├── SceneHierarchyPanel
│ ├── InspectorPanel
│ ├── ContentBrowserPanel
│ └── MenuBarPanel
└── Engine Core场景管理、渲染、物理等
```
#### 核心机制Scene 复制
编辑器在 Play/Simulate 时,会将当前编辑的场景**完整复制**一份交给运行时:
```
m_activeScene = Scene::copy(m_editorScene);
m_activeScene->onRuntimeStart();
```
这样设计的好处是:
- 编辑器场景保持不变,方便运行时修改调试
- 运行时可以自由修改场景对象,不影响编辑器状态
- Stop 时只需切回编辑器场景即可
---
### 3.3 Runtime Application运行时应用
#### 职责
运行时是游戏的运行载体,面向最终用户:
- **加载游戏项目**:读取项目配置、启动场景
- **运行游戏循环**:持续执行 Scene 的更新逻辑
- **执行脚本**:运行 C# 脚本游戏逻辑
- **渲染画面**:调用渲染器输出图像
#### 与 Engine Core 的关系
运行时同样**链接** Engine Core但不包含任何编辑器 UI
```
Runtime Application
├── Runtime Layer游戏入口、更新循环
└── Engine Core与编辑器相同
```
#### 特点
- **无 UI**:运行时没有任何编辑器界面
- **最小化**:只包含游戏运行必需的组件
- **独立发行**:可打包为独立的游戏可执行文件
---
### 3.4 Launcher启动器
#### 职责
启动器是用户进入游戏的第一个界面:
- **项目浏览**:扫描并显示本地的游戏项目列表
- **项目管理**:创建、打开、删除项目
- **启动选择**:启动编辑器或直接运行游戏
#### 设计要点
启动器**不链接** Engine Core而是作为一个独立的小型程序
```
Launcher Application
├── 项目浏览器 UI
├── 项目文件系统扫描
└── 进程启动(调用 <Editor>.exe 或 <Runtime>.exe
```
这种设计使得:
- 启动器可以独立编译、独立发布
- 即使引擎编译失败,启动器也可能正常工作
- 未来可以单独改进启动器的 UI/UX
---
### 3.5 Script System脚本系统
#### 定位
脚本系统是连接游戏逻辑与引擎的桥梁。游戏开发者编写脚本(当前采用 C#),脚本调用引擎提供的 API。
#### 隔离方式
脚本系统采用**独立编译 + 运行时加载**的模式:
```
<Script>C# 项目)
├── 游戏脚本源码(.cs
└── 编译输出 ──> GameScripts.dll
<Core>(运行时加载)
└── Mono Runtime 执行
```
#### 设计要点
- **脚本独立编译**:不与引擎一起编译,便于快速迭代
- **运行时加载**Engine Core 通过 Mono Runtime 加载编译好的脚本
- **引擎 API 绑定**:引擎提供 C++ 函数,脚本通过 InternalCall 调用
---
## 4. 编辑器与运行时交互机制
### 4.1 Scene 复制机制
编辑器维护两种 Scene
| Scene | 用途 | 修改权限 |
|-------|------|----------|
| `m_editorScene` | 编辑器中显示的场景 | 编辑器随时可改 |
| `m_runtimeScene` | Play/Simulate 时复制的场景 | 运行时逻辑可改 |
复制过程:
```cpp
std::shared_ptr<Scene> Scene::copy(std::shared_ptr<Scene> other) {
std::shared_ptr<Scene> newScene = std::make_shared<Scene>();
// 1. 复制所有 Entity保留 UUID
auto idView = srcSceneRegistry.view<IDComponent>();
for (auto e : idView) {
UUID uuid = srcSceneRegistry.get<IDComponent>(e).ID;
Entity newEntity = newScene->createEntityWithUUID(uuid, name);
enttMap[uuid] = (entt::entity)newEntity;
}
// 2. 复制所有 Component
copyComponent(AllComponents{}, dstSceneRegistry, srcSceneRegistry, enttMap);
return newScene;
}
```
复制确保:
- Entity UUID 不变,便于调试和序列化
- 所有 Component 数据完整复制
- 编辑器场景不受影响
---
### 4.2 状态机Edit / Play / Simulate
编辑器有三种状态:
| 状态 | Scene | 物理 | 脚本 | 相机 | 层级可编辑 |
|------|-------|------|------|------|------------|
| **Edit** | `m_editorScene` | ❌ | ❌ | EditorCamera | ✅ |
| **Play** | `m_runtimeScene` | ✅ | ✅ | MainCamera | ❌ |
| **Simulate** | `m_runtimeScene` | ✅ | ✅ | EditorCamera | ❌ |
状态转换:
```
[Edit Mode]
┌─────────┼─────────┐
│ │ │
[Play] [Simulate] [New/Open Scene]
│ │ │
└────┬────┴────┬────┘
│ │
[Stop] ←────┘
[Edit Mode](切回编辑器场景)
```
**Edit → Play**
1. `m_editorScene` 深复制为 `m_runtimeScene`
2. `m_runtimeScene->onRuntimeStart()` 初始化运行时
3. 层级面板禁用编辑
**Edit → Simulate**
1. 与 Play 相同,但使用 EditorCamera
2. 物理运行,方便调试物理效果
**Stop**
1. 运行时场景 `onRuntimeStop()` 清理
2. 切回 `m_editorScene`
3. 层级面板恢复编辑
---
### 4.3 资源管理差异
编辑器与运行时对资源的访问权限不同:
| 操作 | EditorAssetManager | RuntimeAssetManager |
|------|-------------------|---------------------|
| 加载资源 | ✅ | ✅ |
| 导入资源 | ✅ | ❌ |
| 创建资源 | ✅ | ❌ |
| 删除资源 | ✅ | ❌ |
这种设计确保:
- **运行时安全**:玩家无法导入新资源,防止作弊
- **编辑器便捷**:开发者可以直接在编辑器内导入、管理资源
- **职责清晰**:资源导入是开发流程的一部分,不是运行时必需的
实现上,两个 AssetManager 继承自同一基类:
```cpp
class AssetManagerBase { ... };
class EditorAssetManager : public AssetManagerBase {
void importAsset(const std::filesystem::path&) { /* 实现 */ }
};
class RuntimeAssetManager : public AssetManagerBase {
void importAsset(const std::filesystem::path&) = delete; // 禁止
};
```
---
## 5. 总结
### 5.1 分层架构的核心价值
| 价值 | 说明 |
|------|------|
| **职责清晰** | 编辑器管开发,运行时管执行,各司其职 |
| **代码复用** | Engine Core 被两方共享,维护一份代码 |
| **灵活发布** | 按需构建,只发行运行时 |
| **易于测试** | 核心逻辑可脱离编辑器独立测试 |
### 5.2 关键设计决策
| 决策 | 选择 | 理由 |
|------|------|------|
| 核心库形式 | 静态库 | 编译时链接,确保版本一致 |
| 脚本隔离 | 独立 C# 项目 | 快速编译迭代,与引擎解耦 |
| 场景复制 | 深复制 | 确保编辑/运行时隔离 |
| 状态机 | Edit/Play/Simulate 三态 | 支持物理调试 |
### 5.3 架构优势
这种分层架构让引擎具备:
- 类似 Unity 的开发体验
- 可独立迭代的模块
- 可裁剪的运行时体积
- 清晰的代码边界
---
*文档版本1.0*
*参考Unity Editor/Player Architecture*

15
参考/Fermion/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
.vscode
build
bin/*
!bin/
!bin/imgui.ini
*.log
*.ini
.vs
out
cmake-build-debug
cmake-build-release
.idea
docs
.cache
.claude

40
参考/Fermion/.gitmodules vendored Normal file
View File

@@ -0,0 +1,40 @@
[submodule "Fermion/external/spdlog"]
path = Fermion/external/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "Fermion/external/entt"]
path = Fermion/external/entt
url = https://github.com/skypjack/entt.git
[submodule "Fermion/external/glm"]
path = Fermion/external/glm
url = https://github.com/g-truc/glm.git
[submodule "Fermion/external/imgui"]
path = Fermion/external/imgui
url = https://github.com/ocornut/imgui.git
[submodule "Fermion/external/GLFW"]
path = Fermion/external/GLFW
url = https://github.com/glfw/glfw.git
[submodule "Fermion/external/yaml-cpp"]
path = Fermion/external/yaml-cpp
url = https://github.com/jbeder/yaml-cpp.git
[submodule "Fermion/external/ImGuizmo"]
path = Fermion/external/ImGuizmo
url = https://github.com/CedricGuillemet/ImGuizmo.git
[submodule "Fermion/external/box2d"]
path = Fermion/external/box2d
url = https://github.com/erincatto/box2d.git
[submodule "Fermion/external/msdf-atlas-gen"]
path = Fermion/external/msdf-atlas-gen
url = https://github.com/Chlumsky/msdf-atlas-gen.git
[submodule "Fermion/external/freetype"]
path = Fermion/external/freetype
url = https://github.com/freetype/freetype.git
[submodule "Fermion/external/assimp"]
path = Fermion/external/assimp
url = https://github.com/assimp/assimp.git
[submodule "Fermion/external/JoltPhysics"]
path = Fermion/external/JoltPhysics
url = https://github.com/jrouwe/JoltPhysics.git
[submodule "Fermion/external/ImguiNodeEditor"]
path = Fermion/external/ImguiNodeEditor
url = https://github.com/thedmd/imgui-node-editor.git
branch = develop

View File

@@ -0,0 +1,35 @@
set(BOSON_SOUCES
${CMAKE_CURRENT_LIST_DIR}/src/BosonEditor.cpp
${CMAKE_CURRENT_LIST_DIR}/src/BosonLayer.cpp
${CMAKE_CURRENT_LIST_DIR}/src/BosonLayer_Project.cpp
${CMAKE_CURRENT_LIST_DIR}/src/BosonLayer_Scene.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/SceneHierarchyPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/ContentBrowserPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/AssetManagerPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/InspectorPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/MenuBarPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/MaterialEditorPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/TextureConfigPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/ViewportPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/SettingsPanel.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Panels/OverlayRenderPanel.cpp
)
add_executable(BosonEditor ${BOSON_SOUCES})
set_target_properties(BosonEditor PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR}
)
target_include_directories(BosonEditor PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_SOURCE_DIR}/Fermion/external/ImGuizmo
${CMAKE_SOURCE_DIR}/HilbertSpace/)
target_link_libraries(BosonEditor PRIVATE engine)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,120 @@
// ============================================================================
// BRDF积分查找表生成
// ============================================================================
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoords;
out vec2 v_TexCoords;
void main() {
v_TexCoords = a_TexCoords;
gl_Position = vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
out vec2 FragColor;
in vec2 v_TexCoords;
const float PI = 3.14159265359;
// 优化的 pow5
float pow5(float x) {
float x2 = x * x;
return x2 * x2 * x;
}
// 低差异序列采样
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;
}
vec2 Hammersley(uint i, uint N) {
return vec2(float(i)/float(N), RadicalInverse_VdC(i));
}
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) {
float a = roughness * roughness;
float phi = 2.0 * PI * Xi.x;
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
vec3 H;
H.x = cos(phi) * sinTheta;
H.y = sin(phi) * sinTheta;
H.z = cosTheta;
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangent = normalize(cross(up, N));
vec3 bitangent = cross(N, tangent);
vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return normalize(sampleVec);
}
float GeometrySchlickGGX(float NdotV, float roughness) {
float a = roughness;
float k = (a * a) / 2.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;
}
vec2 IntegrateBRDF(float NdotV, float roughness) {
vec3 V;
V.x = sqrt(1.0 - NdotV*NdotV);
V.y = 0.0;
V.z = NdotV;
float A = 0.0;
float B = 0.0;
vec3 N = vec3(0.0, 0.0, 1.0);
const uint SAMPLE_COUNT = 1024u;
for(uint i = 0u; i < SAMPLE_COUNT; ++i) {
vec2 Xi = Hammersley(i, SAMPLE_COUNT);
vec3 H = ImportanceSampleGGX(Xi, N, roughness);
vec3 L = normalize(2.0 * dot(V, H) * H - V);
float NdotL = max(L.z, 0.0);
float NdotH = max(H.z, 0.0);
float VdotH = max(dot(V, H), 0.0);
if(NdotL > 0.0) {
float G = GeometrySmith(N, V, L, roughness);
float G_Vis = (G * VdotH) / max(NdotH * NdotV, 0.001);
float Fc = pow5(1.0 - VdotH);
A += (1.0 - Fc) * G_Vis;
B += Fc * G_Vis;
}
}
A /= float(SAMPLE_COUNT);
B /= float(SAMPLE_COUNT);
return vec2(A, B);
}
void main() {
vec2 integratedBRDF = IntegrateBRDF(v_TexCoords.x, v_TexCoords.y);
FragColor = integratedBRDF;
}

View File

@@ -0,0 +1,76 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_WorldPosition;
layout(location = 1) in vec3 a_LocalPosition;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in float a_Thickness;
layout(location = 4) in float a_Fade;
layout(location = 5) in int a_ObjectID;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
struct VertexOutput
{
vec3 LocalPosition;
vec4 Color;
float Thickness;
float Fade;
};
layout (location = 0) out VertexOutput Output;
layout (location = 4) out flat int v_ObjectID;
void main()
{
Output.LocalPosition = a_LocalPosition;
Output.Color = a_Color;
Output.Thickness = a_Thickness;
Output.Fade = a_Fade;
v_ObjectID = a_ObjectID;
gl_Position = u_ViewProjection * vec4(a_WorldPosition, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
struct VertexOutput
{
vec3 LocalPosition;
vec4 Color;
float Thickness;
float Fade;
};
layout (location = 0) in VertexOutput Input;
layout (location = 4) in flat int v_ObjectID;
void main()
{
// Calculate distance and fill circle with white
float distance = 1.0 - length(Input.LocalPosition);
float circle = smoothstep(0.0, Input.Fade, distance);
circle *= smoothstep(Input.Thickness + Input.Fade, Input.Thickness, distance);
if (circle == 0.0)
discard;
// Set output color
o_Color = Input.Color;
o_Color.a *= circle;
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,32 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in int a_ObjectID;
uniform mat4 u_ViewProjection;
out vec3 v_Normal;
out vec4 v_Color;
flat out int v_ObjectID;
void main() {
v_Normal = a_Normal;
v_Color = a_Color;
v_ObjectID = a_ObjectID;
gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
in vec3 v_Normal;
in vec4 v_Color;
flat in int v_ObjectID;
out vec4 color;
void main() {
vec3 lightDir = normalize(vec3(0.5, 1.0, 0.3));
float diff = max(dot(normalize(v_Normal), lightDir), 0.2);
color = vec4(v_Color.rgb * diff, v_Color.a);
}

View File

@@ -0,0 +1,357 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoords;
out vec2 v_TexCoords;
void main()
{
v_TexCoords = a_TexCoords;
gl_Position = vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
in vec2 v_TexCoords;
const float PI = 3.14159265359;
const float F0_NON_METAL = 0.04;
const float MIN_ROUGHNESS = 0.045;
#define MAX_DIR_LIGHTS 4
#define MAX_POINT_LIGHTS 16
#define MAX_SPOT_LIGHTS 16
// 优化的数学函数
float pow5(float x) {
float x2 = x * x;
return x2 * x2 * x;
}
float sq(float x) {
return x * x;
}
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
vec3 saturate(vec3 x) {
return clamp(x, 0.0, 1.0);
}
struct DirectionalLight {
vec3 direction;
vec3 color;
float intensity;
};
struct PointLight {
vec3 position;
vec3 color;
float intensity;
float range;
};
struct SpotLight {
vec3 position;
vec3 direction;
vec3 color;
float intensity;
float range;
float innerConeAngle;
float outerConeAngle;
};
uniform int u_DirLightCount;
uniform DirectionalLight u_DirLights[MAX_DIR_LIGHTS];
uniform int u_PointLightCount;
uniform PointLight u_PointLights[MAX_POINT_LIGHTS];
uniform int u_SpotLightCount;
uniform SpotLight u_SpotLights[MAX_SPOT_LIGHTS];
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
uniform sampler2D u_GBufferAlbedo;
uniform sampler2D u_GBufferNormal;
uniform sampler2D u_GBufferMaterial;
uniform sampler2D u_GBufferEmissive;
uniform sampler2D u_GBufferDepth;
uniform mat4 u_InverseViewProjection;
// Shadow mapping
uniform sampler2D u_ShadowMap;
// IBL
uniform bool u_UseIBL;
uniform samplerCube u_IrradianceMap;
uniform samplerCube u_PrefilterMap;
uniform sampler2D u_BRDFLT;
uniform float u_PrefilterMaxLOD;
// ============================================================================
// BRDF 函数
// ============================================================================
float D_GGX(float roughness, float NoH) {
float a = roughness * roughness;
float a2 = a * a;
float NoH2 = NoH * NoH;
float nom = a2;
float denom = (NoH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / max(denom, 1e-7);
}
float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
float a = roughness * roughness;
float a2 = a * a;
float lambdaV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
float lambdaL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
return 0.5 / max(lambdaV + lambdaL, 1e-5);
}
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow5(saturate(1.0 - cosTheta));
}
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow5(saturate(1.0 - cosTheta));
}
float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
float lightScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoL);
float viewScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoV);
return lightScatter * viewScatter * (1.0 / PI);
}
vec3 ACESFilm(vec3 x) {
float a = 2.51;
float b = 0.03;
float c = 2.43;
float d = 0.59;
float e = 0.14;
return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
}
float computeSpecularOcclusion(float NoV, float ao, float roughness) {
return clamp(pow(NoV + ao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ao, 0.0, 1.0);
}
float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) {
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0)
return 0.0;
float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1);
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 += projCoords.z - bias > pcfDepth ? 1.0 : 0.0;
sampleCount++;
}
}
shadow /= float(sampleCount);
return shadow;
}
vec3 reconstructWorldPosition(vec2 uv, float depth) {
vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 world = u_InverseViewProjection * clip;
return world.xyz / max(world.w, 0.0001);
}
// ============================================================================
// 综合光照评估
// ============================================================================
vec3 evaluateLighting(vec3 N, vec3 V, vec3 L, vec3 albedo, float roughness,
float metallic, vec3 F0, float NoV, vec3 radiance, float occlusion) {
vec3 H = normalize(V + L);
float NoL = max(dot(N, L), 0.0);
float NoH = saturate(dot(N, H));
float LoH = saturate(dot(L, H));
// 标准 BRDF
float D = D_GGX(roughness, NoH);
float V_term = V_SmithGGXCorrelated(roughness, NoV, NoL);
vec3 kS = fresnelSchlick(LoH, F0);
vec3 specularBRDF = (D * V_term) * kS;
// Burley 漫反射
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
vec3 diffuseBRDF = kD * albedo * Fd_Burley(roughness, NoV, NoL, LoH);
vec3 color = (diffuseBRDF + specularBRDF) * NoL;
return color * radiance * occlusion;
}
// ============================================================================
// 主渲染函数
// ============================================================================
void main() {
float depth = texture(u_GBufferDepth, v_TexCoords).r;
if (depth >= 1.0)
discard;
vec3 albedo = texture(u_GBufferAlbedo, v_TexCoords).rgb;
vec3 N = normalize(texture(u_GBufferNormal, v_TexCoords).rgb);
vec3 material = texture(u_GBufferMaterial, v_TexCoords).rgb;
vec3 emissive = texture(u_GBufferEmissive, v_TexCoords).rgb;
float roughness = max(material.r, MIN_ROUGHNESS);
float metallic = saturate(material.g);
float ao = saturate(material.b);
vec3 worldPos = reconstructWorldPosition(v_TexCoords, depth);
vec3 V = normalize(u_CameraPosition - worldPos);
float NoV = max(dot(N, V), 1e-4);
vec3 F0 = mix(vec3(F0_NON_METAL), albedo, metallic);
vec3 Lo = vec3(0.0);
// Main directional light (with shadow)
{
vec3 L = normalize(-u_DirLightDirection);
vec3 radiance = u_DirLightColor * u_DirLightIntensity;
float shadow = 0.0;
if (u_EnableShadows != 0) {
vec4 fragPosLightSpace = u_LightSpaceMatrix * vec4(worldPos, 1.0);
shadow = calculateShadow(fragPosLightSpace, N, L);
}
Lo += evaluateLighting(N, V, L, albedo, roughness, metallic, F0, NoV, radiance, 1.0 - shadow);
}
// Additional directional lights
for (int i = 0; i < u_DirLightCount; i++) {
DirectionalLight light = u_DirLights[i];
vec3 L = normalize(-light.direction);
vec3 radiance = light.color * light.intensity;
Lo += evaluateLighting(N, V, L, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
// Point lights
for (int i = 0; i < u_PointLightCount; i++) {
PointLight light = u_PointLights[i];
vec3 L = light.position - worldPos;
float dist = length(L);
if (dist > light.range)
continue;
L = normalize(L);
float distanceAttenuation = 1.0 / (dist * dist + 1.0);
float windowFactor = sq(saturate(1.0 - sq(sq(dist / light.range))));
float attenuation = distanceAttenuation * windowFactor;
vec3 radiance = light.color * light.intensity * attenuation;
Lo += evaluateLighting(N, V, L, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
// Spot lights
for (int i = 0; i < u_SpotLightCount; i++) {
SpotLight light = u_SpotLights[i];
vec3 L = light.position - worldPos;
float dist = length(L);
if (dist > light.range)
continue;
L = normalize(L);
float theta = dot(L, normalize(light.direction));
float epsilon = light.innerConeAngle - light.outerConeAngle;
float spotIntensity = saturate((theta - light.outerConeAngle) / epsilon);
if (spotIntensity <= 0.0)
continue;
float distanceAttenuation = 1.0 / (dist * dist + 1.0);
float windowFactor = sq(saturate(1.0 - sq(sq(dist / light.range))));
float attenuation = distanceAttenuation * windowFactor;
vec3 radiance = light.color * light.intensity * attenuation * spotIntensity;
Lo += evaluateLighting(N, V, L, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
// IBL
vec3 ambient = vec3(0.0);
if (u_UseIBL) {
vec3 F = fresnelSchlickRoughness(NoV, F0, roughness);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
vec3 irradiance = texture(u_IrradianceMap, N).rgb;
if (dot(irradiance, irradiance) < 0.000001)
irradiance = vec3(0.03);
vec3 diffuse = irradiance * albedo;
vec3 R = reflect(-V, N);
float lod = roughness * u_PrefilterMaxLOD;
vec3 prefilteredColor = textureLod(u_PrefilterMap, R, lod).rgb;
if (dot(prefilteredColor, prefilteredColor) < 0.000001)
prefilteredColor = vec3(0.03);
vec2 brdf = texture(u_BRDFLT, vec2(NoV, roughness)).rg;
float specOcclusion = computeSpecularOcclusion(NoV, ao, roughness);
vec3 specular = prefilteredColor * (F * brdf.x + brdf.y) * specOcclusion;
ambient = (kD * diffuse + specular) * ao;
} else {
ambient = vec3(u_AmbientIntensity) * albedo * ao;
}
vec3 color = ambient + Lo + emissive;
color = ACESFilm(color);
color = pow(color, vec3(1.0 / 2.2));
o_Color = vec4(color, 1.0);
}

View File

@@ -0,0 +1,33 @@
#type vertex
#version 450 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
out vec2 vUV;
void main() {
vUV = aUV;
gl_Position = vec4(aPos, 0, 1);
}
#type fragment
#version 450 core
out vec4 FragColor;
in vec2 vUV;
uniform sampler2D u_Depth;
uniform float u_Near;
uniform float u_Far;
uniform int u_IsPerspective;
uniform float u_Power;
float LinearizeDepth(float d) {
float z = d * 2.0 - 1.0;
return (2.0 * u_Near * u_Far) / (u_Far + u_Near - z * (u_Far - u_Near));
}
void main() {
float depth = texture(u_Depth, vUV).r;
float depth01 = (u_IsPerspective != 0) ? (LinearizeDepth(depth) / u_Far) : depth;
depth01 =1.0 - clamp(depth01, 0.0, 1.0);
depth01 = pow(depth01, max(u_Power*10.0, 0.0001));
FragColor = vec4(vec3(depth01), 1.0);
}

View File

@@ -0,0 +1,43 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
out vec3 v_LocalPos;
uniform mat4 u_Projection;
uniform mat4 u_View;
void main() {
v_LocalPos = a_Position;
// 只使用view的旋转部分移除平移与skybox一致
mat4 rotView = mat4(mat3(u_View));
vec4 clipPos = u_Projection * rotView * vec4(a_Position, 1.0);
gl_Position = clipPos.xyww; // 防止从立方体内部渲染时被近平面裁剪
}
#type fragment
#version 450 core
out vec4 FragColor;
in vec3 v_LocalPos;
uniform sampler2D u_EquirectangularMap;
const vec2 invAtan = vec2(0.1591, 0.3183);
vec2 SampleSphericalMap(vec3 v) {
vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
uv *= invAtan;
uv += 0.5;
return uv;
}
void main() {
vec2 uv = SampleSphericalMap(normalize(v_LocalPos));
vec3 color = texture(u_EquirectangularMap, uv).rgb;
// 限制极端HDR值防止后续IBL生成时出现问题
color = min(color, vec3(500.0));
FragColor = vec4(color, 1.0);
}

View File

@@ -0,0 +1,20 @@
#type vertex
#version 450 core
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 core
out vec4 FragColor;
uniform vec4 u_Color;
void main() {
FragColor = u_Color;
}

View File

@@ -0,0 +1,104 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoords;
out vec2 v_TexCoords;
void main()
{
v_TexCoords = a_TexCoords;
gl_Position = vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
in vec2 v_TexCoords;
uniform sampler2D u_GBufferAlbedo;
uniform sampler2D u_GBufferNormal;
uniform sampler2D u_GBufferMaterial;
uniform sampler2D u_GBufferEmissive;
uniform sampler2D u_GBufferDepth;
uniform isampler2D u_GBufferObjectID;
uniform int u_Mode;
uniform float u_Near;
uniform float u_Far;
uniform float u_DepthPower;
float LinearizeDepth(float d)
{
float z = d * 2.0 - 1.0;
return (2.0 * u_Near * u_Far) / (u_Far + u_Near - z * (u_Far - u_Near));
}
vec3 EncodeObjectID(int id)
{
if (id < 0)
return vec3(0.0);
int r = (id >> 0) & 0xFF;
int g = (id >> 8) & 0xFF;
int b = (id >> 16) & 0xFF;
return vec3(float(r), float(g), float(b)) / 255.0;
}
void main()
{
vec3 result = vec3(0.0);
if (u_Mode == 1)
{
vec3 albedo = texture(u_GBufferAlbedo, v_TexCoords).rgb;
result = pow(albedo, vec3(1.0 / 2.2));
}
else if (u_Mode == 2)
{
vec3 normal = normalize(texture(u_GBufferNormal, v_TexCoords).rgb);
result = normal * 0.5 + 0.5;
}
else if (u_Mode == 3)
{
result = texture(u_GBufferMaterial, v_TexCoords).rgb;
}
else if (u_Mode == 4)
{
float roughness = texture(u_GBufferMaterial, v_TexCoords).r;
result = vec3(roughness);
}
else if (u_Mode == 5)
{
float metallic = texture(u_GBufferMaterial, v_TexCoords).g;
result = vec3(metallic);
}
else if (u_Mode == 6)
{
float ao = texture(u_GBufferMaterial, v_TexCoords).b;
result = vec3(ao);
}
else if (u_Mode == 7)
{
vec3 emissive = texture(u_GBufferEmissive, v_TexCoords).rgb;
result = pow(emissive, vec3(1.0 / 2.2));
}
else if (u_Mode == 8)
{
float depth = texture(u_GBufferDepth, v_TexCoords).r;
float depth01 = LinearizeDepth(depth) / max(u_Far, 0.0001);
depth01 = 1.0 - clamp(depth01, 0.0, 1.0);
depth01 = pow(depth01, max(u_DepthPower * 10.0, 0.0001));
result = vec3(depth01);
}
else if (u_Mode == 9)
{
int id = texture(u_GBufferObjectID, v_TexCoords).r;
result = EncodeObjectID(id);
}
o_Color = vec4(result, 1.0);
}

View File

@@ -0,0 +1,71 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
out vec3 v_Normal;
out vec2 v_TexCoords;
flat out int v_ObjectID;
void main()
{
vec4 worldPos = u_Model * vec4(a_Position, 1.0);
v_Normal = mat3(u_NormalMatrix) * a_Normal;
v_TexCoords = a_TexCoords;
v_ObjectID = u_ObjectID;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Albedo;
layout(location = 1) out vec4 o_Normal;
layout(location = 2) out vec4 o_Material;
layout(location = 3) out vec4 o_Emissive;
layout(location = 4) out int o_ObjectID;
in vec3 v_Normal;
in vec2 v_TexCoords;
flat in int v_ObjectID;
uniform bool u_UseTexture;
uniform sampler2D u_Texture;
uniform vec4 u_Kd;
uniform bool u_FlipUV;
void main()
{
vec2 uv = v_TexCoords;
if (u_FlipUV)
uv.y = 1.0 - uv.y;
vec4 baseColor = u_UseTexture ? texture(u_Texture, uv) : u_Kd;
o_Albedo = vec4(baseColor.rgb, baseColor.a);
o_Normal = vec4(normalize(v_Normal), 1.0);
o_Material = vec4(1.0, 0.0, 1.0, 1.0);
o_Emissive = vec4(0.0, 0.0, 0.0, 1.0);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,152 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
layout(location = 4) in vec3 a_Tangent;
layout(location = 5) in vec3 a_Bitangent;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
out vec3 v_WorldPos;
out vec3 v_Normal;
out vec2 v_TexCoords;
flat out int v_ObjectID;
void main()
{
vec4 worldPos = u_Model * vec4(a_Position, 1.0);
v_WorldPos = worldPos.xyz;
// Use precomputed normal matrix from UBO
mat3 normalMatrix = mat3(u_NormalMatrix);
v_Normal = normalize(normalMatrix * a_Normal);
v_TexCoords = a_TexCoords;
v_ObjectID = u_ObjectID;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Albedo;
layout(location = 1) out vec4 o_Normal;
layout(location = 2) out vec4 o_Material;
layout(location = 3) out vec4 o_Emissive;
layout(location = 4) out int o_ObjectID;
in vec3 v_WorldPos;
in vec3 v_Normal;
in vec2 v_TexCoords;
flat in int v_ObjectID;
struct Material
{
vec3 albedo;
float metallic;
float roughness;
float ao;
};
uniform Material u_Material;
uniform bool u_UseAlbedoMap;
uniform sampler2D u_AlbedoMap;
uniform bool u_UseNormalMap;
uniform sampler2D u_NormalMap;
uniform float u_NormalStrength;
uniform float u_ToksvigStrength;
uniform bool u_UseMetallicMap;
uniform sampler2D u_MetallicMap;
uniform bool u_UseRoughnessMap;
uniform sampler2D u_RoughnessMap;
uniform bool u_UseAOMap;
uniform sampler2D u_AOMap;
uniform bool u_FlipUV;
vec3 getNormalFromMap(vec2 uv, out float normalVariance, out float normalLength)
{
normalVariance = 0.0;
normalLength = 1.0;
if (!u_UseNormalMap)
return normalize(v_Normal);
vec3 tangentNormal = texture(u_NormalMap, uv).xyz * 2.0 - 1.0;
normalLength = length(tangentNormal);
vec3 Q1 = dFdx(v_WorldPos);
vec3 Q2 = dFdy(v_WorldPos);
vec2 st1 = dFdx(uv);
vec2 st2 = dFdy(uv);
vec3 N = normalize(v_Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
vec3 worldNormal = normalize(TBN * tangentNormal);
worldNormal = normalize(mix(N, worldNormal, u_NormalStrength));
vec3 dndx = dFdx(worldNormal);
vec3 dndy = dFdy(worldNormal);
normalVariance = max(0.0, dot(dndx, dndx) + dot(dndy, dndy));
return worldNormal;
}
void main()
{
vec2 uv = v_TexCoords;
if (u_FlipUV)
uv.y = 1.0 - uv.y;
vec3 baseColorLinear = pow(u_Material.albedo, vec3(2.2));
vec3 texColorLinear = u_UseAlbedoMap ? pow(texture(u_AlbedoMap, uv).rgb, vec3(2.2)) : vec3(1.0);
vec3 albedo = texColorLinear * baseColorLinear;
float metallic = u_UseMetallicMap ? texture(u_MetallicMap, uv).r : u_Material.metallic;
float roughness = u_UseRoughnessMap ? texture(u_RoughnessMap, uv).r : u_Material.roughness;
float ao = u_UseAOMap ? texture(u_AOMap, uv).r : u_Material.ao;
float normalVariance;
float normalLength;
vec3 normal = getNormalFromMap(uv, normalVariance, normalLength);
float roughnessAA = clamp(roughness, 0.04, 1.0);
if (u_UseNormalMap)
{
float kernelRoughness = min(2.0 * normalVariance, 1.0);
float tokvsig = clamp(1.0 - normalLength, 0.0, 1.0);
roughnessAA = clamp(sqrt(roughnessAA * roughnessAA + kernelRoughness + tokvsig * 0.5 * u_ToksvigStrength), 0.04, 1.0);
}
o_Albedo = vec4(albedo, 1.0);
o_Normal = vec4(normalize(normal), 1.0);
o_Material = vec4(roughnessAA, metallic, ao, 1.0);
o_Emissive = vec4(0.0, 0.0, 0.0, 1.0);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,119 @@
// ============================================================================
// 预过滤环境贴图生成 - 用于镜面反射IBL
// ============================================================================
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
out vec3 v_WorldPos;
uniform mat4 u_Projection;
uniform mat4 u_View;
void main() {
v_WorldPos = a_Position;
// 只使用view的旋转部分移除平移与skybox一致
mat4 rotView = mat4(mat3(u_View));
vec4 clipPos = u_Projection * rotView * vec4(v_WorldPos, 1.0);
gl_Position = clipPos.xyww; // 确保深度为最远
}
#type fragment
#version 450 core
out vec4 FragColor;
in vec3 v_WorldPos;
uniform samplerCube u_EnvironmentMap;
uniform float u_Roughness;
const float PI = 3.14159265359;
// 低差异序列采样
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) {
return vec2(float(i) / float(N), RadicalInverse_VdC(i));
}
float DistributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
// 基于粗糙度的重要性采样
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) {
float a = roughness * roughness;
float phi = 2.0 * PI * Xi.x;
float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y));
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
// 切线空间的半向量
vec3 H;
H.x = cos(phi) * sinTheta;
H.y = sin(phi) * sinTheta;
H.z = cosTheta;
// 切线空间转世界空间
vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
vec3 tangent = normalize(cross(up, N));
vec3 bitangent = cross(N, tangent);
vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;
return normalize(sampleVec);
}
void main() {
vec3 N = normalize(v_WorldPos);
// 假设视角方向等于反射方向
vec3 R = N;
vec3 V = R;
const uint SAMPLE_COUNT = 1024u;
vec3 prefilteredColor = vec3(0.0);
float totalWeight = 0.0;
for(uint i = 0u; i < SAMPLE_COUNT; ++i) {
vec2 Xi = Hammersley(i, SAMPLE_COUNT);
vec3 H = ImportanceSampleGGX(Xi, N, u_Roughness);
vec3 L = normalize(2.0 * dot(V, H) * H - V);
float NdotL = max(dot(N, L), 0.0);
if(NdotL > 0.0) {
float NdotH = max(dot(N, H), 0.0);
float HdotV = max(dot(H, V), 0.0);
float D = DistributionGGX(N, H, u_Roughness);
float pdf = (D * NdotH / (4.0 * HdotV)) + 0.0001;
float resolution = 512.0;
float saTexel = 4.0 * PI / (6.0 * resolution * resolution);
float saSample = 1.0 / (float(SAMPLE_COUNT) * pdf + 0.0001);
float mipLevel = u_Roughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel);
prefilteredColor += textureLod(u_EnvironmentMap, L, mipLevel).rgb;
// prefilteredColor += texture(u_EnvironmentMap, L).rgb;
totalWeight += 1.0;
}
}
prefilteredColor = prefilteredColor / totalWeight;
FragColor = vec4(prefilteredColor, 1.0);
}

View File

@@ -0,0 +1,55 @@
// ============================================================================
// 辐照度贴图生成 - 用于漫反射IBL
// ============================================================================
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
out vec3 v_WorldPos;
uniform mat4 u_Projection;
uniform mat4 u_View;
void main() {
v_WorldPos = a_Position;
gl_Position = u_Projection * u_View * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
out vec4 FragColor;
in vec3 v_WorldPos;
uniform samplerCube u_EnvironmentMap;
const float PI = 3.14159265359;
void main() {
vec3 N = normalize(v_WorldPos);
vec3 irradiance = vec3(0.0);
vec3 up = vec3(0.0, 1.0, 0.0);
if(abs(N.y) > 0.999) {
up = vec3(0.0, 0.0, 1.0);
}
vec3 right = normalize(cross(up, N));
up = normalize(cross(N, right));
// 在半球上采样
float sampleDelta = 0.015;
float nrSamples = 0.0;
for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) {
for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) {
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
irradiance += texture(u_EnvironmentMap, sampleVec).rgb * cos(theta) * sin(theta);
nrSamples++;
}
}
irradiance = PI * irradiance * (1.0 / float(nrSamples));
FragColor = vec4(irradiance, 1.0);
}

View File

@@ -0,0 +1,220 @@
// 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;
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
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);
gl_Position = vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec3 v_NearPoint;
in vec3 v_FarPoint;
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Grid plane: 0 = XZ (Y up), 1 = XY (Z forward), 2 = YZ (X right)
uniform int u_GridPlane = 0;
uniform float u_GridScale = 1.0;
uniform vec4 u_GridColorThin = vec4(0.5, 0.5, 0.5, 0.4);
uniform vec4 u_GridColorThick = vec4(0.5, 0.5, 0.5, 0.6);
uniform vec4 u_AxisColorX = vec4(0.9, 0.2, 0.2, 1.0);
uniform vec4 u_AxisColorZ = vec4(0.2, 0.2, 0.9, 1.0);
uniform float u_FadeDistance = 500.0;
float computeDepth(vec3 pos) {
vec4 clipSpacePos = u_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 - u_CameraPosition);
float radialFade = 1.0 - smoothstep(u_FadeDistance * 0.3, u_FadeDistance, dist);
// Normal fade (view angle)
vec3 viewDir = normalize(fragPos3D - u_CameraPosition);
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(0.3, 0.6, 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);
o_ObjectID = -1;
}

View File

@@ -0,0 +1,48 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec4 a_Color;
layout(location = 2) in int a_ObjectID;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
struct VertexOutput {
vec4 Color;
};
layout(location = 0) out VertexOutput Output;
layout(location = 1) out flat int v_ObjectID;
void main() {
Output.Color = a_Color;
v_ObjectID = a_ObjectID;
gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
struct VertexOutput {
vec4 Color;
};
layout(location = 0) in VertexOutput Input;
layout(location = 1) in flat int v_ObjectID;
void main() {
o_Color = Input.Color;
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,225 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
uniform mat4 u_ViewProjection;
uniform mat4 u_Model;
uniform mat4 u_NormalMatrix;
out vec3 v_WorldPos;
out vec3 v_Normal;
out vec2 v_TexCoords;
void main() {
vec4 worldPos = u_Model * vec4(a_Position, 1.0);
v_WorldPos = worldPos.xyz;
mat3 normalMatrix = mat3(u_NormalMatrix);
v_Normal = normalize(normalMatrix * a_Normal);
v_TexCoords = a_TexCoords;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
in vec3 v_WorldPos;
in vec3 v_Normal;
in vec2 v_TexCoords;
const float PI = 3.14159265359;
const float F0_NON_METAL = 0.04;
const float MIN_ROUGHNESS = 0.045;
// Camera
uniform vec3 u_CameraPosition;
// Light
uniform vec3 u_LightDirection;
uniform vec3 u_LightColor;
uniform float u_LightIntensity;
uniform float u_AmbientIntensity;
// PBR Material
struct Material {
vec3 albedo;
float metallic;
float roughness;
float ao;
};
uniform Material u_Material;
// Textures
uniform bool u_UseAlbedoMap;
uniform sampler2D u_AlbedoMap;
uniform bool u_UseNormalMap;
uniform sampler2D u_NormalMap;
uniform bool u_UseMetallicMap;
uniform sampler2D u_MetallicMap;
uniform bool u_UseRoughnessMap;
uniform sampler2D u_RoughnessMap;
uniform bool u_UseAOMap;
uniform sampler2D u_AOMap;
// ============================================================================
// Utility Functions
// ============================================================================
float pow5(float x) {
float x2 = x * x;
return x2 * x2 * x;
}
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
vec3 saturate(vec3 x) {
return clamp(x, 0.0, 1.0);
}
// ============================================================================
// PBR Functions
// ============================================================================
// Normal Distribution Function (GGX)
float D_GGX(float roughness, float NoH) {
float a = roughness * roughness;
float a2 = a * a;
float NoH2 = NoH * NoH;
float nom = a2;
float denom = (NoH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / max(denom, 1e-7);
}
// Smith-GGX Correlated Visibility
float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
float a = roughness * roughness;
float a2 = a * a;
float lambdaV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
float lambdaL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
return 0.5 / max(lambdaV + lambdaL, 1e-5);
}
// Fresnel-Schlick
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow5(saturate(1.0 - cosTheta));
}
// Burley Diffuse
float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
float lightScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoL);
float viewScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoV);
return lightScatter * viewScatter * (1.0 / PI);
}
// Get normal from normal map using screen-space derivatives
vec3 getNormalFromMap() {
if (!u_UseNormalMap) {
return normalize(v_Normal);
}
vec3 tangentNormal = texture(u_NormalMap, v_TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(v_WorldPos);
vec3 Q2 = dFdy(v_WorldPos);
vec2 st1 = dFdx(v_TexCoords);
vec2 st2 = dFdy(v_TexCoords);
vec3 N = normalize(v_Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
return normalize(TBN * tangentNormal);
}
// ACES Filmic Tone Mapping
vec3 ACESFilm(vec3 x) {
float a = 2.51;
float b = 0.03;
float c = 2.43;
float d = 0.59;
float e = 0.14;
return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
}
void main() {
// Get material properties
vec3 baseColorLinear = pow(u_Material.albedo, vec3(2.2));
vec3 texColorLinear = u_UseAlbedoMap ? pow(texture(u_AlbedoMap, v_TexCoords).rgb, vec3(2.2)) : vec3(1.0);
vec3 albedo = texColorLinear * baseColorLinear;
float metallic = u_UseMetallicMap ? texture(u_MetallicMap, v_TexCoords).r : u_Material.metallic;
float roughness = u_UseRoughnessMap ? texture(u_RoughnessMap, v_TexCoords).r : u_Material.roughness;
float ao = u_UseAOMap ? texture(u_AOMap, v_TexCoords).r : u_Material.ao;
// Clamp roughness
roughness = max(roughness, MIN_ROUGHNESS);
// Get normal
vec3 N = getNormalFromMap();
vec3 V = normalize(u_CameraPosition - v_WorldPos);
float NoV = max(dot(N, V), 1e-4);
// Calculate F0
vec3 F0 = mix(vec3(F0_NON_METAL), albedo, metallic);
// Reflectance equation
vec3 Lo = vec3(0.0);
// Main directional light
{
vec3 L = normalize(-u_LightDirection);
vec3 H = normalize(V + L);
float NoL = max(dot(N, L), 0.0);
float NoH = saturate(dot(N, H));
float LoH = saturate(dot(L, H));
// Cook-Torrance BRDF
float D = D_GGX(roughness, NoH);
float V_term = V_SmithGGXCorrelated(roughness, NoV, NoL);
vec3 F = fresnelSchlick(LoH, F0);
vec3 specularBRDF = (D * V_term) * F;
// Burley diffuse
vec3 kS = F;
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
vec3 diffuseBRDF = kD * albedo * Fd_Burley(roughness, NoV, NoL, LoH);
vec3 radiance = u_LightColor * u_LightIntensity;
Lo += (diffuseBRDF + specularBRDF) * radiance * NoL;
}
// Ambient lighting
vec3 ambient = vec3(u_AmbientIntensity) * albedo * ao;
vec3 color = ambient + Lo;
// HDR tone mapping (ACES Filmic)
color = ACESFilm(color);
// Gamma correction
color = pow(color, vec3(1.0 / 2.2));
o_Color = vec4(color, 1.0);
}

View File

@@ -0,0 +1,272 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
out vec3 v_WorldPos;
out vec3 v_Normal;
out vec4 v_Color;
out vec2 v_TexCoords;
out vec4 v_FragPosLightSpace;
flat out int v_ObjectID;
void main() {
vec4 worldPos = u_Model * vec4(a_Position, 1.0);
v_WorldPos = worldPos.xyz;
// Use precomputed normal matrix from UBO
v_Normal = mat3(u_NormalMatrix) * a_Normal;
v_Color = a_Color;
v_TexCoords = a_TexCoords;
v_FragPosLightSpace = u_LightSpaceMatrix * worldPos;
v_ObjectID = u_ObjectID;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec3 v_WorldPos;
in vec3 v_Normal;
in vec4 v_Color;
in vec2 v_TexCoords;
in vec4 v_FragPosLightSpace;
flat in int v_ObjectID;
#define MAX_DIR_LIGHTS 4
#define MAX_POINT_LIGHTS 16
#define MAX_SPOT_LIGHTS 16
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
struct DirectionalLight {
vec3 direction;
vec3 color;
float intensity;
};
struct PointLight {
vec3 position;
vec3 color;
float intensity;
float range;
};
struct SpotLight {
vec3 position;
vec3 direction;
vec3 color;
float intensity;
float range;
float innerConeAngle;
float outerConeAngle;
};
uniform int u_DirLightCount;
uniform DirectionalLight u_DirLights[MAX_DIR_LIGHTS];
uniform int u_PointLightCount;
uniform PointLight u_PointLights[MAX_POINT_LIGHTS];
uniform int u_SpotLightCount;
uniform SpotLight u_SpotLights[MAX_SPOT_LIGHTS];
uniform bool u_UseTexture;
uniform sampler2D u_Texture;
uniform vec4 u_Kd; // diffuse
uniform vec4 u_Ka; // ambient
uniform bool u_FlipUV;
// Shadow mapping
uniform sampler2D u_ShadowMap;
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;
}
void main()
{
vec3 normal = normalize(v_Normal);
vec2 uv = v_TexCoords;
if(u_FlipUV)
uv.y = 1.0 - uv.y;
vec3 baseColor;
if(u_UseTexture)
baseColor = texture(u_Texture, uv).rgb;
else
baseColor = u_Kd.rgb;
// Ambient
vec3 result = u_Ka.rgb * baseColor;
// Main directional light with shadow
vec3 dirLightDir = normalize(-u_DirLightDirection);
float NdotL = max(dot(normal, dirLightDir), 0.0);
float shadow = 0.0;
if (u_EnableShadows != 0) {
shadow = calculateShadow(v_FragPosLightSpace, normal, dirLightDir);
}
result += u_DirLightColor * u_DirLightIntensity * NdotL * (1.0 - shadow) * baseColor;
// Additional directional lights (without shadow)
for(int i = 0; i < u_DirLightCount; i++)
{
DirectionalLight light = u_DirLights[i];
vec3 lightDir = normalize(-light.direction);
float NdotL = max(dot(normal, lightDir), 0.0);
result += light.color * light.intensity * NdotL * baseColor;
}
// Point lights
for(int i = 0; i < u_PointLightCount; i++)
{
PointLight light = u_PointLights[i];
vec3 L = light.position - v_WorldPos;
float distance = length(L);
if(distance > light.range)
continue;
vec3 lightDir = normalize(L);
float NdotL = max(dot(normal, lightDir), 0.0);
float attenuation = 1.0 - distance / light.range;
result += light.color *
light.intensity *
NdotL *
attenuation *
baseColor;
}
// Spot lights
for(int i = 0; i < u_SpotLightCount; i++)
{
SpotLight light = u_SpotLights[i];
vec3 L = light.position - v_WorldPos;
float distance = length(L);
if(distance > light.range)
continue;
vec3 lightDir = normalize(L);
float theta = dot(lightDir, normalize(light.direction));
float innerCos = light.innerConeAngle;
float outerCos = light.outerConeAngle;
if(theta < outerCos)
continue;
float spot = clamp((theta - outerCos) / (innerCos - outerCos), 0.0, 1.0);
float NdotL = max(dot(normal, lightDir), 0.0);
float attenuation = 1.0 - distance / light.range;
result += light.color *
light.intensity *
NdotL *
attenuation *
spot *
baseColor;
}
o_Color = vec4(result, u_Kd.a);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,45 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
uniform mat4 u_Model;
uniform mat4 u_View;
uniform mat4 u_Projection;
uniform float u_OutlineWidth;
uniform float u_Epsilon;
void main() {
vec4 viewPos = u_View * u_Model * vec4(a_Position, 1.0);
mat3 normalMatrix = mat3(transpose(inverse(u_View * u_Model)));
vec3 viewNormal = normalize(normalMatrix * a_Normal);
float ndotv = abs(dot(viewNormal, vec3(0.0, 0.0, -1.0)));
float outlineFactor = 1.0 - smoothstep(0.0, u_Epsilon, ndotv);
vec3 expandNormal = normalize(vec3(viewNormal.xy, 0.0));
float depth = -viewPos.z;
float depthScale = clamp(depth * 0.02, 0.5, 2.0);
viewPos.xyz += expandNormal * u_OutlineWidth * depthScale * outlineFactor;
gl_Position = u_Projection * viewPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
uniform vec4 u_OutlineColor = vec4(0.0, 0.0, 0.0, 1.0);
uniform int u_ObjectID = -1;
void main() {
o_Color = u_OutlineColor;
o_ObjectID = u_ObjectID;
}

View File

@@ -0,0 +1,768 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
layout(location = 4) in vec3 a_Tangent;
layout(location = 5) in vec3 a_Bitangent;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
out vec3 v_WorldPos;
out vec3 v_Normal;
out vec4 v_Color;
out vec2 v_TexCoords;
out vec4 v_FragPosLightSpace;
out mat3 v_TBN;
flat out int v_ObjectID;
void main() {
vec4 worldPos = u_Model * vec4(a_Position, 1.0);
v_WorldPos = worldPos.xyz;
// Use precomputed normal matrix from UBO
mat3 normalMatrix = mat3(u_NormalMatrix);
v_Normal = normalize(normalMatrix * a_Normal);
// Build TBN matrix for normal mapping
vec3 T = normalize(normalMatrix * a_Tangent);
vec3 N = v_Normal;
// Re-orthogonalize T with respect to N
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
v_TBN = mat3(T, B, N);
v_Color = a_Color;
v_TexCoords = a_TexCoords;
v_FragPosLightSpace = u_LightSpaceMatrix * worldPos;
v_ObjectID = u_ObjectID;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec3 v_WorldPos;
in vec3 v_Normal;
in vec4 v_Color;
in vec2 v_TexCoords;
in vec4 v_FragPosLightSpace;
in mat3 v_TBN;
flat in int v_ObjectID;
const float PI = 3.14159265359;
const float HALF_PI = 1.570796327;
const float F0_NON_METAL = 0.04;
const float MIN_ROUGHNESS = 0.045; // 避免高光过于尖锐
#define MAX_DIR_LIGHTS 4
#define MAX_POINT_LIGHTS 16
#define MAX_SPOT_LIGHTS 16
// 优化的数学函数
float pow5(float x) {
float x2 = x * x;
return x2 * x2 * x;
}
float sq(float x) {
return x * x;
}
// 饱和函数
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
vec3 saturate(vec3 x) {
return clamp(x, 0.0, 1.0);
}
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
// 光源结构
struct DirectionalLight {
vec3 direction;
vec3 color;
float intensity;
};
struct PointLight {
vec3 position;
vec3 color;
float intensity;
float range;
};
struct SpotLight {
vec3 position;
vec3 direction;
vec3 color;
float intensity;
float range;
float innerConeAngle;
float outerConeAngle;
};
// PBR材质参数
struct Material {
vec3 albedo;
float metallic;
float roughness;
float ao;
};
// ============================================================================
// 高级材质参数
// ============================================================================
// Clear Coat (透明涂层) - 用于汽车漆、涂漆表面等
uniform bool u_UseClearCoat;
uniform float u_ClearCoat; // 涂层强度 [0, 1]
uniform float u_ClearCoatRoughness; // 涂层粗糙度 [0, 1]
// Anisotropic (各向异性) - 用于拉丝金属、头发等
uniform bool u_UseAnisotropic;
uniform float u_Anisotropy; // 各向异性强度 [-1, 1],负值为垂直方向
uniform vec3 u_AnisotropyDirection; // 各向异性方向(切线空间)
// Subsurface Scattering (次表面散射) - 用于皮肤、蜡、玉石等
uniform bool u_UseSubsurface;
uniform vec3 u_SubsurfaceColor; // 次表面散射颜色
uniform float u_SubsurfacePower; // 散射指数
uniform float u_Thickness; // 材质厚度 [0, 1]0=完全不透光
// Uniforms
uniform int u_DirLightCount;
uniform DirectionalLight u_DirLights[MAX_DIR_LIGHTS];
uniform int u_PointLightCount;
uniform PointLight u_PointLights[MAX_POINT_LIGHTS];
uniform int u_SpotLightCount;
uniform SpotLight u_SpotLights[MAX_SPOT_LIGHTS];
uniform Material u_Material;
// 纹理
uniform bool u_UseAlbedoMap;
uniform sampler2D u_AlbedoMap;
uniform bool u_UseNormalMap;
uniform sampler2D u_NormalMap;
uniform float u_NormalStrength;
uniform float u_ToksvigStrength;
uniform bool u_UseMetallicMap;
uniform sampler2D u_MetallicMap;
uniform bool u_UseRoughnessMap;
uniform sampler2D u_RoughnessMap;
uniform bool u_UseAOMap;
uniform sampler2D u_AOMap;
uniform bool u_FlipUV;
// Shadow mapping
uniform sampler2D u_ShadowMap;
// IBL
uniform bool u_UseIBL;
uniform samplerCube u_IrradianceMap;
uniform samplerCube u_PrefilterMap;
uniform sampler2D u_BRDFLT;
uniform float u_PrefilterMaxLOD;
// GGX 法线分布函数 - 标准实现,使用 roughness² 参数化
// Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
float D_GGX(float roughness, float NoH) {
float a = roughness * roughness;
float a2 = a * a;
float NoH2 = NoH * NoH;
float nom = a2;
float denom = (NoH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / max(denom, 1e-7);
}
// Smith-GGX Correlated Visibility 函数 - 比 Schlick 近似更精确
// Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
float a = roughness * roughness;
float a2 = a * a;
float lambdaV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
float lambdaL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
return 0.5 / max(lambdaV + lambdaL, 1e-5);
}
// 快速近似版本 - 性能更好,质量略低
// Hammon 2017, "PBR Diffuse Lighting for GGX+Smith Microsurfaces"
float V_SmithGGXCorrelated_Fast(float roughness, float NoV, float NoL) {
float a = roughness * roughness;
float v = 0.5 / max(mix(2.0 * NoL * NoV, NoL + NoV, a), 1e-5);
return v;
}
// Schlick Fresnel - 使用优化的 pow5
vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
return f0 + (vec3(f90) - f0) * pow5(1.0 - VoH);
}
// Fresnel方程 - 标准版本f90 = 1.0
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow5(saturate(1.0 - cosTheta));
}
// Fresnel方程带粗糙度 - 用于IBL
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow5(saturate(1.0 - cosTheta));
}
// Lambert 漫反射
float Fd_Lambert() {
return 1.0 / PI;
}
// Burley 漫反射 - 比 Lambert 更真实,考虑粗糙度
// Burley 2012, "Physically-Based Shading at Disney"
float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
float lightScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoL);
float viewScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoV);
return lightScatter * viewScatter * (1.0 / PI);
}
// ACES Filmic Tone Mapping - 更真实的色调映射
// Krzysztof Narkowicz 2015
vec3 ACESFilm(vec3 x) {
float a = 2.51;
float b = 0.03;
float c = 2.43;
float d = 0.59;
float e = 0.14;
return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
}
float computeSpecularOcclusion(float NoV, float ao, float roughness) {
return clamp(pow(NoV + ao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ao, 0.0, 1.0);
}
// ============================================================================
// Clear Coat BRDF 函数
// 透明涂层使用简化的 Cook-TorranceIOR 固定为 1.5 (聚氨酯)
// ============================================================================
// Clear Coat 专用的 Kelemen Visibility 函数 - 更高效
float V_Kelemen(float LoH) {
return 0.25 / max(LoH * LoH, 1e-5);
}
// Clear Coat Fresnel - 固定 F0 = 0.04 (IOR 1.5)
float F_Schlick_ClearCoat(float VoH) {
float f0 = 0.04;
return f0 + (1.0 - f0) * pow5(1.0 - VoH);
}
// Clear Coat Lobe
float clearCoatLobe(float clearCoatRoughness, float NoH, float LoH, float clearCoat, out float Fcc) {
float D = D_GGX(clearCoatRoughness, NoH);
float V = V_Kelemen(LoH);
float F = F_Schlick_ClearCoat(LoH) * clearCoat;
Fcc = F;
return D * V * F;
}
// ============================================================================
// Anisotropic BRDF 函数
// 各向异性高光 - 用于拉丝金属、头发、唱片等
// ============================================================================
// Anisotropic GGX NDF
// Burley 2012, "Physically-Based Shading at Disney"
float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) {
float a2 = at * ab;
vec3 d = vec3(ab * ToH, at * BoH, a2 * NoH);
float d2 = dot(d, d);
float b2 = a2 / max(d2, 1e-7);
return a2 * b2 * b2 * (1.0 / PI);
}
// Anisotropic Smith-GGX Visibility
float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV,
float ToL, float BoL, float NoV, float NoL) {
float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV));
float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL));
return 0.5 / max(lambdaV + lambdaL, 1e-5);
}
// Anisotropic Specular Lobe
vec3 anisotropicLobe(vec3 N, vec3 V, vec3 L, vec3 H, vec3 T, vec3 B,
float roughness, float anisotropy, vec3 F0, out vec3 kS, out float NoL_out) {
float NoV = max(dot(N, V), 1e-4);
float NoL = max(dot(N, L), 0.0);
float NoH = saturate(dot(N, H));
float LoH = saturate(dot(L, H));
float ToV = dot(T, V);
float BoV = dot(B, V);
float ToL = dot(T, L);
float BoL = dot(B, L);
float ToH = dot(T, H);
float BoH = dot(B, H);
// 计算各向异性粗糙度
// Kulla 2017, "Revisiting Physically Based Shading at Imageworks"
float at = max(roughness * (1.0 + anisotropy), MIN_ROUGHNESS);
float ab = max(roughness * (1.0 - anisotropy), MIN_ROUGHNESS);
float D = D_GGX_Anisotropic(at, ab, ToH, BoH, NoH);
float V_term = V_SmithGGXCorrelated_Anisotropic(at, ab, ToV, BoV, ToL, BoL, NoV, NoL);
vec3 F = fresnelSchlick(LoH, F0);
kS = F;
NoL_out = NoL;
return (D * V_term) * F;
}
// ============================================================================
// Subsurface Scattering BRDF 函数
// 次表面散射 - 用于皮肤、蜡、玉石、树叶等半透明材质
// ============================================================================
// Wrap Diffuse - 能量守恒的包裹漫反射,模拟次表面散射
float Fd_Wrap(float NoL, float w) {
return saturate((NoL + w) / sq(1.0 + w));
}
// Subsurface Scattering Lobe
// 基于 Filament 的简化 BTDF 实现
vec3 subsurfaceLobe(vec3 N, vec3 V, vec3 L, vec3 subsurfaceColor,
float subsurfacePower, float thickness, vec3 diffuseColor) {
float NoL = dot(N, L);
// Forward scattering - 使用球面高斯近似
// 模拟光线穿透材质后从背面散射出来
float scatterVoH = saturate(dot(V, -L));
float forwardScatter = exp2(scatterVoH * subsurfacePower - subsurfacePower);
// Back scatter - 简化的背面散射
float backScatter = saturate(NoL * thickness + (1.0 - thickness)) * 0.5;
// 组合散射
float subsurface = mix(backScatter, 1.0, forwardScatter) * (1.0 - thickness);
return subsurfaceColor * subsurface * diffuseColor * (1.0 / PI);
}
// 阴影计算
float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) {
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0)
return 0.0;
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1);
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;
}
// 获取法线使用导数计算TBN
vec3 getNormalFromMap(out float normalVariance) {
if(!u_UseNormalMap) {
normalVariance = 0.0;
return normalize(v_Normal);
}
// 采样切线空间法线
vec3 tangentNormal = texture(u_NormalMap, v_TexCoords).xyz * 2.0 - 1.0;
// 构建 TBN屏幕导数方式避免切线错误
vec3 Q1 = dFdx(v_WorldPos);
vec3 Q2 = dFdy(v_WorldPos);
vec2 st1 = dFdx(v_TexCoords);
vec2 st2 = dFdy(v_TexCoords);
vec3 N = normalize(v_Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
// 世界空间法线
vec3 worldNormal = normalize(TBN * tangentNormal);
// 计算法线变化率(用于 Specular AA
vec3 dndx = dFdx(worldNormal);
vec3 dndy = dFdy(worldNormal);
normalVariance = max(0.0, dot(dndx, dndx) + dot(dndy, dndy));
return worldNormal;
}
// Lambert diffuse - 使用优化版本
vec3 LambertDiffuse(vec3 kS, vec3 albedo, float metallic) {
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
return kD * albedo * Fd_Lambert();
}
// Burley diffuse - 更真实的漫反射
vec3 BurleyDiffuse(vec3 kS, vec3 albedo, float metallic, float roughness, float NoV, float NoL, float LoH) {
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
return kD * albedo * Fd_Burley(roughness, NoV, NoL, LoH);
}
// ============================================================================
// 综合着色函数 - 支持 Clear Coat, Anisotropic, Subsurface
// ============================================================================
vec3 evaluateLighting(vec3 N, vec3 V, vec3 L, vec3 T, vec3 B,
vec3 albedo, float roughness, float metallic, vec3 F0,
float NoV, vec3 radiance, float occlusion) {
vec3 H = normalize(V + L);
float NoL = max(dot(N, L), 0.0);
float NoH = saturate(dot(N, H));
float LoH = saturate(dot(L, H));
vec3 color = vec3(0.0);
// ==================== 基础 BRDF ====================
vec3 kS;
vec3 specularBRDF;
if(u_UseAnisotropic && abs(u_Anisotropy) > 0.01) {
// 各向异性高光
float dummy;
specularBRDF = anisotropicLobe(N, V, L, H, T, B, roughness, u_Anisotropy, F0, kS, dummy);
} else {
// 标准各向同性高光
float D = D_GGX(roughness, NoH);
float V_term = V_SmithGGXCorrelated(roughness, max(dot(N, V), 1e-4), NoL);
kS = fresnelSchlick(LoH, F0);
specularBRDF = (D * V_term) * kS;
}
// 漫反射
vec3 diffuseBRDF = BurleyDiffuse(kS, albedo, metallic, roughness, NoV, NoL, LoH);
// 次表面散射修正漫反射
if(u_UseSubsurface) {
// 使用 Wrap Diffuse 模拟次表面散射对漫反射的影响
float wrapNoL = Fd_Wrap(dot(N, L), 0.5);
diffuseBRDF *= saturate(u_SubsurfaceColor + wrapNoL);
}
color = (diffuseBRDF + specularBRDF) * NoL;
// ==================== 次表面散射 ====================
if(u_UseSubsurface) {
vec3 sss = subsurfaceLobe(N, V, L, u_SubsurfaceColor,
u_SubsurfacePower, u_Thickness, albedo);
color += sss;
}
// ==================== Clear Coat ====================
if(u_UseClearCoat && u_ClearCoat > 0.01) {
// Clear Coat 使用几何法线,避免底层法线贴图细节影响涂层
vec3 clearCoatNormal = normalize(v_Normal);
float clearCoatNoH = saturate(dot(clearCoatNormal, H));
float clearCoatNoL = saturate(dot(clearCoatNormal, L));
float Fcc;
float clearCoatSpecular = clearCoatLobe(
max(u_ClearCoatRoughness, MIN_ROUGHNESS),
clearCoatNoH, LoH, u_ClearCoat, Fcc);
// Clear Coat 吸收底层能量
float attenuation = 1.0 - Fcc;
color *= attenuation;
// 添加 Clear Coat 高光
color += clearCoatSpecular * clearCoatNoL;
}
return color * radiance * occlusion;
}
// ============================================================================
// 主渲染函数
// ============================================================================
void main() {
// 处理UV
vec2 uv = v_TexCoords;
if(u_FlipUV)
uv.y = 1.0 - uv.y;
// 获取材质属性
vec3 baseColorLinear = pow(u_Material.albedo, vec3(2.2));
vec3 texColorLinear = u_UseAlbedoMap ? pow(texture(u_AlbedoMap, uv).rgb, vec3(2.2)) : vec3(1.0);
vec3 albedo = texColorLinear * baseColorLinear;
float metallic = u_UseMetallicMap ? texture(u_MetallicMap, uv).r : u_Material.metallic;
float roughness = u_UseRoughnessMap ? texture(u_RoughnessMap, uv).r : u_Material.roughness;
float ao = u_UseAOMap ? texture(u_AOMap, uv).r : u_Material.ao;
// 获取法线
float normalVariance;
vec3 N = getNormalFromMap(normalVariance);
vec3 V = normalize(u_CameraPosition - v_WorldPos);
float NoV = max(dot(N, V), 1e-4);
// 构建 TBN 矩阵用于各向异性计算
vec3 T = normalize(v_TBN[0]);
vec3 B = normalize(v_TBN[1]);
// 如果有自定义各向异性方向,应用它
if(u_UseAnisotropic) {
vec3 anisotropyDir = normalize(u_AnisotropyDirection);
T = normalize(v_TBN * anisotropyDir);
B = normalize(cross(N, T));
}
// 法线强度混合
vec3 N_geom = normalize(v_Normal);
N = normalize(mix(N_geom, N, u_NormalStrength));
NoV = max(dot(N, V), 1e-4);
// ================= Specular Anti-Aliasing =================
float roughnessAA = clamp(roughness, 0.04, 1.0);
if(u_UseNormalMap) {
float variance = normalVariance;
float kernelRoughness = min(2.0 * variance, 1.0);
float normalLength = length(texture(u_NormalMap, uv).xyz * 2.0 - 1.0);
float tokvsig = clamp((1.0 - normalLength), 0.0, 1.0);
roughnessAA = clamp(sqrt(roughnessAA * roughnessAA + kernelRoughness + tokvsig * 0.5 * u_ToksvigStrength), 0.04, 1.0);
}
roughness = max(roughnessAA, MIN_ROUGHNESS);
// 计算F0
vec3 F0 = mix(vec3(F0_NON_METAL), albedo, metallic);
// 反射方程
vec3 Lo = vec3(0.0);
// ========================================================================
// 主方向光(带阴影)
// ========================================================================
{
vec3 L = normalize(-u_DirLightDirection);
vec3 radiance = u_DirLightColor * u_DirLightIntensity;
float shadow = 0.0;
if(u_EnableShadows != 0) {
shadow = calculateShadow(v_FragPosLightSpace, N, L);
}
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0 - shadow);
}
// ========================================================================
// 额外方向光(无阴影)
// ========================================================================
for(int i = 0; i < u_DirLightCount; i++) {
DirectionalLight light = u_DirLights[i];
vec3 L = normalize(-light.direction);
vec3 radiance = light.color * light.intensity;
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
// ========================================================================
// 点光源
// ========================================================================
for(int i = 0; i < u_PointLightCount; i++) {
PointLight light = u_PointLights[i];
vec3 L = light.position - v_WorldPos;
float lightDist = length(L);
if(lightDist > light.range)
continue;
L = normalize(L);
float distanceAttenuation = 1.0 / (lightDist * lightDist + 1.0);
float windowFactor = sq(saturate(1.0 - sq(sq(lightDist / light.range))));
float attenuation = distanceAttenuation * windowFactor;
vec3 radiance = light.color * light.intensity * attenuation;
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
// ========================================================================
// 聚光灯
// ========================================================================
for(int i = 0; i < u_SpotLightCount; i++) {
SpotLight light = u_SpotLights[i];
vec3 L = light.position - v_WorldPos;
float lightDist = length(L);
if(lightDist > light.range)
continue;
L = normalize(L);
float theta = dot(L, normalize(light.direction));
float epsilon = light.innerConeAngle - light.outerConeAngle;
float spotIntensity = saturate((theta - light.outerConeAngle) / epsilon);
if(spotIntensity <= 0.0)
continue;
float distanceAttenuation = 1.0 / (lightDist * lightDist + 1.0);
float windowFactor = sq(saturate(1.0 - sq(sq(lightDist / light.range))));
float attenuation = distanceAttenuation * windowFactor;
vec3 radiance = light.color * light.intensity * attenuation * spotIntensity;
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
// ========================================================================
// 环境光 (IBL)
// ========================================================================
vec3 ambient = vec3(0.0);
if(u_UseIBL) {
float NoV_ibl = max(dot(N, V), 1e-4);
vec3 F = fresnelSchlickRoughness(NoV_ibl, F0, roughness);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
// 漫反射 IBL
vec3 irradiance = texture(u_IrradianceMap, N).rgb;
if(dot(irradiance, irradiance) < 0.000001)
irradiance = vec3(0.03);
vec3 diffuse = irradiance * albedo;
// 镜面反射 IBL
vec3 R = reflect(-V, N);
float lod = roughness * u_PrefilterMaxLOD;
vec3 prefilteredColor = textureLod(u_PrefilterMap, R, lod).rgb;
if(dot(prefilteredColor, prefilteredColor) < 0.000001)
prefilteredColor = vec3(0.03);
vec2 brdf = texture(u_BRDFLT, vec2(NoV_ibl, roughness)).rg;
float specOcclusion = computeSpecularOcclusion(NoV_ibl, ao, roughness);
vec3 specular = prefilteredColor * (F * brdf.x + brdf.y) * specOcclusion;
ambient = (kD * diffuse + specular) * ao;
// Clear Coat IBL
if(u_UseClearCoat && u_ClearCoat > 0.01) {
vec3 clearCoatR = reflect(-V, normalize(v_Normal));
float clearCoatLod = u_ClearCoatRoughness * u_PrefilterMaxLOD;
vec3 clearCoatPrefilteredColor = textureLod(u_PrefilterMap, clearCoatR, clearCoatLod).rgb;
float Fc = F_Schlick_ClearCoat(NoV_ibl) * u_ClearCoat;
ambient *= (1.0 - Fc);
ambient += clearCoatPrefilteredColor * Fc;
}
} else {
ambient = vec3(u_AmbientIntensity) * albedo * ao;
}
vec3 color = ambient + Lo;
// HDR色调映射 (ACES Filmic)
color = ACESFilm(color);
// Gamma校正
color = pow(color, vec3(1.0 / 2.2));
o_Color = vec4(color, 1.0);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,142 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
uniform mat4 u_Projection;
uniform mat4 u_View;
out vec3 v_LocalPos;
void main()
{
v_LocalPos = a_Position;
// Remove translation from view matrix
mat4 rotView = mat4(mat3(u_View));
vec4 clipPos = u_Projection * rotView * vec4(a_Position, 1.0);
gl_Position = clipPos.xyww;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 FragColor;
in vec3 v_LocalPos;
// Sky parameters
uniform vec3 u_SunDirection; // Normalized sun direction
uniform float u_SunIntensity; // HDR sun brightness
uniform float u_SunAngularRadius; // Sun angular radius in radians
uniform vec3 u_SkyColorZenith; // Zenith color (linear)
uniform vec3 u_SkyColorHorizon; // Horizon color (linear)
uniform vec3 u_GroundColor; // Ground color (linear)
uniform float u_SkyExposure; // Sky exposure multiplier
const float PI = 3.14159265359;
// Rayleigh scattering approximation
// Simulates how shorter wavelengths (blue) scatter more at zenith
vec3 computeRayleighScattering(vec3 direction, vec3 sunDir)
{
// Height gradient: zenith (y=1) to horizon (y=0)
float height = max(direction.y, 0.0);
// Exponential falloff for more natural sky gradient
float zenithFactor = pow(height, 0.5);
// Blend between horizon and zenith colors
vec3 skyColor = mix(u_SkyColorHorizon, u_SkyColorZenith, zenithFactor);
// Add slight brightening near horizon (atmospheric haze)
float horizonGlow = exp(-height * 4.0) * 0.3;
skyColor += u_SkyColorHorizon * horizonGlow;
return skyColor;
}
// Mie scattering - creates glow around the sun
vec3 computeMieScattering(vec3 direction, vec3 sunDir)
{
float cosTheta = dot(direction, sunDir);
// Henyey-Greenstein phase function approximation
// g controls asymmetry (0.76 gives forward scattering)
float g = 0.76;
float g2 = g * g;
float phase = (1.0 - g2) / (4.0 * PI * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));
// Scale and color the Mie scattering
// Warm color for sunset-like glow
vec3 mieColor = vec3(1.0, 0.9, 0.7);
return mieColor * phase * 0.15;
}
// Sun disc with soft edge
vec3 computeSunDisc(vec3 direction, vec3 sunDir)
{
float cosTheta = dot(direction, sunDir);
float sunCosAngle = cos(u_SunAngularRadius);
// Soft edge using smoothstep
float edgeSoftness = u_SunAngularRadius * 0.3;
float sunDisc = smoothstep(
cos(u_SunAngularRadius + edgeSoftness),
sunCosAngle,
cosTheta
);
// HDR sun color (slightly warm)
vec3 sunColor = vec3(1.0, 0.95, 0.9) * u_SunIntensity;
return sunColor * sunDisc;
}
void main()
{
vec3 direction = normalize(v_LocalPos);
vec3 sunDir = normalize(u_SunDirection);
vec3 color = vec3(0.0);
// Check if we're looking at sky or ground
if (direction.y > -0.02)
{
// Sky hemisphere
// Base sky color with Rayleigh scattering
vec3 skyColor = computeRayleighScattering(direction, sunDir);
// Add Mie scattering (sun glow)
vec3 mie = computeMieScattering(direction, sunDir);
skyColor += mie;
// Add sun disc (only if sun is above horizon)
if (sunDir.y > 0.0)
{
vec3 sunDisc = computeSunDisc(direction, sunDir);
skyColor += sunDisc;
}
// Apply exposure
color = skyColor * u_SkyExposure;
// Soft transition to ground at horizon
float horizonBlend = smoothstep(-0.02, 0.05, direction.y);
color = mix(u_GroundColor * u_SkyExposure * 0.5, color, horizonBlend);
}
else
{
// Ground hemisphere
// Darken towards nadir for depth
float groundFade = smoothstep(-1.0, -0.1, direction.y);
color = u_GroundColor * u_SkyExposure * 0.5 * groundFade;
}
// Output linear HDR (no tonemapping - IBL pipeline will process this)
FragColor = vec4(color, 1.0);
}

View File

@@ -0,0 +1,100 @@
// Basic Texture Shader
#type vertex
#version 450 core
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 = 4) in float a_TilingFactor;
layout(location = 5) in int a_ObjectID;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
out vec4 v_Color;
out vec2 v_TexCoord;
out float v_TilingFactor;
flat out float v_TexIndex;
flat out int v_ObjectID;
void main()
{
v_Color = a_Color;
v_TexCoord = a_TexCoord;
v_TilingFactor = a_TilingFactor;
v_TexIndex = a_TexIndex;
v_ObjectID = a_ObjectID;
gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec4 v_Color;
in vec2 v_TexCoord;
in float v_TilingFactor;
flat in float v_TexIndex;
flat in int v_ObjectID;
uniform sampler2D u_Textures[32];
void main()
{
vec4 texColor = v_Color;
switch (int(v_TexIndex))
{
case 0: texColor *= texture(u_Textures[ 0], v_TexCoord * v_TilingFactor); break;
case 1: texColor *= texture(u_Textures[ 1], v_TexCoord * v_TilingFactor); break;
case 2: texColor *= texture(u_Textures[ 2], v_TexCoord * v_TilingFactor); break;
case 3: texColor *= texture(u_Textures[ 3], v_TexCoord * v_TilingFactor); break;
case 4: texColor *= texture(u_Textures[ 4], v_TexCoord * v_TilingFactor); break;
case 5: texColor *= texture(u_Textures[ 5], v_TexCoord * v_TilingFactor); break;
case 6: texColor *= texture(u_Textures[ 6], v_TexCoord * v_TilingFactor); break;
case 7: texColor *= texture(u_Textures[ 7], v_TexCoord * v_TilingFactor); break;
case 8: texColor *= texture(u_Textures[ 8], v_TexCoord * v_TilingFactor); break;
case 9: texColor *= texture(u_Textures[ 9], v_TexCoord * v_TilingFactor); break;
case 10: texColor *= texture(u_Textures[10], v_TexCoord * v_TilingFactor); break;
case 11: texColor *= texture(u_Textures[11], v_TexCoord * v_TilingFactor); break;
case 12: texColor *= texture(u_Textures[12], v_TexCoord * v_TilingFactor); break;
case 13: texColor *= texture(u_Textures[13], v_TexCoord * v_TilingFactor); break;
case 14: texColor *= texture(u_Textures[14], v_TexCoord * v_TilingFactor); break;
case 15: texColor *= texture(u_Textures[15], v_TexCoord * v_TilingFactor); break;
case 16: texColor *= texture(u_Textures[16], v_TexCoord * v_TilingFactor); break;
case 17: texColor *= texture(u_Textures[17], v_TexCoord * v_TilingFactor); break;
case 18: texColor *= texture(u_Textures[18], v_TexCoord * v_TilingFactor); break;
case 19: texColor *= texture(u_Textures[19], v_TexCoord * v_TilingFactor); break;
case 20: texColor *= texture(u_Textures[20], v_TexCoord * v_TilingFactor); break;
case 21: texColor *= texture(u_Textures[21], v_TexCoord * v_TilingFactor); break;
case 22: texColor *= texture(u_Textures[22], v_TexCoord * v_TilingFactor); break;
case 23: texColor *= texture(u_Textures[23], v_TexCoord * v_TilingFactor); break;
case 24: texColor *= texture(u_Textures[24], v_TexCoord * v_TilingFactor); break;
case 25: texColor *= texture(u_Textures[25], v_TexCoord * v_TilingFactor); break;
case 26: texColor *= texture(u_Textures[26], v_TexCoord * v_TilingFactor); break;
case 27: texColor *= texture(u_Textures[27], v_TexCoord * v_TilingFactor); break;
case 28: texColor *= texture(u_Textures[28], v_TexCoord * v_TilingFactor); break;
case 29: texColor *= texture(u_Textures[29], v_TexCoord * v_TilingFactor); break;
case 30: texColor *= texture(u_Textures[30], v_TexCoord * v_TilingFactor); break;
case 31: texColor *= texture(u_Textures[31], v_TexCoord * v_TilingFactor); break;
default: break;
}
if (texColor.a == 0.0)
discard;
o_Color = texColor;
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,178 @@
// Instanced quad shader
#type vertex
#version 450 core
layout(location = 0) in mat4 a_Transform;
layout(location = 4) in vec4 a_Color;
layout(location = 5) in float a_TexIndex;
layout(location = 6) in float a_TilingFactor;
layout(location = 7) in int a_ObjectID;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
out vec4 v_Color;
out vec2 v_TexCoord;
out float v_TilingFactor;
flat out float v_TexIndex;
flat out int v_ObjectID;
const vec3 s_Positions[4] = vec3[](
vec3(-0.5, -0.5, 0.0),
vec3(0.5, -0.5, 0.0),
vec3(0.5, 0.5, 0.0),
vec3(-0.5, 0.5, 0.0));
const vec2 s_TexCoords[4] = vec2[](
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0),
vec2(0.0, 1.0));
void main()
{
int vertexIndex = gl_VertexID % 4;
vec4 worldPosition = a_Transform * vec4(s_Positions[vertexIndex], 1.0);
v_Color = a_Color;
v_TexCoord = s_TexCoords[vertexIndex];
v_TilingFactor = a_TilingFactor;
v_TexIndex = a_TexIndex;
v_ObjectID = a_ObjectID;
gl_Position = u_ViewProjection * worldPosition;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec4 v_Color;
in vec2 v_TexCoord;
in float v_TilingFactor;
flat in float v_TexIndex;
flat in int v_ObjectID;
uniform sampler2D u_Textures[32];
void main()
{
vec4 texColor = v_Color;
switch (int(v_TexIndex))
{
case 0:
texColor *= texture(u_Textures[0], v_TexCoord * v_TilingFactor);
break;
case 1:
texColor *= texture(u_Textures[1], v_TexCoord * v_TilingFactor);
break;
case 2:
texColor *= texture(u_Textures[2], v_TexCoord * v_TilingFactor);
break;
case 3:
texColor *= texture(u_Textures[3], v_TexCoord * v_TilingFactor);
break;
case 4:
texColor *= texture(u_Textures[4], v_TexCoord * v_TilingFactor);
break;
case 5:
texColor *= texture(u_Textures[5], v_TexCoord * v_TilingFactor);
break;
case 6:
texColor *= texture(u_Textures[6], v_TexCoord * v_TilingFactor);
break;
case 7:
texColor *= texture(u_Textures[7], v_TexCoord * v_TilingFactor);
break;
case 8:
texColor *= texture(u_Textures[8], v_TexCoord * v_TilingFactor);
break;
case 9:
texColor *= texture(u_Textures[9], v_TexCoord * v_TilingFactor);
break;
case 10:
texColor *= texture(u_Textures[10], v_TexCoord * v_TilingFactor);
break;
case 11:
texColor *= texture(u_Textures[11], v_TexCoord * v_TilingFactor);
break;
case 12:
texColor *= texture(u_Textures[12], v_TexCoord * v_TilingFactor);
break;
case 13:
texColor *= texture(u_Textures[13], v_TexCoord * v_TilingFactor);
break;
case 14:
texColor *= texture(u_Textures[14], v_TexCoord * v_TilingFactor);
break;
case 15:
texColor *= texture(u_Textures[15], v_TexCoord * v_TilingFactor);
break;
case 16:
texColor *= texture(u_Textures[16], v_TexCoord * v_TilingFactor);
break;
case 17:
texColor *= texture(u_Textures[17], v_TexCoord * v_TilingFactor);
break;
case 18:
texColor *= texture(u_Textures[18], v_TexCoord * v_TilingFactor);
break;
case 19:
texColor *= texture(u_Textures[19], v_TexCoord * v_TilingFactor);
break;
case 20:
texColor *= texture(u_Textures[20], v_TexCoord * v_TilingFactor);
break;
case 21:
texColor *= texture(u_Textures[21], v_TexCoord * v_TilingFactor);
break;
case 22:
texColor *= texture(u_Textures[22], v_TexCoord * v_TilingFactor);
break;
case 23:
texColor *= texture(u_Textures[23], v_TexCoord * v_TilingFactor);
break;
case 24:
texColor *= texture(u_Textures[24], v_TexCoord * v_TilingFactor);
break;
case 25:
texColor *= texture(u_Textures[25], v_TexCoord * v_TilingFactor);
break;
case 26:
texColor *= texture(u_Textures[26], v_TexCoord * v_TilingFactor);
break;
case 27:
texColor *= texture(u_Textures[27], v_TexCoord * v_TilingFactor);
break;
case 28:
texColor *= texture(u_Textures[28], v_TexCoord * v_TilingFactor);
break;
case 29:
texColor *= texture(u_Textures[29], v_TexCoord * v_TilingFactor);
break;
case 30:
texColor *= texture(u_Textures[30], v_TexCoord * v_TilingFactor);
break;
case 31:
texColor *= texture(u_Textures[31], v_TexCoord * v_TilingFactor);
break;
default:
break;
}
if (texColor.a == 0.0)
discard;
o_Color = texColor;
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,40 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
float _lightPadding1;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
void main() {
gl_Position = u_LightSpaceMatrix * u_Model * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
void main() {
}

View File

@@ -0,0 +1,174 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
layout(location = 4) in ivec4 a_BoneIDs;
layout(location = 5) in vec4 a_BoneWeights;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
// Bone uniform buffer (binding = 4)
layout(std140, binding = 4) uniform BoneData
{
mat4 u_BoneMatrices[128];
};
out vec3 v_WorldPos;
out vec3 v_Normal;
out vec2 v_TexCoords;
flat out int v_ObjectID;
void main()
{
// Compute skin matrix from bone influences
mat4 skinMatrix = mat4(0.0);
for (int i = 0; i < 4; i++) {
if (a_BoneIDs[i] >= 0) {
skinMatrix += a_BoneWeights[i] * u_BoneMatrices[a_BoneIDs[i]];
}
}
// If no bones affect this vertex, use identity
if (a_BoneWeights[0] == 0.0 && a_BoneWeights[1] == 0.0 &&
a_BoneWeights[2] == 0.0 && a_BoneWeights[3] == 0.0) {
skinMatrix = mat4(1.0);
}
vec4 skinnedPos = skinMatrix * vec4(a_Position, 1.0);
vec4 worldPos = u_Model * skinnedPos;
v_WorldPos = worldPos.xyz;
// Skin the normal
mat3 skinNormalMatrix = mat3(skinMatrix);
mat3 normalMatrix = mat3(u_NormalMatrix);
v_Normal = normalize(normalMatrix * skinNormalMatrix * a_Normal);
v_TexCoords = a_TexCoords;
v_ObjectID = u_ObjectID;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Albedo;
layout(location = 1) out vec4 o_Normal;
layout(location = 2) out vec4 o_Material;
layout(location = 3) out vec4 o_Emissive;
layout(location = 4) out int o_ObjectID;
in vec3 v_WorldPos;
in vec3 v_Normal;
in vec2 v_TexCoords;
flat in int v_ObjectID;
struct Material
{
vec3 albedo;
float metallic;
float roughness;
float ao;
};
uniform Material u_Material;
uniform bool u_UseAlbedoMap;
uniform sampler2D u_AlbedoMap;
uniform bool u_UseNormalMap;
uniform sampler2D u_NormalMap;
uniform float u_NormalStrength;
uniform float u_ToksvigStrength;
uniform bool u_UseMetallicMap;
uniform sampler2D u_MetallicMap;
uniform bool u_UseRoughnessMap;
uniform sampler2D u_RoughnessMap;
uniform bool u_UseAOMap;
uniform sampler2D u_AOMap;
uniform bool u_FlipUV;
vec3 getNormalFromMap(vec2 uv, out float normalVariance, out float normalLength)
{
normalVariance = 0.0;
normalLength = 1.0;
if (!u_UseNormalMap)
return normalize(v_Normal);
vec3 tangentNormal = texture(u_NormalMap, uv).xyz * 2.0 - 1.0;
normalLength = length(tangentNormal);
vec3 Q1 = dFdx(v_WorldPos);
vec3 Q2 = dFdy(v_WorldPos);
vec2 st1 = dFdx(uv);
vec2 st2 = dFdy(uv);
vec3 N = normalize(v_Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
vec3 worldNormal = normalize(TBN * tangentNormal);
worldNormal = normalize(mix(N, worldNormal, u_NormalStrength));
vec3 dndx = dFdx(worldNormal);
vec3 dndy = dFdy(worldNormal);
normalVariance = max(0.0, dot(dndx, dndx) + dot(dndy, dndy));
return worldNormal;
}
void main()
{
vec2 uv = v_TexCoords;
if (u_FlipUV)
uv.y = 1.0 - uv.y;
vec3 baseColorLinear = pow(u_Material.albedo, vec3(2.2));
vec3 texColorLinear = u_UseAlbedoMap ? pow(texture(u_AlbedoMap, uv).rgb, vec3(2.2)) : vec3(1.0);
vec3 albedo = texColorLinear * baseColorLinear;
float metallic = u_UseMetallicMap ? texture(u_MetallicMap, uv).r : u_Material.metallic;
float roughness = u_UseRoughnessMap ? texture(u_RoughnessMap, uv).r : u_Material.roughness;
float ao = u_UseAOMap ? texture(u_AOMap, uv).r : u_Material.ao;
float normalVariance;
float normalLength;
vec3 normal = getNormalFromMap(uv, normalVariance, normalLength);
float roughnessAA = clamp(roughness, 0.04, 1.0);
if (u_UseNormalMap)
{
float kernelRoughness = min(2.0 * normalVariance, 1.0);
float tokvsig = clamp(1.0 - normalLength, 0.0, 1.0);
roughnessAA = clamp(sqrt(roughnessAA * roughnessAA + kernelRoughness + tokvsig * 0.5 * u_ToksvigStrength), 0.04, 1.0);
}
o_Albedo = vec4(albedo, 1.0);
o_Normal = vec4(normalize(normal), 1.0);
o_Material = vec4(roughnessAA, metallic, ao, 1.0);
o_Emissive = vec4(0.0, 0.0, 0.0, 1.0);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,577 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 a_Normal;
layout(location = 2) in vec4 a_Color;
layout(location = 3) in vec2 a_TexCoords;
layout(location = 4) in ivec4 a_BoneIDs;
layout(location = 5) in vec4 a_BoneWeights;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
// Bone uniform buffer (binding = 4)
layout(std140, binding = 4) uniform BoneData
{
mat4 u_BoneMatrices[128];
};
out vec3 v_WorldPos;
out vec3 v_Normal;
out vec4 v_Color;
out vec2 v_TexCoords;
out vec4 v_FragPosLightSpace;
flat out int v_ObjectID;
void main() {
// Compute skin matrix from bone influences
mat4 skinMatrix = mat4(0.0);
for (int i = 0; i < 4; i++) {
if (a_BoneIDs[i] >= 0) {
skinMatrix += a_BoneWeights[i] * u_BoneMatrices[a_BoneIDs[i]];
}
}
// If no bones affect this vertex, use identity
if (a_BoneWeights[0] == 0.0 && a_BoneWeights[1] == 0.0 &&
a_BoneWeights[2] == 0.0 && a_BoneWeights[3] == 0.0) {
skinMatrix = mat4(1.0);
}
vec4 skinnedPos = skinMatrix * vec4(a_Position, 1.0);
vec4 worldPos = u_Model * skinnedPos;
v_WorldPos = worldPos.xyz;
// Skin the normal
mat3 skinNormalMatrix = mat3(skinMatrix);
mat3 normalMatrix = mat3(u_NormalMatrix);
v_Normal = normalize(normalMatrix * skinNormalMatrix * a_Normal);
v_Color = a_Color;
v_TexCoords = a_TexCoords;
v_FragPosLightSpace = u_LightSpaceMatrix * worldPos;
v_ObjectID = u_ObjectID;
gl_Position = u_ViewProjection * worldPos;
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec3 v_WorldPos;
in vec3 v_Normal;
in vec4 v_Color;
in vec2 v_TexCoords;
in vec4 v_FragPosLightSpace;
flat in int v_ObjectID;
const float PI = 3.14159265359;
const float HALF_PI = 1.570796327;
const float F0_NON_METAL = 0.04;
const float MIN_ROUGHNESS = 0.045;
float pow5(float x) {
float x2 = x * x;
return x2 * x2 * x;
}
float sq(float x) {
return x * x;
}
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
vec3 saturate(vec3 x) {
return clamp(x, 0.0, 1.0);
}
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
int u_NumDirLights;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
#define MAX_DIR_LIGHTS 4
#define MAX_POINT_LIGHTS 16
#define MAX_SPOT_LIGHTS 16
struct DirectionalLight {
vec3 direction;
vec3 color;
float intensity;
};
struct PointLight {
vec3 position;
vec3 color;
float intensity;
float range;
};
struct SpotLight {
vec3 position;
vec3 direction;
vec3 color;
float intensity;
float range;
float innerConeAngle;
float outerConeAngle;
};
struct Material {
vec3 albedo;
float metallic;
float roughness;
float ao;
};
// Clear Coat
uniform bool u_UseClearCoat;
uniform float u_ClearCoat;
uniform float u_ClearCoatRoughness;
// Anisotropic
uniform bool u_UseAnisotropic;
uniform float u_Anisotropy;
uniform vec3 u_AnisotropyDirection;
// Subsurface Scattering
uniform bool u_UseSubsurface;
uniform vec3 u_SubsurfaceColor;
uniform float u_SubsurfacePower;
uniform float u_Thickness;
uniform int u_DirLightCount;
uniform DirectionalLight u_DirLights[MAX_DIR_LIGHTS];
uniform int u_PointLightCount;
uniform PointLight u_PointLights[MAX_POINT_LIGHTS];
uniform int u_SpotLightCount;
uniform SpotLight u_SpotLights[MAX_SPOT_LIGHTS];
uniform Material u_Material;
uniform bool u_UseAlbedoMap;
uniform sampler2D u_AlbedoMap;
uniform bool u_UseNormalMap;
uniform sampler2D u_NormalMap;
uniform float u_NormalStrength;
uniform float u_ToksvigStrength;
uniform bool u_UseMetallicMap;
uniform sampler2D u_MetallicMap;
uniform bool u_UseRoughnessMap;
uniform sampler2D u_RoughnessMap;
uniform bool u_UseAOMap;
uniform sampler2D u_AOMap;
uniform bool u_FlipUV;
uniform sampler2D u_ShadowMap;
// IBL
uniform bool u_UseIBL;
uniform samplerCube u_IrradianceMap;
uniform samplerCube u_PrefilterMap;
uniform sampler2D u_BRDFLT;
uniform float u_PrefilterMaxLOD;
// GGX NDF
float D_GGX(float roughness, float NoH) {
float a = roughness * roughness;
float a2 = a * a;
float NoH2 = NoH * NoH;
float nom = a2;
float denom = (NoH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / max(denom, 1e-7);
}
float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
float a = roughness * roughness;
float a2 = a * a;
float lambdaV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
float lambdaL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
return 0.5 / max(lambdaV + lambdaL, 1e-5);
}
vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
return f0 + (vec3(f90) - f0) * pow5(1.0 - VoH);
}
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow5(saturate(1.0 - cosTheta));
}
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow5(saturate(1.0 - cosTheta));
}
float Fd_Lambert() {
return 1.0 / PI;
}
float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
float lightScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoL);
float viewScatter = 1.0 + (f90 - 1.0) * pow5(1.0 - NoV);
return lightScatter * viewScatter * (1.0 / PI);
}
vec3 ACESFilm(vec3 x) {
float a = 2.51;
float b = 0.03;
float c = 2.43;
float d = 0.59;
float e = 0.14;
return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
}
float computeSpecularOcclusion(float NoV, float ao, float roughness) {
return clamp(pow(NoV + ao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ao, 0.0, 1.0);
}
float V_Kelemen(float LoH) {
return 0.25 / max(LoH * LoH, 1e-5);
}
float F_Schlick_ClearCoat(float VoH) {
float f0 = 0.04;
return f0 + (1.0 - f0) * pow5(1.0 - VoH);
}
float clearCoatLobe(float clearCoatRoughness, float NoH, float LoH, float clearCoat, out float Fcc) {
float D = D_GGX(clearCoatRoughness, NoH);
float V = V_Kelemen(LoH);
float F = F_Schlick_ClearCoat(LoH) * clearCoat;
Fcc = F;
return D * V * F;
}
float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) {
float a2 = at * ab;
vec3 d_vec = vec3(ab * ToH, at * BoH, a2 * NoH);
float d2 = dot(d_vec, d_vec);
float b2 = a2 / max(d2, 1e-7);
return a2 * b2 * b2 * (1.0 / PI);
}
float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV,
float ToL, float BoL, float NoV, float NoL) {
float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV));
float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL));
return 0.5 / max(lambdaV + lambdaL, 1e-5);
}
vec3 anisotropicLobe(vec3 N, vec3 V, vec3 L, vec3 H, vec3 T, vec3 B,
float roughness, float anisotropy, vec3 F0, out vec3 kS, out float NoL_out) {
float NoV = max(dot(N, V), 1e-4);
float NoL = max(dot(N, L), 0.0);
float NoH = saturate(dot(N, H));
float LoH = saturate(dot(L, H));
float ToV = dot(T, V);
float BoV = dot(B, V);
float ToL = dot(T, L);
float BoL = dot(B, L);
float ToH = dot(T, H);
float BoH = dot(B, H);
float at = max(roughness * (1.0 + anisotropy), MIN_ROUGHNESS);
float ab = max(roughness * (1.0 - anisotropy), MIN_ROUGHNESS);
float D = D_GGX_Anisotropic(at, ab, ToH, BoH, NoH);
float V_term = V_SmithGGXCorrelated_Anisotropic(at, ab, ToV, BoV, ToL, BoL, NoV, NoL);
vec3 F = fresnelSchlick(LoH, F0);
kS = F;
NoL_out = NoL;
return (D * V_term) * F;
}
float Fd_Wrap(float NoL, float w) {
return saturate((NoL + w) / sq(1.0 + w));
}
vec3 subsurfaceLobe(vec3 N, vec3 V, vec3 L, vec3 subsurfaceColor,
float subsurfacePower, float thickness, vec3 diffuseColor) {
float NoL = dot(N, L);
float scatterVoH = saturate(dot(V, -L));
float forwardScatter = exp2(scatterVoH * subsurfacePower - subsurfacePower);
float backScatter = saturate(NoL * thickness + (1.0 - thickness)) * 0.5;
float subsurface = mix(backScatter, 1.0, forwardScatter) * (1.0 - thickness);
return subsurfaceColor * subsurface * diffuseColor * (1.0 / PI);
}
float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) {
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0)
return 0.0;
float closestDepth = texture(u_ShadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1);
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;
}
vec3 getNormalFromMap(out float normalVariance) {
if(!u_UseNormalMap) {
normalVariance = 0.0;
return normalize(v_Normal);
}
vec3 tangentNormal = texture(u_NormalMap, v_TexCoords).xyz * 2.0 - 1.0;
vec3 Q1 = dFdx(v_WorldPos);
vec3 Q2 = dFdy(v_WorldPos);
vec2 st1 = dFdx(v_TexCoords);
vec2 st2 = dFdy(v_TexCoords);
vec3 N = normalize(v_Normal);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = normalize(cross(N, T));
mat3 TBN = mat3(T, B, N);
vec3 worldNormal = normalize(TBN * tangentNormal);
vec3 dndx = dFdx(worldNormal);
vec3 dndy = dFdy(worldNormal);
normalVariance = max(0.0, dot(dndx, dndx) + dot(dndy, dndy));
return worldNormal;
}
vec3 BurleyDiffuse(vec3 kS, vec3 albedo, float metallic, float roughness, float NoV, float NoL, float LoH) {
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
return kD * albedo * Fd_Burley(roughness, NoV, NoL, LoH);
}
vec3 evaluateLighting(vec3 N, vec3 V, vec3 L, vec3 T, vec3 B,
vec3 albedo, float roughness, float metallic, vec3 F0,
float NoV, vec3 radiance, float occlusion) {
vec3 H = normalize(V + L);
float NoL = max(dot(N, L), 0.0);
float NoH = saturate(dot(N, H));
float LoH = saturate(dot(L, H));
vec3 color = vec3(0.0);
vec3 kS;
vec3 specularBRDF;
if(u_UseAnisotropic && abs(u_Anisotropy) > 0.01) {
float dummy;
specularBRDF = anisotropicLobe(N, V, L, H, T, B, roughness, u_Anisotropy, F0, kS, dummy);
} else {
float D = D_GGX(roughness, NoH);
float V_term = V_SmithGGXCorrelated(roughness, max(dot(N, V), 1e-4), NoL);
kS = fresnelSchlick(LoH, F0);
specularBRDF = (D * V_term) * kS;
}
vec3 diffuseBRDF = BurleyDiffuse(kS, albedo, metallic, roughness, NoV, NoL, LoH);
if(u_UseSubsurface) {
float wrapNoL = Fd_Wrap(dot(N, L), 0.5);
diffuseBRDF *= saturate(u_SubsurfaceColor + wrapNoL);
}
color = (diffuseBRDF + specularBRDF) * NoL;
if(u_UseSubsurface) {
vec3 sss = subsurfaceLobe(N, V, L, u_SubsurfaceColor,
u_SubsurfacePower, u_Thickness, albedo);
color += sss;
}
if(u_UseClearCoat && u_ClearCoat > 0.01) {
vec3 clearCoatNormal = normalize(v_Normal);
float clearCoatNoH = saturate(dot(clearCoatNormal, H));
float clearCoatNoL = saturate(dot(clearCoatNormal, L));
float Fcc;
float clearCoatSpecular = clearCoatLobe(
max(u_ClearCoatRoughness, MIN_ROUGHNESS),
clearCoatNoH, LoH, u_ClearCoat, Fcc);
float attenuation = 1.0 - Fcc;
color *= attenuation;
color += clearCoatSpecular * clearCoatNoL;
}
return color * radiance * occlusion;
}
void main() {
vec2 uv = v_TexCoords;
if(u_FlipUV)
uv.y = 1.0 - uv.y;
vec3 baseColorLinear = pow(u_Material.albedo, vec3(2.2));
vec3 texColorLinear = u_UseAlbedoMap ? pow(texture(u_AlbedoMap, uv).rgb, vec3(2.2)) : vec3(1.0);
vec3 albedo = texColorLinear * baseColorLinear;
float metallic = u_UseMetallicMap ? texture(u_MetallicMap, uv).r : u_Material.metallic;
float roughness = u_UseRoughnessMap ? texture(u_RoughnessMap, uv).r : u_Material.roughness;
float ao = u_UseAOMap ? texture(u_AOMap, uv).r : u_Material.ao;
float normalVariance;
vec3 N = getNormalFromMap(normalVariance);
vec3 V = normalize(u_CameraPosition - v_WorldPos);
float NoV = max(dot(N, V), 1e-4);
// Compute T and B using screen-space derivatives (same as getNormalFromMap)
vec3 Q1 = dFdx(v_WorldPos);
vec3 Q2 = dFdy(v_WorldPos);
vec2 st1 = dFdx(uv);
vec2 st2 = dFdy(uv);
vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
vec3 B = normalize(cross(N, T));
if(u_UseAnisotropic) {
vec3 anisotropyDir = normalize(u_AnisotropyDirection);
vec3 N_geom = normalize(v_Normal);
mat3 TBN = mat3(T, B, N_geom);
T = normalize(TBN * anisotropyDir);
B = normalize(cross(N, T));
}
vec3 N_geom = normalize(v_Normal);
float roughnessWeight = smoothstep(0.15, 0.75, 1.0 - roughness);
float grazingAttenuation = smoothstep(0.0, 0.4, NoV);
float normalWeight = roughnessWeight * grazingAttenuation;
normalWeight *= u_NormalStrength;
float dist = length(u_CameraPosition - v_WorldPos);
float distanceBoost = clamp(1.5 - dist * 0.15, 0.7, 1.5);
normalWeight *= distanceBoost;
N = normalize(mix(N_geom, N, normalWeight));
NoV = max(dot(N, V), 1e-4);
float roughnessAA = clamp(roughness, 0.04, 1.0);
if(u_UseNormalMap) {
float variance = normalVariance;
float kernelRoughness = min(2.0 * variance, 1.0);
float normalLength = length(texture(u_NormalMap, uv).xyz * 2.0 - 1.0);
float tokvsig = clamp((1.0 - normalLength), 0.0, 1.0);
roughnessAA = clamp(sqrt(roughnessAA * roughnessAA + kernelRoughness + tokvsig * 0.5 * u_ToksvigStrength), 0.04, 1.0);
}
roughness = max(roughnessAA, MIN_ROUGHNESS);
vec3 F0 = mix(vec3(F0_NON_METAL), albedo, metallic);
vec3 Lo = vec3(0.0);
{
vec3 L = normalize(-u_DirLightDirection);
vec3 radiance = u_DirLightColor * u_DirLightIntensity;
float shadow = 0.0;
if(u_EnableShadows != 0) {
shadow = calculateShadow(v_FragPosLightSpace, N, L);
}
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0 - shadow);
}
for(int i = 0; i < u_DirLightCount; i++) {
DirectionalLight light = u_DirLights[i];
vec3 L = normalize(-light.direction);
vec3 radiance = light.color * light.intensity;
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
for(int i = 0; i < u_PointLightCount; i++) {
PointLight light = u_PointLights[i];
vec3 L = light.position - v_WorldPos;
float lightDist = length(L);
if(lightDist > light.range) continue;
L = normalize(L);
float distanceAttenuation = 1.0 / (lightDist * lightDist + 1.0);
float windowFactor = sq(saturate(1.0 - sq(sq(lightDist / light.range))));
float attenuation = distanceAttenuation * windowFactor;
vec3 radiance = light.color * light.intensity * attenuation;
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
for(int i = 0; i < u_SpotLightCount; i++) {
SpotLight light = u_SpotLights[i];
vec3 L = light.position - v_WorldPos;
float lightDist = length(L);
if(lightDist > light.range) continue;
L = normalize(L);
float theta = dot(L, normalize(light.direction));
float epsilon = light.innerConeAngle - light.outerConeAngle;
float spotIntensity = saturate((theta - light.outerConeAngle) / epsilon);
if(spotIntensity <= 0.0) continue;
float distanceAttenuation = 1.0 / (lightDist * lightDist + 1.0);
float windowFactor = sq(saturate(1.0 - sq(sq(lightDist / light.range))));
float attenuation = distanceAttenuation * windowFactor;
vec3 radiance = light.color * light.intensity * attenuation * spotIntensity;
Lo += evaluateLighting(N, V, L, T, B, albedo, roughness, metallic, F0, NoV, radiance, 1.0);
}
vec3 ambient = vec3(0.0);
if(u_UseIBL) {
float NoV_ibl = max(dot(N, V), 1e-4);
vec3 F = fresnelSchlickRoughness(NoV_ibl, F0, roughness);
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
vec3 irradiance = texture(u_IrradianceMap, N).rgb;
if(dot(irradiance, irradiance) < 0.000001) irradiance = vec3(0.03);
vec3 diffuse = irradiance * albedo;
vec3 R = reflect(-V, N);
float lod = roughness * u_PrefilterMaxLOD;
vec3 prefilteredColor = textureLod(u_PrefilterMap, R, lod).rgb;
if(dot(prefilteredColor, prefilteredColor) < 0.000001) prefilteredColor = vec3(0.03);
vec2 brdf = texture(u_BRDFLT, vec2(NoV_ibl, roughness)).rg;
float specOcclusion = computeSpecularOcclusion(NoV_ibl, ao, roughness);
vec3 specular = prefilteredColor * (F * brdf.x + brdf.y) * specOcclusion;
ambient = (kD * diffuse + specular) * ao;
if(u_UseClearCoat && u_ClearCoat > 0.01) {
vec3 clearCoatR = reflect(-V, normalize(v_Normal));
float clearCoatLod = u_ClearCoatRoughness * u_PrefilterMaxLOD;
vec3 clearCoatPrefilteredColor = textureLod(u_PrefilterMap, clearCoatR, clearCoatLod).rgb;
float Fc = F_Schlick_ClearCoat(NoV_ibl) * u_ClearCoat;
ambient *= (1.0 - Fc);
ambient += clearCoatPrefilteredColor * Fc;
}
} else {
ambient = vec3(u_AmbientIntensity) * albedo * ao;
}
vec3 color = ambient + Lo;
color = ACESFilm(color);
color = pow(color, vec3(1.0 / 2.2));
o_Color = vec4(color, 1.0);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,63 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
layout(location = 4) in ivec4 a_BoneIDs;
layout(location = 5) in vec4 a_BoneWeights;
// Model uniform buffer (binding = 1)
layout(std140, binding = 1) uniform ModelData
{
mat4 u_Model;
mat4 u_NormalMatrix;
int u_ObjectID;
};
// Light uniform buffer (binding = 2)
layout(std140, binding = 2) uniform LightData
{
mat4 u_LightSpaceMatrix;
vec3 u_DirLightDirection;
float u_DirLightIntensity;
vec3 u_DirLightColor;
float _lightPadding0;
float u_ShadowBias;
float u_ShadowSoftness;
int u_EnableShadows;
float _lightPadding1;
float u_AmbientIntensity;
int u_NumPointLights;
int u_NumSpotLights;
};
// Bone uniform buffer (binding = 4)
layout(std140, binding = 4) uniform BoneData
{
mat4 u_BoneMatrices[128];
};
void main() {
// Compute skin matrix from bone influences
mat4 skinMatrix = mat4(0.0);
for (int i = 0; i < 4; i++) {
if (a_BoneIDs[i] >= 0) {
skinMatrix += a_BoneWeights[i] * u_BoneMatrices[a_BoneIDs[i]];
}
}
// If no bones affect this vertex, use identity
if (a_BoneWeights[0] == 0.0 && a_BoneWeights[1] == 0.0 &&
a_BoneWeights[2] == 0.0 && a_BoneWeights[3] == 0.0) {
skinMatrix = mat4(1.0);
}
vec4 skinnedPos = skinMatrix * vec4(a_Position, 1.0);
gl_Position = u_LightSpaceMatrix * u_Model * skinnedPos;
}
#type fragment
#version 450 core
void main() {
}

View File

@@ -0,0 +1,35 @@
#type vertex
#version 450 core
layout(location = 0) in vec3 a_Position;
out vec3 v_TexCoords;
uniform mat4 u_View;
uniform mat4 u_Projection;
void main() {
v_TexCoords = a_Position;
mat4 view = mat4(mat3(u_View));
vec4 clipPos = u_Projection * view * vec4(a_Position, 1.0);
gl_Position = vec4(clipPos.xy, clipPos.w, clipPos.w); // Force depth to far plane to avoid occluding scene geometry
}
#type fragment
#version 450 core
in vec3 v_TexCoords;
out vec4 FragColor;
uniform samplerCube u_Cubemap;
void main() {
vec3 color = texture(u_Cubemap, v_TexCoords).rgb;
// HDR色调映射 (Reinhard)
color = color / (color + vec3(1.0));
// Gamma校正
color = pow(color, vec3(1.0/2.2));
FragColor = vec4(color, 1.0);
}

View File

@@ -0,0 +1,61 @@
#type vertex
#version 450 core
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 int a_ObjectID;
// Camera uniform buffer (binding = 0)
layout(std140, binding = 0) uniform CameraData
{
mat4 u_ViewProjection;
mat4 u_View;
mat4 u_Projection;
vec3 u_CameraPosition;
};
out vec4 v_Color;
out vec2 v_TexCoord;
flat out int v_ObjectID;
void main()
{
v_Color = a_Color;
v_TexCoord = a_TexCoord;
v_ObjectID = a_ObjectID;
gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
}
#type fragment
#version 450 core
layout(location = 0) out vec4 o_Color;
layout(location = 1) out int o_ObjectID;
in vec4 v_Color;
in vec2 v_TexCoord;
flat in int v_ObjectID;
uniform sampler2D u_Atlas;
float median(float r, float g, float b)
{
return max(min(r, g), min(max(r, g), b));
}
void main()
{
vec3 msd = texture(u_Atlas, v_TexCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
// Smooth threshold around 0.5 (MSDF center)
float alpha = smoothstep(0.5 - 0.1, 0.5 + 0.1, sd);
if (alpha <= 0.0)
discard;
o_Color = vec4(v_Color.rgb, v_Color.a * alpha);
o_ObjectID = v_ObjectID;
}

View File

@@ -0,0 +1,93 @@
Copyright 2010 The Cuprum Project Authors (lemonad@jovanny.ru), with Reserved Font Name "Cuprum".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,93 @@
Copyright (c) 2011, Jonas Hecksher, Playtypes, e-types AS (lasse@e-types.com), with Reserved Font Name 'Play', 'Playtype', 'Playtype Sans'.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -0,0 +1,65 @@
Material:
Type: 2
Name: Material_MR
Albedo: [1, 1, 1]
Metallic: 1
Roughness: 1
AO: 1
Emissive: [0, 0, 0]
EmissiveIntensity: 1
AlbedoMap: 2493715504153704453
NormalMap: 7553078236290796322
MetallicMap: 16026405867881152170
RoughnessMap: 16026405867881152170
AOMap: 5345533721503086083
EmissiveMap: 10970895745636314528
Editor:
NextNodeID: 8
NextLinkID: 7
Nodes:
- ID: 1
Type: 0
Position: [400, 100]
- ID: 2
Type: 1
Position: [50, 0]
TextureHandle: 2493715504153704453
- ID: 3
Type: 1
Position: [50, 150]
TextureHandle: 7553078236290796322
- ID: 4
Type: 1
Position: [50, 300]
TextureHandle: 16026405867881152170
- ID: 5
Type: 1
Position: [50, 450]
TextureHandle: 16026405867881152170
- ID: 6
Type: 1
Position: [50, 600]
TextureHandle: 5345533721503086083
- ID: 7
Type: 1
Position: [39, 770]
TextureHandle: 10970895745636314528
Links:
- ID: 1
StartPin: 201
EndPin: 101
- ID: 2
StartPin: 301
EndPin: 102
- ID: 3
StartPin: 401
EndPin: 103
- ID: 4
StartPin: 501
EndPin: 104
- ID: 5
StartPin: 601
EndPin: 105
- ID: 6
StartPin: 701
EndPin: 106

View File

@@ -0,0 +1,5 @@
Asset:
Handle: 12539830590652719771
Type: Material
Name: Material_MR
FilePath: Assets/Materials/Material_MR.fmat

View File

@@ -0,0 +1,9 @@
TextureAsset:
SourcePath: Assets/Materials/Plastic/albedo.png
GenerateMipmaps: true
MinFilter: Linear
MagFilter: Linear
WrapS: Repeat
WrapT: Repeat
Anisotropy: 1
sRGB: false

View File

@@ -0,0 +1,5 @@
Asset:
Handle: 10678462134903437624
Type: Texture
Name: albedo
FilePath: Assets/Materials/Plastic/albedo.ftex

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -0,0 +1,9 @@
TextureAsset:
SourcePath: Assets/Materials/Plastic/ao.png
GenerateMipmaps: true
MinFilter: Linear
MagFilter: Linear
WrapS: Repeat
WrapT: Repeat
Anisotropy: 1
sRGB: false

View File

@@ -0,0 +1,5 @@
Asset:
Handle: 4645923213129979568
Type: Texture
Name: ao
FilePath: Assets/Materials/Plastic/ao.ftex

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -0,0 +1,9 @@
TextureAsset:
SourcePath: Assets/Materials/Plastic/normal.png
GenerateMipmaps: true
MinFilter: Linear
MagFilter: Linear
WrapS: Repeat
WrapT: Repeat
Anisotropy: 1
sRGB: false

View File

@@ -0,0 +1,5 @@
Asset:
Handle: 12443521726984459823
Type: Texture
Name: normal
FilePath: Assets/Materials/Plastic/normal.ftex

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

@@ -0,0 +1,9 @@
TextureAsset:
SourcePath: Assets/Materials/Plastic/roughness.png
GenerateMipmaps: true
MinFilter: Linear
MagFilter: Linear
WrapS: Repeat
WrapT: Repeat
Anisotropy: 1
sRGB: false

View File

@@ -0,0 +1,5 @@
Asset:
Handle: 13216614171579863546
Type: Texture
Name: roughness
FilePath: Assets/Materials/Plastic/roughness.ftex

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

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