一、精灵是怎么被渲染出来的
cocos渲染用了opengl。所有精灵顶点的位置,都是真实的在opengl世界坐标系中的大小。这些点在XOY平面上,z都为0,它们进行模型视图变换,再进行投影变换,投影空间进行除法运算规格化最后通过视口变换转化为窗口上的一点。
void CCDirector::setProjection(ccDirectorProjection kProjection)
{
CCSize size = m_obWinSizeInPoints;
setViewport();
switch (kProjection)
{
case kCCDirectorProjection2D:
{
kmGLMatrixMode(KM_GL_PROJECTION);
kmGLLoadIdentity();
#if CC_TARGET_PLATFORM == CC_PLATFORM_WP8
kmGLMultMatrix(CCEGLView::sharedOpenGLView()->getOrientationMatrix());
#endif
kmMat4 orthoMatrix;
kmMat4OrthographicProjection(&orthoMatrix, 0, size.width, 0, size.height, -1024, 1024 );
kmGLMultMatrix(&orthoMatrix);
kmGLMatrixMode(KM_GL_MODELVIEW);
kmGLLoadIdentity();
}
break;
case kCCDirectorProjection3D:
{
float zeye = this->getZEye();
kmMat4 matrixPerspective, matrixLookup;
kmGLMatrixMode(KM_GL_PROJECTION);
kmGLLoadIdentity();
#if CC_TARGET_PLATFORM == CC_PLATFORM_WP8
//if needed, we need to add a rotation for Landscape orientations on Windows Phone 8 since it is always in Portrait Mode
kmGLMultMatrix(CCEGLView::sharedOpenGLView()->getOrientationMatrix());
#endif
// issue #1334
kmMat4PerspectiveProjection( &matrixPerspective, 60, (GLfloat)size.width/size.height, 0.1f, zeye*2);
// kmMat4PerspectiveProjection( &matrixPerspective, 60, (GLfloat)size.width/size.height, 0.1f, 1500);
kmGLMultMatrix(&matrixPerspective);
kmGLMatrixMode(KM_GL_MODELVIEW);
kmGLLoadIdentity();
kmVec3 eye, center, up;
kmVec3Fill( &eye, size.width/2, size.height/2, zeye );
kmVec3Fill( ¢er, size.width/2, size.height/2, 0.0f );
kmVec3Fill( &up, 0.0f, 1.0f, 0.0f);
kmMat4LookAt(&matrixLookup, &eye, ¢er, &up);
kmGLMultMatrix(&matrixLookup);
}
上面是设置顶点变换矩阵的,kmMat4PerspectiveProjection设置的是透视投影矩阵P,把顶点变换到一个投影空间中。kmMat4LookAt是设置眼睛的位置,求得的是视图变换矩阵V,opengl把模型跟视图并起来了,这里同样也是并起来的,跟模型变化矩阵M相乘放在了一个KM_GL_MODELVIEW栈中。注意opengl顶点是列向量,左乘变换矩阵的。比如顶点是P,变换后点P'=PVMP,这个表示P先与M乘进行模型变换,再与V乘,进行视图变换,再与P乘进行投影变换。cocos着色器里对应变量CC_MVPMatrix。
上面求得的矩阵放在两个栈中,一个投影栈、一个模型视图栈,视图矩阵V跟栈顶单位矩阵做乘法得到的还是V,现在我们有矩阵P与V了,还差M。
void CCNode::transform()
{
kmMat4 transfrom4x4;
// Convert 3x3 into 4x4 matrix
CCAffineTransform tmpAffine = this->nodeToParentTransform();
CGAffineToGL(&tmpAffine, transfrom4x4.mat);
// Update Z vertex manually
transfrom4x4.mat[14] = m_fVertexZ;
kmGLMultMatrix( &transfrom4x4 );
// XXX: Expensive calls. Camera should be integrated into the cached affine matrix
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 );
}
}
上面代码就是求结点的模型变换矩阵,至于什么时候调用这个然后,可以设置跟断点跟踪下你就知道了,每帧渲染drawScene会遍历所有node的visit,visit中调用了这个方法,其中
this
->
nodeToParentTransform
()是关键,代码如下:
CCAffineTransform CCNode::nodeToParentTransform(void)
{
if (m_bTransformDirty)
{
// Translate values
float x = m_obPosition.x;
float y = m_obPosition.y;
if (m_bIgnoreAnchorPointForPosition)
{
x += m_obAnchorPointInPoints.x;
y += m_obAnchorPointInPoints.y;
}
// Rotation values
// Change rotation code to handle X and Y
// If we skew with the exact same value for both x and y then we're simply just rotating
float cx = 1, sx = 0, cy = 1, sy = 0;
if (m_fRotationX || m_fRotationY)
{
float radiansX = -CC_DEGREES_TO_RADIANS(m_fRotationX);
float radiansY = -CC_DEGREES_TO_RADIANS(m_fRotationY);
cx = cosf(radiansX);
sx = sinf(radiansX);
cy = cosf(radiansY);
sy = sinf(radiansY);
}
bool needsSkewMatrix = ( m_fSkewX || m_fSkewY );
// optimization:
// inline anchor point calculation if skew is not needed
// Adjusted transform calculation for rotational skew
if (! needsSkewMatrix && !m_obAnchorPointInPoints.equals(CCPointZero))
{
x += cy * -m_obAnchorPointInPoints.x * m_fScaleX + -sx * -m_obAnchorPointInPoints.y * m_fScaleY;
y += sy * -m_obAnchorPointInPoints.x * m_fScaleX + cx * -m_obAnchorPointInPoints.y * m_fScaleY;
}
m_obAnchorPointInPoints是锚点位置,物体旋转、缩放需要一个中心点,锚点就充当这个角色。然后计算模型变换矩阵要注意,要先计算平移,这个平移根据锚点进行平移,调整物体在局部坐标中的位置,开始时物体左下角在局部坐标原点,锚点(0.5, 0.5)把物体中心移动局部坐标原点了。它的x、y都是减小,根据这个-m_obAnchorPointInPoints可以得到平移矩阵T1,然后再求旋转与缩放,分别为R、S。最后得到局部坐标的变换矩阵,精灵的顶点P变换后为P'=SRT1,变换后还是局部坐标。m_obPosition是精灵世界坐标,最后根据它求出局部坐标到世界坐标的变换矩阵T2.最后精灵点坐标P''=T2P'=T2SRT1。上面函数是直接根据最后的矩阵填充结果的,没有推导过程,自己可以推导一下。
bool CCSprite::initWithTexture(CCTexture2D *pTexture, const CCRect& rect, bool rotated)
{
if (CCNodeRGBA::init())
{
m_pobBatchNode = NULL;
m_bRecursiveDirty = false;
setDirty(false);
m_bOpacityModifyRGB = true;
m_sBlendFunc.src = CC_BLEND_SRC;//设置或者方式<span style="font-family: Arial, Helvetica, sans-serif;">GL_ONE </span>
m_sBlendFunc.dst = CC_BLEND_DST;//<span style="font-family: Arial, Helvetica, sans-serif;">GL_ONE_MINUS_SRC_ALPHA</span>
m_bFlipX = m_bFlipY = false;
// default transform anchor: center
setAnchorPoint(ccp(0.5f, 0.5f)); //锚点
// zwoptex default values
m_obOffsetPosition = CCPointZero;
m_bHasChildren = false;
// clean the Quad
memset(&m_sQuad, 0, sizeof(m_sQuad));
// Atlas: Color
ccColor4B tmpColor = { 255, 255, 255, 255 };
<span style="white-space:pre"> </span>//精灵四个顶点的颜色,颜色有什么用?CCSprite::setColor()这个改变这个值的
<span style="white-space:pre"> </span>//在精灵使用的片元着色器里,会用从纹理上采样下的样色乘以这个(值/255)的浮点数得到最终片元颜色
m_sQuad.bl.colors = tmpColor;
m_sQuad.br.colors = tmpColor;
m_sQuad.tl.colors = tmpColor;
m_sQuad.tr.colors = tmpColor;
// shader program
setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));//设置着色器
// update texture (calls updateBlendFunc)
setTexture(pTexture);//这个代码设置纹理,下面分析
setTextureRect(rect, rotated, rect.size);//这个代码设置精灵顶点位置坐标与纹理坐标,下面分析
// by default use "Self Render".
// if the sprite is added to a batchnode, then it will automatically switch to "batchnode Render"
setBatchNode(NULL);
return true;
}
else
{
return false;
}
}
上面代码是精灵创建时初始化代码,cocos有个习惯,create是静态函数,new出对象autorelease之后会调用对象的初始化函数。 initWithTexture( CCTexture2D *pTexture, const