cocos源码分析--Sprite绘图原理
精灵是2D游戏中最重要的元素,可以用来构成游戏中的元素,如人物,建筑等,用Sprite类表示,他将一张纹理的一部分或者全部矩形区域绘制到屏幕上。我们可以使用精灵表来减少OpenGL ES 绘制的次数,可以使用Sprite来播放动画,也可以设置Sprite的颜色,与场景中其他元素的混合模式等。另外一些复杂的元素,如地图,粒子系统,字体等,都是基于Sprite构建的。通过指定一张纹理和该纹理上的一个区域,就可以创建一个Sprite对象。
Sprite类定义了几个重载方法以方便的创建Sprite对象。这些方法最终都会使Sprite关联一个Texture2D对象和上面的一个区域,本文主要讲Sprite的绘制过程,Texture2D是一个比较复杂的类,另写一篇文章分析。
1 Sprite初始化
// designated initializer
bool Sprite::initWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
{
bool result;
if (Node::init())
{
_batchNode = nullptr; _recursiveDirty = false;
setDirty(false); _opacityModifyRGB = true; _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;//混合模式 _flippedX = _flippedY = false; // default transform anchor: center
//设置锚点
setAnchorPoint(Vec2(0.5f, 0.5f)); // zwoptex default values
_offsetPosition = Vec2::ZERO; // clean the Quad
memset(&_quad, , sizeof(_quad)); // Atlas: Color
//四个顶点的颜色都为白色
_quad.bl.colors = Color4B::WHITE;
_quad.br.colors = Color4B::WHITE;
_quad.tl.colors = Color4B::WHITE;
_quad.tr.colors = Color4B::WHITE; // shader state
//得到对应的的program和programstate
setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP)); // update texture (calls updateBlendFunc)
setTexture(texture);
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(nullptr);
result = true;
}
else
{
result = false;
}
_recursiveDirty = true;
setDirty(true);
return result;
}
void Sprite::updateBlendFunc(void)
{
CCASSERT(! _batchNode, "CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a SpriteBatchNode"); // it is possible to have an untextured sprite
if (! _texture || ! _texture->hasPremultipliedAlpha())
{
_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
setOpacityModifyRGB(false);
}
else
{
_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
setOpacityModifyRGB(true);
}
}
这个方法是 更新混合模式,里面有一个Alpha预乘的概念,如果一张图片包含Alpha通道,那么最终组合时一般使用颜色值乘以Alpha值,然后与用剩余的Alpha值乘以
背景的颜色值相加(自身的颜色为 ‘源’,背景颜色为 ‘目标’)。比如一个半透明的物体透过一部分光穿透到背景,Alpha用于决定有多少光可以穿透该物体,
Sprite的混合模式为{GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA},这种模式的最终组合颜色公式为
(Rs,Gs,Bs)* As +(Rd,Gd,Bd)*(1-As)。为了减少组合时候的计算量,提高应用程序的性能,Alpha预乘的概念被提出。它将RGB通道的值保存为自身实际的颜色
乘以Alpha通道的值之后的值,这样运行时就只需要计算背景不分的颜色以进行组合。Alpha预乘只是一种思路,实际的图片存储格式png,pvr等并不支持。
因此 实现它需要程序的支持,通过需要设置混合模式。cocos不能从图片信息中获知该纹理是否使用了Alpha预乘,但是cocos提供了对Premultiplied的支持。
比如,通过设置Sprite的BlendFunc使用,我们很容易想到,只要修改BlendFunc的设置为{GL_ONE,GL_ONE_MINUS_SRC_ALPHA},就可以正确显示纹理。但是这要求
对每个premultiplied的纹理都进行设置。模式设置为这个,‘源’的权重值都为1,混合的时候只考虑 ‘目标’的权重,然后Rs,Gs,Bs)* As +(Rd,Gd,Bd)*(1-As)只需要计算(Rd,Gd,Bd)*(1-As)就可以,前半部分在下面的代码中提前计算好:
void Sprite::updateBlendFunc(void)
{
CCASSERT(! _batchNode, "CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a SpriteBatchNode"); // it is possible to have an untextured sprite
if (! _texture || ! _texture->hasPremultipliedAlpha())
{
_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
setOpacityModifyRGB(false);
}
else
{
_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
setOpacityModifyRGB(true);
}
void Sprite::updateColor(void)
{
Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity ); // special opacity for premultiplied textures
if (_opacityModifyRGB)
{
float ff=_displayedOpacity;
color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
} _quad.bl.colors = color4;
_quad.br.colors = color4;
_quad.tl.colors = color4;
_quad.tr.colors = color4; // renders using batch node
if (_batchNode)
{
if (_atlasIndex != INDEX_NOT_INITIALIZED)
{
_textureAtlas->updateQuad(&_quad, _atlasIndex);
}
else
{
// no need to set it recursively
// update dirty_, don't update recursiveDirty_
setDirty(true);
}
} // self render
// do nothing
}
标红的部分,提前进行alpha预乘。
使用Premultiplied也有缺点,如预乘减小了颜色值的精度。如果我们在shader或者其他场景中需要将颜色值还原,严重的情况下就会造成比较明显的质量损失,所有
应该根据实际情况使用Alpha预乘
2 Sprite纹理与颜色的叠加
如以下调用方法
Sprite* sprite=Sprite::create("aaa.png");
sprite->setPosition(,);
scene->addChild(sprite);
sprite->setColor(Color3B(, , ));
为sprite加一个绿色的背景色,效果如下图:
看一下setColor的代码,是继承了Node::setColor,
void Node::setColor(const Color3B& color)
{
_displayedColor = _realColor = color;
//更新叠加颜色
updateCascadeColor();
}
void Node::updateCascadeColor()
{
Color3B parentColor = Color3B::WHITE;
//如果父亲节点可以颜色叠加,获取父亲颜色
if (_parent && _parent->isCascadeColorEnabled())
{
parentColor = _parent->getDisplayedColor();
}
//传入父亲的颜色,更新自己的颜色
updateDisplayedColor(parentColor);
}
//叠加公式使用每个对应通道的值想乘,如果设置了cascade的相关属性,则会向下传递
void Node::updateDisplayedColor(const Color3B& parentColor)
{
//如果parentColor为白色,没有影响
_displayedColor.r = _realColor.r * parentColor.r/255.0;
_displayedColor.g = _realColor.g * parentColor.g/255.0;
_displayedColor.b = _realColor.b * parentColor.b/255.0;
updateColor();
//如果自己允许颜色叠加,自己的颜色作为父亲颜色,传递给子节点,递归
if (_cascadeColorEnabled)//默认为false
{
for(const auto &child : _children){
child->updateDisplayedColor(_displayedColor);
}
}
}
void Sprite::updateColor(void)
{
//把颜色和alpha组成结构体传到4个顶点中
Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity ); // special opacity for premultiplied textures
//alpha预乘,上面解释过
if (_opacityModifyRGB)
{ color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
} _quad.bl.colors = color4;
_quad.br.colors = color4;
_quad.tl.colors = color4;
_quad.tr.colors = color4; // renders using batch node
//批处理,更新对应的顶点信息,有专门文章解释批处理
if (_batchNode)
{
if (_atlasIndex != INDEX_NOT_INITIALIZED)
{
_textureAtlas->updateQuad(&_quad, _atlasIndex);
}
else
{
// no need to set it recursively
// update dirty_, don't update recursiveDirty_
setDirty(true);
}
} // self render
// do nothing
}
3 Sprite 的alpha设置以及传递
透明度和颜色叠加原理是一样的,通过设置
sprite->setOpacity();
sprite->setCascadeOpacityEnabled(true);
来控制透明度和是否传递给子元素
原理代码如下:
void Node::setOpacity(GLubyte opacity)
{
_displayedOpacity = _realOpacity = opacity; updateCascadeOpacity();
}
void Node::updateCascadeOpacity()
{
GLubyte parentOpacity = ; if (_parent != nullptr && _parent->isCascadeOpacityEnabled())
{
parentOpacity = _parent->getDisplayedOpacity();
} updateDisplayedOpacity(parentOpacity);
}
void Node::updateDisplayedOpacity(GLubyte parentOpacity)
{ _displayedOpacity = _realOpacity * parentOpacity/255.0; updateColor(); if (_cascadeOpacityEnabled)
{
for(auto child : _children){
child->updateDisplayedOpacity(_displayedOpacity);
}
}
}
void Sprite::updateColor(void){}
4 Sprite 的draw方法
这里也不是真的opengl绘制,而是把绘制命令放到quadCommand命令类中
// draw
//本地坐标乘以transfrom就是世界坐标
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
// Don't do calculate the culling if the transform was not updated
_insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
//元素是否在屏幕范围内,如果一点不在,不绘制
if(_insideBounds)
{
//初始化绘制命令
_quadCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, &_quad, , transform);
//renderer将RenderCommand放到战中
//等场景中的UI全部遍历完毕,才开始绘制
renderer->addCommand(&_quadCommand);
#if CC_SPRITE_DEBUG_DRAW
_customDebugDrawCommand.init(_globalZOrder);
_customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
renderer->addCommand(&_customDebugDrawCommand);
#endif //CC_SPRITE_DEBUG_DRAW
}
}
5 Renderer的render()会开始真正绘制,会调用visitRenderQueue开始遍历,并配置顶点信息
visitRenderQueue
void Renderer::visitRenderQueue(const RenderQueue& queue)
{
ssize_t size = queue.size(); for (ssize_t index = ; index < size; ++index)
{
auto command = queue[index];
auto commandType = command->getType();
if(RenderCommand::Type::QUAD_COMMAND == commandType)//比如 Sprite
{
flush3D();
auto cmd = static_cast<QuadCommand*>(command);
//Batch quads
//如果自动批绘制的精灵超过VBO_SIZE,就立马绘制,绘制完了之后_numQuads清零
if(_numQuads + cmd->getQuadCount() > VBO_SIZE)
{
CCASSERT(cmd->getQuadCount()>= && cmd->getQuadCount() < VBO_SIZE, "VBO is not big enough for quad data, please break the quad data down or use customized render command"); //Draw batched quads if VBO is full
drawBatchedQuads(); } _batchedQuadCommands.push_back(cmd);
//memcpy指的是c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。
//numQuads为几个顶点,然后进行自动批绘制
memcpy(_quads + _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
//把各个顶点的本地坐标转换为世界坐标,并把值保存到_quads中
convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView()); _numQuads += cmd->getQuadCount(); } }
}
drawBatchedQuads()方法比较重要,运用的思路是 自动批绘制 ,和SpriteBatchNode基本类似,但是不用用户手动操作,用起来更加简单
自动批绘制要求使用同一张纹理,相同的BlendFunc设置,相同的Shader程序,并且在绘制顺序上处于相邻等。
代码:
void Renderer::drawBatchedQuads()
{
//TODO we can improve the draw performance by insert material switching command before hand. int quadsToDraw = ;
int startQuad = ; //Upload buffer to VBO
if(_numQuads <= || _batchedQuadCommands.empty())
{
return;
} if (Configuration::getInstance()->supportsShareableVAO())
{
//Set VBO data
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]); // option 1: subdata
// glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] ); // option 2: data
// glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW); // option 3: orphaning + glMapBuffer
//先清空制定大小的缓冲区 sizeof(_quads[0]) * (_numQuads)
/*
更新缓冲区对象的数据值
方法一:假设已经在用用程序的一个缓冲区中准备了相同类型的数据,glBufferSubData()将用我们提供的数据替换被绑定的缓冲区对象的一些数据子集。
void glBufferSubData(GLenum target,GLuint offset,GLsizei size,const GLvoid *data); 用data 指向的数据更新 与target 相关联的当前绑定缓冲区对象中从offset开始的size个字节数据。 方法二:允许灵活的选择需要更新的数据。
GLvoid *glMapBuffer(GLenum target,GLuenum access):返回一个指向缓冲区对象的指针,可以在这个缓冲区对象中写入新值及更新之后,再调用GLboolean glUnMapBuffer(GLenum target)表示已经完成了对数据的更新,取消对这个缓冲区的映射。如果只需要更新所需范围内的数据值,也可调用GLvoid *glMapBuffwerRange(GLenum target,GLintptr offset,GLsizeiptr length,GLbitfield access)
*/
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quads, sizeof(_quads[])* (_numQuads));
glUnmapBuffer(GL_ARRAY_BUFFER);
//清0
glBindBuffer(GL_ARRAY_BUFFER, ); //Bind VAO
GL::bindVAO(_quadVAO);
}
else
{
/*
在绘制精灵的时候,首先使用BindBuffer创建顶点缓冲对象,并使用BufferData将所有顶点属性数据存储到GL服务端缓冲对象中,然后VertexAttribPointer方法的pointer参数不再用来指定一个客户端的数组指针地址,而是指定该属性在数组一个顶点中的偏移量,因为GL将从服务端而不是客户端缓冲对象获取顶点数据
*/
#define kQuadSize sizeof(_quads[0].bl)
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]); glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * _numQuads , _quads, GL_DYNAMIC_DRAW); GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);//开启坐标,颜色,纹理 // vertices
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, , GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices)); // colors
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, , GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors)); // tex coords
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, , GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords)); /*
当一个非0 的缓冲对象被绑定到 ELEMENT_ARRAY_BUFFER,DrawElements将会从ELEMENT_ARRAY_BUFFER缓冲对象中获取顶点索引数据,此时,参数indices表示缓冲对象中顶点索引数组的偏移量
*/
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[]);
} //Start drawing verties in batch
for(const auto& cmd : _batchedQuadCommands)
{
auto newMaterialID = cmd->getMaterialID();//生成一个四边形的材料
if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH)
{
//Draw quads
if(quadsToDraw > 0)
{
glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += quadsToDraw*6; startQuad += quadsToDraw;
quadsToDraw = 0; glActiveTexture(GL_TEXTURE0+0);
} //Use new material
cmd->useMaterial();// _lastMaterialID = newMaterialID;
} quadsToDraw += cmd->getQuadCount();
} //Draw any remaining quad
if(quadsToDraw > )
{
glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad**sizeof(_indices[])) );
_drawnBatches++;
_drawnVertices += quadsToDraw*; glActiveTexture(GL_TEXTURE0+);
} if (Configuration::getInstance()->supportsShareableVAO())
{
//Unbind VAO
GL::bindVAO();
}
else
{
glBindBuffer(GL_ARRAY_BUFFER, );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, );
} _batchedQuadCommands.clear();
_numQuads = ;//顶点数目画完了,清0 ,重新开始
}
过程如下:
第一次遇到QuadCommand不会立即绘制,而是放到一个数组中缓存起来,然后继续迭代后面的RenderCommand。
如果遇到的第二个QuadCommand的类型仍然是QUAD_COMMAND,并且透明使用同样的‘材料’,则继续将该QuadCommand添加到
缓存数组中。如果他们使用不同的 ‘材料’,或者类型不是QUAD_COMMAND,则首先绘制之前缓存的QuadCommand数组。
这里的‘材料’不仅指纹理和着色器,还包括使用的混合模式以及一些OpenGL ES的状态设置。
生成材料的代码如下:
void QuadCommand::generateMaterialID()
{
/*
首先检查是否包含自定义着色器全局变量,因为如果有自定义着色器变量,那么想使用这些变量,开发者必须提供自定义
着色器,意味着他不能和系统的QuadCommand形成批绘制。如果开发者提供了自定义的着色器,_materialID将被设置为
MATERIAL_ID_DO_NOT_BATCH,表示该QuadCommand不能参与任何批绘制,即两个QuadCommand使用同一个自定义的着色器和相关状态
*/
if(_glProgramState->getUniformCount() > )
{
_materialID = QuadCommand::MATERIAL_ID_DO_NOT_BATCH;
}
else//不包含自定义的全局变量,则使用着色器名称,纹理名称和混合方程相关的参数计算一个Hash值,只有相同Hash的QuadCOmmand才能参与批绘制
{
int glProgram = (int)_glProgramState->getGLProgram()->getProgram(); int intArray[] = { glProgram, (int)_textureID->getName(), (int)_blendType.src, (int)_blendType.dst};
//xxHash 支持生成 32 位和 64 位哈希值,多个 benchmark 显示,其性能比 MurMurHash 的 32 位版本快接近一倍。如果程序的热点在于哈希操作,作为一种优化手段,xxHash 值得一试。
_materialID = XXH32((const void*)intArray, sizeof(intArray), );
}
}
6 简要分析以下使用材料函数
在自动批绘制的时候,如果监测到两个QuadCommand使用的材料相同,那么调用一次useMaterial即可,代码如下:
void QuadCommand::useMaterial() const
{
//Set texture
// glActiveTexture(GL_TEXTURE0)表示把活动的纹理单元设置为纹理单元0,调用glBindTexture将textureId指向的纹理绑定到纹理单元0,最后,调用glUniform1i把选定的纹理单元传递给片段着色器中的u_TextureUnit(sampler2D)。
//激活一个纹理单元,纹理单元也是从0开始
//这里没有glUniform1i(uTextureUnitLocation, 0);
//我认为是在updateUniforms里面设置了
glActiveTexture(GL_TEXTURE0 + );
GL::bindTexture2D(_textureID->getName()); //set blend mode 设置混合模式
GL::blendFunc(_blendType.src, _blendType.dst); _glProgramState->apply(_mv); }
然后跳到apply方法内
apply 方法首先将其着色器程序设置为当前着色器程序,然后分别设置每个顶点属性和全局属性的值。如果全局属性是纹理,还会绑定纹理到对应的纹理单元
对于另外一些频繁改动的数据,则需要在绘制的时候设置相应的数据,每个顶点属性和全局属性在apply的时候提供一个回调,用来通知应用程序设置正确的状态数据
void GLProgramState::apply(const Mat4& modelView)
{
//使用当前程序
applyGLProgram(modelView);
//执行顶点的相关操作
applyAttributes();
//执行全局变量的相关操作
applyUniforms();
}
void GLProgramState::applyGLProgram(const Mat4& modelView)
{
CCASSERT(_glprogram, "invalid glprogram");
if(_uniformAttributeValueDirty)
{
//_uniformsByName key为属性名字,value为插槽位置
for(auto& uniformLocation : _uniformsByName)
{
//_uniforms key为插槽的位置,value为变量的一些属性
if(_uniforms[uniformLocation.second]._uniform==_glprogram->getUniform(uniformLocation.first)){
CCLOG("相等");
}
//让_uniform的指针重新指向Uniform的地址位置 一般是不变的
_uniforms[uniformLocation.second]._uniform = _glprogram->getUniform(uniformLocation.first);
} _vertexAttribsFlags = ;
// _attributes key为属性名字,value为属性一些属性
for(auto& attributeValue : _attributes)
{
if(attributeValue.second._vertexAttrib == _glprogram->getVertexAttrib(attributeValue.first)){
CCLOG("相等");
}
//让_vertexAttrib的指针重新指向VertexAttrib的位置 一般是不变的
attributeValue.second._vertexAttrib = _glprogram->getVertexAttrib(attributeValue.first);;
//_enable默认是false,当setPointer或者setCallBack改变顶点的值的时候,开启
//通过左移记录开启了哪个GPU插槽,到时候会在GPU中开启
if(attributeValue.second._enabled)
_vertexAttribsFlags |= << attributeValue.second._vertexAttrib->index;
} _uniformAttributeValueDirty = false; }
// set shader
_glprogram->use();
//把模型矩阵传到Shader GPU
_glprogram->setUniformsForBuiltins(modelView);
}
void GLProgramState::applyAttributes(bool applyAttribFlags)
{
// Don't set attributes if they weren't set
// Use Case: Auto-batching
/*
//如果没有设置属性,请不要设置属性
//使用案例:自动批处理
*/
if(_vertexAttribsFlags) {//开启对应的 插槽
// enable/disable vertex attribs
if (applyAttribFlags)
GL::enableVertexAttribs(_vertexAttribsFlags);
// set attributes
for(auto &attribute : _attributes)
{
//属性完成重新赋值给vbo
attribute.second.apply();
}
}
}
/*
每个VertexAttibValue变量会在apply()方法 被调用的时候 设置这些属性状态信息,对于那些需要在外部动态修改顶点属性的情况,还提供了一个回调函数来设置顶点属性状态,如下
然而,由于顶点数组和DrawArryas()或DrawElements方法一起工作,如果使用VBO在服务端存储顶点数组,还需要和更多的绘制命令一起工作,所以通常顶点属性还少在外面单独设置
*/
void VertexAttribValue::apply()
{
if(_enabled) {
if(_useCallback) {
(*_value.callback)(_vertexAttrib);
}
else
{
//作用:GPU如何把vbo中的数据分发到各个不同的shader去执行
// 他设置的参数主要是告诉GPU如何去遍历vbo的内存块的
//这个方法执行在drawBatchedQuads之后,默认在drawBatchedQuads中会设置完glVertexAttribPointer,这里是如果在外界修改了,然后替换之前的默认设置
/*
GLuint index 属性索引, shader中由layout(location=0)指定 GLint size成员个数1\2\3\4,或者RGBA表示4 GLenum type 成员类型,一般为GL_FLOAT GLboolean normalized 是否需要normalized,一般GL_FALSE GLsizei stride 跨距,0表示紧密排列,相当于size * sizeof(type) const GLvoid* pointer 对应buffer偏移量:(const GLvoid *) offset
*/
glVertexAttribPointer(_vertexAttrib->index,
_value.pointer.size,
_value.pointer.type,
_value.pointer.normalized,
_value.pointer.stride,
_value.pointer.pointer);
}
}
}
void GLProgramState::applyUniforms()
{
// set uniforms
for(auto& uniform : _uniforms) {
uniform.second.apply();//全局变量重新赋值到GPU
}
}
/*
UniformValue 变量会在apply方法被调用的时候设置对应的全局变量名称,如果全局变量是一个纹理,则apply方法还是执行纹理绑定相关工作
*/
void UniformValue::apply()
{
if(_useCallback) {
(*_value.callback)(_glprogram, _uniform);
}
else
{
switch (_uniform->type) {
case GL_SAMPLER_2D:
_glprogram->setUniformLocationWith1i(_uniform->location, _value.tex.textureUnit);
GL::bindTexture2DN(_value.tex.textureUnit, _value.tex.textureId);
break; case GL_INT:
_glprogram->setUniformLocationWith1i(_uniform->location, _value.intValue);
break; case GL_FLOAT:
_glprogram->setUniformLocationWith1f(_uniform->location, _value.floatValue);
break; case GL_FLOAT_VEC2:
_glprogram->setUniformLocationWith2f(_uniform->location, _value.v2Value[], _value.v2Value[]);
break; case GL_FLOAT_VEC3:
_glprogram->setUniformLocationWith3f(_uniform->location, _value.v3Value[], _value.v3Value[], _value.v3Value[]);
break; case GL_FLOAT_VEC4:
_glprogram->setUniformLocationWith4f(_uniform->location, _value.v4Value[], _value.v4Value[], _value.v4Value[], _value.v4Value[]);
break; case GL_FLOAT_MAT4:
_glprogram->setUniformLocationWithMatrix4fv(_uniform->location, (GLfloat*)&_value.matrixValue, );
break; default:
CCASSERT(false, "Invalid UniformValue");
break;
}
}
}
这就是执行useMaterial的基本步骤,设置完毕这个,GPU中有了新的值,glDrawElements进行绘画
cocos源码分析--Sprite绘图原理的更多相关文章
- cocos源码分析--ClippingNode绘图原理
在OpenGL 绘制过程中,与帧缓冲有关的是模版,深度测试,混合操作.模版测试使应用程序可以定义一个遮罩,在遮罩内的片段将被保留或者丢弃,在遮罩外的片段操作行为则相反.深度测试用来剔除那些被场景遮挡的 ...
- cocos源码分析--SpriteBatchNode绘图原理
SpriteBatchNode继承Node,并实现了TextureProtocol接口,重写了Node的addChild()方法,visit()方法以及draw()方法. addChild()方法限制 ...
- Guava 源码分析(Cache 原理 对象引用、事件回调)
前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...
- 深入源码分析SpringMVC底层原理(二)
原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...
- 【转】MaBatis学习---源码分析MyBatis缓存原理
[原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...
- php中foreach源码分析(编译原理)
php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...
- Kafka源码分析及图解原理之Producer端
一.前言 任何消息队列都是万变不离其宗都是3部分,消息生产者(Producer).消息消费者(Consumer)和服务载体(在Kafka中用Broker指代).那么本篇主要讲解Producer端,会有 ...
- Robotium源码分析之运行原理
从上一章<Robotium源码分析之Instrumentation进阶>中我们了解到了Robotium所基于的Instrumentation的一些进阶基础,比如它注入事件的原理等,但Rob ...
- Dubbo 源码分析 - 自适应拓展原理
1.原理 我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心.Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol.Clu ...
随机推荐
- [转]总结@Autowired 和@Resource
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按byName自动注入罢了. @Resource有两个属性是比较重要的,分 ...
- 记一个视频播放器插件 video.js
最近在看扣丁学堂上面的一些视频, 突然对他用的视频播放器有点兴趣, 他也是采用的 ts切片播放, 如果使用传统的video标签是无法实现的 他使用的插件叫做 video.js 官网地址 官网提供的播放 ...
- 关于Bagging
Bagging分为两种:Bagging和Pasting,前者是概率中的放回随机采样,后者是不放回随机采样:默认是放回采样随机:设置bootstrap=False即设置为不放回采样:默认bootstra ...
- python 通过pymongo操作mongoDB执行sort
在mongo shell 中对数据进行排序操作的时候 db.getCollection('ANJUKE_PRICE').find({},{'id':1,'_id':0}).sort({'id':1}) ...
- java自动装箱的一个例子
Object obj = 56; int i = (Integer)obj; 第一行等价于: Object obj = Integer.valueOf(56); Integer.valueO ...
- NET设计模式 第二部分 行为型模式(18):观察者模式(Observer Pattern)
概述 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知.如果这样的依赖关系过于紧密,将使软件不能很好地抵御 ...
- hanlp提取文本关键词的使用方法记录
本文是csu_zipple 分享的关于使用hanlp汉语言处理包提取关键词的过程一个简单的记录分享.想要使用hanlp提取文本关键词的新手朋友们可以参考学习一下! 如何在一段文本之中提取出相应的关键词 ...
- Mongodb主从复制 及 副本集+分片集群梳理
转载努力哥原文,原文连接https://www.cnblogs.com/nulige/p/7613721.html 介绍了Mongodb的安装使用,在 MongoDB 中,有两种数据冗余方式,一种 是 ...
- 电信版华为MATE7 EMUI4.0回退3.1和3.0教程与中转包
mate7升级6.0后遇到很多问题,想回退版本,找了很多教程,现在总结一下用中转包回退.EMUI4.0回退3.1,先下载B500中转包,将dload复制到2G以上内存卡根目录,不要三键强刷,会卡在开机 ...
- A* search算法
今天,还是国庆和中秋双节的时间节点,一个天气不错的日子,孩子已经早早的睡觉了,玩了一整天,也不睡觉,累的实在扛不住了,勉强洗澡结束,倒床即睡着的节奏... 不多说题外话,进入正题. 什么是A*搜索算法 ...