diff --git a/CMakeLists.txt b/CMakeLists.txt index 907ca6a8d..cf3c24a45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ endif () add_subdirectory(trinityal) add_subdirectory(trinity) +add_subdirectory(shaders) if(CMAKE_PROJECT_NAME STREQUAL carbon-trinity AND NOT BUILD_FOR_PYTHON_2) # Determine if/when to generate/build documentation. The rules are as follows: @@ -139,3 +140,4 @@ if(CMAKE_PROJECT_NAME STREQUAL carbon-trinity AND NOT BUILD_FOR_PYTHON_2) ) endif() endif() + diff --git a/shadercompiler/CachingIncludeHandler.cpp b/shadercompiler/CachingIncludeHandler.cpp index 7f2150290..d80eadccc 100644 --- a/shadercompiler/CachingIncludeHandler.cpp +++ b/shadercompiler/CachingIncludeHandler.cpp @@ -61,6 +61,26 @@ std::optional JoinPath( const char* path1, const char* path2 ) #endif } +std::optional JoinPathAndCheckExists( const char* path1, const char* path2 ) +{ + if( auto joined = JoinPath( path1, path2 ) ) + { +#if _WIN32 + if( PathFileExists( joined->c_str() ) ) + { + return joined; + } +#else + struct stat buffer; + if( stat( joined->c_str(), &buffer ) == 0 ) + { + return joined; + } +#endif + } + return std::nullopt; +} + } @@ -85,37 +105,62 @@ CachingIncludeHandler::~CachingIncludeHandler() // used for resolving relative paths // rootPath - Custom root path for resolving relative paths // -------------------------------------------------------------------------------------- -std::optional CachingIncludeHandler::Open( const char* fileName, const char* parentData, const char* rootPath ) +std::optional CachingIncludeHandler::Open( const char* fileName, IncludeType includeType, const char* parentData, const char* rootPath ) { std::lock_guard scope( m_cs ); std::string fullPath; if( PathIsRelative( fileName ) ) { - std::string parentPath; - if( parentData == nullptr ) - { - parentPath = rootPath ? rootPath : m_rootPath; - } - else - { - auto parent = m_pathFromFile.find( parentData - 1 ); - if( parent != m_pathFromFile.end() ) + auto FindLocalPath = [&]() -> bool { + std::string parentPath; + if( parentData == nullptr ) { - parentPath = parent->second; + parentPath = rootPath ? rootPath : m_rootPath; } else { - parentPath = ""; + auto parent = m_pathFromFile.find( parentData - 1 ); + if( parent != m_pathFromFile.end() ) + { + parentPath = parent->second; + } + else + { + parentPath = ""; + } } - } - if( auto joined = JoinPath( parentPath.c_str(), fileName ) ) + if( auto joined = JoinPathAndCheckExists( parentPath.c_str(), fileName ) ) + { + fullPath = *joined; + return true; + } + return false; + }; + auto FindSystemPath = [&]() -> bool { + for( auto it = m_systemIncludePaths.begin(); it != m_systemIncludePaths.end(); ++it ) + { + if( auto joined = JoinPathAndCheckExists( it->c_str(), fileName ) ) + { + fullPath = *joined; + return true; + } + } + return false; + }; + if( includeType == IncludeLocal ) { - fullPath = *joined; + if( !FindLocalPath() && !FindSystemPath() ) + { + return std::nullopt; + } } else { - return std::nullopt; + if( !FindSystemPath() && !FindLocalPath() ) + { + return std::nullopt; + } } } else @@ -221,6 +266,12 @@ std::optional CachingIncludeHandler::AddPre return info; } +void CachingIncludeHandler::AddSystemIncludePath( const char* path ) +{ + std::lock_guard scope( m_cs ); + m_systemIncludePaths.push_back( path ); +} + // -------------------------------------------------------------------------------------- // Description: // Assigns path to the "root", entry point file. diff --git a/shadercompiler/CachingIncludeHandler.h b/shadercompiler/CachingIncludeHandler.h index 48672b78d..91333c648 100644 --- a/shadercompiler/CachingIncludeHandler.h +++ b/shadercompiler/CachingIncludeHandler.h @@ -22,12 +22,20 @@ class CachingIncludeHandler time_t modifiedTime; }; - std::optional Open( const char* fileName, const char* parentData = nullptr, const char* rootPath = nullptr ); + enum IncludeType + { + IncludeLocal, + IncludeSystem + }; + + std::optional Open( const char* fileName, IncludeType includeType, const char* parentData = nullptr, const char* rootPath = nullptr ); void SetRootPath( const char* shaderPath ); std::optional AddPrefix( const char* fileName, const char* prefix ); + void AddSystemIncludePath( const char* path ); + private: typedef std::map PathFromFile; typedef std::map FileFromPath; @@ -38,6 +46,7 @@ class CachingIncludeHandler FileFromPath m_fileFromPath; // Path of the entry point file std::string m_rootPath; + std::vector m_systemIncludePaths; std::mutex m_cs; }; diff --git a/shadercompiler/EffectCompilerDX11.cpp b/shadercompiler/EffectCompilerDX11.cpp index 9512daa97..1ad97abea 100644 --- a/shadercompiler/EffectCompilerDX11.cpp +++ b/shadercompiler/EffectCompilerDX11.cpp @@ -1789,6 +1789,13 @@ bool EffectCompilerDX11::CompileEffect( const char* source, size_t sourceLength, CComPtr pCompileResult; HRESULT hrCompilation = compiler->Compile( &sourceBuffer, arguments, argumentsSize, nullptr, IID_PPV_ARGS( &pCompileResult ) ); + if( SUCCEEDED( hrCompilation ) ) + { + if( FAILED( pCompileResult->GetStatus( &hrCompilation ) ) ) + { + hrCompilation = E_FAIL; + } + } if( FAILED( hrCompilation ) ) { diff --git a/shadercompiler/HLSLScanner.re b/shadercompiler/HLSLScanner.re index ee976dec2..259505133 100644 --- a/shadercompiler/HLSLScanner.re +++ b/shadercompiler/HLSLScanner.re @@ -41,16 +41,16 @@ ID = LETTER(LETTER|DECIMAL)*; #define YYMARKER (marker) #define YYCTXMARKER (ctxMarker) -bool GetIncludePath( ParserState &state, InlineString& path ) +bool GetIncludePath( ParserState &state, InlineString& path, CachingIncludeHandler::IncludeType& includeType ) { - bool local; + includeType = CachingIncludeHandler::IncludeLocal; if( *YYCURSOR == '"' ) { - local = true; + includeType = CachingIncludeHandler::IncludeLocal; } else if( *YYCURSOR == '<' ) { - local = false; + includeType = CachingIncludeHandler::IncludeSystem; } else { @@ -61,7 +61,7 @@ bool GetIncludePath( ParserState &state, InlineString& path ) { if( *YYCURSOR == '"' ) { - if( local ) + if( includeType == CachingIncludeHandler::IncludeLocal ) { path.end = ++YYCURSOR; return true; @@ -69,7 +69,7 @@ bool GetIncludePath( ParserState &state, InlineString& path ) } else if( *YYCURSOR == '>' ) { - if( !local ) + if( includeType == CachingIncludeHandler::IncludeSystem ) { path.end = ++YYCURSOR; return true; @@ -141,14 +141,15 @@ std: { state.GetCurrentLocation().lineNumber++; InlineString path; - if( !GetIncludePath( state, path ) ) + CachingIncludeHandler::IncludeType includeType; + if( !GetIncludePath( state, path, includeType ) ) { state.ShowMessage( EC_INVALID_INCLUDE_PATH ); return PPSR_ERROR; } if( !state.InSkipMode() ) { - state.IncludeFile( path ); + state.IncludeFile( path, includeType ); } start = YYCURSOR; continue; @@ -566,7 +567,7 @@ std: ++YYCURSOR; } - if( !state.InSkipMode() ) + if( !state.InSkipMode() && !state.InDiscoverMode() ) { state.ShowMessage( EC_USER_ERROR, std::string( start, YYCURSOR ).c_str() ); return PPSR_ERROR; diff --git a/shadercompiler/MSLInclude.h b/shadercompiler/MSLInclude.h index 100ceabfd..0bf2a50a4 100644 --- a/shadercompiler/MSLInclude.h +++ b/shadercompiler/MSLInclude.h @@ -332,6 +332,17 @@ void GetDimensions( texturecube tex, thread W& w, thread H& h ) h = H( tex.get_height() ); } +template +void GetDimensions( texturecube tex, uint mip, thread W& w, thread H& h, thread M& m ) +{ + w = W( tex.get_width( mip ) ); + h = H( tex.get_height( mip ) ); + m = M( tex.get_num_mip_levels() ); +} + +)MSL" + R"MSL( + template void GetDimensions( texture3d tex, thread W& w, thread H& h, thread D& d ) { diff --git a/shadercompiler/ModifiedTime.cpp b/shadercompiler/ModifiedTime.cpp index bd11d80ad..4dbee5566 100644 --- a/shadercompiler/ModifiedTime.cpp +++ b/shadercompiler/ModifiedTime.cpp @@ -47,7 +47,7 @@ struct HashCheckArguments std::mutex s_modifiedOutputsCS; std::set s_modifiedOutputs; -const std::regex s_include( "#[[:space:]]*include[[:space:]]*[<\"]([^>\"]*)" ); +const std::regex s_include( "#[[:space:]]*include[[:space:]]*([<\"])([^>\"]*)" ); bool IsOutputUpToDate( const char* path, const std::string& sourceHash ) { @@ -112,14 +112,14 @@ bool CheckHash( const HashCheckArguments& query ) return true; } -void GetSourceHash( const char* sourcePath, const char* parentData, const char* rootPath, std::set& visited, MD5& md5 ) +void GetSourceHash( const char* sourcePath, const char* parentData, const char* rootPath, CachingIncludeHandler::IncludeType includeType, std::set& visited, MD5& md5 ) { if( visited.find( sourcePath ) != end( visited ) ) { return; } visited.insert( sourcePath ); - if( auto opened = s_includeHandler.Open( sourcePath, parentData, rootPath ) ) + if( auto opened = s_includeHandler.Open( sourcePath, includeType, parentData, rootPath ) ) { md5.add( opened->data, opened->size ); @@ -127,7 +127,8 @@ void GetSourceHash( const char* sourcePath, const char* parentData, const char* auto begin = opened->data; while( std::regex_search( begin, opened->data + opened->size, match, s_include ) ) { - GetSourceHash( match[1].str().c_str(), opened->data, rootPath, visited, md5 ); + CachingIncludeHandler::IncludeType it = match[1].str() == "<" ? CachingIncludeHandler::IncludeSystem : CachingIncludeHandler::IncludeLocal; + GetSourceHash( match[2].str().c_str(), opened->data, rootPath, it, visited, md5 ); begin += match.position() + match.length(); } } @@ -159,7 +160,7 @@ std::string GetSourceHash( const char* sourcePath, const std::vector& def md5.add( &g_generatePDB, sizeof( g_generatePDB ) ); md5.add( &g_skipOptimization, sizeof( g_skipOptimization ) ); - GetSourceHash( sourcePath, nullptr, sourcePath, visited, md5 ); + GetSourceHash( sourcePath, nullptr, sourcePath, CachingIncludeHandler::IncludeLocal, visited, md5 ); return md5.getHash(); } diff --git a/shadercompiler/ParserState.cpp b/shadercompiler/ParserState.cpp index 6883c82b1..3e604aec0 100644 --- a/shadercompiler/ParserState.cpp +++ b/shadercompiler/ParserState.cpp @@ -988,12 +988,12 @@ bool ParserState::InMacro() const return m_fileStack.back().isMacro; } -void ParserState::IncludeFile( const InlineString& fileName ) +void ParserState::IncludeFile( const InlineString& fileName, CachingIncludeHandler::IncludeType includeType ) { extern CachingIncludeHandler g_includeHandler; std::string path( fileName.start + 1, fileName.end - 1 ); - if( auto file = g_includeHandler.Open( path.c_str(), m_fileStack.back().code.start + 1 ) ) + if( auto file = g_includeHandler.Open( path.c_str(), includeType, m_fileStack.back().code.start + 1 ) ) { FileContents fileContents; diff --git a/shadercompiler/ParserState.h b/shadercompiler/ParserState.h index 1bafdde7f..73cdc73d7 100644 --- a/shadercompiler/ParserState.h +++ b/shadercompiler/ParserState.h @@ -3,6 +3,7 @@ #pragma once #include "InlineString.h" +#include "CachingIncludeHandler.h" class SymbolTable; class ASTNode; @@ -234,7 +235,7 @@ class ParserState bool InDiscoverMode() const; PreprocessorScanResult GetPreprocessorToken( PreprocessorToken& token ); - void IncludeFile( const InlineString& fileName ); + void IncludeFile( const InlineString& fileName, CachingIncludeHandler::IncludeType includeType ); void ParseDefine( const PreprocessorDefine& define, const FileLocation& location, const std::vector& arguments ); PreprocessorDefine* FindDefine( const InlineString& name ); diff --git a/shadercompiler/ParserUtils.cpp b/shadercompiler/ParserUtils.cpp index d42566e00..a3c313409 100644 --- a/shadercompiler/ParserUtils.cpp +++ b/shadercompiler/ParserUtils.cpp @@ -905,23 +905,27 @@ int GetNodeOrder( ASTNode* t ) { if( child->GetNodeType() == NT_CBUFFER ) { - return 2; + return 3; } } if( auto child = t->GetChildOrNull( 1 ) ) { if( child->GetNodeType() == NT_CBUFFER ) { - return 2; + return 3; } } if( GetRegisterType( t->GetType() ) ) { - return 2; + return 3; } else { - return 1; + if( t->GetType().modifier == OP_CONST && t->GetType().storageClass == OP_STATIC ) + { + return 1; + } + return 2; } case NT_TECHNIQUE: return 12; @@ -985,7 +989,7 @@ void CreateGlobalsCB( ParserState& state ) continue; } } - if( !GetRegisterType( child->GetType() ) ) + if( !GetRegisterType( child->GetType() ) && !( child->GetType().modifier == OP_CONST && child->GetType().storageClass == OP_STATIC ) ) { varStart = std::min( varStart, i ); varEnd = std::max( varEnd, i ); diff --git a/shadercompiler/ShaderCompiler.cpp b/shadercompiler/ShaderCompiler.cpp index 294755c73..00d410db8 100644 --- a/shadercompiler/ShaderCompiler.cpp +++ b/shadercompiler/ShaderCompiler.cpp @@ -208,6 +208,7 @@ void PrintUsage() printf( " /E{e,w,d}[extension] - Specify support for all or certain GLES extensions\n" ); printf( " /novalidate - Skip validating converted GLSL code\n" ); printf( " /permutations - Print permutations of the shader\n" ); + printf( " /I - Specify additional include directory\n" ); #if CCP_TELEMETRY_ENABLED printf( " /telemetry - Enable RAD Telemetry\n" ); #endif @@ -302,6 +303,18 @@ bool ExtractCommandLineArguments( ProgramArguments& args, int argc, char* argv[] { args.ignorePermutations = true; } + else if( strcmp( argv[i], "/I" ) == 0 ) + { + ++i; + if( i < argc ) + { + g_includeHandler.AddSystemIncludePath( argv[i] ); + } + else + { + return false; + } + } else if( strcmp( argv[i], "/define" ) == 0 ) { Macro define; @@ -411,7 +424,7 @@ bool ExtractCommandLineArguments( ProgramArguments& args, int argc, char* argv[] bool PrintPermutations( const char* shaderPath ) { - auto shader = g_includeHandler.Open( shaderPath ); + auto shader = g_includeHandler.Open( shaderPath, CachingIncludeHandler::IncludeLocal ); if( !shader ) { @@ -624,7 +637,7 @@ int main( int argc, char* argv[] ) } // Preload shader file - auto shader = g_includeHandler.Open( args.shaderPath ); + auto shader = g_includeHandler.Open( args.shaderPath, CachingIncludeHandler::IncludeLocal ); if( !shader ) { printf( "%s: error X0000: Could not open input file \"%s\"\n", args.shaderPath, args.shaderPath ); diff --git a/shadercompiler/TextureFunctionConversionDX11.cpp b/shadercompiler/TextureFunctionConversionDX11.cpp index c3fa7a993..f61a52a8c 100644 --- a/shadercompiler/TextureFunctionConversionDX11.cpp +++ b/shadercompiler/TextureFunctionConversionDX11.cpp @@ -596,6 +596,7 @@ static void PatchCalls( ParserState& state, } if( texture == nullptr || texture->type.symbol ) { + state.ShowMessage( node->GetLocation(), EC_CUSTOM_ERROR, "Sampler %s is used in a dx9-style texture sampling function without a link to the texture", ToString( samplerArg->name ).c_str() ); assert( false ); return; } diff --git a/shaders/CMakeLists.txt b/shaders/CMakeLists.txt new file mode 100644 index 000000000..38a60a771 --- /dev/null +++ b/shaders/CMakeLists.txt @@ -0,0 +1,103 @@ +# Copyright © 2026 CCP ehf. + +cmake_minimum_required(VERSION 3.16) + +project(TrinityShaders VERSION 1.0.0) + +set(_SHADER_SOURCES + include/Carbon/InteriorScene/PerObjectData.fxh + include/Carbon/InteriorScene/PerSceneData.fxh + include/Carbon/SpaceScene/Raytracing/GeometryData.fxh + include/Carbon/SpaceScene/Raytracing/ShadowData.fxh + include/Carbon/SpaceScene/BehaviorGroupBooster.fxh + include/Carbon/SpaceScene/DepthMap.fxh + include/Carbon/SpaceScene/DynamicLighting.fxh + include/Carbon/SpaceScene/EnvironmentMap.fxh + include/Carbon/SpaceScene/EveBannerSet.fxh + include/Carbon/SpaceScene/EveBoosterSet.fxh + include/Carbon/SpaceScene/EveChildBoosterSet.fxh + include/Carbon/SpaceScene/EveChildBulletStorm.fxh + include/Carbon/SpaceScene/EveChildCloud.fxh + include/Carbon/SpaceScene/EveChildCloud2.fxh + include/Carbon/SpaceScene/EveHazeSet.fxh + include/Carbon/SpaceScene/EveLensflare.fxh + include/Carbon/SpaceScene/EveOccluder.fxh + include/Carbon/SpaceScene/EvePlaneSet.fxh + include/Carbon/SpaceScene/EveChildQuad.fxh + include/Carbon/SpaceScene/EveSpaceObjectDecal.fxh + include/Carbon/SpaceScene/EveSpherePin.fxh + include/Carbon/SpaceScene/EveSpotlightSet.fxh + include/Carbon/SpaceScene/EveSpriteSet.fxh + include/Carbon/SpaceScene/EveStarfield.fxh + include/Carbon/SpaceScene/EveTransform.fxh + include/Carbon/SpaceScene/EveStretch2.fxh + include/Carbon/SpaceScene/EveTacticalOverlay.fxh + include/Carbon/SpaceScene/EveTurretSet.fxh + include/Carbon/SpaceScene/FroxelFog.fxh + include/Carbon/SpaceScene/GpuParticles.fxh + include/Carbon/SpaceScene/MeshMorphing.fxh + include/Carbon/SpaceScene/PerObjectData.fxh + include/Carbon/SpaceScene/PerSceneData.fxh + include/Carbon/SpaceScene/Volumetrics.fxh + include/Carbon/UI/UI.fxh + include/Carbon/Bindless.fxh + include/Carbon/BoneTransforms.fxh + include/Carbon/Intersections.fxh + include/Carbon/Math.fxh + include/Carbon/PackedTangents.fxh + include/Carbon/ParticleAnnotations.fxh + include/Carbon/Picking.fxh + include/Carbon/RandomXOrShift.fxh + include/Carbon/System.fxh + include/Carbon/Tr2CurveLineSet.fxh + include/Carbon/VertexBufferUtil.fxh + Managed/Space/SpecialFX/Lensflares/OccluderManagement.fx + Managed/Space/SpecialFX/Particles/Gpu/Clear.fx + Managed/Space/SpecialFX/Particles/Gpu/Emit.fx + Managed/Space/SpecialFX/Particles/Gpu/SetDrawParameters.fx + Managed/Space/SpecialFX/Particles/Gpu/SetSortArgs.fx + Managed/Space/SpecialFX/Particles/Gpu/Sort.fx + Managed/Space/SpecialFX/Particles/Gpu/SortInner.fx + Managed/Space/SpecialFX/Particles/Gpu/SortStep.fx + Managed/Space/SpecialFX/Particles/Gpu/Update.fx + Managed/Space/SpecialFX/Volumetric/Fog/ApplyFroxels.fx + Managed/Space/SpecialFX/Volumetric/Fog/FilterFroxels.fx + Managed/Space/SpecialFX/Volumetric/Fog/RaymarchFroxels.fx + Managed/Space/SpecialFX/Volumetric/Fog/UpdateMieEnvironmentMap.fx + Managed/Space/SpecialFX/Volumetric/BlurVolumetric.fx + Managed/Space/SpecialFX/Volumetric/DownsampleDepth.fx + Managed/Space/System/Raytracing/SkinVertices.fx + Managed/Space/System/ComputeLightLists.fx + Managed/Space/System/Denoise1D.fx + Managed/Space/System/DenoiseEstimate.fx + Managed/Space/System/EstimateNoise.fx + Managed/Utility/BillboardNoZ.fx + Managed/Utility/BillboardPicking.fx + Managed/Utility/Debug.fx + Managed/Utility/DebugLine.fx + Managed/Utility/DebugPrimitive.fx + Managed/Utility/DebugPrimitivePicking.fx + Managed/Utility/Lines.fxh + Managed/Utility/LinesNoZ.fx + Managed/Utility/LinesRotationTool.fx + Managed/Utility/LinesRotationToolLH.fx + Managed/Utility/LinesWithZ.fx + Managed/Utility/PerFrameVSData.fxh + Managed/Utility/PrimitivePicking.fx + Managed/Utility/SolidNoLightNoCullWithZ.fx + Managed/Utility/Solids.fxh + Managed/Utility/SolidsNoZ.fx + Managed/Utility/SolidsWithZ.fx + Utility/CubeTextureViewer.fx + Utility/GeometryViewer.fx + Utility/LineGraph.fx + Utility/TextureViewer.fx + ) + +add_custom_target(Shaders SOURCES ${_SHADER_SOURCES}) +source_group( + TREE "${CMAKE_CURRENT_SOURCE_DIR}" + FILES ${_SHADER_SOURCES} +) + +install(DIRECTORY . DESTINATION shaders FILES_MATCHING PATTERN "*.fx" PATTERN "*.fxh") diff --git a/shaders/Managed/Space/SpecialFX/Lensflares/OccluderManagement.fx b/shaders/Managed/Space/SpecialFX/Lensflares/OccluderManagement.fx new file mode 100644 index 000000000..d0f90aecf --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Lensflares/OccluderManagement.fx @@ -0,0 +1,58 @@ +float OcclusionBufferOffset ; +RWBuffer FlareOcclusionBuffer ; + +groupshared float occlusions[4]; + +[numthreads( 4, 1, 1 )] +void CopyCountersCS( uint3 groupIdx : SV_GroupID, uint3 threadIdx : SV_GroupThreadID ) +{ + uint flareOffset = groupIdx.x * 13; + uint occluderOffset = flareOffset + 5 + threadIdx.x * 2; + uint persistedOffset = flareOffset + 1 + threadIdx.x; + + uint total = FlareOcclusionBuffer[occluderOffset]; + if( total > 0 ) + { + uint unoccluded = FlareOcclusionBuffer[occluderOffset + 1]; + + float occlusion = float( unoccluded ) / float( total ) / 100.0; + FlareOcclusionBuffer[persistedOffset] = asuint( occlusion ); + + FlareOcclusionBuffer[occluderOffset] = 0; + FlareOcclusionBuffer[occluderOffset + 1] = 0; + occlusions[threadIdx.x] = occlusion; + } + else + { + occlusions[threadIdx.x] = asfloat( FlareOcclusionBuffer[persistedOffset] ); + } + + GroupMemoryBarrierWithGroupSync(); + + if( threadIdx.x == 0 ) + { + FlareOcclusionBuffer[flareOffset] = asuint( occlusions[0] * occlusions[1] * occlusions[2] * occlusions[3] ); + } +} + +[numthreads( 13, 1, 1 )] +void ClearCS( uint3 threadIdx : SV_GroupThreadID ) +{ + FlareOcclusionBuffer[asuint( OcclusionBufferOffset ) + threadIdx.x] = threadIdx.x < 5 ? asuint( 1.0 ) : 0; +} + +technique CopyCounters +{ + pass P0 + { + ComputeShader = compile cs_5_0 CopyCountersCS(); + } +} + +technique Clear +{ + pass P0 + { + ComputeShader = compile cs_5_0 ClearCS(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/Clear.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Clear.fx new file mode 100644 index 000000000..18524e524 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Clear.fx @@ -0,0 +1,64 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/SpaceScene/GpuParticles.fxh" + +struct PerObjectVSData +{ + uint bufferSize; +} PerObjectVS; + +RWStructuredBuffer DeadBuffer; +RWBuffer ParticleCounters; +RWStructuredBuffer ParticleBuffer; + + +[numthreads( 1, 1, 1 )] +void ClearCounter() +{ + ParticleCounters[0] = 0; +} + +#define POPULATE_DEAD_DIM 16 + +[numthreads( POPULATE_DEAD_DIM, POPULATE_DEAD_DIM, 1 )] +void PopulateDead( uint groupIndex : SV_GroupIndex ) +{ + uint iterations = PerObjectVS.bufferSize / POPULATE_DEAD_DIM / POPULATE_DEAD_DIM; + for( uint i = 0; i < iterations; ++i ) + { + GpuParticleData particle = (GpuParticleData)0; + particle.age = -1; + ParticleBuffer[groupIndex + POPULATE_DEAD_DIM * POPULATE_DEAD_DIM * i] = particle; + int index; + InterlockedAdd( ParticleCounters[0], 1, index ); + DeadBuffer[index] = groupIndex + POPULATE_DEAD_DIM * POPULATE_DEAD_DIM * i; + } + + GroupMemoryBarrierWithGroupSync(); + + if( groupIndex == 0 ) + { + for( uint i = iterations * POPULATE_DEAD_DIM * POPULATE_DEAD_DIM; i < PerObjectVS.bufferSize; ++i ) + { + GpuParticleData particle = (GpuParticleData)0; + particle.age = -1; + ParticleBuffer[i] = particle; + int index; + InterlockedAdd( ParticleCounters[0], 1, index ); + DeadBuffer[index] = i; + } + } +} + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 ClearCounter(); + } + pass p1 + { + ComputeShader = compile cs_5_0 PopulateDead(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/Emit.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Emit.fx new file mode 100644 index 000000000..f778edf3b --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Emit.fx @@ -0,0 +1,139 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/System.fxh" +#include "../../../../../include/Carbon/Math.fxh" +#include "../../../../../include/Carbon/RandomXOrShift.fxh" +#include "../../../../../include/Carbon/SpaceScene/GpuParticles.fxh" + + +float3 GetRandomDirection( float3 direction, float angle, float innerAngle, float3 rands ) +{ + if( angle >= PI / 2 ) + { + angle = PI; + } + float3 dir = normalize( direction ); + + float phi = lerp( innerAngle, angle, rands.x ); + float theta = rands.y * 4 * PI; + + float4 q = QuaternionRotationYawPitchRoll( 0, phi, theta ); + float4 qp = float4( -q.xyz, q.w ); + float4 v = float4( 0, 0, 1, 0 ); + v = QuaternionMultiply( QuaternionMultiply( q, v ), qp ); + + float3 x = abs( dir.x ) > abs( dir.y ) ? float3( 0, 1, 0 ) : float3( 1, 0, 0 ); + float3 y = normalize( cross( dir, x ) ); + x = cross( dir, y ); + + return dir * v.z + x * v.x + y * v.y; +} + + +#define GROUP_X 16 +#define GROUP_Y 16 + +struct EmitterData +{ + float3 position; + uint count; + + float3 positionPrevious; + float radius; + + float3 direction; + float angle; + + float3 directionPrevious; + uint emitterSeed; // LO: emitter, HI: seed + + float3 velocity; + float minSpeed; + + float3 velocityPrevious; + float maxSpeed; + + float innerAngle; + float3 unused; +}; + +struct PerObjectVSData +{ + uint count; + uint3 padding; + // should be EmitterData data[(PLATFORM_MAX_CONSTANT_BUFFER_SIZE - 16)/sizeof(EmitterData)], but ShaderCompiler can't handle that +#if PLATFORM_MAX_CONSTANT_BUFFER_SIZE == 4096 + EmitterData data[36]; +#elif PLATFORM_MAX_CONSTANT_BUFFER_SIZE == 65536 + EmitterData data[585]; +#else +#error "Need to define proper length for the emitter CB" +#endif + +} PerObjectVS; + +RWStructuredBuffer ParticleBuffer; +RWBuffer ParticleCounters; +RWStructuredBuffer DeadBuffer; + +groupshared EmitterData g_emitter; + +[numthreads( GROUP_X, GROUP_Y, 1 )] +void Emit( uint3 grid : SV_GroupID, uint groupIndex : SV_GroupIndex, uint3 threadID : SV_GroupThreadID ) +{ + if( groupIndex == 0 ) + { + uint emitIndex = grid.x; + g_emitter = PerObjectVS.data[emitIndex]; + } + + GroupMemoryBarrierWithGroupSync(); + + uint rng_state = RandInit( groupIndex + ( g_emitter.emitterSeed >> 16 ) ); + + uint start = threadID.x + threadID.y * GROUP_X; + for( uint i = start; i < g_emitter.count; i += GROUP_X * GROUP_Y ) + { + int deadIndex; + InterlockedAdd( ParticleCounters[0], -1, deadIndex ); + --deadIndex; + + if( deadIndex >= 0 ) + { + float birthTime = RandFloat( rng_state ); + float3 velocityDirection = GetRandomDirection( + normalize( lerp( g_emitter.directionPrevious, g_emitter.direction, birthTime ) ), + g_emitter.angle, + g_emitter.innerAngle, + float3( RandFloat( rng_state ), RandFloat( rng_state ), RandFloat( rng_state ) ) ); + + uint index = DeadBuffer[deadIndex]; + GpuParticleData particle; + particle.position = lerp( g_emitter.positionPrevious, g_emitter.position, birthTime ); + particle.position += velocityDirection * g_emitter.radius; + particle.velocity = velocityDirection * + lerp( g_emitter.minSpeed, g_emitter.maxSpeed, RandFloat( rng_state ) ) + lerp( g_emitter.velocityPrevious, g_emitter.velocity, birthTime ); + particle.age = 0; + particle.emitterSeed = ( g_emitter.emitterSeed & 0xffff ) | ( Rand( rng_state ) << 16 ); + + + ParticleBuffer[index] = particle; + } + else + { + int ignored; + InterlockedAdd( ParticleCounters[0], 1, ignored ); + break; + } + } +} + + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 Emit(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/SetDrawParameters.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SetDrawParameters.fx new file mode 100644 index 000000000..6807ffdd1 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SetDrawParameters.fx @@ -0,0 +1,24 @@ +// Copyright © 2026 CCP ehf. + + +RWBuffer DrawParameters; +Buffer ParticleCounters; + +[numthreads( 1, 1, 1 )] +void SetParameters() +{ + uint count = ParticleCounters[1]; + DrawParameters[0] = count * 6; + DrawParameters[1] = 1; + DrawParameters[2] = 0; + DrawParameters[3] = 0; +} + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 SetParameters(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/SetSortArgs.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SetSortArgs.fx new file mode 100644 index 000000000..29a8e019d --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SetSortArgs.fx @@ -0,0 +1,24 @@ +// Copyright © 2026 CCP ehf. + + +RWBuffer SortParameters; +Buffer ParticleCounters; + +[numthreads( 1, 1, 1 )] +void SetParameters() +{ + uint count = ParticleCounters[1]; + SortParameters[0] = ( ( max( count, (uint)1 ) - 1 ) >> 9 ) + 1; + SortParameters[1] = 1; + SortParameters[2] = 1; + SortParameters[3] = count; +} + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 SetParameters(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/Sort.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Sort.fx new file mode 100644 index 000000000..0c1a9f509 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Sort.fx @@ -0,0 +1,93 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/SpaceScene/GpuParticles.fxh" + +// Code based on https://github.com/GPUOpen-LibrariesAndSDKs/GPUParticles11 + +#define SORT_SIZE 512 + +#define HALF_SIZE 256 // (SORT_SIZE/2) +#define ITERATIONS 1 // (HALF_SIZE > 1024 ? HALF_SIZE/1024 : 1) +#define NUM_THREADS 256 // (HALF_SIZE/ITERATIONS) + + +RWStructuredBuffer VisibleBuffer; +Buffer SortParameters; + + +groupshared GpuParticleVisibleData g_LDS[SORT_SIZE]; + + +[numthreads( NUM_THREADS, 1, 1 )] +void BitonicSortLDS( uint3 Gid : SV_GroupID, + uint3 DTid : SV_DispatchThreadID, + uint3 GTid : SV_GroupThreadID, + uint GI : SV_GroupIndex ) +{ + int GlobalBaseIndex = ( Gid.x * SORT_SIZE ) + GTid.x; + int LocalBaseIndex = GI; + + int g_NumElements = int( SortParameters[3] ); + if( g_NumElements == 0 ) + { + return; + } + + uint numElementsInThreadGroup = max( 0, min( SORT_SIZE, g_NumElements - int( Gid.x * SORT_SIZE ) ) ); + + // Load shared data + int i; + [unroll] + for( i = 0; i < 2 * ITERATIONS; ++i ) + { + if( GI + i * NUM_THREADS < numElementsInThreadGroup ) + g_LDS[LocalBaseIndex + i * NUM_THREADS] = VisibleBuffer[GlobalBaseIndex + i * NUM_THREADS]; + } + GroupMemoryBarrierWithGroupSync(); + + // Bitonic sort + for( uint nMergeSize = 2; nMergeSize <= SORT_SIZE; nMergeSize = nMergeSize * 2 ) + { + for( int nMergeSubSize = nMergeSize >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1 ) + { + [unroll] + for( i = 0; i < ITERATIONS; ++i ) + { + int tmp_index = GI + NUM_THREADS * i; + int index_low = tmp_index & ( nMergeSubSize - 1 ); + int index_high = 2 * ( tmp_index - index_low ); + int index = index_high + index_low; + + uint nSwapElem = uint( nMergeSubSize ) == nMergeSize >> 1 ? index_high + ( 2 * nMergeSubSize - 1 ) - index_low : index_high + nMergeSubSize + index_low; + if( nSwapElem < numElementsInThreadGroup ) + { + GpuParticleVisibleData a = g_LDS[index]; + GpuParticleVisibleData b = g_LDS[nSwapElem]; + + if( a.depth > b.depth ) + { + g_LDS[index] = b; + g_LDS[nSwapElem] = a; + } + } + GroupMemoryBarrierWithGroupSync(); + } + } + } + + // Store shared data + [unroll] + for( i = 0; i < 2 * ITERATIONS; ++i ) + { + if( GI + i * NUM_THREADS < numElementsInThreadGroup ) + VisibleBuffer[GlobalBaseIndex + i * NUM_THREADS] = g_LDS[LocalBaseIndex + i * NUM_THREADS]; + } +} + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 BitonicSortLDS(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/SortInner.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SortInner.fx new file mode 100644 index 000000000..bb8303aed --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SortInner.fx @@ -0,0 +1,89 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/SpaceScene/GpuParticles.fxh" + +// Code based on https://github.com/GPUOpen-LibrariesAndSDKs/GPUParticles11 + +#define SORT_SIZE 512 + +#define NUM_THREADS 256 // (SORT_SIZE/2) + +RWStructuredBuffer VisibleBuffer; +Buffer SortParameters; + +groupshared GpuParticleVisibleData g_LDS[SORT_SIZE]; + + +[numthreads( NUM_THREADS, 1, 1 )] +void BitonicInnerSort( + uint3 Gid : SV_GroupID, + uint3 DTid : SV_DispatchThreadID, + uint3 GTid : SV_GroupThreadID, + uint GI : SV_GroupIndex ) +{ + int4 tgp; + + tgp.x = Gid.x * 256; + tgp.y = 0; + tgp.z = SortParameters[3]; + tgp.w = min( 512, max( 0, tgp.z - int( Gid.x * 512 ) ) ); + + int GlobalBaseIndex = tgp.y + tgp.x * 2 + GTid.x; + int LocalBaseIndex = GI; + int i; + + // Load shared data + if( GlobalBaseIndex < tgp.z ) + { + [unroll] + for( i = 0; i < 2; ++i ) + { + if( (int)GI + i * NUM_THREADS < tgp.w ) + g_LDS[LocalBaseIndex + i * NUM_THREADS] = VisibleBuffer[GlobalBaseIndex + i * NUM_THREADS]; + } + } + GroupMemoryBarrierWithGroupSync(); + + // sort threadgroup shared memory + for( int nMergeSubSize = SORT_SIZE >> 1; nMergeSubSize > 0; nMergeSubSize = nMergeSubSize >> 1 ) + { + int tmp_index = GI; + int index_low = tmp_index & ( nMergeSubSize - 1 ); + int index_high = 2 * ( tmp_index - index_low ); + int index = index_high + index_low; + + uint nSwapElem = index_high + nMergeSubSize + index_low; + + if( (int)nSwapElem < tgp.w ) + { + GpuParticleVisibleData a = g_LDS[index]; + GpuParticleVisibleData b = g_LDS[nSwapElem]; + + if( a.depth > b.depth ) + { + g_LDS[index] = b; + g_LDS[nSwapElem] = a; + } + } + GroupMemoryBarrierWithGroupSync(); + } + + // Store shared data + if( GlobalBaseIndex < tgp.z ) + { + [unroll] + for( i = 0; i < 2; ++i ) + { + if( (int)GI + i * NUM_THREADS < tgp.w ) + VisibleBuffer[GlobalBaseIndex + i * NUM_THREADS] = g_LDS[LocalBaseIndex + i * NUM_THREADS]; + } + } +} + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 BitonicInnerSort(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/SortStep.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SortStep.fx new file mode 100644 index 000000000..d6aa03839 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/SortStep.fx @@ -0,0 +1,52 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/SpaceScene/GpuParticles.fxh" + +// Code based on https://github.com/GPUOpen-LibrariesAndSDKs/GPUParticles11 + +RWStructuredBuffer VisibleBuffer; +Buffer SortParameters; + +struct PerObjectVSData +{ + int4 JobParams; +} PerObjectVS; + +[numthreads( 256, 1, 1 )] +void BitonicSortStep( uint3 Gid : SV_GroupID, uint3 GTid : SV_GroupThreadID ) +{ + int4 tgp; + + tgp.x = Gid.x * 256; + tgp.y = 0; + tgp.z = SortParameters[3]; + tgp.w = min( 512, max( 0, tgp.z - (int)Gid.x * 512 ) ); + + uint localID = tgp.x + GTid.x; // calculate threadID within this sortable-array + + uint index_low = localID & ( PerObjectVS.JobParams.x - 1 ); + uint index_high = 2 * ( localID - index_low ); + + uint index = tgp.y + index_high + index_low; + uint nSwapElem = tgp.y + index_high + PerObjectVS.JobParams.y + PerObjectVS.JobParams.z * index_low; + + if( nSwapElem < uint( tgp.y + tgp.z ) ) + { + GpuParticleVisibleData a = VisibleBuffer[index]; + GpuParticleVisibleData b = VisibleBuffer[nSwapElem]; + + if( a.depth > b.depth ) + { + VisibleBuffer[index] = b; + VisibleBuffer[nSwapElem] = a; + } + } +} + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 BitonicSortStep(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Particles/Gpu/Update.fx b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Update.fx new file mode 100644 index 000000000..5ade62772 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Particles/Gpu/Update.fx @@ -0,0 +1,218 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/Math.fxh" +#include "../../../../../include/Carbon/SpaceScene/GpuParticles.fxh" +#include "../../../../../include/Carbon/SpaceScene/PerSceneData.fxh" + + +#define GROUP_X 16 +#define GROUP_Y 16 + + +Texture3D NoiseMap +< + bool SasUiVisible = true; +>; + +SamplerState NoiseMapSampler +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Wrap; + AddressV = Wrap; + AddressW = Wrap; +}; + +float3 GetTurbulence( float3 position, float frequency, float3 offset, float3 animation ) +{ + position += offset; + position *= frequency; + position += NoiseMap.SampleLevel( NoiseMapSampler, animation / 32, 0.0 ).xyz; + float3 result = 0; + float amplitude = 1; + + [unroll] + for( int i = 0; i < 3; ++i ) + { + result += ( NoiseMap.SampleLevel( NoiseMapSampler, position, 0.0 ).xyz - 0.5 ) * amplitude; + amplitude *= 0.5; + position = 2.0 * position.zyx; + } + return result; +} + +bool UpdateParticle( inout GpuParticleData particle, GpuParticleEmitterParams emitter, float dt, float seedf, float3 originShift, float3 turbulenceOffset, float3 turbulenceAnimation ) +{ + if( particle.age >= 0 ) + { + particle.age += dt; + + if( particle.age >= lerp( emitter.minLifeTime, emitter.maxLifeTime, seedf ) ) + { + particle.age = -1; + return false; + } + else + { + // forces + particle.velocity += float3( 0, emitter.gravity * dt, 0 ); + particle.velocity += GetTurbulence( particle.position, emitter.turbulenceFrequency, turbulenceOffset, turbulenceAnimation ) * emitter.turbulenceAmplitude * dt; + if( emitter.attractor.w != 0 ) + { + float3 dir = normalize( emitter.attractor.xyz - particle.position ); + particle.velocity += dir * emitter.attractor.w * dt; + } + if( emitter.drag < 0 ) + { + float speed = length( particle.velocity ); + if( speed > 0 ) + { + particle.velocity += particle.velocity * speed * emitter.drag * dt; + } + } + else + { + particle.velocity -= particle.velocity * emitter.drag * dt; + } + + particle.position += particle.velocity * dt + originShift; + if( any( isnan( particle.position ) ) ) + { + return false; + } + return true; + } + } + else + { + return false; + } +} + + +struct PerObjectVSData +{ + uint groupCountX; + uint groupCountY; + uint groupCountZ; + float deltaTime; + + float3 originShift; + uint bufferSize; + float4 turbulenceOffset; + float4 turbulenceAnimation; + + float4 frustumPlanes[6]; +} PerObjectVS; + +RWStructuredBuffer ParticleBuffer; +RWStructuredBuffer DeadBuffer; +RWBuffer ParticleCounters; +RWStructuredBuffer VisibleBuffer; +StructuredBuffer Emitters; + +EveSpaceSceneDataVS PerFrameVS : register( PERFRAME_VS_STARTREGISTER ); + +bool SphereInFrustum( float4 xyzr, float4 frustumPlane[6] ) +{ + [unroll] + for( uint i = 0; i != 6; ++i ) + { + if( dot( float4( xyzr.xyz, 1 ), -frustumPlane[i] ) > xyzr.w ) + { + return false; + } + } + return true; +} + +[numthreads( GROUP_X, GROUP_Y, 1 )] +void Update( uint3 grid : SV_GroupID, uint groupIndex : SV_GroupIndex ) +{ + uint offset = ( grid.x + grid.y * PerObjectVS.groupCountX + grid.z * PerObjectVS.groupCountX * PerObjectVS.groupCountY ) * GROUP_X * GROUP_Y + groupIndex; + float3 eye = ExtractEyePosFromViewMatrix( PerFrameVS.ViewInverseTransposeMat ); + float3 dir = normalize( ExtractEyeDirFromViewMatrix( PerFrameVS.ViewMat ) ); + float widthProjection = PerFrameVS.ProjectionMat[0][0] * PerFrameVS.TargetResolution.x; + + uint bufferSize = PerObjectVS.bufferSize; + + for( uint i = offset; i < bufferSize; i += GROUP_X * GROUP_Y * PerObjectVS.groupCountX * PerObjectVS.groupCountY * PerObjectVS.groupCountZ ) + { + GpuParticleData particle = ParticleBuffer[i]; + GpuParticleEmitterParams emitter = Emitters[particle.emitterSeed & 0xffff]; + uint seed = particle.emitterSeed >> 16; + float seedf = float( seed ) / float( 0xffff ); + + if( particle.age >= 0 ) + { + if( UpdateParticle( particle, emitter, PerObjectVS.deltaTime, seedf, PerObjectVS.originShift.xyz, PerObjectVS.turbulenceOffset.xyz, PerObjectVS.turbulenceAnimation.xyz ) ) + { + float maxSize = max( emitter.sizes.x, max( emitter.sizes.y, emitter.sizes.z ) ) * ( 1 + abs( emitter.sizeVariance ) ); + float4 frustumPlanes[6] = + { + PerObjectVS.frustumPlanes[0], + PerObjectVS.frustumPlanes[1], + PerObjectVS.frustumPlanes[2], + PerObjectVS.frustumPlanes[3], + PerObjectVS.frustumPlanes[4], + PerObjectVS.frustumPlanes[5] + }; + if( SphereInFrustum( float4( particle.position, maxSize ), frustumPlanes ) ) + { + float depth = -dot( dir, particle.position - eye ); + float screenSize = maxSize / max( 1.0, depth ) * widthProjection; + if( screenSize > 2 ) + { + int index; + InterlockedAdd( ParticleCounters[1], 1, index ); + + GpuParticleVisibleData ad; + ad.index = i; + ad.depth = -depth; + VisibleBuffer[index] = ad; + } + } + } + else + { + int index; + InterlockedAdd( ParticleCounters[0], 1, index ); + + DeadBuffer[index] = i; + } + ParticleBuffer[i] = particle; + } + else + { + int index; + InterlockedAdd( ParticleCounters[0], 1, index ); + DeadBuffer[index] = i; + } + } +} + + +[numthreads( 1, 1, 1 )] +void ClearCountersCS() +{ + ParticleCounters[0] = 0; + ParticleCounters[1] = 0; +} + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 Update(); + } +} + +technique ClearCounters +{ + pass p0 + { + ComputeShader = compile cs_5_0 ClearCountersCS(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Volumetric/BlurVolumetric.fx b/shaders/Managed/Space/SpecialFX/Volumetric/BlurVolumetric.fx new file mode 100644 index 000000000..11f947d9c --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Volumetric/BlurVolumetric.fx @@ -0,0 +1,79 @@ +#pragma permutation(SOURCE_TYPE, values=(SOURCE_TYPE_ARRAY, SOURCE_TYPE_TEXTURE)) + +#include "../../../../include/Carbon/SpaceScene/PerSceneData.fxh" +#include "../../../../include/Carbon/SpaceScene/Volumetrics.fxh" + +EveSpaceSceneDataPS PerFramePS : register( PERFRAME_PS_STARTREGISTER ); + + +float4 vs( float4 pos : POSITION ) : SV_Position +{ + return pos; +}; + + +SamplerState EveSceneFogVolumeMapPSampler +{ + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; +}; + +float4 DepthSizes; +Texture2D VolumetricDepthMap; +Texture2D SourceMap; + +float GetLinearDepth( float d ) +{ + return PerFramePS.ProjectionData.x / ( d + PerFramePS.ProjectionData.y ); +} + +float4 ps( float4 pos : SV_Position ) : SV_Target +{ + float2 uv = pos.xy / DepthSizes.xy; + float2 invTexelSize = DepthSizes.zw; + + float centerDepth = GetLinearDepth( VolumetricDepthMap.SampleLevel( EveSceneFogVolumeMapPSampler, uv, 0 ) ); + + half4 color = 0; + half weight = 0; + +#define HALFTAP 2 + const half weights[HALFTAP * 2 + 1] = { 18, 46, 90, 46, 18 }; + + [unroll] + for( float i = -HALFTAP; i <= HALFTAP; ++i ) + { + float depth = GetLinearDepth( VolumetricDepthMap.SampleLevel( EveSceneFogVolumeMapPSampler, uv + i * invTexelSize, 0 ) ); + + half w = weights[i + HALFTAP]; + float denom = 1 + min( depth, centerDepth ); + w *= half( 1 - saturate( abs( depth - centerDepth ) / denom ) ); +#if SOURCE_TYPE == SOURCE_TYPE_TEXTURE + color += w * SourceMap.SampleLevel( EveSceneFogVolumeMapPSampler, uv + i * invTexelSize, 0 ); +#else + color += w * half4( EveSceneFogVolumeMap.SampleLevel( EveSceneFogVolumeMapPSampler, float3( uv + i * invTexelSize, 3 ), 0 ) ); +#endif + weight += w; + } + color /= weight; + + color.a = saturate( color.a ); //half precision math means we can get over 1.0 alpha, so saturate it. + + return float4( color ); +} + +technique Main +{ + pass P0 + { + AlphaBlendEnable = false; + CullMode = None; + ZEnable = False; + VertexShader = compile vs_5_0 vs(); + PixelShader = compile ps_5_0 ps(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Volumetric/DownsampleDepth.fx b/shaders/Managed/Space/SpecialFX/Volumetric/DownsampleDepth.fx new file mode 100644 index 000000000..757aa103f --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Volumetric/DownsampleDepth.fx @@ -0,0 +1,31 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../include/Carbon/System.fxh" +#include "../../../../include/Carbon/SpaceScene/DepthMap.fxh" + + +float4 vs( float4 pos : POSITION ) : SV_Position +{ + return pos; +} + + +float4 DepthSizes; + +float4 ps( float4 pos : SV_Position ) : SV_Target +{ + return DepthMap.Load( float3( pos.xy / DepthSizes.xy * DepthSizes.zw, 0 ) ); +} + + +technique Main +{ + pass P0 + { + AlphaBlendEnable = false; + CullMode = None; + ZEnable = False; + VertexShader = compile vs_5_0 vs(); + PixelShader = compile ps_5_0 ps(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Volumetric/Fog/ApplyFroxels.fx b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/ApplyFroxels.fx new file mode 100644 index 000000000..6602e0e5c --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/ApplyFroxels.fx @@ -0,0 +1,53 @@ +// Copyright © 2026 CCP ehf. + +#pragma permutation(ENVIRONMENT_LIGHTING, values = ( ENVIRONMENT_LIGHTING_DISABLED, ENVIRONMENT_LIGHTING_ENABLED ) ) + +#include "../../../../../include/Carbon/System.fxh" +#include "../../../../../include/Carbon/SpaceScene/PerSceneData.fxh" +#include "../../../../../include/Carbon/SpaceScene/Volumetrics.fxh" +#include "../../../../../include/Carbon/SpaceScene/DepthMap.fxh" + +EveSpaceSceneDataPS PerFramePS : register( PERFRAME_PS_STARTREGISTER ); + + +SamplerState DepthMapSamplerClamp +{ + MinFilter = Point; + MagFilter = Point; + MipFilter = None; + AddressU = Clamp; + AddressV = Clamp; +}; + + +float4 vs( float4 pos : POSITION ) : SV_Position +{ + return pos; +}; + +float4 ps( float4 pos : SV_Position ) : SV_Target0 +{ + float2 texCoords = pos.xy / PerFramePS.RenderTargetData.xy; + float sceneDepth = DepthMap.SampleLevel( DepthMapSamplerClamp, texCoords, 0.0 ).r; + + float4 result; +#if ENVIRONMENT_LIGHTING == ENVIRONMENT_LIGHTING_ENABLED + result = SampleEveSceneFroxelFog( texCoords, sceneDepth, PerFramePS, true ); +#else + result = SampleEveSceneFroxelFog( texCoords, sceneDepth, PerFramePS, false ); +#endif + return result; +} + +technique Main +{ + pass P0 + { + SrcBlend = One; + DestBlend = SrcAlpha; + CullMode = None; + ZEnable = False; + VertexShader = compile vs_5_0 vs(); + PixelShader = compile ps_5_0 ps(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Volumetric/Fog/FilterFroxels.fx b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/FilterFroxels.fx new file mode 100644 index 000000000..79d1ae614 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/FilterFroxels.fx @@ -0,0 +1,118 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/SpaceScene/FroxelFog.fxh" + +cbuffer FroxelPerObjectCB : register( b3 ) +{ + FroxelPerObjectData FroxelPerObject; +} + +SamplerState NearestClampSampler = sampler_state +{ + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; +}; +SamplerState LinearClampSampler = sampler_state +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; +}; + +Texture3D InputTexture; +Texture3D PreviousTexture; + + +RWTexture3D OutputTexture; + + +void accumulate( inout float4 m1, inout float4 m2, float weight, float4 froxel ) +{ + m1 += froxel * weight; + m2 += ( froxel * froxel ) * weight; +} + +[numthreads( 4, 4, 4 )] +void Test( uint3 coords : SV_DispatchThreadID ) +{ + if( coords.x >= FroxelPerObject.Resolution.x || coords.y >= FroxelPerObject.Resolution.y || coords.z >= FroxelPerObject.Resolution.z ) + { + return; + } + + float3 texCoords = ( float3( coords ) + 0.5 ) / float3( FroxelPerObject.Resolution ); + + float4 current = InputTexture.SampleLevel( NearestClampSampler, texCoords, 0 ); + + float4 m1 = 0.0; + float4 m2 = 0.0; + + float weight = 1.0 / 7.0; + + accumulate( m1, m2, weight, current ); + accumulate( m1, m2, weight, InputTexture.SampleLevel( NearestClampSampler, texCoords, 0, int3( -1, 0, 0 ) ) ); + accumulate( m1, m2, weight, InputTexture.SampleLevel( NearestClampSampler, texCoords, 0, int3( +1, 0, 0 ) ) ); + accumulate( m1, m2, weight, InputTexture.SampleLevel( NearestClampSampler, texCoords, 0, int3( 0, -1, 0 ) ) ); + accumulate( m1, m2, weight, InputTexture.SampleLevel( NearestClampSampler, texCoords, 0, int3( 0, +1, 0 ) ) ); + accumulate( m1, m2, weight, InputTexture.SampleLevel( NearestClampSampler, texCoords, 0, int3( 0, 0, -1 ) ) ); + accumulate( m1, m2, weight, InputTexture.SampleLevel( NearestClampSampler, texCoords, 0, int3( 0, 0, +1 ) ) ); + + float4 mean = m1; + float4 std = sqrt( max( m2 - mean * mean, 0.0 ) ); + + float4 result = current; + + //if(true) //Could attempt an early out here to improve performance when std is small (i.e. tight clamping --> we basically ignore the previous frame anyway) + { + +#if 1 + float layerDistance = FroxelGetLayerDistance( texCoords.z, FroxelPerObject ); + float3 viewDirection = float3( texCoords.xy * FroxelPerObject.UnprojectParams.xy + FroxelPerObject.UnprojectParams.zw, -1.0 ); + float3 viewPosition = viewDirection * ( layerDistance * rsqrt( dot( viewDirection, viewDirection ) ) ); + + float3 previousViewPosition = mul( float4( viewPosition, 1.0 ), FroxelPerObject.ReprojectionMatrix ).xyz; //no need to do perspective divide + float2 previousCoords = ( previousViewPosition.xy / -previousViewPosition.z ) * FroxelPerObject.PreviousProjectParams.xy + FroxelPerObject.PreviousProjectParams.zw; + float previousNormalizedZ = FroxelGetNormalizedZ( length( previousViewPosition ), FroxelPerObject ); + + float3 previousTexCoords = float3( previousCoords, previousNormalizedZ ); + float4 previous = PreviousTexture.SampleLevel( LinearClampSampler, previousTexCoords, 0 ); +#else + float4 previous = PreviousTexture.SampleLevel(NearestClampSampler, texCoords, 0); +#endif + + + float gamma = 2.0; + previous = clamp( previous, mean - std * gamma, mean + std * gamma ); + + result = lerp( previous, current, 0.05 ); + + //result = float4(abs(texCoords - previousTexCoords) * 100.0, 1.0); + + //result = float4(abs(previousTexCoords - texCoords.xy), 0, current.a); + } + + if( any( isnan( result ) ) || any( isinf( result ) ) ) + { + result = float4( 1, 0, 0, 1 ); + } + + OutputTexture[coords] = result; + +} + + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 Test(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Volumetric/Fog/RaymarchFroxels.fx b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/RaymarchFroxels.fx new file mode 100644 index 000000000..e89c0b9c5 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/RaymarchFroxels.fx @@ -0,0 +1,71 @@ +// Copyright © 2026 CCP ehf. + +#pragma permutation(INPUT_SOURCE, values = ( INPUT_SOURCE_INPUT_TEXTURE, INPUT_SOURCE_OUTPUT_TEXTURE ) ) + +#include "../../../../../include/Carbon/SpaceScene/FroxelFog.fxh" + +cbuffer FroxelPerObjectCB : register( b3 ) +{ + FroxelPerObjectData FroxelPerObject; +} + +Texture3D InputTexture; + +RWTexture3D OutputTexture; + +[numthreads( 8, 4, 1 )] +void Test( uint3 coords : SV_DispatchThreadID ) +{ + if( coords.x >= FroxelPerObject.Resolution.x || coords.y >= FroxelPerObject.Resolution.y || coords.z >= FroxelPerObject.Resolution.z ) + { + return; + } + + float inverseLayers = 1.0 / FroxelPerObject.Resolution.z; + + float4 total = 0.0; + + float previousDistance = 0.0; + + for( uint i = 0; i < FroxelPerObject.Resolution.z; i++ ) + { + + float normalizedZ = ( float( i ) + 1.0 ) * inverseLayers; + + float layerDistance = min( FroxelPerObject.Far, FroxelGetLayerDistance( normalizedZ, FroxelPerObject ) ); + float stepSize = layerDistance - previousDistance; + previousDistance = layerDistance; + +#if INPUT_SOURCE == INPUT_SOURCE_INPUT_TEXTURE + float3 froxel = InputTexture[uint3( coords.xy, i )]; +#else + float3 froxel = OutputTexture[uint3( coords.xy, i )]; +#endif + float density = stepSize * FroxelPerObject.BaseDensity; + + //Naive integration explodes when density is large, greatly overestimating the scattering + //This is especially bad at high fog density with the last layer, as that one goes to the far plane + //float3 color = froxel * Scattering * density; + + //Physically correct integration makes sure we don't emit more light than is coming in. + float3 color = froxel * FroxelPerObject.Scattering * ( 1.0 - exp( -density ) ); + + float visibility = exp( -total.a ); + + total += float4( color * visibility, density ); + + OutputTexture[uint3( coords.xy, i )] = total.rgb; + } + + +} + + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 Test(); + } +} diff --git a/shaders/Managed/Space/SpecialFX/Volumetric/Fog/UpdateMieEnvironmentMap.fx b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/UpdateMieEnvironmentMap.fx new file mode 100644 index 000000000..d0be66964 --- /dev/null +++ b/shaders/Managed/Space/SpecialFX/Volumetric/Fog/UpdateMieEnvironmentMap.fx @@ -0,0 +1,148 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../../include/Carbon/System.fxh" + + +uint Resolution; +float2 Jitter; + +float EnvironmentG; + +float BlendWeight; + +float Random; + +TextureCube EveSpaceSceneStaticEnvMap; + +SamplerState LinearClampSampler = sampler_state +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; +}; + +RWTexture2DArray PrecomputedMieEnvironmentMap; + + +float dither( uint2 coords, float scale ) +{ + float g = 1.32471795724474602596; + return frac( Random + dot( float2( coords ), 1.0 / float2( g, g * g ) ) ) * ( 2.0 * scale ) - scale; +} + + +[numthreads( 8, 8, 1 )] +void Test( uint3 coords : SV_DispatchThreadID ) +{ + if( coords.x >= Resolution || coords.y >= Resolution ) + { + return; + } + + float u = ( coords.x + 0.5 ) / Resolution * +2.0 - 1.0; + float v = ( coords.y + 0.5 ) / Resolution * -2.0 + 1.0; + uint face = coords.z; + + + float3 direction; + + if( face == 0 ) + direction = float3( 1, v, -u ); + else if( face == 1 ) + direction = float3( -1, v, u ); + else if( face == 2 ) + direction = float3( u, 1, -v ); + else if( face == 3 ) + direction = float3( u, -1, v ); + else if( face == 4 ) + direction = float3( u, v, 1 ); + else + direction = float3( -u, v, -1 ); + + direction = normalize( direction ); + + float3 normal = -direction; + + float3 up = float3( 1, 0, 0 ); + if( abs( dot( normal, up ) ) > 0.9 ) + { + up = float3( 0, 1, 0 ); + } + float3 tangent = normalize( cross( normal, up ) ); + float3 bitangent = cross( normal, tangent ); + + float4x4 transform = float4x4( + float4( tangent, 0.0 ), + float4( bitangent, 0.0 ), + float4( normal, 0.0 ), + float4( 0.0, 0.0, 0.0, 1.0 ) + ); + + const int ENVIRONMENT_SAMPLES = 32; + + const float PI = 3.14159265359; + + float g = EnvironmentG; + float g2 = g * g; + + const float GRADIENT_STRENGTH = 32.0; + float gradient = GRADIENT_STRENGTH / -g - GRADIENT_STRENGTH; + + float mipmap = log2( gradient ); + + + float weight = rcp( float( ENVIRONMENT_SAMPLES ) ); + + float3 environmentLighting = 0.0; + for( int i = 0; i < ENVIRONMENT_SAMPLES; i++ ) + { + + float2 randomOffset = float2( ( i + 0.5 ) / ENVIRONMENT_SAMPLES, i * 0.61803398875 ); + + randomOffset = frac( randomOffset + Jitter ); + + + float a = ( 1.0 / ( 2.0 * g ) ) * ( 1.0 + g2 - pow( ( 1.0 - g2 ) / ( 1.0 - g + 2.0 * g * randomOffset.x ), 2.0 ) ); + float b = randomOffset.y * ( PI * 2.0 ); + + float2 vertical = float2( a, sqrt( 1.0 - a * a ) ); + + float2 horizontal = float2( cos( b ), sin( b ) ); + float3 direction = mul( float4( horizontal.x * vertical.y, horizontal.y * vertical.y, vertical.x, 0.0 ), transform ).xyz; + float3 environmentColor = EveSpaceSceneStaticEnvMap.SampleLevel( LinearClampSampler, direction, 0.0 ).rgb; + + environmentLighting += environmentColor * weight; + } + + float3 previous = PrecomputedMieEnvironmentMap[coords]; + + float3 result = lerp( previous, environmentLighting, BlendWeight ); + + + if( any( isnan( result ) ) || any( isinf( result ) ) ) + { + result = float3( 1, 0, 0 ); + } + + //None of this helps with the precision issues :( + + //result *= 1.0 + dither(coords.xy, 1.0 / 512.0); + + //float3 test = asfloat(int3(asint(result)) + int(dither(coords.xy, 512.0 * 160))); + //result = test; + + PrecomputedMieEnvironmentMap[coords] = result; +} + + + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 Test(); + } +} diff --git a/shaders/Managed/Space/System/ComputeLightLists.fx b/shaders/Managed/Space/System/ComputeLightLists.fx new file mode 100644 index 000000000..62614cb23 --- /dev/null +++ b/shaders/Managed/Space/System/ComputeLightLists.fx @@ -0,0 +1,355 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../include/Carbon/System.fxh" + +#define TILE_WIDTH 16 +#define TILE_HEIGHT 16 + +Texture2D DepthMap; + +struct PerLightData +{ + float4 position; // xyz - world position, w - radius + float4 color; // xyz - color, w - bits 0..15 - innerRadius (float 16), bits 16..31 - flags + float4 direction; // see next few lines: + // x bits 0..15 - direction.x (float 16), x bits 16..31 - direction.y (float 16) + // y bits 0..15 - direction.z (float 16), y bits 16..31 - projectionPlaneDistance (float 16) + // z bits 0..15 - outerAngle (float 16), z bits 16..31 - innerAngle (float 16) + // w bits 0..1 - padding, w bits 2..11 - shadowMapScale, w bits 12..21 - shadowMapOffsetX, w bits 22..31 - shadowMapOffsetY +}; +static const uint LIGHT_FLAG_AFFECTS_PARTICLES = 1 << 17; + + + +StructuredBuffer LightBuffer; +RWStructuredBuffer LightIndices : register( u1 ) < + // These annotations are used by Tr2LightManager + int TileWidth = TILE_WIDTH; + int TileHeight = TILE_HEIGHT; + >; +RWBuffer LightIndexCount; + +struct +{ + float4x4 ViewInverse; + float4x4 ProjInverse; + float4 CameraPosition; + uint4 FrameBufferSizes; // xy - frame buffer size; zw - tiles per width and height + uint LightCount; // number of lights in LightBuffer + uint IndexBufferSize; // number of elements in LightIndices +} PerFramePS : register( c200 ); + +float3 ComputeWorldPos( float2 pixel, float depth, float2 wh ) +{ + float2 uv = pixel / wh; + float2 clipPos = uv * 2 - 1; + clipPos.y = -clipPos.y; + + float4 pos = float4( clipPos, depth, 1 ); + pos = mul( pos, PerFramePS.ProjInverse ); + pos /= pos.w; + pos = mul( pos, PerFramePS.ViewInverse ); + return pos.xyz; +} + +float GetViewDepth( float depth ) +{ + float4 p = mul( float4( 0, 0, depth, 1 ), PerFramePS.ProjInverse ); + return p.z / p.w; +} + +float ConeSphereIntersect( float3 conePosition, float3 coneDirection, float sinAngle, float3 center, float radius ) +{ + float3 d = center - conePosition; + float c = length( d ); + d = normalize( d ); + float ca = dot( d, coneDirection ); + float a = c * ca; + float b = sqrt( c * c - a * a ); + + return saturate( radius + sinAngle * c - b ); +} + + +struct Frustum +{ + float3 coneDirection; + float coneAngleSine; + float3 cameraDirection; + // near/far planes constained to depth buffer contents (for opaque lists) + float nearDepth; + float farDepth; + // near/far planes from the camera (for transparent lists) + float nearPlane; + float farPlane; +}; + +void CreateFrustum( out Frustum frustum, float2 tile, float2 depths, float transparentDepth ) +{ + float2 screenSize = (float2)PerFramePS.FrameBufferSizes.xy; + float minD = depths.x; + float maxD = depths.y; + + // near, far + float minDV = GetViewDepth( minD ); + float maxDV = GetViewDepth( maxD ); + float3 viewDir = mul( float4( 0, 0, -1, 0 ), PerFramePS.ViewInverse ).xyz; + + frustum.cameraDirection = viewDir; + frustum.nearDepth = dot( PerFramePS.ViewInverse[3].xyz, -viewDir ) + minDV; + frustum.farDepth = dot( PerFramePS.ViewInverse[3].xyz, -viewDir ) + maxDV; + frustum.nearPlane = dot( PerFramePS.ViewInverse[3].xyz, -viewDir ) + GetViewDepth( 0 ); + frustum.farPlane = dot( PerFramePS.ViewInverse[3].xyz, -viewDir ) + GetViewDepth( transparentDepth ); + + float left = tile.x * TILE_WIDTH; + float top = tile.y * TILE_HEIGHT; + float right = min( screenSize.x, left + TILE_WIDTH ); + float bottom = min( screenSize.y, top + TILE_HEIGHT ); + + float3 ful = ComputeWorldPos( float2( left, top ), maxD, screenSize ); + float3 fbr = ComputeWorldPos( float2( right, bottom ), maxD, screenSize ); + + float3 c = PerFramePS.CameraPosition.xyz; + + float3 r0 = normalize( ful - c ); + float3 r1 = normalize( fbr - c ); + + float3 dir = normalize( r0 + r1 ); + frustum.coneDirection.xyz = dir; + frustum.coneAngleSine = sqrt( 1 - dot( dir, r0 ) * dot( dir, r0 ) ); +} + +bool SphereInFrustum( float3 center, float radius, Frustum frustum, out bool transparent ) +{ + // check depth: near/far planes + float inside = 1; + + inside *= ConeSphereIntersect( PerFramePS.CameraPosition.xyz, frustum.coneDirection, frustum.coneAngleSine, center, radius ); + + float transparentInside = inside; + + inside *= saturate( radius - dot( float4( center, 1 ), float4( -frustum.cameraDirection, -frustum.nearDepth ) ) ); + inside *= saturate( radius - dot( float4( center, 1 ), float4( frustum.cameraDirection, frustum.farDepth ) ) ); + + transparentInside *= saturate( radius - dot( float4( center, 1 ), float4( -frustum.cameraDirection, -frustum.nearPlane ) ) ); + transparentInside *= saturate( radius - dot( float4( center, 1 ), float4( frustum.cameraDirection, frustum.farPlane ) ) ); + + transparent = inside <= 0; + + return inside > 0 || transparentInside > 0; +} + +bool ConeInFrustum( float3 center, float radius, float3 spotlightDir, float spotlightAngleCos, Frustum frustum, out bool transparent ) +{ + // check first for a sphere that the cone is a subsection of. + float inside = ConeSphereIntersect( PerFramePS.CameraPosition.xyz, frustum.coneDirection, frustum.coneAngleSine, center, radius ); + + // this is a very gross approxomation of the spotlight occlusion... + float cosAngle = spotlightAngleCos; + float hypotenuse = radius / cosAngle; + float sphereRadius = 0.5f * hypotenuse / cosAngle; + float3 sphereCenter = clamp( 0.0, radius, sphereRadius ) * normalize( -spotlightDir ); + + inside *= ConeSphereIntersect( PerFramePS.CameraPosition.xyz, frustum.coneDirection, frustum.coneAngleSine, center + sphereCenter, sphereRadius ); + + float transparentInside = inside; + + inside *= saturate( radius - dot( float4( center, 1 ), float4( -frustum.cameraDirection, -frustum.nearDepth ) ) ); + inside *= saturate( radius - dot( float4( center, 1 ), float4( frustum.cameraDirection, frustum.farDepth ) ) ); + + transparentInside *= saturate( radius - dot( float4( center, 1 ), float4( -frustum.cameraDirection, -frustum.nearPlane ) ) ); + transparentInside *= saturate( radius - dot( float4( center, 1 ), float4( frustum.cameraDirection, frustum.farPlane ) ) ); + + transparent = inside <= 0; + + return inside > 0 || transparentInside > 0; +} + + +groupshared uint g_tileHeadIndex; +groupshared uint g_tileTransparentHeadIndex; +groupshared uint g_tileParticleHeadIndex; +groupshared uint g_tileTransparentTailIndex; + + +void AddLight( uint light, uint heapOffset, uint indexBufferSize, bool transparent ) +{ + int iHead; + InterlockedAdd( LightIndexCount[0], 1, iHead ); + uint head = iHead; + + head = head * 2 + heapOffset; + if( head + 1 < indexBufferSize ) + { + if( transparent ) + { + uint next; + InterlockedExchange( g_tileTransparentHeadIndex, head, next ); + + LightIndices[head] = light; + LightIndices[head + 1] = next; + + if( next == 0 ) + { + g_tileTransparentTailIndex = head + 1; + } + } + else + { + uint next; + InterlockedExchange( g_tileHeadIndex, head, next ); + + LightIndices[head] = light; + LightIndices[head + 1] = next; + } + } +} + +groupshared uint g_DepthMinShared; +groupshared uint g_DepthMaxShared; +groupshared uint g_DepthMaxTransparentShared; +groupshared Frustum g_frustum; + + +[numthreads( TILE_WIDTH, TILE_HEIGHT, 1 )] +void ComputeLightLists( uint3 nGid : SV_GroupID, uint groupIndex : SV_GroupIndex, uint3 offset : SV_DispatchThreadID ) +{ + if( groupIndex == 0 ) + { + g_DepthMinShared = asuint( 1.0 ); + g_DepthMaxShared = asuint( 0.0 ); + g_DepthMaxTransparentShared = asuint( 0.0 ); + } + + GroupMemoryBarrierWithGroupSync(); + + float minD = 1; + float minDD = 1; + float maxD = 0; + float d = DepthMap[offset.xy]; + minDD = min( d, minDD ); + if( d != 0 ) + { + minD = maxD = d; + } + if( maxD != 0 ) + { + // have to add epsilon values to depth range because of precision issues when depth buffer range is too large + minD = max( 0.0, minD - 1.19e-07 ); + minDD = max( 0.0, minDD - 1.19e-07 ); + maxD = min( 1.0, maxD + 1.19e-07 ); + } + InterlockedMin( g_DepthMinShared, asuint( 1 - maxD ) ); + InterlockedMax( g_DepthMaxShared, asuint( 1 - minD ) ); + InterlockedMax( g_DepthMaxTransparentShared, asuint( 1 - minDD ) ); + + GroupMemoryBarrierWithGroupSync(); + + if( groupIndex == 0 ) + { + float2 tileDepth = float2( asfloat( g_DepthMinShared ), asfloat( g_DepthMaxShared ) ); + float tileTransparentDepth = asfloat( g_DepthMaxTransparentShared ); + + Frustum frustum; + CreateFrustum( frustum, (float2)nGid.xy, tileDepth, tileTransparentDepth ); + g_frustum = frustum; + g_tileHeadIndex = 0; + g_tileTransparentHeadIndex = 0; + g_tileTransparentTailIndex = 0; + g_tileParticleHeadIndex = 0; + } + + GroupMemoryBarrierWithGroupSync(); + + uint numLights = PerFramePS.LightCount; + uint heapOffset = PerFramePS.FrameBufferSizes.z * PerFramePS.FrameBufferSizes.w * 3; + uint indexBufferSize = PerFramePS.IndexBufferSize; + + for( uint light = groupIndex; light < numLights; light += TILE_WIDTH * TILE_HEIGHT ) + { + PerLightData lightData = LightBuffer[light]; + float4 lightPos = lightData.position; + float3 lightDir = float3( + f16tof32( asuint( lightData.direction.x ) & 0xffff ), + f16tof32( asuint( lightData.direction.x ) >> 16 ), + f16tof32( asuint( lightData.direction.y ) & 0xffff ) + ); + float spotlightAngleCos = f16tof32( asuint( lightData.direction.z ) & 0xffff ); + + bool inside = false; + bool transparent = false; + + if( spotlightAngleCos <= 0 ) + { + if( SphereInFrustum( lightPos.xyz, lightPos.w, g_frustum, transparent ) ) + { + inside = true; + } + } + else + { + if( ConeInFrustum( lightPos.xyz, lightPos.w, lightDir.xyz, spotlightAngleCos, g_frustum, transparent ) ) + { + inside = true; + } + } + if( inside ) + { + AddLight( light, heapOffset, indexBufferSize, transparent ); + uint lightFlags = asuint( lightData.color.w ); + if( lightFlags & LIGHT_FLAG_AFFECTS_PARTICLES ) + { + int iHead; + InterlockedAdd( LightIndexCount[0], 1, iHead ); + uint head = iHead; + + head = head * 2 + heapOffset; + if( head < indexBufferSize ) + { + uint next; + InterlockedExchange( g_tileParticleHeadIndex, head, next ); + + LightIndices[head] = light; + LightIndices[head + 1] = next; + } + } + } + } + + GroupMemoryBarrierWithGroupSync(); + + if( groupIndex == 0 ) + { + uint linearTile = ( nGid.x + nGid.y * PerFramePS.FrameBufferSizes.z ) * 3; + LightIndices[linearTile] = g_tileHeadIndex; + LightIndices[linearTile + 1] = g_tileTransparentHeadIndex ? g_tileTransparentHeadIndex : g_tileHeadIndex; + LightIndices[linearTile + 2] = g_tileParticleHeadIndex; + if( g_tileTransparentTailIndex != 0 ) + { + LightIndices[g_tileTransparentTailIndex] = g_tileHeadIndex; + } + } +} + +[numthreads( 1, 1, 1 )] +void ClearLightListCounter() +{ + LightIndexCount[0] = 0; +} + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 ComputeLightLists(); + } +} + + +technique Clear +{ + pass p0 + { + ComputeShader = compile cs_5_0 ClearLightListCounter(); + } +} diff --git a/shaders/Managed/Space/System/Denoise1D.fx b/shaders/Managed/Space/System/Denoise1D.fx new file mode 100644 index 000000000..684d12db2 --- /dev/null +++ b/shaders/Managed/Space/System/Denoise1D.fx @@ -0,0 +1,129 @@ +// Copyright © 2026 CCP ehf. + +#pragma permutation(NORMALS_INPUT, values=(NORMALS_INPUT_NONE, NORMALS_INPUT_NORMALS)) + +#include "../../../include/Carbon/System.fxh" + +// The code is based on https://sourceforge.net/p/g3d/code/HEAD/tree/G3D10/data-files/shader/BilateralFilter/BilateralFilter_apply.pix + +Texture2D Source; +Texture2D NoiseEstimate; +Texture2D DepthBuffer; +Texture2D NormalBuffer; +#define LOAD_TEX( tex, coord ) ( tex[coord] ) + +float DepthWeight; +float NormalWeight; +float PlaneWeight; +float PassThrough; +int Radius; + +float2 Axis; + +float2 ClipInfo; // x - projection[3][2], y - projection[2][2] +float4x4 ProjectionInv; + +float GetLinearDepth( float d ) +{ + return ClipInfo.x / ( d + ClipInfo.y ); +} + +float3 GetViewSpacePos( float2 xy, float depth ) +{ + float2 dd = ( xy * 2.f - 1.f ); + dd.y = -dd.y; + float4 projPos = float4( dd, depth, 1 ); + float4 viewPos = mul( projPos, ProjectionInv ); + viewPos /= viewPos.w; + return viewPos.xyz; +} + +struct TapKey +{ + float csZ; + float3 csPosition; + float3 normal; +}; + +TapKey GetTapKey( int2 C, float2 fragCoord ) +{ + TapKey key = (TapKey)0; + + if( ( DepthWeight != 0.0 ) || ( PlaneWeight != 0.0 ) ) + { + key.csZ = GetLinearDepth( LOAD_TEX( DepthBuffer, C ) ); + } + + if( PlaneWeight != 0.0 ) + { + key.csPosition = GetViewSpacePos( fragCoord.xy, key.csZ ); + } + + return key; +} + +float CalculateBilateralWeight( TapKey center, TapKey tap ) +{ + float depthWeight = 1.0; + float normalWeight = 1.0; + float planeWeight = 1.0; + + if( DepthWeight != 0.0 ) + { + depthWeight = max( 0.0, 1.0 - abs( tap.csZ - center.csZ ) / max( abs( tap.csZ ), 0.01 ) * DepthWeight ); + } + + return depthWeight * normalWeight * planeWeight; +} + +float4 VS( float4 pos : POSITION ) : SV_Position +{ + return pos; +} + +float4 PS( float4 pixelPos: SV_Position ) : SV_Target +{ + int2 ssC = int2( pixelPos.xy ); + + // 3* is because the estimator produces larger values than we want to use, for visualization purposes + float gaussianRadius = saturate( NoiseEstimate[ssC] * 10.5 ) * Radius; + + float result = 0; + + // Detect sky and noiseless pixels and reject them + if( ( PassThrough != 1 ) && ( LOAD_TEX( DepthBuffer, ssC ) > 0 ) && ( gaussianRadius > 0.5 ) ) + { + float sum0 = 0; + float totalWeight = 0.0; + TapKey key = GetTapKey( ssC, pixelPos.xy ); + + for( int r = -Radius; r <= Radius; ++r ) + { + int2 tapOffset = int2( Axis * r ); + int2 tapLoc = ssC + tapOffset; + + float gaussian = exp( -pow( float( r ) / gaussianRadius, 2 ) ); + float weight = gaussian * ( ( r == 0 ) ? 1.0 : CalculateBilateralWeight( key, GetTapKey( tapLoc, pixelPos.xy + float2(tapOffset) ) ) ); + sum0 += Source[tapLoc].r * weight; + totalWeight += weight; + } + + // totalWeight >= gaussian[0], so no division by zero here + result = sum0 / totalWeight; + } + else + { + // No denoising needed + result = Source[ssC]; + } + return result; +} + +technique Main +{ + pass P0 + { + VertexShader = compile vs_5_0 VS(); + PixelShader = compile ps_5_0 PS(); + } +} diff --git a/shaders/Managed/Space/System/DenoiseEstimate.fx b/shaders/Managed/Space/System/DenoiseEstimate.fx new file mode 100644 index 000000000..1cffd6b9f --- /dev/null +++ b/shaders/Managed/Space/System/DenoiseEstimate.fx @@ -0,0 +1,52 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../include/Carbon/System.fxh" + +Texture2D Source; +SamplerState SourceSampler +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = None; + AddressU = Clamp; + AddressV = Clamp; +}; + +float4 SourceDimensions; + + +float4 VS( float4 pos : POSITION ) : SV_Position +{ + return pos; +} + + +float4 PS( float4 pixelPos: SV_Position ) : SV_Target +{ + float2 center = ( pixelPos.xy + 0.5 ) * SourceDimensions.zw; + + float dst = 0.0; + const int R = 3; + for( int2 delta = int2( -R, -R ); delta.y <= R; delta.y += 2 ) + { + for( delta.x = -R; delta.x <= R; delta.x += 2 ) + { + dst += Source.SampleLevel( SourceSampler, center + float2(delta) * SourceDimensions.zw, 0); + } + } + int samples = R + 1; + + dst *= ( 1.0 / float( samples * samples ) ); + return dst; +} + + + +technique Main +{ + pass P0 + { + VertexShader = compile vs_5_0 VS(); + PixelShader = compile ps_5_0 PS(); + } +} diff --git a/shaders/Managed/Space/System/EstimateNoise.fx b/shaders/Managed/Space/System/EstimateNoise.fx new file mode 100644 index 000000000..d97f2a25e --- /dev/null +++ b/shaders/Managed/Space/System/EstimateNoise.fx @@ -0,0 +1,84 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../include/Carbon/System.fxh" + +Texture2D Source; + +// Pixel radius for noise search. It relates to dithering +static const int NOISE_ESTIMATION_RADIUS = 3; + + +/* Sample the signal used for the noise estimation at offset from ssC */ +float Tap( int2 ssC, int2 offset ) +{ + return Source[ssC + offset].r; +} + + +/////////////////////////////////////////////////////////////////////////////// +// Estimate desired radius from the second derivative of the signal itself in source0 relative to baseline, +// which is the noisier image because it contains shadows +float EstimateNoise( int2 ssC, float2 axis ) +{ + float v2 = Tap( ssC, int2( -NOISE_ESTIMATION_RADIUS * axis ) ); + float v1 = Tap( ssC, int2( ( 1 - NOISE_ESTIMATION_RADIUS ) * axis ) ); + + float d2mag = 0.0; + // The first two points are accounted for above + for( int r = -NOISE_ESTIMATION_RADIUS + 2; r <= NOISE_ESTIMATION_RADIUS; ++r ) + { + float v0 = Tap( ssC, int2( axis * r ) ); + + // Second derivative + float d2 = v2 - v1 * 2.0 + v0; + + d2mag += abs( d2 ); + + // Shift weights in the window + v2 = v1; + v1 = v0; + } + + // Scaled value by 1.5 *before* clamping to visualize going out of range + // It is clamped again when applied. + return clamp( sqrt( d2mag * ( 1.0 / NOISE_ESTIMATION_RADIUS ) ) * ( 1.0 / 1.5 ), 0.0, 1.0 ); +} + + +float Hash( float n ) +{ + return frac( sin( n ) * 1e4 ); +} + + +float4 VS( float4 pos : POSITION ) : SV_Position +{ + return pos; +} + + +float4 PS( float4 pixelPos : SV_Position ) : SV_Target +{ + + float angle = Hash( pixelPos.x + pixelPos.y * 1920.0 ); + + float result = 0; + const int N = 3; + for( float t = 0; t < N; ++t, angle += 3.1428 / float( N ) ) + { + float c = cos( angle ), s = sin( angle ); + result = max( EstimateNoise( int2( pixelPos.xy ), float2( c, s ) ), result ); + } + return result; +} + + + +technique Main +{ + pass P0 + { + VertexShader = compile vs_5_0 VS(); + PixelShader = compile ps_5_0 PS(); + } +} diff --git a/shaders/Managed/Space/System/Raytracing/SkinVertices.fx b/shaders/Managed/Space/System/Raytracing/SkinVertices.fx new file mode 100644 index 000000000..d3b40c3da --- /dev/null +++ b/shaders/Managed/Space/System/Raytracing/SkinVertices.fx @@ -0,0 +1,136 @@ +// Copyright © 2026 CCP ehf. + +#include "../../../../include/Carbon/System.fxh" +#include "../../../../include/Carbon/BoneTransforms.fxh" +#include "../../../../include/Carbon/VertexBufferUtil.fxh" +#include "../../../../include/Carbon/SpaceScene/MeshMorphing.fxh" + +StructuredBuffer InVB; +RWStructuredBuffer OutVB; +RWBuffer BakedMorphTargetBuffer ; + +cbuffer PerObjectVS : register( b3 ) +{ + uint VertexCount; + uint Stride; + uint PositionOffset; + uint BoneOffset; + uint BoneWeightOffset; + uint TransformOffset; + uint InVBIndex; + uint OutVBIndex; + uint OutVBOffset; + uint MorphAnimationDataOffset; + uint MorphAnimationDataCount; + uint MorphTargetPositionOffset; + uint MorphTargetStride; + uint MorphTargetSize; + uint BakedMorphTargetPositionOffset; + uint Padding2; +}; + +#if PLATFORM==PLATFORM_DX12 + +StructuredBuffer HeapView_StructuredBuffer[] +< + bool IsHeapView = true; +>; + +RWStructuredBuffer HeapView_RWStructuredBuffer[] +< + bool IsHeapView = true; +>; + +float LoadVB( uint offset ) +{ + return HeapView_StructuredBuffer[InVBIndex][offset]; +} + +void StoreVB( uint offset, float value ) +{ + HeapView_RWStructuredBuffer[OutVBIndex][offset + OutVBOffset] = value; +} + +#else + +float LoadVB( uint offset ) +{ + return InVB[offset]; +} + +void StoreVB( uint offset, float value ) +{ + OutVB[offset + OutVBOffset] = value; +} + +#endif + +uint4 FromUByte4( uint x ) +{ + return uint4( x & 0xff, ( x >> 8 ) & 0xff, ( x >> 16 ) & 0xff, x >> 24 ); +} + +[numthreads( 64, 1, 1 )] +void SkinVertices( uint3 offset : SV_DispatchThreadID ) +{ + uint vtx = offset.x; + + if( vtx < VertexCount ) + { + uint poffset = ( vtx * Stride + PositionOffset ); + float3 pos = float3( LoadVB( poffset ), LoadVB( poffset + 1 ), LoadVB( poffset + 2 ) ); + + int morphOffset = vtx * MorphTargetStride + MorphTargetPositionOffset; + float3 tmpPos = pos; + for( uint i = 0; i < MorphAnimationDataCount; i++ ) + { + MorphTargetAnimationData data = MorphTargetAnimations[MorphAnimationDataOffset + i]; + int offset = morphOffset + MorphTargetSize * data.index; + float3 morphPos = float3( LoadVB( offset ), LoadVB( offset + 1 ), LoadVB( offset + 2 ) ); + pos += data.weight * ( morphPos - tmpPos ); + } + + if( BakedMorphTargetPositionOffset != 0xFFFFFFFF ) + { + int bakedPosOffset = BakedMorphTargetPositionOffset; // offset in shared index/vertex buffer, but for baked morphs + bakedPosOffset += ( vtx * MorphTargetStride ) << 2; + + float3 morphPos = ReadFloat32_3( BakedMorphTargetBuffer, bakedPosOffset ); + + pos += morphPos - tmpPos; + } + + uint boffset = vtx * Stride + BoneOffset; + + float4x4 boneMatrix; + uint boneIndices = asuint( LoadVB( boffset ) ); + if( BoneWeightOffset != 0xffffffff ) + { + uint4 bones = FromUByte4( boneIndices ); + uint4 w = FromUByte4( asuint( LoadVB( vtx * Stride + BoneWeightOffset ) ) ); + float4 weights = float4( float4( w ) / 255.0 ); + + boneMatrix = + GetBoneTransform( TransformOffset + bones.x ) * weights.x + + GetBoneTransform( TransformOffset + bones.y ) * weights.y + + GetBoneTransform( TransformOffset + bones.z ) * weights.z + + GetBoneTransform( TransformOffset + bones.w ) * weights.w; + } + else + { + boneMatrix = GetBoneTransform( TransformOffset + ( boneIndices & 0xff ) ); + } + pos = mul( float4( pos, 1 ), boneMatrix ).xyz; + StoreVB( vtx * 3, pos.x ); + StoreVB( vtx * 3 + 1, pos.y ); + StoreVB( vtx * 3 + 2, pos.z ); + } +} + +technique Main +{ + pass p0 + { + ComputeShader = compile cs_5_0 SkinVertices(); + } +} diff --git a/shaders/Managed/Utility/BillboardNoZ.fx b/shaders/Managed/Utility/BillboardNoZ.fx new file mode 100644 index 000000000..76370afb2 --- /dev/null +++ b/shaders/Managed/Utility/BillboardNoZ.fx @@ -0,0 +1,72 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" +#include "PerFrameVSData.fxh" + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +Texture2D TextureMap +< + bool SasUiVisible = true; +>; + +SamplerState LinearSampler +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct SolidVertex +{ + float3 position : POSITION; + float4 color : TEXCOORD0; +}; + +struct SolidVertexPS +{ + float4 position : POSITION; + float2 texCoord : TEXCOORD0; + float4 color : TEXCOORD1; +}; + +SolidVertexPS SpriteVS( SolidVertex inVtx ) +{ + SolidVertexPS outVtx; + + float3 posWorld = mul( float4( 0, 0, 0, 1 ), PerObjectVS.WorldMat ).xyz; + float3 posView = mul( float4( posWorld, 1.0f ), PerFrameVS.ViewMat ).xyz; + posView += mul( inVtx.position, (float3x3)PerObjectVS.WorldMat ); + outVtx.position = mul( float4( posView, 1.0f ), PerFrameVS.ProjectionMat ); + outVtx.color = inVtx.color; + outVtx.texCoord = inVtx.position.xy * 0.5 + 0.5; + + return outVtx; +} + +float4 SpritePS( SolidVertexPS inVtx ) : SV_Target +{ + return inVtx.color * TextureMap.Sample( LinearSampler, inVtx.texCoord ); +} + +technique Main +{ + pass P0 + { + ZWriteEnable = False; + ZEnable = False; + AlphaBlendEnable = True; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + CullMode = NONE; + + VertexShader = compile vs_5_0 SpriteVS(); + PixelShader = compile ps_5_0 SpritePS(); + } +} \ No newline at end of file diff --git a/shaders/Managed/Utility/BillboardPicking.fx b/shaders/Managed/Utility/BillboardPicking.fx new file mode 100644 index 000000000..64838521a --- /dev/null +++ b/shaders/Managed/Utility/BillboardPicking.fx @@ -0,0 +1,57 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" +#include "../../include/Carbon/Picking.fxh" +#include "PerFrameVSData.fxh" + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +struct SolidVertex +{ + float3 position : POSITION; +}; + +struct SolidVertexPS +{ + float4 position : POSITION; + float2 texCoord : TEXCOORD0; +}; + +// selected.AddTriangle((-1, -1, 0), (1,1,1,1), (1, -1, 0), (1,1,1,1), (-1, 1, 0), (1,1,1,1)) + +SolidVertexPS SpriteVS( SolidVertex inVtx ) +{ + SolidVertexPS outVtx; + + float3 posWorld = mul( float4( 0, 0, 0, 1 ), PerObjectVS.WorldMat ).xyz; + float3 posView = mul( float4( posWorld, 1.0f ), PerFrameVS.ViewMat ).xyz; + posView += mul( inVtx.position, (float3x3)PerObjectVS.WorldMat ); + outVtx.position = mul( float4( posView, 1.0f ), PerFrameVS.ProjectionMat ); + outVtx.texCoord = inVtx.position.xy * 0.5 + 0.5; + + return outVtx; +} + +float4 SpritePS( SolidVertexPS inVtx ) : SV_Target +{ + // Primitive scene in Trinity uses a completely different picking scheme compared to EveSpaceScene. + return float4( areaId + 1, pickingObjectLowBits + 1, 1, 0 ); +} + +technique Main +{ + pass P0 + { + ZWriteEnable = TRUE; + ZEnable = TRUE; + ZFunc = LESSEQUAL; + AlphaBlendEnable = FALSE; + AlphaTestEnable = FALSE; + + VertexShader = compile vs_5_0 SpriteVS(); + PixelShader = compile ps_5_0 SpritePS(); + } +} \ No newline at end of file diff --git a/shaders/Managed/Utility/Debug.fx b/shaders/Managed/Utility/Debug.fx new file mode 100644 index 000000000..0b412a43f --- /dev/null +++ b/shaders/Managed/Utility/Debug.fx @@ -0,0 +1,59 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" + +float4x4 WorldMat; +float4x4 ViewProjectionMat; + +// ------------------------------------------------------------- +// Output channels +// ------------------------------------------------------------- +struct VS_OUTPUT +{ + float4 pos : SV_Position; + float4 col : COLOR0; +}; + +// ------------------------------------------------------------- +// vertex shader function (input channels) +// ------------------------------------------------------------- +VS_OUTPUT PerVertex(float3 pos : POSITION, float4 col : COLOR0 ) +{ + VS_OUTPUT Out = (VS_OUTPUT)0; + + float4 worldpos = mul( float4( pos, 1.0f), WorldMat ); + Out.pos = mul( worldpos, ViewProjectionMat ); + Out.col = col; + return Out; +} + + +// ------------------------------------------------------------- +// Pixel Shader (input channels):output channel +// ------------------------------------------------------------- +float4 PerPixel( VS_OUTPUT inVtx ) : SV_Target +{ + return inVtx.col; +} + + +// ------------------------------------------------------------- +// +// ------------------------------------------------------------- +technique Main +{ + pass P0 + { + ZEnable = FALSE; + ZWriteEnable = TRUE; + ZFunc = LESSEQUAL; + CullMode = NONE; + AlphaBlendEnable = TRUE; + AlphaTestEnable = FALSE; + FillMode = SOLID; + + // compile shaders + VertexShader = compile vs_5_0 PerVertex(); + PixelShader = compile ps_5_0 PerPixel(); + } +} diff --git a/shaders/Managed/Utility/DebugLine.fx b/shaders/Managed/Utility/DebugLine.fx new file mode 100644 index 000000000..caa213cb3 --- /dev/null +++ b/shaders/Managed/Utility/DebugLine.fx @@ -0,0 +1,59 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" + +float4x4 WorldMat; +float4x4 ViewProjectionMat; + +// ------------------------------------------------------------- +// Output channels +// ------------------------------------------------------------- +struct VS_OUTPUT +{ + float4 pos : SV_Position; + float4 col : COLOR0; +}; + +// ------------------------------------------------------------- +// vertex shader function (input channels) +// ------------------------------------------------------------- +VS_OUTPUT PerVertex( float3 pos : POSITION, float4 col : COLOR0 ) +{ + VS_OUTPUT Out = (VS_OUTPUT)0; + + float4 worldpos = mul( float4( pos, 1.0f ), WorldMat ); + Out.pos = mul( worldpos, ViewProjectionMat ); + Out.col = col; + return Out; +} + + +// ------------------------------------------------------------- +// Pixel Shader (input channels):output channel +// ------------------------------------------------------------- +float4 PerPixel( VS_OUTPUT inVtx ) : SV_Target +{ + return inVtx.col; +} + + +// ------------------------------------------------------------- +// +// ------------------------------------------------------------- +technique Main +{ + pass P0 + { + ZEnable = FALSE; + ZWriteEnable = FALSE; + ZFunc = LESSEQUAL; + CullMode = NONE; + AlphaBlendEnable = FALSE; + AlphaTestEnable = FALSE; + FillMode = Wireframe; + + // compile shaders + VertexShader = compile vs_5_0 PerVertex(); + PixelShader = compile ps_5_0 PerPixel(); + } +} \ No newline at end of file diff --git a/shaders/Managed/Utility/DebugPrimitive.fx b/shaders/Managed/Utility/DebugPrimitive.fx new file mode 100644 index 000000000..d67d7cd65 --- /dev/null +++ b/shaders/Managed/Utility/DebugPrimitive.fx @@ -0,0 +1,85 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" +#include "../../include/Carbon/SpaceScene/PerSceneData.fxh" + +struct PerFrameVSData +{ + // matrices + float4x4 ViewInverseTransposeMat; + float4x4 ViewProjectionMat; +} PerFrameVS : register( PERFRAME_VS_STARTREGISTER ); + +EveSpaceSceneDataPS PerFramePS : register( PERFRAME_PS_STARTREGISTER ); + +struct AppDebugVtx +{ + float3 position : POSITION; + float3 normal : NORMAL; + float4 zPassColor : COLOR1; + float4 zFailColor : COLOR2; +}; + +struct DebugVtx +{ + float4 position : SV_Position; + float3 normal : TEXCOORD0; + float4 color : TEXCOORD1; + float4 colorBack : TEXCOORD2; +}; + +Texture2D DepthMap ; +sampler DepthMapSamplerClamp = sampler_state +{ + MinFilter = Point; + MagFilter = Point; + MipFilter = None; + AddressU = Clamp; + AddressV = Clamp; +}; + + +float Get3dUISceneDepthOcclusion( float4 clipPos ) +{ + float depth = clipPos.z; + float sceneDepth = DepthMap.Sample( DepthMapSamplerClamp, clipPos.xy / PerFramePS.RenderTargetData.xy ).r; + return (float)( depth > sceneDepth ); +} + + +DebugVtx DebugVS( AppDebugVtx inVtx, uniform bool front ) +{ + DebugVtx outVtx; + outVtx.position = mul( float4( inVtx.position, 1 ), PerFrameVS.ViewProjectionMat ); + outVtx.normal = inVtx.normal; + outVtx.color = inVtx.zPassColor; + outVtx.colorBack = inVtx.zFailColor; + return outVtx; +} + +float4 DebugPS( DebugVtx inVtx ) : SV_Target +{ + float4 color = lerp( inVtx.colorBack, inVtx.color, Get3dUISceneDepthOcclusion( inVtx.position ) ); + color = color.bgra; + if( any( inVtx.normal != 0 ) ) + { + float3 normal = normalize( inVtx.normal ); + float diffuse = lerp( 0.3, 1, saturate( dot( normal, 1 ) ) ); + color.rgb *= diffuse; + } + return color; +} + +technique Main +{ + pass P0 + { + ZFunc = LessEqual; + AlphaTestEnable = False; + AlphaBlendEnable = True; + SrcBlend = SrcAlpha; + DestBlend = InvSrcAlpha; + VertexShader = compile vs_3_0 DebugVS( true ); + PixelShader = compile ps_3_0 DebugPS(); + } +} \ No newline at end of file diff --git a/shaders/Managed/Utility/DebugPrimitivePicking.fx b/shaders/Managed/Utility/DebugPrimitivePicking.fx new file mode 100644 index 000000000..ac4458738 --- /dev/null +++ b/shaders/Managed/Utility/DebugPrimitivePicking.fx @@ -0,0 +1,52 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" + +struct PerFrameVSData +{ + // matrices + float4x4 ViewInverseTransposeMat; + float4x4 ViewProjectionMat; +} PerFrameVS : register( PERFRAME_VS_STARTREGISTER ); + +struct AppDebugVtx +{ + float3 position : POSITION; + float2 index : TEXCOORD0; +}; + +struct DebugVtx +{ + float4 position : SV_Position; + float4 output : TEXCOORD0; +}; + + +DebugVtx DebugVS( AppDebugVtx inVtx ) +{ + DebugVtx outVtx; + outVtx.position = mul( float4( inVtx.position, 1 ), PerFrameVS.ViewProjectionMat ); + outVtx.output = float4( inVtx.index, outVtx.position.zw ); + return outVtx; +} + +uint PickingPointerLowBits; +uint PickingPointerHighBits; +uint PickingArea; + +uint4 DebugPS( DebugVtx inVtx ) : COLOR +{ + return uint4( PickingPointerLowBits, PickingPointerHighBits, PickingArea, 0 ); +} + +technique Main +{ + pass P0 + { + ZEnable = True; + ZWriteEnable = True; + ZFunc = LessEqual; + VertexShader = compile vs_3_0 DebugVS(); + PixelShader = compile ps_3_0 DebugPS(); + } +} \ No newline at end of file diff --git a/shaders/Managed/Utility/Lines.fxh b/shaders/Managed/Utility/Lines.fxh new file mode 100644 index 000000000..d641820d9 --- /dev/null +++ b/shaders/Managed/Utility/Lines.fxh @@ -0,0 +1,42 @@ +// Copyright © 2026 CCP ehf. + +#ifndef LINES_FXH +#define LINES_FXH + +#include "../../include/Carbon/System.fxh" +#include "PerFrameVSData.fxh" + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +struct LineVertex +{ + float3 position : POSITION; + float4 color : TEXCOORD0; +}; + +struct LineVertexPS +{ + float4 position : SV_Position; + float4 color : TEXCOORD0; +}; + +LineVertexPS SpriteVS( LineVertex inVtx ) +{ + LineVertexPS outVtx; + + const float4 posWorld = mul( float4( inVtx.position, 1.0f ), PerObjectVS.WorldMat ); + outVtx.position = mul( posWorld, PerFrameVS.ViewProjectionMat ); + outVtx.color = inVtx.color; + + return outVtx; +} + +float4 SpritePS( LineVertexPS inVtx ) : COLOR +{ + return inVtx.color; +} + +#endif \ No newline at end of file diff --git a/shaders/Managed/Utility/LinesNoZ.fx b/shaders/Managed/Utility/LinesNoZ.fx new file mode 100644 index 000000000..3e949afdb --- /dev/null +++ b/shaders/Managed/Utility/LinesNoZ.fx @@ -0,0 +1,20 @@ +// Copyright © 2026 CCP ehf. + +#include "Lines.fxh" + +technique Main +{ + pass P0 + { + ZWriteEnable = TRUE; + ZEnable = FALSE; + + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + VertexShader = compile vs_3_0 SpriteVS(); + PixelShader = compile ps_3_0 SpritePS(); + } +} diff --git a/shaders/Managed/Utility/LinesRotationTool.fx b/shaders/Managed/Utility/LinesRotationTool.fx new file mode 100644 index 000000000..6b8c04c72 --- /dev/null +++ b/shaders/Managed/Utility/LinesRotationTool.fx @@ -0,0 +1,59 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" +#include "PerFrameVSData.fxh" + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +struct LineVertex +{ + float3 position : POSITION; + float4 color : TEXCOORD0; +}; + +struct LineVertexPS +{ + float4 position : SV_Position; + float4 color : TEXCOORD0; +}; + +LineVertexPS SpriteVS( LineVertex inVtx ) +{ + LineVertexPS outVtx; + const float4 posCenter = mul( float4( 0.0, 0.0, 0.0, 1.0 ), PerObjectVS.WorldMat ); + const float4 normal = float4( PerFrameVS.ViewMat[0][2], PerFrameVS.ViewMat[1][2], PerFrameVS.ViewMat[2][2], 1.0 ); + + const float4 posWorld = mul( float4( inVtx.position, 1.0f ), PerObjectVS.WorldMat ); + outVtx.position = mul( posWorld, PerFrameVS.ViewProjectionMat ); + outVtx.color = inVtx.color; + if( dot( posWorld - posCenter, normal ) < 0.0 ) + { + outVtx.color.a = 0.0; + } + + return outVtx; +} +float4 SpritePS( LineVertexPS inVtx ) : SV_Target +{ + return inVtx.color; +} + +technique Main +{ + pass P0 + { + ZWriteEnable = FALSE; + ZEnable = FALSE; + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + + VertexShader = compile vs_3_0 SpriteVS(); + PixelShader = compile ps_3_0 SpritePS(); + } +} diff --git a/shaders/Managed/Utility/LinesRotationToolLH.fx b/shaders/Managed/Utility/LinesRotationToolLH.fx new file mode 100644 index 000000000..80228ccef --- /dev/null +++ b/shaders/Managed/Utility/LinesRotationToolLH.fx @@ -0,0 +1,59 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" +#include "PerFrameVSData.fxh" + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +struct LineVertex +{ + float3 position : POSITION; + float4 color : TEXCOORD0; +}; + +struct LineVertexPS +{ + float4 position : SV_Position; + float4 color : TEXCOORD0; +}; + +LineVertexPS SpriteVS( LineVertex inVtx ) +{ + LineVertexPS outVtx; + const float4 posCenter = mul( float4( 0.0, 0.0, 0.0, 1.0 ), PerObjectVS.WorldMat ); + const float4 normal = float4( PerFrameVS.ViewMat[0][2], PerFrameVS.ViewMat[1][2], PerFrameVS.ViewMat[2][2], 1.0 ); + + const float4 posWorld = mul( float4( inVtx.position, 1.0f ), PerObjectVS.WorldMat ); + outVtx.position = mul( posWorld, PerFrameVS.ViewProjectionMat ); + outVtx.color = inVtx.color; + if( dot( posWorld - posCenter, normal ) > 0.0 ) + { + outVtx.color.a = 0.0; + } + + return outVtx; +} +float4 SpritePS( LineVertexPS inVtx ) : SV_Target +{ + return inVtx.color; +} + +technique Main +{ + pass P0 + { + ZWriteEnable = FALSE; + ZEnable = FALSE; + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + + VertexShader = compile vs_3_0 SpriteVS(); + PixelShader = compile ps_3_0 SpritePS(); + } +} diff --git a/shaders/Managed/Utility/LinesWithZ.fx b/shaders/Managed/Utility/LinesWithZ.fx new file mode 100644 index 000000000..29fdb3356 --- /dev/null +++ b/shaders/Managed/Utility/LinesWithZ.fx @@ -0,0 +1,20 @@ +// Copyright © 2026 CCP ehf. + +#include "Lines.fxh" + +technique Main +{ + pass P0 + { + ZWriteEnable = TRUE; + ZEnable = TRUE; + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + DepthBias = 0.001; + VertexShader = compile vs_3_0 SpriteVS(); + PixelShader = compile ps_3_0 SpritePS(); + } +} diff --git a/shaders/Managed/Utility/PerFrameVSData.fxh b/shaders/Managed/Utility/PerFrameVSData.fxh new file mode 100644 index 000000000..22ade7000 --- /dev/null +++ b/shaders/Managed/Utility/PerFrameVSData.fxh @@ -0,0 +1,18 @@ +// Copyright © 2026 CCP ehf. + +#ifndef PERFRAMEVSDATA_FXH +#define PERFRAMEVSDATA_FXH + +#include "../../include/Carbon/System.fxh" + +struct PerFrameVSData +{ + float4x4 ViewInverseTransposeMat; + float4 SunWorldDir; + float4 SceneFogColor; + float4x4 ViewProjectionMat; + float4x4 ViewMat; + float4x4 ProjectionMat; +} PerFrameVS : register( PERFRAME_VS_STARTREGISTER ); + +#endif \ No newline at end of file diff --git a/shaders/Managed/Utility/PrimitivePicking.fx b/shaders/Managed/Utility/PrimitivePicking.fx new file mode 100644 index 000000000..aba0dfa03 --- /dev/null +++ b/shaders/Managed/Utility/PrimitivePicking.fx @@ -0,0 +1,56 @@ +// Copyright © 2026 CCP ehf. + +#include "../../include/Carbon/System.fxh" +#include "../../include/Carbon/Picking.fxh" +#include "PerFrameVSData.fxh" + + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +struct PickingAppVtx +{ + float3 pos : POSITION; + float2 tex : TEXCOORD0; +}; + +struct StandardShaderVtx +{ + float4 posClip : SV_Position; + float4 eyeDirWorld : TEXCOORD0; + float4 texcoord01 : TEXCOORD1; +}; + +float4 PickingPS( StandardShaderVtx inVtx ) : SV_Target +{ + // Primitive scene in Trinity uses a completely different picking scheme compared to EveSpaceScene. + return float4( areaId + 1, pickingObjectLowBits + 1, inVtx.eyeDirWorld.w, 0 ); +} + +StandardShaderVtx PickingVS( PickingAppVtx inVtx ) +{ + StandardShaderVtx outVtx = (StandardShaderVtx)0; + + float4 posWorld = mul( float4( inVtx.pos, 1.0f ), PerObjectVS.WorldMat ); + outVtx.posClip = mul( posWorld, PerFrameVS.ViewProjectionMat ); + outVtx.eyeDirWorld = mul( posWorld, PerFrameVS.ViewMat ); + outVtx.texcoord01 = float4( inVtx.tex, 0, 0 ); + return outVtx; +} + +technique Main +{ + pass P0 + { + ZWriteEnable = TRUE; + ZEnable = TRUE; + ZFunc = LESSEQUAL; + AlphaBlendEnable = FALSE; + AlphaTestEnable = FALSE; + + VertexShader = compile vs_3_0 PickingVS(); + PixelShader = compile ps_3_0 PickingPS(); + } +} diff --git a/shaders/Managed/Utility/SolidNoLightNoCullWithZ.fx b/shaders/Managed/Utility/SolidNoLightNoCullWithZ.fx new file mode 100644 index 000000000..bd19b9ed9 --- /dev/null +++ b/shaders/Managed/Utility/SolidNoLightNoCullWithZ.fx @@ -0,0 +1,48 @@ +// Copyright © 2026 CCP ehf. + +#include "Solids.fxh" + +struct SolidColorVertex +{ + float3 position : POSITION; + float4 color : TEXCOORD0; +}; + +struct SolidColorVertexPS +{ + float4 position : SV_Position; + float4 color : TEXCOORD0; +}; + +SolidColorVertexPS SpriteSolidColorVS( SolidColorVertex inVtx ) +{ + SolidColorVertexPS outVtx; + + const float4 posWorld = mul( float4( inVtx.position, 1.0f ), PerObjectVS.WorldMat ); + outVtx.position = mul( posWorld, PerFrameVS.ViewProjectionMat ); + + outVtx.color = inVtx.color; + return outVtx; +} + +float4 SpriteSolidColorPS( SolidColorVertexPS inVtx ) : SV_Target +{ + return inVtx.color; +} + +technique Main +{ + pass P0 + { + ZWriteEnable = FALSE; + ZEnable = TRUE; + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + CullMode = NONE; + VertexShader = compile vs_3_0 SpriteSolidColorVS(); + PixelShader = compile ps_3_0 SpriteSolidColorPS(); + } +} diff --git a/shaders/Managed/Utility/Solids.fxh b/shaders/Managed/Utility/Solids.fxh new file mode 100644 index 000000000..b5fd50c2a --- /dev/null +++ b/shaders/Managed/Utility/Solids.fxh @@ -0,0 +1,52 @@ +// Copyright © 2026 CCP ehf. + +#ifndef SOLIDS_FXH +#define SOLIDS_FXH + +#include "../../include/Carbon/System.fxh" +#include "PerFrameVSData.fxh" + +struct PerObjectVSData +{ + float4x4 WorldMat : World; +} PerObjectVS : register( PEROBJECT_VS_STARTREGISTER ); + +struct SolidVertex +{ + float3 position : POSITION; + float3 normal : NORMAL; + float4 color : TEXCOORD0; +}; + +struct SolidVertexPS +{ + float4 position : SV_Position; + float4 color : TEXCOORD0; + float3 viewNormal : TEXCOORD1; + float3 vtxNormal : TEXCOORD2; +}; + +SolidVertexPS SpriteVS( SolidVertex inVtx ) +{ + SolidVertexPS outVtx; + + const float4 posWorld = mul( float4( inVtx.position, 1.0f ), PerObjectVS.WorldMat ); + outVtx.position = mul( posWorld, PerFrameVS.ViewProjectionMat ); + const float4 normal = float4( PerFrameVS.ViewMat[0][2], PerFrameVS.ViewMat[1][2], PerFrameVS.ViewMat[2][2], 0.0 ); + + outVtx.color = inVtx.color; + outVtx.vtxNormal = normalize( mul( float4( inVtx.normal, 0.0f ), PerObjectVS.WorldMat ).xyz ); + outVtx.viewNormal = normal.xyz; + + return outVtx; +} + +float4 SpritePS( SolidVertexPS inVtx ) : SV_Target +{ + float alpha = inVtx.color.a; + float4 color = inVtx.color * saturate( dot( inVtx.vtxNormal, inVtx.viewNormal ) ); + color.a = alpha; + return color; +} + +#endif \ No newline at end of file diff --git a/shaders/Managed/Utility/SolidsNoZ.fx b/shaders/Managed/Utility/SolidsNoZ.fx new file mode 100644 index 000000000..6e9eea3c7 --- /dev/null +++ b/shaders/Managed/Utility/SolidsNoZ.fx @@ -0,0 +1,20 @@ +// Copyright © 2026 CCP ehf. + +#include "Solids.fxh" + +technique Main +{ + pass P0 + { + ZWriteEnable = TRUE; + ZEnable = FALSE; + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + + VertexShader = compile vs_3_0 SpriteVS(); + PixelShader = compile ps_3_0 SpritePS(); + } +} \ No newline at end of file diff --git a/shaders/Managed/Utility/SolidsWithZ.fx b/shaders/Managed/Utility/SolidsWithZ.fx new file mode 100644 index 000000000..48162dad5 --- /dev/null +++ b/shaders/Managed/Utility/SolidsWithZ.fx @@ -0,0 +1,20 @@ +// Copyright © 2026 CCP ehf. + +#include "Solids.fxh" + +technique Main +{ + pass P0 + { + ZWriteEnable = TRUE; + ZEnable = TRUE; + AlphaBlendEnable = TRUE; + BlendOp = ADD; + SrcBlend = SRCALPHA; + DestBlend = INVSRCALPHA; + AlphaTestEnable = FALSE; + DepthBias = 0.0001; + VertexShader = compile vs_3_0 SpriteVS(); + PixelShader = compile ps_3_0 SpritePS(); + } +} diff --git a/shaders/Utility/CubeTextureViewer.fx b/shaders/Utility/CubeTextureViewer.fx new file mode 100644 index 000000000..eee9e4eb6 --- /dev/null +++ b/shaders/Utility/CubeTextureViewer.fx @@ -0,0 +1,81 @@ +// Copyright © 2026 CCP ehf. + +#include "../include/Carbon/System.fxh" + +TextureCube Texture; +float4 ColorTransform0; +float4 ColorTransform1; +float4 ColorTransform2; +float4 ColorTransform3; +float4 MinValue; +float4 MaxValue; +float UseForceMipMap; +float ForceMipMap; +float MsaaType; + +float4x4 ViewMat; + +SamplerState AutoMipMapTexture +{ + AddressU = Border; + AddressV = Border; + BorderColor = 0; + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; +}; + +SamplerState NoMipMapTexture +{ + AddressU = Border; + AddressV = Border; + BorderColor = 0; + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; +}; + +struct BlitAppVertex +{ + float4 pos : POSITION; + float2 texCoord : TEXCOORD0; +}; + +struct BlitVertex +{ + float4 pos : SV_Position; + float2 texCoord : TEXCOORD0; +}; + + +BlitVertex BlitVS( BlitAppVertex inVtx ) +{ + BlitVertex outVtx = { inVtx.pos, inVtx.texCoord }; + return outVtx; +} + +float4 BlitPS( BlitVertex inVtx ) : SV_Target +{ + float2 pos = inVtx.texCoord * 2 - 1; + float3 viewDir = float3( pos.x, -pos.y, -1 ); + float3 dir = mul( (float3x3)ViewMat, viewDir ); + float4x4 ColorTransform = { ColorTransform0, ColorTransform1, ColorTransform2, ColorTransform3 }; + float4 color; + + color = lerp( Texture.Sample( AutoMipMapTexture, dir ), Texture.SampleLevel( NoMipMapTexture, dir, ForceMipMap ), UseForceMipMap ); + + color = mul( color, ColorTransform ); + color = ( color - MinValue ) / ( MaxValue - MinValue ); + return float4( color.rgb, 1 ); +} + +technique Main +{ + pass P0 + { + AlphaBlendEnable = False; + + VertexShader = compile vs_3_0 BlitVS(); + PixelShader = compile ps_3_0 BlitPS(); + } +} diff --git a/shaders/Utility/GeometryViewer.fx b/shaders/Utility/GeometryViewer.fx new file mode 100644 index 000000000..7971c2fd5 --- /dev/null +++ b/shaders/Utility/GeometryViewer.fx @@ -0,0 +1,175 @@ +// Copyright © 2026 CCP ehf. + +#pragma permutation(FILL_MODE, values=(FILL_SOLID, FILL_WIREFRAME, FILL_NORMALS, FILL_TANGENTS), default=FILL_WIREFRAME) +#pragma permutation(COLOR_MODE, values=(COLOR_SOLID, COLOR_LIT, COLOR_UV0, COLOR_UV1, COLOR_VERTEX_COLOR)) +#pragma permutation(TANGENT_PACKING, values=(TANGENT_NONE, TANGENT_PACKED, TANGENT_UNPACKED, TANGENT_NORMALS)) + +#include "../include/Carbon/System.fxh" +#include "../include/Carbon/Math.fxh" +#include "../include/Carbon/PackedTangents.fxh" + +struct VSIn +{ + float3 position : POSITION; +#if TANGENT_PACKING == TANGENT_PACKED + float4 tangents : TANGENT; +#elif TANGENT_PACKING == TANGENT_UNPACKED + float3 normal : NORMAL; + float3 tangent : TANGENT; + float3 binormal : BINORMAL; +#elif TANGENT_PACKING == TANGENT_NORMALS + float3 normal : NORMAL; +#endif + +#if COLOR_MODE == COLOR_UV0 + float2 texCoord : TEXCOORD0; +#elif COLOR_MODE == COLOR_UV1 + float2 texCoord : TEXCOORD1; +#elif COLOR_MODE == COLOR_VERTEX_COLOR + float4 color : COLOR; +#endif +}; + +struct VSOut +{ + float4 position : SV_Position; + float4 normal : TEXCOORD0; + float3 tangent : TEXCOORD1; + float3 bitangent : TEXCOORD2; + float3 modelPos : TEXCOORD3; +}; + +struct PerFrameVSData +{ + float4x4 ViewInverseTransposeMat; + float4x4 ViewProjectionMat; +} PerFrameVS : register( PERFRAME_VS_STARTREGISTER ); + + +TangentSpace UnpackTangentSpace( VSIn inVtx ) +{ + TangentSpace ts; +#if TANGENT_PACKING == TANGENT_PACKED + ts = UnpackTangentSpaceLegacy( inVtx.tangents ); +#elif TANGENT_PACKING == TANGENT_NORMALS + ts.normal = inVtx.normal; + ts.tangent = 0; + ts.bitangent = 0; +#elif TANGENT_PACKING == TANGENT_UNPACKED + ts.normal = inVtx.normal; + ts.tangent = inVtx.tangent; + ts.bitangent = inVtx.binormal; +#else + ts.normal = 1; + ts.tangent = 1; + ts.bitangent = 1; +#endif + return ts; +} + +VSOut VS( VSIn inVtx ) +{ + VSOut outVtx; + outVtx.position = mul( float4( inVtx.position, 1 ), PerFrameVS.ViewProjectionMat ); + outVtx.normal = 1; + outVtx.tangent = 1; + outVtx.bitangent = 1; + outVtx.modelPos = inVtx.position; +#if COLOR_MODE == COLOR_LIT || FILL_MODE == FILL_NORMALS || FILL_MODE == FILL_TANGENTS + TangentSpace ts = UnpackTangentSpace( inVtx ); + outVtx.normal.xyz = ts.normal; + outVtx.tangent = ts.tangent; + outVtx.bitangent = ts.bitangent; +#elif COLOR_MODE == COLOR_UV0 || COLOR_MODE == COLOR_UV1 + outVtx.normal.xy = inVtx.texCoord; +#elif COLOR_MODE == COLOR_VERTEX_COLOR + outVtx.normal = inVtx.color; +#endif + return outVtx; +} + +void AddVector( VSOut vertex, float3 direction, float4 color, inout LineStream triStream ) +{ + float4 p0 = mul( float4( vertex.modelPos, 1 ), PerFrameVS.ViewProjectionMat ); + float length = p0.z; + float4 p1 = mul( float4( vertex.modelPos + normalize( direction ) * length, 1 ), PerFrameVS.ViewProjectionMat ); + + VSOut p = (VSOut)0; + p.normal = color; + + triStream.RestartStrip(); + + p.position = p0; + triStream.Append( p ); + + p.position = p1; + triStream.Append( p ); +} + +[maxvertexcount( 18 )] +void GS( triangle VSOut inVtx[3], inout LineStream triStream, uint primitiveID : SV_PrimitiveID ) +{ + for( int i = 0; i < 3; ++i ) + { + VSOut vertex = inVtx[i]; + AddVector( vertex, vertex.normal.xyz, float4( 0, 0, 1, 1 ), triStream ); +#if FILL_MODE == FILL_TANGENTS + AddVector( vertex, vertex.tangent, float4( 1, 0, 0, 1 ), triStream ); + AddVector( vertex, vertex.bitangent, float4( 0, 1, 0, 1 ), triStream ); +#endif + } +} + +float4 SolidPS( VSOut inVtx ) : SV_Target +{ + return inVtx.normal; +} + +float4 PS( VSOut inVtx ) : SV_Target +{ +#if COLOR_MODE == COLOR_LIT + return saturate( dot( normalize( inVtx.normal.xyz ), normalize( float3( 1, 1, 1 ) ) ) ) * 0.9 + 0.1; +#elif COLOR_MODE == COLOR_UV0 || COLOR_MODE == COLOR_UV1 + return float4( inVtx.normal.xy, 0, 1 ); +#elif COLOR_MODE == COLOR_VERTEX_COLOR + return inVtx.normal; +#endif + return 1; +} + +technique Main +{ +#if FILL_MODE == FILL_WIREFRAME + pass p0 + { + FillMode = Solid; + ColorWriteEnable = 0; + + VertexShader = compile vs_3_0 VS(); + PixelShader = compile ps_3_0 PS(); + } + pass p1 + { + ColorWriteEnable = 0xf; + FillMode = Wireframe; + SlopeScaleDepthBias = 0.5; + + VertexShader = compile vs_3_0 VS(); + PixelShader = compile ps_3_0 PS(); + } +#elif (FILL_MODE == FILL_TANGENTS || FILL_MODE == FILL_NORMALS) && PLATFORM_SUPPORTS_GEOMETRY_SHADERS + pass p1 + { + CullMode = None; + VertexShader = compile vs_3_0 VS(); + GeometryShader = compile gs_5_0 GS(); + PixelShader = compile ps_3_0 SolidPS(); + } +#else + pass p1 + { + VertexShader = compile vs_3_0 VS(); + PixelShader = compile ps_3_0 PS(); + } +#endif +} \ No newline at end of file diff --git a/shaders/Utility/LineGraph.fx b/shaders/Utility/LineGraph.fx new file mode 100644 index 000000000..43894d488 --- /dev/null +++ b/shaders/Utility/LineGraph.fx @@ -0,0 +1,43 @@ +// Copyright © 2026 CCP ehf. + +#include "../include/Carbon/System.fxh" + +float4 lineGraphColor; +float lineGraphScale; + +struct AppVertex +{ + float2 pos : POSITION; +}; + +struct ShaderVertex +{ + float4 posClip : SV_Position; +}; + +ShaderVertex VS( AppVertex inVtx ) +{ + ShaderVertex outVtx; + outVtx.posClip.x = inVtx.pos.x; + outVtx.posClip.y = inVtx.pos.y * lineGraphScale * 2.0f - 1.0f; + outVtx.posClip.z = 0; + outVtx.posClip.w = 1; + return outVtx; +} + +float4 PS( ShaderVertex inVtx ) : SV_Target +{ + return lineGraphColor; +} + +technique Main +{ + pass P0 + { + ZEnable = False; + AlphaBlendEnable = False; + AlphaTestEnable = False; + VertexShader = compile vs_3_0 VS(); + PixelShader = compile ps_3_0 PS(); + } +} diff --git a/shaders/Utility/TextureViewer.fx b/shaders/Utility/TextureViewer.fx new file mode 100644 index 000000000..3a161719b --- /dev/null +++ b/shaders/Utility/TextureViewer.fx @@ -0,0 +1,323 @@ +// Copyright © 2026 CCP ehf. + +#pragma permutation(TEXTURE_TYPE, values=(TEXTURE_2D, TEXTURE_CUBE, TEXTURE_3D), default=TEXTURE_2D, description="Texture type") +#pragma permutation(TEXTURE_MSAA, values=(MSAA_NO, MSAA_YES), default=MSAA_NO, description="Texture MSAA type") +#pragma permutation(TEXTURE_ARRAY, values=(ARRAY_NO, ARRAY_YES), default=ARRAY_NO, description="Texture array type") +#pragma permutation(TEXTURE_FORMAT, values=(FORMAT_FLOAT, FORMAT_INT, FORMAT_UINT)) +#pragma permutation(COLOR_SPACE, values = ( LINEAR_SPACE, SRGB_SPACE, LOGARITHMIC_SPACE ) ) +#pragma permutation(CUBE_RENDERING_MODE, values=(VIEW, CROSS, ROWS, FORWARD, BACKWARD, RIGHT, LEFT, UP, DOWN), default=VIEW, description="Cube Texture Rendering Mode") + +#include "../include/Carbon/System.fxh" + + +// TODO: Support metal and CubeArrays +#if PLATFORM == PLATFORM_METAL +#define TEXTURE_ARRAY ARRAY_NO +#endif + +#if TEXTURE_ARRAY == ARRAY_NO + +#if TEXTURE_TYPE == TEXTURE_2D +#if TEXTURE_MSAA == MSAA_NO +#define _TEXTURE_TYPE Texture2D +#else // TEXTURE_MSAA == MSAA_NO +#define _TEXTURE_TYPE Texture2DMS +#endif // TEXTURE_MSAA == MSAA_NO +#elif TEXTURE_TYPE == TEXTURE_CUBE +#define _TEXTURE_TYPE TextureCube +#else // TEXTURE_TYPE +#define _TEXTURE_TYPE Texture3D +#endif // TEXTURE_TYPE + +#else // TEXTURE_ARRAY + +#if TEXTURE_TYPE == TEXTURE_2D +#if TEXTURE_MSAA == MSAA_NO +#define _TEXTURE_TYPE Texture2DArray +#else // TEXTURE_MSAA == MSAA_NO +#define _TEXTURE_TYPE Texture2DMSArray +#endif // TEXTURE_MSAA == MSAA_NO +#elif TEXTURE_TYPE == TEXTURE_CUBE +#define _TEXTURE_TYPE TextureCubeArray +#else // TEXTURE_TYPE +// There is no 3DArray +#define _TEXTURE_TYPE Texture3D +#endif // TEXTURE_TYPE + +#endif // TEXTURE_ARRAY + +#if TEXTURE_FORMAT == FORMAT_INT +#define _TEXTURE_FORMAT +#elif TEXTURE_FORMAT == FORMAT_UINT +#define _TEXTURE_FORMAT +#else +#define _TEXTURE_FORMAT +#endif + +_TEXTURE_TYPE _TEXTURE_FORMAT Texture; + +float4 ColorTransform0; +float4 ColorTransform1; +float4 ColorTransform2; +float4 ColorTransform3; +float4 MinValue; +float4 MaxValue; +float UseForceMipMap; +float ForceMipMap; +float VolumeSlice = 0; +float ArraySlice = 0; + + +float4x4 ViewMat; + +SamplerState AutoMipMapTexture +{ + AddressU = Border; + AddressV = Border; + AddressW = Clamp; + BorderColor = 0; + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; +}; + +SamplerState NoMipMapTexture +{ + AddressU = Border; + AddressV = Border; + AddressW = Clamp; + BorderColor = 0; + MinFilter = Point; + MagFilter = Point; + MipFilter = Point; +}; + +struct BlitAppVertex +{ + float4 pos : POSITION; + float2 texCoord : TEXCOORD0; +}; + +struct BlitVertex +{ + float4 pos : SV_Position; + float2 texCoord : TEXCOORD0; +}; + + +BlitVertex BlitVS( BlitAppVertex inVtx ) +{ + BlitVertex outVtx = { inVtx.pos, inVtx.texCoord }; + return outVtx; +} + +float4 LinearToSRGB( float4 inColor ) +{ + float4 result; + result.r = inColor.r < 0.0031308 ? inColor.r * 12.92 : pow( saturate( inColor.r ), 1 / 2.4 ) * 1.055 - 0.055; + result.g = inColor.g < 0.0031308 ? inColor.g * 12.92 : pow( saturate( inColor.g ), 1 / 2.4 ) * 1.055 - 0.055; + result.b = inColor.b < 0.0031308 ? inColor.b * 12.92 : pow( saturate( inColor.b ), 1 / 2.4 ) * 1.055 - 0.055; + result.a = inColor.w; + return result; +} + +float4 SRGBToLinear( float4 inColor ) +{ + float4 result; + result.r = inColor.r < 0.040449936 ? inColor.r / 12.92 : pow( abs( ( inColor.r + 0.055 ) / 1.055 ), 2.4 ); + result.g = inColor.g < 0.040449936 ? inColor.g / 12.92 : pow( abs( ( inColor.g + 0.055 ) / 1.055 ), 2.4 ); + result.b = inColor.b < 0.040449936 ? inColor.b / 12.92 : pow( abs( ( inColor.b + 0.055 ) / 1.055 ), 2.4 ); + result.a = inColor.a < 0.040449936 ? inColor.a / 12.92 : pow( abs( ( inColor.a + 0.055 ) / 1.055 ), 2.4 ); + return result; +} + +float4 LogarithmicToLinearish( float4 inColor ) +{ + // Avert your eyes! Extreme HACK ahead! + return pow( abs( inColor ), float4( 0.25, 0.25, 0.25, 0.25 ) ); +} + + +#if TEXTURE_TYPE == TEXTURE_CUBE && TEXTURE_ARRAY == ARRAY_NO + +static const float3x3 POS_X_ROTATION = float3x3( 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 0.0 ); +static const float3x3 NEG_X_ROTATION = float3x3( 0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0 ); +static const float3x3 POS_Z_ROTATION = float3x3( -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0 ); +static const float3x3 NEG_Z_ROTATION = float3x3( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ); +static const float3x3 POS_Y_ROTATION = float3x3( -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -1.0, 0.0 ); +static const float3x3 NEG_Y_ROTATION = float3x3( -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0 ); +static const float3x3 ERROR_ROTATION = float3x3( 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ); + +float3 ConvertUvToViewDir( float2 uv ) +{ + float2 pos = uv * 2 - 1; + return float3( pos.x, -pos.y, -1 ); +} +// returns the correct 3d view direction +float3 GetView( float2 uv, float2 columnRow, float2 size, float3x3 rotation ) +{ + uv -= columnRow * size; + uv /= size; + // rotate the view direction + return mul( rotation, ConvertUvToViewDir( uv ) ); +} +// returns the column and row for a specific grid size +float2 GetColumnRow( float2 uv, float columns, float rows ) +{ + return float2( floor( uv.x * columns ), floor( uv.y * rows ) ); +} +// moves a float value to a new min max +float MoveValueToRange( float2 oldMinMax, float2 newMinMax, float value ) +{ + return newMinMax.x + ( newMinMax.y - newMinMax.x ) / ( oldMinMax.y - oldMinMax.x ) * ( value - oldMinMax.x ); +} +// returns a +float4 CubeColor( float2 uv ) +{ + float3 viewDir; +#if CUBE_RENDERING_MODE == CROSS + // up + // left forward right back + // down + float gutter = 1.0 / 8.0; + if( uv.y < gutter || uv.y > 1.0 - gutter ) + { + // gutter + return float4( 0, 0, 0, 0 ); + } + // rejig the uv to go from float2([0-1], [gutter - (1 - gutter)]) to float2([0-1], [0-1]) + float2 transformedUv = float2( uv.x, MoveValueToRange( float2( gutter, 1.0 - gutter ), float2( 0, 1 ), uv.y ) ); + float3x3 ROTATION_MATRICES[12] = + { + ERROR_ROTATION, POS_Y_ROTATION, ERROR_ROTATION, ERROR_ROTATION, + NEG_X_ROTATION, POS_Z_ROTATION, POS_X_ROTATION, NEG_Z_ROTATION, + ERROR_ROTATION, NEG_Y_ROTATION, ERROR_ROTATION, ERROR_ROTATION + }; + float2 gridSize = float2( 4.0, 3.0 ); + float2 columnRow = GetColumnRow( transformedUv, gridSize.x, gridSize.y ); + + float3x3 rotation = ROTATION_MATRICES[columnRow.x + 4 * columnRow.y]; + // check the dead zone + if( !any( mul( rotation, float3( 1, 1, 1 ) ) ) ) + { + return float4( 0, 0, 0, 0 ); + } + viewDir = GetView( transformedUv, columnRow, 1.0 / gridSize, rotation ); +#elif CUBE_RENDERING_MODE == ROWS + // recalculate the direction to show all rows + // left right up + // forward back down + float gutter = 1.0 / 6.0; + if(uv.y < gutter || uv.y > 1.0 - gutter ) + { + // gutter + return float4(0, 0, 0, 0); + } + + float3x3 ROTATION_MATRICES[6] = { + NEG_X_ROTATION, POS_X_ROTATION, POS_Y_ROTATION, + POS_Z_ROTATION, NEG_Z_ROTATION, NEG_Y_ROTATION + }; + // rejig the uv to go from float2([0-1], [gutter - (1 - gutter)]) to float2([0-1], [0-1]) + float2 transformedUv = float2(uv.x, MoveValueToRange(float2(gutter, 1.0 - gutter), float2(0, 1), uv.y)); + float2 gridSize = float2(3.0, 2.0); + float2 columnRow = GetColumnRow(transformedUv, gridSize.x, gridSize.y); + viewDir = GetView(transformedUv, columnRow, 1.0 / gridSize, ROTATION_MATRICES[columnRow.x + 3 * columnRow.y]); +#elif CUBE_RENDERING_MODE == FORWARD + // recalculate the direction to show only -z + viewDir = mul( POS_Z_ROTATION, ConvertUvToViewDir(uv) ); +#elif CUBE_RENDERING_MODE == BACKWARD + // recalculate the direction to show only z + viewDir = mul( NEG_Z_ROTATION, ConvertUvToViewDir(uv) ); + +#elif CUBE_RENDERING_MODE == RIGHT + // recalculate the direction to show only x + viewDir = mul( POS_X_ROTATION, ConvertUvToViewDir(uv) ); + +#elif CUBE_RENDERING_MODE == LEFT + // recalculate the direction to show only -x + viewDir = mul( NEG_X_ROTATION, ConvertUvToViewDir(uv) ); + +#elif CUBE_RENDERING_MODE == UP + // recalculate the direction to show only y + viewDir = mul( POS_Y_ROTATION, ConvertUvToViewDir(uv) ); + +#elif CUBE_RENDERING_MODE == DOWN + // recalculate the direction to show only -y + viewDir = mul( NEG_Y_ROTATION, ConvertUvToViewDir(uv) ); +#else // CUBE + viewDir = mul( ( float3x3 )ViewMat, ConvertUvToViewDir(uv) ); +#endif + return lerp( Texture.Sample( AutoMipMapTexture, viewDir ), Texture.SampleLevel( NoMipMapTexture, viewDir, ForceMipMap ), UseForceMipMap ); +} +#endif + + +float4 BlitPS( BlitVertex inVtx ) : COLOR +{ + float2 UV = inVtx.texCoord; // * Zoom.xy + Zoom.zw; + float4x4 ColorTransform = { ColorTransform0, ColorTransform1, ColorTransform2, ColorTransform3 }; + float4 color; + +#if TEXTURE_TYPE == TEXTURE_2D +#if TEXTURE_MSAA == MSAA_YES + uint w, h, d, s; +#if TEXTURE_ARRAY == ARRAY_NO + Texture.GetDimensions( w, h, d ); + color = float4( Texture.Load( UV * float2( w, h ), 0 ) ); +#else + Texture.GetDimensions( w, h, d, s ); + color = float4( Texture.Load( float3(UV * float2( w, h ), ArraySlice), 0 ) ); +#endif +#elif ( TEXTURE_FORMAT == FORMAT_INT || TEXTURE_FORMAT == FORMAT_UINT || TEXTURE_ARRAY == ARRAY_YES ) + uint w, h, d; + uint mip = UseForceMipMap ? ForceMipMap : 0; +#if TEXTURE_ARRAY == ARRAY_NO + Texture.GetDimensions( w, h ); + color = float4( Texture.Load( float3( UV * float2( w, h ), mip ) ) ); +#else + Texture.GetDimensions( w, h, d ); + color = float4( Texture.Load( float4( UV * float2( w, h ), ArraySlice, mip ) ) ); +#endif +#else + color = lerp( Texture.Sample( AutoMipMapTexture, UV ), Texture.SampleLevel( NoMipMapTexture, UV, ForceMipMap ), UseForceMipMap ); +#endif +#elif TEXTURE_TYPE == TEXTURE_3D +#if ( TEXTURE_FORMAT == FORMAT_INT || TEXTURE_FORMAT == FORMAT_UINT ) + uint w, h, d; + Texture.GetDimensions( w, h, d ); + uint mip = UseForceMipMap ? ForceMipMap : 0; + color = float4( Texture.Load( float4( UV * float2( w, h ), VolumeSlice * d, mip ) ) ); +#else + color = lerp( Texture.Sample( AutoMipMapTexture, float3( UV, VolumeSlice ) ), Texture.SampleLevel( NoMipMapTexture, float3( UV, VolumeSlice ), ForceMipMap ), UseForceMipMap ); +#endif +#else +#if ( TEXTURE_FORMAT == FORMAT_INT || TEXTURE_FORMAT == FORMAT_UINT || TEXTURE_ARRAY == ARRAY_YES ) + color = 0; +#else + color = CubeColor(inVtx.texCoord); +#endif +#endif + +#if COLOR_SPACE == SRGB_SPACE + color = SRGBToLinear( color ); +#elif COLOR_SPACE == LOGARITHMIC_SPACE + color = LogarithmicToLinearish( color ); +#endif + + color = mul( color, ColorTransform ); + color = ( color - MinValue ) / ( MaxValue - MinValue ); + color = LinearToSRGB( color ); + return float4( color.rgb, 1 ); +} + +technique Main +{ + pass P0 + { + AlphaBlendEnable = False; + + VertexShader = compile vs_3_0 BlitVS(); + PixelShader = compile ps_3_0 BlitPS(); + } +} diff --git a/shaders/include/Carbon/Bindless.fxh b/shaders/include/Carbon/Bindless.fxh new file mode 100644 index 000000000..04be0b958 --- /dev/null +++ b/shaders/include/Carbon/Bindless.fxh @@ -0,0 +1,97 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_BINDLESS_FXH +#define CARBON_BINDLESS_FXH + +#include "System.fxh" + +// Support for bindless rendering with Trinity. Difines types and functions to work with +// bindless resources and heap views in a platform-independent manner. + +// BINDLESS_RENDERING define controls whether bindless rendering is enabled. + +// Don't even try to use bindless rendering on platforms that don't support it (DirectX 11) +#if !PLATFORM_SUPPORTS_SUPPORTS_HEAP_VIEW +#define BINDLESS_RENDERING 0 +#endif + +#ifndef BINDLESS_RENDERING +#define BINDLESS_RENDERING 1 +#endif + +#if BINDLESS_RENDERING + +Texture2D HeapView_Texture2D[] +< + bool IsHeapView = true; +>; + +Texture3D HeapView_Texture3D[] +< + bool IsHeapView = true; +>; + +TextureCube HeapView_TextureCube[] +< + bool IsHeapView = true; +>; + +SamplerState HeapView_Sampler[] +< + bool IsHeapView = true; +>; + +#endif + +// Convenient shortcut to declaring bindless/class texture variables +#if BINDLESS_RENDERING +// CCP Shader compiler defines custom types BindlessHandleTexture... that map to uint on DX12 and Metal +#define BindlessTexture2D BindlessHandleTexture2D +#define BindlessTexture3D BindlessHandleTexture3D +#define BindlessTextureCube BindlessHandleTextureCube +#define BindlessSampler BindlessHandleSampler +#else +#define BindlessTexture2D Texture2D +#define BindlessTexture3D Texture3D +#define BindlessTextureCube TextureCube +#define BindlessSampler SamplerState +#endif + +Texture2D Deref2D( BindlessTexture2D tex ) +{ +#if BINDLESS_RENDERING + return HeapView_Texture2D[tex]; +#else + return tex; +#endif +} + +Texture3D Deref3D( BindlessTexture3D tex ) +{ +#if BINDLESS_RENDERING + return HeapView_Texture3D[tex]; +#else + return tex; +#endif +} + +TextureCube DerefCube( BindlessTextureCube tex ) +{ +#if BINDLESS_RENDERING + return HeapView_TextureCube[tex]; +#else + return tex; +#endif +} + +SamplerState DerefSampler( BindlessSampler sampl ) +{ +#if BINDLESS_RENDERING + return HeapView_Sampler[sampl]; +#else + return sampl; +#endif +} + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/BoneTransforms.fxh b/shaders/include/Carbon/BoneTransforms.fxh new file mode 100644 index 000000000..7a948441f --- /dev/null +++ b/shaders/include/Carbon/BoneTransforms.fxh @@ -0,0 +1,21 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_BONETRANSFORMS_FXH_ +#define CARBON_BONETRANSFORMS_FXH_ + +// Contains bone transforms for skeletal animation for all objects in the render pass. Each bone transform is a +// 4x3 matrix (3 rows of 4 floats) that represents the transformation of a bone in the skeleton. +StructuredBuffer BoneTransforms ; + +float4x4 GetBoneTransform( int boneIndex ) +{ + float4x3 bone = BoneTransforms[boneIndex]; + + float4x4 boneMatrix = float4x4( float4( bone[0], 0.0 ), + float4( bone[1], 0.0 ), + float4( bone[2], 0.0 ), + float4( bone[3], 1.0 ) ); + return boneMatrix; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/InteriorScene/PerObjectData.fxh b/shaders/include/Carbon/InteriorScene/PerObjectData.fxh new file mode 100644 index 000000000..e17020513 --- /dev/null +++ b/shaders/include/Carbon/InteriorScene/PerObjectData.fxh @@ -0,0 +1,59 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_INTERIOR_PEROBJECTDATA_FXH +#define CARBON_INTERIOR_PEROBJECTDATA_FXH + +#include "../System.fxh" + +// Constant buffer format for per-object data for interior scene objects. This data is provided by the engine to the vertex shader stage. +struct InteriorObjectDataVS +{ + // World transform of this object + float4x4 WorldMat; + float4 boundingBoxData; +}; + +struct InteriorSkinnedObjectDataVS +{ + // Set this up as a column-major 4x3 matrix, which is like a + // row-major 3x4. This allows us to use the same mul syntax + // as we normally do, but still allow packing of a 3x4 into + // 3 registers. + column_major float4x3 JointMat[69]; + float4x4 WorldMat; + float3 WorldPos; +}; + + +// interior +struct InteriorObjectLightData +{ + float4 position; // position in [x,y,z], radius in [w] + float4 color; // color in [r,g,b], falloff in [a] + float4 exData; // additional data: [x] = shadow0 influence, [y] = shadow1 influence, [z] = cos of spotlight outer cone alpha, [w] = cos of spotlight inner cone alpha + // or box light's world to box transform row 1 for box lights + float4 spotDirection; // spotlight's direction: [x,y,z} = normalized direction + // or box light's world to box transform row 2 for box lights + float4 boxTransformRow3; // box light's world to box transform row 3 + float4 boxTransformRow4; // box light's world to box transform row 4 +}; + +#define NUM_INTERIORLIGHTS 10 + +// Constant buffer format for per-object data for interior scene objects. This data is provided by the engine to the pixel shader stage. +struct InteriorObjectDataPS +{ + int4 pointLightsCount; // yzw - unused + + // data on lights + InteriorObjectLightData lights[NUM_INTERIORLIGHTS]; + // data on the shadow casting point lights: [x,y,z] = position, [w] = radius + float4 shadowCaster0; + float4 shadowCaster1; + // spotlight view projection matrices + float4x4 spotLights[4]; +}; + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/InteriorScene/PerSceneData.fxh b/shaders/include/Carbon/InteriorScene/PerSceneData.fxh new file mode 100644 index 000000000..a5452c3a1 --- /dev/null +++ b/shaders/include/Carbon/InteriorScene/PerSceneData.fxh @@ -0,0 +1,39 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_INTERIOR_PERSCENEDATA_FXH +#define CARBON_INTERIOR_PERSCENEDATA_FXH + + +#include "../System.fxh" + + +// Per-scene (per-frame) constant buffer data for the vertex shader. +struct InteriorSceneDataVS +{ + float4x4 ViewInverseTransposeMat; + float4 sunDirWorld; + float4 sceneFogColor; + + float4x4 ViewProjectionMat; + float4x4 ViewMat; + float4x4 ProjectionMat; +}; + +// Per-scene (per-frame) constant buffer data for the pixel shader. +struct InteriorSceneDataPS +{ + float4x4 ViewInverseTransposeMat; + float4 sceneAmbientColor; + float4 sceneFogColor; + float4 sunDirWorld; + float4 sunDiffuseColor; + float4 sunSpecularColor; + float4 fogValues; // x = maxFogAmount, y = maxFogDistance, z = minFogDistance, w unused + float4x4 ViewProjectionMat; + float4 misc; // x = Global spherical harmonic scale factor, y = shadow count, z = inv.shadow size, w = radius + float4 viewPort; // xy - viewport width/height, zw - viewport offset + float4x4 ViewProjInverse; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/Intersections.fxh b/shaders/include/Carbon/Intersections.fxh new file mode 100644 index 000000000..151d02196 --- /dev/null +++ b/shaders/include/Carbon/Intersections.fxh @@ -0,0 +1,421 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_INTERSECTIONS_FXH +#define CARBON_INTERSECTIONS_FXH + + + +struct Ray +{ + float3 origin; + float3 dir; +}; + +Ray InitRay(float3 o, float3 d) +{ + Ray r; + r.origin = o; + r.dir = d; + return r; +} + + +struct Plane +{ + float3 normal; + float3 planePoint; +}; + +Plane InitPlane(float3 normal, float3 planePoint) +{ + Plane p; + p.normal = normal; + p.planePoint = planePoint; + return p; +} + +float GetDistanceToPlane( Plane plane, Ray ray ) +{ + float denom = dot( ray.dir, plane.normal ); + float denom2 = dot( ray.dir, -plane.normal ); + float d = max( denom, denom2 ); + if( d == denom2 ) + { + plane.normal = -plane.normal; + } + + const float epsilon = 0.0000001; + if( d >= epsilon ) + { + float3 rayToPlanePoint = plane.planePoint - ray.origin; + + return dot( rayToPlanePoint, plane.normal ) / d; + } + return -1; +} + +bool PointOnPlane( Ray ray, float t, float3 planePoint, float radius ) +{ + if( t < 0 ) + { + return false; + } + float3 pointInPlaneSpace = ray.origin + t * ray.dir; + float3 dist = planePoint - pointInPlaneSpace; + float r = radius; + + return dot( dist, dist ) - r * r <= 0; +} + +float GetDistanceToCircle(float3 normal, float3 planePoint, float radius, Ray ray) +{ + float t = GetDistanceToPlane( InitPlane( normal, planePoint ), ray); + if( PointOnPlane( ray, t, planePoint, radius ) ) + { + return t; + } + return -1; +} + +// ------------------------------------------------------------------------------- +// Sphere +// ------------------------------------------------------------------------------- +struct Sphere +{ + float3 center; + float radius; +}; + +Sphere InitSphere( float3 center, float radius ) +{ + Sphere s; + s.center = center; + s.radius = radius; + return s; +} + +struct SphereHitInfo +{ + bool occurred; + float4 firstIntersection; // xyz - position, w - distance from eye + float4 secondIntersection; // xyz - position, w - distance from eye +}; + +SphereHitInfo SphereRayHitTest( Sphere sphere, Ray ray ) +{ + SphereHitInfo info; + info.occurred = false; + info.firstIntersection = float4(0, 0, 0, 0); + info.secondIntersection = float4(0, 0, 0, 0); + + // algorithm taken from Real Time Rendering fourth edition, page 958 + float3 originToSphereCenter = sphere.center - ray.origin; + float s = dot( originToSphereCenter, ray.dir ); + float originToSphereCenterLengthSq = dot(originToSphereCenter, originToSphereCenter); + float radSq = sphere.radius * sphere.radius; + + if( s < 0 && originToSphereCenterLengthSq > radSq ) + { + return info; + } + + float mSq = originToSphereCenterLengthSq - s*s; + if( mSq > radSq ) + { + return info; + } + + float intersectionPerpendicularToCenter = sqrt( radSq - mSq ); + float minT = max( 0.0, min( s - intersectionPerpendicularToCenter, s + intersectionPerpendicularToCenter ) ); + float maxT = max( s - intersectionPerpendicularToCenter, s + intersectionPerpendicularToCenter ); + + info.firstIntersection = float4( ray.origin + ray.dir * minT - sphere.center, minT ); + info.secondIntersection = float4( ray.origin + ray.dir * maxT - sphere.center, maxT ); + info.occurred = true; + + return info; +} + + +// ------------------------------------------------------------------------------- +// Cones +// ------------------------------------------------------------------------------- +struct Cone +{ + float height; + float3 tip; + float cosa; // half cone angle + // float tipRadius; + float3 axis; + float baseRadius; + float tipOffset; +}; + +struct ConeHitInfo +{ + float4 firstIntersection; // xyz - position, w - distance from eye + float4 secondIntersection; // xyz - position, w - distance from eye + float4 hits; // frontCone, backCone, base, tip + float shadowConeHit; + bool occurred; + float4 tmp; +}; + +bool PointInCone( Ray ray, float t, Cone cone ) +{ + if( t < 0 ) + { + return false; + } + + float3 pointInConeSpace = ray.origin + t * ray.dir - cone.tip; + + float cone_dist = dot( pointInConeSpace, cone.axis ); + const float epsilon = 0.0000001; + + if( cone.tipOffset - cone_dist > 0 || cone_dist > cone.height + epsilon ) + { + return false; + } + + float cone_radius_at_dist = ( cone_dist / cone.height ) * cone.baseRadius; + float3 orth_point = pointInConeSpace - cone_dist * cone.axis; + float orth_distance_sq = dot(orth_point, orth_point); + return cone.baseRadius * cone.baseRadius - orth_distance_sq >= 0; +} + +bool GetConeIntersectionDistances(Cone cone, Ray ray, out float t1, out float t2, out float baseT, out float tipT ) +{ + t1 = -1; + t2 = -1; + baseT = -1; + tipT = -1; + + baseT = GetDistanceToCircle(cone.axis, cone.tip + cone.axis * cone.height, cone.baseRadius, ray); + + float tipOffsetRadius = cone.tipOffset / cone.cosa; + tipOffsetRadius = sqrt(tipOffsetRadius * tipOffsetRadius - cone.tipOffset * cone.tipOffset); + tipT = GetDistanceToCircle(cone.axis, cone.tip + cone.axis * cone.tipOffset, tipOffsetRadius, ray); + + // code comes from here http://lousodrome.net/blog/light/2017/01/03/intersection-of-a-ray-and-a-cone/ + float3 co = ray.origin - cone.tip; + + float dirAxisDot = dot(ray.dir, cone.axis); + float coAxisDot = dot(co, cone.axis); + + float a = dirAxisDot * dirAxisDot - cone.cosa * cone.cosa; + float b = 2. * (dirAxisDot * coAxisDot - dot(ray.dir, co) * cone.cosa * cone.cosa); + float c = coAxisDot * coAxisDot - dot(co, co) * cone.cosa * cone.cosa; + + float det = b * b - 4. * a * c; + + if( det < 0 ) + { + if( baseT < 0 && tipT < 0 ) + { + return false; + } + return true; + } + + det = sqrt(abs(det)); + + // if t1 is < 0 then we are will never hit the cone i.e the ray will never intersect + t1 = ( -b - det ) / ( 2. * a ); + // if t2 is < 0 then we are looking down the axis of the cone (in either way) + t2 = ( -b + det ) / ( 2. * a ); + + return true; +} + +// Sorts a vector4 in ascending order (x, y, z, w) +float4 sort(float4 values) +{ + float minv, maxv; + + // move the maximum value to w + minv = min(values.x, values.y); + maxv = max(values.x, values.y); + values.x = minv; + values.y = maxv; + + minv = min(values.y, values.z); + maxv = max(values.y, values.z); + values.y = minv; + values.z = maxv; + + minv = min(values.z, values.w); + maxv = max(values.z, values.w); + values.z = minv; + values.w = maxv; + // now w has the highest value + + // now we can sort xyz easily + minv = min(values.x, min(values.y, values.z)); + maxv = max(values.x, max(values.y, values.z)); + float midv = values.x + values.y + values.z - minv - maxv; + values.x = minv; + values.y = midv; + values.z = maxv; + + // now values are sorted + return values; +} + +ConeHitInfo ConeFlatBaseHitTest( Cone cone, Ray ray ) +{ + ConeHitInfo hitInfo; + hitInfo.occurred = false; + hitInfo.firstIntersection = float4(0, 0, 0, 0); + hitInfo.secondIntersection = float4(0, 0, 0, 0); + hitInfo.hits = float4(0, 0, 0, 0); + hitInfo.tmp = float4(0, 0, 0, 0); + hitInfo.shadowConeHit = 0; + + float coneT1, coneT2, baseT, tipT = -1; + if( !GetConeIntersectionDistances(cone, ray, coneT1, coneT2, baseT, tipT) ) + { + return hitInfo; + } + + bool frontConeHit = PointInCone( ray, coneT2, cone ); + bool backConeHit = PointInCone( ray, coneT1, cone ); + bool rayOriginConeHit = PointInCone( ray, 0, cone ); + bool baseConeHit = baseT > -1; + bool tipConeHit = tipT > -1; + + hitInfo.hits = float4(frontConeHit, backConeHit, baseConeHit, tipConeHit); + hitInfo.shadowConeHit = 1; + + if( !any( hitInfo.hits.xyzw ) ) + { + return hitInfo; + } + + // for the "distances" that don't create a hit, put in invalid values + float4 modifiedT = float4(frontConeHit ? coneT2 : -1, + backConeHit ? coneT1 : -1, + tipConeHit ? tipT : -1, + baseConeHit ? baseT : -1); + + float4 sortedModifiedT = sort(modifiedT); + float2 tToUse; + + if( rayOriginConeHit ) + { + tToUse.x = 0; + tToUse.y = sortedModifiedT.w; + } + else + { + tToUse.x = sortedModifiedT.z >= 0 ? sortedModifiedT.z: sortedModifiedT.w; + tToUse.y = sortedModifiedT.w; + } + + hitInfo.occurred = true; + hitInfo.firstIntersection = float4(ray.origin + ray.dir * tToUse.x - cone.tip, tToUse.x); + hitInfo.secondIntersection = float4(ray.origin + ray.dir * tToUse.y - cone.tip, tToUse.y); + + return hitInfo; +} + +ConeHitInfo ConeRoundBaseHitTest( Cone cone, Ray ray ) +{ + ConeHitInfo hitInfo; + hitInfo.occurred = false; + hitInfo.firstIntersection = float4(0, 0, 0, 0); + hitInfo.secondIntersection = float4(0, 0, 0, 0); + hitInfo.hits = float4(0, 0, 0, 0); + hitInfo.shadowConeHit = false; + hitInfo.tmp = false; + + float coneT1, coneT2, tmp, tipT = -1; + // are we intersecting the cone (endless and in both directions...) + if( !GetConeIntersectionDistances(cone, ray, coneT1, coneT2, tmp, tipT ) ) + { + return hitInfo; + } + + hitInfo.shadowConeHit = true; + + // first do the sphereintersection since that is cheaper + Sphere s; + s.radius = cone.height; + s.center = cone.tip; + + SphereHitInfo si = SphereRayHitTest(s, ray); + + // Early exit, if no spherical intersection occurred + // either of the intersection points are not within the cone radius + if( !si.occurred ) + { + return hitInfo; + } + + float firstIntersectionDot = dot(normalize(si.firstIntersection.xyz), cone.axis); + float secondIntersectionDot = dot(normalize(si.secondIntersection.xyz), cone.axis); + + bool firstBaseHit = firstIntersectionDot - cone.cosa >= 0; + bool secondBaseHit = secondIntersectionDot - cone.cosa >= 0; + + float baseT1 = si.firstIntersection.w; + float baseT2 = si.secondIntersection.w; + + // modify the cone so it fits nicely to the sphere + cone.height *= cone.cosa; + bool frontConeHit = PointInCone( ray, coneT1, cone ); + bool backConeHit = PointInCone( ray, coneT2, cone ); + bool rayOriginConeHit = PointInCone( ray, 0, cone ); + + float tipOffsetRadius = cone.tipOffset / cone.cosa; + tipOffsetRadius = sqrt(tipOffsetRadius * tipOffsetRadius - cone.tipOffset - cone.tipOffset); + bool tipConeHit = PointOnPlane( ray, tipT, cone.tip + cone.axis * cone.tipOffset, tipOffsetRadius); + + float4 coneIntersections = float4(frontConeHit, backConeHit, rayOriginConeHit, tipConeHit); + float2 sphereIntersections = float2(firstBaseHit, secondBaseHit); + hitInfo.hits = float4(frontConeHit, backConeHit, (any(coneIntersections) && any(sphereIntersections)) || all(sphereIntersections), tipConeHit ); + + // if we don't hit the cone or have any cone and sphere intersections or don't hit the sphere, we can return + if( !any(hitInfo.hits.xyzw) ) + { + return hitInfo; + } + + float2 tToUse; + // figure out the t-s to use + // we have 6 ts to pick from: + // - front and back cone intersection + // - front and back sphere + // - tip + // - 0 (the camera) + + // there are 2 scenarios though + // if the camera is intersecting with the cone, then we need the next t (or min(coneT1, coneT2, sphereT1, sphereT2)) + // otherwise we need the middle values of (coneT1, coneT2, sphereT1, sphereT2) + float4 ts = sort(float4( + hitInfo.hits.x ? coneT1 : -1, + hitInfo.hits.y ? coneT2 : -1, + (any(coneIntersections) && sphereIntersections.x) || all(sphereIntersections)? baseT1 : -1, + (any(coneIntersections) && sphereIntersections.y) || all(sphereIntersections)? baseT2 : -1 + ) + ); + + if( rayOriginConeHit ) + { + tToUse.x = 0; + tToUse.y = max(tipT, ts.w); + } + else + { + tToUse.x = ts.z >= 0 ? ts.z: ts.w; + tToUse.y = ts.w; + } + + hitInfo.occurred = true; + hitInfo.firstIntersection = float4(ray.origin + ray.dir * tToUse.x - cone.tip, tToUse.x); + hitInfo.secondIntersection = float4(ray.origin + ray.dir * tToUse.y - cone.tip, tToUse.y); + + return hitInfo; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/Math.fxh b/shaders/include/Carbon/Math.fxh new file mode 100644 index 000000000..1f2cda8d4 --- /dev/null +++ b/shaders/include/Carbon/Math.fxh @@ -0,0 +1,359 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_MATH_FXH +#define CARBON_MATH_FXH + +static const float PI = 3.1415926535897932384626433832795; +static const float _2PI = 6.283185307179586476925286766559; +static const float4x4 IDENTITY_MATRIX = float4x4(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0); + + +// ---------------------------------------------------------------------------------------------- +// extract the eye-pos in world-space from the view matrix +// ---------------------------------------------------------------------------------------------- +float3 ExtractEyePosFromViewMatrix(float4x4 mat) +{ + return float3(mat[0].w, mat[1].w, mat[2].w); +} + +// ---------------------------------------------------------------------------------------------- +// extract the eye-dir in world-space from the view matrix +// ---------------------------------------------------------------------------------------------- +float3 ExtractEyeDirFromViewMatrix(float4x4 mat) +{ + return float3(mat[0].z, mat[1].z, mat[2].z); +} + +// ---------------------------------------------------------------------------------------------- +// extract z_near from projection matrix +// ---------------------------------------------------------------------------------------------- +float ExtractZnearFromProjectionMatrix(float4x4 mat) +{ + return mat[3][2] / mat[2][2]; +} + +// ---------------------------------------------------------------------------------------------- +// extract z_far from projection matrix +// ---------------------------------------------------------------------------------------------- +float ExtractZfarFromProjectionMatrix(float4x4 mat) +{ + return mat[3][2] / (mat[2][2] + 1.0); +} + +// ---------------------------------------------------------------------------------------------- +// extract FovY from projection matrix +// ---------------------------------------------------------------------------------------------- +float ExtractFovYFromProjectionMatrix(float4x4 mat) +{ + return 2.0 * atan(1.0 / mat[1][1]); +} + +// ---------------------------------------------------------------------------------------------- +// compute a simple rotation matrix from a given vector and angle +// ---------------------------------------------------------------------------------------------- +float3x3 GetRotationMatrixAxisAngle(float3 axis, float angle) +{ + float3x3 m; + + // pre + float oneMinusCosA = 1.0 - cos(angle); + float sinA = sin(angle); + + // matrix + m[0][0] = 1.0 + oneMinusCosA * (axis.x * axis.x - 1.0); + m[1][0] = -axis.z * sinA + oneMinusCosA * axis.x * axis.y; + m[2][0] = axis.y * sinA + oneMinusCosA * axis.x * axis.z; + + m[0][1] = axis.z * sinA + oneMinusCosA * axis.x * axis.y; + m[1][1] = 1.0 + oneMinusCosA * (axis.y * axis.y - 1.0); + m[2][1] = -axis.x * sinA + oneMinusCosA * axis.y * axis.z; + + m[0][2] = -axis.y * sinA + oneMinusCosA * axis.x * axis.z; + m[1][2] = axis.x * sinA + oneMinusCosA * axis.y * axis.z; + m[2][2] = 1.0 + oneMinusCosA * (axis.z * axis.z - 1.0); + + return m; +} + +// ---------------------------------------------------------------------------------------------- +// compute a rotation matrix which rotates axis1 into axis2 +// ---------------------------------------------------------------------------------------------- +float3x3 GetRotationMatrixNrmToNrm(float3 normal1, float3 normal2) +{ + float3 rotAxis = normalize(cross(normal1, normal2)); + float rotAngle = acos(dot(normal1, normal2)); + return GetRotationMatrixAxisAngle(rotAxis, rotAngle); +} + +// ---------------------------------------------------------------------------------------------- +// Create a transformation matrix from a quaternion of the form x, y, z, w +// ---------------------------------------------------------------------------------------------- +float4x4 CreateRotationMatrixFromQuaterion(float4 q) +{ + float tx2 = 2 * q.x * q.x; + float ty2 = 2 * q.y * q.y; + float tz2 = 2 * q.z * q.z; + + float txy = 2 * q.x * q.y; + float txz = 2 * q.x * q.z; + float txw = 2 * q.x * q.w; + float tyz = 2 * q.y * q.z; + float tyw = 2 * q.y * q.w; + float tzw = 2 * q.z * q.w; + + float4x4 m3 = + { + { 1 - ty2 - tz2, txy + tzw, txz - tyw, 0 }, + { txy - tzw, 1 - tx2 - tz2, tyz + txw, 0 }, + { txz + tyw, tyz - txw, 1 - tx2 - ty2, 0 }, + { 0, 0, 0, 1 } + }; + return m3; +} + +// ---------------------------------------------------------------------------------------------- +// combine a 3x3 rotation matrix and a float3 position to a 4x4 transform +// ---------------------------------------------------------------------------------------------- +float4x4 CreateTransformMatrix(float3x3 rotationMatrix, float3 translation) +{ + float4x4 m; + + m[0] = float4(rotationMatrix[0], 0.0); + m[1] = float4(rotationMatrix[1], 0.0); + m[2] = float4(rotationMatrix[2], 0.0); + m[3] = float4(translation, 1.0); + + return m; +} + +// ---------------------------------------------------------------------------------------------- +// combine a rotation quaternion and a float4 position to a 4x4 transform +// ---------------------------------------------------------------------------------------------- +float4x4 CreateTransformMatrix(float4 rotationQuaternion, float4 translation) +{ + float4x4 m = CreateRotationMatrixFromQuaterion(rotationQuaternion); + m[3] = translation; + + return m; +} + +// ---------------------------------------------------------------------------------------------- +// extract translation from transform matrix +// ---------------------------------------------------------------------------------------------- +float3 ExtractTranslationFromTransformMatrix(float4x4 mat) +{ + return mat[3].xyz; +} + +// ---------------------------------------------------------------------------------------------- +// extract rotation from transform matrix +// ---------------------------------------------------------------------------------------------- +float3x3 ExtractRotationFromTransformMatrix(float4x4 mat) +{ + float3x3 m; + + m[0] = mat[0].xyz; + m[1] = mat[1].xyz; + m[2] = mat[2].xyz; + return m; +} + +// ---------------------------------------------------------------------------------------------- +// remove translation from a transform matrix +// ---------------------------------------------------------------------------------------------- +float4x4 RemoveTranslationFromMatrix(float4x4 transformMatrix) +{ + float4x4 m = transformMatrix; + m[3] = float4(0.0, 0.0, 0.0, 1.0); + + return m; +} + +// ---------------------------------------------------------------------------------------------- +// create an identity matrix +// ---------------------------------------------------------------------------------------------- +float4x4 CreateIdentityMatrix() +{ + float4x4 m; + + m[0] = float4(1.0, 0.0, 0.0, 0.0); + m[1] = float4(0.0, 1.0, 0.0, 0.0); + m[2] = float4(0.0, 0.0, 1.0, 0.0); + m[3] = float4(0.0, 0.0, 0.0, 1.0); + + return m; +} + +// ---------------------------------------------------------------------------------------------- +// create a non-uniform scaling matrix +// ---------------------------------------------------------------------------------------------- +float4x4 CreateScalingMatrix(float3 s) +{ + float4x4 m; + + m[0] = float4(s.x, 0.0, 0.0, 0.0); + m[1] = float4(0.0, s.y, 0.0, 0.0); + m[2] = float4(0.0, 0.0, s.z, 0.0); + m[3] = float4(0.0, 0.0, 0.0, 1.0); + + return m; +} + +// ---------------------------------------------------------------------------------------------- +// compute a perpendicular vector to a given base vector: is any vector, only perpendicular +// ---------------------------------------------------------------------------------------------- +float3 GetVectorPerpendicular(float3 basis) +{ + // somehow this simple problem of constructing ANY perpendicular to a given vector + // is unsolvable.... at least in a nice, non-hacky way + float3 v = basis.yzx; + return normalize(cross(v, basis)); +} + +// ---------------------------------------------------------------------------------------------- +// "re-linearize" v from min to max +// ---------------------------------------------------------------------------------------------- +float LinStep(float min, float max, float v) +{ + return clamp((v - min) / (max - min), 0.0, 1.0); +} + +// ---------------------------------------------------------------------------------------------- +// remap a value from input range to an output range +// ---------------------------------------------------------------------------------------------- +float RemapFloatRange(float x, float inMin, float inMax, float outMin = 0.0f, float outMax = 1.0f) +{ + return outMin + ((x - inMin) / (inMax - inMin)) * (outMax - outMin); +} + +// ---------------------------------------------------------------------------------------------- +// Hermite spline position +// ---------------------------------------------------------------------------------------------- +float3 HermiteSplinePos(float3 v1, float3 t1, float3 v2, float3 t2, float s) +{ + // spline parameters + float3 a = 2.0 * v1 - 2.0 * v2 + t2 + t1; + float3 b = 3.0 * v2 - 3.0 * v1 - 2.0 * t1 - t2; + float3 c = t1; + float3 d = v1; + // cubic equation + return a * s * s * s + b * s * s + c * s + d; +} + +// ---------------------------------------------------------------------------------------------- +// Hermite spline direction (derive!) +// ---------------------------------------------------------------------------------------------- +float3 HermiteSplineDir(float3 v1, float3 t1, float3 v2, float3 t2, float s) +{ + // spline parameters + float3 a = 2.0 * v1 - 2.0 * v2 + t2 + t1; + float3 b = 3.0 * v2 - 3.0 * v1 - 2.0 * t1 - t2; + float3 c = t1; + // cubic equation becomes 2-dim cause of derive + return 3.0 * a * s * s + 2.0 * b * s + c; +} + +// ---------------------------------------------------------------------------------------------- +// Convert cartesian direction vector to spherical +// ---------------------------------------------------------------------------------------------- +float2 CartesianToSpherical(float3 dir) +{ + // x = sin( theta ) * sin( phi ) + // y = cos( phi ) + // z = cos( theta ) * sin( phi ) + float phi = acos(dir.y); + float theta = PI + atan2(dir.z, dir.x); + return float2(theta, phi); +} + +// ---------------------------------------------------------------------------------------------- +// Convert spherical coords into cartesian vector +// ---------------------------------------------------------------------------------------------- +float3 SphericalToCartesian(float2 spherical) +{ + float z = -sin(spherical.x) * sin(spherical.y); + float y = cos(spherical.y); + float x = -cos(spherical.x) * sin(spherical.y); + return float3(x, y, z); +} + + +float4 Vec4Cross(float4 v1, float4 v2, float4 v3) +{ + float4 result; + result.x = v1.y * (v2.z * v3.w - v3.z * v2.w) - v1.z * (v2.y * v3.w - v3.y * v2.w) + v1.w * (v2.y * v3.z - v2.z * v3.y); + result.y = -(v1.x * (v2.z * v3.w - v3.z * v2.w) - v1.z * (v2.x * v3.w - v3.x * v2.w) + v1.w * (v2.x * v3.z - v3.x * v2.z)); + result.z = v1.x * (v2.y * v3.w - v3.y * v2.w) - v1.y * (v2.x * v3.w - v3.x * v2.w) + v1.w * (v2.x * v3.y - v3.x * v2.y); + result.w = -(v1.x * (v2.y * v3.z - v3.y * v2.z) - v1.y * (v2.x * v3.z - v3.x * v2.z) + v1.z * (v2.x * v3.y - v3.x * v2.y)); + return result; +} + +float4x4 MatrixInverse(float4x4 m) +{ + float4 v, vec[3]; + float d; + + d = determinant(m); + if (!d) + { + return m; + } + float4x4 result; + [unroll] + for (int i = 0; i < 4; i++) + { + float signedDet = (i % 2 == 1) ? -1.f : 1.f; + signedDet /= d; + for (int j = 0; j < 4; j++) + { + if (j != i) + { + int a = j; + if (j > i) + { + a = a - 1; + } + vec[a].x = m[j][0]; + vec[a].y = m[j][1]; + vec[a].z = m[j][2]; + vec[a].w = m[j][3]; + } + } + v = Vec4Cross(vec[0], vec[1], vec[2]); + result[0][i] = signedDet * v.x; + result[1][i] = signedDet * v.y; + result[2][i] = signedDet * v.z; + result[3][i] = signedDet * v.w; + } + return result; +} + + +float4 QuaternionRotationYawPitchRoll( float yaw, float pitch, float roll ) +{ + float sinYaw = sin( yaw / 2.0f ); + float cosYaw = cos( yaw / 2.0f ); + float sinPitch = sin( pitch / 2.0f ); + float cosPitch = cos( pitch / 2.0f ); + float sinRoll = sin( roll / 2.0f ); + float cosRoll = cos( roll / 2.0f ); + + float4 result; + result.x = sinYaw * cosPitch * sinRoll + cosYaw * sinPitch * cosRoll; + result.y = sinYaw * cosPitch * cosRoll - cosYaw * sinPitch * sinRoll; + result.z = cosYaw * cosPitch * sinRoll - sinYaw * sinPitch * cosRoll; + result.w = cosYaw * cosPitch * cosRoll + sinYaw * sinPitch * sinRoll; + return result; +} + +float4 QuaternionMultiply( float4 q1, float4 q2 ) +{ + float4 result; + result.x = q2.w * q1.x + q2.x * q1.w + q2.y * q1.z - q2.z * q1.y; + result.y = q2.w * q1.y - q2.x * q1.z + q2.y * q1.w + q2.z * q1.x; + result.z = q2.w * q1.z + q2.x * q1.y - q2.y * q1.x + q2.z * q1.w; + result.w = q2.w * q1.w - q2.x * q1.x - q2.y * q1.y - q2.z * q1.z; + return result; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/PackedTangents.fxh b/shaders/include/Carbon/PackedTangents.fxh new file mode 100644 index 000000000..745b88ee8 --- /dev/null +++ b/shaders/include/Carbon/PackedTangents.fxh @@ -0,0 +1,81 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_PACKEDTANGENTS_FXH +#define CARBON_PACKEDTANGENTS_FXH + + +struct TangentSpace +{ + float3 tangent; + float3 bitangent; + float3 normal; +}; + +// Support for fma insrinsic varies for dx11 +float _Fma( float a, float b, float c ) +{ + return a * b + c; +} + +// Unpacks a tangent space using quaternion representation corresponding to +// cmf::Usage::PackedTangent in carbon-mesh. +TangentSpace UnpackTangentSpace( float4 t ) +{ + // Heavily optimized shader code that constructs the TBN matrix. + + // Extract the xyz components and square them + float x = t.x; + float y = t.y; + float z = t.z; + float x2 = x * x; + float y2 = y * y; + float z2 = z * z; + + // Optimized fma() chain to reconstruct W = sqrt(1 - x2 - y2 - z2) + // Don't use the above x2, y2 and z2 values, to reduce pipeline dependencies. + float w2 = saturate( _Fma( z, -z, _Fma( y, -y, _Fma( x, -x, 1.0f ) ) ) ); + float w = sqrt( w2 ); + + // Calculate shared values. + // These multiplications by 2.0f are free on some GPUs. + float xy = x * y * 2.0f; + float xz = x * z * 2.0f; + float yz = y * z * 2.0f; + float xw = x * w * 2.0f; + float yw = y * w * 2.0f; + float zw = z * w * 2.0f; + + // Compute the three vectors. + TangentSpace space; + + space.tangent = float3( _Fma( -2.0f, y2, _Fma( -2.0f, z2, 1.0f ) ), + xy + zw, + xz - yw ); + space.bitangent = float3( - zw + xy, _Fma( -2.0f, x2, _Fma( -2.0f, z2, 1.0f ) ), + yz + xw ); + space.normal = float3( + yw + xz, + yz - xw, _Fma( -2.0f, x2, _Fma( -2.0f, y2, 1.0f ) ) ) * t.w; // packed normal sign multiplication + + return space; +} + + +// Unpacks a tangent space using angle representation corresponding to +// cmf::Usage::PackedTangentLegacy in carbon-mesh. +TangentSpace UnpackTangentSpaceLegacy( float4 tangents ) +{ + float pi = 3.1415926535897932384626433832795; + + float4 angles = tangents * 2.0 * pi - pi; + float4 sc0, sc1; + sincos( angles.x, sc0.x, sc0.y ); + sincos( angles.y, sc0.z, sc0.w ); + sincos( angles.z, sc1.x, sc1.y ); + sincos( angles.w, sc1.z, sc1.w ); + TangentSpace space; + space.tangent = float3( sc0.y * abs( sc0.z ), sc0.x * abs( sc0.z ), sc0.w ); + space.bitangent = float3( sc1.y * abs( sc1.z ), sc1.x * abs( sc1.z ), sc1.w ); + space.normal = -cross( space.tangent, space.bitangent ); + space.normal = all( angles.yw > 0.0 ) ? -space.normal : space.normal; + return space; +} + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/ParticleAnnotations.fxh b/shaders/include/Carbon/ParticleAnnotations.fxh new file mode 100644 index 000000000..679ec6242 --- /dev/null +++ b/shaders/include/Carbon/ParticleAnnotations.fxh @@ -0,0 +1,37 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_PARTICLEANNOTATIONS_FXH +#define CARBON_PARTICLEANNOTATIONS_FXH + +// This file contains definitions for particle system shader annotations. +// These annotations are used to provide metadata and hints to the editor. +// These annotations should be specified for the particle system vertex shader, and +// they describe the particle attributes used by the shader. +// For example: +// ParticleVtx ParticleVS( ParticleAppVtx inVtx ) +// < +// DESC_PARTICLE_POSITION; +// DESC_PARTICLE_LIFETIME; +// DESC_PARTICLE_CUSTOM( 0, "size", 2 ); +// > +// { +// ... +// } + +#define DESC_PARTICLE_CONCAT( x, y ) x##y + +#define DESC_PARTICLE_ELEMENT( usage, index, name, dimension ) \ + string DESC_PARTICLE_CONCAT( ParticleElement_Name_, __LINE__ ) = name; \ + int DESC_PARTICLE_CONCAT( ParticleElement_Dimension_, __LINE__ ) = dimension; \ + int DESC_PARTICLE_CONCAT( ParticleElement_Usage_, __LINE__ ) = usage; \ + int DESC_PARTICLE_CONCAT( ParticleElement_Index_, __LINE__ ) = index + +#define DESC_PARTICLE_POSITION DESC_PARTICLE_ELEMENT( 1, 0, "POSITION", 3 ) +#define DESC_PARTICLE_VELOCITY DESC_PARTICLE_ELEMENT( 2, 0, "VELOCITY", 3 ) +#define DESC_PARTICLE_LIFETIME DESC_PARTICLE_ELEMENT( 0, 0, "LIFETIME", 2 ) +#define DESC_PARTICLE_MASS DESC_PARTICLE_ELEMENT( 3, 0, "MASS", 1 ) + +#define DESC_PARTICLE_CUSTOM( index, name, dimension ) DESC_PARTICLE_ELEMENT( 4, index, name, dimension ) + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/Picking.fxh b/shaders/include/Carbon/Picking.fxh new file mode 100644 index 000000000..43fca2b7d --- /dev/null +++ b/shaders/include/Carbon/Picking.fxh @@ -0,0 +1,39 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_PICKING_FXH +#define CARBON_PICKING_FXH + +// Picking object ID (64 bits) is split into two 32-bit unsigned integers for shader compatibility. +// These variables are provided by Trinity when rendering objects for picking purposes. +// Picking pixel shader needs to forward these values to the output target. +uint pickingObjectLowBits; +uint pickingObjectHighBits; +uint areaId; + + +// The expected output of a picking pixel shader. +// To be filled with CreatePickingOutput or CreateInstancedPickingOutput functions. +struct TrinityPickingOutput +{ + uint4 pickingTarget : SV_Target; +}; + +// Creates a TrinityPickingOutput for a non-instanced object. +TrinityPickingOutput CreatePickingOutput() +{ + TrinityPickingOutput result; + result.pickingTarget = uint4( pickingObjectLowBits, pickingObjectHighBits, areaId, 0 ); + return result; +} + +// Creates a TrinityPickingOutput for an instanced object. +// Instance data is piped from the instance vertex buffer. +TrinityPickingOutput CreateInstancedPickingOutput( uint2 instanceData ) +{ + TrinityPickingOutput result; + result.pickingTarget = uint4( pickingObjectLowBits, pickingObjectHighBits, instanceData ); + return result; +} + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/RandomXOrShift.fxh b/shaders/include/Carbon/RandomXOrShift.fxh new file mode 100644 index 000000000..7495c0efc --- /dev/null +++ b/shaders/include/Carbon/RandomXOrShift.fxh @@ -0,0 +1,38 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_RANDOM_FXH +#define CARBON_RANDOM_FXH + + +// Based on https://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/ + +// Initializes a random number generator state based on a seed +uint RandInit( uint seed ) +{ + seed = ( seed ^ 61 ) ^ ( seed >> 16 ); + seed *= 9; + seed = seed ^ ( seed >> 4 ); + seed *= 0x27d4eb2d; + seed = seed ^ ( seed >> 15 ); + return seed; +} + +// Returns a random uint in the range [0, 2^32 - 1] and updates the rngState +uint Rand( inout uint rngState ) +{ + // Xorshift algorithm from George Marsaglia's paper + rngState ^= ( rngState << 13 ); + rngState ^= ( rngState >> 17 ); + rngState ^= ( rngState << 5 ); + return rngState; +} + +// Returns a random float in the range [0, 1) and updates the rngState +float RandFloat( inout uint rngState ) +{ + return float( Rand( rngState ) ) * ( 1.0 / 4294967296.0 ); +} + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/BehaviorGroupBooster.fxh b/shaders/include/Carbon/SpaceScene/BehaviorGroupBooster.fxh new file mode 100644 index 000000000..64e54e5c8 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/BehaviorGroupBooster.fxh @@ -0,0 +1,23 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_BEHAVIORGROUPBOOSTER_FXH +#define CARBON_BEHAVIORGROUPBOOSTER_FXH + + +// Data passed from the engine to the vertex shader stage for BehaviourGroupBooster draw calls. +struct BehaviourGroupBoosterVertexInput +{ + // Vertex position in local instance space + float3 position : POSITION; + // UV coordinates + float2 texCoord : TEXCOORD0; + // Owner agent position (xyz) and scale (w) + float4 agentPos : TEXCOORD1; + // Owner agent rotation quaternion + float4 agentRot : TEXCOORD2; + // Booster data: x=intensity, y= per agent random number (0 to 1), z=atlasIndex0, w=atlasindex1 + float4 boosterData : TEXCOORD3; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/DepthMap.fxh b/shaders/include/Carbon/SpaceScene/DepthMap.fxh new file mode 100644 index 000000000..b2bcc1d95 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/DepthMap.fxh @@ -0,0 +1,11 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_DEPTHMAP_FXH +#define CARBON_DEPTHMAP_FXH + +// Contains SRV representation of the depth buffer for the current render pass. Note that not all render passes may use a depth buffer, +// so this may be unbound in some cases. For example, opaque forward rendering passes may not use a depth buffer, while transparent +// forward rendering passes may use the depth buffer from the opaque pass. +Texture2D DepthMap ; + +#endif // CARBON_DEPTHMAP_FXH \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/DynamicLighting.fxh b/shaders/include/Carbon/SpaceScene/DynamicLighting.fxh new file mode 100644 index 000000000..3df0ea43f --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/DynamicLighting.fxh @@ -0,0 +1,403 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_DYNAMICLIGHTING_FXH +#define CARBON_DYNAMICLIGHTING_FXH + +#include "../System.fxh" +#include "PerSceneData.fxh" +#include "../Math.fxh" + +// Packed light data for a single light source. See LightBuffer variable. +struct PerLightData +{ + float4 position; // xyz - world position, w - radius + float4 color; // xyz - color, w - bits 0..15 - innerRadius (float 16), bits 16..31 - flags + float4 direction; // see next few lines: + // x bits 0..15 - direction.x (float 16), x bits 16..31 - direction.y (float 16) + // y bits 0..15 - direction.z (float 16), y bits 16..31 - projectionPlaneDistance (float 16) + // z bits 0..15 - outerAngle (float 16), z bits 16..31 - innerAngle (float 16) + // w bits 0..1 - padding, w bits 2..11 - shadowMapScale, w bits 12..21 - shadowMapOffsetX, w bits 22..31 - shadowMapOffsetY +}; + +// Light flags stored in PerLightData.color.w + +// Light affects surfaces (opaque and transparent) +static const uint LIGHT_FLAG_AFFECTS_SURFACE = 1 << 16; +// Light affects particles (opaque and transparent) +static const uint LIGHT_FLAG_AFFECTS_PARTICLES = 1 << 17; +// Light casts shadows +static const uint LIGHT_FLAG_CASTS_SHADOWS = 1 << 18; +// Light affects volumetric fog (if enabled) +static const uint LIGHT_FLAG_IS_VOLUMETRIC = 1 << 19; + +// Stores linked lists of indices into LightBuffer for each tile in the screen. The buffer is partitioned into two sections: +// 3 arrays of per-tile list heads (opaque, transparent and particle arrays) and per-light next indices. The first section contains one uint per tile, +// which is the index (into LightIndexBuffer) of the first light in the linked list for that tile. The second section contains one uint per light, which is the index of the +// next light in the linked list for that tile. A value of for the index 0 indicates the end of the list. +StructuredBuffer LightIndexBuffer ; +// Stores the actual light data for each potentially visible light in the scene. The index into this buffer is stored in LightIndexBuffer. +StructuredBuffer LightBuffer ; + +// Global array of light profiles (IES profiles) for lights that use them. The index into this array is stored in PerLightData.color.w. +Texture2DArray LightProfileArray ; +SamplerState LightProfileSampler +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = None; + AddressU = Clamp; + AddressV = Clamp; +}; + +// Global shadow map atlas for all lights that cast shadows. The window of this atlas is stored in PerLightData.direction.w. +// This atlas is used when rendering rastrized shadows. Raytraced shadows are stored in EveSpaceSceneDynamicShadowMap. +DepthTexture2D ShadowMapAtlas < bool AutoRegister = true; >; + +SamplerComparisonState ShadowMapAtlasSampler +{ + AddressU = Clamp; + AddressV = Clamp; + ComparisonFunc = Greater; + Filter = COMPARISON_MIN_MAG_LINEAR_MIP_POINT; +}; + +Texture2D EveSpaceSceneDynamicShadowMap ; + +// The size of a tile in pixels. This is used to determine which tile a pixel belongs to when looking up the light list for that tile. +#define TILE_SIZE 16 + + +// ------------------------------------------------------------ PCF ------------------------------------------------------------- + +float2x2 _PCFTransform( float2 vpos, bool hasJitter, int frameIndex, float inverseShadowMapAtlasSize ) +{ + float angle = 0.0; + + //Randomize the angle if jittering is enabled (= we have some form of temporal filtering) + //Otherwise, we do no rotating at all to make sure the image isn't noisy or unstable when no AA or upscaling is enabled. + + if( hasJitter ) + { + int2 pixelPos = int2( vpos ); + //We use 9 evenly distributed rotations, which change both per pixel and over time + //This gives us effectively NUM_SAMPLES * 9 samples over time. + uint dither = ( pixelPos.x * 3u + pixelPos.y * 4u + frameIndex * 5u ) % 9u; + angle += float( dither ) * ( PI * 2.0 / 9.0 ); + } + + //Build a rotation matrix so that we can apply the quickly to each sample. + float s; + float c; + sincos( angle, s, c ); + float2x2 rotation = float2x2( + c, -s, + s, c + ); + + return rotation * inverseShadowMapAtlasSize; +} + +float2x2 _PCFTransform( float2 vpos, EveSpaceSceneDataPS perFramePS ) +{ + bool hasJitter = perFramePS.MiscData2.y != 0; + int frameIndex = perFramePS.MiscData2.x; + float inverseShadowMapAtlasSize = asfloat( perFramePS.MiscData2.z ); + return _PCFTransform( vpos, hasJitter, frameIndex, inverseShadowMapAtlasSize ); +} + +// based on: https://www.shadertoy.com/view/4djSRW +float3 _Random( float2 p, uint timer ) +{ + timer &= 2047; + float3 p3 = float3( p, float( timer ) ); + p3 = frac( p3 * float3( .1031, .1030, .0973 ) ); + p3 += dot( p3, p3.yxz + 33.33 ); + return frac( ( p3.xxy + p3.yxx ) * p3.zyx ); +} + +float3 _PCFOffset( float2 vpos, EveSpaceSceneDataPS perFramePS ) +{ + bool hasJitter = perFramePS.MiscData2.y != 0; + return hasJitter ? _Random( vpos, perFramePS.MiscData2.x ).xyz * .004 : 0.0.xxx; +} + +struct _PCFJitter +{ + float2x2 transform; + float3 offset; +}; + +_PCFJitter _GetPCFJitter( float2 vpos, EveSpaceSceneDataPS perFramePS ) +{ + _PCFJitter jitter; + jitter.transform = _PCFTransform( vpos, perFramePS ); + jitter.offset = _PCFOffset( vpos, perFramePS ); + return jitter; +} + +float _PCF( _PCFJitter pcfJitter, DepthTexture2D ShadowMapAtlas, SamplerComparisonState ShadowMapAtlasSampler, float centerDepth, float2 uv ) +{ + const int NUM_SAMPLES = 16; //higher count reduces flickering, but is slower + const float RADIUS = 4.5; //higher increases filter radius, making the transition softer and reducing flickering + + //Here we do a golden angle spiral sample pattern ("optimal" even distribution over a circle) over a larger area + float pcf = 0.0; + [unroll] + for( int i = 0; i < NUM_SAMPLES; i++ ) + { + //The following values are all constant in an unrolled loop + //That's why we don't include the extra "random" rotation here! + const float GOLDEN_ANGLE = PI * ( 3.0 - sqrt( 5.0 ) ); + float angle = float( i ) * GOLDEN_ANGLE; + float f = ( float( i ) + 0.5 ) / float( NUM_SAMPLES ); + float2 sampleOffset = float2( cos( angle ), sin( angle ) ) * ( sqrt( f ) * RADIUS ); + float weight = 1.0 / float( NUM_SAMPLES ); + + //These are actually calculated per sample, so we apply the per-pixel rotation here + float2 sampleUV = uv + mul( pcfJitter.transform, sampleOffset ); + + float comparisonResult = ShadowMapAtlas.SampleCmpLevelZero( ShadowMapAtlasSampler, sampleUV, centerDepth ); + pcf += comparisonResult * weight; + } + + float visibility = pcf; + return visibility; +} + +// ------------------------------------------------------------------------------------------------------------------------------ + +struct ShadowData +{ + _PCFJitter pcfJitter; + uint raytracedMask; +}; + +ShadowData GetShadowData( float2 vpos, EveSpaceSceneDataPS perFramePS ) +{ + ShadowData shadowData; + shadowData.pcfJitter.transform = float2x2( 0., 0., 0., 0. ); + shadowData.pcfJitter.offset = 0.0.xxx; + shadowData.raytracedMask = 0; + + uint shadowQuality = asuint( perFramePS.ShadowMapSettings2.w ); + if( ( shadowQuality & ( SHADOW_QUALITY_FLAG_LOW | SHADOW_QUALITY_FLAG_HIGH ) ) != 0 ) + { + shadowData.pcfJitter = _GetPCFJitter( vpos, perFramePS ); + } + if( ( shadowQuality & SHADOW_QUALITY_FLAG_RAYTRACED ) != 0 ) + { + shadowData.raytracedMask = EveSpaceSceneDynamicShadowMap.Load( int3( int2( vpos.xy ), 0 ) ).x; + } + return shadowData; +} + +// Light list types for GetDynamicLightingListHead function +static const uint LIGHT_LIST_TYPE_OPAQUE = 0; +static const uint LIGHT_LIST_TYPE_TRANSPARENT = 1; +static const uint LIGHT_LIST_TYPE_PARTICLES = 2; + +// Returns the index of the first light record in the linked list for the tile that contains the pixel at vpos. +// The lightListType parameter specifies which list to return (opaque, transparent or particle). +uint GetDynamicLightingListHead( uint2 vpos, uint targetWidth, uint lightListType ) +{ + uint2 pixelPos = vpos.xy / TILE_SIZE; + uint tilesPerRow = ( targetWidth + TILE_SIZE - 1 ) / TILE_SIZE; + uint index = pixelPos.x + pixelPos.y * tilesPerRow; + return LightIndexBuffer[index * 3 + lightListType]; +} + + +struct ComputedDynamicLight +{ + // Direction from the lit position to the light source, normalized. + float3 lightDirection; + // Distance from the lit position to the light source. + float distance; + // Color of the light source, after shadowing. Does not take distance-based attenuation into account. + float3 lightColor; + // Increase in roughness due to the light. + float roughnessIncrease; + // Light radius + float radius; + // Light inner radius + float innerRadius; +}; + +// Computes the lighting contribution of a single light source at a given position in world space. +// Returns true if the light contributes to the lighting at the given position (based on distance and filters), false otherwise. +// The lighting data is returned in the computedDynamicLight parameter. +// Parameters: +// shadowData - Shadow data for the current pixel, including PCF jitter and raytraced mask. Normally GetDynamicLight is called in a loop over all lights, +// and this data is computed once per pixel and passed to each call to GetDynamicLight. +// inverseShadowMapAtlasSize - Inverse size of the shadow map atlas (from EveSpaceSceneDataPS). +// shadowMapAtlasEntryMinSizeLog2 - Minimum size of a shadow map atlas entry, in log2 (from EveSpaceSceneDataPS). +// shadowQuality - Current shadow quality setting in the application (from EveSpaceSceneDataPS, see SHADOW_QUALITY_FLAG_*). +// lightData - Data for the light source, obtained from LightBuffer. +// litPosition - Position in world space being lit. +// flags - Flags for filtering which lights to consider (LIGHT_FLAG_*). +bool GetDynamicLight( + ShadowData shadowData, + float inverseShadowMapAtlasSize, + uint shadowMapAtlasEntryMinSizeLog2, + uint shadowQuality, + PerLightData lightData, + float3 litPosition, + uint flags, + out ComputedDynamicLight computedDynamicLight ) +{ + computedDynamicLight.lightDirection = 0; + computedDynamicLight.distance = 0; + computedDynamicLight.lightColor = 0; + computedDynamicLight.roughnessIncrease = 0; + computedDynamicLight.radius = 0; + computedDynamicLight.innerRadius = 0; + + computedDynamicLight.lightDirection = lightData.position.xyz - litPosition; + float3 unnormalizedLightDirection = computedDynamicLight.lightDirection; + computedDynamicLight.distance = length( computedDynamicLight.lightDirection ); + computedDynamicLight.lightDirection /= computedDynamicLight.distance; + + float radius = lightData.position.w; + computedDynamicLight.radius = radius; + float innerRadius = f16tof32( asuint( lightData.color.w ) & 0xffff ); + computedDynamicLight.innerRadius = innerRadius; + uint lightFlags = asuint( lightData.color.w ); + uint profile = lightFlags >> 20; + bool gotShadowMap = ( lightFlags & LIGHT_FLAG_CASTS_SHADOWS ) != 0; + + float3 lightDir = float3( + f16tof32( asuint( lightData.direction.x ) & 0xffff ), + f16tof32( asuint( lightData.direction.x ) >> 16 ), + f16tof32( asuint( lightData.direction.y ) & 0xffff ) + ); + float outerCosAngle = f16tof32( asuint( lightData.direction.z ) & 0xffff ); + float innerCosAngle = f16tof32( asuint( lightData.direction.z ) >> 16 ); + float cosAngle = dot( lightDir, computedDynamicLight.lightDirection ); + + bool result = false; + [branch] + if( computedDynamicLight.distance < radius && ( lightFlags & flags ) != 0 ) + { + float pointLightFuzzyness = clamp( innerRadius / computedDynamicLight.distance, 0.0, 0.8 ); + + // fuzzFactor needs to be in the 4th power (or 2 powers stronger than roughness) + // so the fuzz doesn't overshadow the roughness of the material (if that makes sense) + float fuzzFactor = pow( pointLightFuzzyness, 4 ); + + float attenuation = 1; + + if( outerCosAngle > 0.0f ) + { + attenuation *= pow( LinStep( outerCosAngle, innerCosAngle, cosAngle ), 2 ); + } + if( profile > 0 ) + { + attenuation *= LightProfileArray.SampleLevel( LightProfileSampler, float3( cosAngle * -0.5 + 0.5, 0, profile - 1 ), 0 ); + } + + if( ( shadowQuality & ( SHADOW_QUALITY_FLAG_LOW | SHADOW_QUALITY_FLAG_HIGH ) ) != 0 && inverseShadowMapAtlasSize > 0. && gotShadowMap && attenuation > 0. ) + { + uint shadowMapScale = ( ( asuint( lightData.direction.w ) >> 2 ) & ( ( 1 << 10 ) - 1 ) ) << shadowMapAtlasEntryMinSizeLog2; + uint shadowMapOffsetX = ( ( asuint( lightData.direction.w ) >> 12 ) & ( ( 1 << 10 ) - 1 ) ) << shadowMapAtlasEntryMinSizeLog2; + uint shadowMapOffsetY = ( ( asuint( lightData.direction.w ) >> 22 ) & ( ( 1 << 10 ) - 1 ) ) << shadowMapAtlasEntryMinSizeLog2; + float projectionPlaneDistance = f16tof32( asuint( lightData.direction.y ) >> 16 ); + + float depth = 0.; + if( outerCosAngle > 0.0f ) + { + // spotlight + lightDir = -lightDir; + float3 up = abs( lightDir.y ) < .7 ? float3( 0., 1., 0. ) : float3( 1., 0., 0. ); + float3 side = normalize( cross( lightDir, up ) ); + up = cross( side, lightDir ); + float3x3 lightRotation = float3x3( side, up, lightDir ); + unnormalizedLightDirection = mul( lightRotation, -unnormalizedLightDirection ); + depth = unnormalizedLightDirection.z; + } + else + { + // pointlight + float3 absLightDir = abs( computedDynamicLight.lightDirection ); + absLightDir += shadowData.pcfJitter.offset; + float maxDir = max( max( absLightDir.x, absLightDir.y ), absLightDir.z ); + if( absLightDir.x == maxDir ) + { + depth = unnormalizedLightDirection.x; + unnormalizedLightDirection.xy = unnormalizedLightDirection.zy; + shadowMapOffsetX += 0; + } + else if( absLightDir.y == maxDir ) + { + depth = unnormalizedLightDirection.y; + unnormalizedLightDirection.xy = unnormalizedLightDirection.xz; + shadowMapOffsetX += shadowMapScale; + } + else + { + depth = unnormalizedLightDirection.z; + unnormalizedLightDirection.xy = unnormalizedLightDirection.yx; + shadowMapOffsetX += 2 * shadowMapScale; + } + unnormalizedLightDirection.y *= depth > 0. ? -1. : 1.; + shadowMapOffsetY += depth < 0. ? shadowMapScale : 0; + depth = abs( depth ); + } + + // perspective projection of z, assuming reverse z and far clip being the same as near clip divided by 1000 + const float b = 1. / 999.; + float m = radius * b; + unnormalizedLightDirection.z = -b + m / depth; + + // perspective projection of xy and flipping image + unnormalizedLightDirection.xy *= ( projectionPlaneDistance / depth ); + unnormalizedLightDirection.y *= -1.; + + // margin scale and changing value range from [-1, 1] to [0, 1] + const float margin = 8.; + float marginScale = .5 - ( margin / shadowMapScale ); + unnormalizedLightDirection.xy = unnormalizedLightDirection.xy * marginScale + .5.xx; + + // coordinate on atlas + unnormalizedLightDirection.xy = unnormalizedLightDirection.xy * float( shadowMapScale ) + float2( shadowMapOffsetX, shadowMapOffsetY ); + unnormalizedLightDirection.xy *= inverseShadowMapAtlasSize; + + float visibility = _PCF( shadowData.pcfJitter, ShadowMapAtlas, ShadowMapAtlasSampler, unnormalizedLightDirection.z, unnormalizedLightDirection.xy ); + + attenuation *= visibility; + } + + if( ( shadowQuality & SHADOW_QUALITY_FLAG_RAYTRACED ) != 0 && gotShadowMap && attenuation > 0. ) + { + uint shadowMask = ( ( asuint( lightData.direction.w ) ) & ( ( 1 << 16 ) - 1 ) ); + int shadow = shadowData.raytracedMask & shadowMask; + attenuation *= shadow != 0 ? 0. : 1.; + } + + computedDynamicLight.lightColor = lightData.color.rgb * attenuation; // innerRadius now softens the core in GetBoundedAttenuation; (1-fuzz) dimming dropped to avoid double-counting source size (fuzz still drives roughnessIncrease) + computedDynamicLight.roughnessIncrease = fuzzFactor; + result = true; + } + return result; +} + +// Computes the lighting contribution of a single light source at a given position in world space. +// Returns true if the light contributes to the lighting at the given position (based on distance and filters), false otherwise. +// The lighting data is returned in the computedDynamicLight parameter. +// Parameters: +// shadowData - Shadow data for the current pixel, including PCF jitter and raytraced mask. Normally GetDynamicLight is called in a loop over all lights, +// and this data is computed once per pixel and passed to each call to GetDynamicLight. +// index - Index of the light in LightBuffer to compute lighting for. +// litPosition - Position in world space being lit. +// perFramePS - Per-scene pixel shader data (from EveSpaceSceneDataPS). +// flags - Flags for filtering which lights to consider (LIGHT_FLAG_*). +bool GetDynamicLight( ShadowData shadowData, uint index, float3 litPosition, EveSpaceSceneDataPS perFramePS, uint flags, out ComputedDynamicLight computedDynamicLight ) +{ + PerLightData lightData = LightBuffer[index]; + float inverseShadowMapAtlasSize = asfloat( perFramePS.MiscData2.z ); + uint shadowMapAtlasEntryMinSizeLog2 = asuint( perFramePS.MiscData2.w ); + uint shadowQuality = asuint( perFramePS.ShadowMapSettings2.w ); + return GetDynamicLight( shadowData, inverseShadowMapAtlasSize, shadowMapAtlasEntryMinSizeLog2, shadowQuality, lightData, litPosition, flags, computedDynamicLight ); +} + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EnvironmentMap.fxh b/shaders/include/Carbon/SpaceScene/EnvironmentMap.fxh new file mode 100644 index 000000000..2ab4cc415 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EnvironmentMap.fxh @@ -0,0 +1,67 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_ENVIRONMENTMAP_FXH +#define CARBON_ENVIRONMENTMAP_FXH + +#include "../System.fxh" + + +// -------------------------------------------------------------------------------------------------- +// texture +// -------------------------------------------------------------------------------------------------- +// Environment map (refleciton) texture set by EveSpaceScene +TextureCube EveSpaceSceneEnvMap +< + bool AutoRegister = true; + bool Tr2sRGB = true; +>; +SamplerState EveSpaceSceneEnvMapSampler +{ + MinFilter = Anisotropic; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Wrap; + AddressV = Wrap; +}; + +// Helper function to convert PBR roughness to gloss value used for environment map lookup +float RoughnessToGloss( float roughness ) +{ + const float GGX_MAX_SPEC_POWER = 28; + const float EPSILON = 0.00052556; + float gloss = 1.0; + if( roughness >= EPSILON ) + { + gloss = roughness * roughness; + gloss *= gloss; + gloss = ( 2.0f / gloss ) - 1.0f; + gloss = log2( gloss ) / GGX_MAX_SPEC_POWER; + } + + return gloss; +} + +// Helper function to convert PBR roughness to mip level used for environment map lookup +float RoughnessToMip( float roughness, TextureCube envMap ) +{ + uint width; + uint height; + uint mipLevels; + envMap.GetDimensions( 0, width, height, mipLevels ); + + return ( 1.0f - RoughnessToGloss( roughness ) ) * float( mipLevels - 1 ); +} + +// Helper function to convert PBR roughness to mip level used for environment map lookup for EveSpaceSceneEnvMap +float RoughnessToMip( float roughness ) +{ + return RoughnessToMip( roughness, EveSpaceSceneEnvMap ); +} + +// Sample an environment map with given direction and surface roughness +float3 SampleEnvironmentMap( TextureCube envMap, float3 dir, float roughness ) +{ + return envMap.SampleLevel( EveSpaceSceneEnvMapSampler, dir, RoughnessToMip( roughness, envMap ) ).rgb; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveBannerSet.fxh b/shaders/include/Carbon/SpaceScene/EveBannerSet.fxh new file mode 100644 index 000000000..7a7b1bd4f --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveBannerSet.fxh @@ -0,0 +1,22 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVEBANNERSET_FXH +#define CARBON_EVEBANNERSET_FXH + + +// Data passed from the engine to the vertex shader stage for EveBannerSet draw calls. +struct EveBannerSetVertexInput +{ + // Vertex position in local space + float3 position : POSITION; + // Vertex normal in local space + float3 normal : NORMAL; + // UV coordinates + float2 texCoord : TEXCOORD; + // W component contains bone index for skinning, XYZ components are unused + int4 indices : BLENDINDICES; +}; + +// EveBannerSet uses the same per-object data layout as other space objects. See PerObjectData.fxh + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveBoosterSet.fxh b/shaders/include/Carbon/SpaceScene/EveBoosterSet.fxh new file mode 100644 index 000000000..990832fa9 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveBoosterSet.fxh @@ -0,0 +1,49 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVEBOOSTERSET_FXH +#define CARBON_EVEBOOSTERSET_FXH + +// Data passed from the engine to the vertex shader stage for EveBoosterSet draw calls. +struct EveBoosterSetVertexInput +{ + // Vertex position in local instance space + float3 position : POSITION; + // UV coordinates; only filled when shader quality is set to low, for "star" shaped boosters + float2 texCoord : TEXCOORD0; + // 4x4 matrix that transforms the booster from local instance space to object space + float4 row1 : TEXCOORD1; + float4 row2 : TEXCOORD2; + float4 row3 : TEXCOORD3; + float4 row4 : TEXCOORD4; + // Custom values passed from SOF (no particular semantics, but can be used for various purposes in the shader) + float4 functionality : TEXCOORD5; + // Per booster instance random number from 0 to 1 used for offsetting booster animations + float wavePhase : TEXCOORD6; + // Texture atlas indices as floats, passed from SOF + float2 atlasIndex : TEXCOORD7; +}; + + +// Constant buffer format for per-object data for EveBoosterSet. This data is provided by the engine to the vertex shader stage. +struct EveBoosterSetDataVS +{ + // World matrix of the booster's owner ship + float4x4 WorldMat; + // Additional ship-data ( components: .x = boosterGain, .y = speed, .z = maxSize, .w = unused ) + float4 Shipdata; + // Trail spline data + float4 TrailsControlPositions[5]; + float4 TrailsControlNormals[5]; +}; + + +// Constant buffer format for per-object data for EveBoosterSet. This data is provided by the engine to the pixel shader stage. +struct EveBoosterSetDataPS +{ + // Additional ship-data ( components: .x = boosterGain, .y = trailIntensity, .z = warpStrength, .w = unused ) + float4 Shipdata; +}; + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveChildBoosterSet.fxh b/shaders/include/Carbon/SpaceScene/EveChildBoosterSet.fxh new file mode 100644 index 000000000..235bcd3dd --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveChildBoosterSet.fxh @@ -0,0 +1,54 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVECHILDBOOSTERSET_FXH +#define CARBON_EVECHILDBOOSTERSET_FXH + + +// Data passed from the engine to the vertex shader stage for EveChildBoosterSet draw calls. +struct EveChildBoosterSetVertexInput +{ + // Vertex position in local instance space + float3 position : POSITION; +}; + + +// Data passed from the engine to the vertex shader stage for EveChildBoosterSet draw calls. +struct EveChildBoosterSetInstanceData +{ + // 4x3 matrix that transforms the booster from local instance space to object space + float4x3 transform; + // Booster intensity + float intensity; + // Per booster instance random number from 0 to 1 used for offsetting booster animations + float wavePhase; + // Texture atlas indices, passed from SOF + uint atlasIndex0; + uint atlasIndex1; +}; + +StructuredBuffer ChildBoosterSetInstances ; + + +// Constant buffer format for per-object data for EveChildBoosterSet. This data is provided by the engine to the vertex shader stage. +struct EveChildBoosterSetDataVS +{ + // World matrix of the booster's owner ship + float4x4 WorldMat; + float _padding0; + float _padding1; + // Booster size at maximum intensity (used for scaling the booster in the shader) + float maxBoosterSize; + // Offset into the ChildBoosterSetInstances structured buffer for this booster set's instances + uint instanceOffset; +}; + + +// Constant buffer format for per-object data for EveChildBoosterSet. This data is provided by the engine to the pixel shader stage. +struct EveChildBoosterSetDataPS +{ + // Additional ship-data ( components: .x = unused, .y = unused, .z = warpStrength, .w = unused ) + float4 Shipdata; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveChildBulletStorm.fxh b/shaders/include/Carbon/SpaceScene/EveChildBulletStorm.fxh new file mode 100644 index 000000000..dfccfa60a --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveChildBulletStorm.fxh @@ -0,0 +1,34 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVECHILDBULLETSTORM_FXH +#define CARBON_EVECHILDBULLETSTORM_FXH + + +// Data passed from the engine to the vertex shader stage for EveChildBulletStorm draw calls. +struct EveChildBulletStormVertexInput +{ + // Vertex index of the corner of the quad, from 0 to 3 + float cornerID : TEXCOORD0; + // Bullet position in object space + float3 positionOS : TEXCOORD1; + // Bullet direction in object space + float3 directionOS : TEXCOORD2; + // XYZ - three independent random values per bullet (from 0 to 1), W - part ID + float4 data : TEXCOORD3; +}; + + +// Data passed from the engine to the vertex shader stage for EveChildBulletStorm draw calls. +struct EveChildBulletStormDataVS +{ + // World transform matrix + float4x4 WorldMatrix; + // Target information: X - number of targets (for TargetPositionAndSize), Y - effect range, Z - clip sphere radius, W - bullet speed + float4 TargetPositionInfo; + // Up to 10 target positions and sizes (XYZ = position, W = size) + float4 TargetPositionAndSize[10]; +}; + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveChildCloud.fxh b/shaders/include/Carbon/SpaceScene/EveChildCloud.fxh new file mode 100644 index 000000000..fee339ee6 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveChildCloud.fxh @@ -0,0 +1,30 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVECHILDCLOUD_FXH +#define CARBON_EVECHILDCLOUD_FXH + + +// Data passed from the engine to the vertex shader stage for EveChildCloud draw calls. +struct EveChildCloudVertexInput +{ + float2 position : POSITION; +}; + + +// Data passed from the engine to the vertex shader stage for EveChildCloud draw calls. +struct EveChildCloudPerObjectDataVS +{ + float4x4 WorldMat; + float4x4 WorldViewMat; + float4x4 WorldViewInv; + float4x4 ProjectionInv; + // Near plane in object space + float4 NearPlaneLocal; + // XYZ - camera position in object space, W - screen depth of the object (Z in clip space) + float4 EyePosLocal; + // Bounds of the cloud in projection space (XY - min, ZW - max) + float4 ScreenSize; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveChildCloud2.fxh b/shaders/include/Carbon/SpaceScene/EveChildCloud2.fxh new file mode 100644 index 000000000..1af046d7a --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveChildCloud2.fxh @@ -0,0 +1,53 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVECHILDCLOUD2_FXH +#define CARBON_EVECHILDCLOUD2_FXH + + +// Data passed from the engine to the vertex shader stage for EveChildCloud2 draw calls. +struct EveChildCloud2VertexInput +{ + // Vertex position in object space + float3 position : POSITION; +}; + + +struct EveChildCloud2LightInfo +{ + float4 position; // xyz - position, w - radius + float4 color; // xyz - color, w - normalized inner radius +}; + + +// Constant buffer format for per-object data for EveChildCloud2. This data is provided by the engine to all shader stages. +struct EveChildCloud2PerObjectData +{ + float4x4 WorldMat; + float4x4 ProjectionInvMat; + float4x4 WorldViewInvMat; + uint4 LightmapDimensions; // xyz - lightmap width/height/depth, w - current generation offset + uint4 NoiseConfig; // xy - random offset, zw - noise texture width/height + float4 SunDirection; // xyz - sun direction in model space, w - slice 0 w + float4 ViewPosition; // xyz - view position in model space, w - slice 1 w + float4 ViewDirection; // xyz - view direction in model space, w - slice 2 w + float4 RelativeScaling; // xyz - renormalizing factors for non-uniform scaling, w - lod factor + float4 TargetInvSize; // xy - render target inv size x2 + EveChildCloud2LightInfo Lights[4]; + float4 MapOffsets[3]; + float4x4 LightViewProj; +}; + + +// The expected pixel shader output from EveChildCloud2 materials. +// Each slice of the cloud is written to a separate render target. +// Slice depth is taken from EveChildCloud2PerObjectData +struct EveChildCloud2PSOutput +{ + float4 slice0 : SV_Target0; + float4 slice1 : SV_Target1; + float4 slice2 : SV_Target2; + float4 slice3 : SV_Target3; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveChildQuad.fxh b/shaders/include/Carbon/SpaceScene/EveChildQuad.fxh new file mode 100644 index 000000000..487ae2b80 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveChildQuad.fxh @@ -0,0 +1,29 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVECHILDQUAD_FXH +#define CARBON_EVECHILDQUAD_FXH + + +struct EveChildQuadVertexInput +{ + // Quad vertex index (0-3) + float index : TEXCOORD5; + + // Transform matrix (3x4) from owner local space to world space + float4 parentTransform0 : POSITION0; + float4 parentTransform1 : POSITION1; + float4 parentTransform2 : POSITION2; + + // Transform matrix (3x4) from quad local space to owner local space + float4 localTransform0 : POSITION3; + float4 localTransform1 : POSITION4; + float4 localTransform2 : POSITION5; + + // Quad color + float4 color : TEXCOORD0; + // X - quad brightness, Y - unused + float2 data : TEXCOORD1; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveHazeSet.fxh b/shaders/include/Carbon/SpaceScene/EveHazeSet.fxh new file mode 100644 index 000000000..76136241a --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveHazeSet.fxh @@ -0,0 +1,28 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVEHAZESET_FXH +#define CARBON_EVEHAZESET_FXH + + +// Data passed from the engine to the vertex shader stage for EveHazeSet draw calls. +struct EveHazeSetVertexInput +{ + // 3x4 matrix that transforms the haze from local instance space to object space + float4 transform1 : TEXCOORD0; + float4 transform2 : TEXCOORD1; + float4 transform3 : TEXCOORD2; + // 3x4 matrix that transforms the haze from object space to local instance space + float4 invTransform1 : TEXCOORD3; + float4 invTransform2 : TEXCOORD4; + float4 invTransform3 : TEXCOORD5; + // X - haze falloff, Y - source size, Z - source brightness, W - saturation + float4 hazeData : TEXCOORD6; + // Color of the haze + float4 color : COLOR; + // X - vertex index (0-7), Y - skeleton bone index if the haze is attached to the bone + uint4 index : TEXCOORD7; +}; + +// EveHazeSet uses the same per-object data layout as other space objects. See PerObjectData.fxh + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveLensflare.fxh b/shaders/include/Carbon/SpaceScene/EveLensflare.fxh new file mode 100644 index 000000000..604b65dcb --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveLensflare.fxh @@ -0,0 +1,35 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVELENSFLARE_FXH +#define CARBON_EVELENSFLARE_FXH + + +struct EveLensflarePerObjectData +{ + // Direction towards the lens flare that owns the occluder; W - flare size scale + float4 LensflareFxDirectionScale; + // Offset into the occlusion buffer where the occlusion results for this lens flar are stored. + // X - foreground occlusion results, Y - background (planets, etc.) occlusion results, Z, W - unused + uint4 OcclusionIndices; +}; + +// The buffer containing occlusion results of occlusion queries for lens flares. Read values need to be cast to float using asfloat() to get the occlusion ratio in the range [0, 1]. +Buffer FlareOcclusionBuffer ; + +float GetLensFlareOcclusion( uint index ) +{ + return asfloat( FlareOcclusionBuffer[index] ); +} + +// Direction towards the lens flare that owns the occluder; W - flare size scale +// Can be used for querying lens flare position for shaders outside the lens flare system. +float4 LensflareFxDirectionScale ; + +// Stores offsets into the occlusion buffer where the occlusion results for a lens flare are stored. +// X - offset into the occlusion buffer for foreground occlusion results (cast to uint with asuint()) +// Y - offset into the occlusion buffer for background occlusion results (cast to uint with asuint()) +// Z, W - unused +float4 LensflareFxOccScale ; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveOccluder.fxh b/shaders/include/Carbon/SpaceScene/EveOccluder.fxh new file mode 100644 index 000000000..da7670992 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveOccluder.fxh @@ -0,0 +1,44 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVEOCCLUDER_FXH +#define CARBON_EVEOCCLUDER_FXH + +#include "DepthMap.fxh" +#include "Volumetrics.fxh" + +// Offset into the occlusion buffer where the occlusion results for this occluder are stored. +// Even though the variable is declared as a float, it is used as an integer value in the shader code (use asuint). +float OcclusionBufferOffset ; +// Scaling factor for fog occlusion to apply to the resulting occlusion value. +float OcclusionFogWeight ; +// Direction towards the lens flare that owns the occluder; W - flare size scale +float4 LensflareFxDirectionScale ; + + +// Destination buffer for writing occlusion results. Each occluder occupies two consecutive uint values in the buffer. +// The first value is the total occluder's shaded pixel count, and the second value is the unoccluded pixel count. +RWBuffer FlareOcclusionBuffer ; + +SamplerState _EveOccluderDepthMapSampler +{ + MinFilter = Point; + MagFilter = Point; + MipFilter = None; + AddressU = Mirror; + AddressV = Mirror; +}; + +// Records occlusion for a pixel with normalized screen space coordinates. +void EveOccluderRecordOcclusion( float2 pos ) +{ + uint bufferOffset = asuint( OcclusionBufferOffset ); + float depth = DepthMap.SampleLevel( _EveOccluderDepthMapSampler, pos, 0.0 ).r; + if( depth == 0 ) + { + float4 fog = EveSceneFogVolumeMap.SampleLevel( EveSceneFogVolumeMapSampler, float3( pos, 3 ), 0 ); + InterlockedAdd( FlareOcclusionBuffer[1 + bufferOffset], uint( 100 * ( 1 - min( fog.a * OcclusionFogWeight, 0.95 ) ) ) ); + } + InterlockedAdd( FlareOcclusionBuffer[bufferOffset], 1 ); +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EvePlaneSet.fxh b/shaders/include/Carbon/SpaceScene/EvePlaneSet.fxh new file mode 100644 index 000000000..d99d75cfe --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EvePlaneSet.fxh @@ -0,0 +1,32 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVEPLANESET_FXH +#define CARBON_EVEPLANESET_FXH + + +// Data passed from the engine to the vertex shader stage for EvePlaneSet draw calls. +struct EvePlaneSetVertexInput +{ + // 3x4 matrix that transforms the plane from local instance space to world space + float4 transform1 : TEXCOORD0; + float4 transform2 : TEXCOORD1; + float4 transform3 : TEXCOORD2; + // Color of the plane + float4 color : COLOR; + // UV layer 1 transform and scroll data (XY - scale, ZW - offset) + float4 layer1Transform : TEXCOORD3; + // UV layer 2 transform and scroll data (XY - scale, ZW - offset) + float4 layer2Transform : TEXCOORD4; + // UV layer 1 scroll data (XY - scroll speed, ZW - scroll offset) + float4 layer1Scroll : TEXCOORD5; + // UV layer 2 scroll data (XY - scroll speed, ZW - scroll offset) + float4 layer2Scroll : TEXCOORD6; + // Z - texture atlas index + uint4 index : TEXCOORD7; + // Plane blink data coming from SOF (X - blink rate, Y - blink phase, Z - duty cycle, W - blink mode) + float4 blinkData : TEXCOORD8; +}; + +// EvePlaneSet uses the quad renderer for rendering sprites, so shaders do not have access to per-object data. + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveSpaceObjectDecal.fxh b/shaders/include/Carbon/SpaceScene/EveSpaceObjectDecal.fxh new file mode 100644 index 000000000..e551c5937 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveSpaceObjectDecal.fxh @@ -0,0 +1,109 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVESPACEOBJECTDECAL_FXH +#define CARBON_EVESPACEOBJECTDECAL_FXH + +#include "PerObjectData.fxh" +#include "../Math.fxh" + + +// Data passed from the engine to the vertex shader stage for EveSpaceObjectDecal draw calls. +struct EveSpaceObjectDecalDataVS +{ + // World transform of this decal's parent + float4x4 WorldMat; + // Inverse world transform of this decal's parent + float4x4 InvWorldMat; + // Decal projection to parent space (or to local bone) transform + float4x4 DecalMatrix; + // Inverse decal projection to parent space transform + float4x4 InvDecalMatrix; + // Transform from bone space to parent space + float4x4 ParentBoneMatrix; + // Inverse transform from parent space to bone space + float4x4 InvParentBoneMatrix; +}; + + +// Data passed from the engine to the pixel shader stage for EveSpaceObjectDecal draw calls. +struct EveSpaceObjectDecalDataPS +{ + // Parent ship's display data ( components: .x = killcounter, .yzw = unused ) + float4 DisplayData; + // Additional ship-data ( components: .x = boosterGain, .y = activation, .z = dirt, .w = boundingsphere radius ) + float4 Shipdata; + // Clip data for disolve fx ( components: .xyz = center, .w = signed size ) + float4 Clipdata1; + // Misc data ( components: .x = signed square of clip radius 2, .yzw = unused ) + float4 Miscdata; + // SH lighting coefficients + float4 ShLighting[7]; +}; + + + +EveSpaceObjectData CreateEveSpaceObjectData( EveSpaceObjectDecalDataVS perObjectData ) +{ + EveSpaceObjectData pod; + pod.WorldMat = perObjectData.WorldMat; + pod.WorldMatLast = (float4x4)0; + pod.InvWorldMat = perObjectData.InvWorldMat; + pod.BoneOffsets = (uint4)0; + + pod.Shipdata = (float4)0; + pod.Clipdata1 = (float4)0; + pod.Miscdata = (float4)0; + pod.EllipsoidRadii = (float4)0; + pod.EllipsoidCenter = (float4)0; + pod.CustomMaskMatrix[0] = IDENTITY_MATRIX; + pod.CustomMaskMatrix[1] = IDENTITY_MATRIX; + pod.CustomMaskData[0] = (float4)0; + pod.CustomMaskData[1] = (float4)0; + pod.CustomMaskMaterialIDs[0] = (float4)0; + pod.CustomMaskMaterialIDs[1] = (float4)0; + pod.CustomMaskTargets[0] = (float4)0; + pod.CustomMaskTargets[1] = (float4)0; + pod.CustomMaskClamps = (float4)0; + pod.CustomData = (float4)0; + // SH lighting coefficients + for( int i = 0; i < 7; ++i ) + { + pod.ShLighting[i] = 0; + } + return pod; +} + + +EveSpaceObjectData CreateEveSpaceObjectData( EveSpaceObjectDecalDataPS perObjectData ) +{ + EveSpaceObjectData pod; + pod.WorldMat = (float4x4)0; + pod.WorldMatLast = (float4x4)0; + pod.BoneOffsets = (uint4)0; + + pod.Shipdata = perObjectData.Shipdata; + pod.Clipdata1 = perObjectData.Clipdata1; + pod.Miscdata = perObjectData.Miscdata; + pod.EllipsoidRadii = (float4)0; + pod.EllipsoidCenter = (float4)0; + pod.CustomMaskMatrix[0] = IDENTITY_MATRIX; + pod.CustomMaskMatrix[1] = IDENTITY_MATRIX; + pod.CustomMaskData[0] = (float4)0; + pod.CustomMaskData[1] = (float4)0; + pod.CustomMaskMaterialIDs[0] = (float4)0; + pod.CustomMaskMaterialIDs[1] = (float4)0; + pod.CustomMaskTargets[0] = (float4)0; + pod.CustomMaskTargets[1] = (float4)0; + pod.CustomMaskClamps = (float4)0; + pod.CustomData = (float4)0; + // SH lighting coefficients + for( int i = 0; i < 7; ++i ) + { + pod.ShLighting[i] = perObjectData.ShLighting[i]; + } + return pod; +} + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveSpherePin.fxh b/shaders/include/Carbon/SpaceScene/EveSpherePin.fxh new file mode 100644 index 000000000..c2dbf96d3 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveSpherePin.fxh @@ -0,0 +1,25 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVESPHEREPIN_FXH +#define CARBON_EVESPHEREPIN_FXH + +// Data passed from the engine to all shader stages for EveSpherePinData draw calls in "per object" constant buffer. +struct EveSpherePinData +{ + // World transform of this pin + float4x4 WorldMat; + // XYZ - pin position, W - pin radius + float4 PinPosition; + // X - pin rotation, YZW - unused + float4 PinRotation; + // RGBA - pin color + float4 PinColor; + // X - alpha testing threshold, YZW - unused + float4 PinThreshold; + // X - sin(pin radius), Y - cos(pin radius), Z - sin(pin rotation), W - cos(pin rotation) + float4 PinRadiusPrecalc; + // XY - pin UV scale, ZW - pin UV offset + float4 PinUV; +}; + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveSpotlightSet.fxh b/shaders/include/Carbon/SpaceScene/EveSpotlightSet.fxh new file mode 100644 index 000000000..a371df02d --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveSpotlightSet.fxh @@ -0,0 +1,40 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVESPOTLIGHTSET_FXH +#define CARBON_EVESPOTLIGHTSET_FXH + +// Data passed from the engine to the vertex shader stage for EveSpotlightSet cone geometry draw calls. +struct EveSpotlightSetConeVertexInput +{ + // Spotlight color and activation state (w - ship activation) + float4 color : COLOR0; + // 3x4 matrix that transforms the spotlight from local instance space to world space + float4 transform1 : TEXCOORD0; + float4 transform2 : TEXCOORD1; + float4 transform3 : TEXCOORD2; + // X - booster gain, Y - unused + float2 boosterGain : TEXCOORD3; + // Vertex index of the spotlight cone (0-3) + float index : TEXCOORD4; +}; + + +// Data passed from the engine to the vertex shader stage for EveSpotlightSet glow sprite draw calls. +struct EveSpotlightSetGlowVertexInput +{ + // 3x4 matrix that transforms the spotlight from local instance space to world space + float4 transform1 : TEXCOORD0; + float4 transform2 : TEXCOORD1; + float4 transform3 : TEXCOORD2; + // Spotlight sprite color and activation state (w - ship activation) + float4 spriteColor : COLOR0; + // Spotlight flare color (w - unused) + float4 flareColor : COLOR1; + // Sprite scale (XYZ) and boosterGain influence (W) + float4 scale : TEXCOORD3; + // Vertex index of the spotlight cone (0-3) + float index : TEXCOORD4; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveSpriteSet.fxh b/shaders/include/Carbon/SpaceScene/EveSpriteSet.fxh new file mode 100644 index 000000000..c5f7147ab --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveSpriteSet.fxh @@ -0,0 +1,26 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVESPRITESET_FXH +#define CARBON_EVESPRITESET_FXH + +// Data passed from the engine to the vertex shader stage for EveSpriteSet draw calls. +struct EveSpriteSetVertexInput +{ + // Sprite center position in world space + float3 position : POSITION; + // Sprite color + float4 color : COLOR; + // X - owner ship's activation strength, Y - sprite blink phase, Z - sprite blink rate, W - sprite minimum size + float4 texcoord0 : TEXCOORD0; + // X - sprite maximum size, Y - sprite falloff, Z, W - unused + float2 texcoord1 : TEXCOORD1; + // Warp mode color + float4 warpColor : COLOR1; + // Vertex index for sprite quad (0-3) + float index : TEXCOORD5; +}; + +// EveSpriteSet uses the quad renderer for rendering sprites, so shaders do not have access to per-object data. + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveStarfield.fxh b/shaders/include/Carbon/SpaceScene/EveStarfield.fxh new file mode 100644 index 000000000..8bce8f5a3 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveStarfield.fxh @@ -0,0 +1,23 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVESTARFIELD_FXH +#define CARBON_EVESTARFIELD_FXH + +struct EveStarfieldVertexInput +{ + // Start sprite position in local space + float3 position : POSITION; + // Random number from 0 to 1 for each star + float coloridx : TEXCOORD0; + // Maximum intensity of the star sprite + float flashIntensity : TEXCOORD1; + // Random phase for the star sprite's flashing effect (0 to 1) + float phase : TEXCOORD2; + // Random rate for the star sprite's flashing effect + float rate : TEXCOORD3; + // X - vertex corner index (0-3), Y - texture atlas element index, Z - unused, W - unused + uint4 index : TEXCOORD4; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveStretch2.fxh b/shaders/include/Carbon/SpaceScene/EveStretch2.fxh new file mode 100644 index 000000000..9b94d8d96 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveStretch2.fxh @@ -0,0 +1,29 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVESTRETCH2_FXH +#define CARBON_EVESTRETCH2_FXH + + +// Data passed from the engine to the all shader stage for EveStretch2 draw calls. +struct EveStretch2PerObjectData +{ + // XYZ - stretch source position in world space, W - scale factor for the destination point or 0 if the destination effect is hidden + float4 SourcePosition; + // XYZ - stretch destination position in world space, W - scale factor for the destination point + float4 DestPosition; + // X - time from the beginning of the "start" phase, Y - time from the beginning of the firing loop, Z - time from the beginning of the "end" phase, W - random value from 0 to 1 + float4 Timing; + // X - effect intensity, YZW - unused + float4 Misc; +}; + + +// Data passed from the engine to the vertex shader stage for EveStretch2 draw calls. +struct EveStretch2Vertex +{ + // X - index of the quad, Y - index of the vertex in the quad + float2 indices : POSITION; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveTacticalOverlay.fxh b/shaders/include/Carbon/SpaceScene/EveTacticalOverlay.fxh new file mode 100644 index 000000000..85b778385 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveTacticalOverlay.fxh @@ -0,0 +1,47 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVETACTICALOVERLAY_FXH +#define CARBON_EVETACTICALOVERLAY_FXH + + +// Data passed from the engine to the vertex shader stage for EveTacticalOverlay sphere connector draw calls. +struct EveTacticalOverlaySphereConnectorVertexInput +{ + // XYZ - connector position in world space, W - step count and step index + float4 instanceData : TEXCOORD0; + // Object radius in integer part, intensity in fractional part + float instanceData2 : TEXCOORD1; + // Quad vertex index (0-3) + float index : TEXCOORD5; +}; + + +// Data passed from the engine to the vertex shader stage for EveTacticalOverlay anchor draw calls. +struct EveTacticalOverlayAnchorVertexInput +{ + // XYZ - anchor position in world space, W - effect intensity + float4 instanceData : TEXCOORD0; + // Quad vertex index (0-3) + float index : TEXCOORD5; +}; + + +// Data passed from the engine to the vertex shader stage for EveTacticalOverlay velocity connector draw calls. +struct EveTacticalOverlayVelocityConnectorVertexInput +{ + // XYZ - connector position in world space, W - quad index (0 - velocity, 1 - approach amount, 2 - relative approach amount) + float4 instanceData : TEXCOORD0; + // XYZ - velocity in world space, W - radius in integer part, blink phase in fractional part + float4 instanceData2 : TEXCOORD1; + // Quad vertex index (0-3) + float index : TEXCOORD5; +}; + +// Tactical overlay plane position in world space, passed via a variable store +float3 PlanePosition = float3( 0, 0, 0 ); +// Player ship velocity in world space, passed via a variable store +float3 RootVelocity = float3( 0, 0, 0 ); +// X - active range; Y - fadeout length; Z - multiplier for range and fadeout; W - base/source/ship radius +float4 Fadeout = float4( 200000.0, 50000.0, 1.0, 50.0 ); + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveTransform.fxh b/shaders/include/Carbon/SpaceScene/EveTransform.fxh new file mode 100644 index 000000000..915ea3435 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveTransform.fxh @@ -0,0 +1,19 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVETRANSFORM_FXH +#define CARBON_EVETRANSFORM_FXH + + +// Data passed from the engine to the vertex shader stage for EveTransform draw calls. +struct EveTransformDataVS +{ + // World transform of this object + float4x4 WorldMat; + // World transform of this object in the previous frame + float4x4 WorldMatLast; + // Inverse world transform of this object + float4x4 InvWorldMat; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/EveTurretSet.fxh b/shaders/include/Carbon/SpaceScene/EveTurretSet.fxh new file mode 100644 index 000000000..88e51fac0 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/EveTurretSet.fxh @@ -0,0 +1,103 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_EVETURRETSET_FXH +#define CARBON_EVETURRETSET_FXH + +#include "PerObjectData.fxh" +#include "../Math.fxh" + +#define EVE_MAX_TURRETS_PER_SET 24 + +struct EveTurrentSetPerObjectDataVS +{ + // X - "height" of the clipping plane for the turret set, Y - unused, Z - unused, W - unused + float4 ClipData; + // X - number of bones in the turret skeleton, Y - unused, Z - unused, W - unused + float4 TurretData; + // World transform of this object + float4x4 WorldMat; + // World transform of this object in the previous frame + float4x4 WorldMatLast; + // X - current frame offset, Y - previous frame offset + uint4 BoneOffsets; + // Turret instances translation data (XYZ - position, W - 1.0) + float4 TurretTrans[EVE_MAX_TURRETS_PER_SET]; + // Turret instances rotation quaternions + float4 TurretRot[EVE_MAX_TURRETS_PER_SET]; +}; + +struct EveTurrentSetPerObjectDataPS +{ + // Additional ship-data ( components: .x = boosterGain, .y = activation, .z = dirt level, .w = boundingsphere radius ) + float4 Shipdata; + // Clip data for disolve fx ( components: .xyz = center, .w = signed size ) + float4 Clipdata1; + // Misc data ( components: .x = signed square of clip radius 2, .yzw = unused ) + float4 Miscdata; + // SH lighting coefficients + float4 ShLighting[7]; +}; + + +EveSpaceObjectData CreateEveSpaceObjectData( EveTurrentSetPerObjectDataVS PerObjectVS ) +{ + EveSpaceObjectData pod; + pod.WorldMat = PerObjectVS.WorldMat; + pod.WorldMatLast = PerObjectVS.WorldMatLast; + pod.BoneOffsets = PerObjectVS.BoneOffsets; + + pod.Shipdata = (float4)0; + pod.Clipdata1 = (float4)0; + pod.Miscdata = (float4)0; + pod.EllipsoidRadii = (float4)0; + pod.EllipsoidCenter = (float4)0; + pod.CustomMaskMatrix[0] = IDENTITY_MATRIX; + pod.CustomMaskMatrix[1] = IDENTITY_MATRIX; + pod.CustomMaskData[0] = (float4)0; + pod.CustomMaskData[1] = (float4)0; + pod.CustomMaskMaterialIDs[0] = (float4)0; + pod.CustomMaskMaterialIDs[1] = (float4)0; + pod.CustomMaskTargets[0] = (float4)0; + pod.CustomMaskTargets[1] = (float4)0; + pod.CustomMaskClamps = (float4)0; + pod.CustomData = (float4)0; + // SH lighting coefficients + for( int i = 0; i < 7; ++i ) + { + pod.ShLighting[i] = (float4)0; + } + return pod; +} + + +EveSpaceObjectData CreateEveSpaceObjectData( EveTurrentSetPerObjectDataPS PerObjectPS ) +{ + EveSpaceObjectData pod; + pod.WorldMat = (float4x4)0; + pod.WorldMatLast = (float4x4)0; + pod.BoneOffsets = (uint4)0; + + pod.Shipdata = PerObjectPS.Shipdata; + pod.Clipdata1 = PerObjectPS.Clipdata1; + pod.Miscdata = PerObjectPS.Miscdata; + pod.EllipsoidRadii = (float4)0; + pod.EllipsoidCenter = (float4)0; + pod.CustomMaskMatrix[0] = IDENTITY_MATRIX; + pod.CustomMaskMatrix[1] = IDENTITY_MATRIX; + pod.CustomMaskData[0] = (float4)0; + pod.CustomMaskData[1] = (float4)0; + pod.CustomMaskMaterialIDs[0] = (float4)0; + pod.CustomMaskMaterialIDs[1] = (float4)0; + pod.CustomMaskTargets[0] = (float4)0; + pod.CustomMaskTargets[1] = (float4)0; + pod.CustomMaskClamps = (float4)0; + pod.CustomData = (float4)0; + // SH lighting coefficients + for( int i = 0; i < 7; ++i ) + { + pod.ShLighting[i] = PerObjectPS.ShLighting[i]; + } + return pod; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/FroxelFog.fxh b/shaders/include/Carbon/SpaceScene/FroxelFog.fxh new file mode 100644 index 000000000..830c59b1a --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/FroxelFog.fxh @@ -0,0 +1,98 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_FOG_FXH +#define CARBON_FOG_FXH + +#include "../System.fxh" + +struct FroxelPerLightData +{ + float4 position; // xyz - world position, w - radius + float4 color; // xyz - color, w - bits 0..15 - innerRadius (float 16), bits 16..31 - flags + float4 direction; // see next few lines: + // x bits 0..15 - direction.x (float 16), x bits 16..31 - direction.y (float 16) + // y bits 0..15 - direction.z (float 16), y bits 16..31 - projectionPlaneDistance (float 16) + // z bits 0..15 - outerAngle (float 16), z bits 16..31 - innerAngle (float 16) + // w bits 0..1 - padding, w bits 2..11 - shadowMapScale, w bits 12..21 - shadowMapOffsetX, w bits 22..31 - shadowMapOffsetY +}; + +struct FroxelPerObjectData +{ + uint3 Resolution; + uint NumDynamicLights; + + float3 Jitter; + float Far; + + float3 Scattering; + float BaseDensity; + + float MaxDistanceVisibility; + float LightG; + float EnvironmentIntensity; + float InverseShadowMapAtlasSize; + + float3 Extinction; + uint ShadowMapAtlasEntryMinSizeLog2; + + float4x4 InverseViewMatrix; + + + float GodRayNoiseFrequency; + float GodRayNoiseLerp; + float GodRayNoiseAnimation; + float GodRayNoiseIntensity; + + float4x4 GodRayNoiseMatrix; + + + float3 FogNoiseOffset; + float FogNoiseFrequency; + + float FogNoiseLerp; + float FogNoiseIntensity; + float2 LinearizeDepthParams; + + + float4 UnprojectParams; + float4 PreviousProjectParams; + float4x4 ReprojectionMatrix; + + float3 SunViewDirection; + float SunAngle; + + float3 SunWorldDirection; + float pad0; + + float3 SunColor; + float LightProfileTextureWidth; + + //Directional light shadows + float4 ShadowMapValues[4]; // x = zFar value[0], y = zFar value[1], z = zFar value[2], w = zFar value[3]..etc + float4x4 ShadowMatrix[16]; // Matrix that takes a coordinate from view space all the way to the packed cascades + float4 SplitInfo; // x = NrOfSplits, y = , z = , w = + + FroxelPerLightData DynamicLights[16]; + + float4 Planets[2]; +}; + +float FroxelDither( uint2 xy, float jitter ) +{ + float g = 1.32471795724474602596; + return frac( jitter + dot( float2( xy ), 1.0 / float2( g, g * g ) ) ); +} + +float FroxelGetLayerDistance( float normalizedZ, FroxelPerObjectData froxelPerObject ) +{ + float layerDistance = -log( lerp( 1.0, froxelPerObject.MaxDistanceVisibility, normalizedZ ) ) / froxelPerObject.BaseDensity; + return layerDistance; +} + +float FroxelGetNormalizedZ( float layerDistance, FroxelPerObjectData froxelPerObject ) +{ + float normalizedZ = saturate( ( exp( -layerDistance * froxelPerObject.BaseDensity ) - 1.0 ) / ( froxelPerObject.MaxDistanceVisibility - 1.0 ) ); + return normalizedZ; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/GpuParticles.fxh b/shaders/include/Carbon/SpaceScene/GpuParticles.fxh new file mode 100644 index 000000000..f40b9fae3 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/GpuParticles.fxh @@ -0,0 +1,86 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_GPUPARTICLES_FXH +#define CARBON_GPUPARTICLES_FXH + +// Per particle data stored in the particle buffer. The particle buffer is a structured buffer of GpuParticleData elements. +struct GpuParticleData +{ + // Position of the particle in world space + float3 position; + // Age of the particle in seconds + float age; + // Particle velocity in world space + float3 velocity; + // 16 low bits - emitter index, 16 high bits - random seed for this particle + uint emitterSeed; +}; + +// Per emitter data stored in the emitter buffer. The emitter buffer is a structured buffer of GpuParticleEmitterParams elements. +struct GpuParticleEmitterParams +{ + // Minimal particle lifetime in seconds + float minLifeTime; + // Maximal particle lifetime in seconds + float maxLifeTime; + // Index of the texture in the texture atlas + float textureIndex; + // Particle color over lifetime, defined by a piecewise linear curve of 4 colors + float4 colors[4]; + // Particle sizes over lifetime, defined by a spline of 3 sizes + float3 sizes; + // Variance of the particle size + float sizeVariance; + // Drag force coefficient applied to the particles + float drag; + // Amplitude of the turbulence force applied to the particles + float turbulenceAmplitude; + // Frequency of the turbulence force applied to the particles + float turbulenceFrequency; + // Gravity force applied to the particles + float gravity; + // Attractor force applied to the particle: XYZ - attractor position, W - attractor strength + float4 attractor; + // Velocity stretch factor or rotation speed + // If velocityStretchRotation is negative, it is used as a velocity stretch factor. If it is positive, it is used as a rotation speed in radians per second. + float velocityStretchRotation; +}; + +float GetGpuParticleSize( GpuParticleEmitterParams params, float age, float seed ) +{ + float v = 1.0 - age; + float spline = params.sizes.x * v * v + params.sizes.y * 2.0 * age * v + params.sizes.z * age * age; + return spline * ( 1 + params.sizeVariance * seed ); +} + +float4 GetGpuParticleColor( GpuParticleEmitterParams params, float age, float exponent ) +{ + age = pow( abs( age ), log( exponent ) / log( 0.5 ) ); + float4 color; + if( age <= 0.333 ) + { + color = lerp( params.colors[0], params.colors[1], age / 0.333 ); + } + else if( age <= 0.666 ) + { + color = lerp( params.colors[1], params.colors[2], age / 0.333 - 1 ); + } + else + { + color = lerp( params.colors[2], params.colors[3], age / ( 1 - 0.666 ) - 0.666 / ( 1 - 0.666 ) ); + } + return color; +} + + +// Per particle data stored in the visible buffer. The visible buffer is a structured buffer of GpuParticleVisibleData elements. +struct GpuParticleVisibleData +{ + // Particle index in the particle buffer + uint index; + // Particle depth in projection space + float depth; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/MeshMorphing.fxh b/shaders/include/Carbon/SpaceScene/MeshMorphing.fxh new file mode 100644 index 000000000..41c566326 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/MeshMorphing.fxh @@ -0,0 +1,113 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_MESHMORPHING_FXH +#define CARBON_MESHMORPHING_FXH + + +#include "../VertexBufferUtil.fxh" + +Buffer SharedIndexVertexBuffer ; + +struct MorphTargetAnimationData +{ + uint index; + float weight; +}; +StructuredBuffer MorphTargetAnimations ; + +// the following assumes that all morphed objects will be preprocessed and hence packed +// it also assumes no delta compression is being used +#define MORPH_TARGERT_CONSTANT_SIZE 24 +struct MorphTargetGeometryConstants +{ + uint vertexBufferStride; + uint positionOffset; + uint positionType; + uint tangentOffset; + uint tangentType; + uint vertexCount; +}; + +MorphTargetGeometryConstants GetMorphTargetGeometryConstants( uint4 MorphTargetData ) +{ + MorphTargetGeometryConstants constants; + constants.vertexBufferStride = ReadUInt32( SharedIndexVertexBuffer, MorphTargetData.x + 0 ); + constants.positionOffset = ReadUInt32( SharedIndexVertexBuffer, MorphTargetData.x + 4 ); + constants.positionType = ReadUInt32( SharedIndexVertexBuffer, MorphTargetData.x + 8 ); + constants.tangentOffset = ReadUInt32( SharedIndexVertexBuffer, MorphTargetData.x + 12 ); + constants.tangentType = ReadUInt32( SharedIndexVertexBuffer, MorphTargetData.x + 16 ); + constants.vertexCount = ReadUInt32( SharedIndexVertexBuffer, MorphTargetData.x + 20 ); + + return constants; +} + +void ProcessMorphs( MorphTargetGeometryConstants constants, uint vertexID, uint4 MorphTargetData, inout float3 vtxPos, inout float4 vtxPackedTangent ) +{ + float normalSign = vtxPackedTangent.w; + vtxPackedTangent.w = sqrt( saturate( 1. - dot( vtxPackedTangent.xyz, vtxPackedTangent.xyz ) ) ); + + int dataOffset = MORPH_TARGERT_CONSTANT_SIZE; // MorphTargetGeometryConstants size + dataOffset += MorphTargetData.x; // offset in shared index/vertex buffer + dataOffset += vertexID * constants.vertexBufferStride; + + int posOffset = dataOffset + constants.positionOffset; + int tangentOffset = dataOffset + constants.tangentOffset; + + int morphTargetSize = constants.vertexCount * constants.vertexBufferStride; + + float3 tempPos = vtxPos; + float4 tempPackedTangent = vtxPackedTangent; + for( uint i = 0; i < MorphTargetData.z; i++ ) + { + MorphTargetAnimationData data = MorphTargetAnimations[MorphTargetData.y + i]; + int morphTargetIndex = data.index; + int offset = morphTargetSize * morphTargetIndex; + + float3 pos = ReadFloat32_3( SharedIndexVertexBuffer, posOffset + offset ); + float4 packedTangent = float4( ReadInt16_4( SharedIndexVertexBuffer, tangentOffset + offset ) ); + packedTangent = max( packedTangent * ( 1. / 32767.0f ), float4( -1.0f, -1.0f, -1.0f, -1.0f ) ); + packedTangent.w = sqrt( saturate( 1. - dot( packedTangent.xyz, packedTangent.xyz ) ) ); + + [flatten] + if( dot( packedTangent, tempPackedTangent ) < 0. ) + { + packedTangent = -packedTangent; + } + + vtxPos += data.weight * ( pos - tempPos ); + vtxPackedTangent += data.weight * ( packedTangent - tempPackedTangent ); + } + vtxPackedTangent = normalize( vtxPackedTangent ); + + [flatten] + if( vtxPackedTangent.w < 0. ) + { + vtxPackedTangent = -vtxPackedTangent; + } + + vtxPackedTangent.w = normalSign; +} + +void ProcessMorphs( MorphTargetGeometryConstants constants, uint vertexID, uint4 MorphTargetData, inout float3 vtxPos ) +{ + int dataOffset = MORPH_TARGERT_CONSTANT_SIZE; // MorphTargetGeometryConstants size + dataOffset += MorphTargetData.x; // offset in shared index/vertex buffer + dataOffset += vertexID * constants.vertexBufferStride; + + int posOffset = dataOffset + constants.positionOffset; + + int morphTargetSize = constants.vertexCount * constants.vertexBufferStride; + + float3 tempPos = vtxPos; + for( uint i = 0; i < MorphTargetData.z; i++ ) + { + MorphTargetAnimationData data = MorphTargetAnimations[MorphTargetData.y + i]; + int morphTargetIndex = data.index; + int offset = morphTargetSize * morphTargetIndex; + float3 pos = ReadFloat32_3( SharedIndexVertexBuffer, posOffset + offset ); + vtxPos += data.weight * ( pos - tempPos ); + } +} + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/PerObjectData.fxh b/shaders/include/Carbon/SpaceScene/PerObjectData.fxh new file mode 100644 index 000000000..f36d9bdde --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/PerObjectData.fxh @@ -0,0 +1,194 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_PEROBJECTDATA_FXH +#define CARBON_PEROBJECTDATA_FXH + +#include "../BoneTransforms.fxh" + +// Constant buffer format for per-object data for space scene objects. This data is provided by the engine to the vertex shader stage. +struct EveSpaceObjectDataVS +{ + // world transform of this object + float4x4 WorldMat; + // world transform of this object in the previous frame + float4x4 WorldMatLast; + // inverse world transform of this object + float4x4 InvWorldMat; + // additional ship-data ( components: .x = boosterGain, .y = activation, .z = unused, .w = boundingsphere radius ) + float4 Shipdata; + // clip data for disolve fx ( components: .xyz = center, .w = signed squared size ) + float4 Clipdata1; + // bounding ellipsoid data + float4 EllipsoidRadii; + float4 EllipsoidCenter; + // custom mask data for pattern projection + float4x4 CustomMaskMatrix[2]; + // additional custommap-data ( components: .x = layerCount, components: .y = isMirrored, ) + float4 CustomMaskData[2]; + + uint4 BoneOffsets; // .x = current frame offset, .y = previous frame offset + uint4 MorphTarget; // .x = VertexDataOffset, .y = AnimationDataOffset, .z = MorphTargetsCount, .w = BakedVertexDataOffset + + float4 CustomData; +}; + +// Constant buffer format for per-object data for space scene objects. This data is provided by the engine to the pixel shader stage. +struct EveSpaceObjectDataPS +{ + // world transform of this object + float4x4 WorldMat; + // world transform of this object in the previous frame + float4x4 WorldMatLast; + // inverse world transform of this object + float4x4 InvWorldMat; + + // additional ship-data ( components: .x = boosterGain, .y = activation, .z = dirt level, .w = boundingsphere radius ) + float4 Shipdata; + // clip data for disolve fx ( components: .xyz = center, .w = signed squared size ) + float4 Clipdata1; + // misc data ( .x = signed squared clip sphere 2 radius, .y = impact data offset, .z = clip sphere 2 factor, .w = clip sphere factor) + float4 Miscdata; + // SH lighting coefficients + float4 ShLighting[7]; + // custom mask material ids ( components: .x = material1, .y = material2, .z = material3, .w = material4 ) + float4 CustomMaskMaterialIDs[2]; + // custom mask targets ( components: .x = isTargetMtl1, .y = isTargetMtl2, .z = isTargetMtl3, .w = isTargetMtl4 ) + float4 CustomMaskTargets[2]; + float4 CustomMaskClamps; + float4 ScreenSize; + float4 CustomData; +}; + +// Unified buffer per-object data for space scene objects. Used by shared instanced rendering and read from InstancedPerObjectBuffer. +// It is also possible to construct this data from the VS and PS per-object data structures, but some fields will be zeroed out. +// See CreateEveSpaceObjectData() functions below. +struct EveSpaceObjectData +{ + // world transform of this object + float4x4 WorldMat; + // world transform of this object in the previous frame + float4x4 WorldMatLast; + // inverse world transform of this object + float4x4 InvWorldMat; + // additional ship-data ( components: .x = boosterGain, .y = activation, .z = unused, .w = boundingsphere radius ) + float4 Shipdata; + // clip data for disolve fx ( components: .xyz = center, .w = signed squared size ) + float4 Clipdata1; + // misc data ( .x = signed squared clip sphere 2 radius, .y = impact data offset, .z = clip sphere 2 factor, .w = clip sphere factor) + float4 Miscdata; + + // bounding ellipsoid data + float4 EllipsoidRadii; + float4 EllipsoidCenter; + + // custom mask data + float4x4 CustomMaskMatrix[2]; + // additional custommap-data ( components: .x = layerCount, components: .y = isMirrored, ) + float4 CustomMaskData[2]; + // custom mask material ids ( components: .x = material1, .y = material2, .z = material3, .w = material4 ) + float4 CustomMaskMaterialIDs[2]; + // custom mask targets ( components: .x = isTargetMtl1, .y = isTargetMtl2, .z = isTargetMtl3, .w = isTargetMtl4 ) + float4 CustomMaskTargets[2]; + float4 CustomMaskClamps; + + // .x = current frame offset, .y = previous frame offset + uint4 BoneOffsets; + // .x - tactical camera object type, .y - tactical view transparency type + float4 CustomData; + // SH lighting coefficients + float4 ShLighting[7]; +}; + + +EveSpaceObjectData CreateEveSpaceObjectData( EveSpaceObjectDataVS vsData ) +{ + EveSpaceObjectData perObjectData; + perObjectData.WorldMat = vsData.WorldMat; + perObjectData.WorldMatLast = vsData.WorldMatLast; + perObjectData.InvWorldMat = vsData.InvWorldMat; + perObjectData.Shipdata = vsData.Shipdata; + perObjectData.Clipdata1 = vsData.Clipdata1; + perObjectData.Miscdata = 0; + perObjectData.EllipsoidRadii = vsData.EllipsoidRadii; + perObjectData.EllipsoidCenter = vsData.EllipsoidCenter; + perObjectData.CustomMaskMatrix[0] = vsData.CustomMaskMatrix[0]; + perObjectData.CustomMaskMatrix[1] = vsData.CustomMaskMatrix[1]; + perObjectData.CustomMaskData[0] = vsData.CustomMaskData[0]; + perObjectData.CustomMaskData[1] = vsData.CustomMaskData[1]; + perObjectData.CustomMaskMaterialIDs[0] = 0; + perObjectData.CustomMaskMaterialIDs[1] = 0; + perObjectData.CustomMaskTargets[0] = 0; + perObjectData.CustomMaskTargets[1] = 0; + perObjectData.CustomMaskClamps = 0; + perObjectData.BoneOffsets = vsData.BoneOffsets; + perObjectData.CustomData = vsData.CustomData; + perObjectData.ShLighting[0] = 0; + perObjectData.ShLighting[1] = 0; + perObjectData.ShLighting[2] = 0; + perObjectData.ShLighting[3] = 0; + perObjectData.ShLighting[4] = 0; + perObjectData.ShLighting[5] = 0; + perObjectData.ShLighting[6] = 0; + return perObjectData; +} + +EveSpaceObjectData CreateEveSpaceObjectData( EveSpaceObjectDataPS psData ) +{ + EveSpaceObjectData perObjectData; + perObjectData.WorldMat = psData.WorldMat; + perObjectData.WorldMatLast = psData.WorldMatLast; + perObjectData.InvWorldMat = psData.InvWorldMat; + perObjectData.Shipdata = psData.Shipdata; + perObjectData.Clipdata1 = psData.Clipdata1; + perObjectData.Miscdata = psData.Miscdata; + perObjectData.EllipsoidRadii = 0; + perObjectData.EllipsoidCenter = 0; + perObjectData.CustomMaskMatrix[0] = float4x4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + perObjectData.CustomMaskMatrix[1] = float4x4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + perObjectData.CustomMaskData[0] = 0; + perObjectData.CustomMaskData[1] = 0; + perObjectData.CustomMaskMaterialIDs[0] = psData.CustomMaskMaterialIDs[0]; + perObjectData.CustomMaskMaterialIDs[1] = psData.CustomMaskMaterialIDs[1]; + perObjectData.CustomMaskTargets[0] = psData.CustomMaskTargets[0]; + perObjectData.CustomMaskTargets[1] = psData.CustomMaskTargets[1]; + perObjectData.CustomMaskClamps = psData.CustomMaskClamps; + perObjectData.BoneOffsets = 0; + perObjectData.CustomData = psData.CustomData; + + perObjectData.ShLighting[0] = psData.ShLighting[0]; + perObjectData.ShLighting[1] = psData.ShLighting[1]; + perObjectData.ShLighting[2] = psData.ShLighting[2]; + perObjectData.ShLighting[3] = psData.ShLighting[3]; + perObjectData.ShLighting[4] = psData.ShLighting[4]; + perObjectData.ShLighting[5] = psData.ShLighting[5]; + perObjectData.ShLighting[6] = psData.ShLighting[6]; + // intentionally skipping ScreenSize as I don't think it's used + return perObjectData; +} + +// This buffer contains per-object data for shared instanced rendering. +StructuredBuffer InstancedPerObjectBuffer ; + + +float4x4 GetBoneMatrix( EveSpaceObjectData perObjectData, int boneIndex ) +{ + return GetBoneTransform( perObjectData.BoneOffsets.x + boneIndex ); +} + +float4x4 GetLastBoneMatrix( EveSpaceObjectData perObjectData, int boneIndex ) +{ + return GetBoneTransform( perObjectData.BoneOffsets.y + boneIndex ); +} + +float4x4 GetBoneMatrix( EveSpaceObjectDataVS perObjectData, int boneIndex ) +{ + return GetBoneTransform( perObjectData.BoneOffsets.x + boneIndex ); +} + +float4x4 GetLastBoneMatrix( EveSpaceObjectDataVS perObjectData, int boneIndex ) +{ + return GetBoneTransform( perObjectData.BoneOffsets.y + boneIndex ); +} + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/PerSceneData.fxh b/shaders/include/Carbon/SpaceScene/PerSceneData.fxh new file mode 100644 index 000000000..f1b0e9951 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/PerSceneData.fxh @@ -0,0 +1,90 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_SPACESCENE_PERSCENEDATA_FXH +#define CARBON_SPACESCENE_PERSCENEDATA_FXH + +#include "../System.fxh" + + +struct FroxelPerFrameData +{ + float3 FogColor; + float BackgroundVisibility; + + float BaseDensity; + float MaxDistance; + float MaxDistanceVisibility; + float EnvironmentIntensity; + + float EnvironmentG; + float FogMultiplier; + float _pad1; + float _pad2; + + float4 Planets[2]; +}; + +// Per-scene (per-frame) constant buffer data for the vertex shader. +struct EveSpaceSceneDataVS +{ + // matrices + float4x4 ViewInverseTransposeMat; + float4x4 ViewProjectionMat; + float4x4 ViewMat; + float4x4 ProjectionMat; + float4x4 ShadowViewMat; + float4x4 ShadowViewProjectionMat; + float4x4 EnvMapRotationMat; + float4x4 ViewProjectionLast; + float4x4 ViewLast; + float4x4 ProjLast; + // lighting info + float4 SunDirection; + float4 SunDiffuseColor; + float3 FogFactors; // Depth multiplier in x, constant in y and max fog in z + // rendertarget resolution and fovXY (rt pixelsize in .xy, fovXY in .zw) + float4 TargetResolution; + float4 ViewportAdjustment; + float4 MiscParams; // x - time, y - upscaling amount, zw - viewport size +}; + +static const uint SHADOW_QUALITY_FLAG_DISABLED = 1; +static const uint SHADOW_QUALITY_FLAG_LOW = 1 << 1; +static const uint SHADOW_QUALITY_FLAG_HIGH = 1 << 2; +static const uint SHADOW_QUALITY_FLAG_RAYTRACED = 1 << 3; + +// Per-scene (per-frame) constant buffer data for the pixel shader. +struct EveSpaceSceneDataPS +{ + // some pixel-shaders do need these... + float4x4 ViewInverseTransposeMat; + float4x4 ViewMat; + float4x4 EnvMapRotationMat; + + // scene info + float4 SunDirection; + float4 SunDiffuseColor; // .rgb - sun diffuse color, .a - environment map diffuse "roughness" + float4 AmbientColor; // .rgb - ambient color, .a - nebula intensity + float4 FogColor; + + // various info + float4 ViewportOffsetSize; // .xy - viewport offset, .zw - viewport size + float4 RenderTargetData; // .xy - RT size in pixel, z - MSAA sample count (legacy, always 1 nowadays), w - debug + float4 ShadowMapSettings; // .xyzw - various shadow filter parameters + float4 ShadowMapSettings2; // .xy - shadow camera range, .z - shadow lightness, .w - shadow quality, uint (SHADOW_QUALITY_FLAG_*) + float4 ProjectionData; // x - projection[3][2], y - projection[2][2], .zw = FoV + float4 MiscData; // x - time, y - scenemiplodbias, z - upscaling amount, w - gamma brightness + uint4 MiscData2; // x - frame counter, y - Jittering (bool), z - InverseShadowMapAtlasSize (float), w - ShadowMapAtlasEntryMinSizeLog2 (uint) + float4 VolumetricSlices; + float4 ShadowMapValues[4]; // x = zFar value[0], y = zFar value[1], z = zFar value[2], w = zFar value[3]..etc + float4x4 ShadowMatrix[16]; // Matrix that takes a coordinate from view space all the way to the packed cascades + float4 SplitInfo; // x = NrOfSplits, y = , z = , w = + float4x4 ProjectionInverseMat; // + float4 CascadeRanges[16]; + + //Froxel fog related global variables + FroxelPerFrameData FroxelFogData; +}; + + +#endif // CARBON_SPACESCENE_PERSCENEDATA_FXH \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/Raytracing/GeometryData.fxh b/shaders/include/Carbon/SpaceScene/Raytracing/GeometryData.fxh new file mode 100644 index 000000000..674181bcf --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/Raytracing/GeometryData.fxh @@ -0,0 +1,158 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_RAYTRACING_GEOMETRYDATA_FXH +#define CARBON_RAYTRACING_GEOMETRYDATA_FXH + +#include "../../PackedTangents.fxh" +#include "../../VertexBufferUtil.fxh" + +// Constant buffer layout for data describing the geometry of a mesh for ray tracing shaders. +// This is used to read vertex and index data from buffers in a shader. +// Normally the constant buffer for this data is declared as: +// cbuffer RtVertexBufferData : register( b5, space8 ) { RayTracingGeometryData geometryData; } +struct RayTracingGeometryData +{ + // Index into the buffer heap view array for the index buffer of the mesh. + uint indexBufferId; + // Index buffer stride in bytes (2 for 16-bit indices, 4 for 32-bit indices). + uint indexBufferStride; + + // Offset in bytes to the start of the index data in the index buffer. + uint indexOffset; + + // Index into the buffer heap view array for the vertex buffer of the mesh. + uint vertexBufferId; + // Vertex buffer stride in bytes. + uint vertexBufferStride; + + // Offset in bytes to the start of the position (POSITION0 semantics) data in the vertex buffer. + uint positionOffset; + // Data type of the position attribute. See constants in VertexBufferUtil.fxh for possible values. + uint positionType; + + // Offset in bytes to the start of the normal (NORMAL0 semantics) data in the vertex buffer. + uint normalOffset; + // Data type of the normal attribute. See constants in VertexBufferUtil.fxh for possible values. + uint normalType; + + // Offset in bytes to the start of the tangent (TANGENT0 semantics) data in the vertex buffer. + uint tangentOffset; + // Data type of the tangent attribute. See constants in VertexBufferUtil.fxh for possible values. + uint tangentType; + + // Offset in bytes to the start of the bitangent (BITANGENT0 semantics) data in the vertex buffer. + uint bitangentOffset; + // Data type of the bitangent attribute. See constants in VertexBufferUtil.fxh for possible values. + uint bitangentType; + + // Offset in bytes to the start of the first texture coordinate (TEXCOORD0 semantics) data in the vertex buffer. + uint texCoord0Offset; + // Data type of the first texture coordinate attribute. + uint texCoord0Type; + + // Offset in bytes to the start of the second texture coordinate (TEXCOORD1 semantics) data in the vertex buffer. + uint texCoord1Offset; + // Data type of the second texture coordinate attribute. + uint texCoord1Type; + + // Offset in bytes to the start of the third texture coordinate (TEXCOORD2 semantics) data in the vertex buffer. + uint texCoord2Offset; + // Data type of the third texture coordinate attribute. + uint texCoord2Type; + + uint padding; +}; + +// Reads trangle vertex indices from the index buffer for a given primitive index. Returns a uint3 containing the three vertex indices of the triangle. +// Handles both 16-bit and 32-bit index buffers based on the indexBufferStride in the RayTracingGeometryData. Assumes "triangle list" topology. +uint3 ReadIndices( Buffer indexBuffer, RayTracingGeometryData data, uint primitiveIndex ) +{ + uint indicesAddress = ( 3 * primitiveIndex ) * data.indexBufferStride + data.indexOffset; + if( data.indexBufferStride == 2 ) + { + return ReadUInt16_3( indexBuffer, indicesAddress ); + } + else + { + return ReadUInt32_3( indexBuffer, indicesAddress ); + } +} + +// Reads the position of a vertex (POSITION0 semantics) from the vertex buffer for a given vertex index. Returns a float3 containing the position. +// Assumes positions are stored as float32_3 in the vertex buffer. The position offset and stride are taken from the RayTracingGeometryData. +float3 ReadPosition( Buffer vertexBuffer, RayTracingGeometryData data, uint vertexIndex ) +{ + return ReadFloat32_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.positionOffset ); +} + +// Reads the normal, tangent, and bitangent of a vertex from the vertex buffer for a given vertex index. Returns a TangentSpace struct containing the three vectors. +// Supports both unpacked and packed tangent space representations based on the tangentType in the RayTracingGeometryData. +TangentSpace ReadNormalTangentBitangent( Buffer vertexBuffer, RayTracingGeometryData data, uint vertexIndex ) +{ + TangentSpace ts; + if( data.tangentType == FLOAT32_3 ) + { + ts.normal = ReadFloat32_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.normalOffset ); + ts.tangent = ReadFloat32_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.tangentOffset ); + ts.bitangent = ReadFloat32_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.bitangentOffset ); + } + else if( data.tangentType == FLOAT16_3 ) + { + ts.normal = ReadFloat16_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.normalOffset ); + ts.tangent = ReadFloat16_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.tangentOffset ); + ts.bitangent = ReadFloat16_3( vertexBuffer, vertexIndex * data.vertexBufferStride + data.bitangentOffset ); + } + else if( data.tangentType == UBYTE_4_NORM ) + { + // untested... + uint packed = vertexBuffer.Load( ( vertexIndex * data.vertexBufferStride + data.tangentOffset ) >> 2 ); + float4 tangents; + tangents.x = float( ( packed >> 0 ) & 0xFF ); + tangents.y = float( ( packed >> 8 ) & 0xFF ); + tangents.z = float( ( packed >> 16 ) & 0xFF ); + tangents.w = float( ( packed >> 24 ) & 0xFF ); + tangents *= ( 1. / 255. ); + ts = UnpackTangentSpace( tangents ); + } + else if( data.tangentType == USHORT_4_NORM ) + { + // untested... + uint packed0 = vertexBuffer.Load( ( vertexIndex * data.vertexBufferStride + data.tangentOffset + 0 ) >> 2 ); + uint packed1 = vertexBuffer.Load( ( vertexIndex * data.vertexBufferStride + data.tangentOffset + 4 ) >> 2 ); + float4 tangents; + tangents.x = float( ( packed0 >> 0 ) & 0xFFFF ); + tangents.y = float( ( packed0 >> 16 ) & 0xFFFF ); + tangents.z = float( ( packed1 >> 0 ) & 0xFFFF ); + tangents.w = float( ( packed1 >> 16 ) & 0xFFFF ); + tangents *= ( 1. / 65535. ); + ts = UnpackTangentSpace( tangents ); + } + else + { + ts.normal = 0; + ts.tangent = 0; + ts.bitangent = 0; + } + return ts; +} + +// Reads the texture coordinate (TEXCOORD0 semantics) of a vertex from the vertex buffer for a given vertex index. Returns a float2 containing the texture coordinate. +float2 ReadTexcoord0( Buffer vertexBuffer, RayTracingGeometryData data, uint vertexIndex ) +{ + return ReadFloat_2( vertexBuffer, vertexIndex * data.vertexBufferStride + data.texCoord0Offset, data.texCoord0Type ); +} + +// Reads the texture coordinate (TEXCOORD1 semantics) of a vertex from the vertex buffer for a given vertex index. Returns a float2 containing the texture coordinate. +float2 ReadTexcoord1( Buffer vertexBuffer, RayTracingGeometryData data, uint vertexIndex ) +{ + return ReadFloat_2( vertexBuffer, vertexIndex * data.vertexBufferStride + data.texCoord1Offset, data.texCoord1Type ); +} + +// Reads the texture coordinate (TEXCOORD2 semantics) of a vertex from the vertex buffer for a given vertex index. Returns a float2 containing the texture coordinate. +float2 ReadTexcoord2( Buffer vertexBuffer, RayTracingGeometryData data, uint vertexIndex ) +{ + return ReadFloat_2( vertexBuffer, vertexIndex * data.vertexBufferStride + data.texCoord2Offset, data.texCoord2Type ); +} + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/Raytracing/ShadowData.fxh b/shaders/include/Carbon/SpaceScene/Raytracing/ShadowData.fxh new file mode 100644 index 000000000..c397f8f54 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/Raytracing/ShadowData.fxh @@ -0,0 +1,64 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_RAYTRACING_SHADOWDATA_FXH +#define CARBON_RAYTRACING_SHADOWDATA_FXH + + +// Data passed to the shadow ray tracing shaders when rendering main sun light shadows in a "b2" constant buffer using Tr2RaytracingManager. +// Can be used by declaring a constant buffer as follows: +// cbuffer RtShadowsPerFrameBuffer: register( b2 ) { RtShadowsSunData RtShadowsPerFrame; } +struct RtShadowsSunData +{ + // The inverse projection transform of the camera for the current frame. + float4x4 ProjectionInv; + // The inverse view transform of the camera for the current frame. + float4x4 ViewInv; + // XYZ - sun direction in world space, W - sun angular size in radians. + float4 SunDirection; + + // Planet positions and radii in world space. X, Y, Z - position, W - radius. + float4 Planets[2]; + + // The resolution of the render target in pixels. + float2 Resolution; + // The current frame index, used for temporal accumulation of shadows. + uint FrameIndex; +}; + +struct RtShadowsPerLightData +{ + float4 position; // xyz - world position, w - radius + float4 color; // xyz - color, w - bits 0..15 - innerRadius (float 16), bits 16..31 - flags + float4 direction; // see next few lines: + // x bits 0..15 - direction.x (float 16), x bits 16..31 - direction.y (float 16) + // y bits 0..15 - direction.z (float 16), y bits 16..31 - projectionPlaneDistance (float 16) + // z bits 0..15 - outerAngle (float 16), z bits 16..31 - innerAngle (float 16) + // w bits 0..1 - padding, w bits 2..11 - shadowMapScale, w bits 12..21 - shadowMapOffsetX, w bits 22..31 - shadowMapOffsetY +}; + +// Data passed to the shadow ray tracing shaders when rendering main sun light shadows in a "b7" constant buffer using Tr2LightManager. +// Can be used by declaring a constant buffer as follows: +// cbuffer RtShadowsPerFrameBuffer: register( b7 ) { RtShadowsPointLightsData RtShadowsPerFrame; } +struct RtShadowsPointLightsData +{ + // The inverse projection transform of the camera for the current frame. + float4x4 ProjectionInv; + // The inverse view transform of the camera for the current frame. + float4x4 ViewInv; + + // Planet positions and radii in world space. X, Y, Z - position, W - radius. + float4 Planets[2]; + + // The resolution of the render target in pixels. + float2 Resolution; + // Number of dynamic lights in DynamicLights array. + uint NumDynamicLights; + float Padding; + + // Per-light data for dynamic lights in the scene. Up to 16 shadow casting lights are supported. + RtShadowsPerLightData DynamicLights[16]; +}; + + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/SpaceScene/Volumetrics.fxh b/shaders/include/Carbon/SpaceScene/Volumetrics.fxh new file mode 100644 index 000000000..39ff72939 --- /dev/null +++ b/shaders/include/Carbon/SpaceScene/Volumetrics.fxh @@ -0,0 +1,224 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_SPACESCENE_VOLUMETRICS_FXH +#define CARBON_SPACESCENE_VOLUMETRICS_FXH + +#include "../System.fxh" +#include "../Intersections.fxh" +#include "PerSceneData.fxh" +#include "../Math.fxh" + +// Depth slices for the rendered cloud volume (see EveChildCloud2) +Texture2DArray EveSceneFogVolumeMap ; + +// 3D froxel fog map and Mie environment map for volumetric effects +Texture3D EveSceneFroxelFogMap ; +TextureCube EveSceneMieEnvironmentMap ; + + +SamplerState EveSceneFogSampler = sampler_state +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; +}; + +SamplerState EveSceneFogVolumeMapSampler = sampler_state +{ + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; +}; + + +float4 _CubicWeights( float x ) +{ + float x2 = x * x; + float x3 = x2 * x; + + const float4x4 m = float4x4( + +2, -3, -3, +5, + -2, +3, +3, +1, + +3, -6, +0, +4, + +1, +0, +0, +0 + ) * (1.0 / 6.0); + + return mul( m, float4( x3, x2, x, 1 ) ); +} + + +// Sample the 3D froxel fog map with bicubic filtering in the XY plane and bilinear filtering in the Z direction. +float3 SampleEveSceneFroxelFogRaw( float2 tc, float z ) +{ + + //Uncomment to disable bicubic filtering + //return EveSceneFroxelFogMap.SampleLevel(EveSceneFogSampler, float3(tc, z), 0.0f); + + + //The following code does 4x4 bicubic filtering on the froxel texture, using only bilinear for Z. + + //float2 size = PerFramePS.FroxelFogData.FroxelResolution.xy; + //float2 invSize = PerFramePS.FroxelFogData.FroxelResolution.zw; + + float width, height, dummy; + EveSceneFroxelFogMap.GetDimensions(width, height, dummy); + float2 size = float2(width, height); + float2 invSize = rcp(size); + + tc = tc * size - 0.5; + + float2 floored = floor(tc); + float2 frac = tc - floored; + + float2 baseTC = floored * invSize; + + float4 weightsX = _CubicWeights(frac.x); + float4 weightsY = _CubicWeights(frac.y); + + + //Since the specific spline used for the cubic weights means that all 4 weights will be over 1.0, + //we can sample a 2x2 area with specific weights for each of the 4 samples by carefully choosing + //the point we sample to achieve the correct ratio of weight between them, and then multiplying + //that ratio by the total of all 4 samples. + + //The problem is simplified due to the fact that we can calculate the weights along X and Y separately. + + //These are the offsets we need to use to get the correct ratio of the texels we want to sample. + float2 offsets0 = float2(weightsX.z / weightsX.x - 0.5, weightsY.z / weightsY.x - 0.5) * invSize + baseTC; //Top left + float2 offsets1 = float2(weightsX.w / weightsX.y + 1.5, weightsY.w / weightsY.y + 1.5) * invSize + baseTC; //Bot right + + //This is the total weight for each of the four samples. + float2 w = float2(weightsX.x, weightsY.x) / float2(weightsX.x + weightsX.y, weightsY.x + weightsY.y); //Top left weight + float2 iw = 1.0 - w; //Bot right weight + + //Do the 4 samples at the correct locations and apply the correct weights. + return + EveSceneFroxelFogMap.SampleLevel(EveSceneFogSampler, float3(offsets0.x, offsets0.y, z), 0.0f) * (( w.x) * ( w.y)) + + EveSceneFroxelFogMap.SampleLevel(EveSceneFogSampler, float3(offsets1.x, offsets0.y, z), 0.0f) * ((iw.x) * ( w.y)) + + EveSceneFroxelFogMap.SampleLevel(EveSceneFogSampler, float3(offsets0.x, offsets1.y, z), 0.0f) * (( w.x) * (iw.y)) + + EveSceneFroxelFogMap.SampleLevel(EveSceneFogSampler, float3(offsets1.x, offsets1.y, z), 0.0f) * ((iw.x) * (iw.y)); +} + + +// Get the froxel fog color for the given screen-space texture coordinates, scene depth. +float4 SampleEveSceneFroxelFog( float2 texCoords, float sceneDepth, EveSpaceSceneDataPS sceneData, bool sampleEnvironmentLighting ) +{ + float4 viewPositionProj = mul(float4(texCoords.x * 2.0 - 1.0, texCoords.y * -2.0 + 1.0, sceneDepth, 1.0), sceneData.ProjectionInverseMat); + float3 viewPosition = viewPositionProj.xyz / viewPositionProj.w; + + + + float dist = min(sceneData.FroxelFogData.MaxDistance, length(viewPosition)); + if(sceneDepth <= 0.0) + { + //If we're at the far plane, override the distance to match the game's expected back clip. + //This makes sure that the fog is the same even if the back clip is adjusted in Graphite. + dist = sceneData.FroxelFogData.MaxDistance; + } + + float4x4 inverseViewMatrix = transpose(sceneData.ViewInverseTransposeMat); + + float3 cameraWorldPosition = mul(float4(0.0, 0.0, 0.0, 1.0), inverseViewMatrix).xyz; + + float3 viewDirection = normalize(viewPosition); + float3 worldDirection = mul(float4(viewDirection, 0.0), inverseViewMatrix).xyz; + + [unroll] + for( int index = 0; index < 2; ++index) + { + float4 sphereData = sceneData.FroxelFogData.Planets[index]; + SphereHitInfo hitInfo = SphereRayHitTest( InitSphere( sphereData.xyz, sphereData.w ), InitRay( cameraWorldPosition, worldDirection ) ); + if( hitInfo.occurred ) + { + dist = min( dist, hitInfo.firstIntersection.w ); + } + } + + float normalizedZ = saturate((exp(-dist * sceneData.FroxelFogData.BaseDensity) - 1.0) / (sceneData.FroxelFogData.MaxDistanceVisibility - 1.0)); + + float3 froxel = SampleEveSceneFroxelFogRaw(texCoords, normalizedZ); + + float extinction = lerp(sceneData.FroxelFogData.BackgroundVisibility, 1.0, exp(-dist * sceneData.FroxelFogData.BaseDensity)); + + + + if( sampleEnvironmentLighting ) + { + float3 environmentLighting = EveSceneMieEnvironmentMap.SampleLevel(EveSceneFogSampler, worldDirection, 0.0).rgb; + + //float shadowing = sceneDepth != 0.0 ? pow(saturate(1.0 - extinction), -sceneData.FroxelFogData.EnvironmentG * 4.0) : 1.0; + float shadowing = 1.0; //Shadowing the environment lighting causes a mismatch between clouds and opaque objects. + + froxel += ((1.0 - extinction) * shadowing * sceneData.FroxelFogData.EnvironmentIntensity) * (environmentLighting * sceneData.FroxelFogData.FogColor); + } + + froxel.rgb *= sceneData.FroxelFogData.FogMultiplier; + + return float4( froxel, extinction ); +} + + +float4 SampleEveSceneFog( float4 pos, EveSpaceSceneDataPS sceneData ) // pos is SV_Position +{ + float t0 = sceneData.VolumetricSlices.x; + float t1 = sceneData.VolumetricSlices.y; + float t2 = sceneData.VolumetricSlices.z; + float t3 = sceneData.VolumetricSlices.w; + + float i0, i1; + float z; + float depth = sceneData.ProjectionData.x / ( pos.z + sceneData.ProjectionData.y ); + float weight = 1; + if( depth > t2 ) + { + i0 = 2; + i1 = 3; + z = saturate( LinStep( t2, t3, depth ) ); + } + else if( depth > t1 ) + { + i0 = 1; + i1 = 2; + z = LinStep(t1, t2, depth); + } + else if( depth > t0 ) + { + i0 = 0; + i1 = 1; + z = LinStep(t0, t1, depth); + } + else + { + i0 = 0; + i1 = 0; + z = LinStep(0, t0, depth); + weight = depth / t0; + } + + float2 texCoords = pos.xy / sceneData.RenderTargetData.xy; + + float4 color0 = EveSceneFogVolumeMap.SampleLevel( EveSceneFogVolumeMapSampler, float3( texCoords, i0 ), 0 ); + float4 color1 = EveSceneFogVolumeMap.SampleLevel( EveSceneFogVolumeMapSampler, float3( texCoords, i1 ), 0 ); + float4 color = lerp( color0, color1, z ); + color *= weight; + return color; +} + +float4 SampleEveSceneVolumetrics( float4 pos, EveSpaceSceneDataPS sceneData ) // pos is SV_Position +{ + float4 result = SampleEveSceneFog( pos, sceneData ); + float2 texCoords = pos.xy / sceneData.RenderTargetData.xy; + float4 froxels = SampleEveSceneFroxelFog( texCoords, pos.z, sceneData, sceneData.FroxelFogData.EnvironmentIntensity > 0.0 ); + froxels.a = 1.0 - froxels.a; + result += (1.0 - result.a) * froxels; + + return result; +} + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/System.fxh b/shaders/include/Carbon/System.fxh new file mode 100644 index 000000000..624090fcc --- /dev/null +++ b/shaders/include/Carbon/System.fxh @@ -0,0 +1,107 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_SYSTEM_FXH +#define CARBON_SYSTEM_FXH + +// This file contains definitions for backend API, including capability flags, and shader quality setting defines. + +// Trinity graphics API backend +#define PLATFORM_DX11 2 +#define PLATFORM_DX12 6 +#define PLATFORM_METAL 10 + +#ifndef PLATFORM +#error Missing value for PLATFORM macro +#endif + +// API supports compute shaders +#define PLATFORM_SUPPORTS_COMPUTE 1 + +// API supports geometry shaders +#if PLATFORM==PLATFORM_DX11 || PLATFORM==PLATFORM_DX12 +#define PLATFORM_SUPPORTS_GEOMETRY_SHADERS 1 +#else +#define PLATFORM_SUPPORTS_GEOMETRY_SHADERS 0 +#endif + +// API supports reading MSAA textures +#define PLATFORM_SUPPORTS_MSAA_READS 1 +// API supports GetDimensions method for textures +#define PLATFORM_SUPPORTS_TEXTURE_SIZE_QUERIES 1 +// API supports .Load methods for textures +#define PLATFORM_SUPPORTS_TEXTURE_LOAD 1 + +// API supports SRV/UAV/Sampler heap views +#if PLATFORM==PLATFORM_METAL || PLATFORM==PLATFORM_DX12 +#define PLATFORM_SUPPORTS_SUPPORTS_HEAP_VIEW 1 +#else +#define PLATFORM_SUPPORTS_SUPPORTS_HEAP_VIEW 0 +#endif + +// Maximum size of constant buffer in bytes +#if PLATFORM==PLATFORM_DX11 || PLATFORM==PLATFORM_DX12 +#define PLATFORM_MAX_CONSTANT_BUFFER_SIZE 65536 +#else +#define PLATFORM_MAX_CONSTANT_BUFFER_SIZE 4096 +#endif + +// API supports ray tracing +#if PLATFORM==PLATFORM_DX12 || PLATFORM==PLATFORM_METAL +#define PLATFORM_SUPPORTS_RAY_TRACING 1 +#else +#define PLATFORM_SUPPORTS_RAY_TRACING 0 +#endif + +// Support for "precise" keyword is for DirectX only +#if PLATFORM==PLATFORM_DX11 || PLATFORM==PLATFORM_DX12 +#define DX11_PRECISE precise +#else +#define DX11_PRECISE +#endif + + +// Shader quality setting. Comes from project user settings via Trinity +#define SHADER_QUALITY_LOW 3 +#define SHADER_QUALITY_MEDIUM 4 +#define SHADER_QUALITY_HIGH 5 + +// Legacy quality defines +#define SM_3_0_LO SHADER_QUALITY_LOW +#define SM_3_0_HI SHADER_QUALITY_MEDIUM +#define SM_3_0_DEPTH SHADER_QUALITY_HIGH + +#ifndef SHADERMODEL +#define SHADERMODEL SHADER_QUALITY_HIGH +#endif + +#ifndef SHADER_QUALITY +#define SHADER_QUALITY SHADERMODEL +#endif + + +// Hits for the shader compiler signifying per-object and per-frame constant buffers. +// Used in global variable declarations. The actual register numbers specified in macros are +// just hints to the compiler, and have nothing to do with the actual binding. Declaration example: +// PerFrameStructType PerFrameVS : register( PERFRAME_VS_STARTREGISTER ); +#define PERFRAME_VS_STARTREGISTER vs, c220 +#define PERFRAME_PS_STARTREGISTER ps, c200 +#define PEROBJECT_VS_STARTREGISTER vs, c16 +#define PEROBJECT_PS_STARTREGISTER ps, c40 + + +#if __INTELLISENSE__ + +// Intellisense stub definions for custom types used by the shader compiler. + +// DepthTexture2D is a custom type recognized by the shader compiler. It is used to declare depth textures in a platform-independent way. +#define DepthTexture2D Texture2D + +// Bindless handles are essentially uint values on all supported platforms, but the shader compiler recognizes them as custom types for additional semantics. +#define BindlessHandleTexture2D uint +#define BindlessHandleTexture3D uint +#define BindlessHandleTextureCube uint +#define BindlessHandleSampler uint + +#endif + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/Tr2CurveLineSet.fxh b/shaders/include/Carbon/Tr2CurveLineSet.fxh new file mode 100644 index 000000000..b1b0793e2 --- /dev/null +++ b/shaders/include/Carbon/Tr2CurveLineSet.fxh @@ -0,0 +1,33 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_TR2CURVELINESET_FXH +#define CARBON_TR2CURVELINESET_FXH + +// Data passed from the engine to the vertex shader stage for Tr2CurveLineSet draw calls. +struct Tr2CurveLineSetVertexInput +{ + // Vertex position in local space + float3 position : POSITION; + // XYZ - offset to next/previous line segment, W - width (incl. -1.f to indicate direction) + float4 lineDir : TEXCOORD0; + // X - 0 or 1 for begin/end, Y - uniform scalar from 0 to 1 across length of line segment, Z - override color border, W - length of vert in uniform scalar + float4 beginEnd : TEXCOORD1; + // X - animation speed, Y - animation scale, Z - lineID + float3 animationData : TEXCOORD2; + // Offset to one after next/previous line segment + float3 nextLineDir : TEXCOORD3; + // Line color + float4 color : COLOR0; + // Override color + float4 overrideColor : COLOR1; + // Overlay color + float4 overlayColor : COLOR2; +}; + +// Data passed from the engine to any shader stage for Tr2CurveLineSet draw calls. +struct Tr2CurveLineSetData +{ + float4x4 WorldMat; +}; + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/UI/UI.fxh b/shaders/include/Carbon/UI/UI.fxh new file mode 100644 index 000000000..4c2701558 --- /dev/null +++ b/shaders/include/Carbon/UI/UI.fxh @@ -0,0 +1,82 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_UI_FXH +#define CARBON_UI_FXH + + +// The following constants mirror the Tr2SpriteObjectBlendMode enum in C++ +static const uint TR2_SBM_NONE = 0; +static const uint TR2_SBM_BLEND = 1; +static const uint TR2_SBM_ADD = 2; +static const uint TR2_SBM_ADDX2 = 3; + +static const uint S2D_TS_TILE_X = 8; +static const uint S2D_TS_TILE_Y = 16; + +static const uint S2D_RT_COLOR = 1 << 3; +static const uint S2D_RT_GLOW = 1 << 4; + + +// The following constants mirror the Tr2SpriteObjectEffect enum in C++ +static const uint TR2_SFX_FILL = 0; +static const uint TR2_SFX_FILL_AA = 1; + +static const uint TR2_SFX_COPY = 32; +static const uint TR2_SFX_DOT = 33; +static const uint TR2_SFX_NOALPHA = 34; +static const uint TR2_SFX_DROPSHADOW = 35; +static const uint TR2_SFX_OUTLINE = 36; +static const uint TR2_SFX_COLOROVERLAY = 37; +static const uint TR2_SFX_SOFTLIGHT = 38; +static const uint TR2_SFX_BLUR = 39; +static const uint TR2_SFX_BLURBACKGROUNDCOLORED = 40; +static const uint TR2_SFX_BLURBACKGROUND = 41; +static const uint TR2_SFX_GLOW = 42; +static const uint TR2_SFX_FONT = 43; +static const uint TR2_SFX_NOISEFADE = 44; + +static const uint TR2_SFX_MODULATE = 64; +static const uint TR2_SFX_MASK = 65; + +#define TR2_SS_MAX_TRANSFORM_COUNT 32 + +// x, y = 1/width, 1/height of the containing atlas texture, always +// z, w = 0.5/width, 0.5/height of the atlas texture: the shift to apply to TEX2D to sample the texel center. +float4 g_texelSizeUI0; +float4 g_texelSizeUI1; + +// x, y = width, height of the viewport in pixels +float4 UIViewportSize; + +// UI transform stack +float4x4 g_uiTransforms[TR2_SS_MAX_TRANSFORM_COUNT] : register( c5 ); + +// Vertex as it is passed from the application to the vertex shader +struct UIVertexInput +{ + // Vertex position + float3 pos : POSITION; + // Sprite color + float4 color : COLOR0; + // Outline color + float4 outlineColor : COLOR1; + // Texture coordinates for the primary texture + float2 tex0 : TEXCOORD0; + // Texture coordinates for the secondary texture + float2 tex1 : TEXCOORD1; + // Clipping rectangle for the sprite (left, top, right, bottom) + float4 clipRect : TEXCOORD2; + // Glow brightness for the sprite + float glowBrightness : TEXCOORD3; + // Threshold value for the opacity of the outline effect + float outlineThreshold : TEXCOORD4; + + // data.x : transform index + // data.y : blend mode + // data.z : sprite effect (0-2 bits), glow bit (3 bit) + // data.w : tile mode + uint4 data : BLENDINDICES; +}; + + +#endif \ No newline at end of file diff --git a/shaders/include/Carbon/VertexBufferUtil.fxh b/shaders/include/Carbon/VertexBufferUtil.fxh new file mode 100644 index 000000000..6a1a25885 --- /dev/null +++ b/shaders/include/Carbon/VertexBufferUtil.fxh @@ -0,0 +1,346 @@ +// Copyright © 2026 CCP ehf. + +#ifndef CARBON_VERTEX_BUFFER_UTIL_FXH +#define CARBON_VERTEX_BUFFER_UTIL_FXH + +// ------------------------------------------------------------------------ +// --- mirrors Tr2VertexDefinition::DataType, see Tr2VertexDefinition.h --- +// ------------------------------------------------------------------------ +static const uint DT_INT8 = 0u; +static const uint DT_INT16 = 1u; +static const uint DT_INT32 = 2u; +static const uint DT_FLOAT16 = 3u; +static const uint DT_FLOAT32 = 4u; +static const uint DT_TYPE_MASK = 7u; +static const uint DT_UNSIGNED_BIT = ( 1u << 3u ); +static const uint DT_NORMALIZED_BIT = ( 1u << 4u ); +static const uint DT_SIZE_OFFSET = 5u; +static const uint DT_SIZE_MASK = ( 3u << DT_SIZE_OFFSET ); +static const uint DT_SIZE_1 = ( 0u << DT_SIZE_OFFSET ); +static const uint DT_SIZE_2 = ( 1u << DT_SIZE_OFFSET ); +static const uint DT_SIZE_3 = ( 2u << DT_SIZE_OFFSET ); +static const uint DT_SIZE_4 = ( 3u << DT_SIZE_OFFSET ); +static const uint BYTE_1 = ( DT_INT8 | DT_SIZE_1 ); +static const uint BYTE_2 = ( DT_INT8 | DT_SIZE_2 ); +static const uint BYTE_3 = ( DT_INT8 | DT_SIZE_3 ); +static const uint BYTE_4 = ( DT_INT8 | DT_SIZE_4 ); +static const uint UBYTE_1 = ( DT_INT8 | DT_SIZE_1 | DT_UNSIGNED_BIT ); +static const uint UBYTE_2 = ( DT_INT8 | DT_SIZE_2 | DT_UNSIGNED_BIT ); +static const uint UBYTE_3 = ( DT_INT8 | DT_SIZE_3 | DT_UNSIGNED_BIT ); +static const uint UBYTE_4 = ( DT_INT8 | DT_SIZE_4 | DT_UNSIGNED_BIT ); +static const uint SHORT_1 = ( DT_INT16 | DT_SIZE_1 ); +static const uint SHORT_2 = ( DT_INT16 | DT_SIZE_2 ); +static const uint SHORT_3 = ( DT_INT16 | DT_SIZE_3 ); +static const uint SHORT_4 = ( DT_INT16 | DT_SIZE_4 ); +static const uint USHORT_1 = ( DT_INT16 | DT_SIZE_1 | DT_UNSIGNED_BIT ); +static const uint USHORT_2 = ( DT_INT16 | DT_SIZE_2 | DT_UNSIGNED_BIT ); +static const uint USHORT_3 = ( DT_INT16 | DT_SIZE_3 | DT_UNSIGNED_BIT ); +static const uint USHORT_4 = ( DT_INT16 | DT_SIZE_4 | DT_UNSIGNED_BIT ); +static const uint INT32_1 = ( DT_INT32 | DT_SIZE_1 ); +static const uint INT32_2 = ( DT_INT32 | DT_SIZE_2 ); +static const uint INT32_3 = ( DT_INT32 | DT_SIZE_3 ); +static const uint INT32_4 = ( DT_INT32 | DT_SIZE_4 ); +static const uint UINT32_1 = ( DT_INT32 | DT_SIZE_1 | DT_UNSIGNED_BIT ); +static const uint UINT32_2 = ( DT_INT32 | DT_SIZE_2 | DT_UNSIGNED_BIT ); +static const uint UINT32_3 = ( DT_INT32 | DT_SIZE_3 | DT_UNSIGNED_BIT ); +static const uint UINT32_4 = ( DT_INT32 | DT_SIZE_4 | DT_UNSIGNED_BIT ); +static const uint FLOAT16_1 = ( DT_FLOAT16 | DT_SIZE_1 ); +static const uint FLOAT16_2 = ( DT_FLOAT16 | DT_SIZE_2 ); +static const uint FLOAT16_3 = ( DT_FLOAT16 | DT_SIZE_3 ); +static const uint FLOAT16_4 = ( DT_FLOAT16 | DT_SIZE_4 ); +static const uint UFLOAT16_1 = ( DT_FLOAT16 | DT_SIZE_1 | DT_UNSIGNED_BIT ); +static const uint UFLOAT16_2 = ( DT_FLOAT16 | DT_SIZE_2 | DT_UNSIGNED_BIT ); +static const uint UFLOAT16_3 = ( DT_FLOAT16 | DT_SIZE_3 | DT_UNSIGNED_BIT ); +static const uint UFLOAT16_4 = ( DT_FLOAT16 | DT_SIZE_4 | DT_UNSIGNED_BIT ); +static const uint FLOAT32_1 = ( DT_FLOAT32 | DT_SIZE_1 ); +static const uint FLOAT32_2 = ( DT_FLOAT32 | DT_SIZE_2 ); +static const uint FLOAT32_3 = ( DT_FLOAT32 | DT_SIZE_3 ); +static const uint FLOAT32_4 = ( DT_FLOAT32 | DT_SIZE_4 ); +static const uint UFLOAT32_1 = ( DT_FLOAT32 | DT_SIZE_1 | DT_UNSIGNED_BIT ); +static const uint UFLOAT32_2 = ( DT_FLOAT32 | DT_SIZE_2 | DT_UNSIGNED_BIT ); +static const uint UFLOAT32_3 = ( DT_FLOAT32 | DT_SIZE_3 | DT_UNSIGNED_BIT ); +static const uint UFLOAT32_4 = ( DT_FLOAT32 | DT_SIZE_4 | DT_UNSIGNED_BIT ); +static const uint BYTE_1_NORM = ( BYTE_1 | DT_NORMALIZED_BIT ); +static const uint BYTE_2_NORM = ( BYTE_2 | DT_NORMALIZED_BIT ); +static const uint BYTE_3_NORM = ( BYTE_3 | DT_NORMALIZED_BIT ); +static const uint BYTE_4_NORM = ( BYTE_4 | DT_NORMALIZED_BIT ); +static const uint UBYTE_1_NORM = ( UBYTE_1 | DT_NORMALIZED_BIT ); +static const uint UBYTE_2_NORM = ( UBYTE_2 | DT_NORMALIZED_BIT ); +static const uint UBYTE_3_NORM = ( UBYTE_3 | DT_NORMALIZED_BIT ); +static const uint UBYTE_4_NORM = ( UBYTE_4 | DT_NORMALIZED_BIT ); +static const uint SHORT_1_NORM = ( SHORT_1 | DT_NORMALIZED_BIT ); +static const uint SHORT_2_NORM = ( SHORT_2 | DT_NORMALIZED_BIT ); +static const uint SHORT_3_NORM = ( SHORT_3 | DT_NORMALIZED_BIT ); +static const uint SHORT_4_NORM = ( SHORT_4 | DT_NORMALIZED_BIT ); +static const uint USHORT_1_NORM = ( USHORT_1 | DT_NORMALIZED_BIT ); +static const uint USHORT_2_NORM = ( USHORT_2 | DT_NORMALIZED_BIT ); +static const uint USHORT_3_NORM = ( USHORT_3 | DT_NORMALIZED_BIT ); +static const uint USHORT_4_NORM = ( USHORT_4 | DT_NORMALIZED_BIT ); +static const uint DT_UNKNOWN_TYPE = 0xffFFffFFu; + +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ + +// the following assumes addresses are aligned with size of the respective datatype +// I couldn't find vertex buffer alignment rules for dx12, so I'm basing this on vulkan... +// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#fxvertex-input-extraction +// "attribAddress must be a multiple of the size in bytes of the size of the format" + + +// Read Write Buffers + +uint ReadUInt32( RWBuffer buffer, uint address ) +{ + return buffer[( address + 0 ) >> 2]; +} + +uint2 ReadUInt32_2( RWBuffer buffer, uint address ) +{ + uint2 result; + result.x = buffer[( address + 0 ) >> 2]; + result.y = buffer[( address + 4 ) >> 2]; + return result; +} + +uint3 ReadUInt32_3( RWBuffer buffer, uint address ) +{ + uint3 result; + result.x = buffer[( address + 0 ) >> 2]; + result.y = buffer[( address + 4 ) >> 2]; + result.z = buffer[( address + 8 ) >> 2]; + return result; +} + +float ReadFloat32( RWBuffer buffer, uint address ) +{ + return asfloat( ReadUInt32( buffer, address ) ); +} + +float2 ReadFloat32_2( RWBuffer buffer, uint address ) +{ + return asfloat( ReadUInt32_2( buffer, address ) ); +} + +float3 ReadFloat32_3( RWBuffer buffer, uint address ) +{ + return asfloat( ReadUInt32_3( buffer, address ) ); +} + +uint4 ReadUInt16_4( RWBuffer buffer, uint address ) +{ + uint4 result; + uint packed0 = buffer[( address + 0 ) >> 2]; + uint packed1 = buffer[( address + 4 ) >> 2]; + result.x = packed0 & 0xFFFF; + result.y = packed0 >> 16; + result.z = packed1 & 0xFFFF; + result.w = packed1 >> 16; + return result; +} + +int4 ReadInt16_4( RWBuffer buffer, uint address ) +{ + int4 result; + uint packed0 = buffer[( address + 0 ) >> 2]; + uint packed1 = buffer[( address + 4 ) >> 2]; + result.x = asint( ( packed0 & 0xFFFF ) << 16 ) >> 16; + result.y = asint( packed0 & 0xFFFF0000 ) >> 16; + result.z = asint( ( packed1 & 0xFFFF ) << 16 ) >> 16; + result.w = asint( packed1 & 0xFFFF0000 ) >> 16; + return result; +} + +// Read Only Buffers + +uint ReadUInt16( Buffer buffer, uint address ) +{ + uint result; + uint packed = buffer.Load( address >> 2 ); + if( ( address & 3 ) == 0 ) + { + result = packed & 0xFFFF; + } + else + { + result = packed >> 16; + } + return result; +} + +uint2 ReadUInt16_2( Buffer buffer, uint address ) +{ + uint2 result; + uint packed = buffer.Load( ( address + 0 ) >> 2 ); + result.x = packed & 0xFFFF; + result.y = packed >> 16; + return result; +} + +uint3 ReadUInt16_3( Buffer buffer, uint address ) +{ + uint3 result; + uint packed0 = buffer.Load( ( address + 0 ) >> 2 ); + uint packed1 = buffer.Load( ( address + 4 ) >> 2 ); + if( ( address & 3 ) == 0 ) + { + result.x = packed0 & 0xFFFF; + result.y = packed0 >> 16; + result.z = packed1 & 0xFFFF; + } + else + { + result.x = packed0 >> 16; + result.y = packed1 & 0xFFFF; + result.z = packed1 >> 16; + } + return result; +} + +uint4 ReadUInt16_4( Buffer buffer, uint address ) +{ + uint4 result; + uint packed0 = buffer.Load( ( address + 0 ) >> 2 ); + uint packed1 = buffer.Load( ( address + 4 ) >> 2 ); + result.x = packed0 & 0xFFFF; + result.y = packed0 >> 16; + result.z = packed1 & 0xFFFF; + result.w = packed1 >> 16; + return result; +} + +int4 ReadInt16_4( Buffer buffer, uint address ) +{ + int4 result; + uint packed0 = buffer.Load( ( address + 0 ) >> 2 ); + uint packed1 = buffer.Load( ( address + 4 ) >> 2 ); + result.x = asint( ( packed0 & 0xFFFF ) << 16 ) >> 16; + result.y = asint( packed0 & 0xFFFF0000 ) >> 16; + result.z = asint( ( packed1 & 0xFFFF ) << 16 ) >> 16; + result.w = asint( packed1 & 0xFFFF0000 ) >> 16; + return result; +} + + +uint ReadUInt32( Buffer buffer, uint address ) +{ + return buffer.Load( ( address + 0 ) >> 2 ); +} + +uint2 ReadUInt32_2( Buffer buffer, uint address ) +{ + uint2 result; + result.x = buffer.Load( ( address + 0 ) >> 2 ); + result.y = buffer.Load( ( address + 4 ) >> 2 ); + return result; +} + +uint3 ReadUInt32_3( Buffer buffer, uint address ) +{ + uint3 result; + result.x = buffer.Load( ( address + 0 ) >> 2 ); + result.y = buffer.Load( ( address + 4 ) >> 2 ); + result.z = buffer.Load( ( address + 8 ) >> 2 ); + return result; +} + +float ReadFloat16( Buffer buffer, uint address ) +{ + return f16tof32( ReadUInt16( buffer, address ) ); +} + +float2 ReadFloat16_2( Buffer buffer, uint address ) +{ + return f16tof32( ReadUInt16_2( buffer, address ) ); +} + +float3 ReadFloat16_3( Buffer buffer, uint address ) +{ + return f16tof32( ReadUInt16_3( buffer, address ) ); +} + +float ReadFloat32( Buffer buffer, uint address ) +{ + return asfloat( ReadUInt32( buffer, address ) ); +} + +float2 ReadFloat32_2( Buffer buffer, uint address ) +{ + return asfloat( ReadUInt32_2( buffer, address ) ); +} + +float3 ReadFloat32_3( Buffer buffer, uint address ) +{ + return asfloat( ReadUInt32_3( buffer, address ) ); +} + +float2 ReadFloat_2( Buffer buffer, uint address, uint dataType ) +{ + if( dataType == FLOAT16_2 ) + { + return ReadFloat16_2( buffer, address ); + } + else if( dataType == FLOAT32_2 ) + { + return ReadFloat32_2( buffer, address ); + } + else + { + return 0.0.xx; + } +} + +void WriteInt16_4( RWBuffer buffer, uint address, int4 data ) +{ + uint packed0 = ( ( asuint( data.x ) & 0xFFFF ) | ( asuint( data.y ) << 16 ) ); + uint packed1 = ( ( asuint( data.z ) & 0xFFFF ) | ( asuint( data.w ) << 16 ) ); + buffer[( address + 0 ) >> 2] = packed0; + buffer[( address + 4 ) >> 2] = packed1; +} + +void WriteUInt16_4( RWBuffer buffer, uint address, uint4 data ) +{ + uint packed0 = ( data.x & 0xFFFF ) | data.y << 16; + uint packed1 = ( data.z & 0xFFFF ) | data.w << 16; + + buffer[( address + 0 ) >> 2] = packed0; + buffer[( address + 4 ) >> 2] = packed1; +} + +void WriteUInt32_2( RWBuffer buffer, uint address, uint2 data ) +{ + buffer[( address + 0 ) >> 2] = data.x; + buffer[( address + 4 ) >> 2] = data.y; +} + +void WriteUInt32_3( RWBuffer buffer, uint address, uint3 data ) +{ + buffer[( address + 0 ) >> 2] = data.x; + buffer[( address + 4 ) >> 2] = data.y; + buffer[( address + 8 ) >> 2] = data.z; +} + +void WriteUInt32_4( RWBuffer buffer, uint address, uint4 data ) +{ + buffer[( address + 0 ) >> 2] = data.x; + buffer[( address + 4 ) >> 2] = data.y; + buffer[( address + 8 ) >> 2] = data.z; + buffer[( address + 12 ) >> 2] = data.w; +} + +void WriteFloat32_2( RWBuffer buffer, uint address, float2 data ) +{ + WriteUInt32_2( buffer, address, asuint( data ) ); +} + +void WriteFloat32_3( RWBuffer buffer, uint address, float3 data ) +{ + WriteUInt32_3( buffer, address, asuint( data ) ); +} + +void WriteFloat32_4( RWBuffer buffer, uint address, float4 data ) +{ + WriteUInt32_4( buffer, address, asuint( data ) ); +} + +#endif // VERTEX_BUFFER_UTIL_FXH