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.