学习OpenGL有一段时间了

看了别人的多重纹理讲解,收获很多

cocos里使用最频繁的Sprite类并没有使用多重纹理

于是我想设法封装一个UVSprite

给Sprite增加一个uv纹理

可以实现一系列动态效果

比如云彩飘动/流水/明暗等等

大大丰富Sprite的表现能力

--[[

转载请注明原文地址

http://www.cnblogs.com/billyrun/articles/5577176.html

]]

 

开发思路如下

 

1.继承Sprite

UVSprite除了显示上对Sprite进行了扩展

其他行为应与Sprite保持一致

新增接口如下,设置UV纹理

//设置并启用uv纹理(若不调用,与普通Sprite行为一致)
bool setUVTexture(const std::string& filename);

bool UVSprite::setUVTexture(const std::string& filename)
{
    CCASSERT(filename.size()>0, "Invalid filename for sprite");
    Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
    bool result = texture != nullptr;
    if (texture)
    {
        __setUVTexture(texture);

        //记录下Sprite与uv纹理的尺寸比例
        _uvScale.x = getContentSize().width / texture->getContentSize().width;
        _uvScale.y = getContentSize().height / texture->getContentSize().height;

        //若是修改了shader中的代码
        //C++代码也需要修改后重新编译一次才能生效
        auto program = new GLProgram();
        program->initWithFilenames("uvSprite.vert", "uvSprite.frag");
        program->link();
        //set uniform locations
        program->updateUniforms();
        this->setGLProgram(program); 

        //记录shader中uniform的loc
        _uvScaleLoc = glGetUniformLocation(program->getProgram(), "u_uvScale"); 
        _uvOpacityLoc = glGetUniformLocation(program->getProgram(), "u_vOpacity");
        _uvVelocityLoc = glGetUniformLocation(program->getProgram(), "u_vVelocity");
        _uvAlphaFilterLoc = glGetUniformLocation(program->getProgram(), "u_vAlphaFilter");
    }
    return result;
}

保存了UV纹理之后

我们重新设置了GLProgram

使用自定义的shader(代码见随后章节)

shader里面声明的几个变量用于实现不同效果(如横纵方向纹理移动等)

在此取得其位置,以便之后onDraw方法中赋值使用

 

2.渲染流程设计

Sprite使用的是QuadCommand

QuadCommand适合于对四边形的通用渲染

UVSprite需要做一些个性化的处理

所以改为使用CustomCommand做自定义渲染处理

为此我们要重写draw函数

virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;

再新增一个渲染函数

void onDraw(const Mat4 &transform, uint32_t flags);

void UVSprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    if (_uvTexture)
    {
        //重写draw函数 使用customCommand渲染命令
        _customCommand.init(_globalZOrder);
        _customCommand.func = CC_CALLBACK_0(UVSprite::onDraw, this, transform, flags);
        renderer->addCommand(&_customCommand);
    }
    else
    {
        Sprite::draw(renderer, transform, flags);
    }
}

_uvTexture就是我们设置的UV纹理

当用户启动UV后,进入自定义渲染流程UVSprite::onDraw

若用户未启动则执行Sprite::draw,UVSprite退化为Sprite

 

3.渲染实现逻辑

shader代码如下

attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

void main()
{
    gl_Position = CC_PMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
}
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform vec2 u_uvScale;

uniform float u_vOpacity;
uniform vec2 u_vVelocity;
uniform int u_vAlphaFilter;
void main()
{
    vec2 uv_Coord = v_texCoord * u_uvScale;
    vec4 color0 = texture2D(CC_Texture0, v_texCoord);
    vec4 color1 = texture2D(CC_Texture1, uv_Coord + u_vVelocity);
    if(u_vAlphaFilter == 0)
    {
        gl_FragColor = v_fragmentColor * (color0 + u_vOpacity * color1);
    }
    else
    {
        gl_FragColor = v_fragmentColor * (color0 + color0.a * u_vOpacity * color1);
    }
}

顶点部分与Sprite所使用的ccShader_PositionTextureColor_noMVP完全一致(确实没啥要改的)

主要是片段部分,上文写到setUVTexture中记录shader中uniform的loc

指的就是以下4个变量

uniform vec2 u_uvScale;//表示uv纹理是否缩放  
uniform float u_vOpacity;//uv纹理透明度  
uniform vec2 u_vVelocity;//uv纹理偏移位置(移动速度)
uniform int u_vAlphaFilter;//uv纹理是否受原图纹理alpha值影响透明度

