欢迎阅读指正和转载,但请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
 
深度探索DxFramework
 
第11章 3D primitives的剖析 3
 
11.1.5.球(sphere)
 
相对于圆锥圆柱等三维几何体而言,用三角形构造球的方法要复杂一些。用三角形构造球,第一步首先要把球分成若干个切片(slice)。分成若干个切片之后,每个切片便可以视为由一系列的三角形组成,和计算圆锥圆柱的顶点方法类似,也是把每一个切片的顶点投影到XZ平面中。下图便是一个切片的投影图。
 
 
每个切片有上底面和下底面。上图中的虚线便是组成每个切片的三角形。其计算方法如下代码:
 
class C_Sphere : public C_Primitive
{
public:
    C_Sphere(); 
    virtual ~C_Sphere(); 
    virtual void Update();
    virtual void Render3D(D3DXMATRIX *p_WorldMatrix); 
private:
    unsigned int numSlices;    //沿着Y轴球的切片数
    unsigned int numSegments;//每一切片的段数
    unsigned int numTriangles;//Number of triangles used 
};

C_Sphere::C_Sphere() 
{
    quality = 7;
}

C_Sphere::~C_Sphere() 
{
}

void C_Sphere::Update() 
{
    if (updateMemory) 
    {
        numSlices = quality + 2;
        /* 切片数为quality+2, 但是渲染的时候,实际切片数是quality+1
            切片多边形的边数为quality+2 */
        numSegments = quality + 3;  
        /* 每个切片分成的段,也就是说,每个切片由几个四边形(每个四边形由
            两个三角形组成)组成,在渲染的时候,实际的segment是qulity+2 */
        numVertices = numSlices * numSegments; 
        
        //三角形的数目,由上面的注解可以得知这个数量的来由
        numTriangles = 2 * (numSlices - 1) * (numSegments - 1);

        //因为使用的是三角形列表的方式来渲染,所以索引数为三角形数×3
        numIndices = numTriangles * 3; 
        CreateVertexBuffer(numVertices);
        CreateIndexBuffer(numIndices);
        unsigned short* p_DeviceIBMem;
        LockIndexBuffer(&p_DeviceIBMem);

        //填充索引数
        for( unsigned int slice = 0; slice < (numSlices - 1); slice++) 
        {
            for( unsigned int segment = 0; segment < (numSegments - 1); segment++) 
            {
                unsigned int sliceBase = slice * numSegments;
                unsigned int segIndex = sliceBase + segment;
                unsigned int baseIndex = (slice * numSlices + segment) * 6;
                //递归填充某slice的某segment的两个
                //三角形的六个顶点索引
                p_DeviceIBMem[baseIndex] = segIndex;
                p_DeviceIBMem[baseIndex+1] = segIndex + numSegments;
                p_DeviceIBMem[baseIndex+2] = sliceBase +  segment + 1;
                p_DeviceIBMem[baseIndex+3] = sliceBase + segment + 1;
                p_DeviceIBMem[baseIndex+4] = segIndex + numSegments;
                p_DeviceIBMem[baseIndex+5] = segIndex + numSegments+1;
            }
        }

        UnlockIndexBuffer();
        CreateCommandList(1);
        SetDrawIndexedPrimitiveCommand(0, D3DPT_TRIANGLELIST, 0, numVertices, 0, numTriangles);
        updateMemory = false;
    }

    if (updateVertices) 
    {
        int i;
        C_VertexManipulator vertexMemory;
        LockVertexBuffer(&vertexMemory);

        for(unsigned int slice = 0;slice < numSlices;slice++) 
        {
            float heightAngle = ((float)slice/(numSlices-1)) * D3DX_PI; 
            float y = 0.5f * 1.0f * cosf(heightAngle);
            float radius = (float)fabs(0.5f*sinf(heightAngle)); 

            for( unsigned int segment = 0; segment < numSegments; segment++) 
            {
                float angle = ((float)segment / numSlices) * 2 * D3DX_PI; 
                float x = radius * 1.0f * sinf(angle); 
                float z = radius * 1.0f * cosf(angle);
                unsigned int point = slice*numSegments+segment;
                vertexMemory[point].position = D3DXVECTOR3(x, y, z);
 
                switch (cullMode) 
                {
                case D3DCULL_CW: // Visible from inside
                    vertexMemory[point].normal = D3DXVECTOR3(-x, -y, -z);
                    break
                case D3DCULL_CCW: // Visible from outside
                    vertexMemory[point].normal = D3DXVECTOR3(x, y, z);
                    break;
                }
                D3DXVec3Normalize(&vertexMemory[point].normal, &vertexMemory[point].normal);

                // Write texture coordinate
                for (i = 0; i < numTexture; i++) 
                {
                    vertexMemory[point].texCoords[i] = 
                    D3DXVECTOR2(
                        textureOffset[i].x + textureScale[i].x * 
                        (1.0f-(float)segment/(numSlices-1)), 
                        textureOffset[i].y + 
                        textureScale[i].y *
                        (float)slice/(numSlices - 1) );
                }
            }
        }

        for (i = 0; i < numVertices; i++) 
        {
            vertexMemory[i].diffuseColor = 0xffffffff; 
        }

        UnlockVertexBuffer();
        updateVertices = false;
        p_BoundingBox->FindBoundingBox(this);
    }
}

