Parallax Corrected Cubemaps: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
mNo edit summary
(Cleanup, consistency, code formatting and QoL changes)
Line 2: Line 2:
| ru = Parallax_Corrected_Cubemaps:ru
| ru = Parallax_Corrected_Cubemaps:ru
}}
}}
== What Is This? ==
== What Is This? ==
By default, Source's cubemaps are naively reflected on the ground and do not follow the player's perspective. This causes an unrealistic-looking reflection for most surfaces. A possible solution can be to parallax-correct the cubemaps based on the player's camera position, using a custom shader and a bounding box trigger for a cubemap. This tutorial, based directly on the work of Brian Charles, will show you how to do just that.
By default, Source's cubemaps are naively reflected on the ground and do not follow the player's perspective. This causes an unrealistic-looking reflection for most surfaces. A possible solution can be to parallax-correct the cubemaps based on the player's camera position, using a custom shader and a bounding box trigger for a cubemap. This tutorial, based directly on the work of Brian Charles, will show you how to do just that.


Brian Charles' video that showcases the before and after of this tutorial can be found here: [https://www.youtube.com/watch?v=ZH6s1hbwoQQ Parallax Corrected Cubemaps in the Source Engine]
Brian Charles' video that showcases the before and after of this tutorial can be found here: [https://www.youtube.com/watch?v=ZH6s1hbwoQQ Parallax Corrected Cubemaps in the Source Engine]


== The Code ==


== The Code ==
Before starting, we'll need this file:
Before starting, we'll need this file:
* [[Parallax Corrected Cubemaps/matrixinvert.h|matrixinvert.h]]
* [[Parallax Corrected Cubemaps/matrixinvert.h|matrixinvert.h]]
Line 22: Line 24:
=== cubemap.cpp ===
=== cubemap.cpp ===


Now add this under (not inside) the <code>SideHasCubemapAndWasntManuallyReferenced( ... )</code> function:
Now add this under (not inside) the <code>SideHasCubemapAndWasntManuallyReferenced( int iSide )</code> function:
<source lang="cpp">
<source lang="cpp">
char* g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];
char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];
</source>
</source>


Then right below, change the <code>CubeMap_InsertSample( ... )</code> function to look like this:
Then right below, change the <code>CubeMap_InsertSample( ... )</code> function to look like this:
<source lang="cpp">
<source lang="cpp">
void Cubemap_InsertSample( const Vector& origin, int size, char* pParallaxObbStr = "" )
void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr = "" )
</source>
</source>


Line 44: Line 46:
While in the same function, scroll a tiny bit down to the <code>if ( pDependentMaterial )</code> statement and change the line in it to this:
While in the same function, scroll a tiny bit down to the <code>if ( pDependentMaterial )</code> statement and change the line in it to this:
<source lang="cpp">
<source lang="cpp">
bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix );
bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix );
</source>
</source>


Scroll a tiny bit down again until you find this line:
Scroll a tiny bit down again until you find this line:
<source lang="cpp">
<source lang="cpp">
MaterialPatchInfo_t pPatchInfo[2];
MaterialPatchInfo_t pPatchInfo[2];
</source>
</source>


Then just change 2 to 6, like so:
Then just change 2 to 6, like so:
<source lang="cpp">
<source lang="cpp">
MaterialPatchInfo_t pPatchInfo[6];
MaterialPatchInfo_t pPatchInfo[6];
</source>
</source>


Do another tiny scroll down, and above this line:
Do another tiny scroll down, and above this line:
<source lang="cpp">
<source lang="cpp">
char pDependentPatchedMaterialName[1024];
char pDependentPatchedMaterialName[1024];
</source>
</source>


Add:
Add:
<source lang="cpp">
<source lang="cpp">
// Parallax cubemap matrix
// Parallax cubemap matrix
CUtlVector<char *> matRowList;
CUtlVector<char *> matRowList;
if ( pParallaxObbMatrix[0] != '\0' )
if ( pParallaxObbMatrix[0] != '\0' )
{
{
V_SplitString(pParallaxObbMatrix, ";", matRowList);
V_SplitString( pParallaxObbMatrix, ";", matRowList );
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB1";
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB1";
pPatchInfo[nPatchCount].m_pValue = matRowList[0];
pPatchInfo[nPatchCount].m_pValue = matRowList[0];
++nPatchCount;
++nPatchCount;
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB2";
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB2";
pPatchInfo[nPatchCount].m_pValue = matRowList[1];
pPatchInfo[nPatchCount].m_pValue = matRowList[1];
++nPatchCount;
++nPatchCount;
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB3";
pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB3";
pPatchInfo[nPatchCount].m_pValue = matRowList[2];
pPatchInfo[nPatchCount].m_pValue = matRowList[2];
++nPatchCount;
++nPatchCount;
pPatchInfo[nPatchCount].m_pKey = "$envMapOrigin";
pPatchInfo[nPatchCount].m_pKey = "$envMapOrigin";
pPatchInfo[nPatchCount].m_pValue = matRowList[3];
pPatchInfo[nPatchCount].m_pValue = matRowList[3];
++nPatchCount;
++nPatchCount;
}
}
</source>
</source>


At the bottom of the function you'll find this line:
At the bottom of the function you'll find this line:
<source lang="cpp">
<source lang="cpp">
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE );
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE );
</source>
</source>


Change it to this:
Change it to this:
<source lang="cpp">
<source lang="cpp">
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT );
CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT );
</source>
</source>


Then below that add this:
Then below that add this:
<source lang="cpp">
<source lang="cpp">
// Clean up parallax stuff
// Clean up parallax stuff
matRowList.PurgeAndDeleteElements();
matRowList.PurgeAndDeleteElements();
</source>
</source>


Line 108: Line 110:
Scroll a bit down inside that function and find this line:
Scroll a bit down inside that function and find this line:
<source lang="cpp">
<source lang="cpp">
GeneratePatchedName( "c", info, false, pTextureName, 1024 );
GeneratePatchedName( "c", info, false, pTextureName, 1024 );
</source>
</source>


Now below that line add this:
Now below that line add this:
<source lang="cpp">
<source 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] = "";
if (g_pParallaxObbStrs[cubemapIndex][0] != '\0')
if ( g_pParallaxObbStrs[cubemapIndex][0] != '\0' )
{
{
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] );
}
}
</source>
</source>


A bit down you'll see this if statement:
A bit down you'll see this if statement:
<source lang="cpp">
<source lang="cpp">
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) )
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) )
</source>
</source>


Change it to:
Change it to:
<source lang="cpp">
<source lang="cpp">
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) )
if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) )
</source>
</source>


Scroll down until you find the <code>Cubemap_FixupBrushSidesMaterials( void )</code> function, and inside it find this line:
Scroll down until you find the <code>Cubemap_FixupBrushSidesMaterials( void )</code> function, and inside it find this line:
<source lang="cpp">
<source lang="cpp">
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin );
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin );
</source>
</source>


Change it to this:
Change it to this:
<source lang="cpp">
<source lang="cpp">
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID );
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID );
</source>
</source>


Scroll down to the <code>Cubemap_AttachDefaultCubemapToSpecularSides( void )</code> function, and inside it find this line:
Scroll down to the <code>Cubemap_AttachDefaultCubemapToSpecularSides( void )</code> function, and inside it find this line:
<source lang="cpp">
<source lang="cpp">
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin );
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin );
</source>
</source>


