学习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__
#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); }
#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__
#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; }