转载自: http://www.cocoachina.com/bbs/read.php?tid-228797.html
shader部分另外加了注释
GLSL Shader特效-1
在游戏中很多特效可以通过美工等工具来实现。在此教大家如何通过GPU的shader来实现,让CPU打盹。
作为该系列的第一个教程,教大家如何让写一个遮罩特效。这样比较容易上手。在高级阶段教大家如何在shader中实现复杂的3D特效,包括光线追踪和体积渲染。
如果你对opengl、es没有基础,可以先放开不理会。你只需要了解C语言。
GLSL和C语音很相似,语法也比较简单。
GPU的shader编程对数学和物理的要求比较高,特别是立体几何和线性代数。
如果你已经把立体几何忘光了,后面可以补,现在要熟悉平面几何里的向量和矩阵、坐标系的变换。
作为第一个教程,还没涉及矩阵和坐标系变换。但如何你真的忘记了,要花点时间来复习下了,时间挤挤都会有的。
在开始之前先普及下GPU和CPU的区别:
两者的主要区别在于GPU是完全并行的,具有多个(例如512)逻辑运算单元,但计算能力不如CPU的逻辑运算单元;
CPU虽然有多核的,但一般不多于10 。一般都是 2、4、8核,多于16核的一般人都用不到。
在CPU的编程中你可以假设“邻居”数据而完成一些复杂的算法运算。但在GPU中数据是完全并行的,你不能假设其他数据,这也是
为什么在GPU中你永远看不到“求和”函数的存在,例如在GLSL中,你找不到类似sum的求和函数。
在CPU的多核和多线程、多进程的并行编程中,不是真正严格意义上的并行。因为只要涉及到数据的共享就会导致资源的竞争,
为了数据的一致性和安全,就必须采取加锁(读写锁、自旋锁、RCU等)或者线程等待,这些机制都将会导致某些进程、线程被迫
等待而被内核调度器挂起(或者主动放弃CPU),放在运行等待队列中,等待其他线程的唤起或者再次被内核调度器调度。
好比,茅坑被占了,即使后面有再多的人(线程或者进程)都必须要排队。因此,这就不是真正的并行了。可以说这是真正并行和串行产生的一个折中的处理机制。
GPU的设计是真正的并行设计。因此在编程的过程中你不能假设数据的先后顺序,也不允许访问其他线程的数据。例如sum求和就是一个最明显的例子。
GPU擅长向量的点积和叉积、矩阵变换、插值处理等。
在编写shader的时候,尽量避免使用:
float TWOPI = 3.14*2.0;//应该使用float TWOPI = 6.28;不要让GPU帮你这种已经知道固定值是多少的运算,尤其是浮点运算;
float radius = 10.0/2.0; //应该使用 float radius = 5.0;
float dist = radius / 2.0; //应该使用float dist = radius * 0.5; 尽量避免使用除法;
向量之间相乘,可以使用点积来代替,这是GPU的强项。
好的,开始我们的第一个shader特效教程-遮罩特效。
cocos2dx提供的clipnode可以用来实现遮罩特效。但是clipnode是使用OPENGL的模板测试来实现的。因此测试的结果只存在2种,即通过和不通过。
不存在中间状态。这就导致实现出来的遮罩效果不存在渐变的过度带。这和我们现实中看到明暗的不一致,因为明暗之间必定有过度带。好比晚上看到的
影子,有明到暗的渐变过程。当然你可以使用clipnode+blend混合来达到渐变的效果。但这样显得过于复杂了。
我们可以使用shader来实现,先看效果图:
正常
反向50%
该shader遮罩可以控制:大小、位置、遮罩的颜色、遮罩渐变大小、快慢、明暗程度。
下面是伪代码:
get_mask()
{
if (uv_2_centre_distance < radius) {
dd = uv_2_centre_distance /radius;
return smoothstep(0.0, gradient, pow(dd, brightness));
}
return 0.0;
}
void main()
{
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 tc = texture2D();
float mask = get_mask();
if (!inverted)
gl_FragColor = vec4(tc*mask*color);
else
gl_FragColor = vec4(tc*(1.0-inverted*mask*color));
}
其中:
radius:遮罩的半径大小;
gradient:遮罩的渐变速度;
brightness:遮罩的明暗;
color:是否改变被遮罩texture的颜色。
inverted:是否反向,反向的百分比是多少。
具体实现见源代码。
描述: 红色遮罩
图片:mask-inverted-00p-red.jpg
图片:mask-inverted-00p-red.jpg
下面是shaer源码部分:
// mask.fs:
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 coefficient; /* inverted, radius, gradient, brightness. */
uniform vec4 color;
uniform vec2 touchPoint;
uniform vec2 resolution;
uniform sampler2D CC_Texture0;
float getMask(float radius, vec2 pos, vec2 centre)
{
float dist = distance(pos, centre);
if (dist < radius) {
float dd = dist/radius;
return smoothstep(0.0, coefficient.z, 1.0 - pow(dd, coefficient.w));
}
return 0.0;
}
void main(void)
{
float aspe = resolution.x / resolution.y; //因为要保证非遮罩部分为圆形
vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0; // * 2.0 - 1.0 的目的是把 [0, 1] 变为 [-1, 1],我认为是没有必要的
vec2 pos = uv;
pos.x *= aspe;//因为要乘以一个比例,所以上面要把坐标范围从 [0, 1] 变为 [-1, 1]? 我认为是没有必要的
vec2 centre = (touchPoint.xy / resolution.xy) * 2.0 - 1.0;
centre.x *= aspe;
vec4 tc = texture2D(CC_Texture0, vec2(uv.x, 1.0-uv.y));//这里纹理坐标应该是有问题的,应该用原始 [0, 1]的值(gl_FragCoord.xy / resolution.xy)
float mask = getMask(coefficient.y, pos, centre);
if (coefficient.x == 0.0)
gl_FragColor = vec4(tc*mask*color);
else
gl_FragColor = vec4(tc*(1.0-coefficient.x*mask*color));
}
precision highp float;
#endif
uniform vec4 coefficient; /* inverted, radius, gradient, brightness. */
uniform vec4 color;
uniform vec2 touchPoint;
uniform vec2 resolution;
uniform sampler2D CC_Texture0;
float getMask(float radius, vec2 pos, vec2 centre)
{
float dist = distance(pos, centre);
if (dist < radius) {
float dd = dist/radius;
return smoothstep(0.0, coefficient.z, 1.0 - pow(dd, coefficient.w));
}
return 0.0;
}
void main(void)
{
float aspe = resolution.x / resolution.y; //因为要保证非遮罩部分为圆形
vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0; // * 2.0 - 1.0 的目的是把 [0, 1] 变为 [-1, 1],我认为是没有必要的
vec2 pos = uv;
pos.x *= aspe;//因为要乘以一个比例,所以上面要把坐标范围从 [0, 1] 变为 [-1, 1]? 我认为是没有必要的
vec2 centre = (touchPoint.xy / resolution.xy) * 2.0 - 1.0;
centre.x *= aspe;
vec4 tc = texture2D(CC_Texture0, vec2(uv.x, 1.0-uv.y));//这里纹理坐标应该是有问题的,应该用原始 [0, 1]的值(gl_FragCoord.xy / resolution.xy)
float mask = getMask(coefficient.y, pos, centre);
if (coefficient.x == 0.0)
gl_FragColor = vec4(tc*mask*color);
else
gl_FragColor = vec4(tc*(1.0-coefficient.x*mask*color));
}
// ShaderEffects.h:
/*
* Effects implemented by and only for fragment shader.
*
* xfw,2014-3.<xfw5@163.com>
*/
#ifndef _SHADER_EFFECTS_H_
#define _SHADER_EFFECTS_H_
#include "cocos2d.h"
USING_NS_CC;
class ShaderNode : public Node
{
public:
ShaderNode();
~ShaderNode();
void setResolution(const float& w, const float& h);
virtual void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) override;
protected:
bool initWithVertex(const std::string &vert, const std::string &frag);
void loadShaderVertex(const std::string &vert, const std::string &frag);
virtual void buildCustomUniforms() = 0;
virtual void setCustomUniforms() = 0;
void onDraw(const kmMat4 &transform, bool transformUpdated);
Vertex2F _resolution;
GLuint _uniformResolution;
std::string _vertFileName;
std::string _fragFileName;
CustomCommand _customCommand;
};
typedef struct _MaskParams {
_MaskParams(): inverted(0.0f), radius(0.0f), gradient(0.0f), \
brightness(0.0f), color(Color4F::WHITE) {}
_MaskParams(float i, float r, float g, float b, Color4F c) : inverted(i), \
radius(r), gradient(g), brightness(b), color(c) {}
/*
* Inverted mask, 0.0~1.0
* 0.0: un-inverted.
* 1.0: inverted.
* 0.5: 50% inverted, it make the inverted masker looks transparency.
* In an word, when @inverted not eq 0.0, it just like the opacity.
*/
GLfloat inverted;
/* masker radius, 0.0~1.0 */
GLfloat radius;
/* how fast falloff, greater than 0.5 is suggestion */
GLfloat gradient;
/* brightness of masker, greater than 3.0 is suggestion */
GLfloat brightness;
/*
* masker color, if you want to keep the orig texture color.
* Just set as white.
*/
Color4F color;
}MaskParams;
/*
* Usage:
* Texture2D::TexParams tParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
* MaskParams mParams = MaskParams(0.0f, 0.5f, 1.0f, 3.0f, Color4F::WHITE);
* auto maskEffect = MaskShaderEffect::create("common.vs", "mask.fs", maskBackground, tParams, true);
* maskEffect->setMaskParams(mParams);
* maskEffect->setPosition(VisibleRect::center()); // screen centre.
* this->addChild(maskEffect);
*/
class MaskShaderEffect:public ShaderNode
{
public:
/*
* @vs: cocos2dx common vertex shader.
* @fs: fragment shader.
* @maskBackground: background image being masked.
* @params: Texture params, how to mapping your texture.
* @touchCtrl: touch event support. Get the touch point as the masker centre.
*
* Note:
* The default contents size of @MaskShaderEffect is set as texture size.
* You can call @setResolution to change the viewport/enlarge the texture size.
*/
static MaskShaderEffect * create(const std::string &vs, const std::string &fs,
const std::string &maskBackground,
Texture2D::TexParams ¶ms, bool touchCtrl);
/* Setup the masker centre. */
void setMaskPosition(Point &p);
/* setup masker coefficient */
void setMaskParams(MaskParams ¶ms);
/* Touch event support. */
void enableTouchCtrl(bool enable);
MaskShaderEffect();
~MaskShaderEffect();
private:
bool initWithTexture(const std::string &texture, Texture2D::TexParams ¶ms);
virtual void buildCustomUniforms();
virtual void setCustomUniforms();
bool onTouchBegan(Touch* touches, Event* event);
#ifdef ES_30_SUPPORT
/* Interface blocks supported in GLSL ES 3.00 only. */
bool setupUBO(MaskParams ¶ms);
#endif
Vertex2F _touchPoint;
MaskParams _maskParams;
GLuint _uniformTouchPoint;
#ifdef ES_30_SUPPORT
GLuint _uniformMaskParams;
#else
GLuint _uniformCoefficient; /* hold: inverted, radius, gradient, brightness. */
GLuint _uniformColor;
#endif
GLuint _textureID;
Texture2D *_texture;
};
#endif
* Effects implemented by and only for fragment shader.
*
* xfw,2014-3.<xfw5@163.com>
*/
#ifndef _SHADER_EFFECTS_H_
#define _SHADER_EFFECTS_H_
#include "cocos2d.h"
USING_NS_CC;
class ShaderNode : public Node
{
public:
ShaderNode();
~ShaderNode();
void setResolution(const float& w, const float& h);
virtual void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) override;
protected:
bool initWithVertex(const std::string &vert, const std::string &frag);
void loadShaderVertex(const std::string &vert, const std::string &frag);
virtual void buildCustomUniforms() = 0;
virtual void setCustomUniforms() = 0;
void onDraw(const kmMat4 &transform, bool transformUpdated);
Vertex2F _resolution;
GLuint _uniformResolution;
std::string _vertFileName;
std::string _fragFileName;
CustomCommand _customCommand;
};
typedef struct _MaskParams {
_MaskParams(): inverted(0.0f), radius(0.0f), gradient(0.0f), \
brightness(0.0f), color(Color4F::WHITE) {}
_MaskParams(float i, float r, float g, float b, Color4F c) : inverted(i), \
radius(r), gradient(g), brightness(b), color(c) {}
/*
* Inverted mask, 0.0~1.0
* 0.0: un-inverted.
* 1.0: inverted.
* 0.5: 50% inverted, it make the inverted masker looks transparency.
* In an word, when @inverted not eq 0.0, it just like the opacity.
*/
GLfloat inverted;
/* masker radius, 0.0~1.0 */
GLfloat radius;
/* how fast falloff, greater than 0.5 is suggestion */
GLfloat gradient;
/* brightness of masker, greater than 3.0 is suggestion */
GLfloat brightness;
/*
* masker color, if you want to keep the orig texture color.
* Just set as white.
*/
Color4F color;
}MaskParams;
/*
* Usage:
* Texture2D::TexParams tParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
* MaskParams mParams = MaskParams(0.0f, 0.5f, 1.0f, 3.0f, Color4F::WHITE);
* auto maskEffect = MaskShaderEffect::create("common.vs", "mask.fs", maskBackground, tParams, true);
* maskEffect->setMaskParams(mParams);
* maskEffect->setPosition(VisibleRect::center()); // screen centre.
* this->addChild(maskEffect);
*/
class MaskShaderEffect:public ShaderNode
{
public:
/*
* @vs: cocos2dx common vertex shader.
* @fs: fragment shader.
* @maskBackground: background image being masked.
* @params: Texture params, how to mapping your texture.
* @touchCtrl: touch event support. Get the touch point as the masker centre.
*
* Note:
* The default contents size of @MaskShaderEffect is set as texture size.
* You can call @setResolution to change the viewport/enlarge the texture size.
*/
static MaskShaderEffect * create(const std::string &vs, const std::string &fs,
const std::string &maskBackground,
Texture2D::TexParams ¶ms, bool touchCtrl);
/* Setup the masker centre. */
void setMaskPosition(Point &p);
/* setup masker coefficient */
void setMaskParams(MaskParams ¶ms);
/* Touch event support. */
void enableTouchCtrl(bool enable);
MaskShaderEffect();
~MaskShaderEffect();
private:
bool initWithTexture(const std::string &texture, Texture2D::TexParams ¶ms);
virtual void buildCustomUniforms();
virtual void setCustomUniforms();
bool onTouchBegan(Touch* touches, Event* event);
#ifdef ES_30_SUPPORT
/* Interface blocks supported in GLSL ES 3.00 only. */
bool setupUBO(MaskParams ¶ms);
#endif
Vertex2F _touchPoint;
MaskParams _maskParams;
GLuint _uniformTouchPoint;
#ifdef ES_30_SUPPORT
GLuint _uniformMaskParams;
#else
GLuint _uniformCoefficient; /* hold: inverted, radius, gradient, brightness. */
GLuint _uniformColor;
#endif
GLuint _textureID;
Texture2D *_texture;
};
#endif
// ShaderEffects.cpp
/*
* Effects implemented by and only for fragment shader.
*
* xfw,2014-3.<xfw5@163.com>
*/
#include "ShaderEffects.h"
ShaderNode::ShaderNode()
:_resolution(Vertex2F(0.0f, 0.0f))
,_uniformResolution(0)
{
}
ShaderNode::~ShaderNode()
{
}
bool ShaderNode::initWithVertex(const std::string &vs, const std::string &fs)
{
loadShaderVertex(vs, fs);
_vertFileName = vs;
_fragFileName = fs;
return true;
}
void ShaderNode::setResolution(const float& w, const float& h)
{
_resolution = Vertex2F(w, h);
setContentSize(Size(w, h));
setAnchorPoint(Point(0.5f, 0.5f));
}
void ShaderNode::loadShaderVertex(const std::string &vs, const std::string &fs)
{
auto shader = new GLProgram();
shader->initWithFilenames(vs, fs);
this->setShaderProgram(shader);
shader->release();
CHECK_GL_ERROR_DEBUG();
shader->bindAttribLocation("a_position", GLProgram::VERTEX_ATTRIB_POSITION);
CHECK_GL_ERROR_DEBUG();
shader->link();
CHECK_GL_ERROR_DEBUG();
shader->updateUniforms();
CHECK_GL_ERROR_DEBUG();
_uniformResolution = shader->getUniformLocation("resolution");
this->buildCustomUniforms();
CHECK_GL_ERROR_DEBUG();
}
void ShaderNode::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
_customCommand.init(_globalZOrder);
_customCommand.func = CC_CALLBACK_0(ShaderNode::onDraw, this, transform, transformUpdated);
renderer->addCommand(&_customCommand);
}
void ShaderNode::onDraw(const kmMat4 &transform, bool transformUpdated)
{
auto shader = getShaderProgram();
shader->use();
shader->setUniformsForBuiltins(transform);
shader->setUniformLocationWith2f(_uniformResolution, _resolution.x, _resolution.y);
//glUniform1f(_uniformTime, _time); // We uses the build-in @CC_Time[4]
this->setCustomUniforms();
GL::enableVertexAttribs( cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION );
/* Draw viewport with two triangles */
GLfloat vertices[12] = {0, 0, _resolution.x, 0, _resolution.x, _resolution.y,
0, 0, 0, _resolution.y, _resolution.x,_resolution.y};
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glDrawArrays(GL_TRIANGLES, 0, 6);
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,6);
}
MaskShaderEffect::MaskShaderEffect()
:_touchPoint(Vertex2F(0.0f, 0.0f))
,_uniformTouchPoint(0)
,_uniformCoefficient(0)
,_maskParams()
,_textureID(0)
,_texture(NULL)
{
}
MaskShaderEffect::~MaskShaderEffect()
{
}
MaskShaderEffect* MaskShaderEffect::create(const std::string &vs, const std::string &fs,
const std::string &maskBackground,
Texture2D::TexParams ¶ms, bool touchCtrl)
{
MaskShaderEffect *sn = new MaskShaderEffect();
if (sn && sn->init()) {
sn->initWithTexture(maskBackground, params);
sn->initWithVertex(vs, fs);
sn->enableTouchCtrl(touchCtrl);
sn->autorelease();
return sn;
}else {
delete sn;
sn = NULL;
return NULL;
}
}
bool MaskShaderEffect::initWithTexture(const std::string &texture, Texture2D::TexParams ¶ms)
{
_texture = Director::getInstance()->getTextureCache()->getTextureForKey(texture);
if (_texture) {
_textureID = _texture->getName();
if (params.minFilter == GL_LINEAR_MIPMAP_LINEAR)
_texture->generateMipmap();
_texture->setTexParameters(params);
float w = _texture->getContentSize().width;
float h = _texture->getContentSize().height;
/*
* Set the shader viewport as the texture contents size.
* You can set as the screen size, like:
* setResolution(screen.width, screen.height);
* At this solution, the texture will mapping as what you have
* set before,like { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
* So, the texture params is important.
*/
this->setResolution(w, h);
this->setMaskPosition(Point(w*0.5f,h*0.5f));
return true;
}
log("Please make sure texture[%s] was pre-load to the cache.", texture);
return false;
}
void MaskShaderEffect::setMaskPosition(Point &p)
{
Point q = convertToNodeSpaceAR(p);
if (abs(q.x) > _resolution.x*0.5 ||
abs(q.y) > _resolution.y*0.5)
log("warning: mask position out of contents size!");
/* We setup the @_touchPoint as the mask position. */
_touchPoint.x = p.x;
_touchPoint.y = p.y;
}
/*
* UBO:
* uniform MaskParams{
* float radius;
* float gradient;
* float brightness;
* vec4 color;
* };
*/
#ifdef ES_30_SUPPORT
bool MaskShaderEffect::setupUBO(MaskParams & params)
{
GLuint program = getShaderProgram()->getProgram();
GLuint blockIdx = glGetUniformBlockIndex(program, "MaskParams");
GLint blockSize;
glGetActiveUniformBlockiv(program, blockIdx, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
GLubyte *blockBuffer = (GLubyte*)malloc(blockSize);
if (!blockBuffer)
return;
else
memset(blockBuffer, 0, sizeof(blockBuffer));
const GLchar *name[] = {"radius", "gradient", "brightness", "color"};
GLuint indices[4]; // indices[ARRAY_SIZE(name)];
glGetUniformIndices(program, 4, name, indices);
GLint offset[4];
glGetActiveUniformsiv(program, 4, indices, GL_UNIFORM_OFFSET, offset);
memcpy(blockBuffer+offset[0], ¶ms.radius, sizeof(float));
memcpy(blockBuffer+offset[1], ¶ms.gradient, sizeof(float));
memcpy(blockBuffer+offset[2], ¶ms.brightness, sizeof(float));
memcpy(blockBuffer+offset[3], ¶ms.color, 4*sizeof(float));
GLuint uboBuffer;
glGenBuffers(1, &uboBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, uboBuffer);
glBufferData(GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_STATIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, blockIdx, uboBuffer);
free(blockBuffer);
glDeleteBuffers(1, &uboBuffer);
}
#endif
void MaskShaderEffect::setMaskParams(MaskParams ¶ms)
{
memcpy(&_maskParams, ¶ms, sizeof(params));
#ifdef ES_30_SUPPORT
if (!setupUBO(params))
log("warning:setup UBO failed!");
#endif
}
bool MaskShaderEffect::onTouchBegan(Touch* touch, Event* event)
{
auto point = touch->getLocation();
this->setMaskPosition(point);
return true;
}
void MaskShaderEffect::enableTouchCtrl(bool enable)
{
if (enable) {
auto dispatcher = Director::getInstance()->getEventDispatcher();
auto listener = EventListenerTouchOneByOne ::create();
listener->onTouchBegan = CC_CALLBACK_2(MaskShaderEffect::onTouchBegan, this);
dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}
}
void MaskShaderEffect::buildCustomUniforms()
{
GLProgram *shader = getShaderProgram();
_uniformTouchPoint = shader->getUniformLocation("touchPoint");
#ifndef ES_30_SUPPORT
_uniformCoefficient = shader->getUniformLocation("coefficient");
_uniformColor = shader->getUniformLocation("color");
#endif
}
void MaskShaderEffect::setCustomUniforms()
{
GLProgram *shader = getShaderProgram();
shader->setUniformLocationWith2f(_uniformTouchPoint, _touchPoint.x, _touchPoint.y);
#ifndef ES_30_SUPPORT
shader->setUniformLocationWith4f(_uniformCoefficient, _maskParams.inverted, \
_maskParams.radius, _maskParams.gradient, _maskParams.brightness);
shader->setUniformLocationWith4f(_uniformColor, _maskParams.color.r, \
_maskParams.color.g, _maskParams.color.b, _maskParams.color.a);
#endif
if (_textureID != 0) {
/* cocos2dx alway binding GL_TEXTURE0 with 2d samlper/CC_Texture0. */
glActiveTexture(GL_TEXTURE0);
GL::bindTexture2D(_textureID);
}
}
* Effects implemented by and only for fragment shader.
*
* xfw,2014-3.<xfw5@163.com>
*/
#include "ShaderEffects.h"
ShaderNode::ShaderNode()
:_resolution(Vertex2F(0.0f, 0.0f))
,_uniformResolution(0)
{
}
ShaderNode::~ShaderNode()
{
}
bool ShaderNode::initWithVertex(const std::string &vs, const std::string &fs)
{
loadShaderVertex(vs, fs);
_vertFileName = vs;
_fragFileName = fs;
return true;
}
void ShaderNode::setResolution(const float& w, const float& h)
{
_resolution = Vertex2F(w, h);
setContentSize(Size(w, h));
setAnchorPoint(Point(0.5f, 0.5f));
}
void ShaderNode::loadShaderVertex(const std::string &vs, const std::string &fs)
{
auto shader = new GLProgram();
shader->initWithFilenames(vs, fs);
this->setShaderProgram(shader);
shader->release();
CHECK_GL_ERROR_DEBUG();
shader->bindAttribLocation("a_position", GLProgram::VERTEX_ATTRIB_POSITION);
CHECK_GL_ERROR_DEBUG();
shader->link();
CHECK_GL_ERROR_DEBUG();
shader->updateUniforms();
CHECK_GL_ERROR_DEBUG();
_uniformResolution = shader->getUniformLocation("resolution");
this->buildCustomUniforms();
CHECK_GL_ERROR_DEBUG();
}
void ShaderNode::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
_customCommand.init(_globalZOrder);
_customCommand.func = CC_CALLBACK_0(ShaderNode::onDraw, this, transform, transformUpdated);
renderer->addCommand(&_customCommand);
}
void ShaderNode::onDraw(const kmMat4 &transform, bool transformUpdated)
{
auto shader = getShaderProgram();
shader->use();
shader->setUniformsForBuiltins(transform);
shader->setUniformLocationWith2f(_uniformResolution, _resolution.x, _resolution.y);
//glUniform1f(_uniformTime, _time); // We uses the build-in @CC_Time[4]
this->setCustomUniforms();
GL::enableVertexAttribs( cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION );
/* Draw viewport with two triangles */
GLfloat vertices[12] = {0, 0, _resolution.x, 0, _resolution.x, _resolution.y,
0, 0, 0, _resolution.y, _resolution.x,_resolution.y};
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glDrawArrays(GL_TRIANGLES, 0, 6);
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,6);
}
MaskShaderEffect::MaskShaderEffect()
:_touchPoint(Vertex2F(0.0f, 0.0f))
,_uniformTouchPoint(0)
,_uniformCoefficient(0)
,_maskParams()
,_textureID(0)
,_texture(NULL)
{
}
MaskShaderEffect::~MaskShaderEffect()
{
}
MaskShaderEffect* MaskShaderEffect::create(const std::string &vs, const std::string &fs,
const std::string &maskBackground,
Texture2D::TexParams ¶ms, bool touchCtrl)
{
MaskShaderEffect *sn = new MaskShaderEffect();
if (sn && sn->init()) {
sn->initWithTexture(maskBackground, params);
sn->initWithVertex(vs, fs);
sn->enableTouchCtrl(touchCtrl);
sn->autorelease();
return sn;
}else {
delete sn;
sn = NULL;
return NULL;
}
}
bool MaskShaderEffect::initWithTexture(const std::string &texture, Texture2D::TexParams ¶ms)
{
_texture = Director::getInstance()->getTextureCache()->getTextureForKey(texture);
if (_texture) {
_textureID = _texture->getName();
if (params.minFilter == GL_LINEAR_MIPMAP_LINEAR)
_texture->generateMipmap();
_texture->setTexParameters(params);
float w = _texture->getContentSize().width;
float h = _texture->getContentSize().height;
/*
* Set the shader viewport as the texture contents size.
* You can set as the screen size, like:
* setResolution(screen.width, screen.height);
* At this solution, the texture will mapping as what you have
* set before,like { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
* So, the texture params is important.
*/
this->setResolution(w, h);
this->setMaskPosition(Point(w*0.5f,h*0.5f));
return true;
}
log("Please make sure texture[%s] was pre-load to the cache.", texture);
return false;
}
void MaskShaderEffect::setMaskPosition(Point &p)
{
Point q = convertToNodeSpaceAR(p);
if (abs(q.x) > _resolution.x*0.5 ||
abs(q.y) > _resolution.y*0.5)
log("warning: mask position out of contents size!");
/* We setup the @_touchPoint as the mask position. */
_touchPoint.x = p.x;
_touchPoint.y = p.y;
}
/*
* UBO:
* uniform MaskParams{
* float radius;
* float gradient;
* float brightness;
* vec4 color;
* };
*/
#ifdef ES_30_SUPPORT
bool MaskShaderEffect::setupUBO(MaskParams & params)
{
GLuint program = getShaderProgram()->getProgram();
GLuint blockIdx = glGetUniformBlockIndex(program, "MaskParams");
GLint blockSize;
glGetActiveUniformBlockiv(program, blockIdx, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
GLubyte *blockBuffer = (GLubyte*)malloc(blockSize);
if (!blockBuffer)
return;
else
memset(blockBuffer, 0, sizeof(blockBuffer));
const GLchar *name[] = {"radius", "gradient", "brightness", "color"};
GLuint indices[4]; // indices[ARRAY_SIZE(name)];
glGetUniformIndices(program, 4, name, indices);
GLint offset[4];
glGetActiveUniformsiv(program, 4, indices, GL_UNIFORM_OFFSET, offset);
memcpy(blockBuffer+offset[0], ¶ms.radius, sizeof(float));
memcpy(blockBuffer+offset[1], ¶ms.gradient, sizeof(float));
memcpy(blockBuffer+offset[2], ¶ms.brightness, sizeof(float));
memcpy(blockBuffer+offset[3], ¶ms.color, 4*sizeof(float));
GLuint uboBuffer;
glGenBuffers(1, &uboBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, uboBuffer);
glBufferData(GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_STATIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, blockIdx, uboBuffer);
free(blockBuffer);
glDeleteBuffers(1, &uboBuffer);
}
#endif
void MaskShaderEffect::setMaskParams(MaskParams ¶ms)
{
memcpy(&_maskParams, ¶ms, sizeof(params));
#ifdef ES_30_SUPPORT
if (!setupUBO(params))
log("warning:setup UBO failed!");
#endif
}
bool MaskShaderEffect::onTouchBegan(Touch* touch, Event* event)
{
auto point = touch->getLocation();
this->setMaskPosition(point);
return true;
}
void MaskShaderEffect::enableTouchCtrl(bool enable)
{
if (enable) {
auto dispatcher = Director::getInstance()->getEventDispatcher();
auto listener = EventListenerTouchOneByOne ::create();
listener->onTouchBegan = CC_CALLBACK_2(MaskShaderEffect::onTouchBegan, this);
dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}
}
void MaskShaderEffect::buildCustomUniforms()
{
GLProgram *shader = getShaderProgram();
_uniformTouchPoint = shader->getUniformLocation("touchPoint");
#ifndef ES_30_SUPPORT
_uniformCoefficient = shader->getUniformLocation("coefficient");
_uniformColor = shader->getUniformLocation("color");
#endif
}
void MaskShaderEffect::setCustomUniforms()
{
GLProgram *shader = getShaderProgram();
shader->setUniformLocationWith2f(_uniformTouchPoint, _touchPoint.x, _touchPoint.y);
#ifndef ES_30_SUPPORT
shader->setUniformLocationWith4f(_uniformCoefficient, _maskParams.inverted, \
_maskParams.radius, _maskParams.gradient, _maskParams.brightness);
shader->setUniformLocationWith4f(_uniformColor, _maskParams.color.r, \
_maskParams.color.g, _maskParams.color.b, _maskParams.color.a);
#endif
if (_textureID != 0) {
/* cocos2dx alway binding GL_TEXTURE0 with 2d samlper/CC_Texture0. */
glActiveTexture(GL_TEXTURE0);
GL::bindTexture2D(_textureID);
}
}