cocos2d源码分析(十二):Sprite3D之网格顶点数据

在cocos2d中,主要用Sprite3D对象来渲染3D模型。它的主要结构如下:


5994163-9ec37f7d37d0fce5.png

从图中可以看出它的成员主要有Skeleton3D对象_skeleton,MeshVerterData对象数组_meshVertexDatas,和Mesh对象数组_meshs。本文主要分析MeshVerterData对象。

MeshVerterData从命名上看,即网格顶点数据。它主要用于管理顶点数据,如顶点位置,法线,纹理坐标的内存数据等,以及OpenGL的vbo。前面的加载c3t文件,加载obj文件中分析过,当cocos2d把obj(或c3t,c3b)文件解析后,最终会把数据存储到MeshDatas,MaterialDatas和NodeDatas这三个数据结构中,Sprite3D中的MeshVerterData成员的数据,主要是来自MeshDatas中的数据。下面看下Sprite3D的创建过程:

bool Sprite3D::initWithFile(const std::string& path)
{
    _aabbDirty = true;
    _meshes.clear();
    _meshVertexDatas.clear();
    CC_SAFE_RELEASE_NULL(_skeleton);
    removeAllAttachNode();
    
    if (loadFromCache(path))
        return true;
    
    MeshDatas* meshdatas = new (std::nothrow) MeshDatas();
    MaterialDatas* materialdatas = new (std::nothrow) MaterialDatas();
    NodeDatas* nodeDatas = new (std::nothrow) NodeDatas();
    
    //加载模型文件,将信息存到meshdatas,materialdatas,nodeDatas中
    if (loadFromFile(path, nodeDatas, meshdatas, materialdatas))
    {
        //根据meshdatas,materialdatas,nodeDatas初始化Sprite3D
        if (initFrom(*nodeDatas, *meshdatas, *materialdatas))
        {
            //add to cache
            auto data = new (std::nothrow) Sprite3DCache::Sprite3DData();
            data->materialdatas = materialdatas;
            data->nodedatas = nodeDatas;
            data->meshVertexDatas = _meshVertexDatas;
            for (const auto mesh : _meshes) {
                data->glProgramStates.pushBack(mesh->getGLProgramState());
            }
            
            Sprite3DCache::getInstance()->addSprite3DData(path, data);
            CC_SAFE_DELETE(meshdatas);
            _contentSize = getBoundingBox().size;
            return true;
        }
    }
    CC_SAFE_DELETE(meshdatas);
    CC_SAFE_DELETE(materialdatas);
    CC_SAFE_DELETE(nodeDatas);
    
    return false;
}

initFrom函数如下:

bool Sprite3D::initFrom(const NodeDatas& nodeDatas, const MeshDatas& meshdatas, const MaterialDatas& materialdatas)
{
    for(const auto& it : meshdatas.meshDatas)  {
        if(it)  {
//            Mesh* mesh = Mesh::create(*it);
//            _meshes.pushBack(mesh);
            
            //通过meshDatas,创建MeshVertexData
            auto meshvertex = MeshVertexData::create(*it);
            _meshVertexDatas.pushBack(meshvertex);
        }
    }  //初始化顶点和索引数据(_meshVertexDatas)

     //创建骨骼
    _skeleton = Skeleton3D::create(nodeDatas.skeleton); 
    CC_SAFE_RETAIN(_skeleton);
    
    auto size = nodeDatas.nodes.size();
    for(const auto& it : nodeDatas.nodes) {
        if(it)  {
            createNode(it, this, materialdatas, size == 1);
        }
    }
    for(const auto& it : nodeDatas.skeleton) {
        if(it) {
             createAttachSprite3DNode(it,materialdatas);
        }
    }
    genMaterial(); //根据attrib绑定值,材质文件,纹理等创建Material
    
    return true;
}

MeshVertexData::create函数如下:

MeshVertexData* MeshVertexData::create(const MeshData& meshdata)
{
    auto vertexdata = new (std::nothrow) MeshVertexData();
    
    //每个顶点占多大内存,例如,一个顶点包含有位置,法线和纹理坐标,则position(3*4)+normal(3*4)+texcoord(2*4),其中4=sizeof(float)
    int pervertexsize = meshdata.getPerVertexSize();
    
    //VertexBuffer内部会创建vbo(glGenBuffers),并向opengl申请存储顶点的内存(glBufferData)
    vertexdata->_vertexBuffer = VertexBuffer::create(pervertexsize, (int)(meshdata.vertex.size() / (pervertexsize / 4)));
    
    vertexdata->_vertexData = VertexData::create();
    CC_SAFE_RETAIN(vertexdata->_vertexData);
    CC_SAFE_RETAIN(vertexdata->_vertexBuffer);
    
    int offset = 0;
    for (const auto& it : meshdata.attribs) {
        
        vertexdata->_vertexData->setStream(vertexdata->_vertexBuffer, VertexStreamAttribute(offset, it.vertexAttrib, it.type, it.size));
        offset += it.attribSizeBytes;
    }
    
    vertexdata->_attribs = meshdata.attribs;
    
    if(vertexdata->_vertexBuffer)
    {
        //将顶点数据从CPU传到GPU, 通过(glBufferSubData)
        vertexdata->_vertexBuffer->updateVertices((void*)&meshdata.vertex[0], (int)meshdata.vertex.size() * 4 / vertexdata->_vertexBuffer->getSizePerVertex(), 0);
    }
    
    bool needCalcAABB = (meshdata.subMeshAABB.size() != meshdata.subMeshIndices.size());
    for (size_t i = 0, size = meshdata.subMeshIndices.size(); i < size; ++i) {

        auto& index = meshdata.subMeshIndices[i];
        
        //内部创建vbo(glGenBuffers),并向OpenGL申请内存(glBufferData)来存储索引数据
        auto indexBuffer = IndexBuffer::create(IndexBuffer::IndexType::INDEX_TYPE_SHORT_16, (int)(index.size()));
        
        //将索引数据从CPU传到GPU(glBufferSubData)
        indexBuffer->updateIndices(&index[0], (int)index.size(), 0);
        std::string id = (i < meshdata.subMeshIds.size() ? meshdata.subMeshIds[i] : "");
        MeshIndexData* indexdata = nullptr;
        
        //计算AABB
        if (needCalcAABB)
        {
            auto aabb = Bundle3D::calculateAABB(meshdata.vertex, meshdata.getPerVertexSize(), index);
            indexdata = MeshIndexData::create(id, vertexdata, indexBuffer, aabb);
        }
        else
            indexdata = MeshIndexData::create(id, vertexdata, indexBuffer, meshdata.subMeshAABB[i]);
        
        vertexdata->_indexs.pushBack(indexdata);
    }
    
    vertexdata->autorelease();
    return vertexdata;
}

它的类结构大致如下:


5994163-0cf630a89a8ebcc9.png

以orc.c3t为例,分析下顶点内存数据的布局,orc.c3t相关文件相关内容如下:

    "meshes": [
        {
            "attributes": [{
                    "size":   3, 
                    "type": "GL_FLOAT", 
                    "attribute": "VERTEX_ATTRIB_POSITION"
                }, {
                    "size":   3, 
                    "type": "GL_FLOAT", 
                    "attribute": "VERTEX_ATTRIB_NORMAL"
                }, {
                    "size":   2, 
                    "type": "GL_FLOAT", 
                    "attribute": "VERTEX_ATTRIB_TEX_COORD"
                }, {
                    "size":   4, 
                    "type": "GL_FLOAT", 
                    "attribute": "VERTEX_ATTRIB_BLEND_WEIGHT"
                }, {
                    "size":   4, 
                    "type": "GL_FLOAT", 
                    "attribute": "VERTEX_ATTRIB_BLEND_INDEX"
                }], 
            "vertices": [
                -4.087269, -0.284269,  2.467412, -0.182764, -0.799652,  0.571974,  0.309707,  0.734820,  0.500000,  0.500000,  0.000000,  0.000000,  0.000000,  1.000000,  0.000000,  0.000000, 
                -3.801909, -0.138538, -0.349688, -0.335480, -0.819229, -0.465099,  0.333359,  0.578025,  1.000000,  0.000000,  0.000000,  0.000000,  0.000000,  0.000000,  0.000000,  0.000000,
...//省略了

attributes对应opengl着色器中的attributes属性类型,orc.c3t文件中一个顶点有5个属性内容,VERTEX_ATTRIB_POSITION是顶点的位置信息,也就是xyz坐标,占3个字节,VERTEX_ATTRIB_NORMAL是顶点法线,VERTEX_ATTRIB_TEX_COORD是纹理坐标,VERTEX_ATTRIB_BLEND_WEIGHT是骨骼蒙皮是要用到的,意思是每个骨骼对该顶点影响的权重是多少,VERTEX_ATTRIB_BLEND_INDEX是骨骼的索引,在后面分析骨骼蒙皮时再讨论。因此一个顶点占的字节数为43+43+42+44+4*4=64。也就是VertexBuffer的_sizePerVertex成员。

我们也可以提前看一下orc.c3t的顶点着色器内的attribute变量,在ccShader_3D_PositionTex.vert文件,如下:

const char* cc3D_SkinPositionTex_vert = R"(
attribute vec3 a_position;        //对应VERTEX_ATTRIB_POSITION

attribute vec4 a_blendWeight;  //对应VERTEX_ATTRIB_BLEND_WEIGHT
attribute vec4 a_blendIndex;    //对应VERTEX_ATTRIB_BLEND_INDEX

attribute vec2 a_texCoord;   //对应VERTEX_ATTRIB_TEX_COORD

const int SKINNING_JOINT_COUNT = 60;
// Uniforms
uniform vec4 u_matrixPalette[SKINNING_JOINT_COUNT * 3];

// Varyings
varying vec2 TextureCoordOut;

vec4 getPosition()
{
    float blendWeight = a_blendWeight[0];

    int matrixIndex = int (a_blendIndex[0]) * 3;

//省略...

顶点数据的内存图如下,


5994163-18438d8a21cead44.png

因为cc3D_SkinPositionTex_vert着色器中并没有用到法线数据,所以图中VERTEX_ATTRIB_NORMAL下没有写在着色器中对应的attribute变量。

猜你喜欢

转载自blog.csdn.net/weixin_34377065/article/details/87427940