Change it to this:
Change it to this:
<source lang="cpp">
<source lang="cpp">
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap );
pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap );
</source>
</source>


Line 162: Line 164:
And change it to this:
And change it to this:
<source lang="cpp">
<source lang="cpp">
void Cubemap_InsertSample( const Vector& origin, int size, char* pParallaxObbStr );
void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr );
</source>
</source>


And above that add this:
And above that add this:
<source lang="cpp">
<source lang="cpp">
extern char* g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];
extern char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];
</source>
</source>


Line 181: Line 183:
Then scroll down to the <code>LoadEntityCallback( ... )</code> function, then inside it find this line:
Then scroll down to the <code>LoadEntityCallback( ... )</code> function, then inside it find this line:
<source lang="cpp">
<source lang="cpp">
const char *pSideListStr = ValueForKey( mapent, "sides" );
const char *pSideListStr = ValueForKey( mapent, "sides" );
</source>
</source>


Now below that line, add:
Now below that line, add:
<source lang="cpp">
<source lang="cpp">
char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" );
char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" );
</source>
</source>


Then a tiny bit down find this line:
Then a tiny bit down find this line:
<source lang="cpp">
<source lang="cpp">
Cubemap_InsertSample( mapent->origin, size );
Cubemap_InsertSample( mapent->origin, size );
</source>
</source>


And change it to this:
And change it to this:
<source lang="cpp">
<source lang="cpp">
Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr);
Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr );
</source>
</source>


Now below the parent if statement we're in, add this:
Now below the parent if statement we're in, add this:
<source lang="cpp">
<source 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  
// the entity's data (ent will be removed after data transferred to patched materials)
// the entity's data (ent will be removed after data transferred to patched materials)
//
//
if (!strcmp("parallax_obb", pClassName))
if ( !strcmp( "parallax_obb", pClassName ) )
{
{
matrix3x4_t obbMatrix, invObbMatrix;
matrix3x4_t obbMatrix, invObbMatrix;
SetIdentityMatrix(obbMatrix);
SetIdentityMatrix( obbMatrix );
SetIdentityMatrix(invObbMatrix);
SetIdentityMatrix( invObbMatrix );


// Get corner and its 3 edges (scaled, local x, y, and z axes)
// Get corner and its 3 edges (scaled, local x, y, and z axes)
mapbrush_t *brush = &mapbrushes[mapent->firstbrush];
mapbrush_t *brush = &mapbrushes[mapent->firstbrush];
Vector corner, x, y, z;
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)
// Find first valid winding (with these whiles, if not enough valid windings then identity matrix is passed through to VMTs)
int i = 0;
int i = 0;
while (i < brush->numsides)
while ( i < brush->numsides )
{
winding_t* wind = brush->original_sides[i].winding;
if (!wind)
{
{
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++;
i++;
continue;
break;
}
}


corner = wind->p[0];
// Skip second valid winding (opposite face from first, unusable for finding Z's length)
y = wind->p[1] - corner;
while ( i < brush->numsides )
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)
{
{
winding_t *wind = brush->original_sides[i].winding;
if ( !wind )
{
i++;
continue;
}
i++;
i++;
continue;
break;
}
}
i++;
break;
}


// Find third valid winding
// Find third valid winding
while (i < brush->numsides)
while ( i < brush->numsides )
{
winding_t* wind = brush->original_sides[i].winding;
if (!wind)
{
{
i++;
winding_t *wind = brush->original_sides[i].winding;
continue;
if ( !wind )
}
{
i++;
continue;
}


// Find length of x
// Find length of X
// Start with diagonal, then scale x by the projection of diag onto x
// Start with diagonal, then scale X by the projection of diag onto X
Vector diag = wind->p[0] - wind->p[2];
Vector diag = wind->p[0] - wind->p[2];
x *= abs(DotProduct(diag, x));
x *= abs( DotProduct( diag, x ) );


// Build transformation matrix (what is needed to turn a [0,0,0] - [1,1,1] cube into this brush)
// Build transformation matrix (what is needed to turn a [0,0,0] - [1,1,1] cube into this brush)
MatrixSetColumn(x, 0, obbMatrix);
MatrixSetColumn( x, 0, obbMatrix );
MatrixSetColumn(y, 1, obbMatrix);
MatrixSetColumn( y, 1, obbMatrix );
MatrixSetColumn(z, 2, obbMatrix);
MatrixSetColumn( z, 2, obbMatrix );
MatrixSetColumn(corner, 3, obbMatrix);
MatrixSetColumn( corner, 3, obbMatrix );


//find inverse (we need the world to local matrix, "transformationmatrix" is kind of a misnomer)
// Find inverse (we need the world to local matrix, "transformationmatrix" is kind of a misnomer)
MatrixInversion(obbMatrix, invObbMatrix);
MatrixInversion( obbMatrix, invObbMatrix );
break;
break;
}
}
char szMatrix[1024];
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]);
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);
SetKeyValue( mapent, "transformationmatrix", szMatrix );


return (ChunkFile_Ok);
return ( ChunkFile_Ok );
}
}
</source>
</source>


Now scroll down to the <code>LoadMapFile( ... )</code> function, inside it scroll down until you find this line:
Now scroll down to the <code>LoadMapFile( ... )</code> function, inside it scroll down until you find this line:
<source lang="cpp">
<source lang="cpp">
if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF))
if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF))
</source>
</source>


Now above that line, add this:
Now above that line, add this:
<source lang="cpp">
<source lang="cpp">
// Fill out parallax obb matrix array
// Fill out parallax obb matrix array
for (int i = 0; i < g_nCubemapSamples; i++)
for ( int i = 0; i < g_nCubemapSamples; i++ )
{
if (g_pParallaxObbStrs[i][0] != '\0')
{
{
entity_t* obbEnt = EntityByName(g_pParallaxObbStrs[i]);
if ( g_pParallaxObbStrs[i][0] != '\0' )
g_pParallaxObbStrs[i] = ValueForKey(obbEnt, "transformationmatrix");
{
entity_t *obbEnt = EntityByName( g_pParallaxObbStrs[i] );
g_pParallaxObbStrs[i] = ValueForKey( obbEnt, "transformationmatrix" );
}
}
}
}


// Remove parallax_obb entities (in a nice slow linear search)
// Remove parallax_obb entities (in a nice slow linear search)
for (int i = 0; i < g_MainMap->num_entities; i++)
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;
entity_t *mapent = &g_MainMap->entities[i];
mapent->epairs = NULL;
const char *pClassName = ValueForKey( mapent, "classname" );
if ( !strcmp( "parallax_obb", pClassName ) )
{
mapent->numbrushes = 0;
mapent->epairs = NULL;
}
}
}
}
</source>
</source>


And that should be it! Try compiling the VBSP project, it should compile without any errors.
And that should be it! Try compiling the VBSP project, it should compile without any errors.


