Shader Importer

Showcase

After working to implement this system we now have the possibility to render very nice looking animated shaders. On the screen, on static models and on animated models.


On screen shaders with Imgui to swap between the different effects. I also created the option to change colors of the shader in real time to easier adjust the colors to our engine (the colors do not match exactly compared to Shader Designer).

 


Animated drill model rendering an effect when it is currently being attacked and is not in sight. This was added to make it easier for the player to find where the drills are when playing the game. 

 


The laser here is a static model using a shader exported by Shader Designer and imported by my tool. It has two textures and the colors are extreme to create the glowing look. Something more subtle in this video is the screen on the gun. It displays the ammo type used and is also a shader that follows the same pipeline.

About

I created a pixel shader importer tool to be able to import pixel shaders created in Visual Studios tool called Shader Designer. This was a challenge I truly needed to dig deep into how models are rendered and how they are connected to both textures, shaders and the GPU.

This was a big project and something I am very pleased with how well it turned out for our games. Working together with the procedural artists in my group I have created a setup for them to be able to work with a visual node based tool. This is nothing required by the school or the project but something I felt was needed to highlight our games as much as possible.

Simple example of a fresnel effect using Shader Designer

Render functions

Shown in the code below is one of the more regular rendering functions of the tool.
If the shader tool holds the shader and the model it can be rendered using the code below. 

void ShaderTool::Render(DreamEngine::GraphicsStateStack& aGraphicsStateStack)
{
	for (int i = 0; i < myShouldRenderStaticVFX.size(); i++)
	{
		myStaticVFXType = static_cast<eStaticVFXType>(i);
		switch (myStaticVFXType)
		{
		default:
			aGraphicsStateStack.SetShaderToolMiscVarsBufferData(0.f, 0.f, myTotalVFXTimer);
			break;
		}

		if (myShouldRenderStaticVFX[i] == true)
		{
			if (i != 1)
			{
				DrawShaderTool(i);
			}
		}
	}

	for (int i = 0; i < myShouldRenderAnimatedVFX.size(); i++)
	{
		myAnimatedVFXType = static_cast<eAnimatedVFXType>(i);
		switch (myAnimatedVFXType)
		{
		default:
			aGraphicsStateStack.SetShaderToolMiscVarsBufferData(0.f, 0.f, myTotalVFXTimer);
			break;
		}

		if (myShouldRenderAnimatedVFX[i] == true)
		{
			DrawShaderToolAnimated(i);
		}
	}
}

If there are special cases, for example if the shader need its own timer for some reason. I created rendering functions for those cases as well. Data to the GPU

To be able to use and get all the correct data needed for the shaders I had to create a bunch of functions to send the correct data to the graphics engine. Some of the functions will be displayed in code below. These are not always needed for each shader, but to have the possibility of full functionality of the .hlsl exporter "Shader Designer" I needed to create them. 

void GraphicsStateStack::SetShaderToolMaterialVarsBufferData(DE::Vector4f aMaterialAmbient = DE::Vector4f{ 0,0,0,0 }, DE::Vector4f aMaterialDiffuse = DE::Vector4f{ 0,0,0,0 }, DE::Vector4f aMaterialSpecular = DE::Vector4f{ 0,0,0,0 }, DE::Vector4f aMaterialEmissive = DE::Vector4f{ 0,0,0,0 }, float aMaterialSpecularPower = 0)
{
	myRenderStateStack.back().shaderToolDataVersion = ++myLatestShaderToolDataVersion;
	myRenderStateStack.back().MaterialAmbient = aMaterialAmbient;
	myRenderStateStack.back().MaterialDiffuse = aMaterialDiffuse;
	myRenderStateStack.back().MaterialSpecular = aMaterialSpecular;
	myRenderStateStack.back().MaterialEmissive = aMaterialEmissive;
	myRenderStateStack.back().MaterialSpecularPower = aMaterialSpecularPower;
}

void GraphicsStateStack::SetShaderToolLightVarsBufferData(DE::Vector4f aAmbientLight = DE::Vector4f{ 0,0,0,0 }, DE::Vector4f aLightColor[4] = 0, DE::Vector4f aLightAttenuation[4] = 0, DE::Vector3f aLightDirection[4] = 0, float aLightSpecularIntensity[4] = 0, unsigned int aIsPointLight[4] = 0, unsigned int aActivLights = 0)
{
	myRenderStateStack.back().shaderToolDataVersion = ++myLatestShaderToolDataVersion;
	myRenderStateStack.back().AmbientLight = aAmbientLight;
	for (int i = 0; i < sizeof(myRenderStateStack.back().LightColor) / sizeof(myRenderStateStack.back().LightColor[1]); i++)
	{
		myRenderStateStack.back().LightColor[i] = aLightColor[i];
	}
	for (int i = 0; i < sizeof(myRenderStateStack.back().LightAttenuation) / sizeof(myRenderStateStack.back().LightAttenuation[1]); i++)
	{
		myRenderStateStack.back().LightAttenuation[i] = aLightAttenuation[i];
	}
	for (int i = 0; i < sizeof(myRenderStateStack.back().LightDirection) / sizeof(myRenderStateStack.back().LightDirection[1]); i++)
	{
		myRenderStateStack.back().LightDirection[i] = aLightDirection[i];
	}
	for (int i = 0; i < sizeof(myRenderStateStack.back().LightSpecularIntensity) / sizeof(myRenderStateStack.back().LightSpecularIntensity[1]); i++)
	{
		myRenderStateStack.back().LightSpecularIntensity[i] = aLightSpecularIntensity[i];
	}
	for (int i = 0; i < sizeof(myRenderStateStack.back().IsPointLight) / sizeof(myRenderStateStack.back().IsPointLight[1]); i++)
	{
		myRenderStateStack.back().IsPointLight[i] = aIsPointLight[i];
	}
	myRenderStateStack.back().ActiveLights = aActivLights;
}

