Lighting shader, high level optimizations?

Started by
6 comments, last by Styves 10 years, 8 months ago

Hi all,

I've been struggling, drawing, calculating and can finally say I have a fully working lighting shader;

- multiple directional lights

- multiple point lights

- forward rendering

- static number of lights, different effect files (no unrolling loops)

- diffuse and specular (blinn phong, using half angle)

- single texture

No copy/pasting, but fully understanding all the math biggrin.png

The normals I'm sending into the VS are normalized, so I've skipped normalizing the normal in the VS.

I think I've moved as much calculations as possible from the PS to the VS, to prevent wasting cycles.

My question; do you see any 'high level' chances of optimization?

(I'm not looking for micro optimizations, just stupid/ things I've overlooked)

The specular highlights are now the color of the source light, unfortunately this introduced 2 new float3's just for the specular highlight color (for both directional and point lights). Don't know if there's an easier way, assuming that non-metals specular highlights take over the color of the light source.

Any input is really appreciated.


/*************************************************************/
/**	CR GENERIC SHADER		1 DIRECTIONAL, 8 POINT LIGHTS **/
/**					PER PIXEL LIGHTING		**/
/**					AMBIENT, DIFFUSE, SPECULAR 	**/
/**					SINGLE TEXTURE, ANISOTROPY	**/
/**	TECHNIQUES:			OPAQUE AND BLENDED		**/
/**	SHADER MODEL:		3.0,NO BWARDS COMPATIBILITY	**/
/*************************************************************/
	                            
/*******************************************************/
/**	UNIFORM INPUT, CONTROLLED BY ENGINE			**/
/**  TRANSFORMATIONS & MATERIALS				**/
/*******************************************************/

uniform extern float4x4			World			: WORLD;
uniform extern shared float4x4	ViewProj		: VIEWPROJECTION;
uniform extern shared float3		CameraPos		: CAMERAPOS;

uniform extern float3	AmbientColInt	: AMB_COLOR_INTENSITY; 
            
uniform extern float4	MatDiff		: MATERIAL_DIFFUSE;
uniform extern float4	MatSpec		: MATERIAL_SPECULAR;
uniform extern float4	MatEmi		: MATERIAL_EMISSIVE;
uniform extern float	MatSpecPower	: MATERIAL_POWER;

uniform extern texture	Tex0			: TEXTURE0 < string name = "roadblock texture.tga"; >;

// modelfile, just for effectedit
string XFile 	= "roadblock.x";


/*******************************************************/
/**	UNIFORM INPUT, CONTROLLED BY ENGINE			**/
/**  LIGHT SOURCES AND PROPERTIES				**/
/*******************************************************/
 
#define MaxDirectionalLights 1
       
extern float3 		DirLightDir[MaxDirectionalLights];
extern float3		DirLightColInt[MaxDirectionalLights];			

#define MaxPointLights 8

extern float3		PointLightPos[MaxPointLights];
extern float		PointLightRange[MaxPointLights];
extern float		PointLightFPRange[MaxPointLights];
extern float3		PointLightColInt[MaxPointLights];				

/*******************************************************/
/**	SAMPLER STATES FOR TEXTURING				**/
/*******************************************************/

sampler2D textureSampler = sampler_state
{
	Texture		= (Tex0);
	MinFilter		= ANISOTROPIC;
	MagFilter		= LINEAR;
	MipFilter		= LINEAR;
	MaxAnisotropy	= 4;
};

/*******************************************************/
/**	VERTEX SHADER INPUT <= VERTEX DECLARATION		**/
/*******************************************************/

struct VS_INPUT
{
	float4 Pos		: POSITION0;
	float4 Normal	: NORMAL0;
	float2 TexCoord	: TEXCOORD0;
};

/*******************************************************/
/**	VERTEX SHADER OUTPUT - PIXEL SHADER INPUT       **/
/*******************************************************/

struct VS_OUTPUT
{
	float4 Pos		: POSITION0;
	float3 Normal	: TEXCOORD1;
	float2 TexCoord	: TEXCOORD2;
	float3 wPos		: TEXCOORD3;
	float3 ViewDir	: TEXCOORD4;
};

/*******************************************************/
/**	VERTEX SHADER PROGRAM					**/
/*******************************************************/
                    
VS_OUTPUT VS_function(VS_INPUT input)
{
	VS_OUTPUT Out = (VS_OUTPUT)0;

	float4 worldPosition = mul(input.Pos, World);
	Out.Pos = mul(worldPosition, ViewProj);

	Out.Normal = mul(input.Normal, (float3x3)World);	// VS input = already normalized
	Out.TexCoord = input.TexCoord;
	Out.wPos = worldPosition.xyz;

	Out.ViewDir = normalize(CameraPos - Out.wPos);
	
	return Out;
}

/*******************************************************/
/**	PIXEL SHADER PROGRAM					**/
/*******************************************************/

