Irrlicht源码分析_2_1_scene_ShadowVolume

这里要说的是Irrlicht中命名空间scene下的CShadowVolumeSceneNode实现。

<这里及以后所有的driver都默认为d3d9>

使用方式:

1 device设置渲染状态ZPass、ZFail

2 由CShadowVolumeSceneNode创建阴影体

3 device绘制阴影

阴影体的理论到处都是,不再做重复工作。

 

前提:

显示driver(IVideoDriver*)存在这么两个方法:

//! Draws a shadow volume into the stencil buffer. To draw a stencil shadow, do
//! this: Frist, draw all geometry. Then use this method, to draw the shadow
//! volume. Then, use IVideoDriver::drawStencilShadow() to visualize the shadow.
virtual void drawStencilShadowVolume(const core::vector3df* triangles, s32 count, bool zfail);

//! Fills the stencil shadow with color. After the shadow volume has been drawn
//! into the stencil buffer using IVideoDriver::drawStencilShadowVolume(), use this
//! to draw the color of the shadow.
virtual void drawStencilShadow(bool clearStencilBuffer=false,
            video::SColor leftUpEdge = video::SColor(0,0,0,0),
            video::SColor rightUpEdge = video::SColor(0,0,0,0),
            video::SColor leftDownEdge = video::SColor(0,0,0,0),
            video::SColor rightDownEdge = video::SColor(0,0,0,0));


第一个方法只是为了设置好driver 的状态,并将其绘制出来,阴影体数据来自CShadowVolumeSceneNode。

譬如设置ZPass的情况如下:

// Draw front-side of shadow volume in stencil/z only
        pID3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW );
        pID3DDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCRSAT);
        pID3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST, count / 3, triangles, sizeof(core::vector3df));

        // Now reverse cull order so front sides of shadow volume are written.
        pID3DDevice->SetRenderState( D3DRS_CULLMODE,   D3DCULL_CW );
        pID3DDevice->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECRSAT);
        pID3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLELIST, count / 3, triangles, sizeof(core::vector3df));

 

第二个方法绘制阴影(只是根据stencil调节一些阴影效果罢了)

 Irrlicht使用的方法很简单粗暴,直接绘制一个矩形就是了。

 

 

所以这里的重点就是如何生成正确的ShadowVolume,也就是CShadowVolumeSceneNode在这里面都做了些啥。

 

入口点:CShadowVolumeSceneNode::createShadowVolume

 

void CShadowVolumeSceneNode::createZPassVolume(s32 faceCount,s32 &numEdges,const core::vector3df& lightsource,SShadowVolume* svp,bool caps)
{
    //1:normalize the light
    core::vector3df light(lightsource);
    light*=Infinity;
    if(light == core::vector3df(0,0,0))
        light = core::vector3df(0.0001f,0.0001f,0.0001f);

    //2: get the face normal
    core::vector3df normal;
    u16 wFace0,wFace1,wFace2;

    for(s32 i=0;i<faceCount;i++)
    {
        wFace0 = Indices[3*i+0];
        wFace1 = Indices[3*i+1];
        wFace2 = Indices[3*i+2];

        core::vector3df v0(Vertices[wFace2]-Vertices[wFace1]);
        core::vector3df v1(Vertices[wFace1]-Vertices[wFace0]);

        normal = v0.crossProduct(v1);

        //if face is belong to back side
        if(normal.dotProduct(light) >=0.0f)
        {
            //back side
            Edges[2*numEdges+0]=wFace0;
            Edges[2*numEdges+1]=wFace1;
            numEdges++;

            
            Edges[2*numEdges+0]=wFace1;
            Edges[2*numEdges+1]=wFace2;
            numEdges++;

            Edges[2*numEdges+0]=wFace2;
            Edges[2*numEdges+1]=wFace0;
            numEdges++;

            //caps=ZFail
            if(caps && svp->vertices && svp->count <svp->size-5)
            {
                //store back vertex pos
                svp->vertices[svp->count++] = Vertices[wFace0];
                svp->vertices[svp->count++] = Vertices[wFace2];
                svp->vertices[svp->count++] = Vertices[wFace1];

                //move front pos 
                svp->vertices[svp->count++] = Vertices[wFace0]-light;
                svp->vertices[svp->count++] = Vertices[wFace1]-light;
                svp->vertices[svp->count++] = Vertices[wFace2]-light;

            }
        }

    }
};


这里构造了Edges这个数组:

他是在光照下物体背面下的网格边

 

在创建了背面的网格边缘数据后:

for(s32 i=0;i<numEdges;i++)
    {
        core::vector3df& v1 = Vertices[Edges[2*i+0]];
        core::vector3df& v2 = Vertices[Edges[2*i+1]];
        core::vector3df v3(v1-ls);
        core::vector3df v4(v2-ls);

        //add a quad as boundary of volume
        if(svp->vertices&& svp->count <svp->size-5)
        {
            svp->vertices[svp->count++]=v1;
            svp->vertices[svp->count++]=v2;
            svp->vertices[svp->count++]=v3;

            svp->vertices[svp->count++]=v2;
            svp->vertices[svp->count++]=v4;
            svp->vertices[svp->count++]=v3;


        }
    }


可以看到,将背面的每一条边沿着光线挤出为一个四边形,构建两个三角形组成阴影体,ZPass不同于ZFail,可以不用保持封口。等之后添加上实验图。

 

使用如此粗鲁的阴影体生成方式,居然还能到fps74的情况。

还有一张,这张由于没有把台阶作为阴影体源来渲染,所以阴影会有悬空的感觉。

作为参考这里把Irrlicht Engine的渲染图片上传一张:

问题:运行中Irrlicht使用ZPass方法(它默认ZFail)的时候,阴影出现大幅的抖动。效果不好。

发现Irrlicht实现的时候ZPass的实现并不准确,在CD3D9Driver::setRenderStatesStencilFillMode方法中:

在绘制Stencil阴影时

pID3DDevice->SetRenderState(D3DRS_STENCILREF, 0x1);
        pID3DDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_LESSEQUAL);
        //pID3DDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_GREATEREQUAL);

但是它是这么清空Stencil的(CD3D9Driver::drawStencilShadow):

if (clearStencilBuffer)
        pID3DDevice->Clear( 0, NULL, D3DCLEAR_STENCIL,0, 1.0, 0);

原型:STDMETHOD(Clear)(THIS_ DWORD Count,CONST D3DRECT* pRects,DWORD Flags,D3DCOLOR Color,float Z,DWORD Stencil) PURE;

可以看出,将stencil清空为0(最后一个)。

所以正确的方式应为:

pID3DDevice->SetRenderState(D3DRS_STENCILREF, 0x0);
pID3DDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL);

我看的是1.3版本。只知道后续的版本是否已修改。

 

思考:Irrlicht使用背面每一个三角形而不是只有边缘去创建爱你阴影体,这时候driver绘制大量无用的数据,虽然最终stencil不会错,但是增加了stencil加1减1的次数。在CShadowVolumeSceneNode::setMeshToRenderForm这个方法中计算的领域信息没有使用到(ZFail用到了)。暂时先就这样,ZFail留待之后加入。

 

posted @ 2015-01-24 19:24  kalluwa  阅读(103)  评论(0编辑  收藏  举报