Irrlicht对于Shadow Volume的实现:
对于Shadow Volume技术:
1:先渲染出整个场景,构造出整个场景的Depth Buffer
2:对于每一个需要产生阴影的Object,使用他的Shadow Volume(注意,该Volume是依赖于Light的位置的,所以一个Shadow Volume对应一个Light)采用Z Pass或者Z Fail技术,构造出Stencl Buffer。在Stencil Buffer中 Non-Zero的地方为阴影,Zero的地方为非阴影,并且注意:阴影的地方一定是Front Cap多于Back Cap。所以在Stencil Buffer阴影区一定是全正或者全负(这取决于Front Cap的Stencil Func)。
3:如果是多个物体,重复2过程。
4:最后得到的Stencil buffer中Non-Zero区域就是阴影区。
5:绘制一个阴影Quad,该Quad覆盖在整个屏幕,且仅在阴影区域绘制。
上面的过程也就是Irrlicht实现的过程。
这种实现方法有一个问题,不能实现多个光源的阴影叠加,也即是所有的阴影都是一样的。
具体实现:
1:指定一个节点要产生阴影
Irrlicht中的阴影节点不是独立的,他必须依附在一个节点上。因此一个节点要产生阴影,就对该节点添加一个阴影节点:
- virtual IShadowVolumeSceneNode* IAnimatedMeshSceneNode::addShadowVolumeSceneNode(const IMesh* shadowMesh=0, s32 id=-1, bool zfailmethod=true, f32 infinity=10000.0f) = 0;
- IShadowVolumeSceneNode* CAnimatedMeshSceneNode::addShadowVolumeSceneNode(const IMesh* shadowMesh,
- s32 id, bool zfailmethod, f32 infinity)
- {
- if (!SceneManager->getVideoDriver()->queryFeature(video::EVDF_STENCIL_BUFFER))
- return 0;
- if (Shadow)
- return Shadow;
- if (!shadowMesh)
- shadowMesh = Mesh; // if null is given, use the mesh of node
- Shadow = new CShadowVolumeSceneNode(shadowMesh, this, SceneManager, id, zfailmethod, infinity);
- return Shadow;
- }
将Shadow节点附到原节点上去。
2:实时生成Shadow Volume
注意:实时的生成Shadow Volume不是在OnAnimate和OnRegisterSceneNode中的而是在render中。
在CAnimatedMeshSceneNode::render中
// If the node will generate shadow, update the shadow
if (Shadow && PassCount==1)
Shadow->updateShadowVolumes();
updataShadowVolumes生成了该帧状态的Shadow Volumes。
3:"渲染"Shadow Volume,生成Stencil Buffer。
这一步在Shadow Pass中完成,Shadow Pass在Solid Pass之后,在Transparent Pass之前。
- // render shadows
- {
- CurrentRendertime = ESNRP_SHADOW;
- Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);
- if(LightManager)
- {
- LightManager->OnRenderPassPreRender(CurrentRendertime);
- for (i=0; i<ShadowNodeList.size(); ++i)
- {
- ISceneNode* node = ShadowNodeList[i];
- LightManager->OnNodePreRender(node);
- // Generate the shadow stencil buffer
- node->render();
- LightManager->OnNodePostRender(node);
- }
- }
- else
- {
- // Generate the stencil buffer one by one
- for (i=0; i<ShadowNodeList.size(); ++i)
- ShadowNodeList[i]->render();
- }
- // Draw a quad to simulate the shadow
- if (!ShadowNodeList.empty())
- // Draw the shadow
- Driver->drawStencilShadow(true,ShadowColor, ShadowColor,
- ShadowColor, ShadowColor);
- ShadowNodeList.set_used(0);
- if(LightManager)
- LightManager->OnRenderPassPostRender(CurrentRendertime);
- }
因为在Solid Pass之后,整个场景的Depth Buffer已经构建好了,这里就直接用Shadow Volume去生成Stencil Buffer。最后在画一个覆盖整个屏幕的shadow quad,这里只会在shadow区域绘制。
因此Shadow Pass就贴上了Shadow。
Shadow Volume的具体过程
1:生成Shadow Volume
void CShadowVolumeSceneNode::updateShadowVolumes()在原结点的render中调用
这个过程,主要是根据Light的位置,将Front Cap和Front Cap直接投影到无穷远处的Cap而组成得到Shadow Volume。
注意:
1:他的做法和标准做法不太一样。他投影的是Front Cap。
2:对于每一个Light生成一个Shadow Volume
2:生成Stencil Buffer的过程
Irrlicht中只实现了Z Pass的算法,没有实现Z Fail算法。
3:绘制过程
这里直接在Stencil Value > 0 的地方绘制
注意:
Irrlicht在生成Stencil Buffer和用Stencil Buffer绘制Quad是一个连续的过程,Stencil 在2中打开,3中是没有这个动作的。因此在2-3过程有其他的绘制过程绝对不能影响到Stencil Buffer也不能关闭Stencil。Irrlicht在绘制Stencil Buffer的Debug Data的代码有Bug。
如果要实现阴影的叠加效果,就应该单独的的一个光源处理,然后每一个光源叠加起来。
阅读(2073) | 评论(0) | 转发(0) |