Parallax Corrected Cubemaps

From Valve Developer Community
Revision as of 03:18, 29 September 2018 by GamerDude27 (talk | contribs) (Initial (though unfinished) write up of the article)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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.