Parallax Corrected Cubemaps: Difference between revisions
GamerDude27 (talk | contribs) m (Better comparison image width (previous would break formatting on mobile)) |
GamerDude27 (talk | contribs) (Cleanup, better template/tag usage, use "syntaxhighlight" instead of deprecated "source" tag) |
||
Line 7: | Line 7: | ||
==Overview== | ==Overview== | ||
{{src|1.bold}}'s native [[cubemap]] implementation does not allow them to follow the player's perspective. While this is passable in most cases, tying the reflections to the player's view can increase realism, especially when using high-resolution reflections. A method to achieve this is called ''parallax correction''. | {{src|1.bold}}'s native [[cubemap]] implementation does not allow them to follow the player's perspective. While this is passable in most cases, tying the reflections to the player's view can increase realism, especially when using high-resolution reflections. A method to achieve this is called ''parallax correction''. | ||
Parallax-corrected cubemaps use a [[parallax_obb|bounding box]] brush to bake their reflection based on a specified area around them, and a custom [[shader]] to make use of it. | Parallax-corrected cubemaps use a [[parallax_obb|bounding box]] brush to bake their reflection based on a specified area around them, and a custom [[shader]] to make use of it. | ||
Line 23: | Line 23: | ||
===cubemap.cpp=== | ===cubemap.cpp=== | ||
Add the following below the <code>SideHasCubemapAndWasntManuallyReferenced( int iSide )</code> function: | Add the following below the <code>SideHasCubemapAndWasntManuallyReferenced( int iSide )</code> function: | ||
< | <syntaxhighlight lang="cpp"> | ||
char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES]; | char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES]; | ||
</ | </syntaxhighlight> | ||
Then, modify the <code>CubeMap_InsertSample( ... )</code> function declaration as follows: | Then, modify the <code>CubeMap_InsertSample( ... )</code> function declaration as follows: | ||
< | <syntaxhighlight lang="cpp"> | ||
void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr = "" ) | void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr = "" ) | ||
</ | </syntaxhighlight> | ||
At the top of the function body, add: | At the top of the function body, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
g_pParallaxObbStrs[g_nCubemapSamples] = pParallaxObbStr; | g_pParallaxObbStrs[g_nCubemapSamples] = pParallaxObbStr; | ||
</ | </syntaxhighlight> | ||
Update the <code>PatchEnvmapForMaterialAndDependents( ... )</code> function declaration to: | Update the <code>PatchEnvmapForMaterialAndDependents( ... )</code> function declaration to: | ||
< | <syntaxhighlight lang="cpp"> | ||
static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture, const char *pParallaxObbMatrix = "" ) | static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture, const char *pParallaxObbMatrix = "" ) | ||
</ | </syntaxhighlight> | ||
Within the <code>if ( pDependentMaterial )</code> statement, change the code to: | Within the <code>if ( pDependentMaterial )</code> statement, change the code to: | ||
< | <syntaxhighlight lang="cpp"> | ||
bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix ); | bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix ); | ||
</ | </syntaxhighlight> | ||
Change | Change | ||
< | <syntaxhighlight lang="cpp"> | ||
MaterialPatchInfo_t pPatchInfo[2]; | MaterialPatchInfo_t pPatchInfo[2]; | ||
</ | </syntaxhighlight> | ||
to: | to: | ||
< | <syntaxhighlight lang="cpp"> | ||
MaterialPatchInfo_t pPatchInfo[6]; | MaterialPatchInfo_t pPatchInfo[6]; | ||
</ | </syntaxhighlight> | ||
Above the line | Above the line | ||
< | <syntaxhighlight lang="cpp"> | ||
char pDependentPatchedMaterialName[1024]; | char pDependentPatchedMaterialName[1024]; | ||
</ | </syntaxhighlight> | ||
add: | add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemap matrix | // Parallax cubemap matrix | ||
CUtlVector<char *> matRowList; | CUtlVector<char *> matRowList; | ||
Line 81: | Line 81: | ||
++nPatchCount; | ++nPatchCount; | ||
} | } | ||
</ | </syntaxhighlight> | ||
At the bottom of the function, change: | At the bottom of the function, change: | ||
< | <syntaxhighlight lang="cpp"> | ||
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); | CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); | ||
</ | </syntaxhighlight> | ||
to: | to: | ||
< | <syntaxhighlight lang="cpp"> | ||
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT ); | CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT ); | ||
</ | </syntaxhighlight> | ||
Then add: | Then add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Clean up parallax stuff | // Clean up parallax stuff | ||
matRowList.PurgeAndDeleteElements(); | matRowList.PurgeAndDeleteElements(); | ||
</ | </syntaxhighlight> | ||
Update the <code>Cubemap_CreateTexInfo( ... )</code> function declaration to: | Update the <code>Cubemap_CreateTexInfo( ... )</code> function declaration to: | ||
< | <syntaxhighlight lang="cpp"> | ||
static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3], int cubemapIndex ) | static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3], int cubemapIndex ) | ||
</ | </syntaxhighlight> | ||
After | After | ||
< | <syntaxhighlight lang="cpp"> | ||
GeneratePatchedName( "c", info, false, pTextureName, 1024 ); | GeneratePatchedName( "c", info, false, pTextureName, 1024 ); | ||
</ | </syntaxhighlight> | ||
add: | add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Append origin info if this cubemap has a parallax OBB | // Append origin info if this cubemap has a parallax OBB | ||
char originAppendedString[1024] = ""; | char originAppendedString[1024] = ""; | ||
Line 115: | Line 115: | ||
Q_snprintf( originAppendedString, 1024, "%s;[%d %d %d]", g_pParallaxObbStrs[cubemapIndex], origin[0], origin[1], origin[2] ); | Q_snprintf( originAppendedString, 1024, "%s;[%d %d %d]", g_pParallaxObbStrs[cubemapIndex], origin[0], origin[1], origin[2] ); | ||
} | } | ||
</ | </syntaxhighlight> | ||
Change | Change | ||
< | <syntaxhighlight lang="cpp"> | ||
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) | if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) | ||
</ | </syntaxhighlight> | ||
to: | to: | ||
< | <syntaxhighlight lang="cpp"> | ||
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) ) | if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) ) | ||
</ | </syntaxhighlight> | ||
In the <code>Cubemap_FixupBrushSidesMaterials( void )</code> function, change: | In the <code>Cubemap_FixupBrushSidesMaterials( void )</code> function, change: | ||
< | <syntaxhighlight lang="cpp"> | ||
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); | pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); | ||
</ | </syntaxhighlight> | ||
to: | to: | ||
< | <syntaxhighlight lang="cpp"> | ||
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID ); | pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID ); | ||
</ | </syntaxhighlight> | ||
In <code>Cubemap_AttachDefaultCubemapToSpecularSides( void )</code>, change: | In <code>Cubemap_AttachDefaultCubemapToSpecularSides( void )</code>, change: | ||
< | <syntaxhighlight lang="cpp"> | ||
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); | pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); | ||
</ | </syntaxhighlight> | ||
to: | to: | ||
< | <syntaxhighlight lang="cpp"> | ||
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap ); | pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap ); | ||
</ | </syntaxhighlight> | ||
This completes the changes for | This completes the changes for {{path|cubemap|cpp}}. | ||
===vbsp.h=== | ===vbsp.h=== | ||
Change the declaration: | Change the declaration: | ||
< | <syntaxhighlight lang="cpp"> | ||
void Cubemap_InsertSample( const Vector& origin, int size ); | void Cubemap_InsertSample( const Vector& origin, int size ); | ||
</ | </syntaxhighlight> | ||
to: | to: | ||
< | <syntaxhighlight lang="cpp"> | ||
void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr ); | void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr ); | ||
</ | </syntaxhighlight> | ||
Above that, add: | Above that, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
extern char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES]; | extern char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES]; | ||
</ | </syntaxhighlight> | ||
This completes the changes for | This completes the changes for {{path|vbsp|h}}. | ||
===map.cpp=== | ===map.cpp=== | ||
At the top, add: | At the top, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
#include "matrixinvert.h" | #include "matrixinvert.h" | ||
</ | </syntaxhighlight> | ||
In the <code>LoadEntityCallback( ... )</code> function, after: | In the <code>LoadEntityCallback( ... )</code> function, after: | ||
< | <syntaxhighlight lang="cpp"> | ||
const char *pSideListStr = ValueForKey( mapent, "sides" ); | const char *pSideListStr = ValueForKey( mapent, "sides" ); | ||
</ | </syntaxhighlight> | ||
add: | add: | ||
< | <syntaxhighlight lang="cpp"> | ||
char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" ); | char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" ); | ||
</ | </syntaxhighlight> | ||
Replace: | Replace: | ||
< | <syntaxhighlight lang="cpp"> | ||
Cubemap_InsertSample( mapent->origin, size ); | Cubemap_InsertSample( mapent->origin, size ); | ||
</ | </syntaxhighlight> | ||
with: | with: | ||
< | <syntaxhighlight lang="cpp"> | ||
Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr ); | Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr ); | ||
</ | </syntaxhighlight> | ||
After the closing bracket of the <code>if</code> statement, add: | After the closing bracket of the <code>if</code> statement, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// | // | ||
// parallax_obb brushes are removed after the transformation matrix is found and saved into | // parallax_obb brushes are removed after the transformation matrix is found and saved into | ||
Line 268: | Line 268: | ||
return ( ChunkFile_Ok ); | return ( ChunkFile_Ok ); | ||
} | } | ||
</ | </syntaxhighlight> | ||
In the <code>LoadMapFile( ... )</code> function, after: | In the <code>LoadMapFile( ... )</code> function, after: | ||
< | <syntaxhighlight lang="cpp"> | ||
g_LoadingMap->CheckForInstances( pszFileName ); | g_LoadingMap->CheckForInstances( pszFileName ); | ||
</ | </syntaxhighlight> | ||
add: | add: | ||
< | <syntaxhighlight lang="cpp"> | ||
if ( g_MainMap == g_LoadingMap ) | if ( g_MainMap == g_LoadingMap ) | ||
{ | { | ||
Line 300: | Line 300: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
This completes the changes for | This completes the changes for {{path|map|cpp}}. | ||
The | The {{vbsp|4}} project should now compile without errors. If errors occur, review the changes for accuracy. | ||
Once {{code|vbsp.exe}} is compiled, move it to the game's {{ | Once {{code|vbsp.exe}} is compiled, move it to the game's {{path|bin/}} folder (not the mod's {{code|bin/}}; the game's - typically the same one where [[Hammer]] is located). | ||
{{warning|[[Steam]] often replaces custom vbsp.exe with its original version whenever it verifies the file integrity of the game.{{workaround|To prevent the loss of the custom vbsp.exe, rename it, then point to it in [[Hammer]]'s game configuration.}}}} | {{warning|[[Steam]] often replaces custom vbsp.exe with its original version whenever it verifies the file integrity of the game.{{workaround|To prevent the loss of the custom vbsp.exe, rename it, then point to it in [[Hammer]]'s game configuration.}}}} | ||
Line 323: | Line 323: | ||
====lightmappedgeneric_dx9_helper.h==== | ====lightmappedgeneric_dx9_helper.h==== | ||
Add the following lines to the end of the <code>LightmappedGeneric_DX9_Vars_t</code> struct (after the <code>int m_nOutlineEnd1;</code> member): | Add the following lines to the end of the <code>LightmappedGeneric_DX9_Vars_t</code> struct (after the <code>int m_nOutlineEnd1;</code> member): | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
int m_nEnvmapParallaxObb1; | int m_nEnvmapParallaxObb1; | ||
Line 329: | Line 329: | ||
int m_nEnvmapParallaxObb3; | int m_nEnvmapParallaxObb3; | ||
int m_nEnvmapOrigin; | int m_nEnvmapOrigin; | ||
</ | </syntaxhighlight> | ||
====lightmappedgeneric_dx9.cpp==== | ====lightmappedgeneric_dx9.cpp==== | ||
Add the following lines inside the | Add the following lines inside the | ||
< | <syntaxhighlight lang="cpp"> | ||
BEGIN_SHADER_PARAMS | BEGIN_SHADER_PARAMS | ||
</ | </syntaxhighlight> | ||
block, after the | block, after the | ||
< | <syntaxhighlight lang="cpp"> | ||
SHADER_PARAM( OUTLINEEND1, SHADER_PARAM_TYPE_FLOAT, "0.0", "outer end value for outline") | SHADER_PARAM( OUTLINEEND1, SHADER_PARAM_TYPE_FLOAT, "0.0", "outer end value for outline") | ||
</ | </syntaxhighlight> | ||
line: | line: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
SHADER_PARAM( ENVMAPPARALLAXOBB1, SHADER_PARAM_TYPE_VEC4, "[1 0 0 0]", "The first line of the parallax correction OBB matrix" ) | SHADER_PARAM( ENVMAPPARALLAXOBB1, SHADER_PARAM_TYPE_VEC4, "[1 0 0 0]", "The first line of the parallax correction OBB matrix" ) | ||
Line 347: | Line 347: | ||
SHADER_PARAM( ENVMAPPARALLAXOBB3, SHADER_PARAM_TYPE_VEC4, "[0 0 1 0]", "The third line of the parallax correction OBB matrix" ) | SHADER_PARAM( ENVMAPPARALLAXOBB3, SHADER_PARAM_TYPE_VEC4, "[0 0 1 0]", "The third line of the parallax correction OBB matrix" ) | ||
SHADER_PARAM( ENVMAPORIGIN, SHADER_PARAM_TYPE_VEC3, "[0 0 0]", "The world space position of the env_cubemap being corrected" ) | SHADER_PARAM( ENVMAPORIGIN, SHADER_PARAM_TYPE_VEC3, "[0 0 0]", "The world space position of the env_cubemap being corrected" ) | ||
</ | </syntaxhighlight> | ||
In the same file, inside the | In the same file, inside the | ||
< | <syntaxhighlight lang="cpp"> | ||
void SetupVars( LightmappedGeneric_DX9_Vars_t& info ) | void SetupVars( LightmappedGeneric_DX9_Vars_t& info ) | ||
</ | </syntaxhighlight> | ||
function, add the following lines at the end (after <code>info.m_nOutlineEnd1 = OUTLINEEND1;</code>): | function, add the following lines at the end (after <code>info.m_nOutlineEnd1 = OUTLINEEND1;</code>): | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
info.m_nEnvmapParallaxObb1 = ENVMAPPARALLAXOBB1; | info.m_nEnvmapParallaxObb1 = ENVMAPPARALLAXOBB1; | ||
Line 360: | Line 360: | ||
info.m_nEnvmapParallaxObb3 = ENVMAPPARALLAXOBB3; | info.m_nEnvmapParallaxObb3 = ENVMAPPARALLAXOBB3; | ||
info.m_nEnvmapOrigin = ENVMAPORIGIN; | info.m_nEnvmapOrigin = ENVMAPORIGIN; | ||
</ | </syntaxhighlight> | ||
====lightmappedgeneric_dx9_helper.cpp==== | ====lightmappedgeneric_dx9_helper.cpp==== | ||
Inside | Inside | ||
< | <syntaxhighlight lang="cpp"> | ||
void InitParamsLightmappedGeneric_DX9( CBaseVSShader *pShader, IMaterialVar** params, const char *pMaterialName, LightmappedGeneric_DX9_Vars_t &info ) | void InitParamsLightmappedGeneric_DX9( CBaseVSShader *pShader, IMaterialVar** params, const char *pMaterialName, LightmappedGeneric_DX9_Vars_t &info ) | ||
</ | </syntaxhighlight> | ||
add the following lines after <code>InitFloatParam( info.m_nOutlineAlpha, params, 1.0 );</code>: | add the following lines after <code>InitFloatParam( info.m_nOutlineAlpha, params, 1.0 );</code>: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
// Cubemap parallax correction requires all 4 lines (if the 2nd, 3rd, or 4th are undef, undef the first one (checking done on first var) | // Cubemap parallax correction requires all 4 lines (if the 2nd, 3rd, or 4th are undef, undef the first one (checking done on first var) | ||
Line 375: | Line 375: | ||
params[info.m_nEnvmapParallaxObb1]->SetIntValue( 0 ); | params[info.m_nEnvmapParallaxObb1]->SetIntValue( 0 ); | ||
} | } | ||
</ | </syntaxhighlight> | ||
In the | In the | ||
< | <syntaxhighlight lang="cpp"> | ||
void DrawLightmappedGeneric_DX9_Internal( ... ) | void DrawLightmappedGeneric_DX9_Internal( ... ) | ||
</ | </syntaxhighlight> | ||
function, after | function, after | ||
< | <syntaxhighlight lang="cpp"> | ||
bool hasNormalMapAlphaEnvmapMask = IS_FLAG_SET( MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK ); | bool hasNormalMapAlphaEnvmapMask = IS_FLAG_SET( MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK ); | ||
</ | </syntaxhighlight> | ||
add: | add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
bool hasParallaxCorrection = params[info.m_nEnvmapParallaxObb1]->GetType() == MATERIAL_VAR_TYPE_VECTOR; | bool hasParallaxCorrection = params[info.m_nEnvmapParallaxObb1]->GetType() == MATERIAL_VAR_TYPE_VECTOR; | ||
</ | </syntaxhighlight> | ||
Within the <code>if ( g_pHardwareConfig->SupportsPixelShaders_2_b() )</code> block, add before | Within the <code>if ( g_pHardwareConfig->SupportsPixelShaders_2_b() )</code> block, add before | ||
< | <syntaxhighlight lang="cpp"> | ||
SET_STATIC_PIXEL_SHADER( lightmappedgeneric_ps20b ); | SET_STATIC_PIXEL_SHADER( lightmappedgeneric_ps20b ); | ||
</ | </syntaxhighlight> | ||
the following: | the following: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps enabled for 2_0b and onwards | // Parallax cubemaps enabled for 2_0b and onwards | ||
SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, hasParallaxCorrection ); | SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, hasParallaxCorrection ); | ||
</ | </syntaxhighlight> | ||
In the <code>else</code> statement for the above, before <code>SET_STATIC_PIXEL_SHADER( lightmappedgeneric_ps20 );</code>, add: | In the <code>else</code> statement for the above, before <code>SET_STATIC_PIXEL_SHADER( lightmappedgeneric_ps20 );</code>, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, 0 ); // No parallax cubemaps with ps_2_0 :( | SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, 0 ); // No parallax cubemaps with ps_2_0 :( | ||
</ | </syntaxhighlight> | ||
Before <code>pContextData->m_SemiStaticCmdsOut.End();</code>, add: | Before <code>pContextData->m_SemiStaticCmdsOut.End();</code>, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
if ( hasParallaxCorrection ) | if ( hasParallaxCorrection ) | ||
Line 430: | Line 430: | ||
pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 22, &matrix[0][0], 4 ); | pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 22, &matrix[0][0], 4 ); | ||
} | } | ||
</ | </syntaxhighlight> | ||
===The Shader Files=== | ===The Shader Files=== | ||
====lightmappedgeneric_ps2_3_x.h==== | ====lightmappedgeneric_ps2_3_x.h==== | ||
Below | Below | ||
< | <syntaxhighlight lang="cpp"> | ||
const float4 g_ShadowTweaks : register( c19 ); | const float4 g_ShadowTweaks : register( c19 ); | ||
</ | </syntaxhighlight> | ||
add: | add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax cubemaps | // Parallax cubemaps | ||
#if ( PARALLAXCORRECT ) | #if ( PARALLAXCORRECT ) | ||
Line 445: | Line 445: | ||
const float4x4 g_ObbMatrix : register( c22 ); // Through c25 | const float4x4 g_ObbMatrix : register( c22 ); // Through c25 | ||
#endif | #endif | ||
</ | </syntaxhighlight> | ||
Within the cubemap processing block, after <code>fresnel = fresnel * g_OneMinusFresnelReflection + g_FresnelReflection;</code>, add: | Within the cubemap processing block, after <code>fresnel = fresnel * g_OneMinusFresnelReflection + g_FresnelReflection;</code>, add: | ||
< | <syntaxhighlight lang="cpp"> | ||
// Parallax correction (2_0b and beyond) | // Parallax correction (2_0b and beyond) | ||
// Adapted from http://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ | // Adapted from http://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ | ||
Line 467: | Line 467: | ||
#endif | #endif | ||
#endif | #endif | ||
</ | </syntaxhighlight> | ||
====lightmappedgeneric_ps2x.fxc==== | ====lightmappedgeneric_ps2x.fxc==== | ||
After <code>// STATIC: "FLASHLIGHT" "0..1" [ps20b] [XBOX]</code>, add: | After <code>// STATIC: "FLASHLIGHT" "0..1" [ps20b] [XBOX]</code>, add: | ||
< | <syntaxhighlight lang="text"> | ||
// STATIC: "PARALLAXCORRECT" "0..1" | // STATIC: "PARALLAXCORRECT" "0..1" | ||
</ | </syntaxhighlight> | ||
After <code>// SKIP ($DETAIL_BLEND_MODE == 11 ) && ($BUMPMAP != 0 )</code>, add: | After <code>// SKIP ($DETAIL_BLEND_MODE == 11 ) && ($BUMPMAP != 0 )</code>, add: | ||
< | <syntaxhighlight lang="text"> | ||
// SKIP: $PARALLAXCORRECT && !$CUBEMAP | // SKIP: $PARALLAXCORRECT && !$CUBEMAP | ||
// SKIP: $PARALLAXCORRECT [ps20] | // SKIP: $PARALLAXCORRECT [ps20] | ||
</ | </syntaxhighlight> | ||
===Compile the Shader(s)=== | ===Compile the Shader(s)=== | ||
Ensure the following shaders are listed in '''stdshader_dx9_20b.txt''' (or the custom game file): | Ensure the following shaders are listed in '''stdshader_dx9_20b.txt''' (or the custom game file): | ||
< | <syntaxhighlight lang="text"> | ||
lightmappedgeneric_ps2x.fxc | lightmappedgeneric_ps2x.fxc | ||
lightmappedgeneric_ps11.fxc | lightmappedgeneric_ps11.fxc | ||
lightmappedgeneric_vs20.fxc | lightmappedgeneric_vs20.fxc | ||
</ | </syntaxhighlight> | ||
Compile the shaders. This process may take a significant amount of time. Upon completion, the appropriate | Compile the shaders. This process may take a significant amount of time. Upon completion, the appropriate {{path|lightmappedgeneric_*|inc|icon=file}} and {{path|lightmappedgeneric_*|vcs|icon=file}} files will be generated. Copy these to the game's {{path|shaders/fxc/}} folder. | ||
With the new '''.inc''' files, compile the | With the new '''[[INC|.inc]]''' files, compile the {{path|game_shader_dx9|sln|icon=file}} project. The resulting {{path|game_shader_dx9|dll|icon=file}} should be copied to the game's {{path|bin/}} folder, alongside the server and client DLLs. | ||
==What Next?== | ==What Next?== | ||
===The FGD File=== | ===The FGD File=== | ||
{{CodeBlock|src=parallaxcubes.fgd | |||
< | |<nowiki>@include "YOUR FGD HERE. THIS ONE WILL OVERRIDE THE REGULAR ENV_CUBEMAP ENTITY AND ALSO ADD THE PARALLAX_OBB ENTITY. ONLY INCLUDE THIS ONE IN HAMMER." | ||
@include "YOUR FGD HERE. THIS ONE WILL OVERRIDE THE REGULAR ENV_CUBEMAP ENTITY AND ALSO ADD THE PARALLAX_OBB ENTITY. ONLY INCLUDE THIS ONE IN HAMMER." | |||
@PointClass color(0 0 255) sidelist(sides) iconsprite("editor/env_cubemap.vmt") = env_cubemap : | @PointClass color(0 0 255) sidelist(sides) iconsprite("editor/env_cubemap.vmt") = env_cubemap : | ||
Line 524: | Line 523: | ||
targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." | ||
] | ] | ||
</ | </nowiki>}} | ||
===(Optional) Test Map=== | ===(Optional) Test Map=== |
Revision as of 11:41, 14 October 2025
Overview
Source's native cubemap implementation does not allow them to follow the player's perspective. While this is passable in most cases, tying the reflections to the player's view can increase realism, especially when using high-resolution reflections. A method to achieve this is called parallax correction.
Parallax-corrected cubemaps use a bounding box brush to bake their reflection based on a specified area around them, and a custom shader to make use of it.
This tutorial is based on the work of Brian Charles. A demonstration video can be found here: Parallax Corrected Cubemaps in the Source Engine.
The Code
First, the file
should be added to <src code directory>/src/utils/vbsp/
.
Next, open the project solution (everything.sln
) and navigate to /utils/vbsp/cubemap.cpp
.
cubemap.cpp
Add the following below the SideHasCubemapAndWasntManuallyReferenced( int iSide )
function:
char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];
Then, modify the CubeMap_InsertSample( ... )
function declaration as follows:
void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr = "" )
At the top of the function body, add:
g_pParallaxObbStrs[g_nCubemapSamples] = pParallaxObbStr;
Update the PatchEnvmapForMaterialAndDependents( ... )
function declaration to:
static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture, const char *pParallaxObbMatrix = "" )
Within the if ( pDependentMaterial )
statement, change the code to:
bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix );
Change
MaterialPatchInfo_t pPatchInfo[2];
to:
MaterialPatchInfo_t pPatchInfo[6];
Above the line
char pDependentPatchedMaterialName[1024];
add:
// Parallax cubemap matrix
CUtlVector<char *> matRowList;
if ( pParallaxObbMatrix[0] != '\0' )
{
V_SplitString( pParallaxObbMatrix, ";", matRowList );
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB1";
pPatchInfo[nPatchCount].m_pValue = matRowList[0];
++nPatchCount;
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB2";
pPatchInfo[nPatchCount].m_pValue = matRowList[1];
++nPatchCount;
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB3";
pPatchInfo[nPatchCount].m_pValue = matRowList[2];
++nPatchCount;
pPatchInfo[nPatchCount].m_pKey = "$envMapOrigin";
pPatchInfo[nPatchCount].m_pValue = matRowList[3];
++nPatchCount;
}
At the bottom of the function, change:
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE );
to:
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT );
Then add:
// Clean up parallax stuff
matRowList.PurgeAndDeleteElements();
Update the Cubemap_CreateTexInfo( ... )
function declaration to:
static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3], int cubemapIndex )
After
GeneratePatchedName( "c", info, false, pTextureName, 1024 );
add:
// Append origin info if this cubemap has a parallax OBB
char originAppendedString[1024] = "";
if ( g_pParallaxObbStrs[cubemapIndex][0] != '\0' )
{
Q_snprintf( originAppendedString, 1024, "%s;[%d %d %d]", g_pParallaxObbStrs[cubemapIndex], origin[0], origin[1], origin[2] );
}
Change
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) )
to:
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) )
In the Cubemap_FixupBrushSidesMaterials( void )
function, change:
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin );
to:
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID );
In Cubemap_AttachDefaultCubemapToSpecularSides( void )
, change:
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin );
to:
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap );
This completes the changes for cubemap.cpp
.
vbsp.h
Change the declaration:
void Cubemap_InsertSample( const Vector& origin, int size );
to:
void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr );
Above that, add:
extern char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];
This completes the changes for vbsp.h
.
map.cpp
At the top, add:
#include "matrixinvert.h"
In the LoadEntityCallback( ... )
function, after:
const char *pSideListStr = ValueForKey( mapent, "sides" );
add:
char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" );
Replace:
Cubemap_InsertSample( mapent->origin, size );
with:
Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr );
After the closing bracket of the if
statement, add:
//
// parallax_obb brushes are removed after the transformation matrix is found and saved into
// the entity's data (ent will be removed after data transferred to patched materials)
//
if ( !strcmp( "parallax_obb", pClassName ) )
{
matrix3x4_t obbMatrix, invObbMatrix;
SetIdentityMatrix( obbMatrix );
SetIdentityMatrix( invObbMatrix );
// Get corner and its 3 edges (scaled, local x, y, and z axes)
mapbrush_t *brush = &mapbrushes[mapent->firstbrush];
Vector corner, x, y, z;
// Find first valid winding (with these whiles, if not enough valid windings then identity matrix is passed through to VMTs)
int i = 0;
while ( i < brush->numsides )
{
winding_t *wind = brush->original_sides[i].winding;
if ( !wind )
{
i++;
continue;
}
corner = wind->p[0];
y = wind->p[1] - corner;
z = wind->p[3] - corner;
x = CrossProduct( y, z ).Normalized();
i++;
break;
}
// Skip second valid winding (opposite face from first, unusable for finding Z's length)
while ( i < brush->numsides )
{
winding_t *wind = brush->original_sides[i].winding;
if ( !wind )
{
i++;
continue;
}
i++;
break;
}
// Find third valid winding
while ( i < brush->numsides )
{
winding_t *wind = brush->original_sides[i].winding;
if ( !wind )
{
i++;
continue;
}
// Find length of X
// Start with diagonal, then scale X by the projection of diag onto X
Vector diag = wind->p[0] - wind->p[2];
x *= abs( DotProduct( diag, x ) );
// Build transformation matrix (what is needed to turn a [0,0,0] - [1,1,1] cube into this brush)
MatrixSetColumn( x, 0, obbMatrix );
MatrixSetColumn( y, 1, obbMatrix );
MatrixSetColumn( z, 2, obbMatrix );
MatrixSetColumn( corner, 3, obbMatrix );
// Find inverse (we need the world to local matrix, "transformationmatrix" is kind of a misnomer)
MatrixInversion( obbMatrix, invObbMatrix );
break;
}
char szMatrix[1024];
Q_snprintf( szMatrix, 1024, "[%f %f %f %f];[%f %f %f %f];[%f %f %f %f]", invObbMatrix[0][0], invObbMatrix[0][1], invObbMatrix[0][2], invObbMatrix[0][3], invObbMatrix[1][0], invObbMatrix[1][1], invObbMatrix[1][2], invObbMatrix[1][3], invObbMatrix[2][0], invObbMatrix[2][1], invObbMatrix[2][2], invObbMatrix[2][3] );
SetKeyValue( mapent, "transformationmatrix", szMatrix );
return ( ChunkFile_Ok );
}
In the LoadMapFile( ... )
function, after:
g_LoadingMap->CheckForInstances( pszFileName );
add:
if ( g_MainMap == g_LoadingMap )
{
// Fill out parallax obb matrix array
for ( int i = 0; i < g_nCubemapSamples; i++ )
{
if ( g_pParallaxObbStrs[i][0] != '\0' )
{
entity_t *obbEnt = EntityByName( g_pParallaxObbStrs[i] );
g_pParallaxObbStrs[i] = ValueForKey( obbEnt, "transformationmatrix" );
}
}
// Remove parallax_obb entities (in a nice slow linear search)
for ( int i = 0; i < g_MainMap->num_entities; i++ )
{
entity_t *mapent = &g_MainMap->entities[i];
const char *pClassName = ValueForKey( mapent, "classname" );
if ( !strcmp( "parallax_obb", pClassName ) )
{
mapent->numbrushes = 0;
mapent->epairs = NULL;
}
}
}
This completes the changes for map.cpp
.
The VBSP project should now compile without errors. If errors occur, review the changes for accuracy.
Once vbsp.exe is compiled, move it to the game's bin/
folder (not the mod's bin/; the game's - typically the same one where Hammer is located).


