point light manager ...

Started by
6 comments, last by cozzie 11 years, 1 month ago

Hi all,

I've managed to write an effect/ shader which does single pass lighting with up to 16 point lights and 3 directional light (with PS/VS 3.0).

The process is as follows:

- a scene can contain numerous point lights

- every frame I check them against my view frustum (binary/ tree/ spacial culling to be done)

- all visible point lights are sorted on distane from camera (close to far)

- the first 16 (or max. for the used hardware) are send to the effect and rendered

- if visible point lights is <16, I set range to '0' and in the effect/shader I exclude point lights with range '0'.

Is there a better/ more efficient way, maybe to not go through them at all? (no if check).

Here's the point light part of the effect:


	for(int i=0;i<MaxPointLights;i++)
	{
		if(distt[i] != 0.0f)
		{
			distt[i] 	= distance(PointLightPos[i], input.wPos);
			att[i]	= 1 - saturate((distt[i] - PointLightFPRange[i]) / PointLightRange[i]);
			att[i]	= (pow(att[i], 2)) * PointLightInt[i];
		
			attcolored	= att[i] * PointLightCol[i];
			perpixel	= saturate(dot(normalize(PointLightPos[i] - input.wPos), normalize(input.Normal)));

			att_total	+= (attcolored * perpixel);
		}
	}

	return saturate((diff + amb + att_total) * textureColor);

This 'works', but brings me to a few questions/ need advice :)

First thing I found out that I should rather cull against a max. distance from camera, now I (potentially) apply lot of point lights which are definately not visible to the user (probably occlusion culling also a future improvement). OR implement occlusion culling (tried it before, but not much improvement, or worse, performance decrease).

My questions/ would like your advice;