void C_Sphere::Render3D(D3DXMATRIX *p_WorldMatrix) 
{
    if (updateMemory)
        return;
    C_View::p_D3DDevice->SetTransform(D3DTS_WORLD, p_WorldMatrix);
    SetupRendering();

    // Setup D3D stuffs
    C_View::p_D3DDevice->SetStreamSource(0, p_DeviceVB, GetVertexSize());
    C_View::p_D3DDevice->SetVertexShader(GetVertexFormat());
    C_View::p_D3DDevice->SetIndices(p_DeviceIB, 0);
    C_View::p_D3DDevice->SetRenderState(D3DRS_FILLMODE,renderMode);

    if (cullMode == D3DCULL_NONE) 
    {
        this->SetCullMode(D3DCULL_CW);
        Update();
        C_View::p_D3DDevice->DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST, 0, numVertices, 0, numTriangles);
        this->SetCullMode(D3DCULL_CCW);
        Update();
        SetupRendering();
        C_View::p_D3DDevice->DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST, 0, numVertices, 0, numTriangles);
        cullMode = D3DCULL_NONE;
    }
    else 
    {
        C_View::p_D3DDevice->DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST, 0, numVertices, 0, numTriangles);
    }
}
 
球的实现就如上。接下来就是最后的一个三维图元——立方体了。
 
11.1.6.立方体(cube)
 
立方体由六个正方形构成了它的表面。每个正方形的顶点构成方法和上述的C_Square类是一样的。依次计算六个面的顶点及其索引便可。示意图如下。
 
 
知道了顶点生成的方法,实现代码如下:
 
class C_Cube : public C_Primitive
{
public:
    C_Cube(); 
    virtual ~C_Cube();
    virtual void Update();
private:
    unsigned int numTrianglesPerSide;
    unsigned int numTriangles;
};

C_Cube::C_Cube() 
{
    quality = 1;
}

C_Cube::~C_Cube()
{
}