这些都是我粗略想到的

还可以扩展许多处理策略,丰富表现形式

为了方便阅读我把代码改成了现在的样子

之前它是这样的

gl_FragColor = v_fragmentColor * (texture2D(CC_Texture0, v_texCoord) + texture2D(CC_Texture0, v_texCoord).a * u_vOpacity * texture2D(CC_Texture1, uv_Coord + u_vVelocity));

目前还不清楚像下面这样提出来color0/color1会不会对效率或者显存有影响

vec4 color0 = texture2D(CC_Texture0, v_texCoord);
vec4 color1 = texture2D(CC_Texture1, uv_Coord + u_vVelocity);

有待测试,也欢迎高人解答~

 

shader的使用都在以下函数中

void UVSprite::onDraw(const Mat4 &transform, uint32_t flags)

void UVSprite::onDraw(const Mat4 &transform, uint32_t flags)
{
    auto glProgramState = getGLProgramState();

    //转换图片的4个顶点
    //对应quadCommand中的Renderer::fillQuads操作
    //若不进行如下变换,Node使用的pos,scale都表现不出来
    transform.transformPoint(_quad.tl.vertices, &_verticesTransformed[0]);
    transform.transformPoint(_quad.bl.vertices, &_verticesTransformed[1]);
    transform.transformPoint(_quad.tr.vertices, &_verticesTransformed[2]);
    transform.transformPoint(_quad.br.vertices, &_verticesTransformed[3]);
    
    glProgramState->apply(transform);
    GL::blendFunc(_blendFunc.src, _blendFunc.dst);

    //分别取本身纹理和uv纹理绑定至默认的0,1纹理单元中
    //这两个纹理单元在shader中对应CC_Texture0/CC_Texture1
    GL::bindTexture2D(_texture->getName());
    GL::bindTexture2DN(1, _uvTexture->getName());
    GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

#define kQuadSize sizeof(_quad.bl)
    size_t offset = (size_t)&_quad;

    // 对于vertex绑定我们变换过的顶点信息
    // 对于texCoods和color绑定_quad中的信息
    // vertex
    int diff = offsetof(V3F_C4B_T2F, vertices);
    //glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset2 + diff));
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _verticesTransformed);
    // texCoods
    diff = offsetof(V3F_C4B_T2F, texCoords);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));

    // color
    diff = offsetof(V3F_C4B_T2F, colors);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));

    // 设置我们的自定义变量
    // 位置很关键 
    // 提前只能取位置loc
    // 赋值需要在这里
    // uvScale
    glUniform2f(_uvScaleLoc, _uvStretch ? 1.0f : _uvScale.x, _uvStretch ? 1.0f : _uvScale.y);
    glUniform1f(_uvOpacityLoc, _uvOpacity);
    _uvPosition = _uvPosition + _uvVelocity;
    glUniform2f(_uvVelocityLoc, _uvPosition.x, _uvPosition.y);
    //原图透明度有区分才有效
    //若原图a值全部为1 则全都会绘制纹理
    glUniform1i(_uvAlphaFilterLoc, (int)_uvAlphaFilter);
    
    //绘制三角形
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 
    CHECK_GL_ERROR_DEBUG();
    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4);
}

这里注释已经写得比较清楚了

在初次测试的时候我并没有加入坐标系变换

于是我的UVSprite十分诡异的出现在了屏幕的左下角

setPosition/setScale统统无效

坐标系变换是OpenGL绘制的一大块内容

里面的数学知识我们就不多提了

cocos对于四边形(Sprite)的处理策略是在渲染时统一变换

Renderer::fillQuads函数里拿到Sprite的位置缩放信息

计算得到顶点坐标直接保存在_quadVerts里面

然后渲染顶点的时候直接从_quadVerts取顶点信息

我们这里做了相似的处理

将变换后的顶点信息保存在_verticesTransformed

这样一来setPosition/setScale等方法就可以正常使用了

 

4.效果

球是的一个半透明png图片

雪花是一个个小图片

雪花在自上而下飘落(自行脑补)

 

完整代码如下(shader上面已经贴过了)

#ifndef __UVSprite_H__
#define __UVSprite_H__

#include "cocos2d.h"
USING_NS_CC;


