Parallax Corrected Cubemaps
Contents
What Is This?
To do: This section needs to be filled in.
Video showcase: Parallax Corrected Cubemaps in the Source Engine
The Code
Before starting, we'll need this file:
Which you'll put into 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(...) 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 if (pDependentMaterial) 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_INSERT );
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 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 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 bin folder
The Shaders
To do: Coming soon (1.10.2018)
What Next?
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
To do: Coming soon (1.10.2018)
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 remains untested as of writing this article.