float4 PS_function(VS_OUTPUT input): COLOR0
{
	float4 textureColor = tex2D(textureSampler, input.TexCoord);
	float3 normal = normalize(input.Normal);

/** 	DIRECTIONAL LIGHTS - PER PIXEL (DIFFUSE & SPECULAR) **/
	float3 diffuseDir = 0.0f;
	float3 specularDir = 0.0f;

	for(int i=0;i<MaxDirectionalLights;i++)
	{	
		diffuseDir	+= saturate(DirLightColInt[i] * dot(normal,  DirLightDir[i]));

		float3 lightdir = normalize(DirLightDir[i] - input.wPos);
		float3 h = normalize(lightdir + input.ViewDir);
		specularDir += (pow(saturate(dot(h, normal)), MatSpecPower) * DirLightColInt[i]);

		/** DirLightColInt, should be just DirLightCol?? (no intensity) **/
	}

/** 	POINT LIGHTS - PER PIXEL (DIFFUSE & SPECULAR) **/

	float3 diffusePoint = 0.0f;
	float3 specularPoint = 0.0f;

	for(int i=0;i<MaxPointLights;++i)
	{
		float3 lightDir = normalize(PointLightPos[i] - input.wPos);

		// PER PIXEL ATTENUATION
		float dist = length(PointLightPos[i] - input.wPos);
		float att = saturate(1 - ((dist - PointLightFPRange[0]) / (PointLightRange[0] - PointLightFPRange[0])));

		/** WHY DO THIS, POWER OF 2, RANGE GETS SMALLER? **/
		att *= att;	

		// DIFFUSE

		float diffIntPoint = saturate(dot(normal, lightDir) * att);
		diffusePoint += saturate(diffIntPoint * PointLightColInt[i]);

		// SPECULAR; USING BLINN HALF ANGLE

		float3 h = normalize(lightDir + input.ViewDir);

		specularPoint += pow(saturate(dot(h, normal)), MatSpecPower) * att * PointLightColInt[i];

		/** SPECULAR HIGHLIGHTS, currently non metals			  **/
		/**										  **/
		/** NON-METALS	= SPEC COLOR = LIGHT COLOR 			  **/
		/** METALS		= SPEC COLOR = MATERIAL SPEC COLOR		  **/
		/**										  **/
		/** CYCLES: WITH MAT SPEC COLOR, SPEC INTENSITY CAN BE FLOAT INSTEAD OF FLOAT3's  **/
	}

/**	FINAL PIXEL COLOR (specular only for point lights **/

	return float4((saturate(AmbientColInt + (MatDiff * (diffuseDir + diffusePoint)) + (MatSpec * (specularDir + specularPoint)) + MatEmi) * textureColor.rgb), textureColor.a);
}

/*******************************************************/
/**	TECHNIQUES & PASSES					**/
/*******************************************************/

technique OpaqueTechnique
{
	pass P0
	{
		AlphaBlendEnable	= FALSE;

		VertexShader = compile vs_3_0 VS_function();
		PixelShader = compile ps_3_0 PS_function();
	}
}

/*******************************************************/
/**	BLENDED							**/
/*******************************************************/

technique BlendedTechnique
{
	pass P0
	{
		AlphaBlendEnable	= TRUE;
		SrcBlend		= SRCALPHA;
		DestBlend		= INVSRCALPHA;
		AlphaOp[0]		= SelectArg1;
		AlphaArg1[0]	= Texture;

		VertexShader = compile vs_3_0 VS_function();
		PixelShader = compile ps_3_0 PS_function();
	}
}

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Advertisement

Is it that optimized already, that there are no suggestions laugh.png

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Try splitting your final output into different code. Having that big set of mads is not fun to look at.

Don't saturate after applying the color of the light, saturate before. This way you don't clamp your light values if you decide to go (or are already going) HDR. Not important if you're keeping everything LDR.

att*att = doesn't cut the falloff in half, just makes it falloff non linearly. Tends to be more of a gradient and sometimes looks better.

"assuming that non-metals specular highlights take over the color of the light source." - I don't know if you misworded this, but the specular highlights of a non-metal material will be the same color as the light, it won't "take over the color" of the light.

I have no idea why you're so concerned with extra float3s, but if it's really a bother to you, try multiplying the light color with the specular color on engine side before sending the constant to the shader. That way the lights that get sent in will all be pre-multiplied with your specular color.


[...]
float3 PosToLight_Vec = PointLight_Pos.xyz - WorldPos.xyz;
float PosToLight_QDist = dot(PosToLight_Vec.xyz, PosToLight_Vec.xyz); //quadratic distance
 
float Attenuation = saturate(1.0 - PosToLight_QDist * PointLight_rQRange); //scaled by reciprocal quadratic light range + falloff inversion
 
Attenuation *= Attenuation; //makes it fade out smoothly
[...]

Also, you are always using index 0 for your attenuation calculation.

"assuming that non-metals specular highlights take over the color of the light source." - I don't know if you misworded this, but the specular highlights of a non-metal material will be the same color as the light, it won't "take over the color" of the light.

@Styves; I think we're saying the same, to get the final specularcolor component I multiply specular intensity with the lightcolor (and in the summed up specular light also multiply by the specular material color). That way I can control metal/ non metals by setting the material color for non-metals to 'white' (1.0, 1.0, 1.0). And for metals to a specific color.

@Bummel; thanks, I've corrected it. I'll also give the suggested attenuation equation a try. Struggling a bit with finding the nicest way. I also tried this:


// PER PIXEL ATTENUATION
float dist = length(PointLightPos[i] - input.wPos);
float att_s = 40;
	
float att1 	= exp2(dist) / (exp2(PointLightRange[i] - 3));
att1		= 1.0f / (exp2(att1) + 1);
		
att_s 	= 1.0f / (att_s+1);
		
float att 	= att1 - att_s;

att /= 1.0f - att_s;

But here I don't take the fullpower (fully lit) range into the equation (yet).

Which I'm thinking about, because the way of attenuation looks quite nice, although there are a lot of calculations.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Rather than store your light values in array-of-structures form, store things as a structure-of-arrays.

This explains what I'm talking about very nicely: http://zeuxcg.org/2007/10/28/my-own-lighting-shader-with-blackjack-and-hookers/

Thanks, I'm gonna look into it

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

I gave you some tips in your other post. You can find it here.

This topic is closed to new replies.

Advertisement