class UVSprite : public cocos2d::Sprite
{
public:
    static UVSprite* create(const std::string& filename);
    UVSprite();
    virtual ~UVSprite();
    //重写draw
    virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;
    void onDraw(const Mat4 &transform, uint32_t flags);
    //设置并启用uv纹理(若不调用,与普通Sprite行为一致)
    bool setUVTexture(const std::string& filename);
private:
    void __setUVTexture(cocos2d::Texture2D *texture);
    cocos2d::Texture2D*       _uvTexture;
    CustomCommand _customCommand;
    Vec3 _verticesTransformed[4];

    //uvTexture与Sprite自身纹理的宽高比例,uv纹理是否拉伸
    Vec2 _uvScale;
    GLuint _uvScaleLoc;
    CC_SYNTHESIZE(bool, _uvStretch, UvStretch);

    //uv纹理是否受原图alpha值影响
    CC_SYNTHESIZE(bool, _uvAlphaFilter, UvAlphaFilter);
    GLuint _uvAlphaFilterLoc;

    //uv纹理透明度
    CC_SYNTHESIZE(float, _uvOpacity, UvOpacity);
    GLuint _uvOpacityLoc;

    //uv纹理移动速度,坐标偏移量
    CC_SYNTHESIZE(Vec2, _uvVelocity, UvVelocity);
    CC_SYNTHESIZE_READONLY(Vec2, _uvPosition, UvPosition);
    GLuint _uvVelocityLoc;
};

#endif // __UVSprite_H__
View Code
#include "UVSprite.h"
USING_NS_CC;

UVSprite* UVSprite::create(const std::string& filename)
{
    UVSprite *sprite = new (std::nothrow) UVSprite();
    if (sprite && sprite->initWithFile(filename))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}
UVSprite::UVSprite()
    :_uvTexture(nullptr)
    ,_uvOpacity(1.0f)
    ,_uvStretch(false)
    ,_uvAlphaFilter(false)
    ,_uvVelocity(0.0f, 0.0f)
    ,_uvPosition(0.0f, 0.0f)
{}

UVSprite::~UVSprite()
{
    CC_SAFE_RELEASE(_uvTexture);
}

void UVSprite::__setUVTexture(Texture2D *texture)
{
    CC_SAFE_RETAIN(texture);
    CC_SAFE_RELEASE(_uvTexture);
    _uvTexture = texture;

    //GL_LINEAR 避免放大后色块失真
    //GL_REPEAT u/v超界时(0~1)repeat
    Texture2D::TexParams        tRepeatParams;
    tRepeatParams.magFilter = GL_LINEAR;
    tRepeatParams.minFilter = GL_LINEAR;
    tRepeatParams.wrapS = GL_REPEAT;
    tRepeatParams.wrapT = GL_REPEAT;
    _uvTexture->setTexParameters(tRepeatParams);
}


bool UVSprite::setUVTexture(const std::string& filename)
{
    CCASSERT(filename.size()>0, "Invalid filename for sprite");
    Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
    bool result = texture != nullptr;
    if (texture)
    {
        __setUVTexture(texture);

        //记录下Sprite与uv纹理的尺寸比例
        _uvScale.x = getContentSize().width / texture->getContentSize().width;
        _uvScale.y = getContentSize().height / texture->getContentSize().height;

        //若是修改了shader中的代码
        //C++代码也需要修改后重新编译一次才能生效
        auto program = new GLProgram();
        program->initWithFilenames("uvSprite.vert", "uvSprite.frag");
        program->link();
        //set uniform locations
        program->updateUniforms();
        this->setGLProgram(program); 

        //记录shader中uniform的loc
        _uvScaleLoc = glGetUniformLocation(program->getProgram(), "u_uvScale"); 
        _uvOpacityLoc = glGetUniformLocation(program->getProgram(), "u_vOpacity");
        _uvVelocityLoc = glGetUniformLocation(program->getProgram(), "u_vVelocity");
        _uvAlphaFilterLoc = glGetUniformLocation(program->getProgram(), "u_vAlphaFilter");
    }
    return result;
}
void UVSprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    if (_uvTexture)
    {
        //重写draw函数 使用customCommand渲染命令
        _customCommand.init(_globalZOrder);
        _customCommand.func = CC_CALLBACK_0(UVSprite::onDraw, this, transform, flags);
        renderer->addCommand(&_customCommand);
    }
    else
    {
        Sprite::draw(renderer, transform, flags);
    }
}
void UVSprite::onDraw(const Mat4 &transform, uint32_t flags)
{
    auto glProgramState = getGLProgramState();

    //转换图片的4个顶点
    //对应quadCommand中的Renderer::fillQuads操作
    //若不进行如下变换,Node使用的pos,scale都表现不出来
    transform.transformPoint(_quad.tl.vertices, &_verticesTransformed[0]);
    transform.transformPoint(_quad.bl.vertices, &_verticesTransformed[1]);
    transform.transformPoint(_quad.tr.vertices, &_verticesTransformed[2]);
    transform.transformPoint(_quad.br.vertices, &_verticesTransformed[3]);
    
    glProgramState->apply(transform);
    GL::blendFunc(_blendFunc.src, _blendFunc.dst);

    //分别取本身纹理和uv纹理绑定至默认的0,1纹理单元中
    //这两个纹理单元在shader中对应CC_Texture0/CC_Texture1
    GL::bindTexture2D(_texture->getName());
    GL::bindTexture2DN(1, _uvTexture->getName());
    GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

#define kQuadSize sizeof(_quad.bl)
    size_t offset = (size_t)&_quad;

    // 对于vertex绑定我们变换过的顶点信息
    // 对于texCoods和color绑定_quad中的信息
    // vertex
    int diff = offsetof(V3F_C4B_T2F, vertices);
    //glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset2 + diff));
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _verticesTransformed);
    // texCoods
    diff = offsetof(V3F_C4B_T2F, texCoords);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));

    // color
    diff = offsetof(V3F_C4B_T2F, colors);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));

    // 设置我们的自定义变量
    // 位置很关键 
    // 提前只能取位置loc
    // 赋值需要在这里
    // uvScale
    glUniform2f(_uvScaleLoc, _uvStretch ? 1.0f : _uvScale.x, _uvStretch ? 1.0f : _uvScale.y);
    glUniform1f(_uvOpacityLoc, _uvOpacity);
    _uvPosition = _uvPosition + _uvVelocity;
    glUniform2f(_uvVelocityLoc, _uvPosition.x, _uvPosition.y);
    //原图透明度有区分才有效
    //若原图a值全部为1 则全都会绘制纹理
    glUniform1i(_uvAlphaFilterLoc, (int)_uvAlphaFilter);
    
    //绘制三角形
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 
    CHECK_GL_ERROR_DEBUG();
    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4);
}
View Code
#ifndef __UVSprite_TEXT_H__
#define __UVSprite_TEXT_H__

#include "cocos2d.h"
#include "UVSprite.h"


class UVSpriteTest : public cocos2d::Layer
{
public:
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();
    
    // implement the "static create()" method manually
    CREATE_FUNC(UVSpriteTest);

    UVSpriteTest();

};

#endif // __UVSprite_TEXT_H__
View Code
#include "UVSpriteTest.h"
#include "../cocos/ui/shaders/UIShaders.h"
USING_NS_CC;

Scene* UVSpriteTest::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();

    // 'layer' is an autorelease object
    auto layer = UVSpriteTest::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

UVSpriteTest::UVSpriteTest()
{}

bool UVSpriteTest::init()
{
    //////////////////////////////
    // 1. super init first
    if (!Layer::init())
    {
        return false;
    }
    auto visibleSize = Director::getInstance()->getVisibleSize();

    /*auto background = Sprite::create("HelloWorld.png");
    background->setColor(Color3B(255, 0, 0));
    background->setPosition(Vec2(visibleSize / 2));
    addChild(background);*/

    //UVSprite * sprite = UVSprite::create("HelloWorld.png");
    UVSprite * sprite = UVSprite::create("ball.png");
    sprite->setPosition(Vec2(visibleSize / 2));
    //sprite->setPosition(Vec2(0, visibleSize.height / 2));
    //sprite->setScale(0.6f);
    addChild(sprite);
    //sprite->setUVTexture("3D/caustics.png");
    sprite->setUVTexture("snowflake2.png");
    sprite->setUvStretch(false);
    //sprite->setUvOpacity(0.5f); 
    sprite->setUvVelocity(Vec2(0.0f , -0.01f));
    sprite->setUvAlphaFilter(true);

    //auto foreground = Sprite::create("HelloWorld.png");
    //foreground->setColor(Color3B(0, 0, 255));
    //foreground->setPosition(Vec2(0, visibleSize.height / 2));
    //foreground->setScale(1);
    //addChild(foreground);

    return true;
}
View Code