cocos2d源码分析(十):Material

2019-01-14  本文已影响0人  奔向火星005

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中。

上一篇下一篇

猜你喜欢

热点阅读