本文主要针对于2.2.3 cocos2d-x渲染做一分析,TestCpp ios项目启动,⾸先运行的当然就是main.m这个类了啊
main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, @"AppController");
[pool release];
return retVal;
}
现在跟进到AppController,这里的AppController并不是cocos2d-x中的AppController,而是ios项目中的,找到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法,在该方法中有这么一句话cocos2d::CCApplication::sharedApplication()->run()
,好了咱得去找CCApplication了。
####CCApplication run方法中
int CCApplication::run()
{
if (applicationDidFinishLaunching())
{
[[CCDirectorCaller sharedDirectorCaller] startMainLoop];//启动了线程
}
return 0;
} ####跟进到startMainLoop中
[displayLink invalidate];
displayLink = nil;
//实例化CADisplayLink
displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)];//回调doCaller:
[displayLink setFrameInterval: self.interval];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。
在doCaller:这个回调方法中,也就这么一句话cocos2d::CCDirector::sharedDirector()->mainLoop()
,终于找到了党,CCDirector熟悉了吧。话说CCDirector没有直接实现mainLoop()这个方法,而是请他小弟CCDisplayLinkDirector来实现的。
####CCDisplayLinkDirector的mainLoop()
if (m_bPurgeDirecotorInNextLoop)//m_bPurgeDirecotorInNextLoop在end()方法中设置为true
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();//重点还是drawScene()这个方法
// release the objects
CCPoolManager::sharedPoolManager()->pop(); //每次回调都要清除下pool池
} 接下来还是先讨论下关于CCDirector吧。在初始化CCDirector方法中,真正的初始化值是s_SharedDirector,也就是说在CCDirecor包裹了一成皮,里面真正干事的是它的子类CCDisplayLinkDirector,`static CCDisplayLinkDirector *s_SharedDirector = NULL;` ####CCDirecotr的sharedDirector(void)方法
if (!s_SharedDirector)
{
s_SharedDirector = new CCDisplayLinkDirector();
s_SharedDirector->init();//实现是在CCDirector中
}
return s_SharedDirector;//真正返回的是CCDisplayLinkDirector ####跟进到CCDirecotr的init()方法
setDefaultValues();//设置默认初始值,从CCConfiguration中读取初始值
// 关于scene相关属性
m_pRunningScene = NULL;//正在运行的场景
m_pNextScene = NULL;
m_pNotificationNode = NULL;//可以通过该Node设置悬浮窗或者过渡的节点,该节点不在场景中draw,是单独出来画的
m_pobScenesStack = new CCArray();
m_pobScenesStack->init();//初始化保存scene的栈
// projection delegate if "Custom" projection is used
m_pProjectionDelegate = NULL;//CCDirectorDelegate类只有当ccDirectorProjection为kCCDirectorProjectionCustom时候才实现运行该类
// FPS
m_fAccumDt = 0.0f;
m_fFrameRate = 0.0f;
m_pFPSLabel = NULL;
m_pSPFLabel = NULL;
m_pDrawsLabel = NULL;
m_uTotalFrames = m_uFrames = 0;
m_pszFPS = new char[10];
m_pLastUpdate = new struct cc_timeval();
m_fSecondsPerFrame = 0.0f;
// paused ?
m_bPaused = false;//在CCDirector::pause(void)设置为true,CCDirector::resume(void)设置为false
// purge ?
m_bPurgeDirecotorInNextLoop = false;//CCDirector::end()中设置为true
m_obWinSizeInPoints = CCSizeZero;
m_pobOpenGLView = NULL;//CCEGLView
m_fContentScaleFactor = 1.0f;//缩放因子
// scheduler
m_pScheduler = new CCScheduler();//CCScheduler定时调度器,
// action manager
m_pActionManager = new CCActionManager();//Action的管理器
m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);//将CCActionManager根据nPriority>0,-0,<0分别放到_listEntry类型的m_pUpdatesPosList,m_pUpdates0List,m_pUpdatesNegList。具体逻辑都在CCScheduler中的update()方法中,在后文会具体介绍CCScheduler
// touchDispatcher
m_pTouchDispatcher = new CCTouchDispatcher();//初始化CCTouchDispatcher,在CCLayer的setToucherEnable()中可以通过CCTouchDispatcher::addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority)或者CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches)方法将CCTouchDelegate注册到CCTouchDispatcher
m_pTouchDispatcher->init();
// KeypadDispatcher
m_pKeypadDispatcher = new CCKeypadDispatcher();//同上,关于实体键
// Accelerometer
m_pAccelerometer = new CCAccelerometer();//加速度
// create autorelease pool
CCPoolManager::sharedPoolManager()->push();//CCPoolManager管理多个CCAutoreleasePool,将CCAutoreleasePool放到CCPoolManager中的m_pReleasePoolStack
return true; 好了,也不讲关于CCDirector的初始化了,还是说说drawScene()吧。 ####挺近大别山CCDirecotr的drawScene()方法
// calculate "global" dt
calculateDeltaTime();//距离上次main loop的时间
//tick before glClear: issue #533
if (! m_bPaused)//如果暂停
{
m_pScheduler->update(m_fDeltaTime);//就是刚才在init中所说的CCScheduler的update
}
/**glClear()函数的作用是用当前缓冲区清除值,也就是glClearColor或者glClearDepth、 glClearIndex、glClearStencil、glClearAccum等函数所指定的值来清除指定的缓冲区,也可以使用glDrawBuffer一次清除多个颜色缓存。比如:glClearColor(0.0,0.0,0.0,0.0);glClear(GL_COLOR_BUFFER_BIT);第一条语句表示清除颜色设为黑色,第二条语句表示实际完成了把整个窗口清除为黑色的任务,glClear()的唯一参数表示需要被清除的缓冲区
**/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (m_pNextScene)
{
setNextScene();//初始化场景相关工作,如onEnter()或onExit()
}
kmGLPushMatrix();//矩阵压栈
// draw the scene
if (m_pRunningScene)
{
m_pRunningScene->visit();//遍历正在运行的Scene,先遍历的是CCNode的visit(),因为CCScene没有实现该方法
}
// draw the notifications node
if (m_pNotificationNode)
{
m_pNotificationNode->visit();//遍历m_pNotificationNode,可以通过该m_pNotificationNode绘制特殊的节点
}
if (m_bDisplayStats)
{
showStats();//左下角的fps
}
kmGLPopMatrix();//从矩阵栈中弹出
m_uTotalFrames++;
// swap buffers
if (m_pobOpenGLView)
{
m_pobOpenGLView->swapBuffers();
}
if (m_bDisplayStats)
{
calculateMPF();
}
OpenGL ES 2.0 已经放弃了固定的渲染流水线,取而代之的是自定义 的各种着色器,在这种情况下变换操作通常需要由开发者来维护。所幸引擎也引入了一套第三方库 Kazmath,它使得我们几 乎可以按照原来 OpenGL ES 1.0 所采用的方式进行开发
#####Cocos2d-x2.0中矩阵函数的替代函数
上诉是关于OpenGL ES 1.0和Kazmath比对图
####跟进到CCNode的visit()方法
if (!m_bVisible)//如果不可见,则直接返回,不递归迭代
{
return;
}
kmGLPushMatrix();//压入矩阵
if (m_pGrid && m_pGrid->isActive())//处理CCGridBase的特效
{
m_pGrid->beforeDraw();
}
this->transform();//进行坐标系的变换,没有坐标系的变换,则无法在正确的位置绘制出纹理.变换矩阵等价于坐标系变换.变换矩阵是如何根据当前节点的位置、旋转角度和 缩放比例等属性计算出来的了。形象地讲,transform 方法的任务就是根据当前节点的属性计算出如何把绘图坐标系变换为 新坐标系的矩阵
CCNode* pNode = NULL;
unsigned int i = 0;
if(m_pChildren && m_pChildren->count() > 0)//遍历子类
{
sortAllChildren();
// draw children zOrder < 0
ccArray *arrayData = m_pChildren->data;先遍历zOrder < 0的子类
for( ; i < arrayData->num; i++ )
{
pNode = (CCNode*) arrayData->arr[i];
if ( pNode && pNode->m_nZOrder < 0 )
{
pNode->visit();
}
else
{
break;
}
}
// self draw
this->draw();//绘制自身
for( ; i < arrayData->num; i++ )//遍历剩下的CCNode
{
pNode = (CCNode*) arrayData->arr[i];
if (pNode)
{
pNode->visit();
}
}
}
else
{
this->draw();
}
// 恢复工作
m_uOrderOfArrival = 0;
if (m_pGrid && m_pGrid->isActive())
{
m_pGrid->afterDraw(this);
}
kmGLPopMatrix();//出栈 ####下面继续跟进到transform()方法
kmMat4 transfrom4x4;
// Convert 3x3 into 4x4 matrix
CCAffineTransform tmpAffine = this->nodeToParentTransform();//获取节点相对于父节点的变换矩阵
CGAffineToGL(&tmpAffine, transfrom4x4.mat);//具体转换
transfrom4x4.mat[14] = m_fVertexZ;//设置 z 坐标
kmGLMultMatrix( &transfrom4x4 );//右乘一个矩阵transfrom4x4
// 处理m_pGrid特效平移相关
if ( m_pCamera != NULL && !(m_pGrid != NULL && m_pGrid->isActive()) )
{
bool translate = (m_obAnchorPointInPoints.x != 0.0f || m_obAnchorPointInPoints.y != 0.0f);
if( translate )
kmGLTranslatef(RENDER_IN_SUBPIXEL(m_obAnchorPointInPoints.x), RENDER_IN_SUBPIXEL(m_obAnchorPointInPoints.y), 0 );
m_pCamera->locate();
if( translate )
kmGLTranslatef(RENDER_IN_SUBPIXEL(-m_obAnchorPointInPoints.x), RENDER_IN_SUBPIXEL(-m_obAnchorPointInPoints.y), 0 );
} #####仿射变换 一般来说游戏中会大量使用旋转,缩放,平移等仿射变换( 所谓仿射变换是指在线性变换的基础上加上平移,平移不是线性变换)。2D计算机图形学中的仿射变换通常是通过和3x3齐次矩阵相乘来实现的。cocos2d中的仿射变换使用了Quartz 2D中的CGAffineTransform类来表示:
struct CGAffineTransform {
CGFloat a;
CGFloat b;
CGFloat c;
CGFloat d;
CGFloat tx;
CGFloat ty;
};
typedef struct CGAffineTransform CGAffineTransform; CGAffineTransform类表示的齐次矩阵如下,由于变换矩阵最后一列总是[0,0,1],所以被省略了: ![image](/assets/test.png) 由于cocos2d的绘制使用了OpenglES,因此CGAffineTransform只是用来表示2D仿射变换,最终还是要转化成OpenglES的4x4变换矩阵(Opengl是3D的世界,因此它接受的齐次矩阵是4x4的)。转换工作由CGAffineToGL(const CGAffineTransform *t, GLfloat *m)来完成,Opengl的变换矩阵以一维数组表示,它和CGAffineTransform的映射关系如下:
// | m[0] m[4] m[8] m[12] | | m11 m21 m31 m41 | | a c 0 tx |
// | m[1] m[5] m[9] m[13] | | m12 m22 m32 m42 | | b d 0 ty |
// | m[2] m[6] m[10] m[14] | <=> | m13 m23 m33 m43 | <=> | 0 0 1 0 |
// | m[3] m[7] m[11] m[15] | | m14 m24 m34 m44 | | 0 0 0 1 |
m[2] = m[3] = m[6] = m[7] = m[8] = m[9] = m[11] = m[14] = 0.0f;
m[10] = m[15] = 1.0f;
m[0] = t->a; m[4] = t->c; m[12] = t->tx;
m[1] = t->b; m[5] = t->d; m[13] = t->ty; 变换相关的方法: ![image](/assets/截图_01.png) "节点坐标系"指的是以一个节点作为参考而产生的坐标系,换句话说,它的任何一个子节点的坐标值都是 由这个坐标系确定的,通过以上方法,我们可以方便地处理触摸点,也可以方便地计算两个不同坐标系下点之间的方向关系。例如,若我们需要判断一个点在另一坐标系下是否在同一个矩形之内,则可以把此点转换为世界坐标系,再从世界坐 标系转换到目标坐标系中,此后只需要通过 contentSize 属性进行判断即可,相关代码如下:
bool IsInBox(CCPoint point)
{
CCPoint pointWorld = node1->convertToWorldSpace(point);
CCPoint pointTarget = node2->convertToNodeSpace(pointWorld);
CCSize contentSize = node2->getContentSize();
if(0 <= pointTarget.x && pointTarget.x <= contentSize.width
&& 0 <= pointTarget.y && pointTarget.y <= contentSize.height)
return true;
} 下面就接着讲draw()方法,在CCNode中,实现方法是空的,我们现在就看下CCSpirit的draw()方法:
void CCSprite::draw(void)
{
CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");//准备工作,具体啥意思不晓得
/**
getShaderProgram()->use();
getShaderProgram()->setUniformsForBuiltins();
**/
CC_NODE_DRAW_SETUP();//CC_NODE_DRAW_SETUP宏函数用于准备绘制相关环境,设置着色器
ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );//颜色混合函数,与贴图渲染的方式有关
ccGLBindTexture2D( m_pobTexture->getName() );//绑定纹理
/**
enum {
kCCVertexAttribFlag_None = 0,
kCCVertexAttribFlag_Position = 1 << 0,
kCCVertexAttribFlag_Color = 1 << 1,
kCCVertexAttribFlag_TexCoords = 1 << 2,
kCCVertexAttribFlag_PosColorTex = ( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_Color | kCCVertexAttribFlag_TexCoords ),
};
**/
ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );//设置OpenGL在后面的渲染中用到顶点位置,定点颜色,纹理坐标属性
#define kQuadSize sizeof(m_sQuad.bl)
#ifdef EMSCRIPTEN
long offset = 0;
setGLBufferData(&m_sQuad, 4 * kQuadSize, 0);
#else
long offset = (long)&m_sQuad;
#endif // EMSCRIPTEN
// vertex
int diff = offsetof( ccV3F_C4B_T2F, vertices);
glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));//顶点坐标
// texCoods
diff = offsetof( ccV3F_C4B_T2F, texCoords);
glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));//纹理坐标
// color
diff = offsetof( ccV3F_C4B_T2F, colors);
glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));//顶点颜色
/**
/* BeginMode */
#define GL_POINTS //把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制N个点
#define GL_LINES //把每一个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n条线段,总共绘制N/2条线段
#define GL_LINE_LOOP //绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制n-1条线段
#define GL_LINE_STRIP //绘制从第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点和第一个顶点相连,第n和n+1个顶点定义了线段n,总共绘制n条线段
#define GL_TRIANGLES //把每个顶点作为一个独立的三角形,顶点3n-2、3n-1和3n定义了第n个三角形,总共绘制N/3个三角形
#define GL_TRIANGLE_STRIP //绘制一组相连的三角形,对于奇数n,顶点n、n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1、n和n+2定义了第n个三角形,总共绘制N-2个三角形
#define GL_TRIANGLE_FAN
**/
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//绘制图形
CHECK_GL_ERROR_DEBUG();
//调试相关处理
#if CC_SPRITE_DEBUG_DRAW == 1
// draw bounding box
CCPoint vertices[4]={
ccp(m_sQuad.tl.vertices.x,m_sQuad.tl.vertices.y),
ccp(m_sQuad.bl.vertices.x,m_sQuad.bl.vertices.y),
ccp(m_sQuad.br.vertices.x,m_sQuad.br.vertices.y),
ccp(m_sQuad.tr.vertices.x,m_sQuad.tr.vertices.y),
};
ccDrawPoly(vertices, 4, true);
#elif CC_SPRITE_DEBUG_DRAW == 2
// draw texture box
CCSize s = this->getTextureRect().size;
CCPoint offsetPix = this->getOffsetPosition();
CCPoint vertices[4] = {
ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y +s.height)
};
ccDrawPoly(vertices, 4, true);
#endif // CC_SPRITE_DEBUG_DRAW
CC_INCREMENT_GL_DRAWS(1);
CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");//结束工作
} 好了,上诉就是关于cocos2d-x整体绘制过程,从mainloop,到visit(),再到draw(),这就是绘制的一条线。
青春是一场大雨,即使感冒了,还盼回头再淋一次......微笑永远是一个人身上最好看的东西...