void C_Cube::Update() 
{
    if (updateMemory) 
    {
        // 可参见C_Square类的插图,每个面有(quality+1)*(quality+1)个顶点,共有6个面
        numVertices = (quality + 1) * (quality + 1) * 6;
        
        // 每个小正方形有2个三角形,每个面共quality*quality个小
        // 正方形,而且使用的是三角形列表来渲染。所以每个面的三角形个数为:
        numTrianglesPerSide = quality * quality * 2;
        numTriangles = numTrianglesPerSide * 6;
        // 每个三角形的端点使用一个索引,所以顶点索引的个数为三角形个数
        numIndices = numTriangles * 3; 
        CreateVertexBuffer(numVertices);
        CreateIndexBuffer(numIndices);
        unsigned short* p_DeviceIBMem;
        LockIndexBuffer(&p_DeviceIBMem);
        //计算三角形索引
        for (unsigned int side = 0; side < 6; side++) 
        {
            for (unsigned int row = 0; row < quality; row++) 
            {
                for(unsigned int col = 0; col < quality; col++)
                {
                    //按行优先的原则。先写完一个面再写另外的面每个小正方
                    //形有两个三角形按扇面方式组织,每个正方形有6个三角
                    //形顶点,所以要6 * (row * quality + col)
                    int baseIndex = side * numTrianglesPerSide *3 + 6 * (row * quality + col);

                    //baseVertexIndex变量用来控制跳转到另一个表面上
                    //读取顶点。当它的值发生变化的时候,就是结束读取一个
                    //表面,跳到另一个表面去读取顶点计算索引了。
                    int baseVertexIndex = side * (quality + 1) * (quality + 1);
                    //指向小正方形左上角的顶点的索引
                    p_DeviceIBMem[baseIndex]= baseVertexIndex +(row) * (quality + 1) + col;
                    //指向小正方形右下角的顶点的索引
                    p_DeviceIBMem[baseIndex+1]= baseVertexIndex+(row + 1) *(quality + 1) + col + 1;
                    //指向小正方形左下角的顶点的索引
                    p_DeviceIBMem[baseIndex+2]= baseVertexIndex +(row + 1) * (quality + 1) + col;
                    //指向小正方形左上角的顶点的索引
                    p_DeviceIBMem[baseIndex+3]= baseVertexIndex +(row) * (quality + 1) + col;
                    //指向小正方形右上角的顶点的索引
                    p_DeviceIBMem[baseIndex+4]= baseVertexIndex +(row) * (quality + 1) + col + 1;
                    //指向小正方形右下脚的顶点的索引
                    p_DeviceIBMem[baseIndex+5]= baseVertexIndex + (row + 1) * (quality + 1) + col + 1;
                }
            }
        }
        UnlockIndexBuffer();
        CreateCommandList(1);
        SetDrawIndexedPrimitiveCommand(0, D3DPT_TRIANGLELIST,0, numVertices, 0, numTriangles);
        updateMemory = false;
    }

    if (updateVertices) 
    {
        C_VertexManipulator vertexMemory;
        unsigned i;
        LockVertexBuffer(&vertexMemory);

        for (unsigned int row = 0; row < quality + 1; row++) 
        {
            for (unsigned int col = 0; col < quality + 1; col++)
            {
                int index = row * (quality + 1) + col;
                for (i = 0; i < (unsigned)numTexture; i++) 
                {
                    vertexMemory[index].texCoords[i] = D3DXVECTOR2(
                        textureOffset[i].x + textureScale[i].x * col / quality,
                        textureOffset[i].y + textureScale[i].y * row / quality);
                }
            }
        }
        
        for (unsigned int side = 0; side < 6; side++) 
        {
            for (unsigned int row = 0; row < quality + 1; row++)
            {
                for (unsigned int col = 0; col < quality + 1; col++)
                {
                    int index = side * (quality + 1)*(quality + 1) + 
                        row * (quality + 1) + col;

                    int cullCoeff = 1;

                    if (cullMode == D3DCULL_CW)
                        cullCoeff =-1;
                    
                    switch (side) 
                    {
                    case 0:        //计算顶面的各个顶点位置
                        vertexMemory[index].position =
                            D3DXVECTOR3(-1.0f/2+col*1.0f/quality,
                            1.0f/2, 1.0f/2-row*1.0f/quality);
                        vertexMemory[index].normal = D3DXVECTOR3(0.0f,cullCoeff*1.0f,0.0f);
                        break;
                    case 1:        //计算底面的各个顶点位置
                        vertexMemory[index].position =
                            D3DXVECTOR3(-1.0f/2+col*1.0f/quality,
                            -1.0f/2, -1.0f/2+row*1.0f/quality);
                        vertexMemory[index].normal = D3DXVECTOR3(0.0f,cullCoeff*(-1.0f),0.0f);
                        break;
                    case 2:        //计算前侧面的各个顶点位置
                        vertexMemory[index].position = 
                            D3DXVECTOR3(-1.0f/2+col*1.0f/quality, 
                            1.0f/2-row*1.0f/quality, -1.0f/2);
                        vertexMemory[index].normal = D3DXVECTOR3(0.0f, 0.0f,cullCoeff *(-1.0f));
                        break;
                    case 3:        //计算后侧面的各个顶点位置
                        vertexMemory[index].position = 
                        D3DXVECTOR3(1.0f/2-col*1.0f/quality, 
                        1.0f/2-row*1.0f/quality, 1.0f/2);
                        vertexMemory[index].normal = D3DXVECTOR3(0.0f,0.0f,cullCoeff*1.0f);
                        break;
                    case 4:        //计算左侧面的各个顶点位置
                        vertexMemory[index].position = 
                        D3DXVECTOR3(-1.0f/2,
                        1.0f/2-row*1.0f/quality, 
                        1.0f / 2 - col * 1.0f / quality);
                        vertexMemory[index].normal =  D3DXVECTOR3(cullCoeff*(-1.0f),0.0f,0.0f);
                        break;
                    case 5:        //计算右侧面的各个顶点位置
                        vertexMemory[index].position = 
                            D3DXVECTOR3(1.0f/2, 
                            1.0f/2-row*1.0f/quality, 
                            -1.0f/2+col*1.0f/quality);
                        vertexMemory[index].normal = D3DXVECTOR3(cullCoeff*(1.0f),0.0f,0.0f);
                        break;
                    }
                    
                    //计算纹理坐标
                    for (i = 0; i < (unsigned)numTexture; i++) 
                    {
                        vertexMemory[index].texCoords[i] = 
                        D3DXVECTOR2(textureOffset[i].x + 
                        textureScale[i].x * col / quality, 
                        textureOffset[i].y + 
                        textureScale[i].y * row / quality);
                    }
                }
            }
        }

        for (i = 0; i < (unsigned)numVertices; i++) 
        {
            vertexMemory[i].diffuseColor = 0xffffffff; 
        }

        UnlockVertexBuffer();
        updateVertices = false;
        p_BoundingBox->FindBoundingBox(this);  
    }
}