美文网首页
cocos2d源码分析(十):Material

cocos2d源码分析(十):Material

作者: 奔向火星005 | 来源:发表于2019-01-14 16:17 被阅读0次

    Material类,即材质类,在cocos2d中相当重要,因为它内部管理着OpenGL相关的如着色器program,uniform以及OpenGL的状态和配置。它可以根据material配置文件来完成各种特效渲染,光照或贴图等效果。先看下它的类图:


    由图可见,Material内有一个Technique类型的数组,Technique类内有一个Pass类的数组,Pass类内部成员有GLProgramState和VertexAttribBinging,可见,一个Pass对应着一个着色器。具体的如特效,光照的效果实际上就是在着色器中实现的。

    下面看下一个具体的例子,cocos2d中的Material_2DEffects的demo,通过material文件创建Material实现2D特效。代码如下:

    void Material_2DEffects::onEnter()
    {
        MaterialSystemBaseTest::onEnter();
        //解析2d_effects.material文件,得到properties对象
        auto properties = Properties::createNonRefCounted("Materials/2d_effects.material#sample");
    
        // Print the properties of every namespace within this one.
        printProperties(properties, 0);  //打印
    
        Material *mat1 = Material::createWithProperties(properties); //通过properties创建Material
    
        //第一个是模糊效果
        auto spriteBlur = Sprite::create("Images/grossini.png");
        spriteBlur->setPositionNormalized(Vec2(0.2f, 0.5f));
        this->addChild(spriteBlur);
        spriteBlur->setGLProgramState(mat1->getTechniqueByName("blur")->getPassByIndex(0)->getGLProgramState());  //将Material内部的一个GLProgramState对象赋给了sprite的_glProgramState
    
        //第二个是轮廓效果
        auto spriteOutline = Sprite::create("Images/grossini.png");
        spriteOutline->setPositionNormalized(Vec2(0.4f, 0.5f));
        this->addChild(spriteOutline);
        spriteOutline->setGLProgramState(mat1->getTechniqueByName("outline")->getPassByIndex(0)->getGLProgramState());
    
        //第三个是噪声效果
        auto spriteNoise = Sprite::create("Images/grossini.png");
        spriteNoise->setPositionNormalized(Vec2(0.6f, 0.5f));
        this->addChild(spriteNoise);
        spriteNoise->setGLProgramState(mat1->getTechniqueByName("noise")->getPassByIndex(0)->getGLProgramState());
    
        //第四个是边缘效果
        auto spriteEdgeDetect = Sprite::create("Images/grossini.png");
        spriteEdgeDetect->setPositionNormalized(Vec2(0.8f, 0.5f));
        this->addChild(spriteEdgeDetect);
        spriteEdgeDetect->setGLProgramState(mat1->getTechniqueByName("edge_detect")->getPassByIndex(0)->getGLProgramState());
    
        // properties is not a "Ref" object
        CC_SAFE_DELETE(properties);
    }
    

    材质内容的信息放在2d_effects.material文件中,我们看下它具体的内容是什么:

    material sample
    {
        technique blur
        {
            pass 0
            {
                shader
                {
                    defines = THIS_IS_AN_EXAMPLE 1;TOMORROW_IS_HOLIDAY 2
                    vertexShader = Shaders/example_Simple.vsh
                    fragmentShader = Shaders/example_Blur.fsh
                    // Uniforms
                    blurRadius = 3
                    sampleNum = 5
                    resolution = 100,100
                }
            }
        }
        technique outline
        {
            pass 0
            {
                shader
                {
                    vertexShader = Shaders/example_Simple.vsh
                    fragmentShader = Shaders/example_Outline.fsh
                    u_outlineColor = 0.1, 0.2, 0.3
                    u_radius = 0.01
                    u_threshold = 1.75
                }
            }
        }
        technique noise {
            pass 0
            {
                shader
                {
                    vertexShader = Shaders/example_Simple.vsh
                    fragmentShader = Shaders/example_Noisy.fsh
                    resolution = 100,100
                }
            }
        }
        technique edge_detect
        {
            pass 0
            {
                shader
                {
                    defines = 
                    vertexShader = Shaders/example_Simple.vsh
                    fragmentShader = Shaders/example_EdgeDetection.fsh
                    resolution = 100, 100
                }
            }
        }
    }
    

    material文件只是普通的文本文件,解析之后的信息放在properties对象中,Properties的类图如下:


    由类图可见,Properties内部又有Properties的数组,一个Properties对应着material文件中的一个大括号,如此嵌套下去。Propertie类记录着一对key-value值,对应着最内部的大括号的等式。material文件解析后,对应的Properties类的内容为:


    下面看下如何用Properties初始化Material,代码如下:

    bool Material::initWithProperties(Properties* materialProperties)
    {
        return parseProperties(materialProperties);
    }
    

    parseProperties函数如下:

    bool Material::parseProperties(Properties* materialProperties)
    {
        setName(materialProperties->getId());
        auto space = materialProperties->getNextNamespace();
        while (space)  //循环解析子空间
        {
            const char* name = space->getNamespace();
            if (strcmp(name, "technique") == 0)
            {
                parseTechnique(space);  //解析technique块
            }
            else if (strcmp(name, "renderState") == 0)
            {
                parseRenderState(this, space);  //解析renderState块
            }
            space = materialProperties->getNextNamespace();
        }
        return true;
    }
    

    因本次demo的2d_effects.material文件没有renderState块,我们只看parseTechnique函数

    bool Material::parseTechnique(Properties* techniqueProperties)
    {
        auto technique = Technique::create(this);
        _techniques.pushBack(technique);
    
        // first one is the default one
        if (!_currentTechnique)
            _currentTechnique = technique;
    
        // name
        technique->setName(techniqueProperties->getId());
    
        // passes
        auto space = techniqueProperties->getNextNamespace();
        while (space)  //循环解析子空间
        {
            const char* name = space->getNamespace();
            if (strcmp(name, "pass") == 0)
            {
                parsePass(technique, space);  //解析pass块
            }
            else if (strcmp(name, "renderState") == 0)
            {
                parseRenderState(this, space);
            }
            space = techniqueProperties->getNextNamespace();
        }
        return true;
    }
    

    parsePass函数如下:

    bool Material::parsePass(Technique* technique, Properties* passProperties)
    {
        auto pass = Pass::create(technique);
        technique->addPass(pass);
    
        // Pass can have 3 different namespaces:
        //  - one or more "sampler"
        //  - one "renderState"
        //  - one "shader"
    
        auto space = passProperties->getNextNamespace();
        while (space)
        {
            const char* name = space->getNamespace();
            if (strcmp(name, "shader") == 0)
                parseShader(pass, space);  //解析shader块,生成OpenGL的program
            else if (strcmp(name, "renderState") == 0)
                parseRenderState(pass, space);
            else {
                CCASSERT(false, "Invalid namespace");
                return false;
            }
            space = passProperties->getNextNamespace();
        }
        return true;
    }
    

    由代码看到正在解析shader块,比如blur中的内容,如下:

                shader
                {
                    defines = THIS_IS_AN_EXAMPLE 1;TOMORROW_IS_HOLIDAY 2 //这是着色器里的宏定义
                    vertexShader = Shaders/example_Simple.vsh  //顶点着色器名称
                    fragmentShader = Shaders/example_Blur.fsh   //片元着色器名称
                    // Uniforms
                    blurRadius = 3   //着色器中的uniform变量
                    sampleNum = 5
                    resolution = 100,100
                }
    

    parseShader函数内容如下:

    bool Material::parseShader(Pass* pass, Properties* shaderProperties)
    {
        // vertexShader //得到顶点着色器的文件名
        const char* vertShader = getOptionalString(shaderProperties, "vertexShader", nullptr);
    
        // fragmentShader //得到片元着色器的文件名
        const char* fragShader = getOptionalString(shaderProperties, "fragmentShader", nullptr);
    
        // compileTimeDefines  //这个是着色器里的宏定义
        const char* compileTimeDefines = getOptionalString(shaderProperties, "defines", "");
    
        if (vertShader && fragShader)
        {   //根据vertShader和fragShader的文件名,创建glProgramState
            auto glProgramState = GLProgramState::getOrCreateWithShaders(vertShader, fragShader, compileTimeDefines);
            pass->setGLProgramState(glProgramState);
    
            // Parse uniforms only if the GLProgramState was created
            auto property = shaderProperties->getNextProperty();
            while (property)  //根据material文件设置program中的uniform变量
            {
                if (isValidUniform(property))  //是否是有效的uniform,只有"defines","vertexShader"和"fragmentShader"不是
                {
                    parseUniform(glProgramState, shaderProperties, property); //保存到glProgramState中的_uniforms
                }
                property = shaderProperties->getNextProperty();
            }
            auto space = shaderProperties->getNextNamespace();
            while (space)
            {
                const char* name = space->getNamespace();
                if (strcmp(name, "sampler") == 0)
                {
                    parseSampler(glProgramState, space);
                }
                space = shaderProperties->getNextNamespace();
            }
        }
        return true;
    }
    

    分析都在注释里,简而言之就是通过shader块内的着色器文件名及uniform变量设置,来创建
    glProgramState并保存到pass中。

    相关文章

      网友评论

          本文标题:cocos2d源码分析(十):Material

          本文链接:https://www.haomeiwen.com/subject/ledddqtx.html