void GraphicsStateStack::SetShaderToolObjectVarsBufferData(DE::Matrix4x4f aLocalToWorld4x4 = 0, DE::Matrix4x4f aLocalToProjected4x4 = 0, DE::Matrix4x4f aWorldToLocal4x4 = 0, DE::Matrix4x4f aWorldToView4x4 = 0, DE::Matrix4x4f aUVTransform4x4 = 0, DE::Vector3f aEyePosition = 0)
{
	myRenderStateStack.back().shaderToolDataVersion = ++myLatestShaderToolDataVersion;
	myRenderStateStack.back().LocalToWorld4x4 = aLocalToWorld4x4;
	myRenderStateStack.back().LocalToProjected4x4 = aLocalToProjected4x4;
	myRenderStateStack.back().WorldToLocal4x4 = aWorldToLocal4x4;
	myRenderStateStack.back().WorldToView4x4 = aWorldToView4x4;
	myRenderStateStack.back().UVTransform4x4 = aUVTransform4x4;
	myRenderStateStack.back().EyePosition = aEyePosition;
}

void GraphicsStateStack::SetShaderToolMiscVarsBufferData(float aViewportWidth = 0, float aViewportHeight = 0, float aTime = 0)
{
	myRenderStateStack.back().shaderToolDataVersion = ++myLatestShaderToolDataVersion;
	myRenderStateStack.back().ViewportWidth = aViewportWidth;
	myRenderStateStack.back().ViewportHeight = aViewportHeight;
	myRenderStateStack.back().Time = aTime;
}

void GraphicsStateStack::SetColorCorrectionBufferData(float someExposure, float someSaturation, DreamEngine::Vector3f someContrast, DreamEngine::Vector3f someBlackPoint, DreamEngine::Vector3f someTint)
{
	myRenderStateStack.back().shaderDataVersion = ++myLatestShaderDataVersion;
	myRenderStateStack.back().exposure = someExposure;
	myRenderStateStack.back().saturation = someSaturation;
	myRenderStateStack.back().contrast = someContrast;
	myRenderStateStack.back().blackPoint = someBlackPoint;
	myRenderStateStack.back().tint = someTint;
}

Example of implementation

When creating tools I always consider the amount of work myself and other programmers will have to spend on implementing the result of the tool. For that reason I decided to implement my shader importer tool in our singleton to have easy access anywhere in the code without having any model or shader doubled. 

Below is one example of how "drills" can be rendered using the tool. In this example we have three different drills and they are color coded. In this example we are using three different shaders to render them. 

for (int i = 0; i < myLoadedObjects->GetDefencePoints().size(); i++)
{
	if (myLoadedObjects->GetDefencePoints()[i]->IsTarget() && myDrillLineOfSight[i] == true)
	{
		if (myLoadedObjects->GetDefencePoints()[i]->GetCharID() == 'A')
		{
			MainSingleton::GetInstance()->GetShaderTool().Render(graphicsStateStack, engine.GetTotalTime(), myLoadedObjects->GetDefencePoints()[i]->GetAnimatedModelInstance(), static_cast<int>(eAnimatedVFXType::FresnelRed));
		}
		else if (myLoadedObjects->GetDefencePoints()[i]->GetCharID() == 'B')
		{
			MainSingleton::GetInstance()->GetShaderTool().Render(graphicsStateStack, engine.GetTotalTime(), myLoadedObjects->GetDefencePoints()[i]->GetAnimatedModelInstance(), static_cast<int>(eAnimatedVFXType::FresnelBlue));
		}
		else if (myLoadedObjects->GetDefencePoints()[i]->GetCharID() == 'C')
		{
			MainSingleton::GetInstance()->GetShaderTool().Render(graphicsStateStack, engine.GetTotalTime(), myLoadedObjects->GetDefencePoints()[i]->GetAnimatedModelInstance(), static_cast<int>(eAnimatedVFXType::FresnelGreen));
		}
	}
}