Creating a Texture with RenderTexture and apply Blur effect once

Creating a Texture with RenderTexture and apply Blur effect once
0

#1

Hi!

I need to create a Texture with what is currently on the screen (basically a Screenshot) and then I need to apply a blur effect to that texture so I can put it on the back of the scene and disabled all the other nodes behind of that (We need to improve the number of draw call for our game).

To take the screen shot I’m doing this:

         auto winSize = cocos2d::Director::getInstance()->getWinSize();
         auto renderTexture = cocos2d::RenderTexture::create(winSize.width, winSize.height,cocos2d::Texture2D::PixelFormat::RGBA8888, GL_DEPTH24_STENCIL8_OES);
         cocos2d::Scene* curScene = cocos2d::Director::getInstance()->getRunningScene();
         renderTexture->begin();
         curScene->visit();
         cocos2d::Director::getInstance()->getRenderer()->render();
         renderTexture->end();         
         return renderTexture;

Then I’m setting the Blur shader (I’m using the one on the Cocos2d Test example) using a material

    auto renderTexture = ImageUtils::GetScreenTexture();
    auto properties = cocos2d::Properties::createNonRefCounted("Materials/BlurMaterial.material");
    
    cocos2d::Material *mat1 = cocos2d::Material::createWithProperties(properties);
    
    renderTexture->setGLProgramState(mat1->getTechniqueByName("blur")->getPassByIndex(0)->getGLProgramState());
    
    return renderTexture;

But what worries me is that I need to apply the blur effect just once, for what I understand with my code above it will process the blur for every frame.

In Unity3d, there is an Util method from the Graphic class that helps to do this, basically is a blit where you can specify the material (with the texture already set) the Texture target and the index of the pass. On that way I just apply the blur effect just once. There is something similar on cocos2d?

Kind regards


#2

Hi. If you need to improve the number of draw call for game RenderTexture is not the solution because you need those calls for create your “screenshot” in buffer. Also fullscreen renderTexture is a time consuming process.

If you really want to decrease draw calls then you need to use sprite sheets. Cocos2d draws any number of sprites and nodes with same texture (sprite sheet) in one draw call.


#3

Hi amini13a!

Thanks for the advice, we’re doing that already. My objective with this is something very specific, in some cases we have a lot of UI elements in the screen and I just wanted to disable all the unused element at the back (for example a typical use case would be a confirmation popup when doing shopping). For that I would like to generate this blurred texture.

regards,

Micho


#4

Hmm… the only solution I see here is to create additional texture and render there previously created texture with shader :wink: But this is of course not a best solution. Anyone else? :slight_smile:


#5

Hi kds !

Thanks for your answer! In fact I did that… I created a render texture with a screenshot of what is on the camera and I applied the shader to that texture and then I created another render texture to Render the previous one. The only issue with that is painfully slow.

Kind regards,

Micho


#6

What do you mean by slow? Slow as in low frame rate (high frame time)? Or slow as in: the whole process takes a long time at once (i.e. a time significantly longer than the maximum frame time that has to be met in order to get the full frame rate)?

The Render Scene to Texture -> Render Scene Texture with blur to other Texture -> Render other texture way is the “best” way for this specific problem. If with slow you are refering to a noticeable freeze, then there are a few ways to mitigate this.

Sidenote: If you are using global z order values other than 0 for your nodes, you are going to run into problems with RenderTexture. RenderTexture currently only “captures” render commands that have the same global z order value as the RenderTexture.

You can optimize the process by doing the following:

1.) From the looks of your code snippets, you are seemingly rendering the scene twice during the frame you’re capturing the screenshot in. I don’t know how you implemented your Scene/SceneManagement but if your scene is a subclass of Node (Scene also works since it is itself a subclass of Node), you can do the following:

// Make sure the visit function is marked as overriden in your class definition
void YourSceneClassName::visit(cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t flags) {
   if (captureScreen) {
      sceneTexture->begin();
      // Call the super class visit function (Scene in this case)
      Scene::visit(renderer, transform, flags);
      sceneTexture->end();

      sceneTexture->getSprite()->setAnchorPoint(Vec2(0, 0));
      sceneTexture->getSprite()->setPosition(0, 0);
      sceneTexture->getSprite()->visit();
   } else {
      // Just delegate to superclass visit function
      Scene::visit(renderer, transform, flags);
   }
}

This way, the scene is only renderer once during the capture frame.

2.) Don’t know if you’re already doing this, but instead of capturing the scene and blurring it in the same frame, you could split this task up into two frames and thus reduce the noticed freeze. This is of course only possible if you’re okay with the blurred static image not being available during the same frame you requested it at. The process would be the following:

Frame 1: Capture scene to texture, render unmodified scene capture to screen
Frame 2: Blur the captured scene texture, render the result to screen
Frame 3 and following: Just render the blurred texture to screen

3.) Instead of a “real” blur you could try the downsample-then-upsample approach to blurring. During the blur pass, instead of rendering the scene image with a blur material (i.e. shader), render it normally but create the target RenderTexture with 1/16 of the width and height of the screen. When rendering the downsampled scene texture to screen, just scale it back up to full screen size. Make sure that linear filtering is enabled on the texture or else this won’t work. Also note that I just chose 1/16 for the sake of explaining. You have to experiment a bit with this value to get the best visual result.