Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

[with WebGL demo & sample package] what is most simple way to do per object post process on mobile?

Discussion in 'Shaders' started by colin299, Jan 5, 2016.

  1. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176
    WebGL demo:
    https://goo.gl/lNDoYc (Click this to preview the effect)

    Sample package:
    https://goo.gl/NAoSCQ (Click to download)


    the package contain minimum code to produce the effect in the WebGL demo.

    My process of rendering per object post process is:
    1.render the scene to a RenderTexture
    2.then render back to frame buffer with a full screen post process by reading the result in RenderTexture
    3.finally render any per object post process layer on top of frame buffer result by reading the result in RenderTexture.

    if I build to mobile device (I only have apple devices in test lab),
    for OpenGLES 2.0 apple device : it fails to render any per object post process,
    but full screen post process works on all devices.

    for OpenGLES 3.0 / Metal apple device : fully functional

    So I wonder what is the proper way to do per object post process on mobile device in general?
    which use minimum GPU pixel processing time and also stable on all OpenGLES 2.0 devices?

    ---------
    Extra:
    I also tried the following method:
    1.render the scene to a RenderTexture
    2.render all per object post process back to the RenderTexture itself,
    3.then render RenderTexture to frame buffer with a full screen post process

    but as it is reading & writng the RenderTexture at the same time in step 2, the render result has many artifacts.

    in order to solve the read write problem, here is another try:
    1.render the scene to a RenderTexture
    2.copy RenderTexture to RenderTextureReadOnly
    2.by reading RenderTextureReadOnly, render all per object post process to the RenderTexture itself,
    3.then render RenderTexture to frame buffer with a full screen post process

    it solves the problem, all OpenGLES 2.0 devices works with per object post process.
    but it use 2 RenderTexture memory & copying between RenderTexture in step 2 takes a lots of time.
    so it is not the solution.

    Hope someone can give advice about the correct way to do per object post process on OpenGLES 2.0 devices.

    Thank you!
     
  2. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176
    bump, still no idea about how to do per object post process on mobile, as shadowgun do it very well, there must be a solution.
     
  3. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,782
    which effect in shadowgun that you referring?
     
  4. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176
    page 32-24 of this pdf by shadowgun
    http://blogs.unity3d.com/wp-content/uploads/2011/09/Shadowgun_Unite2011.pdf

    shadowgun do the following:
    1.render the scene to a rendertexture,
    2.then use a full screen fit size plane(30x25 vertices) to render this rendertexture to the frame buffer.(with modification to UV in vertex shader)

    I can do what shadowgun is currently doing, full screen postprocess with 60fps in iphone4S (full resolution),because the pixel shader is very simple(just sample the render texture, every FX is done in vertex shader)

    --------------
    but what I really want to do, is per object post process, not full screen.(e.g. invert color behind a cube,similar to the webGL demo in the first post)
    the idea is add 1 more step to what shadowgun is doing:
    1.render the scene to a rendertexture, without rendering objects that needs post process.
    2.render the rendertexture to framebuffer with any post process needed(e.g. curve lens)
    3.use the rendertexture again to render every object that needs the post process to the framebuffer.
    but the object that needs post process will not be affected by step2-full screen post process, so I try the following method

    1.render the scene to a rendertexture, without rendering objects that needs post process.(same)
    2.use the rendertexture to render every object that needs the post process to the rendertexture itself.(read & write at the same time)
    3.render the rendertexture back to frame buffer with any full screen postprocess.
    but step2 will create artifacts in openGLES 2.0 devices ,in order to not produce artifacts, I tried the following method.

    1.render the scene to a RenderTexture
    2.copy RenderTexture to a new rendertexture "RenderTextureReadOnly"
    2.by reading "RenderTextureReadOnly", render all per object post process to the RenderTexture itself,
    3.then render RenderTexture to frame buffer with any full screen post process

    it solves the problem, all OpenGLES 2.0 devices works with per object post process.
    but it use 2 RenderTexture memory & copying between RenderTexture in step 2 takes a lots of time.
    so it is not the solution.

    I am a bit lost now, but I still beileve there is a good solution.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,208
    You could limit yourself to only doing "post process" effects that can be replicated with blend modes, invert, color tint, etc., no warping, desaturation, or color shifting.

    You could forgo the final full screen post doing anything to the per object post. Render scene to render texture. Render full screen post directly to frame buffer, render per-object post directly to frame buffer. If you have full screen post you can do as blend mode (i.e.: a vignette) do that after.

    Try using a named grab pass, basically the same as what you're doing but handled internally. Most of the time people say this is slower, but worth a try.

    Also, just to make sure, you're copying the render texture using Blit and not GetPixels, yes?
     
  6. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176
    Thanks for the reply!

    -The main reason why I want a per object post-process, mainly because I need to do object "shockwave" on mobile. So the blending method you mentioned although it is good, but not suitable in my case.
    -grab pass is slow, unity's official developer recommended render to a rendertexture manually. I tried it before, it is slower(even slower if it triggers CPU read screen)
    -yes, I use Blit(GPU) / DrawMeshNow(GPU), not GetPixels(CPU)

    I know how to make a very fast screen shockwave solution, but it is really "screen" level distortion
    The vidoe below is the final result


    but I am looking for a solution for "object" level postprocess(distortion, blur, etc), which can be blocked by depth.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,208
    By default image effects & blits are done with a quad that is at the near plane depth with no ztest, but there's no reason it has to be. You could push the position of the plane back in world space to be beyond the objects you don't want to be affected and use Ztest Lequal. It'll still appear to distort the edges some since it'll still have those objects in them, but it won't distort the center.

    You might be able to do this with just modifying the post transformed clip space z value, but I forget the math to position it accurately.
     
  8. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176
    yes that's true, I tried the method you mentioned in the past. There is one problem that I have no idea how to solve.

    In short, "I can not render that postprocess object to a RenderTexture while read the same RenderTexture at the same time(It produce unexpected artifacts on different GPU)".

    detail steps :
    1.render the scene to a RenderTexture
    2.render all per object post process back to the RenderTexture itself (this is the problem)
    3.then render RenderTexture to frame buffer with a full screen post process

    as step 2 is reading & writng the RenderTexture at the same time, the render result has many artifacts.
    -------------------------------------------
    In order to solve this "read&write" problem, I have an Idea, let's just call it "ping-pong" solution:
    prepare RenderTexture-A & RenderTextureB

    Frame n:
    0.discard RenderTexture-A 's content
    1.render the scene to RenderTexture-A
    2.render all per object post process to RenderTexture-A, by reading RenderTexture-B
    3.Blit RenderTexture-A to framebuffer with any post process material

    Frame n+1:
    4.discard RenderTexture-B 's content
    5.render the scene to RenderTexture-B
    6.render all per object post process to RenderTexture-B, by reading RenderTexture-A
    7.Blit RenderTexture-B to framebuffer with any post process material

    8.back to step 0
    --------------------------------------------
    This "ping-pong" solution require 2 RenderTexture's memory, instead of 1.
    And the texture read by per object postprocess will be 1 frame late.
    Does this sound reasonable? or there exists a much better solution?
     
    Last edited: Aug 15, 2016
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,208
    If you're paying the cost of an additional blit and copy how is that any better than copying the current frame and reusing it immediately? I can't see how ping ponging between two frames will help at all with the way Unity handles stuff.

    So one last thought.

    Render the objects you don't want affected after the image effect is drawn using DrawMeshNow? If your objects are always going to be on top it might "just work", or you might need to call GL.clear() to clear the depth. Or the depth buffer might remain intact and it'll do proper intersections.
     
  10. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176
    That ping-pong solution do not have any extra blit.
    In every frame, the time cost should be the same with other solutions, it just use 2x memory.

    0.discard "current RenderTexture"'s content (this step is a must for any mobile postprocess)
    1.render scene to "current RenderTexture" (this step is a must for any postprocess)
    2.draw the objects needed to postprocess to "current RenderTexture", by reading from "old RenderTexture"(which keeps 1 frame late content)
    3.blit "current RenderTexture" to framebuffer (this step is a must for any postprocess)

    The ping-pong solution just swap "current RenderTexture" & "old RenderTexture" each frame.
    Maybe I misunderstand something about Graphics at a basic level... because I have tried my best to describe the solution.

    It is very similar to any blur postprocess, which ping-pong between 2 render textures.
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,208
    No, you're right about ping pong. I just didn't fully think it through. My post-post drawing idea still stands though.
     
  12. Rusfighter

    Rusfighter

    Joined:
    Jan 18, 2014
    Posts:
    60

    Could you share the code you achieving the screen shockwave post effect. I am specially interested how you create the mesh and render it to framebuffer.
     
  13. colin299

    colin299

    Joined:
    Sep 2, 2013
    Posts:
    176