精靈的繪制
打開CCSprite的代碼文件,其中draw方法負責(zé)繪制精靈榆俺,其實現(xiàn)代碼如下:
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
if (_texture == nullptr)
{
return;
}
#if CC_USE_CULLING
// Don't calculate the culling if the transform was not updated
auto visitingCamera = Camera::getVisitingCamera();
auto defaultCamera = Camera::getDefaultCamera();
if (visitingCamera == defaultCamera) {
_insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY)
|| visitingCamera->isViewProjectionUpdated())
? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
}
else
{
// XXX: this always return true since
_insideBounds = renderer->checkVisibility(transform, _contentSize);
}
if(_insideBounds)
#endif
{
_trianglesCommand.init(_globalZOrder,
_texture,
getGLProgramState(),
_blendFunc,
_polyInfo.triangles,
transform,
flags);
renderer->addCommand(&_trianglesCommand);
#if CC_SPRITE_DEBUG_DRAW
_debugDrawNode->clear();
auto count = _polyInfo.triangles.indexCount/3;
auto indices = _polyInfo.triangles.indices;
auto verts = _polyInfo.triangles.verts;
for(ssize_t i = 0; i < count; i++)
{
//draw 3 lines
Vec3 from =verts[indices[i*3]].vertices;
Vec3 to = verts[indices[i*3+1]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y),
Vec2(to.x,to.y), Color4F::WHITE);
from =verts[indices[i*3+1]].vertices;
to = verts[indices[i*3+2]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y),
Vec2(to.x,to.y), Color4F::WHITE);
from =verts[indices[i*3+2]].vertices;
to = verts[indices[i*3]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y),
Vec2(to.x,to.y), Color4F::WHITE);
}
#endif //CC_SPRITE_DEBUG_DRAW
}
}
void TrianglesCommand::init(float globalOrder, GLuint textureID,
GLProgramState* glProgramState, BlendFunc blendType,
const Triangles& triangles,const Mat4& mv, uint32_t flags)
{
CCASSERT(glProgramState, "Invalid GLProgramState");
CCASSERT(glProgramState->getVertexAttribsFlags() == 0, \
"No custom attributes are supported in QuadCommand");
RenderCommand::init(globalOrder, mv, flags);
_triangles = triangles;
if(_triangles.indexCount % 3 != 0)
{
int count = _triangles.indexCount;
_triangles.indexCount = count / 3 * 3;
CCLOGERROR("Resize indexCount from %zd to %zd, \
size must be multiple times of 3", count, _triangles.indexCount);
}
_mv = mv;
if( _textureID != textureID || _blendType.src != blendType.src
|| _blendType.dst != blendType.dst ||
_glProgramState != glProgramState)
{
_textureID = textureID;
_blendType = blendType;
_glProgramState = glProgramState;
generateMaterialID();
}
}
采用"服務(wù)器端"(負責(zé)具體的繪制渲染)+"客戶端"(負責(zé)向服務(wù)器端發(fā)送繪圖指令)的方式進行渲染。
初始化 渲染指令TrianglesCommand-_trianglesCommand坞淮,設(shè)置ZOrder茴晋、紋理、OpenGL狀態(tài)回窘、顏色混合模式诺擅、貼圖渲染的方式、頂點坐標(biāo)啡直、紋理坐標(biāo)以及頂點顏色等烁涌。
renderer->addCommand(&_trianglesCommand);將渲染指令加入render渲染列表,等待下一幀主循環(huán)mainLoop調(diào)用繪制Director::drawScene()來渲染_renderer->render();酒觅。
</br>
渲染樹的繪制
無論如何復(fù)雜的游戲場景也都是精靈通過不同的層次撮执、位置組合構(gòu)成的,因此只要可以把精靈按照前后層次阐滩,在不同的位置繪制出來就完成了游戲場景的繪制二打。
Cocos2d-x的渲染樹結(jié)構(gòu),渲染樹是由各種游戲元素按照層次關(guān)系構(gòu)成的樹結(jié)構(gòu)掂榔,它展示了Cocos2d-x游戲的繪制層次继效,因此游戲的渲染順序就是由渲染樹決定的。
回顧Cocos2d-x游戲的層次:導(dǎo)演類CCDirector直接控制渲染樹的根節(jié)點--場景(CCScene)装获,場景包含多個層(CCLayer)瑞信,層中包含多個精靈(CCSprite)。實際上穴豫,每一個上述的游戲元素都在渲染樹中表示為節(jié)點(CCNode)凡简,游戲元素的歸屬關(guān)系就轉(zhuǎn)換為了節(jié)點間的歸屬關(guān)系,進而形成樹結(jié)構(gòu)精肃。
CCNode的visit方法實現(xiàn)了對一棵渲染樹的繪制秤涩。為了繪制樹中的一個節(jié)點,就需要繪制自己的子節(jié)點司抱,直到?jīng)]有子節(jié)點可以繪制時再結(jié)束這個過程筐眷。因此,為了每一幀都繪制一次渲染樹习柠,就需要調(diào)用渲染樹的根節(jié)點匀谣。換句話說照棋,當(dāng)前場景的visit方法在每一幀都會被調(diào)用一次。這個調(diào)用是由游戲主循環(huán)完成的武翎。
繪制父節(jié)點時會引起子節(jié)點的繪制烈炭,同時,子節(jié)點的繪制方式與父節(jié)點的屬性也有關(guān)宝恶。例如符隙,父節(jié)點設(shè)置了放大比例,則子節(jié)點也會隨之放大卑惜;父節(jié)點移動一段距離膏执,則子節(jié)點會隨之移動并保持相對位置不變。顯而易見露久,繪制渲染樹是一個遞歸的過程更米,下面我們來詳細探討visit的實現(xiàn),相關(guān)代碼如下:
void Node::visit(Renderer* renderer, const Mat4 &parentTransform,
uint32_t parentFlags)
{
// quick return if not visible. children won't be drawn.
if (!_visible)
{
return;
}
uint32_t flags = processParentFlags(parentTransform, parentFlags);
// IMPORTANT:
// To ease the migration to v3.0, we still support the Mat4 stack,
// but it is deprecated and your code should not rely on it
_director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
_director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW,
_modelViewTransform);
bool visibleByCamera = isVisitableByVisitingCamera();
int i = 0;
if(!_children.empty())
{
sortAllChildren();
// draw children zOrder < 0
for(auto size = _children.size(); i < size; ++i)
{
auto node = _children.at(i);
if (node && node->_localZOrder < 0)
node->visit(renderer, _modelViewTransform, flags);
else
break;
}
// self draw
if (visibleByCamera)
this->draw(renderer, _modelViewTransform, flags);
for(auto it=_children.cbegin()+i, itCend = _children.cend();
it != itCend; ++it)
(*it)->visit(renderer, _modelViewTransform, flags);
}
else if (visibleByCamera)
{
this->draw(renderer, _modelViewTransform, flags);
}
_director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
// FIX ME: Why need to set _orderOfArrival to 0??
// Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
// reset for next frame
// _orderOfArrival = 0;
}
</br>
坐標(biāo)變換
在繪制渲染樹中毫痕,最關(guān)鍵的步驟之一就是進行坐標(biāo)系的變換征峦。沒有坐標(biāo)系的變換,則無法在正確的位置繪制出紋理消请。同時栏笆,坐標(biāo)系的變換在其他的場合(例如碰撞檢測中)也起著十分重要的作用。因此在這一節(jié)中臊泰,我們將介紹Cocos2d-x中的坐標(biāo)變換功能蛉加。
首先,我們來看一下transform方法缸逃,其代碼如下所示:
Mat4 Node::transform(const Mat4& parentTransform)
{
return parentTransform * this->getNodeToParentTransform();
}
const Mat4& Node::getNodeToParentTransform() const
{
if (_transformDirty)
{
// Translate values
float x = _position.x;
float y = _position.y;
float z = _positionZ;
if (_ignoreAnchorPointForPosition)
{
x += _anchorPointInPoints.x;
y += _anchorPointInPoints.y;
}
bool needsSkewMatrix = ( _skewX || _skewY );
// Build Transform Matrix = translation * rotation * scale
Mat4 translation;
//move to anchor point first, then rotate
Mat4::createTranslation(x, y, z, &translation);
Mat4::createRotation(_rotationQuat, &_transform);
if (_rotationZ_X != _rotationZ_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 radiansX = -CC_DEGREES_TO_RADIANS(_rotationZ_X);
float radiansY = -CC_DEGREES_TO_RADIANS(_rotationZ_Y);
float cx = cosf(radiansX);
float sx = sinf(radiansX);
float cy = cosf(radiansY);
float sy = sinf(radiansY);
float m0 = _transform.m[0], m1 = _transform.m[1], m4 = _transform.m[4], m5 = _transform.m[5], m8 = _transform.m[8], m9 = _transform.m[9];
_transform.m[0] = cy * m0 - sx * m1, _transform.m[4] = cy * m4 - sx * m5, _transform.m[8] = cy * m8 - sx * m9;
_transform.m[1] = sy * m0 + cx * m1, _transform.m[5] = sy * m4 + cx * m5, _transform.m[9] = sy * m8 + cx * m9;
}
_transform = translation * _transform;
if (_scaleX != 1.f)
{
_transform.m[0] *= _scaleX, _transform.m[1] *= _scaleX, _transform.m[2] *= _scaleX;
}
if (_scaleY != 1.f)
{
_transform.m[4] *= _scaleY, _transform.m[5] *= _scaleY, _transform.m[6] *= _scaleY;
}
if (_scaleZ != 1.f)
{
_transform.m[8] *= _scaleZ, _transform.m[9] *= _scaleZ, _transform.m[10] *= _scaleZ;
}
// FIXME:: Try to inline skew
// If skew is needed, apply skew and then anchor point
if (needsSkewMatrix)
{
float skewMatArray[16] =
{
1, (float)tanf(CC_DEGREES_TO_RADIANS(_skewY)), 0, 0,
(float)tanf(CC_DEGREES_TO_RADIANS(_skewX)), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
Mat4 skewMatrix(skewMatArray);
_transform = _transform * skewMatrix;
}
// adjust anchor point
if (!_anchorPointInPoints.isZero())
{
// FIXME:: Argh, Mat4 needs a "translate" method.
// FIXME:: Although this is faster than multiplying a vec4 * mat4
_transform.m[12] += _transform.m[0] * -_anchorPointInPoints.x + _transform.m[4] * -_anchorPointInPoints.y;
_transform.m[13] += _transform.m[1] * -_anchorPointInPoints.x + _transform.m[5] * -_anchorPointInPoints.y;
_transform.m[14] += _transform.m[2] * -_anchorPointInPoints.x + _transform.m[6] * -_anchorPointInPoints.y;
}
}
if (_additionalTransform)
{
// This is needed to support both Node::setNodeToParentTransform() and Node::setAdditionalTransform()
// at the same time. The scenario is this:
// at some point setNodeToParentTransform() is called.
// and later setAdditionalTransform() is called every time. And since _transform
// is being overwritten everyframe, _additionalTransform[1] is used to have a copy
// of the last "_transform without _additionalTransform"
if (_transformDirty)
_additionalTransform[1] = _transform;
if (_transformUpdated)
_transform = _additionalTransform[1] * _additionalTransform[0];
}
_transformDirty = _additionalTransformDirty = false;
return _transform;
}
首先通過getNodeToParentTransform()方法獲取此節(jié)點相對于父節(jié)點的變換矩陣针饥,然后把它轉(zhuǎn)換為OpenGL格式的矩陣并右乘在當(dāng)前繪圖矩陣之上,最后進行了一些攝像機與Gird特效相關(guān)的操作需频。
把此節(jié)點相對于父節(jié)點的變換矩陣與當(dāng)前節(jié)點相連丁眼,也就意味著在當(dāng)前坐標(biāo)系的基礎(chǔ)上進行坐標(biāo)系變換,得到新的合適的坐標(biāo)系昭殉。
形象地講苞七,transform方法的任務(wù)就是根據(jù)當(dāng)前節(jié)點的屬性計算出如何把繪圖坐標(biāo)系變換為新坐標(biāo)系的矩陣。圖10-8形象地描述了這一操作挪丢。
變換相關(guān)的方法
方法 | 描述
-- |
CCAffineTransform nodeToParentTransform() | 獲取節(jié)點相對于父節(jié)點的變換矩陣
CCAffineTransform parentToNodeTransform() | 獲取父節(jié)點相對于節(jié)點的變換矩陣
CCAffineTransform nodeToWorldTransform() | 獲取節(jié)點相對于世界坐標(biāo)系的變換矩陣
CCAffineTransform worldToNodeTransform() | 獲取世界坐標(biāo)系相對于節(jié)點的變換矩陣
CCPoint convertToNodeSpace(const CCPoint& worldPoint) | 把世界坐標(biāo)系中的點坐標(biāo)轉(zhuǎn)換到節(jié)點坐標(biāo)系
CCPoint convertToWorldSpace(const CCPoint& nodePoint) | 把節(jié)點坐標(biāo)系中的點坐標(biāo)轉(zhuǎn)換到世界坐標(biāo)系
CCPoint convertToNodeSpaceAR(const CCPoint& worldPoint) | 把世界坐標(biāo)系中的點坐標(biāo)轉(zhuǎn)換到節(jié)點坐標(biāo)系(相對于錨點)
CCPoint convertToWorldSpaceAR(const CCPoint& nodePoint) | 把節(jié)點坐標(biāo)系中的點坐標(biāo)(相對于錨點)轉(zhuǎn)換到世界坐標(biāo)系
CCPoint convertTouchToNodeSpace(CCTouch * touch) | 獲取觸摸點在節(jié)點坐標(biāo)系中的坐標(biāo)
CCPoint convertTouchToNodeSpaceAR(CCTouch * touch) | 獲取觸摸點在節(jié)點坐標(biāo)系中的坐標(biāo)(相對于錨點)
"節(jié)點坐標(biāo)系"指的是以一個節(jié)點作為參考而產(chǎn)生的坐標(biāo)系蹂风,換句話說,它的任何一個子節(jié)點的坐標(biāo)值都是由這個坐標(biāo)系確定的乾蓬。