The Shaders
The Parallax Corrected Cubemaps that are now patched into materials require a custom LightmappedGeneric shader for proper display.
To edit shaders, open the everything.sln file.


Shader C++ Files
First, update the LightmappedGeneric shader to recognize the Parallax Corrected bounding boxes stored in the material.
lightmappedgeneric_dx9_helper.h
Add the following lines to the end of the LightmappedGeneric_DX9_Vars_t
struct (after the int m_nOutlineEnd1;
member):
// Parallax cubemaps
int m_nEnvmapParallaxObb1;
int m_nEnvmapParallaxObb2;
int m_nEnvmapParallaxObb3;
int m_nEnvmapOrigin;
lightmappedgeneric_dx9.cpp
Add the following lines inside the
BEGIN_SHADER_PARAMS
block, after the
SHADER_PARAM( OUTLINEEND1, SHADER_PARAM_TYPE_FLOAT, "0.0", "outer end value for outline")
line:
// Parallax cubemaps
SHADER_PARAM( ENVMAPPARALLAXOBB1, SHADER_PARAM_TYPE_VEC4, "[1 0 0 0]", "The first line of the parallax correction OBB matrix" )
SHADER_PARAM( ENVMAPPARALLAXOBB2, SHADER_PARAM_TYPE_VEC4, "[0 1 0 0]", "The second line of the parallax correction OBB matrix" )
SHADER_PARAM( ENVMAPPARALLAXOBB3, SHADER_PARAM_TYPE_VEC4, "[0 0 1 0]", "The third line of the parallax correction OBB matrix" )
SHADER_PARAM( ENVMAPORIGIN, SHADER_PARAM_TYPE_VEC3, "[0 0 0]", "The world space position of the env_cubemap being corrected" )
In the same file, inside the
void SetupVars( LightmappedGeneric_DX9_Vars_t& info )
function, add the following lines at the end (after info.m_nOutlineEnd1 = OUTLINEEND1;
):
// Parallax cubemaps
info.m_nEnvmapParallaxObb1 = ENVMAPPARALLAXOBB1;
info.m_nEnvmapParallaxObb2 = ENVMAPPARALLAXOBB2;
info.m_nEnvmapParallaxObb3 = ENVMAPPARALLAXOBB3;
info.m_nEnvmapOrigin = ENVMAPORIGIN;
lightmappedgeneric_dx9_helper.cpp
Inside
void InitParamsLightmappedGeneric_DX9( CBaseVSShader *pShader, IMaterialVar** params, const char *pMaterialName, LightmappedGeneric_DX9_Vars_t &info )
add the following lines after InitFloatParam( info.m_nOutlineAlpha, params, 1.0 );
:
// Parallax cubemaps
// Cubemap parallax correction requires all 4 lines (if the 2nd, 3rd, or 4th are undef, undef the first one (checking done on first var)
if ( !( params[info.m_nEnvmapParallaxObb2]->IsDefined() && params[info.m_nEnvmapParallaxObb3]->IsDefined() && params[info.m_nEnvmapOrigin]->IsDefined() ) )
{
params[info.m_nEnvmapParallaxObb1]->SetIntValue( 0 );
}
In the
void DrawLightmappedGeneric_DX9_Internal( ... )
function, after
bool hasNormalMapAlphaEnvmapMask = IS_FLAG_SET( MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK );
add:
// Parallax cubemaps
bool hasParallaxCorrection = params[info.m_nEnvmapParallaxObb1]->GetType() == MATERIAL_VAR_TYPE_VECTOR;
Within the if ( g_pHardwareConfig->SupportsPixelShaders_2_b() )
block, add before
SET_STATIC_PIXEL_SHADER( lightmappedgeneric_ps20b );
the following:
// Parallax cubemaps enabled for 2_0b and onwards
SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, hasParallaxCorrection );
In the else
statement for the above, before SET_STATIC_PIXEL_SHADER( lightmappedgeneric_ps20 );
, add:
// Parallax cubemaps
SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, 0 ); // No parallax cubemaps with ps_2_0 :(
Before pContextData->m_SemiStaticCmdsOut.End();
, add:
// Parallax cubemaps
if ( hasParallaxCorrection )
{
pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 21, params[info.m_nEnvmapOrigin]->GetVecValue() );
float *vecs[3];
vecs[0] = const_cast<float *>( params[info.m_nEnvmapParallaxObb1]->GetVecValue() );
vecs[1] = const_cast<float *>( params[info.m_nEnvmapParallaxObb2]->GetVecValue() );
vecs[2] = const_cast<float *>( params[info.m_nEnvmapParallaxObb3]->GetVecValue() );
float matrix[4][4];
for ( int i = 0; i < 3; i++ )
{
for ( int j = 0; j < 4; j++ )
{
matrix[i][j] = vecs[i][j];
}
}
matrix[3][0] = matrix[3][1] = matrix[3][2] = 0;
matrix[3][3] = 1;
pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 22, &matrix[0][0], 4 );
}
The Shader Files
lightmappedgeneric_ps2_3_x.h
Below
const float4 g_ShadowTweaks : register( c19 );
add:
// Parallax cubemaps
#if ( PARALLAXCORRECT )
const float3 g_CubemapPos : register( c21 );
const float4x4 g_ObbMatrix : register( c22 ); // Through c25
#endif
Within the cubemap processing block, after fresnel = fresnel * g_OneMinusFresnelReflection + g_FresnelReflection;
, add:
// Parallax correction (2_0b and beyond)
// Adapted from http://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
#if !( defined( SHADER_MODEL_PS_1_1 ) || defined( SHADER_MODEL_PS_1_4 ) || defined( SHADER_MODEL_PS_2_0 ) )
#if ( PARALLAXCORRECT )
float3 worldPos = i.worldPos_projPosZ.xyz;
float3 positionLS = mul( float4( worldPos, 1 ), g_ObbMatrix );
float3 rayLS = mul( reflectVect, (float3x3)g_ObbMatrix );
float3 firstPlaneIntersect = ( float3( 1.0f, 1.0f, 1.0f ) - positionLS ) / rayLS;
float3 secondPlaneIntersect = ( -positionLS ) / rayLS;
float3 furthestPlane = max( firstPlaneIntersect, secondPlaneIntersect );
float distance = min( furthestPlane.x, min( furthestPlane.y, furthestPlane.z ) );
// Use distance in WS directly to recover intersection
float3 intersectPositionWS = worldPos + reflectVect * distance;
reflectVect = intersectPositionWS - g_CubemapPos;
#endif
#endif
lightmappedgeneric_ps2x.fxc
After // STATIC: "FLASHLIGHT" "0..1" [ps20b] [XBOX]
, add:
// STATIC: "PARALLAXCORRECT" "0..1"
After // SKIP ($DETAIL_BLEND_MODE == 11 ) && ($BUMPMAP != 0 )
, add:
// SKIP: $PARALLAXCORRECT && !$CUBEMAP
// SKIP: $PARALLAXCORRECT [ps20]
Compile the Shader(s)
Ensure the following shaders are listed in stdshader_dx9_20b.txt (or the custom game file):
lightmappedgeneric_ps2x.fxc
lightmappedgeneric_ps11.fxc
lightmappedgeneric_vs20.fxc
Compile the shaders. This process may take a significant amount of time. Upon completion, the appropriate lightmappedgeneric_*.inc
and lightmappedgeneric_*.vcs
files will be generated. Copy these to the game's shaders/fxc/
folder.
With the new .inc files, compile the game_shader_dx9.sln
project. The resulting game_shader_dx9.dll
should be copied to the game's bin/
folder, alongside the server and client DLLs.
What Next?
The FGD File
(Optional) Test Map
Instructions for creating Parallax Corrected Cubemaps can be found in the Parallax Corrected Cubemaps/Creation article.
Conclusion
At this point, the mod should be ready to launch with parallax-corrected cubemaps enabled.