mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Compare commits
89 commits
05d1c874d9
...
06979e5c5c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06979e5c5c | ||
|
|
bf997e1900 | ||
|
|
9a1b78bffd | ||
|
|
d65b170774 | ||
|
|
5072c536b5 | ||
|
|
0a1e240831 | ||
|
|
c3ed915649 | ||
|
|
52ef199af3 | ||
|
|
b70f08d14f | ||
|
|
5cfb0817ed | ||
|
|
a250c20d84 | ||
|
|
83d7f26f33 | ||
|
|
59f487c941 | ||
|
|
a7102ab742 | ||
|
|
9dd15ef922 | ||
|
|
01c08b93b0 | ||
|
|
4db97e37b7 | ||
|
|
c35b40391f | ||
|
|
93f873b521 | ||
|
|
3e6de8485b | ||
|
|
1e08d6ff22 | ||
|
|
b7da2408c8 | ||
|
|
f2f6ffd2cd | ||
|
|
efc7e65dfc | ||
|
|
77012adbc6 | ||
|
|
6fc2c36dae | ||
|
|
639df4815e | ||
|
|
58681753e5 | ||
|
|
a58115041f | ||
|
|
e98450f283 | ||
|
|
590131590c | ||
|
|
30e9998a86 | ||
|
|
7dc9bf3766 | ||
|
|
2124761ea8 | ||
|
|
2cfa9d6b19 | ||
|
|
9e1a913060 | ||
|
|
820a36ac12 | ||
|
|
e8c2344226 | ||
|
|
98fb6b47da | ||
|
|
ae3903561c | ||
|
|
ef1e5abe8e | ||
|
|
4511de8d38 | ||
|
|
a7cf0d0c4e | ||
|
|
3ffb7ccc50 | ||
|
|
4acba4110f | ||
|
|
bb67bfb9a3 | ||
|
|
9eeb9ce64d | ||
|
|
3c31c43ca6 | ||
|
|
b12bc1f71e | ||
|
|
bec3190b08 | ||
|
|
eaceb58e77 | ||
|
|
1b16bcf71f | ||
|
|
fb4ff46fe3 | ||
|
|
6563eebb60 | ||
|
|
085fd09b9d | ||
|
|
03a62526e1 | ||
|
|
67e63653a4 | ||
|
|
2c5e0dd313 | ||
|
|
bd0305f6dd | ||
|
|
8efc1548dc | ||
|
|
0631b9f5dc | ||
|
|
37888c666d | ||
|
|
3a9bd0d4e5 | ||
|
|
17a2a1f7ef | ||
|
|
9c8cd44803 | ||
|
|
f4d947fab1 | ||
|
|
6d55c19987 | ||
|
|
ae88b226b5 | ||
|
|
c914295d20 | ||
|
|
4ea4cb761c | ||
|
|
85c8b5d5f4 | ||
|
|
9d647e5622 | ||
|
|
57b049fb2a | ||
|
|
7dd1dada5f | ||
|
|
fc5294eb0f | ||
|
|
e8e859384e | ||
|
|
ebd0084c22 | ||
|
|
325254dfcb | ||
|
|
b1a9d231c7 | ||
|
|
786b35ae0d | ||
|
|
fa1867cf2f | ||
|
|
e12141a673 | ||
|
|
6d213ad49b | ||
|
|
25bd05a631 | ||
|
|
b012906887 | ||
|
|
4fc3689dcc | ||
|
|
69cf39ba02 | ||
|
|
dea52744a4 | ||
|
|
83b576e8d9 |
242 changed files with 21421 additions and 9555 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -89,3 +89,4 @@ ingest/
|
|||
|
||||
# Local texture dumps / extracted art should never be committed
|
||||
assets/textures/
|
||||
node_modules/
|
||||
|
|
|
|||
|
|
@ -1,146 +1,88 @@
|
|||
# Build Instructions
|
||||
# WoWee Build Instructions
|
||||
|
||||
This project builds as a native C++ client for WoW 3.3.5a in online mode.
|
||||
This document provides platform-specific build instructions for WoWee.
|
||||
|
||||
## 1. Install Dependencies
|
||||
---
|
||||
|
||||
### Ubuntu / Debian
|
||||
## 🐧 Linux (Ubuntu / Debian)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
cmake build-essential pkg-config git \
|
||||
libsdl2-dev libglew-dev libglm-dev \
|
||||
libssl-dev zlib1g-dev \
|
||||
libavformat-dev libavcodec-dev libswscale-dev libavutil-dev \
|
||||
libstorm-dev
|
||||
sudo apt install -y build-essential cmake pkg-config git libsdl2-dev libglew-dev libglm-dev libssl-dev zlib1g-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libunicorn-dev libstorm-dev
|
||||
```
|
||||
|
||||
If `libstorm-dev` is unavailable in your distro repos, build StormLib from source:
|
||||
---
|
||||
|
||||
## 🐧 Linux (Arch)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
cd /tmp
|
||||
git clone https://github.com/ladislav-zezula/StormLib.git
|
||||
cd StormLib
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j"$(nproc)"
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
sudo pacman -S --needed base-devel cmake pkgconf git sdl2 glew glm openssl zlib ffmpeg unicorn stormlib
|
||||
```
|
||||
|
||||
### Fedora
|
||||
---
|
||||
|
||||
## 🐧 Linux (All Distros)
|
||||
|
||||
### Clone Repository
|
||||
|
||||
Always clone with submodules:
|
||||
|
||||
```bash
|
||||
sudo dnf install -y \
|
||||
cmake gcc-c++ make pkg-config git \
|
||||
SDL2-devel glew-devel glm-devel \
|
||||
openssl-devel zlib-devel \
|
||||
ffmpeg-devel \
|
||||
StormLib-devel
|
||||
git clone --recurse-submodules https://github.com/Kelsidavis/WoWee.git
|
||||
cd WoWee
|
||||
```
|
||||
|
||||
### Arch
|
||||
If you already cloned without submodules:
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed \
|
||||
cmake base-devel pkgconf git \
|
||||
sdl2 glew glm openssl zlib ffmpeg stormlib
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## 2. Clone + Prepare
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Kelsidavis/WoWee.git
|
||||
cd wowee
|
||||
git clone https://github.com/ocornut/imgui.git extern/imgui
|
||||
```
|
||||
|
||||
## 3. Configure + Build
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j"$(nproc)"
|
||||
```
|
||||
|
||||
Binary output:
|
||||
---
|
||||
|
||||
```text
|
||||
build/bin/wowee
|
||||
## 🪟 Windows (Visual Studio 2022)
|
||||
|
||||
### Install
|
||||
|
||||
- Visual Studio 2022
|
||||
- Desktop development with C++
|
||||
- CMake tools for Windows
|
||||
|
||||
### Clone
|
||||
|
||||
```powershell
|
||||
git clone --recurse-submodules https://github.com/Kelsidavis/WoWee.git
|
||||
cd WoWee
|
||||
```
|
||||
|
||||
## 4. Provide WoW Data (Extract + Manifest)
|
||||
### Build
|
||||
|
||||
Wowee loads assets from an extracted loose-file tree indexed by `manifest.json` (it does not read MPQs at runtime).
|
||||
Open the folder in Visual Studio (it will detect CMake automatically)
|
||||
or build from Developer PowerShell:
|
||||
|
||||
### Option A: Extract into `./Data/` (recommended)
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
# WotLK 3.3.5a example
|
||||
./extract_assets.sh /path/to/WoW/Data wotlk
|
||||
```
|
||||
|
||||
The output includes:
|
||||
|
||||
```text
|
||||
Data/
|
||||
manifest.json
|
||||
interface/
|
||||
sound/
|
||||
world/
|
||||
expansions/
|
||||
```
|
||||
|
||||
### Option B: Use an existing extracted data tree
|
||||
|
||||
Point wowee at your extracted `Data/` directory:
|
||||
|
||||
```bash
|
||||
export WOW_DATA_PATH=/path/to/extracted/Data
|
||||
```
|
||||
|
||||
## 5. Run
|
||||
|
||||
```bash
|
||||
./build/bin/wowee
|
||||
```
|
||||
|
||||
## 6. Local AzerothCore (Optional)
|
||||
|
||||
If you are using a local AzerothCore Docker stack, start it first and then connect from the client realm screen.
|
||||
|
||||
See:
|
||||
|
||||
- `docs/server-setup.md`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `StormLib` not found
|
||||
|
||||
Install distro package or build from source (section 1).
|
||||
|
||||
### `ImGui` missing
|
||||
|
||||
Ensure `extern/imgui` exists:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ocornut/imgui.git extern/imgui
|
||||
```
|
||||
|
||||
### Data not found at runtime
|
||||
|
||||
Verify `Data/manifest.json` exists (or re-run `./extract_assets.sh ...`), or set:
|
||||
|
||||
```bash
|
||||
export WOW_DATA_PATH=/path/to/extracted/Data
|
||||
```
|
||||
|
||||
### Clean rebuild
|
||||
|
||||
```bash
|
||||
rm -rf build
|
||||
```powershell
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j"$(nproc)"
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Notes
|
||||
|
||||
- Case matters on Linux (`WoWee` not `wowee`).
|
||||
- Always use `--recurse-submodules` when cloning.
|
||||
- If you encounter missing headers for ImGui, run:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
|
|
|||
224
CMakeLists.txt
224
CMakeLists.txt
|
|
@ -14,6 +14,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|||
# Options
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
|
||||
option(WOWEE_BUILD_TESTS "Build tests" OFF)
|
||||
option(WOWEE_ENABLE_ASAN "Enable AddressSanitizer (Debug builds)" OFF)
|
||||
|
||||
# Opcode registry generation/validation
|
||||
find_package(Python3 COMPONENTS Interpreter QUIET)
|
||||
|
|
@ -34,8 +35,13 @@ endif()
|
|||
|
||||
# Find required packages
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
find_package(Vulkan REQUIRED)
|
||||
# GL/GLEW kept temporarily for unconverted sub-renderers during Vulkan migration.
|
||||
# These files compile against GL types but their code is never called — the Vulkan
|
||||
# path is the only active rendering backend. Remove in Phase 7 when all renderers
|
||||
# are converted and grep confirms zero GL references.
|
||||
find_package(OpenGL QUIET)
|
||||
find_package(GLEW QUIET)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
|
@ -45,7 +51,7 @@ else()
|
|||
find_package(PkgConfig REQUIRED)
|
||||
endif()
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(FFMPEG REQUIRED libavformat libavcodec libswscale libavutil)
|
||||
pkg_check_modules(FFMPEG libavformat libavcodec libswscale libavutil)
|
||||
else()
|
||||
# Fallback for MSVC/vcpkg — find FFmpeg libraries manually
|
||||
find_path(FFMPEG_INCLUDE_DIRS libavformat/avformat.h)
|
||||
|
|
@ -54,9 +60,15 @@ else()
|
|||
find_library(AVUTIL_LIB NAMES avutil)
|
||||
find_library(SWSCALE_LIB NAMES swscale)
|
||||
set(FFMPEG_LIBRARIES ${AVFORMAT_LIB} ${AVCODEC_LIB} ${AVUTIL_LIB} ${SWSCALE_LIB})
|
||||
if(NOT AVFORMAT_LIB)
|
||||
message(FATAL_ERROR "FFmpeg not found. On Windows install via MSYS2: mingw-w64-x86_64-ffmpeg")
|
||||
endif()
|
||||
endif()
|
||||
if(FFMPEG_INCLUDE_DIRS AND AVFORMAT_LIB)
|
||||
set(HAVE_FFMPEG TRUE)
|
||||
message(STATUS "Found FFmpeg: ${AVFORMAT_LIB}")
|
||||
elseif(FFMPEG_FOUND)
|
||||
set(HAVE_FFMPEG TRUE)
|
||||
else()
|
||||
set(HAVE_FFMPEG FALSE)
|
||||
message(WARNING "FFmpeg not found — video_player will be disabled. Install via vcpkg: ffmpeg:x64-windows")
|
||||
endif()
|
||||
|
||||
# Unicorn Engine (x86 emulator for cross-platform Warden module execution)
|
||||
|
|
@ -77,13 +89,68 @@ if(NOT glm_FOUND)
|
|||
message(STATUS "GLM not found, will use system includes or download")
|
||||
endif()
|
||||
# GLM GTX extensions (quaternion, norm, etc.) require this flag on newer GLM versions
|
||||
add_compile_definitions(GLM_ENABLE_EXPERIMENTAL)
|
||||
add_compile_definitions(GLM_ENABLE_EXPERIMENTAL GLM_FORCE_DEPTH_ZERO_TO_ONE)
|
||||
if(WIN32)
|
||||
add_compile_definitions(NOMINMAX _CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
# SPIR-V shader compilation via glslc
|
||||
find_program(GLSLC glslc HINTS ${Vulkan_GLSLC_EXECUTABLE} "$ENV{VULKAN_SDK}/bin")
|
||||
if(GLSLC)
|
||||
message(STATUS "Found glslc: ${GLSLC}")
|
||||
else()
|
||||
message(WARNING "glslc not found. Install the Vulkan SDK or vulkan-tools package.")
|
||||
message(WARNING "Shaders will not be compiled to SPIR-V.")
|
||||
endif()
|
||||
|
||||
# Function to compile GLSL shaders to SPIR-V
|
||||
function(compile_shaders TARGET_NAME)
|
||||
set(SHADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders)
|
||||
set(SPV_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets/shaders)
|
||||
file(MAKE_DIRECTORY ${SPV_DIR})
|
||||
|
||||
file(GLOB GLSL_SOURCES "${SHADER_DIR}/*.glsl")
|
||||
set(SPV_OUTPUTS)
|
||||
|
||||
foreach(GLSL_FILE ${GLSL_SOURCES})
|
||||
get_filename_component(FILE_NAME ${GLSL_FILE} NAME)
|
||||
# e.g. skybox.vert.glsl -> skybox.vert.spv
|
||||
string(REGEX REPLACE "\\.glsl$" ".spv" SPV_NAME ${FILE_NAME})
|
||||
set(SPV_FILE ${SPV_DIR}/${SPV_NAME})
|
||||
|
||||
# Determine shader stage from filename
|
||||
if(FILE_NAME MATCHES "\\.vert\\.glsl$")
|
||||
set(SHADER_STAGE vertex)
|
||||
elseif(FILE_NAME MATCHES "\\.frag\\.glsl$")
|
||||
set(SHADER_STAGE fragment)
|
||||
elseif(FILE_NAME MATCHES "\\.comp\\.glsl$")
|
||||
set(SHADER_STAGE compute)
|
||||
elseif(FILE_NAME MATCHES "\\.geom\\.glsl$")
|
||||
set(SHADER_STAGE geometry)
|
||||
else()
|
||||
message(WARNING "Cannot determine shader stage for: ${FILE_NAME}")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${SPV_FILE}
|
||||
COMMAND ${GLSLC} -fshader-stage=${SHADER_STAGE} -O ${GLSL_FILE} -o ${SPV_FILE}
|
||||
DEPENDS ${GLSL_FILE}
|
||||
COMMENT "Compiling SPIR-V: ${FILE_NAME} -> ${SPV_NAME}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND SPV_OUTPUTS ${SPV_FILE})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(${TARGET_NAME}_shaders ALL DEPENDS ${SPV_OUTPUTS})
|
||||
add_dependencies(${TARGET_NAME} ${TARGET_NAME}_shaders)
|
||||
endfunction()
|
||||
|
||||
# StormLib for MPQ extraction tool (not needed for main executable)
|
||||
find_library(STORMLIB_LIBRARY NAMES StormLib stormlib storm)
|
||||
find_path(STORMLIB_INCLUDE_DIR StormLib.h PATH_SUFFIXES StormLib)
|
||||
|
||||
# Include ImGui as a static library (we'll add the sources)
|
||||
# Include ImGui as a static library (Vulkan backend)
|
||||
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/extern/imgui)
|
||||
if(EXISTS ${IMGUI_DIR})
|
||||
add_library(imgui STATIC
|
||||
|
|
@ -93,19 +160,30 @@ if(EXISTS ${IMGUI_DIR})
|
|||
${IMGUI_DIR}/imgui_widgets.cpp
|
||||
${IMGUI_DIR}/imgui_demo.cpp
|
||||
${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp
|
||||
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
|
||||
${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp
|
||||
)
|
||||
target_include_directories(imgui PUBLIC
|
||||
${IMGUI_DIR}
|
||||
${IMGUI_DIR}/backends
|
||||
)
|
||||
target_link_libraries(imgui PUBLIC SDL2::SDL2 OpenGL::GL ${CMAKE_DL_LIBS})
|
||||
target_compile_definitions(imgui PUBLIC IMGUI_IMPL_OPENGL_LOADER_GLEW)
|
||||
target_link_libraries(imgui PUBLIC SDL2::SDL2 Vulkan::Vulkan ${CMAKE_DL_LIBS})
|
||||
else()
|
||||
message(WARNING "ImGui not found in extern/imgui. Clone it with:")
|
||||
message(WARNING " git clone https://github.com/ocornut/imgui.git extern/imgui")
|
||||
endif()
|
||||
|
||||
# vk-bootstrap (Vulkan device/instance setup)
|
||||
set(VK_BOOTSTRAP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/extern/vk-bootstrap)
|
||||
if(EXISTS ${VK_BOOTSTRAP_DIR})
|
||||
add_library(vk-bootstrap STATIC
|
||||
${VK_BOOTSTRAP_DIR}/src/VkBootstrap.cpp
|
||||
)
|
||||
target_include_directories(vk-bootstrap PUBLIC ${VK_BOOTSTRAP_DIR}/src)
|
||||
target_link_libraries(vk-bootstrap PUBLIC Vulkan::Vulkan)
|
||||
else()
|
||||
message(FATAL_ERROR "vk-bootstrap not found in extern/vk-bootstrap")
|
||||
endif()
|
||||
|
||||
# Source files
|
||||
set(WOWEE_SOURCES
|
||||
# Core
|
||||
|
|
@ -180,6 +258,15 @@ set(WOWEE_SOURCES
|
|||
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
# Rendering (Vulkan infrastructure)
|
||||
src/rendering/vk_context.cpp
|
||||
src/rendering/vk_utils.cpp
|
||||
src/rendering/vk_shader.cpp
|
||||
src/rendering/vk_texture.cpp
|
||||
src/rendering/vk_buffer.cpp
|
||||
src/rendering/vk_pipeline.cpp
|
||||
src/rendering/vk_render_target.cpp
|
||||
|
||||
# Rendering
|
||||
src/rendering/renderer.cpp
|
||||
src/rendering/shader.cpp
|
||||
|
|
@ -215,7 +302,7 @@ set(WOWEE_SOURCES
|
|||
src/rendering/levelup_effect.cpp
|
||||
src/rendering/charge_effect.cpp
|
||||
src/rendering/loading_screen.cpp
|
||||
src/rendering/video_player.cpp
|
||||
$<$<BOOL:${HAVE_FFMPEG}>:${CMAKE_CURRENT_SOURCE_DIR}/src/rendering/video_player.cpp>
|
||||
|
||||
# UI
|
||||
src/ui/ui_manager.cpp
|
||||
|
|
@ -287,6 +374,13 @@ set(WOWEE_HEADERS
|
|||
include/pipeline/dbc_loader.hpp
|
||||
include/pipeline/terrain_mesh.hpp
|
||||
|
||||
include/rendering/vk_context.hpp
|
||||
include/rendering/vk_utils.hpp
|
||||
include/rendering/vk_shader.hpp
|
||||
include/rendering/vk_texture.hpp
|
||||
include/rendering/vk_buffer.hpp
|
||||
include/rendering/vk_pipeline.hpp
|
||||
include/rendering/vk_render_target.hpp
|
||||
include/rendering/renderer.hpp
|
||||
include/rendering/shader.hpp
|
||||
include/rendering/texture.hpp
|
||||
|
|
@ -343,19 +437,26 @@ if(TARGET opcodes-generate)
|
|||
add_dependencies(wowee opcodes-generate)
|
||||
endif()
|
||||
|
||||
# Compile GLSL shaders to SPIR-V
|
||||
if(GLSLC)
|
||||
compile_shaders(wowee)
|
||||
endif()
|
||||
|
||||
# Include directories
|
||||
target_include_directories(wowee PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/extern
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/extern/vk-bootstrap/src
|
||||
)
|
||||
if(HAVE_FFMPEG)
|
||||
target_include_directories(wowee PRIVATE ${FFMPEG_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(wowee PRIVATE
|
||||
SDL2::SDL2
|
||||
OpenGL::GL
|
||||
GLEW::GLEW
|
||||
Vulkan::Vulkan
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
Threads::Threads
|
||||
|
|
@ -363,9 +464,20 @@ target_link_libraries(wowee PRIVATE
|
|||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
target_link_libraries(wowee PRIVATE ${FFMPEG_LIBRARIES})
|
||||
if (FFMPEG_LIBRARY_DIRS)
|
||||
target_link_directories(wowee PRIVATE ${FFMPEG_LIBRARY_DIRS})
|
||||
# GL/GLEW linked temporarily for unconverted sub-renderers (removed in Phase 7)
|
||||
if(TARGET OpenGL::GL)
|
||||
target_link_libraries(wowee PRIVATE OpenGL::GL)
|
||||
endif()
|
||||
if(TARGET GLEW::GLEW)
|
||||
target_link_libraries(wowee PRIVATE GLEW::GLEW)
|
||||
endif()
|
||||
|
||||
if(HAVE_FFMPEG)
|
||||
target_compile_definitions(wowee PRIVATE HAVE_FFMPEG)
|
||||
target_link_libraries(wowee PRIVATE ${FFMPEG_LIBRARIES})
|
||||
if(FFMPEG_LIBRARY_DIRS)
|
||||
target_link_directories(wowee PRIVATE ${FFMPEG_LIBRARY_DIRS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Platform-specific libraries
|
||||
|
|
@ -385,6 +497,11 @@ if(TARGET imgui)
|
|||
target_link_libraries(wowee PRIVATE imgui)
|
||||
endif()
|
||||
|
||||
# Link vk-bootstrap
|
||||
if(TARGET vk-bootstrap)
|
||||
target_link_libraries(wowee PRIVATE vk-bootstrap)
|
||||
endif()
|
||||
|
||||
# Link Unicorn if available
|
||||
if(HAVE_UNICORN)
|
||||
target_link_libraries(wowee PRIVATE ${UNICORN_LIBRARY})
|
||||
|
|
@ -406,6 +523,47 @@ else()
|
|||
target_compile_options(wowee PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
# Debug build flags
|
||||
if(MSVC)
|
||||
# /ZI — Edit-and-Continue debug info (works with hot-reload in VS 2022)
|
||||
# /RTC1 — stack-frame and uninitialised-variable runtime checks (Debug only)
|
||||
# /sdl — additional SDL security checks
|
||||
# /Od — disable optimisation so stepping matches source lines exactly
|
||||
target_compile_options(wowee PRIVATE
|
||||
$<$<CONFIG:Debug>:/ZI /RTC1 /sdl /Od>
|
||||
)
|
||||
# Ensure the linker emits a .pdb alongside the .exe for every config
|
||||
target_link_options(wowee PRIVATE
|
||||
$<$<CONFIG:Debug>:/DEBUG:FULL>
|
||||
$<$<CONFIG:RelWithDebInfo>:/DEBUG:FASTLINK>
|
||||
)
|
||||
else()
|
||||
# -g3 — maximum DWARF debug info (includes macro definitions)
|
||||
# -Og — optimise for debugging (better than -O0, keeps most frames)
|
||||
# -fno-omit-frame-pointer — preserve frame pointers so stack traces are clean
|
||||
target_compile_options(wowee PRIVATE
|
||||
$<$<CONFIG:Debug>:-g3 -Og -fno-omit-frame-pointer>
|
||||
$<$<CONFIG:RelWithDebInfo>:-g -fno-omit-frame-pointer>
|
||||
)
|
||||
endif()
|
||||
|
||||
# AddressSanitizer — catch buffer overflows, use-after-free, etc.
|
||||
# Enable with: cmake ... -DWOWEE_ENABLE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
if(WOWEE_ENABLE_ASAN)
|
||||
if(MSVC)
|
||||
target_compile_options(wowee PRIVATE /fsanitize=address)
|
||||
# ASAN on MSVC requires the dynamic CRT (/MD or /MDd)
|
||||
target_compile_options(wowee PRIVATE
|
||||
$<$<CONFIG:Debug>:/MDd>
|
||||
$<$<CONFIG:Release>:/MD>
|
||||
)
|
||||
else()
|
||||
target_compile_options(wowee PRIVATE -fsanitize=address -fno-omit-frame-pointer)
|
||||
target_link_options(wowee PRIVATE -fsanitize=address)
|
||||
endif()
|
||||
message(STATUS "AddressSanitizer: ENABLED")
|
||||
endif()
|
||||
|
||||
# Release build optimizations
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT _ipo_supported OUTPUT _ipo_error)
|
||||
|
|
@ -420,9 +578,27 @@ if(NOT MSVC)
|
|||
target_compile_options(wowee PRIVATE $<$<CONFIG:Release>:-fvisibility=hidden -fvisibility-inlines-hidden>)
|
||||
endif()
|
||||
|
||||
# Copy assets to build directory
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets
|
||||
DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
# Copy assets next to the executable (runs every build, not just configure).
|
||||
# Uses $<TARGET_FILE_DIR:wowee> so MSVC multi-config generators place assets
|
||||
# in bin/Debug/ or bin/Release/ alongside the exe, not the common bin/ parent.
|
||||
add_custom_command(TARGET wowee POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/assets
|
||||
$<TARGET_FILE_DIR:wowee>/assets
|
||||
COMMENT "Syncing assets to $<TARGET_FILE_DIR:wowee>/assets"
|
||||
)
|
||||
|
||||
# On Windows, SDL 2.28+ uses LoadLibraryExW with LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
|
||||
# which does NOT include System32. Copy vulkan-1.dll into the output directory so
|
||||
# SDL_Vulkan_LoadLibrary can locate it without needing a full system PATH search.
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET wowee POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"$ENV{SystemRoot}/System32/vulkan-1.dll"
|
||||
"$<TARGET_FILE_DIR:wowee>/vulkan-1.dll"
|
||||
COMMENT "Copying vulkan-1.dll to output directory"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install targets
|
||||
install(TARGETS wowee
|
||||
|
|
@ -482,6 +658,9 @@ if(STORMLIB_LIBRARY AND STORMLIB_INCLUDE_DIR)
|
|||
ZLIB::ZLIB
|
||||
Threads::Threads
|
||||
)
|
||||
if(WIN32)
|
||||
target_link_libraries(asset_extract PRIVATE wininet bz2)
|
||||
endif()
|
||||
set_target_properties(asset_extract PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
)
|
||||
|
|
@ -575,6 +754,7 @@ message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
|
|||
message(STATUS " SDL2: ${SDL2_VERSION}")
|
||||
message(STATUS " OpenSSL: ${OPENSSL_VERSION}")
|
||||
message(STATUS " ImGui: ${IMGUI_DIR}")
|
||||
message(STATUS " ASAN: ${WOWEE_ENABLE_ASAN}")
|
||||
message(STATUS "")
|
||||
|
||||
# ---- CPack packaging ----
|
||||
|
|
@ -628,7 +808,7 @@ chmod +x /usr/local/bin/wowee
|
|||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Wowee")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"libsdl2-2.0-0, libglew2.2 | libglew2.1, libssl3, zlib1g")
|
||||
"libsdl2-2.0-0, libvulkan1, libssl3, zlib1g")
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/packaging/postinst;${CMAKE_CURRENT_BINARY_DIR}/packaging/prerm")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
||||
|
|
|
|||
55
README.md
55
README.md
|
|
@ -4,22 +4,23 @@
|
|||
<img src="assets/Wowee.png" alt="Wowee Logo" width="240" />
|
||||
</p>
|
||||
|
||||
A native C++ World of Warcraft client with a custom OpenGL renderer.
|
||||
A native C++ World of Warcraft client with a custom Vulkan renderer.
|
||||
|
||||
[](https://github.com/sponsors/Kelsidavis)
|
||||
[](https://discord.gg/SDqjA79B)
|
||||
|
||||
[](https://youtu.be/Pd9JuYYxu0o)
|
||||
|
||||
[](https://youtu.be/J4NXegzqWSQ)
|
||||
|
||||
Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. All three expansions are broadly functional with roughly even support.
|
||||
Protocol Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**.
|
||||
|
||||
> **Legal Disclaimer**: This is an educational/research project. It does not include any Blizzard Entertainment assets, data files, or proprietary code. World of Warcraft and all related assets are the property of Blizzard Entertainment, Inc. This project is not affiliated with or endorsed by Blizzard Entertainment. Users are responsible for supplying their own legally obtained game data files and for ensuring compliance with all applicable laws in their jurisdiction.
|
||||
|
||||
## Status & Direction (2026-02-18)
|
||||
|
||||
- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all broadly supported via expansion profiles and per-expansion packet parsers (`src/game/packet_parsers_classic.cpp`, `src/game/packet_parsers_tbc.cpp`). All three expansions are roughly on par — no single one is significantly more complete than the others.
|
||||
- **Tested against**: AzerothCore, TrinityCore, and ChromieCraft.
|
||||
- **Compatibility**: **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a** are all supported via expansion profiles and per-expansion packet parsers (`src/game/packet_parsers_classic.cpp`, `src/game/packet_parsers_tbc.cpp`). All three expansions are roughly on par — no single one is significantly more complete than the others.
|
||||
- **Tested against**: AzerothCore, TrinityCore, and Mangos.
|
||||
- **Current focus**: protocol correctness across server variants, visual accuracy (M2/WMO edge cases, equipment textures), and multi-expansion coverage.
|
||||
- **Warden**: Full module execution via Unicorn Engine CPU emulation. Decrypts (RC4→RSA→zlib), parses and relocates the PE module, executes via x86 emulation with Windows API interception. Module cache at `~/.local/share/wowee/warden_cache/`.
|
||||
|
||||
|
|
@ -27,18 +28,10 @@ Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. All three
|
|||
|
||||
### Rendering Engine
|
||||
- **Terrain** -- Multi-tile streaming with async loading, texture splatting (4 layers), frustum culling
|
||||
- **Water** -- Animated surfaces, reflections, refractions, Fresnel effect
|
||||
- **Sky System** -- WoW-accurate DBC-driven lighting with skybox authority
|
||||
- **Skybox** -- Camera-locked celestial sphere (M2 model support, gradient fallback)
|
||||
- **Celestial Bodies** -- Sun (lighting-driven), White Lady + Blue Child (Azeroth's two moons)
|
||||
- **Moon Phases** -- Game time-driven deterministic phases when server time is available (fallback: local cycling for development)
|
||||
- **Stars** -- Baked into skybox assets (procedural fallback for development/debug only)
|
||||
- **Atmosphere** -- Procedural clouds (FBM noise), lens flare with chromatic aberration, cloud/fog star occlusion
|
||||
- **Weather** -- Rain and snow particle systems (2000 particles, camera-relative)
|
||||
- **Characters** -- Skeletal animation with GPU vertex skinning (256 bones), race-aware textures
|
||||
- **Buildings** -- WMO renderer with multi-material batches, frustum culling, 160-unit distance culling
|
||||
- **Particles** -- M2 particle emitters with WotLK struct parsing, billboarded glow effects
|
||||
- **Post-Processing** -- HDR, tonemapping, shadow mapping (2048x2048)
|
||||
|
||||
### Asset Pipeline
|
||||
- Extracted loose-file **`Data/`** tree indexed by **`manifest.json`** (fast lookup + caching)
|
||||
|
|
@ -71,21 +64,26 @@ Compatible with **Vanilla (Classic) 1.12 + TBC 2.4.3 + WotLK 3.3.5a**. All three
|
|||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install libsdl2-dev libglew-dev libglm-dev \
|
||||
libssl-dev cmake build-essential \
|
||||
libunicorn-dev \ # for Warden module execution
|
||||
libstorm-dev # for asset_extract
|
||||
sudo apt install libsdl2-dev libglm-dev libssl-dev \
|
||||
libvulkan-dev vulkan-tools glslc \
|
||||
libavformat-dev libavcodec-dev libswscale-dev libavutil-dev \
|
||||
zlib1g-dev cmake build-essential libx11-dev \
|
||||
libunicorn-dev \ # optional: Warden module execution
|
||||
libstorm-dev # optional: asset_extract tool
|
||||
|
||||
# Fedora
|
||||
sudo dnf install SDL2-devel glew-devel glm-devel \
|
||||
openssl-devel cmake gcc-c++ \
|
||||
unicorn-devel \ # for Warden module execution
|
||||
StormLib-devel # for asset_extract
|
||||
sudo dnf install SDL2-devel glm-devel openssl-devel \
|
||||
vulkan-devel vulkan-tools glslc \
|
||||
ffmpeg-devel zlib-devel cmake gcc-c++ libX11-devel \
|
||||
unicorn-devel \ # optional: Warden module execution
|
||||
StormLib-devel # optional: asset_extract tool
|
||||
|
||||
# Arch
|
||||
sudo pacman -S sdl2 glew glm openssl cmake base-devel \
|
||||
unicorn \ # for Warden module execution
|
||||
stormlib # for asset_extract
|
||||
sudo pacman -S sdl2 glm openssl \
|
||||
vulkan-devel vulkan-tools shaderc \
|
||||
ffmpeg zlib cmake base-devel libx11 \
|
||||
unicorn # optional: Warden module execution
|
||||
# StormLib: install from AUR for asset_extract tool
|
||||
```
|
||||
|
||||
### Container build
|
||||
|
|
@ -208,15 +206,12 @@ make -j$(nproc)
|
|||
|
||||
## Technical Details
|
||||
|
||||
- **Graphics**: OpenGL 3.3 Core, GLSL 330, forward rendering with post-processing
|
||||
- **Performance**: 60 FPS (vsync), ~50k triangles/frame, ~30 draw calls, <10% GPU
|
||||
- **Platform**: Linux (primary), C++20, CMake 3.15+
|
||||
- **Dependencies**: SDL2, OpenGL/GLEW, GLM, OpenSSL, ImGui, FFmpeg, Unicorn Engine (StormLib for asset extraction tooling)
|
||||
- **Dependencies**: SDL2, Vulkan, GLM, OpenSSL, ImGui, FFmpeg, Unicorn Engine (StormLib for asset extraction tooling)
|
||||
- **Architecture**: Modular design with clear separation (core, rendering, networking, game logic, asset pipeline, UI, audio)
|
||||
- **Networking**: Non-blocking TCP, SRP6a authentication, RC4 encryption, WoW 3.3.5a protocol
|
||||
- **Asset Loading**: Extracted loose-file tree + `manifest.json` indexing, async terrain streaming, overlay layers
|
||||
- **Sky System**: WoW-accurate DBC-driven architecture
|
||||
- **Skybox Authority**: Stars baked into M2 sky dome models (not procedurally generated)
|
||||
- **Lore-Accurate Moons**: White Lady (30-day cycle) + Blue Child (27-day cycle)
|
||||
- **Deterministic Phases**: Computed from server game time when available (fallback: local time/dev cycling)
|
||||
- **Camera-Locked**: Sky dome uses rotation-only transform (translation ignored)
|
||||
|
|
@ -238,8 +233,4 @@ This project does not include any Blizzard Entertainment proprietary data, asset
|
|||
|
||||
## Known Issues
|
||||
|
||||
### Water Rendering
|
||||
- **Stormwind Canal Overflow**: Canal water surfaces extend spatially beyond their intended boundaries, causing water to appear in tunnels, buildings, and the park. This is due to oversized water mesh extents in the WoW data files.
|
||||
- **Current Workaround**: Water heights are lowered by 1 unit in Stormwind (tiles 28-50, 28-52) for surfaces above 94 units, with a 20-unit exclusion zone around the moonwell (-8755.9, 1108.9, 96.1). This hides most problem water while keeping canals and the moonwell functional.
|
||||
- **Limitation**: Some park water may still be visible. The workaround uses hardcoded coordinates and height thresholds rather than fixing the root cause.
|
||||
- **Proper Fix**: Would require trimming water surface meshes to actual boundaries in ADT/WMO data, or implementing spatial clipping at render time.
|
||||
MANY issues this is actively under development
|
||||
|
|
|
|||
BIN
assets/krayonload.png
Normal file
BIN
assets/krayonload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/krayonsignin.png
Normal file
BIN
assets/krayonsignin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 142 KiB |
49
assets/shaders/basic.frag.glsl
Normal file
49
assets/shaders/basic.frag.glsl
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D uTexture;
|
||||
|
||||
layout(set = 1, binding = 1) uniform BasicMaterial {
|
||||
vec4 color;
|
||||
vec3 lightPos;
|
||||
int useTexture;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 FragPos;
|
||||
layout(location = 1) in vec3 Normal;
|
||||
layout(location = 2) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec3 ambient = 0.3 * vec3(1.0);
|
||||
vec3 norm = normalize(Normal);
|
||||
vec3 lightDir2 = normalize(lightPos - FragPos);
|
||||
float diff = max(dot(norm, lightDir2), 0.0);
|
||||
vec3 diffuse = diff * vec3(1.0);
|
||||
|
||||
vec3 viewDir2 = normalize(viewPos.xyz - FragPos);
|
||||
vec3 reflectDir = reflect(-lightDir2, norm);
|
||||
float spec = pow(max(dot(viewDir2, reflectDir), 0.0), 32.0);
|
||||
vec3 specular = 0.5 * spec * vec3(1.0);
|
||||
|
||||
vec3 result = ambient + diffuse + specular;
|
||||
|
||||
if (useTexture != 0) {
|
||||
outColor = texture(uTexture, TexCoord) * vec4(result, 1.0);
|
||||
} else {
|
||||
outColor = color * vec4(result, 1.0);
|
||||
}
|
||||
}
|
||||
BIN
assets/shaders/basic.frag.spv
Normal file
BIN
assets/shaders/basic.frag.spv
Normal file
Binary file not shown.
34
assets/shaders/basic.vert.glsl
Normal file
34
assets/shaders/basic.vert.glsl
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPosition;
|
||||
layout(location = 1) in vec3 aNormal;
|
||||
layout(location = 2) in vec2 aTexCoord;
|
||||
|
||||
layout(location = 0) out vec3 FragPos;
|
||||
layout(location = 1) out vec3 Normal;
|
||||
layout(location = 2) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
vec4 worldPos = push.model * vec4(aPosition, 1.0);
|
||||
FragPos = worldPos.xyz;
|
||||
Normal = mat3(push.model) * aNormal;
|
||||
TexCoord = aTexCoord;
|
||||
gl_Position = projection * view * worldPos;
|
||||
}
|
||||
BIN
assets/shaders/basic.vert.spv
Normal file
BIN
assets/shaders/basic.vert.spv
Normal file
Binary file not shown.
64
assets/shaders/celestial.frag.glsl
Normal file
64
assets/shaders/celestial.frag.glsl
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
vec4 celestialColor; // xyz = color, w = unused
|
||||
float intensity;
|
||||
float moonPhase;
|
||||
float animTime;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
float valueNoise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
float a = fract(sin(dot(i, vec2(127.1, 311.7))) * 43758.5453);
|
||||
float b = fract(sin(dot(i + vec2(1.0, 0.0), vec2(127.1, 311.7))) * 43758.5453);
|
||||
float c = fract(sin(dot(i + vec2(0.0, 1.0), vec2(127.1, 311.7))) * 43758.5453);
|
||||
float d = fract(sin(dot(i + vec2(1.0, 1.0), vec2(127.1, 311.7))) * 43758.5453);
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = TexCoord - 0.5;
|
||||
float dist = length(uv);
|
||||
|
||||
// Hard circular cutoff — nothing beyond radius 0.35
|
||||
if (dist > 0.35) discard;
|
||||
|
||||
// Hard disc with smooth edge
|
||||
float disc = smoothstep(0.35, 0.28, dist);
|
||||
|
||||
// Soft glow confined within cutoff radius
|
||||
float glow = exp(-dist * dist * 40.0) * 0.5;
|
||||
|
||||
// Combine disc and glow
|
||||
float alpha = max(disc, glow) * push.intensity;
|
||||
|
||||
// Smooth fade to zero at cutoff boundary
|
||||
float edgeFade = 1.0 - smoothstep(0.25, 0.35, dist);
|
||||
alpha *= edgeFade;
|
||||
|
||||
vec3 color = push.celestialColor.rgb;
|
||||
|
||||
// Animated haze/turbulence overlay for the sun disc
|
||||
if (push.intensity > 0.5) {
|
||||
float noise = valueNoise(uv * 8.0 + vec2(push.animTime * 0.3, push.animTime * 0.2));
|
||||
float noise2 = valueNoise(uv * 16.0 - vec2(push.animTime * 0.5, push.animTime * 0.15));
|
||||
float turbulence = (noise * 0.6 + noise2 * 0.4) * disc;
|
||||
color += vec3(turbulence * 0.3, turbulence * 0.15, 0.0);
|
||||
}
|
||||
|
||||
// Moon phase shadow (only applied when intensity < 0.5, i.e. for moons)
|
||||
float phaseX = uv.x * 2.0 + push.moonPhase;
|
||||
float phaseShadow = smoothstep(-0.1, 0.1, phaseX);
|
||||
alpha *= mix(phaseShadow, 1.0, step(0.5, push.intensity));
|
||||
|
||||
if (alpha < 0.01) discard;
|
||||
// Pre-multiply for additive blending: RGB is the light contribution
|
||||
outColor = vec4(color * alpha, alpha);
|
||||
}
|
||||
BIN
assets/shaders/celestial.frag.spv
Normal file
BIN
assets/shaders/celestial.frag.spv
Normal file
Binary file not shown.
34
assets/shaders/celestial.vert.glsl
Normal file
34
assets/shaders/celestial.vert.glsl
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
vec4 celestialColor; // xyz = color, w = unused
|
||||
float intensity;
|
||||
float moonPhase;
|
||||
float animTime;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
TexCoord = aTexCoord;
|
||||
// Sky object: remove camera translation so celestial bodies are at infinite distance
|
||||
mat4 rotView = mat4(mat3(view));
|
||||
gl_Position = projection * rotView * push.model * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/celestial.vert.spv
Normal file
BIN
assets/shaders/celestial.vert.spv
Normal file
Binary file not shown.
187
assets/shaders/character.frag.glsl
Normal file
187
assets/shaders/character.frag.glsl
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D uTexture;
|
||||
|
||||
layout(set = 1, binding = 1) uniform CharMaterial {
|
||||
float opacity;
|
||||
int alphaTest;
|
||||
int colorKeyBlack;
|
||||
int unlit;
|
||||
float emissiveBoost;
|
||||
vec3 emissiveTint;
|
||||
float specularIntensity;
|
||||
int enableNormalMap;
|
||||
int enablePOM;
|
||||
float pomScale;
|
||||
int pomMaxSamples;
|
||||
float heightMapVariance;
|
||||
float normalMapStrength;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 2) uniform sampler2D uNormalHeightMap;
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2DShadow uShadowMap;
|
||||
|
||||
layout(location = 0) in vec3 FragPos;
|
||||
layout(location = 1) in vec3 Normal;
|
||||
layout(location = 2) in vec2 TexCoord;
|
||||
layout(location = 3) in vec3 Tangent;
|
||||
layout(location = 4) in vec3 Bitangent;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
const float SHADOW_TEXEL = 1.0 / 4096.0;
|
||||
|
||||
float sampleShadowPCF(sampler2DShadow smap, vec3 coords) {
|
||||
float shadow = 0.0;
|
||||
for (int x = -1; x <= 1; ++x) {
|
||||
for (int y = -1; y <= 1; ++y) {
|
||||
shadow += texture(smap, vec3(coords.xy + vec2(x, y) * SHADOW_TEXEL, coords.z));
|
||||
}
|
||||
}
|
||||
return shadow / 9.0;
|
||||
}
|
||||
|
||||
// LOD factor from screen-space UV derivatives
|
||||
float computeLodFactor() {
|
||||
vec2 dx = dFdx(TexCoord);
|
||||
vec2 dy = dFdy(TexCoord);
|
||||
float texelDensity = max(dot(dx, dx), dot(dy, dy));
|
||||
return smoothstep(0.0001, 0.005, texelDensity);
|
||||
}
|
||||
|
||||
// Parallax Occlusion Mapping with angle-adaptive sampling
|
||||
vec2 parallaxOcclusionMap(vec2 uv, vec3 viewDirTS, float lodFactor) {
|
||||
float VdotN = abs(viewDirTS.z);
|
||||
|
||||
if (VdotN < 0.15) return uv;
|
||||
|
||||
float angleFactor = clamp(VdotN, 0.15, 1.0);
|
||||
int maxS = pomMaxSamples;
|
||||
int minS = max(maxS / 4, 4);
|
||||
int numSamples = int(mix(float(minS), float(maxS), angleFactor));
|
||||
numSamples = int(mix(float(minS), float(numSamples), 1.0 - lodFactor));
|
||||
|
||||
float layerDepth = 1.0 / float(numSamples);
|
||||
float currentLayerDepth = 0.0;
|
||||
|
||||
vec2 P = viewDirTS.xy / max(VdotN, 0.15) * pomScale;
|
||||
float maxOffset = pomScale * 3.0;
|
||||
P = clamp(P, vec2(-maxOffset), vec2(maxOffset));
|
||||
vec2 deltaUV = P / float(numSamples);
|
||||
|
||||
vec2 currentUV = uv;
|
||||
float currentDepthMapValue = 1.0 - texture(uNormalHeightMap, currentUV).a;
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (i >= numSamples || currentLayerDepth >= currentDepthMapValue) break;
|
||||
currentUV -= deltaUV;
|
||||
currentDepthMapValue = 1.0 - texture(uNormalHeightMap, currentUV).a;
|
||||
currentLayerDepth += layerDepth;
|
||||
}
|
||||
|
||||
vec2 prevUV = currentUV + deltaUV;
|
||||
float afterDepth = currentDepthMapValue - currentLayerDepth;
|
||||
float beforeDepth = (1.0 - texture(uNormalHeightMap, prevUV).a) - currentLayerDepth + layerDepth;
|
||||
float weight = afterDepth / (afterDepth - beforeDepth + 0.0001);
|
||||
vec2 result = mix(currentUV, prevUV, weight);
|
||||
|
||||
float fadeFactor = smoothstep(0.15, 0.35, VdotN);
|
||||
return mix(uv, result, fadeFactor);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float lodFactor = computeLodFactor();
|
||||
|
||||
vec3 vertexNormal = normalize(Normal);
|
||||
if (!gl_FrontFacing) vertexNormal = -vertexNormal;
|
||||
|
||||
vec2 finalUV = TexCoord;
|
||||
|
||||
// Build TBN matrix
|
||||
vec3 T = normalize(Tangent);
|
||||
vec3 B = normalize(Bitangent);
|
||||
vec3 N = vertexNormal;
|
||||
mat3 TBN = mat3(T, B, N);
|
||||
|
||||
if (enablePOM != 0 && heightMapVariance > 0.001 && lodFactor < 0.99) {
|
||||
mat3 TBN_inv = transpose(TBN);
|
||||
vec3 viewDirWorld = normalize(viewPos.xyz - FragPos);
|
||||
vec3 viewDirTS = TBN_inv * viewDirWorld;
|
||||
finalUV = parallaxOcclusionMap(TexCoord, viewDirTS, lodFactor);
|
||||
}
|
||||
|
||||
vec4 texColor = texture(uTexture, finalUV);
|
||||
|
||||
if (alphaTest != 0 && texColor.a < 0.5) discard;
|
||||
if (colorKeyBlack != 0) {
|
||||
float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||
float ck = smoothstep(0.12, 0.30, lum);
|
||||
texColor.a *= ck;
|
||||
if (texColor.a < 0.01) discard;
|
||||
}
|
||||
|
||||
// Compute normal (with normal mapping if enabled)
|
||||
vec3 norm = vertexNormal;
|
||||
if (enableNormalMap != 0 && lodFactor < 0.99 && normalMapStrength > 0.001) {
|
||||
vec3 mapNormal = texture(uNormalHeightMap, finalUV).rgb * 2.0 - 1.0;
|
||||
mapNormal.xy *= normalMapStrength;
|
||||
mapNormal = normalize(mapNormal);
|
||||
vec3 worldNormal = normalize(TBN * mapNormal);
|
||||
if (!gl_FrontFacing) worldNormal = -worldNormal;
|
||||
float blendFactor = max(lodFactor, 1.0 - normalMapStrength);
|
||||
norm = normalize(mix(worldNormal, vertexNormal, blendFactor));
|
||||
}
|
||||
|
||||
vec3 result;
|
||||
|
||||
if (unlit != 0) {
|
||||
vec3 warm = emissiveTint * emissiveBoost;
|
||||
result = texColor.rgb * (1.0 + warm);
|
||||
} else {
|
||||
vec3 ldir = normalize(-lightDir.xyz);
|
||||
float diff = max(dot(norm, ldir), 0.0);
|
||||
|
||||
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
||||
vec3 halfDir = normalize(ldir + viewDir);
|
||||
float spec = pow(max(dot(norm, halfDir), 0.0), 32.0) * specularIntensity;
|
||||
|
||||
float shadow = 1.0;
|
||||
if (shadowParams.x > 0.5) {
|
||||
float normalOffset = SHADOW_TEXEL * 2.0 * (1.0 - abs(dot(norm, ldir)));
|
||||
vec3 biasedPos = FragPos + norm * normalOffset;
|
||||
vec4 lsPos = lightSpaceMatrix * vec4(biasedPos, 1.0);
|
||||
vec3 proj = lsPos.xyz / lsPos.w;
|
||||
proj.xy = proj.xy * 0.5 + 0.5;
|
||||
if (proj.x >= 0.0 && proj.x <= 1.0 &&
|
||||
proj.y >= 0.0 && proj.y <= 1.0 &&
|
||||
proj.z >= 0.0 && proj.z <= 1.0) {
|
||||
float bias = max(0.0005 * (1.0 - dot(norm, ldir)), 0.00005);
|
||||
shadow = sampleShadowPCF(uShadowMap, vec3(proj.xy, proj.z - bias));
|
||||
}
|
||||
shadow = mix(1.0, shadow, shadowParams.y);
|
||||
}
|
||||
|
||||
result = ambientColor.rgb * texColor.rgb
|
||||
+ shadow * (diff * lightColor.rgb * texColor.rgb + spec * lightColor.rgb);
|
||||
}
|
||||
|
||||
float dist = length(viewPos.xyz - FragPos);
|
||||
float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0);
|
||||
result = mix(fogColor.rgb, result, fogFactor);
|
||||
|
||||
outColor = vec4(result, texColor.a * opacity);
|
||||
}
|
||||
BIN
assets/shaders/character.frag.spv
Normal file
BIN
assets/shaders/character.frag.spv
Normal file
Binary file not shown.
63
assets/shaders/character.vert.glsl
Normal file
63
assets/shaders/character.vert.glsl
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
} push;
|
||||
|
||||
layout(set = 2, binding = 0) readonly buffer BoneSSBO {
|
||||
mat4 bones[];
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec4 aBoneWeights;
|
||||
layout(location = 2) in ivec4 aBoneIndices;
|
||||
layout(location = 3) in vec3 aNormal;
|
||||
layout(location = 4) in vec2 aTexCoord;
|
||||
layout(location = 5) in vec4 aTangent;
|
||||
|
||||
layout(location = 0) out vec3 FragPos;
|
||||
layout(location = 1) out vec3 Normal;
|
||||
layout(location = 2) out vec2 TexCoord;
|
||||
layout(location = 3) out vec3 Tangent;
|
||||
layout(location = 4) out vec3 Bitangent;
|
||||
|
||||
void main() {
|
||||
mat4 skinMat = bones[aBoneIndices.x] * aBoneWeights.x
|
||||
+ bones[aBoneIndices.y] * aBoneWeights.y
|
||||
+ bones[aBoneIndices.z] * aBoneWeights.z
|
||||
+ bones[aBoneIndices.w] * aBoneWeights.w;
|
||||
|
||||
vec4 skinnedPos = skinMat * vec4(aPos, 1.0);
|
||||
vec3 skinnedNorm = mat3(skinMat) * aNormal;
|
||||
vec3 skinnedTan = mat3(skinMat) * aTangent.xyz;
|
||||
|
||||
vec4 worldPos = push.model * skinnedPos;
|
||||
mat3 modelMat3 = mat3(push.model);
|
||||
FragPos = worldPos.xyz;
|
||||
Normal = modelMat3 * skinnedNorm;
|
||||
TexCoord = aTexCoord;
|
||||
|
||||
// Gram-Schmidt re-orthogonalize tangent w.r.t. normal
|
||||
vec3 N = normalize(Normal);
|
||||
vec3 T = normalize(modelMat3 * skinnedTan);
|
||||
T = normalize(T - dot(T, N) * N);
|
||||
vec3 B = cross(N, T) * aTangent.w;
|
||||
|
||||
Tangent = T;
|
||||
Bitangent = B;
|
||||
|
||||
gl_Position = projection * view * worldPos;
|
||||
}
|
||||
BIN
assets/shaders/character.vert.spv
Normal file
BIN
assets/shaders/character.vert.spv
Normal file
Binary file not shown.
19
assets/shaders/character_shadow.frag.glsl
Normal file
19
assets/shaders/character_shadow.frag.glsl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D uTexture;
|
||||
|
||||
layout(set = 1, binding = 1) uniform ShadowParams {
|
||||
int alphaTest;
|
||||
int colorKeyBlack;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
vec4 texColor = texture(uTexture, TexCoord);
|
||||
if (alphaTest != 0 && texColor.a < 0.5) discard;
|
||||
if (colorKeyBlack != 0) {
|
||||
float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||
if (lum < 0.12) discard;
|
||||
}
|
||||
}
|
||||
BIN
assets/shaders/character_shadow.frag.spv
Normal file
BIN
assets/shaders/character_shadow.frag.spv
Normal file
Binary file not shown.
27
assets/shaders/character_shadow.vert.glsl
Normal file
27
assets/shaders/character_shadow.vert.glsl
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 lightSpaceMatrix;
|
||||
mat4 model;
|
||||
} push;
|
||||
|
||||
layout(set = 2, binding = 0) readonly buffer BoneSSBO {
|
||||
mat4 bones[];
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec4 aBoneWeights;
|
||||
layout(location = 2) in ivec4 aBoneIndices;
|
||||
layout(location = 3) in vec2 aTexCoord;
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
mat4 skinMat = bones[aBoneIndices.x] * aBoneWeights.x
|
||||
+ bones[aBoneIndices.y] * aBoneWeights.y
|
||||
+ bones[aBoneIndices.z] * aBoneWeights.z
|
||||
+ bones[aBoneIndices.w] * aBoneWeights.w;
|
||||
vec4 skinnedPos = skinMat * vec4(aPos, 1.0);
|
||||
TexCoord = aTexCoord;
|
||||
gl_Position = push.lightSpaceMatrix * push.model * skinnedPos;
|
||||
}
|
||||
BIN
assets/shaders/character_shadow.vert.spv
Normal file
BIN
assets/shaders/character_shadow.vert.spv
Normal file
Binary file not shown.
13
assets/shaders/charge_dust.frag.glsl
Normal file
13
assets/shaders/charge_dust.frag.glsl
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vAlpha;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_PointCoord - vec2(0.5);
|
||||
float dist = length(p);
|
||||
if (dist > 0.5) discard;
|
||||
float alpha = smoothstep(0.5, 0.1, dist) * vAlpha * 0.45;
|
||||
outColor = vec4(0.65, 0.55, 0.40, alpha);
|
||||
}
|
||||
BIN
assets/shaders/charge_dust.frag.spv
Normal file
BIN
assets/shaders/charge_dust.frag.spv
Normal file
Binary file not shown.
26
assets/shaders/charge_dust.vert.glsl
Normal file
26
assets/shaders/charge_dust.vert.glsl
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in float aSize;
|
||||
layout(location = 2) in float aAlpha;
|
||||
|
||||
layout(location = 0) out float vAlpha;
|
||||
|
||||
void main() {
|
||||
gl_PointSize = aSize;
|
||||
vAlpha = aAlpha;
|
||||
gl_Position = projection * view * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/charge_dust.vert.spv
Normal file
BIN
assets/shaders/charge_dust.vert.spv
Normal file
Binary file not shown.
16
assets/shaders/charge_ribbon.frag.glsl
Normal file
16
assets/shaders/charge_ribbon.frag.glsl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vAlpha;
|
||||
layout(location = 1) in float vHeat;
|
||||
layout(location = 2) in float vHeight;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec3 top = vec3(1.0, 0.2, 0.0);
|
||||
vec3 mid = vec3(1.0, 0.5, 0.0);
|
||||
vec3 color = mix(mid, top, vHeight);
|
||||
color = mix(color, vec3(1.0, 0.8, 0.3), vHeat * 0.5);
|
||||
float alpha = vAlpha * smoothstep(0.0, 0.3, vHeight);
|
||||
outColor = vec4(color, alpha);
|
||||
}
|
||||
BIN
assets/shaders/charge_ribbon.frag.spv
Normal file
BIN
assets/shaders/charge_ribbon.frag.spv
Normal file
Binary file not shown.
30
assets/shaders/charge_ribbon.vert.glsl
Normal file
30
assets/shaders/charge_ribbon.vert.glsl
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in float aAlpha;
|
||||
layout(location = 2) in float aHeat;
|
||||
layout(location = 3) in float aHeight;
|
||||
|
||||
layout(location = 0) out float vAlpha;
|
||||
layout(location = 1) out float vHeat;
|
||||
layout(location = 2) out float vHeight;
|
||||
|
||||
void main() {
|
||||
vAlpha = aAlpha;
|
||||
vHeat = aHeat;
|
||||
vHeight = aHeight;
|
||||
gl_Position = projection * view * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/charge_ribbon.vert.spv
Normal file
BIN
assets/shaders/charge_ribbon.vert.spv
Normal file
Binary file not shown.
104
assets/shaders/clouds.frag.glsl
Normal file
104
assets/shaders/clouds.frag.glsl
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec4 cloudColor; // xyz = DBC-derived base cloud color, w = unused
|
||||
vec4 sunDirDensity; // xyz = sun direction, w = density
|
||||
vec4 windAndLight; // x = windOffset, y = sunIntensity, z = ambient, w = unused
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 vWorldDir;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
// --- Gradient noise (smoother than hash-based) ---
|
||||
vec2 hash2(vec2 p) {
|
||||
p = vec2(dot(p, vec2(127.1, 311.7)),
|
||||
dot(p, vec2(269.5, 183.3)));
|
||||
return fract(sin(p) * 43758.5453);
|
||||
}
|
||||
|
||||
float gradientNoise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
|
||||
// Quintic interpolation for smoother results
|
||||
vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
|
||||
|
||||
float a = dot(hash2(i + vec2(0.0, 0.0)) * 2.0 - 1.0, f - vec2(0.0, 0.0));
|
||||
float b = dot(hash2(i + vec2(1.0, 0.0)) * 2.0 - 1.0, f - vec2(1.0, 0.0));
|
||||
float c = dot(hash2(i + vec2(0.0, 1.0)) * 2.0 - 1.0, f - vec2(0.0, 1.0));
|
||||
float d = dot(hash2(i + vec2(1.0, 1.0)) * 2.0 - 1.0, f - vec2(1.0, 1.0));
|
||||
|
||||
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y) * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
float fbm(vec2 p) {
|
||||
float val = 0.0;
|
||||
float amp = 0.5;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
val += amp * gradientNoise(p);
|
||||
p *= 2.0;
|
||||
amp *= 0.5;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 dir = normalize(vWorldDir);
|
||||
float altitude = dir.z;
|
||||
if (altitude < 0.0) discard;
|
||||
|
||||
vec3 sunDir = push.sunDirDensity.xyz;
|
||||
float density = push.sunDirDensity.w;
|
||||
float windOffset = push.windAndLight.x;
|
||||
float sunIntensity = push.windAndLight.y;
|
||||
float ambient = push.windAndLight.z;
|
||||
|
||||
vec2 uv = dir.xy / (altitude + 0.001);
|
||||
uv += windOffset;
|
||||
|
||||
// --- 6-octave FBM for cloud shape ---
|
||||
float cloud1 = fbm(uv * 0.8);
|
||||
float cloud2 = fbm(uv * 1.6 + 5.0);
|
||||
float cloud = cloud1 * 0.7 + cloud2 * 0.3;
|
||||
|
||||
// Coverage control: base coverage with detail erosion
|
||||
float baseCoverage = smoothstep(0.30, 0.55, cloud);
|
||||
float detailErosion = gradientNoise(uv * 4.0);
|
||||
cloud = baseCoverage * smoothstep(0.2, 0.5, detailErosion);
|
||||
cloud *= density;
|
||||
|
||||
// Horizon fade
|
||||
float horizonFade = smoothstep(0.0, 0.15, altitude);
|
||||
cloud *= horizonFade;
|
||||
|
||||
if (cloud < 0.01) discard;
|
||||
|
||||
// --- Sun lighting on clouds ---
|
||||
// Sun dot product for view-relative brightness
|
||||
float sunDot = max(dot(vec3(0.0, 0.0, 1.0), sunDir), 0.0);
|
||||
|
||||
// Self-shadowing: sample noise offset toward sun direction, darken if occluded
|
||||
float lightSample = fbm((uv + sunDir.xy * 0.05) * 0.8);
|
||||
float shadow = smoothstep(0.3, 0.7, lightSample);
|
||||
|
||||
// Base lit color: mix dark (shadow) and bright (sunlit) based on shadow and sun
|
||||
vec3 baseColor = push.cloudColor.rgb;
|
||||
vec3 shadowColor = baseColor * (ambient * 0.8);
|
||||
vec3 litColor = baseColor * (ambient + sunIntensity * 0.6);
|
||||
vec3 cloudRgb = mix(shadowColor, litColor, shadow * sunDot);
|
||||
|
||||
// Add ambient fill so clouds aren't too dark
|
||||
cloudRgb = mix(baseColor * ambient, cloudRgb, 0.7 + 0.3 * sunIntensity);
|
||||
|
||||
// --- Silver lining effect at cloud edges ---
|
||||
float edgeLight = smoothstep(0.0, 0.3, cloud) * (1.0 - smoothstep(0.3, 0.8, cloud));
|
||||
cloudRgb += vec3(1.0, 0.95, 0.9) * edgeLight * sunDot * sunIntensity * 0.4;
|
||||
|
||||
// --- Edge softness for alpha ---
|
||||
float edgeSoftness = smoothstep(0.0, 0.3, cloud);
|
||||
float alpha = cloud * edgeSoftness;
|
||||
|
||||
if (alpha < 0.01) discard;
|
||||
outColor = vec4(cloudRgb, alpha);
|
||||
}
|
||||
BIN
assets/shaders/clouds.frag.spv
Normal file
BIN
assets/shaders/clouds.frag.spv
Normal file
Binary file not shown.
25
assets/shaders/clouds.vert.glsl
Normal file
25
assets/shaders/clouds.vert.glsl
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
|
||||
layout(location = 0) out vec3 vWorldDir;
|
||||
|
||||
void main() {
|
||||
vWorldDir = aPos;
|
||||
mat4 rotView = mat4(mat3(view));
|
||||
vec4 pos = projection * rotView * vec4(aPos, 1.0);
|
||||
gl_Position = pos.xyww;
|
||||
}
|
||||
BIN
assets/shaders/clouds.vert.spv
Normal file
BIN
assets/shaders/clouds.vert.spv
Normal file
Binary file not shown.
22
assets/shaders/lens_flare.frag.glsl
Normal file
22
assets/shaders/lens_flare.frag.glsl
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec2 position;
|
||||
float size;
|
||||
float aspectRatio;
|
||||
vec4 color; // rgb + brightness in w
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 UV;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 center = UV - 0.5;
|
||||
float dist = length(center);
|
||||
float alpha = smoothstep(0.5, 0.0, dist);
|
||||
float glow = exp(-dist * dist * 8.0) * 0.5;
|
||||
alpha = max(alpha, glow) * push.color.w;
|
||||
if (alpha < 0.01) discard;
|
||||
outColor = vec4(push.color.rgb, alpha);
|
||||
}
|
||||
BIN
assets/shaders/lens_flare.frag.spv
Normal file
BIN
assets/shaders/lens_flare.frag.spv
Normal file
Binary file not shown.
19
assets/shaders/lens_flare.vert.glsl
Normal file
19
assets/shaders/lens_flare.vert.glsl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec2 position;
|
||||
float size;
|
||||
float aspectRatio;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 aPos;
|
||||
layout(location = 1) in vec2 aUV;
|
||||
|
||||
layout(location = 0) out vec2 UV;
|
||||
|
||||
void main() {
|
||||
UV = aUV;
|
||||
vec2 scaled = aPos * push.size;
|
||||
scaled.x /= push.aspectRatio;
|
||||
gl_Position = vec4(scaled + push.position, 0.0, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/lens_flare.vert.spv
Normal file
BIN
assets/shaders/lens_flare.vert.spv
Normal file
Binary file not shown.
10
assets/shaders/lightning_bolt.frag.glsl
Normal file
10
assets/shaders/lightning_bolt.frag.glsl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vBrightness;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec3 color = mix(vec3(0.6, 0.8, 1.0), vec3(1.0), vBrightness * 0.5);
|
||||
outColor = vec4(color, vBrightness);
|
||||
}
|
||||
BIN
assets/shaders/lightning_bolt.frag.spv
Normal file
BIN
assets/shaders/lightning_bolt.frag.spv
Normal file
Binary file not shown.
27
assets/shaders/lightning_bolt.vert.glsl
Normal file
27
assets/shaders/lightning_bolt.vert.glsl
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
float brightness;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
|
||||
layout(location = 0) out float vBrightness;
|
||||
|
||||
void main() {
|
||||
vBrightness = push.brightness;
|
||||
gl_Position = projection * view * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/lightning_bolt.vert.spv
Normal file
BIN
assets/shaders/lightning_bolt.vert.spv
Normal file
Binary file not shown.
11
assets/shaders/lightning_flash.frag.glsl
Normal file
11
assets/shaders/lightning_flash.frag.glsl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
float intensity;
|
||||
} push;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(1.0, 1.0, 1.0, push.intensity * 0.6);
|
||||
}
|
||||
BIN
assets/shaders/lightning_flash.frag.spv
Normal file
BIN
assets/shaders/lightning_flash.frag.spv
Normal file
Binary file not shown.
7
assets/shaders/lightning_flash.vert.glsl
Normal file
7
assets/shaders/lightning_flash.vert.glsl
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 aPos;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/lightning_flash.vert.spv
Normal file
BIN
assets/shaders/lightning_flash.vert.spv
Normal file
Binary file not shown.
190
assets/shaders/m2.frag.glsl
Normal file
190
assets/shaders/m2.frag.glsl
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D uTexture;
|
||||
|
||||
layout(set = 1, binding = 2) uniform M2Material {
|
||||
int hasTexture;
|
||||
int alphaTest;
|
||||
int colorKeyBlack;
|
||||
float colorKeyThreshold;
|
||||
int unlit;
|
||||
int blendMode;
|
||||
float fadeAlpha;
|
||||
float interiorDarken;
|
||||
float specularIntensity;
|
||||
};
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2DShadow uShadowMap;
|
||||
|
||||
layout(location = 0) in vec3 FragPos;
|
||||
layout(location = 1) in vec3 Normal;
|
||||
layout(location = 2) in vec2 TexCoord;
|
||||
layout(location = 3) flat in vec3 InstanceOrigin;
|
||||
layout(location = 4) in float ModelHeight;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
const float SHADOW_TEXEL = 1.0 / 4096.0;
|
||||
|
||||
float sampleShadowPCF(sampler2DShadow smap, vec3 coords) {
|
||||
float shadow = 0.0;
|
||||
for (int x = -1; x <= 1; ++x) {
|
||||
for (int y = -1; y <= 1; ++y) {
|
||||
shadow += texture(smap, vec3(coords.xy + vec2(x, y) * SHADOW_TEXEL, coords.z));
|
||||
}
|
||||
}
|
||||
return shadow / 9.0;
|
||||
}
|
||||
|
||||
// 4x4 Bayer dither matrix (normalized to 0..1)
|
||||
float bayerDither4x4(ivec2 p) {
|
||||
int idx = (p.x & 3) + (p.y & 3) * 4;
|
||||
float m[16] = float[16](
|
||||
0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0,
|
||||
12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0,
|
||||
3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0,
|
||||
15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0
|
||||
);
|
||||
return m[idx];
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 texColor = hasTexture != 0 ? texture(uTexture, TexCoord) : vec4(1.0);
|
||||
|
||||
bool isFoliage = (alphaTest == 2);
|
||||
|
||||
// Fix DXT fringe: transparent edge texels have garbage (black) RGB.
|
||||
// At low alpha the original RGB is untrustworthy — replace with the
|
||||
// averaged color from nearby opaque texels (high mip). The lower
|
||||
// the alpha the more we distrust the original color.
|
||||
if (alphaTest != 0 && texColor.a > 0.01 && texColor.a < 1.0) {
|
||||
vec3 mipColor = textureLod(uTexture, TexCoord, 4.0).rgb;
|
||||
// trust = 0 at alpha 0, trust = 1 at alpha ~0.9
|
||||
float trust = smoothstep(0.0, 0.9, texColor.a);
|
||||
texColor.rgb = mix(mipColor, texColor.rgb, trust);
|
||||
}
|
||||
|
||||
float alphaCutoff = 0.5;
|
||||
if (alphaTest == 2) {
|
||||
alphaCutoff = 0.4;
|
||||
} else if (alphaTest == 3) {
|
||||
alphaCutoff = 0.25;
|
||||
} else if (alphaTest != 0) {
|
||||
alphaCutoff = 0.4;
|
||||
}
|
||||
if (alphaTest != 0 && texColor.a < alphaCutoff) {
|
||||
discard;
|
||||
}
|
||||
if (colorKeyBlack != 0) {
|
||||
float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||
if (lum < colorKeyThreshold) discard;
|
||||
}
|
||||
if (blendMode == 1 && texColor.a < 0.004) discard;
|
||||
|
||||
// Per-instance color variation (foliage only)
|
||||
if (isFoliage) {
|
||||
float hash = fract(sin(dot(InstanceOrigin.xy, vec2(127.1, 311.7))) * 43758.5453);
|
||||
float hueShiftR = 1.0 + (hash - 0.5) * 0.16; // ±8% red
|
||||
float hueShiftB = 1.0 + (fract(hash * 7.13) - 0.5) * 0.16; // ±8% blue
|
||||
float brightness = 0.85 + hash * 0.30; // 85–115%
|
||||
texColor.rgb *= vec3(hueShiftR, 1.0, hueShiftB) * brightness;
|
||||
}
|
||||
|
||||
vec3 norm = normalize(Normal);
|
||||
bool foliageTwoSided = (alphaTest == 2);
|
||||
if (!foliageTwoSided && !gl_FrontFacing) norm = -norm;
|
||||
|
||||
// Detail normal perturbation (foliage only) — UV-based only so wind doesn't cause flicker
|
||||
if (isFoliage) {
|
||||
float nx = sin(TexCoord.x * 12.0 + TexCoord.y * 5.3) * 0.10;
|
||||
float ny = sin(TexCoord.y * 14.0 + TexCoord.x * 4.7) * 0.10;
|
||||
norm = normalize(norm + vec3(nx, ny, 0.0));
|
||||
}
|
||||
|
||||
vec3 ldir = normalize(-lightDir.xyz);
|
||||
float nDotL = dot(norm, ldir);
|
||||
float diff = foliageTwoSided ? abs(nDotL) : max(nDotL, 0.0);
|
||||
|
||||
vec3 result;
|
||||
if (unlit != 0) {
|
||||
result = texColor.rgb;
|
||||
} else {
|
||||
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
||||
|
||||
float spec = 0.0;
|
||||
float shadow = 1.0;
|
||||
if (!isFoliage) {
|
||||
vec3 halfDir = normalize(ldir + viewDir);
|
||||
spec = pow(max(dot(norm, halfDir), 0.0), 32.0) * specularIntensity;
|
||||
}
|
||||
|
||||
if (shadowParams.x > 0.5) {
|
||||
float normalOffset = SHADOW_TEXEL * 2.0 * (1.0 - abs(dot(norm, ldir)));
|
||||
vec3 biasedPos = FragPos + norm * normalOffset;
|
||||
vec4 lsPos = lightSpaceMatrix * vec4(biasedPos, 1.0);
|
||||
vec3 proj = lsPos.xyz / lsPos.w;
|
||||
proj.xy = proj.xy * 0.5 + 0.5;
|
||||
if (proj.x >= 0.0 && proj.x <= 1.0 &&
|
||||
proj.y >= 0.0 && proj.y <= 1.0 &&
|
||||
proj.z >= 0.0 && proj.z <= 1.0) {
|
||||
float bias = max(0.0005 * (1.0 - abs(dot(norm, ldir))), 0.00005);
|
||||
shadow = sampleShadowPCF(uShadowMap, vec3(proj.xy, proj.z - bias));
|
||||
}
|
||||
shadow = mix(1.0, shadow, shadowParams.y);
|
||||
}
|
||||
|
||||
// Leaf subsurface scattering (foliage only) — uses stable normal, no FragPos dependency
|
||||
vec3 sss = vec3(0.0);
|
||||
if (isFoliage) {
|
||||
float backLit = max(-nDotL, 0.0);
|
||||
float viewDotLight = max(dot(viewDir, -ldir), 0.0);
|
||||
float sssAmount = backLit * pow(viewDotLight, 4.0) * 0.35 * texColor.a;
|
||||
sss = sssAmount * vec3(1.0, 0.9, 0.5) * lightColor.rgb;
|
||||
}
|
||||
|
||||
result = ambientColor.rgb * texColor.rgb
|
||||
+ shadow * (diff * lightColor.rgb * texColor.rgb + spec * lightColor.rgb)
|
||||
+ sss;
|
||||
|
||||
if (interiorDarken > 0.0) {
|
||||
result *= mix(1.0, 0.5, interiorDarken);
|
||||
}
|
||||
}
|
||||
|
||||
// Canopy ambient occlusion (foliage only)
|
||||
if (isFoliage) {
|
||||
float normalizedHeight = clamp(ModelHeight / 18.0, 0.0, 1.0);
|
||||
float aoFactor = mix(0.55, 1.0, smoothstep(0.0, 0.6, normalizedHeight));
|
||||
result *= aoFactor;
|
||||
}
|
||||
|
||||
float dist = length(viewPos.xyz - FragPos);
|
||||
float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0);
|
||||
result = mix(fogColor.rgb, result, fogFactor);
|
||||
|
||||
float outAlpha = texColor.a * fadeAlpha;
|
||||
// Cutout materials should not remain partially transparent after discard,
|
||||
// otherwise foliage cards look view-dependent.
|
||||
if (alphaTest != 0 || colorKeyBlack != 0) {
|
||||
outAlpha = fadeAlpha;
|
||||
}
|
||||
// Foliage cutout should stay opaque after alpha discard to avoid
|
||||
// view-angle translucency artifacts.
|
||||
if (alphaTest == 2 || alphaTest == 3) {
|
||||
outAlpha = 1.0 * fadeAlpha;
|
||||
}
|
||||
outColor = vec4(result, outAlpha);
|
||||
}
|
||||
BIN
assets/shaders/m2.frag.spv
Normal file
BIN
assets/shaders/m2.frag.spv
Normal file
Binary file not shown.
91
assets/shaders/m2.vert.glsl
Normal file
91
assets/shaders/m2.vert.glsl
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
vec2 uvOffset;
|
||||
int texCoordSet;
|
||||
int useBones;
|
||||
int isFoliage;
|
||||
} push;
|
||||
|
||||
layout(set = 2, binding = 0) readonly buffer BoneSSBO {
|
||||
mat4 bones[];
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec3 aNormal;
|
||||
layout(location = 2) in vec2 aTexCoord;
|
||||
layout(location = 3) in vec4 aBoneWeights;
|
||||
layout(location = 4) in vec4 aBoneIndicesF;
|
||||
layout(location = 5) in vec2 aTexCoord2;
|
||||
|
||||
layout(location = 0) out vec3 FragPos;
|
||||
layout(location = 1) out vec3 Normal;
|
||||
layout(location = 2) out vec2 TexCoord;
|
||||
layout(location = 3) flat out vec3 InstanceOrigin;
|
||||
layout(location = 4) out float ModelHeight;
|
||||
|
||||
void main() {
|
||||
vec4 pos = vec4(aPos, 1.0);
|
||||
vec4 norm = vec4(aNormal, 0.0);
|
||||
|
||||
if (push.useBones != 0) {
|
||||
ivec4 bi = ivec4(aBoneIndicesF);
|
||||
mat4 skinMat = bones[bi.x] * aBoneWeights.x
|
||||
+ bones[bi.y] * aBoneWeights.y
|
||||
+ bones[bi.z] * aBoneWeights.z
|
||||
+ bones[bi.w] * aBoneWeights.w;
|
||||
pos = skinMat * pos;
|
||||
norm = skinMat * norm;
|
||||
}
|
||||
|
||||
// Wind animation for foliage
|
||||
if (push.isFoliage != 0) {
|
||||
float windTime = fogParams.z;
|
||||
vec3 worldRef = push.model[3].xyz;
|
||||
float heightFactor = clamp(pos.z / 20.0, 0.0, 1.0);
|
||||
heightFactor *= heightFactor; // quadratic — base stays grounded
|
||||
|
||||
// Layer 1: Trunk sway — slow, large amplitude
|
||||
float trunkPhase = windTime * 0.8 + dot(worldRef.xy, vec2(0.1, 0.13));
|
||||
float trunkSwayX = sin(trunkPhase) * 0.35 * heightFactor;
|
||||
float trunkSwayY = cos(trunkPhase * 0.7) * 0.25 * heightFactor;
|
||||
|
||||
// Layer 2: Branch sway — medium frequency, per-branch phase
|
||||
float branchPhase = windTime * 1.7 + dot(worldRef.xy, vec2(0.37, 0.71));
|
||||
float branchSwayX = sin(branchPhase + pos.y * 0.4) * 0.15 * heightFactor;
|
||||
float branchSwayY = cos(branchPhase * 1.1 + pos.x * 0.3) * 0.12 * heightFactor;
|
||||
|
||||
// Layer 3: Leaf flutter — fast, small amplitude, per-vertex
|
||||
float leafPhase = windTime * 4.5 + dot(aPos, vec3(1.7, 2.3, 0.9));
|
||||
float leafFlutterX = sin(leafPhase) * 0.06 * heightFactor;
|
||||
float leafFlutterY = cos(leafPhase * 1.3) * 0.05 * heightFactor;
|
||||
|
||||
pos.x += trunkSwayX + branchSwayX + leafFlutterX;
|
||||
pos.y += trunkSwayY + branchSwayY + leafFlutterY;
|
||||
}
|
||||
|
||||
vec4 worldPos = push.model * pos;
|
||||
FragPos = worldPos.xyz;
|
||||
Normal = mat3(push.model) * norm.xyz;
|
||||
|
||||
TexCoord = (push.texCoordSet == 1 ? aTexCoord2 : aTexCoord) + push.uvOffset;
|
||||
|
||||
InstanceOrigin = push.model[3].xyz;
|
||||
ModelHeight = pos.z;
|
||||
|
||||
gl_Position = projection * view * worldPos;
|
||||
}
|
||||
BIN
assets/shaders/m2.vert.spv
Normal file
BIN
assets/shaders/m2.vert.spv
Normal file
Binary file not shown.
30
assets/shaders/m2_particle.frag.glsl
Normal file
30
assets/shaders/m2_particle.frag.glsl
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D uTexture;
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec2 tileCount;
|
||||
int alphaKey;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec4 vColor;
|
||||
layout(location = 1) in float vTile;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_PointCoord;
|
||||
float tile = floor(vTile);
|
||||
float tx = mod(tile, push.tileCount.x);
|
||||
float ty = floor(tile / push.tileCount.x);
|
||||
vec2 uv = (vec2(tx, ty) + p) / push.tileCount;
|
||||
vec4 texColor = texture(uTexture, uv);
|
||||
|
||||
if (push.alphaKey != 0) {
|
||||
float lum = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||
if (lum < 0.05) discard;
|
||||
}
|
||||
|
||||
float edge = smoothstep(0.5, 0.4, length(p - 0.5));
|
||||
outColor = texColor * vColor * vec4(vec3(1.0), edge);
|
||||
}
|
||||
BIN
assets/shaders/m2_particle.frag.spv
Normal file
BIN
assets/shaders/m2_particle.frag.spv
Normal file
Binary file not shown.
31
assets/shaders/m2_particle.vert.glsl
Normal file
31
assets/shaders/m2_particle.vert.glsl
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec4 aColor;
|
||||
layout(location = 2) in float aSize;
|
||||
layout(location = 3) in float aTile;
|
||||
|
||||
layout(location = 0) out vec4 vColor;
|
||||
layout(location = 1) out float vTile;
|
||||
|
||||
void main() {
|
||||
vec4 viewPos4 = view * vec4(aPos, 1.0);
|
||||
float dist = -viewPos4.z;
|
||||
gl_PointSize = clamp(aSize * 500.0 / max(dist, 1.0), 1.0, 128.0);
|
||||
vColor = aColor;
|
||||
vTile = aTile;
|
||||
gl_Position = projection * viewPos4;
|
||||
}
|
||||
BIN
assets/shaders/m2_particle.vert.spv
Normal file
BIN
assets/shaders/m2_particle.vert.spv
Normal file
Binary file not shown.
25
assets/shaders/m2_smoke.frag.glsl
Normal file
25
assets/shaders/m2_smoke.frag.glsl
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vLifeRatio;
|
||||
layout(location = 1) in float vIsSpark;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_PointCoord - vec2(0.5);
|
||||
float dist = length(p);
|
||||
if (dist > 0.5) discard;
|
||||
|
||||
if (vIsSpark > 0.5) {
|
||||
float glow = smoothstep(0.5, 0.0, dist);
|
||||
float life = 1.0 - vLifeRatio;
|
||||
vec3 color = mix(vec3(1.0, 0.6, 0.1), vec3(1.0, 0.2, 0.0), vLifeRatio);
|
||||
outColor = vec4(color * glow, glow * life);
|
||||
} else {
|
||||
float edge = smoothstep(0.5, 0.3, dist);
|
||||
float fadeIn = smoothstep(0.0, 0.2, vLifeRatio);
|
||||
float fadeOut = 1.0 - smoothstep(0.6, 1.0, vLifeRatio);
|
||||
float alpha = edge * fadeIn * fadeOut * 0.4;
|
||||
outColor = vec4(vec3(0.5), alpha);
|
||||
}
|
||||
}
|
||||
BIN
assets/shaders/m2_smoke.frag.spv
Normal file
BIN
assets/shaders/m2_smoke.frag.spv
Normal file
Binary file not shown.
36
assets/shaders/m2_smoke.vert.glsl
Normal file
36
assets/shaders/m2_smoke.vert.glsl
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
float screenHeight;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in float aLifeRatio;
|
||||
layout(location = 2) in float aSize;
|
||||
layout(location = 3) in float aIsSpark;
|
||||
|
||||
layout(location = 0) out float vLifeRatio;
|
||||
layout(location = 1) out float vIsSpark;
|
||||
|
||||
void main() {
|
||||
vec4 viewPos4 = view * vec4(aPos, 1.0);
|
||||
float dist = -viewPos4.z;
|
||||
float scale = aIsSpark > 0.5 ? 0.12 : 0.3;
|
||||
gl_PointSize = clamp(aSize * scale * push.screenHeight / max(dist, 1.0), 1.0, 64.0);
|
||||
vLifeRatio = aLifeRatio;
|
||||
vIsSpark = aIsSpark;
|
||||
gl_Position = projection * viewPos4;
|
||||
}
|
||||
BIN
assets/shaders/m2_smoke.vert.spv
Normal file
BIN
assets/shaders/m2_smoke.vert.spv
Normal file
Binary file not shown.
68
assets/shaders/minimap_display.frag.glsl
Normal file
68
assets/shaders/minimap_display.frag.glsl
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D uComposite;
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec4 rect;
|
||||
vec2 playerUV;
|
||||
float rotation;
|
||||
float arrowRotation;
|
||||
float zoomRadius;
|
||||
int squareShape;
|
||||
float opacity;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
float cross2d(vec2 a, vec2 b) {
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
bool pointInTriangle(vec2 p, vec2 a, vec2 b, vec2 c) {
|
||||
float d1 = cross2d(b - a, p - a);
|
||||
float d2 = cross2d(c - b, p - b);
|
||||
float d3 = cross2d(a - c, p - c);
|
||||
bool hasNeg = (d1 < 0.0) || (d2 < 0.0) || (d3 < 0.0);
|
||||
bool hasPos = (d1 > 0.0) || (d2 > 0.0) || (d3 > 0.0);
|
||||
return !(hasNeg && hasPos);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 center = TexCoord - 0.5;
|
||||
float dist = length(center);
|
||||
|
||||
if (push.squareShape == 0) {
|
||||
if (dist > 0.5) discard;
|
||||
}
|
||||
|
||||
float cs = cos(push.rotation);
|
||||
float sn = sin(push.rotation);
|
||||
vec2 rotated = vec2(center.x * cs - center.y * sn, center.x * sn + center.y * cs);
|
||||
vec2 mapUV = push.playerUV + rotated * push.zoomRadius * 2.0;
|
||||
|
||||
vec4 mapColor = texture(uComposite, mapUV);
|
||||
|
||||
// Player arrow
|
||||
float acs = cos(push.arrowRotation);
|
||||
float asn = sin(push.arrowRotation);
|
||||
vec2 ac = center;
|
||||
vec2 arrowPos = vec2(ac.x * acs - ac.y * asn, ac.x * asn + ac.y * acs);
|
||||
|
||||
vec2 tip = vec2(0.0, -0.04);
|
||||
vec2 left = vec2(-0.02, 0.02);
|
||||
vec2 right = vec2(0.02, 0.02);
|
||||
|
||||
if (pointInTriangle(arrowPos, tip, left, right)) {
|
||||
mapColor = vec4(1.0, 0.8, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// Dark border ring
|
||||
float border = smoothstep(0.48, 0.5, dist);
|
||||
if (push.squareShape == 0) {
|
||||
mapColor.rgb *= 1.0 - border * 0.7;
|
||||
}
|
||||
|
||||
outColor = vec4(mapColor.rgb, mapColor.a * push.opacity);
|
||||
}
|
||||
BIN
assets/shaders/minimap_display.frag.spv
Normal file
BIN
assets/shaders/minimap_display.frag.spv
Normal file
Binary file not shown.
16
assets/shaders/minimap_display.vert.glsl
Normal file
16
assets/shaders/minimap_display.vert.glsl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec4 rect; // x, y, w, h in 0..1 screen space
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 aPos;
|
||||
layout(location = 1) in vec2 aUV;
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
TexCoord = aUV;
|
||||
vec2 screenPos = push.rect.xy + aPos * push.rect.zw;
|
||||
gl_Position = vec4(screenPos * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/minimap_display.vert.spv
Normal file
BIN
assets/shaders/minimap_display.vert.spv
Normal file
Binary file not shown.
11
assets/shaders/minimap_tile.frag.glsl
Normal file
11
assets/shaders/minimap_tile.frag.glsl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D uTileTexture;
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = texture(uTileTexture, vec2(TexCoord.y, TexCoord.x));
|
||||
}
|
||||
BIN
assets/shaders/minimap_tile.frag.spv
Normal file
BIN
assets/shaders/minimap_tile.frag.spv
Normal file
Binary file not shown.
17
assets/shaders/minimap_tile.vert.glsl
Normal file
17
assets/shaders/minimap_tile.vert.glsl
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec2 gridOffset;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 aPos;
|
||||
layout(location = 1) in vec2 aUV;
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
TexCoord = aUV;
|
||||
vec2 pos = (aPos + push.gridOffset) / 3.0;
|
||||
pos = pos * 2.0 - 1.0;
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/minimap_tile.vert.spv
Normal file
BIN
assets/shaders/minimap_tile.vert.spv
Normal file
Binary file not shown.
13
assets/shaders/mount_dust.frag.glsl
Normal file
13
assets/shaders/mount_dust.frag.glsl
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vAlpha;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_PointCoord - vec2(0.5);
|
||||
float dist = length(p);
|
||||
if (dist > 0.5) discard;
|
||||
float alpha = smoothstep(0.5, 0.1, dist) * vAlpha * 0.4;
|
||||
outColor = vec4(0.7, 0.65, 0.55, alpha);
|
||||
}
|
||||
BIN
assets/shaders/mount_dust.frag.spv
Normal file
BIN
assets/shaders/mount_dust.frag.spv
Normal file
Binary file not shown.
26
assets/shaders/mount_dust.vert.glsl
Normal file
26
assets/shaders/mount_dust.vert.glsl
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in float aSize;
|
||||
layout(location = 2) in float aAlpha;
|
||||
|
||||
layout(location = 0) out float vAlpha;
|
||||
|
||||
void main() {
|
||||
gl_PointSize = aSize;
|
||||
vAlpha = aAlpha;
|
||||
gl_Position = projection * view * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/mount_dust.vert.spv
Normal file
BIN
assets/shaders/mount_dust.vert.spv
Normal file
Binary file not shown.
14
assets/shaders/overlay.frag.glsl
Normal file
14
assets/shaders/overlay.frag.glsl
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#version 450
|
||||
|
||||
// Full-screen color overlay (e.g. underwater tint).
|
||||
// Uses postprocess.vert.glsl as vertex shader (fullscreen triangle, no vertex input).
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec4 color; // rgb = tint color, a = opacity
|
||||
} push;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = push.color;
|
||||
}
|
||||
BIN
assets/shaders/overlay.frag.spv
Normal file
BIN
assets/shaders/overlay.frag.spv
Normal file
Binary file not shown.
20
assets/shaders/postprocess.frag.glsl
Normal file
20
assets/shaders/postprocess.frag.glsl
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D uScene;
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec3 hdr = texture(uScene, TexCoord).rgb;
|
||||
// Shoulder tone map
|
||||
vec3 mapped = hdr;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (mapped[i] > 0.9) {
|
||||
float excess = mapped[i] - 0.9;
|
||||
mapped[i] = 0.9 + 0.1 * excess / (excess + 0.1);
|
||||
}
|
||||
}
|
||||
outColor = vec4(mapped, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/postprocess.frag.spv
Normal file
BIN
assets/shaders/postprocess.frag.spv
Normal file
Binary file not shown.
10
assets/shaders/postprocess.vert.glsl
Normal file
10
assets/shaders/postprocess.vert.glsl
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
// Fullscreen triangle trick: 3 vertices, no vertex buffer
|
||||
TexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(TexCoord * 2.0 - 1.0, 0.0, 1.0);
|
||||
TexCoord.y = 1.0 - TexCoord.y; // flip Y for Vulkan
|
||||
}
|
||||
BIN
assets/shaders/postprocess.vert.spv
Normal file
BIN
assets/shaders/postprocess.vert.spv
Normal file
Binary file not shown.
18
assets/shaders/quest_marker.frag.glsl
Normal file
18
assets/shaders/quest_marker.frag.glsl
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D markerTexture;
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
float alpha;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec4 texColor = texture(markerTexture, TexCoord);
|
||||
if (texColor.a < 0.1) discard;
|
||||
outColor = vec4(texColor.rgb, texColor.a * push.alpha);
|
||||
}
|
||||
BIN
assets/shaders/quest_marker.frag.spv
Normal file
BIN
assets/shaders/quest_marker.frag.spv
Normal file
Binary file not shown.
28
assets/shaders/quest_marker.vert.glsl
Normal file
28
assets/shaders/quest_marker.vert.glsl
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 model;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
TexCoord = aTexCoord;
|
||||
gl_Position = projection * view * push.model * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/quest_marker.vert.spv
Normal file
BIN
assets/shaders/quest_marker.vert.spv
Normal file
Binary file not shown.
19
assets/shaders/selection_circle.frag.glsl
Normal file
19
assets/shaders/selection_circle.frag.glsl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 mvp;
|
||||
vec4 color;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 vLocalPos;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
float r = length(vLocalPos);
|
||||
float ring = smoothstep(0.93, 0.97, r) * smoothstep(1.0, 0.97, r);
|
||||
float inward = (1.0 - smoothstep(0.0, 0.93, r)) * 0.15;
|
||||
float alpha = max(ring, inward);
|
||||
if (alpha < 0.01) discard;
|
||||
outColor = vec4(push.color.rgb, alpha);
|
||||
}
|
||||
BIN
assets/shaders/selection_circle.frag.spv
Normal file
BIN
assets/shaders/selection_circle.frag.spv
Normal file
Binary file not shown.
14
assets/shaders/selection_circle.vert.glsl
Normal file
14
assets/shaders/selection_circle.vert.glsl
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 mvp;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
|
||||
layout(location = 0) out vec2 vLocalPos;
|
||||
|
||||
void main() {
|
||||
vLocalPos = aPos.xz;
|
||||
gl_Position = push.mvp * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/selection_circle.vert.spv
Normal file
BIN
assets/shaders/selection_circle.vert.spv
Normal file
Binary file not shown.
22
assets/shaders/shadow.frag.glsl
Normal file
22
assets/shaders/shadow.frag.glsl
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D uTexture;
|
||||
|
||||
layout(set = 0, binding = 1) uniform ShadowParams {
|
||||
int useBones;
|
||||
int useTexture;
|
||||
int alphaTest;
|
||||
int foliageSway;
|
||||
float windTime;
|
||||
float foliageMotionDamp;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
layout(location = 1) in vec3 WorldPos;
|
||||
|
||||
void main() {
|
||||
if (useTexture != 0) {
|
||||
vec4 texColor = textureLod(uTexture, TexCoord, 0.0);
|
||||
if (alphaTest != 0 && texColor.a < 0.5) discard;
|
||||
}
|
||||
}
|
||||
BIN
assets/shaders/shadow.frag.spv
Normal file
BIN
assets/shaders/shadow.frag.spv
Normal file
Binary file not shown.
57
assets/shaders/shadow.vert.glsl
Normal file
57
assets/shaders/shadow.vert.glsl
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#version 450
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
mat4 lightSpaceMatrix;
|
||||
mat4 model;
|
||||
} push;
|
||||
|
||||
layout(set = 0, binding = 1) uniform ShadowParams {
|
||||
int useBones;
|
||||
int useTexture;
|
||||
int alphaTest;
|
||||
int foliageSway;
|
||||
float windTime;
|
||||
float foliageMotionDamp;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
layout(location = 2) in vec4 aBoneWeights;
|
||||
layout(location = 3) in vec4 aBoneIndicesF;
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
layout(location = 1) out vec3 WorldPos;
|
||||
|
||||
void main() {
|
||||
vec4 pos = vec4(aPos, 1.0);
|
||||
|
||||
// Wind vertex displacement for foliage (matches m2.vert.glsl)
|
||||
if (foliageSway != 0) {
|
||||
vec3 worldRef = push.model[3].xyz;
|
||||
float heightFactor = clamp(pos.z / 20.0, 0.0, 1.0);
|
||||
heightFactor *= heightFactor;
|
||||
|
||||
// Layer 1: Trunk sway
|
||||
float trunkPhase = windTime * 0.8 + dot(worldRef.xy, vec2(0.1, 0.13));
|
||||
float trunkSwayX = sin(trunkPhase) * 0.35 * heightFactor;
|
||||
float trunkSwayY = cos(trunkPhase * 0.7) * 0.25 * heightFactor;
|
||||
|
||||
// Layer 2: Branch sway
|
||||
float branchPhase = windTime * 1.7 + dot(worldRef.xy, vec2(0.37, 0.71));
|
||||
float branchSwayX = sin(branchPhase + pos.y * 0.4) * 0.15 * heightFactor;
|
||||
float branchSwayY = cos(branchPhase * 1.1 + pos.x * 0.3) * 0.12 * heightFactor;
|
||||
|
||||
// Layer 3: Leaf flutter
|
||||
float leafPhase = windTime * 4.5 + dot(aPos, vec3(1.7, 2.3, 0.9));
|
||||
float leafFlutterX = sin(leafPhase) * 0.06 * heightFactor;
|
||||
float leafFlutterY = cos(leafPhase * 1.3) * 0.05 * heightFactor;
|
||||
|
||||
pos.x += trunkSwayX + branchSwayX + leafFlutterX;
|
||||
pos.y += trunkSwayY + branchSwayY + leafFlutterY;
|
||||
}
|
||||
|
||||
vec4 worldPos = push.model * pos;
|
||||
WorldPos = worldPos.xyz;
|
||||
TexCoord = aTexCoord;
|
||||
gl_Position = push.lightSpaceMatrix * worldPos;
|
||||
}
|
||||
BIN
assets/shaders/shadow.vert.spv
Normal file
BIN
assets/shaders/shadow.vert.spv
Normal file
Binary file not shown.
97
assets/shaders/skybox.frag.glsl
Normal file
97
assets/shaders/skybox.frag.glsl
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
vec4 zenithColor; // DBC skyTopColor
|
||||
vec4 midColor; // DBC skyMiddleColor
|
||||
vec4 horizonColor; // DBC skyBand1Color
|
||||
vec4 fogColorPush; // DBC skyBand2Color
|
||||
vec4 sunDirAndTime; // xyz = sun direction, w = timeOfDay
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
// Reconstruct world-space ray direction from screen position.
|
||||
float ndcX = TexCoord.x * 2.0 - 1.0;
|
||||
float ndcY = -(TexCoord.y * 2.0 - 1.0);
|
||||
|
||||
vec3 viewDir = vec3(ndcX / projection[0][0],
|
||||
ndcY / abs(projection[1][1]),
|
||||
-1.0);
|
||||
|
||||
mat3 invViewRot = transpose(mat3(view));
|
||||
vec3 worldDir = normalize(invViewRot * viewDir);
|
||||
|
||||
vec3 sunDir = push.sunDirAndTime.xyz;
|
||||
float timeOfDay = push.sunDirAndTime.w;
|
||||
|
||||
// Elevation: +1 = zenith, 0 = horizon, -1 = nadir
|
||||
float elev = worldDir.z;
|
||||
float elevClamped = clamp(elev, 0.0, 1.0);
|
||||
|
||||
// --- 3-band sky gradient using DBC colors ---
|
||||
// Zenith dominates upper sky, mid color fills the middle,
|
||||
// horizon band at the bottom with a thin fog fringe.
|
||||
vec3 sky;
|
||||
if (elevClamped > 0.4) {
|
||||
// Upper sky: mid -> zenith
|
||||
float t = (elevClamped - 0.4) / 0.6;
|
||||
sky = mix(push.midColor.rgb, push.zenithColor.rgb, t);
|
||||
} else if (elevClamped > 0.05) {
|
||||
// Lower sky: horizon -> mid (wide band)
|
||||
float t = (elevClamped - 0.05) / 0.35;
|
||||
sky = mix(push.horizonColor.rgb, push.midColor.rgb, t);
|
||||
} else {
|
||||
// Thin fog fringe right at horizon
|
||||
float t = elevClamped / 0.05;
|
||||
sky = mix(push.fogColorPush.rgb, push.horizonColor.rgb, t);
|
||||
}
|
||||
|
||||
// --- Below-horizon darkening (nadir) ---
|
||||
if (elev < 0.0) {
|
||||
float nadirFade = clamp(-elev * 3.0, 0.0, 1.0);
|
||||
vec3 nadirColor = push.fogColorPush.rgb * 0.3;
|
||||
sky = mix(push.fogColorPush.rgb, nadirColor, nadirFade);
|
||||
}
|
||||
|
||||
// --- Rayleigh-like scattering (subtle warm glow near sun) ---
|
||||
float sunDot = max(dot(worldDir, sunDir), 0.0);
|
||||
float sunAboveHorizon = clamp(sunDir.z, 0.0, 1.0);
|
||||
|
||||
float rayleighStrength = pow(1.0 - elevClamped, 3.0) * 0.15;
|
||||
vec3 scatterColor = mix(vec3(0.8, 0.45, 0.15), vec3(0.3, 0.5, 1.0), elevClamped);
|
||||
sky += scatterColor * rayleighStrength * sunDot * sunAboveHorizon;
|
||||
|
||||
// --- Mie-like forward scatter (sun disk glow) ---
|
||||
float mieSharp = pow(sunDot, 64.0) * 0.4;
|
||||
float mieSoft = pow(sunDot, 8.0) * 0.1;
|
||||
vec3 sunGlowColor = mix(vec3(1.0, 0.85, 0.55), vec3(1.0, 1.0, 0.95), elevClamped);
|
||||
sky += sunGlowColor * (mieSharp + mieSoft) * sunAboveHorizon;
|
||||
|
||||
// --- Subtle horizon haze ---
|
||||
float hazeDensity = exp(-elevClamped * 12.0) * 0.06;
|
||||
sky += push.horizonColor.rgb * hazeDensity * sunAboveHorizon;
|
||||
|
||||
// --- Night: slight moonlight tint ---
|
||||
if (sunDir.z < 0.0) {
|
||||
float moonlight = clamp(-sunDir.z * 0.5, 0.0, 0.15);
|
||||
sky += vec3(0.02, 0.03, 0.08) * moonlight;
|
||||
}
|
||||
|
||||
outColor = vec4(sky, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/skybox.frag.spv
Normal file
BIN
assets/shaders/skybox.frag.spv
Normal file
Binary file not shown.
12
assets/shaders/skybox.vert.glsl
Normal file
12
assets/shaders/skybox.vert.glsl
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 450
|
||||
|
||||
// Fullscreen triangle sky — no vertex buffer, no mesh.
|
||||
// Draws 3 vertices covering the entire screen, depth forced to 1.0 (far plane).
|
||||
|
||||
layout(location = 0) out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
// Produces triangle covering NDC [-1,1]² with depth = 1.0 (far)
|
||||
TexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(TexCoord * 2.0 - 1.0, 1.0, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/skybox.vert.spv
Normal file
BIN
assets/shaders/skybox.vert.spv
Normal file
Binary file not shown.
13
assets/shaders/starfield.frag.glsl
Normal file
13
assets/shaders/starfield.frag.glsl
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vBrightness;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_PointCoord - vec2(0.5);
|
||||
float dist = length(p);
|
||||
if (dist > 0.5) discard;
|
||||
float alpha = vBrightness * smoothstep(0.5, 0.2, dist);
|
||||
outColor = vec4(vec3(0.9, 0.95, 1.0) * vBrightness, alpha);
|
||||
}
|
||||
BIN
assets/shaders/starfield.frag.spv
Normal file
BIN
assets/shaders/starfield.frag.spv
Normal file
Binary file not shown.
33
assets/shaders/starfield.vert.glsl
Normal file
33
assets/shaders/starfield.vert.glsl
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform PerFrame {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
mat4 lightSpaceMatrix;
|
||||
vec4 lightDir;
|
||||
vec4 lightColor;
|
||||
vec4 ambientColor;
|
||||
vec4 viewPos;
|
||||
vec4 fogColor;
|
||||
vec4 fogParams;
|
||||
vec4 shadowParams;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
float time;
|
||||
float intensity;
|
||||
} push;
|
||||
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in float aBrightness;
|
||||
layout(location = 2) in float aTwinklePhase;
|
||||
|
||||
layout(location = 0) out float vBrightness;
|
||||
|
||||
void main() {
|
||||
mat4 rotView = mat4(mat3(view));
|
||||
float twinkle = 0.7 + 0.3 * sin(push.time * 1.5 + aTwinklePhase);
|
||||
vBrightness = aBrightness * twinkle * push.intensity;
|
||||
gl_PointSize = mix(2.0, 4.0, aBrightness);
|
||||
gl_Position = projection * rotView * vec4(aPos, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/starfield.vert.spv
Normal file
BIN
assets/shaders/starfield.vert.spv
Normal file
Binary file not shown.
15
assets/shaders/swim_bubble.frag.glsl
Normal file
15
assets/shaders/swim_bubble.frag.glsl
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in float vAlpha;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_PointCoord - vec2(0.5);
|
||||
float dist = length(p);
|
||||
if (dist > 0.5) discard;
|
||||
float ring = smoothstep(0.5, 0.4, dist) - smoothstep(0.38, 0.28, dist);
|
||||
float highlight = smoothstep(0.3, 0.1, length(p - vec2(-0.15, 0.15))) * 0.5;
|
||||
float alpha = (ring + highlight) * vAlpha;
|
||||
outColor = vec4(0.8, 0.9, 1.0, alpha);
|
||||
}
|
||||
BIN
assets/shaders/swim_bubble.frag.spv
Normal file
BIN
assets/shaders/swim_bubble.frag.spv
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue