第四章 8.2.2 动态设置着色器参数
这个例子看得时候很流畅,本以为会很容易调通。结果发现参数总是传不进去,卡了很久。最后请教了一位很有经验的同事,才找到问题所在。还是看实际代码介绍吧。
顶点着色器: dynamic_shader.vert
attribute vec4 a_position;
attribute vec4 a_color;
uniform vec3 u_center; // 注意类型
varying vec4 v_fragmentColor;
varying float v_high;
void main()
{
gl_Position = CC_PMatrix * a_position;
v_fragmentColor = a_color;
vec2 disV = vec2(a_position.x - u_center.x, a_position.y - u_center.y);
float dis = sqrt(disV.x*disV.x + disV.y*disV.y);
float high = (dis-u_center.z)/u_center.z;
v_high = high;
}
最开始的时候把uniform写成attribute了,编译的时候也不报错,但参数根本不会读取的。这是存储限定符,在166页写得很清楚。
片段着色器:dynamic_shader.frag
varying lowp vec4 v_fragmentColor;
varying lowp float v_high;
uniform vec4 u_highColor;
uniform vec4 u_lowColor;
void main(void)
{
if (v_high > 0.0)
{
gl_FragColor = v_fragmentColor * u_highColor * v_high;
}
else
{
gl_FragColor = v_fragmentColor * u_lowColor * -v_high;
}
vec4 white = vec4(1.,1.,1.,1.);
gl_FragColor = gl_FragColor + white*(1. - gl_FragColor.a)*white;
}
这两个文件可以当作素材放到res文件夹下,也可以像类一样引用,还有一种方法是直接以字符串的方式读入。
接下来是在HelloWorld这个场景中使用。这里就是出问题的地方了。
bool HelloWorld::init()
{
... ... // 略掉的原有内容
// 书中使用的是LayerColor 但如果不改变LayerColor的onDraw方法的话 是无法实现效果的
// LayerColor* pLayer = LayerColor::create(Color4B::WHITE);
// addChild(pLayer);
// 这里写了一个简单的LayerColor功能的类 在onDraw的时候添加了一行代码
DynamicShaderLayer* pLayer = DynamicShaderLayer::create();
addChild(pLayer);
Size size = Director::getInstance()->getWinSize();
_center = Vec2(size.width / 2, size.height / 2);
_halfDis = sqrtf(size.width * size.width + size.height * size.height) / 2;
// 没有小球 就用现有素材代替了
auto sprite = Sprite::create("HelloWorld.png");
sprite->setPosition(_center);
addChild(sprite, 10);
auto program = GLProgram::createWithFilenames("res/dynamic_shader.vert", "res/dynamic_shader.frag");
auto pstate = GLProgramState::create(program);
pLayer->setGLProgramState(pstate);
Color4B highColor = Color4B::RED;
Color4B lowColor = Color4B::GREEN;
pstate->setUniformVec4("u_highColor", Vec4(highColor.r / 255.0, highColor.g / 255.0, highColor.b / 255.0, highColor.a / 255.0));
pstate->setUniformVec4("u_lowColor", Vec4(lowColor.r / 255.0, lowColor.g / 255.0, lowColor.b / 255.0, lowColor.a / 255.0));
// 这个回调设置也和书中不一样,书中的参数表应该是弄错了
pstate->setUniformCallback("u_center", [this](GLProgram* p, Uniform* u)
{
p->setUniformLocationWith3f(u->location, _center.x, _center.y, _halfDis);
});
Device::setAccelerometerEnabled(true);
auto listener = EventListenerAcceleration::create([this, size, sprite](Acceleration* acc, Event* event)
{
_center.x += acc->x * 9.81f;
_center.y += acc->y * 9.81f;
if (_center.x < 0)
{
_center.x = 0;
}
else if (_center.x > size.width)
{
_center.x = size.width;
}
if (_center.y < 0)
{
_center.y = 0;
}
else if (_center.y > size.height)
{
_center.y = size.height;
}
sprite->setPosition(_center);
});
getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
return true;
}
如注释中所说,按照LayerColor的渲染方式是没有办法把参数传进去的。上面用到的DynamicShaderLayer代码如下:
DynamicShaderLayer.h
#include "cocos2d.h"
using namespace cocos2d;
class DynamicShaderLayer : public Node
{
public:
CREATE_FUNC(DynamicShaderLayer);
private:
virtual bool init() override;
virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;
void onDraw(const Mat4 &transform, uint32_t flags);
Color4F _squareColors[4];
CustomCommand _customCommand;
Vec3 _noMVPVertices[4];
};
DynamicShaderLayer.cpp
#include "DynamicShaderLayer.hpp"
bool DynamicShaderLayer::init()
{
if (!Node::init())
{
return false;
}
// 这里是LayerColor的默认着色器程序,在这个例子当中我们是要替换着色器程序的,所以注释掉也没有影响。
// setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_COLOR_NO_MVP));
return true;
}
void DynamicShaderLayer::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
Size s = Director::getInstance()->getWinSize();
_noMVPVertices[0] = Vec3(0.0, 0.0, 0.0);
_noMVPVertices[1] = Vec3(s.width, 0.0, 0.0);
_noMVPVertices[2] = Vec3(0.0, s.height, 0.0);
_noMVPVertices[3] = Vec3(s.width, s.height, 0.0);
_squareColors[0] = Color4F::WHITE;
_squareColors[1] = Color4F::WHITE;
_squareColors[2] = Color4F::WHITE;
_squareColors[3] = Color4F::WHITE;
_customCommand.init(getGlobalZOrder(), transform, flags);
_customCommand.func = CC_CALLBACK_0(DynamicShaderLayer::onDraw, this, transform, flags);
renderer->addCommand(&_customCommand);
}
void DynamicShaderLayer::onDraw(const Mat4 &transform, uint32_t flags)
{
getGLProgram()->use();
getGLProgram()->setUniformsForBuiltins(transform);
// 这里就是LayerColor没有的一行 这一句操作是会将参数传入GLProgram的
getGLProgramState()->applyUniforms();
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_COLOR );
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _noMVPVertices);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 0, _squareColors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
对照一下LayerColor的onDraw方法:
void LayerColor::onDraw(const Mat4& transform, uint32_t /*flags*/)
{
getGLProgram()->use();
getGLProgram()->setUniformsForBuiltins(transform);
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_COLOR );
//
// Attributes
//
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, 0, _noMVPVertices);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE, 0, _squareColors);
GL::blendFunc( _blendFunc.src, _blendFunc.dst );
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,4);
}
不知道书中的cocos版本是多少的,我看了3.0的和3.15.1的都是没有这一行的。不过如果我们这里不用LayerColor,用一个带纹理的Sprite,再换成同样的着色器程序,是可以看到参数能传过去的。那就说明3.0的QuadCommand 和 3.15.1的TrianglesCommand是可以带参数的。那位同事也是从这里找到线索,发现需要apply一下的。
另外,一个调试技能。XCode运行当中是可以通过“相机”这个按钮看GPU状态的,这就可以达到调试着色器的效果。
最终效果如下: