• 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成员个数1234,或者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进行绘画

  • 相关阅读:
    HDU 3537
    POJ 1175
    POJ 1021 人品题
    POJ 2068
    POJ 2608
    POJ 2960
    poj 1635
    ustc 1117
    ural 1468
    数字游戏
  • 原文地址:https://www.cnblogs.com/xiaonanxia/p/9202999.html
Copyright © 2020-2023  润新知