ogre 陰影技術分析記錄
—-未完,待續
陰影技術,一般分為 基於 shadow volume 的和基於shadow map的。
shadow volume 原理是 通過模型EdgeData 計算陰影空間
shadow map原理是在燈光下,計算出物體在地面的區域,設定好顏色,動態紋理(陰影紋理)的形式疊加到接收陰影的物體上。
過程如下:
在場景管理器渲染開始時,建立陰影紋理。
mSceneMgr->_renderScene中呼叫
if (isShadowTechniqueInUse())
{
// Prepare shadow materials
initShadowVolumeMaterials() ;
}
跟進initShadowVolumeMaterials();
/* This should have been set in the SceneManager constructor, but if you
created the SceneManager BEFORE the Root object, you will need to call
SceneManager::_setDestinationRenderSystem manually.
*/
assert( mDestRenderSystem );
if (mShadowMaterialInitDone)
return;
if (!mShadowDebugPass)
{
MaterialPtr matDebug =
MaterialManager::getSingleton().getByName("Ogre/Debug/ShadowVolumes");
if (matDebug.isNull())
{
// Create ,建立陰影的動態紋理
matDebug = MaterialManager ::getSingleton().create(
"Ogre/Debug/ShadowVolumes",
ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
mShadowDebugPass = matDebug->getTechnique(0)->getPass(0);
mShadowDebugPass->setSceneBlending(SBT_ADD); //SBT_ADD將紋理值疊加到場景物體上
mShadowDebugPass->setLightingEnabled(false);
mShadowDebugPass->setDepthWriteEnabled(false);
TextureUnitState* t = mShadowDebugPass->createTextureUnitState();
t->setColourOperationEx(LBX_MODULATE, LBS_MANUAL, LBS_CURRENT,
ColourValue(0.7, 0.0, 0.2));
mShadowDebugPass->setCullingMode(CULL_NONE);
if (mDestRenderSystem->getCapabilities()->hasCapability(
RSC_VERTEX_PROGRAM))
//渲染系統支援頂點程式的話就進行 陰影計算的程式
{
/*陰影gpu程式初始化,(ogre中自帶的陰影gpu程式:
"Ogre/ShadowExtrudePointLight",
"Ogre/ShadowExtrudePointLightDebug",
"Ogre/ShadowExtrudeDirLight",
"Ogre/ShadowExtrudeDirLightDebug",
"Ogre/ShadowExtrudePointLightFinite",
"Ogre/ShadowExtrudePointLightFiniteDebug",
"Ogre/ShadowExtrudeDirLightFinite",
"Ogre/ShadowExtrudeDirLightFiniteDebug")*/
ShadowVolumeExtrudeProgram::initialise();
// Enable the (infinite) point light extruder for now, just to get some params ,點光源的先開啟
mShadowDebugPass->setVertexProgram(
ShadowVolumeExtrudeProgram::programNames[ShadowVolumeExtrudeProgram::POINT_LIGHT]);
mShadowDebugPass->setFragmentProgram(ShadowVolumeExtrudeProgram::frgProgramName);
mInfiniteExtrusionParams =
mShadowDebugPass->getVertexProgramParameters();
mInfiniteExtrusionParams->setAutoConstant(0,
GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX);
mInfiniteExtrusionParams->setAutoConstant(4,
GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE);
// Note ignored extra parameter - for compatibility with finite extrusion vertex program
mInfiniteExtrusionParams->setAutoConstant(5,
GpuProgramParameters::ACT_SHADOW_EXTRUSION_DISTANCE);
}
matDebug->compile();
}
else
{
mShadowDebugPass = matDebug->getTechnique(0)->getPass(0);
if (mDestRenderSystem->getCapabilities()->hasCapability(RSC_VERTEX_PROGRAM))
{
mInfiniteExtrusionParams = mShadowDebugPass->getVertexProgramParameters();
}
}
}
/********模板陰影的設定*******/
if (!mShadowStencilPass)
{
MaterialPtr matStencil = MaterialManager::getSingleton().getByName(
"Ogre/StencilShadowVolumes");
if (matStencil.isNull())
{
// Init
matStencil = MaterialManager::getSingleton().create(
"Ogre/StencilShadowVolumes",
ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
mShadowStencilPass = matStencil->getTechnique(0)->getPass(0);
if (mDestRenderSystem->getCapabilities()->hasCapability(
RSC_VERTEX_PROGRAM))
{
// Enable the finite point light extruder for now, just to get some params
mShadowStencilPass->setVertexProgram(
ShadowVolumeExtrudeProgram::programNames[ShadowVolumeExtrudeProgram::POINT_LIGHT_FINITE]);
mShadowStencilPass->setFragmentProgram(ShadowVolumeExtrudeProgram::frgProgramName);
mFiniteExtrusionParams =
mShadowStencilPass->getVertexProgramParameters();
mFiniteExtrusionParams->setAutoConstant(0,
GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX);
mFiniteExtrusionParams->setAutoConstant(4,
GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE);
// Note extra parameter
mFiniteExtrusionParams->setAutoConstant(5,
GpuProgramParameters::ACT_SHADOW_EXTRUSION_DISTANCE);
}
matStencil->compile();
// Nothing else, we don't use this like a 'real' pass anyway,
// it's more of a placeholder
}
else
{
mShadowStencilPass = matStencil->getTechnique(0)->getPass(0);
if (mDestRenderSystem->getCapabilities()->hasCapability(RSC_VERTEX_PROGRAM))
{
mFiniteExtrusionParams = mShadowStencilPass->getVertexProgramParameters();
}
}
}
if (!mShadowModulativePass)
{
MaterialPtr matModStencil = MaterialManager::getSingleton().getByName(
"Ogre/StencilShadowModulationPass");
if (matModStencil.isNull())
{
// Init
matModStencil = MaterialManager::getSingleton().create(
"Ogre/StencilShadowModulationPass",
ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
mShadowModulativePass = matModStencil->getTechnique(0)->getPass(0);
mShadowModulativePass->setSceneBlending(SBF_DEST_COLOUR, SBF_ZERO);
mShadowModulativePass->setLightingEnabled(false);
mShadowModulativePass->setDepthWriteEnabled(false);
mShadowModulativePass->setDepthCheckEnabled(false);
TextureUnitState* t = mShadowModulativePass->createTextureUnitState();
t->setColourOperationEx(LBX_MODULATE, LBS_MANUAL, LBS_CURRENT,
mShadowColour);
mShadowModulativePass->setCullingMode(CULL_NONE);
}
else
{
mShadowModulativePass = matModStencil->getTechnique(0)->getPass(0);
}
}
// Also init full screen quad while we're at it
if (!mFullScreenQuad)
{
mFullScreenQuad = OGRE_NEW Rectangle2D();
mFullScreenQuad->setCorners(-1,1,1,-1);
}
// Also init shadow caster material for texture shadows
/************新增投影材質*******************/
if (!mShadowCasterPlainBlackPass)
{
MaterialPtr matPlainBlack = MaterialManager::getSingleton().getByName(
"Ogre/TextureShadowCaster");
if (matPlainBlack.isNull())
{
matPlainBlack = MaterialManager::getSingleton().create(
"Ogre/TextureShadowCaster",
ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
mShadowCasterPlainBlackPass = matPlainBlack->getTechnique(0)->getPass(0);
// Lighting has to be on, because we need shadow coloured objects
// Note that because we can't predict vertex programs, we'll have to
// bind light values to those, and so we bind White to ambient
// reflectance, and we'll set the ambient colour to the shadow colour
mShadowCasterPlainBlackPass->setAmbient(ColourValue::White);
mShadowCasterPlainBlackPass->setDiffuse(ColourValue::Black);
mShadowCasterPlainBlackPass->setSelfIllumination(ColourValue::Black);
mShadowCasterPlainBlackPass->setSpecular(ColourValue::Black);
// Override fog
mShadowCasterPlainBlackPass->setFog(true, FOG_NONE);
// no textures or anything else, we will bind vertex programs
// every so often though
}
else
{
mShadowCasterPlainBlackPass = matPlainBlack->getTechnique(0)->getPass(0);
}
}
/***************接收陰影材質通道設定******************/
if (!mShadowReceiverPass)
{
MaterialPtr matShadRec = MaterialManager::getSingleton().getByName(
"Ogre/TextureShadowReceiver");
if (matShadRec.isNull())
{
matShadRec = MaterialManager::getSingleton().create(
"Ogre/TextureShadowReceiver",
ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
mShadowReceiverPass = matShadRec->getTechnique(0)->getPass(0);
// Don't set lighting and blending modes here, depends on additive / modulative
TextureUnitState* t = mShadowReceiverPass->createTextureUnitState();
t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
}
else
{
mShadowReceiverPass = matShadRec->getTechnique(0)->getPass(0);
}
}
// Set up spot shadow fade texture (loaded from code data block)
/*************點光源產生的衰減陰影材質***************/
TexturePtr spotShadowFadeTex =
TextureManager::getSingleton().getByName("spot_shadow_fade.png");
if (spotShadowFadeTex.isNull())
{
// Load the manual buffer into an image (don't destroy memory!
DataStreamPtr stream(
OGRE_NEW MemoryDataStream(SPOT_SHADOW_FADE_PNG, SPOT_SHADOW_FADE_PNG_SIZE, false));
Image img;
img.load(stream, "png");
spotShadowFadeTex =
TextureManager::getSingleton().loadImage(
"spot_shadow_fade.png", ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME,
img, TEX_TYPE_2D);
}
mShadowMaterialInitDone = true;
本步驟之後,陰影材質、頂點及片段程式已經確定建立完成。之後就是計算了
首先燈光中做必要的計算。
mSceneMgr->_renderScene
->findLightsAffectingFrustum(camera);
在其中,有關陰影計算的有:燈光、相機距離。
然後在呼叫
prepareShadowTextures(camera, vp);進行其他準備
ensureShadowTexturesCreated(); //確保陰影材質已經建立,若沒有則建立,建立紋理、紋理對應的相機、材質,並編譯,儲存
// Set the illumination stage, prevents recursive calls,照明階段設定,用於控制陰影產生。先將物體渲染到紋理,再渲染物體
IlluminationRenderStage savedStage = mIlluminationStage;
mIlluminationStage = IRS_RENDER_TO_TEXTURE;
if (lightList == 0)
lightList = &mLightsAffectingFrustum;
try
{
// Determine far shadow distance
Real shadowDist = mDefaultShadowFarDist;
if (!shadowDist)
{
// need a shadow distance, make one up
shadowDist = cam->getNearClipDistance() * 300;
}
Real shadowOffset = shadowDist * mShadowTextureOffset;
// Precalculate fading info
Real shadowEnd = shadowDist + shadowOffset;
Real fadeStart = shadowEnd * mShadowTextureFadeStart;
Real fadeEnd = shadowEnd * mShadowTextureFadeEnd;
// Additive lighting should not use fogging, since it will overbrighten; use border clamp
if (!isShadowTechniqueAdditive())
{
// set fogging to hide the shadow edge
mShadowReceiverPass->setFog(true, FOG_LINEAR, ColourValue::White,
0, fadeStart, fadeEnd);
}
else
{
// disable fogging explicitly
mShadowReceiverPass->setFog(true, FOG_NONE);
}
// Iterate over the lights we've found, max out at the limit of light textures
// Note that the light sorting must now place shadow casting lights at the
// start of the light list, therefore we do not need to deal with potential
// mismatches in the light<->shadow texture list any more
//用燈光計算陰影
LightList::const_iterator i, iend;
ShadowTextureList::iterator si, siend;
ShadowTextureCameraList::iterator ci;
iend = lightList->end();
siend = mShadowTextures.end();
ci = mShadowTextureCameras.begin();
mShadowTextureIndexLightList.clear();
size_t shadowTextureIndex = 0;
for (i = lightList->begin(), si = mShadowTextures.begin();
i != iend && si != siend; ++i)
{
Light* light = *i;
// skip light if shadows are disabled
if (!light->getCastShadows())
continue;
if (mShadowTextureCurrentCasterLightList.empty())
mShadowTextureCurrentCasterLightList.push_back(light);
else
mShadowTextureCurrentCasterLightList[0] = light;
// texture iteration per light.
size_t textureCountPerLight = mShadowTextureCountPerType[light->getType()];
for (size_t j = 0; j < textureCountPerLight && si != siend; ++j)
{
TexturePtr &shadowTex = *si;
RenderTarget *shadowRTT = shadowTex->getBuffer()->getRenderTarget();
Viewport *shadowView = shadowRTT->getViewport(0);
Camera *texCam = *ci;
// rebind camera, incase another SM in use which has switched to its cam
shadowView->setCamera(texCam);
// Associate main view camera as LOD camera
//關聯主相機為視相機的lod相機
texCam->setLodCamera(cam);
// set base
//設定相機的位置和方向(點光源除外)
if (light->getType() != Light::LT_POINT)
texCam->setDirection(light->getDerivedDirection());
if (light->getType() != Light::LT_DIRECTIONAL)
texCam->setPosition(light->getDerivedPosition());
// Use the material scheme of the main viewport
// This is required to pick up the correct shadow_caster_material and similar properties.
shadowView->setMaterialScheme(vp->getMaterialScheme());
// update shadow cam - light mapping
ShadowCamLightMapping::iterator camLightIt = mShadowCamLightMapping.find( texCam );
assert(camLightIt != mShadowCamLightMapping.end());
camLightIt->second = light;
if (light->getCustomShadowCameraSetup().isNull())
mDefaultShadowCameraSetup->getShadowCamera(this, cam, vp, light, texCam, j);//獲取,設定陰影相機,位置、旋轉、遠近、FOV、投射方式(方向燈光的相機設定為正交,其他設定為透射)
else
light->getCustomShadowCameraSetup()->getShadowCamera(this, cam, vp, light, texCam, j);
// Setup background colour
shadowView->setBackgroundColour(ColourValue::White);
// Fire shadow caster update, callee can alter camera settings
fireShadowTexturesPreCaster(light, texCam, j);
// Update target ,更新陰影材質
shadowRTT->update();
++si; // next shadow texture
++ci; // next camera
}
// set the first shadow texture index for this light.
mShadowTextureIndexLightList.push_back(shadowTextureIndex);
shadowTextureIndex += textureCountPerLight;
}
}
catch (Exception& e)
{
// we must reset the illumination stage if an exception occurs
mIlluminationStage = savedStage;
throw e;
}
// Set the illumination stage, prevents recursive calls
mIlluminationStage = savedStage;
fireShadowTexturesUpdated(
std::min(lightList->size(), mShadowTextures.size()));
ShadowTextureManager::getSingleton().clearUnused();
下面是建立陰影紋理的過程(ensureShadowTexturesCreated())
,建立紋理、建立紋理對應的渲染相機、建立材質。
if (mShadowTextureConfigDirty)
{
destroyShadowTextures();
//獲取陰影材質們
ShadowTextureManager::getSingleton().getShadowTextures(
mShadowTextureConfigList, mShadowTextures);
// clear shadow cam - light mapping
mShadowCamLightMapping.clear();
//Used to get the depth buffer ID setting for each RTT
size_t __i = 0;
// Recreate shadow textures
//重新建立陰影材質,根據相機的屬性等建立新的
for (ShadowTextureList::iterator i = mShadowTextures.begin();
i != mShadowTextures.end(); ++i, ++__i)
{
const TexturePtr& shadowTex = *i;
// Camera names are local to SM
String camName = shadowTex->getName() + "Cam";
// Material names are global to SM, make specific
String matName = shadowTex->getName() + "Mat" + getName();
RenderTexture *shadowRTT = shadowTex->getBuffer()->getRenderTarget();
//Set appropriate depth buffer
shadowRTT->setDepthBufferPool( mShadowTextureConfigList[__i].depthBufferPoolId );
// Create camera for this texture, but note that we have to rebind
// in prepareShadowTextures to coexist with multiple SMs
//渲染到紋理的形式,先建立相機,將相機所見內容渲染到動態的陰影紋理上
Camera* cam = createCamera(camName);
cam->setAspectRatio((Real)shadowTex->getWidth() / (Real)shadowTex->getHeight());
mShadowTextureCameras.push_back(cam);
// Create a viewport, if not there already
if (shadowRTT->getNumViewports() == 0)
{
// Note camera assignment is transient when multiple SMs
Viewport *v = shadowRTT->addViewport(cam);
v->setClearEveryFrame(true);
// remove overlays
v->setOverlaysEnabled(false);
}
// Don't update automatically - we'll do it when required ,設定為非自動更新
shadowRTT->setAutoUpdated(false);
// Also create corresponding Material used for rendering this shadow,建立材質
MaterialPtr mat = MaterialManager::getSingleton().getByName(matName);
if (mat.isNull())
{
mat = MaterialManager::getSingleton().create(
matName, ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
}
Pass* p = mat->getTechnique(0)->getPass(0);
if (p->getNumTextureUnitStates() != 1 ||
p->getTextureUnitState(0)->_getTexturePtr(0) != shadowTex)
{
mat->getTechnique(0)->getPass(0)->removeAllTextureUnitStates();
// create texture unit referring to render target texture
TextureUnitState* texUnit =
p->createTextureUnitState(shadowTex->getName());
// set projective based on camera,設定基於相機的投射紋理
texUnit->setProjectiveTexturing(!p->hasVertexProgram(), cam);
// clamp to border colour
texUnit->setTextureAddressingMode(TextureUnitState::TAM_BORDER);
texUnit->setTextureBorderColour(ColourValue::White);
mat->touch(); //編譯
}
// insert dummy camera-light combination
mShadowCamLightMapping[cam] = 0;
// Get null shadow texture
if (mShadowTextureConfigList.empty())
{
mNullShadowTexture.setNull();
}
else
{
mNullShadowTexture =
ShadowTextureManager::getSingleton().getNullShadowTexture(
mShadowTextureConfigList[0].format);
}
}
mShadowTextureConfigDirty = false;
}
至此,需要陰影所需的計算已準備完畢。已準備的有:
1、投射投影的材質、紋理
2、接收投影的材質、紋理
3、動態陰影紋理,包含其相機設定(根據燈光屬性設定的方位、遠近、投影模式、顏色)
,在準備的基礎上,陰影紋理程式已經更新。
下面的步驟是,在每一幀中將開了投影功能的物體投影到“畫布”陰影紋理上。即將計算的出的引數值,設定到vertex、fragment 程式中
_renderQueueGroupObjects->
renderAdditiveStencilShadowedQueueGroupObjects 或
renderModulativeStencilShadowedQueueGroupObjects 或
renderTextureShadowCasterQueueGroupObjects 或
renderAdditiveTextureShadowedQueueGroupObjects 或
renderModulativeTextureShadowedQueueGroupObjects 或
renderBasicQueueGroupObjects
取決於不同的投影策略
以renderAdditiveStencilShadowedQueueGroupObjects 為例,進行跟蹤
->renderShadowVolumesToStencil
->renderShadowVolumeObjects
->renderSingleObject (單個物體進行program中引數設定,rend)