Shader Positioning

I found a shader that produces a cool lazer:: https://www.shadertoy.com/view/XtBXW3

I want to use the shader as a weapon in my horizontal shoot em up.

What is the best way to accomplish this?

Ideally I want a Sprite of arbitrary textureSize that the shader renders to, and move this sprite around the scene with setPosition(x,y) like any other sprite.

I tried by creating a Sprite and setting its GLProgram and it looks as expected, but when I move the sprite vertically the lazer beam stays at the bottom of the screen.

I looked at examples in cpptests and found a ShaderNode class. It overrides Node::setPosition() and has the behaviour I want by using a center uniform. So I added a center uniform to the lazer shader and now I can move the lazer beam vertically. But I found it doesn’t handle zoom (glview->setFrameZoomFactor()), so I altered ShaderNode::setPosition() to multiply position by frameZoomFactor but I also had to multiply the textureSize by frameZoomFactor in ShaderNode::initWithVertex(). Everything looked good until I tried on a device with letterboxing, so I altered ShaderNode::setPosition() to use viewPort width/height instead of frameSize, and also multiply the letterbox size by frameZoomFactor.

Finally it is works as expected, but there has to be a simpler way. I feel like I shouldn’t have to use a center uniform because I don’t want the lazer beam moving within the texture space, instead I want to move the entire texture/sprite around the screen. Please excuse my OpenGL ignorance.

1 Like

The two things I’d point out is you should only have to change the sprite’s position and the beam should move around (the generated texture from the shader shouldn’t change with sprite’s position, unless you’re modifying the standard vertex gl_Position assignment).

I can’t help make the shader perfect without spending more time and knowing what output you actually want, but here’s a test that seems to work as you want.

This might be the similar to the final code you ended up writing, but you can just change the position of the sprite as normal and the shader will display as expected. This specific shader has an iResolution variable that is unique to this shader and is only relevant to this specific shader, or some set of shadertoy shaders.

ShaderToy examples can usually be ported over successfully, but you do have to remember those are shaders essentially using a full resolution of a frame buffer, specifically for “full screen” demos.

Any excess/verbose code is due to how I test or reformat code for the forum.

using std::string;
// custom shader similar to alpha test, but with normals and lighting
string kShaderKey("laser_rainbow");
auto glCache = GLProgramCache::getInstance();
auto prog = glCache->getGLProgram(kShaderKey.c_str());
if(! prog)
{
    prog = GLProgram::createWithFilenames("res/" + kShaderKey + ".vert",
                                          "res/" + kShaderKey + ".frag");
    glCache->addGLProgram(prog, kShaderKey);
}

// This is the display size of the quad where for the render beam in pixels
// you should be able to make this any size you want and get 
// a longer/short/wider beam
Size texSize(400,80);

// empty white quad with 2x2 pixel texture
auto sprite = Sprite::create(); 
sprite->setPosition(Vec2(250,150));
// you should be able to anchor anywhere, 
sprite->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
// expand the quad vertices to cover this rect
// since the uv coords, including wrapping, are specific this cannot 
// be changed without affecting display
sprite->setTextureRect(Rect(0, 0, texSize.width, texSize.height));
// looked better with additive :) 
sprite->setBlendFunc(BlendFunc::ADDITIVE);


//auto glprogram = GLProgram::createWithFilenames("water.vsh", "water.fsh");
auto glprogramstate = GLProgramState::getOrCreateWithGLProgram(prog);
sprite->setGLProgramState(glprogramstate);

// note: iResolution is only "size of screen" because shadertoy applys to full view quad
// apparently the shader prefers getting this iResolution at half size in my testing
auto scale_factor = Director::getInstance()->getContentScaleFactor();
Vec3 iResolution(texSize.width/(2.f / scale_factor),
                 texSize.height/(2.f / scale_factor),
                 scale_factor);
glprogramstate->setUniformVec3("iResolution", );


// quick movement test to confirm
auto move = MoveBy::create(5.0f, Vec2(100,100));
auto seq = Sequence::create(move, move->reverse(), NULL);
sprite->runAction(RepeatForever::create(seq));
parent->addChild(testSprite);

This may still not be the result you want, but it seems to work for me ??

Edit: the shader was mostly ported verbatim.

//vert
varying float iGlobalTime;
// inside main (you can test the other 0-3 array index)
iGlobalTime = CC_Time[1]; 
// frag
uniform vec3 iResolution;
varying float iGlobalTime;

Thanks for the reply!!! My issue must be the vertex shader. I didn’t create my own vertex shader, I was using the default one in the ShaderNode class from cpptests, which is ‘ccPositionTextureColor_vert’ and it looks like this:

void main()
{
    gl_Position = CC_MVPMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
}

Using this vertex shader with your exampe code produces the same effect I saw before: the sprite moves but the lazer beam stays at the bottom.

What should my vertex shader look like? What is the standard vertex gl_Position assignment?

The issue looks like you’re using gl_FragCoord as input to the shader methods instead of v_texCoord

v_texCoord - has the interpolated texture coordinate with respect to the triangle being rendered
gl_FragCoord - contains the window-relative coordinates of the current fragment

Change your fragment shader to use v_texCoord instead.

void main(void)
{
    mainImage(gl_FragColor.rgba, v_texCoord); // gl_FragCoord.xy);
    gl_FragColor += vec4(0.6,0,0,0.6);
}

Full source

Edit: fixed CC_PMatrix as noted by red

1 Like

Ahhh YES that was the problem, due to my lack of basic openGL knowledge. Now I can move the sprite around and the texture doesn’t change appearance. The sprite positioning is off because your vert shader is multiplying a_position by CC_MVPMatrix, maybe that was intentional idk. But switching to multiplying by CC_PMatrix fixes that and its working exactly as desired. I will try to upload a video tonight of it in action :slight_smile:

THANKS SO MUCH MR. TRANBY. YOU’RE THE MANBY!!!

You’re welcome. Thanks for the reminder that Sprites use the “noMVP” CC_PMatrix instead of CC_MVPMatrix. I’m not sure which shader I copied from to test, but whoops, hehe.

And here it is in action: https://www.youtube.com/watch?v=QDI0ZEnmkTI&feature=youtu.be

I’m trying a mechanic where the lazer stops at the first enemy with a different color. I scaled the shader sprite to cover the width of the screen, then check every frame for the closest enemy of different color within a certain height and use a clipping node to shorten it if needed.

1 Like