- is this the/ a good way to go (when I'm not ready yet to go for deferred rendering, will try that later)

- framerate drops quite a bit because of a lot of things that are done now each frame to 'activate' the point lights

(visible FPS drop / shocky/ scientifically the constant 60fps limit I have, is jumping between 50 and 60 fps now)

- could I maybe improve all the setting of parameters etc?

Here's a part of the actual code:


// main function for rendering a frame

bool CD3d::RenderFrame(CD3dscene *pD3dscene, CD3dcam *pCam)
{
	if(!CheckDevice()) { mDeviceLost = true; return true; }
	pCam->Update();

	/** CULLING AND SORTING, BOTH MESHINSTANCES AS LIGHTS	**/
	if(!UpdateScene(pD3dscene, pCam)) return false;

	mD3ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
	mD3ddev->BeginScene();

	/** SET SHARED FX/ SHADER PARAMETERS **/
	if(D3DERR_INVALIDCALL == pD3dscene->mShaders[0].mEffect->SetMatrix(pD3dscene->mShaders[0].mHViewProjMatrix, &pCam->mMatViewProjection)) return false;	// SHARED PAREMETER IN POOL
	
	/** RENDER SCENE USING MESH INSTANCE SPECIFIC EFFECTS/ SHADERS, WITH THEIR OWN TECHNIQUE **/
	if(!RenderSceneOpaque(pD3dscene, pCam, "OpaqueTechnique")) return false;
	if(!RenderSceneBlended(pD3dscene, pCam, "BlendedTechnique")) return false;

	if(pD3dscene->mSkyBoxInScene) if(!pD3dscene->mSkyBox.Render(pCam->mPosition, pCam, mD3ddev)) return false;

	/** FFP RENDERING, I.E. SCENE STATISTICS **/		/** not used: SetDefaultRenderStates() doesn't do anything now **/
	PrintSceneInfo(pCam, pD3dscene->mNrMaterials, mInstInScene, mInstInSceneVisible, 
					mSceneGraph.mNrPointLights, mSceneGraph.mNrVisiblePointLights, mSceneGraph.mNrRenderPointLights);		

	/** PRESENT THE FINAL RENDERED SCENE FROM BACKBUFFER **/
	mD3ddev->EndScene();
	HRESULT hr = mD3ddev->Present(NULL, NULL, NULL, NULL); 
	OutputDebugRenderInfo(hr);

	return true;
}

// updating the scene, each frame

bool CD3d::UpdateScene(CD3dscene *pD3dscene, CD3dcam *pCam)
{
	// TODO here; introduce tree - spatial culling etc.					**/

	/** CULL MESH INSTANCES AGAINST FRUSTUM, VISIBLE YES/NO				**/
	for(mi=0;mi<mSceneGraph.mNrMeshInst;++mi)
	{
		if(pCam->BoxInFrustum(&pD3dscene->mMeshInstances[mi].mBoundingBox))
		{
			mSceneGraph.mMeshInst[mi].visible = true;
			++mInstInSceneVisible;
		}
		else mSceneGraph.mMeshInst[mi].visible = false;
	}

	/*** CULL POSITIONAL LIGHTS AGAINST FRUSTUM AND ENABLED YES/NO		**/
	for(lc=0;lc<mSceneGraph.mNrPointLights;++lc)
	{
		if(pCam->SphereInFrustum(&pD3dscene->mLights[mSceneGraph.mPointLights[lc]].mPosition, 
								  pD3dscene->mLights[mSceneGraph.mPointLights[lc]].mRange))
		{
			mSceneGraph.mLights[mSceneGraph.mPointLights[lc]].visible = true;
		}
		else
		{
			mSceneGraph.mLights[mSceneGraph.mPointLights[lc]].visible = false;
		}
	}

	/** UPDATE DIST TO CAM FOR BLENDED MESH INSTANCES AND POINT LIGHTS	**/						
	mSceneGraph.UpdateDistToCamMIBlended(pD3dscene, pCam);
	mSceneGraph.UpdateDistToCamLights(pD3dscene, pCam);
 
	/** SORTING: BLENDED MI BACK TO FRONT, POINT LIGHTS FRONT TO BACK	**/
	if(!mSceneGraph.SortBlendedMeshes(pD3dscene)) return false;
	if(!mSceneGraph.CullAndSortLights()) return false;

	/*** UPDATE LIGHTING SHADER WITH VISIBLE POSITIONAL LIGHTS			**/
	if(!ShaderUpdateLighting(pD3dscene, 0)) return false;

	/** UPDATE WORLD MATRIX, FOR DYNAMIC MESH INSTANCES ONLY			**/
	for(mi=0;mi<mSceneGraph.mNrMeshInstDynamic;++mi)
		pD3dscene->mMeshInstances[mSceneGraph.mDynamicMeshInstIndex[mi]].UpdateWorldMatrix();

// updating the visible point lights

bool CD3d::ShaderUpdateLighting(CD3dscene *pD3dscene, int pEffectId)
{
	// POINT LIGHTS - directional for now static
	for(lc=0;lc<mSceneGraph.mNrRenderPointLights;++lc)
	{
		if(D3D_OK != pD3dscene->mShaders[pEffectId].mEffect->SetFloatArray(pD3dscene->mShaders[pEffectId].mHPLPositionF[lc],
			pD3dscene->mLights[mSceneGraph.mVisiblePointLights[lc]].mPositionF, 3)) return false;

		if(D3D_OK != pD3dscene->mShaders[pEffectId].mEffect->SetFloatArray(pD3dscene->mShaders[pEffectId].mHPLColorF[lc],
			pD3dscene->mLights[mSceneGraph.mVisiblePointLights[lc]].mColorF, 4)) return false;

		if(D3D_OK != pD3dscene->mShaders[pEffectId].mEffect->SetFloat(pD3dscene->mShaders[pEffectId].mHPLRange[lc], 
			pD3dscene->mLights[mSceneGraph.mVisiblePointLights[lc]].mRange)) return false;

		if(D3D_OK != pD3dscene->mShaders[pEffectId].mEffect->SetFloat(pD3dscene->mShaders[pEffectId].mHPLFPRange[lc], 
			pD3dscene->mLights[mSceneGraph.mVisiblePointLights[lc]].mFPRange)) return false;

		if(D3D_OK != pD3dscene->mShaders[pEffectId].mEffect->SetFloat(pD3dscene->mShaders[pEffectId].mHPLIntensity[lc], 
			pD3dscene->mLights[mSceneGraph.mVisiblePointLights[lc]].mIntensity)) return false;
	}
	return true;
}

// scenegraph function where I determine the visible point lights

bool CSceneGraph::CullAndSortLights()
{
	// find all visible positional lights, now only point lights
	mNrVisiblePointLights = 0;

	for(lc=0;lc<mNrPointLights;++lc)
	{
		if(mLights[mPointLights[lc]].visible && mLights[mPointLights[lc]].enabled)
		{
			mVisiblePointLights[mNrVisiblePointLights] = mPointLights[lc];
			++mNrVisiblePointLights;
		}
	}

	// sort from front to back, camera distance
	for(lc=0;lc<mNrVisiblePointLights;++lc) 
	{
		mPosLightOrderTemp[lc] = mVisiblePointLights[lc];
		mPosLightDistToCam[lc] = mLights[mVisiblePointLights[lc]].distToCam;
	}
	
	sort(mPosLightOrderTemp, mPosLightOrderTemp + mNrVisiblePointLights, [&](int a, int b) 
		{ return mPosLightDistToCam[a] < mPosLightDistToCam[b]; });

	for(lc=0;lc<mNrVisiblePointLights;++lc) 
		mVisiblePointLights[lc] = mPosLightOrderTemp[lc];

	if(mNrVisiblePointLights <= mMaxPointLights) mNrRenderPointLights = mNrVisiblePointLights;
	else mNrRenderPointLights = mMaxPointLights;

	return true;
}

I'm also not 100% sure if my check sphere against frustum is OK

(for meshes I check against bounding box which works great)


void CD3dcam::CalculateFrustum()
{
	D3DXMatrixMultiply(&mFrustumMatrix, &mMatView, &mMatProjection);

	// left plane
	mFrustumPlane[0].a = mFrustumMatrix._14 + mFrustumMatrix._11;
	mFrustumPlane[0].b = mFrustumMatrix._24 + mFrustumMatrix._21;
	mFrustumPlane[0].c = mFrustumMatrix._34 + mFrustumMatrix._31;
	mFrustumPlane[0].d = mFrustumMatrix._44 + mFrustumMatrix._41;
	D3DXPlaneNormalize(&mFrustumPlane[0], &mFrustumPlane[0]);

	// right plane
	mFrustumPlane[1].a = mFrustumMatrix._14 - mFrustumMatrix._11;
	mFrustumPlane[1].b = mFrustumMatrix._24 - mFrustumMatrix._21;
	mFrustumPlane[1].c = mFrustumMatrix._34 - mFrustumMatrix._31;
	mFrustumPlane[1].d = mFrustumMatrix._44 - mFrustumMatrix._41;
	D3DXPlaneNormalize(&mFrustumPlane[1], &mFrustumPlane[1]);

	// top plane
	mFrustumPlane[2].a = mFrustumMatrix._14 - mFrustumMatrix._12;
	mFrustumPlane[2].b = mFrustumMatrix._24 - mFrustumMatrix._22;
	mFrustumPlane[2].c = mFrustumMatrix._34 - mFrustumMatrix._32;
	mFrustumPlane[2].d = mFrustumMatrix._44 - mFrustumMatrix._42;
	D3DXPlaneNormalize(&mFrustumPlane[2], &mFrustumPlane[2]);

	// bottom plane
	mFrustumPlane[3].a = mFrustumMatrix._14 + mFrustumMatrix._12;
	mFrustumPlane[3].b = mFrustumMatrix._24 + mFrustumMatrix._22;
	mFrustumPlane[3].c = mFrustumMatrix._34 + mFrustumMatrix._32;
	mFrustumPlane[3].d = mFrustumMatrix._44 + mFrustumMatrix._42;
	D3DXPlaneNormalize(&mFrustumPlane[3], &mFrustumPlane[3]);

	// near plane
	mFrustumPlane[4].a = mFrustumMatrix._13;
	mFrustumPlane[4].b = mFrustumMatrix._23;
	mFrustumPlane[4].c = mFrustumMatrix._33;
	mFrustumPlane[4].d = mFrustumMatrix._43;
	D3DXPlaneNormalize(&mFrustumPlane[4], &mFrustumPlane[4]);

	// far plane
	mFrustumPlane[5].a = mFrustumMatrix._14 - mFrustumMatrix._13;
	mFrustumPlane[5].b = mFrustumMatrix._24 - mFrustumMatrix._23;
	mFrustumPlane[5].c = mFrustumMatrix._34 - mFrustumMatrix._33;
	mFrustumPlane[5].d = mFrustumMatrix._44 - mFrustumMatrix._43;
	D3DXPlaneNormalize(&mFrustumPlane[5], &mFrustumPlane[5]);
}

bool CD3dcam::SphereInFrustum(D3DXVECTOR3 *pPosition, float pRadius)
{
	float fDistance;
	for(int i=0;i<6;++i)
    {
		fDistance = D3DXPlaneDotCoord(&mFrustumPlane[i], pPosition) + pRadius;

		if(fDistance < -pRadius) return false; // outside the frustum
		if((float)fabs(fDistance) < pRadius) return true; // intersects
    }
    return true; // inside the frustum completely or intersecting; see XLS
}

Any input is really appreciated.

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

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

Advertisement

As far as your lighting goes, you can just apply lighting using a "black" light, (color = black). I would run some tests and see which is better for you. It really depends on the complexity of your lighting. "If checks" in shaders can be very costly because it forces a shader unit to run both branches for all the pixels it's currently processing, and just discard the result of the unused branch. (At least this was the case, they may have gotten better at this)

Also, just a nit-pick side note, don't check your returns against D3D_OK. It's better to use the SUCCEEDED( result ) / FAILED( result ) macros. This is because a function may return a success result that isn't D3D_OK.

Perception is when one imagination clashes with another
Hi seabolt.
Thanks for the reply.

- on the if statements i might have a solution, i could load several copies of the appropriate effect with varying max pointlights defined. Then i can select and render using the effect that has closest to max point lights (i.e closest to 4, 8 or 16 in my case) for that frame.
For example max is now 16, 3 visible point lights means 13 unneccessary processed point lights in the shader. Using the effect with max4 point lights would reduce that by 12!
- does the failed/ succeeded result go for all d3d(x) functions? (hresult)
- can you explain a bit more on applying a "black" light (/background)?
I know have: 1 up to 3 max directional lights, ambient "light" and a skybox, accompanied by x positional point lights.
Using some ambient light and the skybox, reduces the "need" for having point lights active very far away from the camera (enabling/ disabling these point lights less noticable). I now also mark point lights visible which are in frustum but not visible, i.e occluded)

For testing the frustum sphere culling i'm gonna render spheres with transparant material with matching position to the point lights (and radiuss == lightrange). To see where it goes

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

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

The multiple effects will work, it's just a pain to manage them sometimes.

Yes, SUCCEEDED and FAILED work for all functions that return an HRESULT

By a "black" light, I mean a light whose color is black. You'll still do all the calculations of lighting, but since lighting is additive, a black light will have no effect on the final lighting.

Perception is when one imagination clashes with another
Thanks, i understand. Thats what i do now with the remaining lights (from nr visible to max fir the effect), i set range, color and position to 0

I'll keep you posted after the changes

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

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

Hi seabolt,
I found the cause of the frustum sphere issue. I was returning true when one point was intersecting the frustum and didn't process all planes.

On the same topic, i'm planning to implement spatial culling by cutting the scene in areas/big boxes (quad tree like but first without tutorial :)) When i do this i have a choice to make:
- make the areas/ tree nodes smaller then my frustum dimensions
(to prevent that i cull when all 8 bounding box points are outside the frustum when my camera is in the center of the area/box)
Or
- check the 8 corners are the area/ box + a 9th point which is the center of the area/box (to prevent the above)

(next step could be dynamically creating nodes, i'm not that far yet)

What would you do with which pros and cons?

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

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

It's been awhile since I've done any partitioning, so bear with me.

Generally when you're doing spatial culling, your goal is to just eliminate as much as possible before you actually test. So your end goal would be to have a list of nodes that intersect with your frustum at all. Then you iterate through the contents of those nodes and perform good old fashioned frustum culling. So the size of your nodes only determine the granularity of your elimination before you frustum cull.

So what I'm saying is that you just want to test to see if any of the corners of your frustum are in a node, and if so, add that node to be tested against. (Or subdivide that node and perform another series of tests.)

I hope I'm making sense.

Perception is when one imagination clashes with another

Thanks, I think I understand.

This means thinking/ acting the other way around then I do now. Now I check points against the frustum plains (pos or neg halfspace), instead of testing with/against the corners of the frustum.

I'll dig into it and start playing around

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

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

This topic is closed to new replies.

Advertisement