Now all that's left is moving the freshly compiled '''vbsp.exe''' file to your mod's '''bin''' folder.
Now all that's left is moving the freshly compiled '''vbsp.exe''' file to your game's '''bin/''' folder.


== The Shaders ==
== The Shaders ==


Now, the '''fun''' stuff: shader editing! The Parallax Corrected Cubemaps that will now be patched into your materials need a custom LightmappedGeneric shader in order for them to be properly displayed.
Now, the '''fun''' stuff: shader editing! The Parallax Corrected Cubemaps that will now be patched into your materials need a custom [[LightmappedGeneric]] shader for them to be properly displayed.


Let's start simple. In order to edit shaders, you'll need to open your '''everything.sln''' file. Bonus points if you already had it open!
Let's start simple. To edit shaders, you'll need to open your '''everything.sln''' file. Bonus points if you already had it open!


{{warning|This section is the lengthiest and most tedious, just due to having to compile a custom LightmappedGeneric shader, which can take upwards of 30 minutes per compile, even on beefy computers!}}
{{warning|This section is the lengthiest and most tedious, just due to having to compile a custom '''LightmappedGeneric''' shader, which can take upwards of 30 minutes per compile, even on beefy computers!}}


{{note|It's recommended to follow the [[Shader Authoring]] tutorial to get set up with the wonderful DirectX 2008 SDK, and a feel for compiling shaders. This tutorial assumes you're comfortable with that.}}
{{note|It's recommended to follow the [[Shader Authoring]] tutorial to get set up with the wonderful DirectX 2008 SDK, and a feel for compiling shaders. This tutorial assumes you're comfortable with that.}}


{{warning|Valve does not like when you override default shaders in the SDK 2013 project. Therefore, we will be creating identical shaders to the lightmappedgeneric one, just with the SDK_ prefix.}}
{{warning|Valve does not like when you override default shaders in the SDK 2013 project. Therefore, we will be creating identical shaders to the '''LightmappedGeneric''' one, just with the '''SDK_''' prefix.}}


=== Shader C++ Files ===
=== Shader C++ Files ===


Firstly, let's get the LightmappedGeneric shader familiar with the Parallax Corrected bounding boxes you stored in the material.
Firstly, let's get the '''LightmappedGeneric''' shader familiar with the Parallax Corrected bounding boxes you stored in the material.


==== lightmappedgeneric_dx9_helper.h ====
==== lightmappedgeneric_dx9_helper.h ====


Add the following lines to the end of the '''LightmappedGeneric_DX9_Vars_t''' struct (after the '''int m_nOutlineEnd1;''' 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):
 
<source lang="cpp">
<syntaxhighlight lang="cpp">
// Parallax cubemaps
    // Parallax cubemaps
int m_nEnvmapParallaxObb1;
    int m_nEnvmapParallaxObb1;
int m_nEnvmapParallaxObb2;
    int m_nEnvmapParallaxObb2;
int m_nEnvmapParallaxObb3;
    int m_nEnvmapParallaxObb3;
int m_nEnvmapOrigin;
    int m_nEnvmapOrigin;
</source>
</syntaxhighlight>


==== lightmappedgeneric_dx9.cpp ====
==== lightmappedgeneric_dx9.cpp ====


As warned about before, we need to turn this into an SDK shader, since compiling a default lightmappedgeneric shader works, but you can't use it in-game. Change the line:
As warned about before, we need to turn this into an SDK shader, since compiling a default '''LightmappedGeneric''' shader works, but you can't use it in-game. Change the line:
 
<source lang="cpp">
<pre>
BEGIN_VS_SHADER( LightmappedGeneric,
BEGIN_VS_SHADER( LightmappedGeneric, "Help for LightmappedGeneric" )
"Help for LightmappedGeneric" )
</pre>
</source>


to be:
to be:
 
<source lang="cpp">
<pre>
BEGIN_VS_SHADER( SDK_LightmappedGeneric, "Help for SDK_LightmappedGeneric" )
BEGIN_VS_SHADER( SDK_LightmappedGeneric, "Help for SDK_LightmappedGeneric" )
</pre>
</source>


Now we need the implementation file to populate these shader parameters. Add the following lines inside of the  
Now we need the implementation file to populate these shader parameters. Add the following lines inside of the  
<pre>
<source lang="cpp">
BEGIN_SHADER_PARAMS
BEGIN_SHADER_PARAMS
</pre>  
</source>
 
block at the top of this file, after the  
block at the top of this file, after the  
<pre>
<source 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")
</pre>  
</source>
 
line:
line:
<source lang="cpp">
// 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" )
</source>


<syntaxhighlight lang="cpp">
If you did your homework in the [[Shader Authoring]] section, you'll know that this is defining the shader parameters inside of the material file. Now let's assign them to the struct's new members. In the same file, inside of the
        // Parallax cubemaps
<source lang="cpp">
        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")
</syntaxhighlight>
 
If you did your homework in the [[Shader Authoring]] section, you'll know that this is defining the shader parameters inside of the material file. Now let's assign them to the struct's new members. In the same file, inside of the  
<syntaxhighlight lang="cpp">
void SetupVars( LightmappedGeneric_DX9_Vars_t& info )
void SetupVars( LightmappedGeneric_DX9_Vars_t& info )
</syntaxhighlight>  
</source>
method, add the following lines at the end of it (after the ''info.m_nOutlineEnd1 = OUTLINEEND1;'' line):


<syntaxhighlight lang="cpp">
function, add the following lines at the end of it (after the <code>info.m_nOutlineEnd1 = OUTLINEEND1;</code> line):
        // Parallax cubemaps
<source lang="cpp">
        info.m_nEnvmapParallaxObb1 = ENVMAPPARALLAXOBB1;
// Parallax cubemaps
        info.m_nEnvmapParallaxObb2 = ENVMAPPARALLAXOBB2;
info.m_nEnvmapParallaxObb1 = ENVMAPPARALLAXOBB1;
        info.m_nEnvmapParallaxObb3 = ENVMAPPARALLAXOBB3;
info.m_nEnvmapParallaxObb2 = ENVMAPPARALLAXOBB2;
        info.m_nEnvmapOrigin = ENVMAPORIGIN;
info.m_nEnvmapParallaxObb3 = ENVMAPPARALLAXOBB3;
</syntaxhighlight>
info.m_nEnvmapOrigin = ENVMAPORIGIN;
</source>


Great, now our shader's code knows about our patched parameters. But we still have to do stuff with them!
Great, now our shader's code knows about our patched parameters. But we still have to do stuff with them!
Line 395: Line 396:


As mentioned before, again, we need to change some prefixes. For your includes, change the lines:
As mentioned before, again, we need to change some prefixes. For your includes, change the lines:
 
<source lang="cpp">
<syntaxhighlight lang="cpp">
#include "lightmappedgeneric_ps20.inc"
#include "lightmappedgeneric_ps20.inc"
#include "lightmappedgeneric_vs20.inc"
#include "lightmappedgeneric_vs20.inc"
#include "lightmappedgeneric_ps20b.inc"
#include "lightmappedgeneric_ps20b.inc"
</syntaxhighlight>
</source>


to be:
to be:
 
<source lang="cpp">
<syntaxhighlight lang="cpp">
#include "SDK_lightmappedgeneric_ps20.inc"
#include "SDK_lightmappedgeneric_ps20.inc"
#include "SDK_lightmappedgeneric_vs20.inc"
#include "SDK_lightmappedgeneric_vs20.inc"
#include "SDK_lightmappedgeneric_ps20b.inc"
#include "SDK_lightmappedgeneric_ps20b.inc"
</syntaxhighlight>
</source>


You'll generate these in a bit, don't worry. But now this will cause some more issues further in the code! You'll need to replace all instances of the code of the default shader to your newly copied SDK one.
You'll generate these in a bit, don't worry. But now this will cause some more issues further in the code! You'll need to replace all instances of the code of the default shader with your newly copied SDK one.


For example:
For example:
 
<source lang="cpp">
<pre>
DECLARE_STATIC_VERTEX_SHADER( lightmappedgeneric_vs20 );
DECLARE_STATIC_VERTEX_SHADER( lightmappedgeneric_vs20 );
</source>
</pre>


to:
to:
 
<source lang="cpp">
<pre>
DECLARE_STATIC_VERTEX_SHADER( sdk_lightmappedgeneric_vs20 );
DECLARE_STATIC_VERTEX_SHADER( sdk_lightmappedgeneric_vs20 );
</source>
</pre>


'''DO IT FOR EVERY INSTANCE!''' Changing the imports will help when you come back to compile this module in a bit, as the old '''lightmappedgeneric_*''' shaders will be underlined red, as to help you find where you missed a specific spot.
'''DO IT FOR EVERY INSTANCE!''' Changing the imports will help when you come back to compile this module in a bit, as the old '''lightmappedgeneric_*''' shaders will be underlined red, as to help you find where you missed a specific spot.


Firstly, let's do a safety check. We need to ensure we have '''all''' the required components for the Parallax Corrected Cubemaps to work properly. Inside of
Firstly, let's do a safety check. We need to ensure we have '''all''' the required components for the Parallax Corrected Cubemaps to work properly. Inside of
<source lang="cpp">
void InitParamsLightmappedGeneric_DX9( CBaseVSShader *pShader, IMaterialVar** params, const char *pMaterialName, LightmappedGeneric_DX9_Vars_t &info )
</source>


<syntaxhighlight lang="cpp">
near the top of this file, add the following lines of code to the (you guessed it) end of it, after the <code>InitFloatParam( info.m_nOutlineAlpha, params, 1.0 );</code> line:
void InitParamsLightmappedGeneric_DX9( CBaseVSShader *pShader, IMaterialVar** params, const char *pMaterialName, LightmappedGeneric_DX9_Vars_t &info )</syntaxhighlight>
<source lang="cpp">
near the top of this file, add the following lines of code to the (you guessed it) end of it, after the ''InitFloatParam( info.m_nOutlineAlpha, params, 1.0 );'' line:
// 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)
<syntaxhighlight lang="cpp">
if ( !( params[info.m_nEnvmapParallaxObb2]->IsDefined() && params[info.m_nEnvmapParallaxObb3]->IsDefined() && params[info.m_nEnvmapOrigin]->IsDefined() ) )
    // 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)
params[info.m_nEnvmapParallaxObb1]->SetUndefined();
    if (!(params[info.m_nEnvmapParallaxObb2]->IsDefined() && params[info.m_nEnvmapParallaxObb3]->IsDefined() && params[info.m_nEnvmapOrigin]->IsDefined()))
}
    {
</source>
        params[info.m_nEnvmapParallaxObb1]->SetUndefined();
    }
</syntaxhighlight>


This allows the parallax-correcting code to not run if there's some issue with your patched materials or something.
This allows the parallax-correcting code to not run if there's some issue with your patched materials or something.


Next, we need to tell the shader itself whether the above code we added returns true, and if so, to render the parallax corrected cubemap. Inside the  
Next, we need to tell the shader itself whether the above code we added returns true, and if so, to render the parallax corrected cubemap. Inside the  
<syntaxhighlight lang="cpp">
<source lang="cpp">
void DrawLightmappedGeneric_DX9_Internal(CBaseVSShader *pShader, ...)
void DrawLightmappedGeneric_DX9_Internal( ... )
</syntaxhighlight>  
</source>
method, after the line
 
<syntaxhighlight lang="cpp">
function, after the line
bool hasNormalMapAlphaEnvmapMask = IS_FLAG_SET( MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK );
<source lang="cpp">
</syntaxhighlight>
bool hasNormalMapAlphaEnvmapMask = IS_FLAG_SET( MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK );
</source>


add the following boolean declaration:
add the following boolean declaration:
<source lang="cpp">
// Parallax cubemaps
bool hasParallaxCorrection = params[info.m_nEnvmapParallaxObb1]->IsDefined();
</source>


<syntaxhighlight lang="cpp">
Now let's check this boolean. Further down this gargantuan function, we need to set the combo on the pixel shader. Inside of the
        // Parallax cubemaps
<source lang="cpp">
        bool hasParallaxCorrection = params[info.m_nEnvmapParallaxObb1]->IsDefined();
if ( g_pHardwareConfig->SupportsPixelShaders_2_b() )
</syntaxhighlight>
</source>


Now let's check this boolean. Further down this gargantuan method, we need to set the combo on the pixel shader. Inside of the
block, add the following line '''BEFORE''' the
<syntaxhighlight lang="cpp">
<source lang="cpp">
if ( g_pHardwareConfig->SupportsPixelShaders_2_b() )</syntaxhighlight>
SET_STATIC_PIXEL_SHADER( sdk_lightmappedgeneric_ps20b );
block, add the following line '''BEFORE''' the <pre>SET_STATIC_PIXEL_SHADER(sdk_lightmappedgeneric_ps20b);</pre> line:
</source>


<syntaxhighlight lang="cpp">
line:
// Parallax cubemaps enabled for 2_0b and onwards
<source lang="cpp">
SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, hasParallaxCorrection );
// Parallax cubemaps enabled for 2_0b and onwards
</syntaxhighlight>
SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, hasParallaxCorrection );
</source>


If you're able to read, you can start to realize what we're going to do next: since Shader Model 2.0 was all the rage for the GoldSource days, and something like parallax corrected cubemaps is a little high-tech for it, we need to enable it only if the graphics card running the engine supports Shader Model 2.0b (and higher). So a little further in this mega-method, inside of the '''else''' statement for the code above, '''RIGHT BEFORE''' the line ''SET_STATIC_PIXEL_SHADER(sdk_lightmappedgeneric_ps20);'', add the following:
If you're able to read, you can start to realize what we're going to do next: since Shader Model 2.0 was all the rage for the GoldSource days, and something like parallax corrected cubemaps is a little high-tech for it, we need to enable it only if the graphics card running the engine supports Shader Model 2.0b (and higher). So a little further in this mega-function, inside of the <code>else</code> statement for the code above, '''RIGHT BEFORE''' the line <code>SET_STATIC_PIXEL_SHADER( sdk_lightmappedgeneric_ps20 );</code>, add the following:
 
<source lang="cpp">
<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 :(
</source>
</syntaxhighlight>


So now our pixel shader (which we're editing next section) knows whether or not it should handle the parallax corrected cubemaps.  
So now our pixel shader (which we're editing next section) knows whether or not it should handle the parallax corrected cubemaps.  


Lastly, but certainly not least-ly (I mean come on we still haven't even edited the shader yet!), we need to send the pixel shader the proper value to use to display our parallax-corrected cubemap. So, even further down in this hulking hunk of hot garbage method, RIGHT BEFORE the ''pContextData->m_SemiStaticCmdsOut.End();'' line, add the following block of code:
Lastly, but certainly not least-ly (I mean come on we still haven't even edited the shader yet!), we need to send the pixel shader the proper value to use to display our parallax-corrected cubemap. So, even further down in this hulking hunk of hot garbage function, RIGHT BEFORE the <code>pContextData->m_SemiStaticCmdsOut.End();</code> line, add the following block of code:
<source lang="cpp">
// Parallax cubemaps
if ( hasParallaxCorrection )
{
pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 21, params[info.m_nEnvmapOrigin]->GetVecValue() );


<syntaxhighlight lang="cpp">
float *vecs[3];
            // Parallax cubemaps
vecs[0] = const_cast<float *>( params[info.m_nEnvmapParallaxObb1]->GetVecValue() );
            if (hasParallaxCorrection)
vecs[1] = const_cast<float *>( params[info.m_nEnvmapParallaxObb2]->GetVecValue() );
            {
vecs[2] = const_cast<float *>( params[info.m_nEnvmapParallaxObb3]->GetVecValue() );
                pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant(21, params[info.m_nEnvmapOrigin]->GetVecValue());
float matrix[4][4];
 
for ( int i = 0; i < 3; i++ )
                float* vecs[3];
{
                vecs[0] = const_cast<float*>(params[info.m_nEnvmapParallaxObb1]->GetVecValue());
for ( int j = 0; j < 4; j++ )
                vecs[1] = const_cast<float*>(params[info.m_nEnvmapParallaxObb2]->GetVecValue());
{
                vecs[2] = const_cast<float*>(params[info.m_nEnvmapParallaxObb3]->GetVecValue());
matrix[i][j] = vecs[i][j];
                float matrix[4][4];
}
                for (int i = 0; i < 3; i++)
}
                {
matrix[3][0] = matrix[3][1] = matrix[3][2] = 0;
                    for (int j = 0; j < 4; j++)
matrix[3][3] = 1;
                    {
pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 22, &matrix[0][0], 4 );
                        matrix[i][j] = vecs[i][j];
}
                    }
</source>
                }
                matrix[3][0] = matrix[3][1] = matrix[3][2] = 0;
                matrix[3][3] = 1;
                pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant(22, &matrix[0][0], 4);
            }
</syntaxhighlight>


Woo! Now our shader has the right data passed into it. Alright, time to put on your DirectX 8 hats on, we're going in!
Woo! Now our shader has the right data passed into it. Alright, time to put on your DirectX 8 hats, we're going in!


=== The Shader Files ===
=== The Shader Files ===


==== lightmappedgeneric_ps2_3_x.h ====
==== lightmappedgeneric_ps2_3_x.h ====
Now, I know what you're thinking. This isn't anywhere close to a shader file! It's a gosh dang C++ header file! But worry not, this file is included inside of the next file we edit, the actual shader source (FXC) file. So all of this code will actually get compiled into the shader, making it a shader source in disguise!
Now, I know what you're thinking. This isn't anywhere close to a shader file! It's a gosh dang C++ header file! But worry not, this file is included inside of the next file we edit, the actual shader source (FXC) file. So all of this code will actually get compiled into the shader, making it a shader source in disguise!


First things first, let's declare us some registers to snag up. If you're wondering why this shader doesn't work with Shader Model 2.0, here is why. Inside this file, right below the <syntaxhighlight lang="cpp">const float4 g_ShadowTweaks : register( c19 );</syntaxhighlight> line, add:
First things first, let's declare us some registers to snag up. If you're wondering why this shader doesn't work with Shader Model 2.0, here is why. Inside this file, right below
<source lang="cpp">
const float4 g_ShadowTweaks : register( c19 );
</source>


<syntaxhighlight lang="cpp">
add:
<source lang="cpp">
// Parallax cubemaps
// Parallax cubemaps
#if (PARALLAXCORRECT)
#if ( PARALLAXCORRECT )
const float3 cubemapPos : register(c21);
const float3 g_CubemapPos : register( c21 );
const float4x4 obbMatrix : register(c22); //through c25
const float4x4 g_ObbMatrix : register( c22 ); // Through c25
#endif
#endif
</syntaxhighlight>
</source>


Don't worry about that ''PARALLAXCORRECT'', it'll make sense in a bit.
Don't worry about that <code>PARALLAXCORRECT</code>, it'll make sense in a bit.


Now, let's do the main shader math we need! We can skip further down the file, and find where cubemaps are processed, inside of the <syntaxhighlight lang="cpp">if( bCubemap )</syntaxhighlight> block, add the following block of code after the <syntaxhighlight lang="cpp">fresnel = fresnel * g_OneMinusFresnelReflection + g_FresnelReflection;</syntaxhighlight> line:
Now, let's do the main shader math we need! We can skip further down the file, and find where cubemaps are processed, inside of the <code>if( bCubemap )</code> block, add the following block of code after the <code>fresnel = fresnel * g_OneMinusFresnelReflection + g_FresnelReflection;</code> line:
<source lang="cpp">
// 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 );


<syntaxhighlight lang="cpp">
float3 firstPlaneIntersect = ( float3( 1.0f, 1.0f, 1.0f ) - positionLS ) / rayLS;
        //Parallax correction (2_0b and beyond)
float3 secondPlaneIntersect = ( -positionLS ) / rayLS;
        //Adapted from http://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
float3 furthestPlane = max( firstPlaneIntersect, secondPlaneIntersect );
#if !(defined(SHADER_MODEL_PS_1_1) || defined(SHADER_MODEL_PS_1_4) || defined(SHADER_MODEL_PS_2_0))
float distance = min( furthestPlane.x, min( furthestPlane.y, furthestPlane.z ) );
#if (PARALLAXCORRECT)
        float3 worldPos = i.worldPos_projPosZ.xyz;
        float3 positionLS = mul(float4(worldPos, 1), obbMatrix);
        float3 rayLS = mul(reflectVect, (float3x3) obbMatrix);


        float3 firstPlaneIntersect = (float3(1.0f, 1.0f, 1.0f) - positionLS) / rayLS;
// Use distance in WS directly to recover intersection
        float3 secondPlaneIntersect = (-positionLS) / rayLS;
float3 intersectPositionWS = worldPos + reflectVect * distance;
        float3 furthestPlane = max(firstPlaneIntersect, secondPlaneIntersect);
reflectVect = intersectPositionWS - g_CubemapPos;
        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 - cubemapPos;
#endif
#endif
#endif
#endif
</syntaxhighlight>
</source>


Wonderful. Our shader will now properly do fancy maths to parallax the cubemap based on the player's position. Onwards to the FXC file!
Wonderful. Our shader will now properly do fancy maths to parallax the cubemap based on the player's position. Onwards to the FXC file!


==== A Shader By Any Other Name ====
==== A Shader By Any Other Name ====
You'll then need to make a copy of these files:
You'll then need to make a copy of these files:
 
<source lang="text">
<syntaxhighlight lang="text">
lightmappedgeneric_ps2x.fxc
lightmappedgeneric_ps2x.fxc
lightmappedgeneric_ps11.fxc
lightmappedgeneric_ps11.fxc
lightmappedgeneric_vs20.fxc
lightmappedgeneric_vs20.fxc
</syntaxhighlight>
</source>


And then rename the copies to:
And then rename the copies to:
 
<source lang="text">
<syntaxhighlight lang="text">
SDK_lightmappedgeneric_ps2x.fxc
SDK_lightmappedgeneric_ps2x.fxc
SDK_lightmappedgeneric_ps11.fxc
SDK_lightmappedgeneric_ps11.fxc
SDK_lightmappedgeneric_vs20.fxc
SDK_lightmappedgeneric_vs20.fxc
</syntaxhighlight>
</source>


==== SDK_lightmappedgeneric_ps2x.fxc ====
==== SDK_lightmappedgeneric_ps2x.fxc ====
Heck yeah, here we go, shader file editing! Told you we'd get here! Alright, you ready? Brace yourself, these edits are ''such'' a doozy.
Heck yeah, here we go, shader file editing! Told you we'd get here! Alright, you ready? Brace yourself, these edits are ''such'' a doozy.


Add the following line, after ''// STATIC: "FLASHLIGHT" "0..1" [ps20b] [XBOX]'':
Add the following line, after <code>// STATIC: "FLASHLIGHT" "0..1" [ps20b] [XBOX]</code>:
 
<source lang="text">
<syntaxhighlight lang="text">
// STATIC: "PARALLAXCORRECT" "0..1"
// STATIC: "PARALLAXCORRECT" "0..1"
</syntaxhighlight>
</source>


This is declaring the static combo that our code from the previous section declares. But hold on tight, we still got more!
This is declaring the static combo that our code from the previous section declares. But hold on tight, we still got more!


We need to optimize the shader to not compile under certain conditions. For example, when shadercompile is working on the Shader Model 2.0 version, we can tell it not to build the version with the Parallax Corrected Cubemaps inside of it. Similarly, if there isn't even a dang cubemap to begin with, we don't want the parallax-correcting code to run! So after the line ''// SKIP ($DETAIL_BLEND_MODE == 11 ) && ($BUMPMAP != 0 )'', add:
We need to optimize the shader to not compile under certain conditions. For example, when shadercompile is working on the Shader Model 2.0 version, we can tell it not to build the version with the Parallax Corrected Cubemaps inside of it. Similarly, if there isn't even a dang cubemap to begin with, we don't want the parallax-correcting code to run! So after the line <code>// SKIP ($DETAIL_BLEND_MODE == 11 ) && ($BUMPMAP != 0 )</code>, add:
 
<source lang="text">
<syntaxhighlight lang="text">
// SKIP: $PARALLAXCORRECT && !$CUBEMAP
// SKIP: $PARALLAXCORRECT && !$CUBEMAP
// SKIP: $PARALLAXCORRECT [ps20]
// SKIP: $PARALLAXCORRECT [ps20]
</syntaxhighlight>
</source>


That's all there is to it! You can open your eyes now. Actually, hopefully they were open for all of this tutorial, because otherwise you will be getting ''very'' interesting results.
That's all there is to it! You can open your eyes now. Actually, hopefully, they were open for all of this tutorial, because otherwise, you will be getting ''very'' interesting results.


=== Compile the Shader(s) ===
=== Compile the Shader(s) ===
{{note|''Psst, if you need extra help with this section, look at the [[Source_SDK_2013:_Your_First_Shader]] page, it's a pretty good resource for compiling shaders!''}}


Now we need to compile the shader. Got some coffee to go make? Some meal to go prepare? Some arbitrary 30 minute task? Good! Compiling a custom LightmappedGeneric shader is going to take roughly that time. Any time you need to change anything with the shader, it'll take 30 minutes to fully recompile it! So, hopefully this works in the first try.
{{note|''Psst, if you need extra help with this section, look at the [[Source_SDK_2013:_Your_First_Shader|Your First Shader]] page, it's a pretty good resource for compiling shaders!''}}
 
Now we need to compile the shader. Got some coffee to go make? Some meal to go prepare? Some arbitrary 30-minute task? Good! Compiling a custom '''LightmappedGeneric''' shader is going to take roughly that time. Any time you need to change anything with the shader, it'll take 30 minutes to fully recompile it! So, hopefully, this works on the first try.


Inside of your '''stdshader_dx9_20b.txt''' file (or your custom game one, I don't judge), make sure that the following shaders are added:
Inside of your '''stdshader_dx9_20b.txt''' file (or your custom game one, I don't judge), make sure that the following shaders are added:


<syntaxhighlight lang="text">
<source lang="text">
SDK_lightmappedgeneric_ps2x.fxc
SDK_lightmappedgeneric_ps2x.fxc
SDK_lightmappedgeneric_ps11.fxc
SDK_lightmappedgeneric_ps11.fxc
SDK_lightmappedgeneric_vs20.fxc
SDK_lightmappedgeneric_vs20.fxc
</syntaxhighlight>
</source>


Now, compile. Go do that task I was asking you about. It's gonna take a while because it's a perl script compiling one of the more complex shaders, so it has a lot of combinations to go through. It's going to use all of your resources, too, so don't get spooked. Is it winter? Good, it'll warm up your room.
Now, compile. Go do that task I was asking you about. It's gonna take a while because it's a Perl script compiling one of the more complex shaders, so it has a lot of combinations to go through. It's going to use all of your resources, too, so don't get spooked. Is it winter? Good, it'll warm up your room.


When it completes, it will generate the proper ''SDK_lightmappedgeneric_*.inc'' files that your code uses, and you should have your ''SDK_lightmappedgeneric_*.vcs'' files inside of the '''shaders/fxc/''' subdirectory. You can copy these over to your game's '''shaders/fxc/''' folder.
When it completes, it will generate the proper '''SDK_lightmappedgeneric_*.inc''' files that your code uses, and you should have your '''SDK_lightmappedgeneric_*.vcs''' files inside of the '''shaders/fxc/''' subdirectory. You can copy these over to your game's '''shaders/fxc/''' folder.


With your new '''.inc''' files, you should be able to compile the ''game_shader_dx9'' project. Ensure the naming of everything matches! If you are missing an SDK_* include, try looking for the file and make sure it's named properly! With the project compiled, you will have a nifty '''game_shader_dx9.dll''' to copy over to your game's bin folder, alongside your server and client DLLs.
With your new '''.inc''' files, you should be able to compile the '''game_shader_dx9''' project. Ensure the naming of everything matches! If you are missing an '''SDK_*''' include, try looking for the file and make sure it's named properly! With the project compiled, you will have a nifty '''game_shader_dx9.dll''' to copy over to your game's '''bin/''' folder, alongside your server and client DLLs.


== What Next? ==
== What Next? ==


=== Using the new SDK_LightmappedGeneric Shader ===
=== Using the new SDK_LightmappedGeneric Shader ===
Now, since Valve doesn't allow the SDK 2013 branch to override the default '''LightmappedGeneric''' shader, you're going to either need to stub the materialsystem and manually replace all of the shaders, or just create/copy VMTs that use the shader.
Now, since Valve doesn't allow the SDK 2013 branch to override the default '''LightmappedGeneric''' shader, you're going to either need to stub the materialsystem and manually replace all of the shaders, or just create/copy VMTs that use the shader.


Line 612: Line 617:


'''parallaxcubes.fgd:'''
'''parallaxcubes.fgd:'''
<pre>
<source lang="text">
@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."


Line 639: Line 644:
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."
]
]
</pre>
</source>


=== (Optional) Test Map ===
=== (Optional) Test Map ===
Line 649: Line 654:
And that's it, try launching your mod to see if it works!
And that's it, try launching your mod to see if it works!


This is currently only tested on the SP branch of Source SDK 2013. The MP branch and Source SDK 2007 remains untested as of writing this article.
This is currently only tested on the SP branch of Source SDK 2013. The MP branch and Source SDK 2007 remain untested as of writing this article.


[[Category:Programming]]
[[Category:Programming]]
[[Category:Shaders]]
[[Category:Shaders]]
[[Category:Free source code]]
[[Category:Free source code]]

Revision as of 07:54, 21 April 2021

Template:Otherlang2

What Is This?

By default, Source's cubemaps are naively reflected on the ground and do not follow the player's perspective. This causes an unrealistic-looking reflection for most surfaces. A possible solution can be to parallax-correct the cubemaps based on the player's camera position, using a custom shader and a bounding box trigger for a cubemap. This tutorial, based directly on the work of Brian Charles, will show you how to do just that.

Brian Charles' video that showcases the before and after of this tutorial can be found here: Parallax Corrected Cubemaps in the Source Engine

The Code

Before starting, we'll need this file:


Which you'll put into <src code directory>/src/utils/vbsp/


Now let's jump right into it, shall we?

Go into your everything.sln solution and open cubemap.cpp

cubemap.cpp

Now add this under (not inside) the SideHasCubemapAndWasntManuallyReferenced( int iSide ) function:

char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];

Then right below, change the CubeMap_InsertSample( ... ) function to look like this:

void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr = "" )

And inside that function, at the very top add this:

g_pParallaxObbStrs[g_nCubemapSamples] = pParallaxObbStr;

Now go down to the PatchEnvmapForMaterialAndDependents( ... ) function and change it to look like this:

static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture, const char *pParallaxObbMatrix = "" )

While in the same function, scroll a tiny bit down to the if ( pDependentMaterial ) statement and change the line in it to this:

	bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix );

Scroll a tiny bit down again until you find this line:

	MaterialPatchInfo_t pPatchInfo[2];

Then just change 2 to 6, like so:

	MaterialPatchInfo_t pPatchInfo[6];

Do another tiny scroll down, and above this 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 you'll find this line:

	CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE );

Change it to this:

	CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT );

Then below that add this:

	// Clean up parallax stuff
	matRowList.PurgeAndDeleteElements();

Scroll down to the Cubemap_CreateTexInfo( ... ) function and change it to this:

static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3], int cubemapIndex )

Scroll a bit down inside that function and find this line:

	GeneratePatchedName( "c", info, false, pTextureName, 1024 );

Now below that line add this:

	// 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] );
	}

A bit down you'll see this if statement:

	if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) )

Change it to:

	if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) )

Scroll down until you find the Cubemap_FixupBrushSidesMaterials( void ) function, and inside it find this line:

	pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin );

Change it to this:

	pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID );

Scroll down to the Cubemap_AttachDefaultCubemapToSpecularSides( void ) function, and inside it find this line:

	pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin );

Change it to this:

	pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap );

Now we're done with cubemap.cpp, we can now move over to vbsp.h

vbsp.h

Now find this line:

void Cubemap_InsertSample( const Vector& origin, int size );

And change it to this:

void Cubemap_InsertSample( const Vector &origin, int size, char *pParallaxObbStr );

And above that add this:

extern char *g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES];

Now we're done with vbsp.h, now let's go into map.cpp

map.cpp

At the very top, add this include:

#include "matrixinvert.h"

Then scroll down to the LoadEntityCallback( ... ) function, then inside it find this line:

	const char *pSideListStr = ValueForKey( mapent, "sides" );

Now below that line, add:

	char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" );

Then a tiny bit down find this line:

	Cubemap_InsertSample( mapent->origin, size );

And change it to this:

	Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr );

Now below the parent if statement we're in, add this:

	//
	// 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 );
	}

Now scroll down to the LoadMapFile( ... ) function, inside it scroll down until you find this line:

	if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF))

Now above that line, add this:

	// 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;
		}
	}

And that should be it! Try compiling the VBSP project, it should compile without any errors.

Now all that's left is moving the freshly compiled vbsp.exe file to your game's bin/ folder.

The Shaders

Now, the fun stuff: shader editing! The Parallax Corrected Cubemaps that will now be patched into your materials need a custom LightmappedGeneric shader for them to be properly displayed.

Let's start simple. To edit shaders, you'll need to open your everything.sln file. Bonus points if you already had it open!

Warning.pngWarning:This section is the lengthiest and most tedious, just due to having to compile a custom LightmappedGeneric shader, which can take upwards of 30 minutes per compile, even on beefy computers!
Note.pngNote:It's recommended to follow the Shader Authoring tutorial to get set up with the wonderful DirectX 2008 SDK, and a feel for compiling shaders. This tutorial assumes you're comfortable with that.
Warning.pngWarning:Valve does not like when you override default shaders in the SDK 2013 project. Therefore, we will be creating identical shaders to the LightmappedGeneric one, just with the SDK_ prefix.

Shader C++ Files

Firstly, let's get the LightmappedGeneric shader familiar with the Parallax Corrected bounding boxes you 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

As warned about before, we need to turn this into an SDK shader, since compiling a default LightmappedGeneric shader works, but you can't use it in-game. Change the line:

BEGIN_VS_SHADER( LightmappedGeneric,
				 "Help for LightmappedGeneric" )

to be:

BEGIN_VS_SHADER( SDK_LightmappedGeneric, "Help for SDK_LightmappedGeneric" )

Now we need the implementation file to populate these shader parameters. Add the following lines inside of the

BEGIN_SHADER_PARAMS

block at the top of this file, 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" )

If you did your homework in the Shader Authoring section, you'll know that this is defining the shader parameters inside of the material file. Now let's assign them to the struct's new members. In the same file, inside of the

void SetupVars( LightmappedGeneric_DX9_Vars_t& info )

function, add the following lines at the end of it (after the info.m_nOutlineEnd1 = OUTLINEEND1; line):

	// Parallax cubemaps
	info.m_nEnvmapParallaxObb1 = ENVMAPPARALLAXOBB1;
	info.m_nEnvmapParallaxObb2 = ENVMAPPARALLAXOBB2;
	info.m_nEnvmapParallaxObb3 = ENVMAPPARALLAXOBB3;
	info.m_nEnvmapOrigin = ENVMAPORIGIN;

Great, now our shader's code knows about our patched parameters. But we still have to do stuff with them!

lightmappedgeneric_dx9_helper.cpp

As mentioned before, again, we need to change some prefixes. For your includes, change the lines:

#include "lightmappedgeneric_ps20.inc"
#include "lightmappedgeneric_vs20.inc"
#include "lightmappedgeneric_ps20b.inc"

to be:

#include "SDK_lightmappedgeneric_ps20.inc"
#include "SDK_lightmappedgeneric_vs20.inc"
#include "SDK_lightmappedgeneric_ps20b.inc"

You'll generate these in a bit, don't worry. But now this will cause some more issues further in the code! You'll need to replace all instances of the code of the default shader with your newly copied SDK one.

For example:

	DECLARE_STATIC_VERTEX_SHADER( lightmappedgeneric_vs20 );

to:

	DECLARE_STATIC_VERTEX_SHADER( sdk_lightmappedgeneric_vs20 );

DO IT FOR EVERY INSTANCE! Changing the imports will help when you come back to compile this module in a bit, as the old lightmappedgeneric_* shaders will be underlined red, as to help you find where you missed a specific spot.

Firstly, let's do a safety check. We need to ensure we have all the required components for the Parallax Corrected Cubemaps to work properly. Inside of

void InitParamsLightmappedGeneric_DX9( CBaseVSShader *pShader, IMaterialVar** params, const char *pMaterialName, LightmappedGeneric_DX9_Vars_t &info )

near the top of this file, add the following lines of code to the (you guessed it) end of it, after the InitFloatParam( info.m_nOutlineAlpha, params, 1.0 ); line:

	// 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]->SetUndefined();
	}

This allows the parallax-correcting code to not run if there's some issue with your patched materials or something.

Next, we need to tell the shader itself whether the above code we added returns true, and if so, to render the parallax corrected cubemap. Inside the

void DrawLightmappedGeneric_DX9_Internal( ... )

function, after the line

	bool hasNormalMapAlphaEnvmapMask = IS_FLAG_SET( MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK );

add the following boolean declaration:

	// Parallax cubemaps
	bool hasParallaxCorrection = params[info.m_nEnvmapParallaxObb1]->IsDefined();

Now let's check this boolean. Further down this gargantuan function, we need to set the combo on the pixel shader. Inside of the

	if ( g_pHardwareConfig->SupportsPixelShaders_2_b() )

block, add the following line BEFORE the

	SET_STATIC_PIXEL_SHADER( sdk_lightmappedgeneric_ps20b );

line:

	// Parallax cubemaps enabled for 2_0b and onwards
	SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, hasParallaxCorrection );

If you're able to read, you can start to realize what we're going to do next: since Shader Model 2.0 was all the rage for the GoldSource days, and something like parallax corrected cubemaps is a little high-tech for it, we need to enable it only if the graphics card running the engine supports Shader Model 2.0b (and higher). So a little further in this mega-function, inside of the else statement for the code above, RIGHT BEFORE the line SET_STATIC_PIXEL_SHADER( sdk_lightmappedgeneric_ps20 );, add the following:

	// Parallax cubemaps
	SET_STATIC_PIXEL_SHADER_COMBO( PARALLAXCORRECT, 0 ); // No parallax cubemaps with ps_2_0 :(

So now our pixel shader (which we're editing next section) knows whether or not it should handle the parallax corrected cubemaps.

Lastly, but certainly not least-ly (I mean come on we still haven't even edited the shader yet!), we need to send the pixel shader the proper value to use to display our parallax-corrected cubemap. So, even further down in this hulking hunk of hot garbage function, RIGHT BEFORE the pContextData->m_SemiStaticCmdsOut.End(); line, add the following block of code:

	// 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 );
	}

Woo! Now our shader has the right data passed into it. Alright, time to put on your DirectX 8 hats, we're going in!

The Shader Files

lightmappedgeneric_ps2_3_x.h

Now, I know what you're thinking. This isn't anywhere close to a shader file! It's a gosh dang C++ header file! But worry not, this file is included inside of the next file we edit, the actual shader source (FXC) file. So all of this code will actually get compiled into the shader, making it a shader source in disguise!

First things first, let's declare us some registers to snag up. If you're wondering why this shader doesn't work with Shader Model 2.0, here is why. Inside this file, right 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

Don't worry about that PARALLAXCORRECT, it'll make sense in a bit.

Now, let's do the main shader math we need! We can skip further down the file, and find where cubemaps are processed, inside of the if( bCubemap ) block, add the following block of code after the fresnel = fresnel * g_OneMinusFresnelReflection + g_FresnelReflection; line:

	// 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

Wonderful. Our shader will now properly do fancy maths to parallax the cubemap based on the player's position. Onwards to the FXC file!

A Shader By Any Other Name

You'll then need to make a copy of these files:

lightmappedgeneric_ps2x.fxc
lightmappedgeneric_ps11.fxc
lightmappedgeneric_vs20.fxc

And then rename the copies to:

SDK_lightmappedgeneric_ps2x.fxc
SDK_lightmappedgeneric_ps11.fxc
SDK_lightmappedgeneric_vs20.fxc

SDK_lightmappedgeneric_ps2x.fxc

Heck yeah, here we go, shader file editing! Told you we'd get here! Alright, you ready? Brace yourself, these edits are such a doozy.

Add the following line, after // STATIC: "FLASHLIGHT" "0..1" [ps20b] [XBOX]:

// STATIC: "PARALLAXCORRECT" "0..1"

This is declaring the static combo that our code from the previous section declares. But hold on tight, we still got more!

We need to optimize the shader to not compile under certain conditions. For example, when shadercompile is working on the Shader Model 2.0 version, we can tell it not to build the version with the Parallax Corrected Cubemaps inside of it. Similarly, if there isn't even a dang cubemap to begin with, we don't want the parallax-correcting code to run! So after the line // SKIP ($DETAIL_BLEND_MODE == 11 ) && ($BUMPMAP != 0 ), add:

// SKIP: $PARALLAXCORRECT && !$CUBEMAP
// SKIP: $PARALLAXCORRECT [ps20]

That's all there is to it! You can open your eyes now. Actually, hopefully, they were open for all of this tutorial, because otherwise, you will be getting very interesting results.

Compile the Shader(s)

Note.pngNote:Psst, if you need extra help with this section, look at the Your First Shader page, it's a pretty good resource for compiling shaders!

Now we need to compile the shader. Got some coffee to go make? Some meal to go prepare? Some arbitrary 30-minute task? Good! Compiling a custom LightmappedGeneric shader is going to take roughly that time. Any time you need to change anything with the shader, it'll take 30 minutes to fully recompile it! So, hopefully, this works on the first try.

Inside of your stdshader_dx9_20b.txt file (or your custom game one, I don't judge), make sure that the following shaders are added:

SDK_lightmappedgeneric_ps2x.fxc
SDK_lightmappedgeneric_ps11.fxc
SDK_lightmappedgeneric_vs20.fxc

Now, compile. Go do that task I was asking you about. It's gonna take a while because it's a Perl script compiling one of the more complex shaders, so it has a lot of combinations to go through. It's going to use all of your resources, too, so don't get spooked. Is it winter? Good, it'll warm up your room.

When it completes, it will generate the proper SDK_lightmappedgeneric_*.inc files that your code uses, and you should have your SDK_lightmappedgeneric_*.vcs files inside of the shaders/fxc/ subdirectory. You can copy these over to your game's shaders/fxc/ folder.

With your new .inc files, you should be able to compile the game_shader_dx9 project. Ensure the naming of everything matches! If you are missing an SDK_* include, try looking for the file and make sure it's named properly! With the project compiled, you will have a nifty game_shader_dx9.dll to copy over to your game's bin/ folder, alongside your server and client DLLs.

What Next?

Using the new SDK_LightmappedGeneric Shader

Now, since Valve doesn't allow the SDK 2013 branch to override the default LightmappedGeneric shader, you're going to either need to stub the materialsystem and manually replace all of the shaders, or just create/copy VMTs that use the shader.

The FGD File

parallaxcubes.fgd:

@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 : 
	"An entity that creates a sample point for the Cubic Environment Map."
[
	cubemapsize(choices) : "Cubemap Size" : 0 =
	[
		0 : "Default"
		1 : "1x1"
		2 : "2x2"
		3 : "4x4"
		4 : "8x8"
		5 : "16x16"
		6 : "32x32"
		7 : "64x64"
		8 : "128x128"
		9 : "256x256"
	]
	sides(sidelist) : "Brush faces": : "(Optional) Brushes faces to directly attach to the env_cubemap. Press Pick then click on faces in the 3D View to select them. Use CTRL while clicking to add or remove from the selection."
	parallaxobb(target_destination) : "Cubemap Bounds" : : "(Optional) assigns this cubemap a bounding box for parallax correction (brush entity tied to parallax_obb)."
]

@SolidClass = parallax_obb
[
	targetname(target_source) : "Name" : : "The name that other entities refer to this entity by."
]

(Optional) Test Map

Todo: Coming soon

Conclusion

And that's it, try launching your mod to see if it works!

This is currently only tested on the SP branch of Source SDK 2013. The MP branch and Source SDK 2007 remain untested as of writing this article.