在cocos2d中,主要用Sprite3D对象来渲染3D模型。它的主要结构如下:
从图中可以看出它的成员主要有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;
}
它的类结构大致如下:
以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;
//省略...
顶点数据的内存图如下,
因为cc3D_SkinPositionTex_vert着色器中并没有用到法线数据,所以图中VERTEX_ATTRIB_NORMAL下没有写在着色器中对应的attribute变量。