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, 0, 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(110,110); scene->addChild(sprite); sprite->setColor(Color3B(0, 255, 0));
为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(120); sprite->setCascadeOpacityEnabled(true);
来控制透明度和是否传递给子元素
原理代码如下:
void Node::setOpacity(GLubyte opacity) { _displayedOpacity = _realOpacity = opacity; updateCascadeOpacity(); } void Node::updateCascadeOpacity() { GLubyte parentOpacity = 255; 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, 1, 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 = 0; 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()>= 0 && 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 = 0; int startQuad = 0; //Upload buffer to VBO if(_numQuads <= 0 || _batchedQuadCommands.empty()) { return; } if (Configuration::getInstance()->supportsShareableVAO()) { //Set VBO data glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]); // 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[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW); void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); memcpy(buf, _quads, sizeof(_quads[0])* (_numQuads)); glUnmapBuffer(GL_ARRAY_BUFFER); //清0 glBindBuffer(GL_ARRAY_BUFFER, 0); //Bind VAO GL::bindVAO(_quadVAO); } else { /* 在绘制精灵的时候,首先使用BindBuffer创建顶点缓冲对象,并使用BufferData将所有顶点属性数据存储到GL服务端缓冲对象中,然后VertexAttribPointer方法的pointer参数不再用来指定一个客户端的数组指针地址,而是指定该属性在数组一个顶点中的偏移量,因为GL将从服务端而不是客户端缓冲对象获取顶点数据 */ #define kQuadSize sizeof(_quads[0].bl) glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _numQuads , _quads, GL_DYNAMIC_DRAW); GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);//开启坐标,颜色,纹理 // vertices glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices)); // colors glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors)); // tex coords glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, 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[1]); } //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 > 0) { glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) ); _drawnBatches++; _drawnVertices += quadsToDraw*6; glActiveTexture(GL_TEXTURE0+0); } if (Configuration::getInstance()->supportsShareableVAO()) { //Unbind VAO GL::bindVAO(0); } else { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } _batchedQuadCommands.clear(); _numQuads = 0;//顶点数目画完了,清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() > 0) { _materialID = QuadCommand::MATERIAL_ID_DO_NOT_BATCH; } else//不包含自定义的全局变量,则使用着色器名称,纹理名称和混合方程相关的参数计算一个Hash值,只有相同Hash的QuadCOmmand才能参与批绘制 { int glProgram = (int)_glProgramState->getGLProgram()->getProgram(); int intArray[4] = { glProgram, (int)_textureID->getName(), (int)_blendType.src, (int)_blendType.dst}; //xxHash 支持生成 32 位和 64 位哈希值,多个 benchmark 显示,其性能比 MurMurHash 的 32 位版本快接近一倍。如果程序的热点在于哈希操作,作为一种优化手段,xxHash 值得一试。 _materialID = XXH32((const void*)intArray, sizeof(intArray), 0); } }
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 + 0); 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 = 0; // _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 |= 1 << 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[0], _value.v2Value[1]); break; case GL_FLOAT_VEC3: _glprogram->setUniformLocationWith3f(_uniform->location, _value.v3Value[0], _value.v3Value[1], _value.v3Value[2]); break; case GL_FLOAT_VEC4: _glprogram->setUniformLocationWith4f(_uniform->location, _value.v4Value[0], _value.v4Value[1], _value.v4Value[2], _value.v4Value[3]); break; case GL_FLOAT_MAT4: _glprogram->setUniformLocationWithMatrix4fv(_uniform->location, (GLfloat*)&_value.matrixValue, 1); break; default: CCASSERT(false, "Invalid UniformValue"); break; } } }
这就是执行useMaterial的基本步骤,设置完毕这个,GPU中有了新的值,glDrawElements进行绘画