孤海傲月

导航

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进行绘画

 

posted on 2018-06-20 11:37  孤海傲月  阅读(1201)  评论(0编辑  收藏  举报