[Help] Theorizing Motion Blur
Blitz3D Forums/Blitz3D Programming/[Help] Theorizing Motion Blur
| ||
Hello everyone, After watching some videos of the currently-in-work Project Offset - which, by the way, looks to be awesomely-eye-candily-gorgeous - I was intrigued on how they managed to achieve a real-time motion blur solution "based on passes". It looks great, obviously, and I was enticed to find out how to achieve a similar result by using our all-familiar Blitz3D. I know what you are going to say to me, "search in the code archives", as there are some (allegedly) motion blur simulations. What these really are, are actually Slow-Motion type of effects, or Ghosting, if you will. There hasn't been any true Motion Blur simulation - so far, these attempts waste processor time (ok, not so much), and aren't that much good looking either. What I come to ask of you, is some pseudo-code as to what to do to accomplish a portraial of true-motion blur. So far, I've managed to think about having a series of plates (same as Quads or Sprites), all in the same physical position, but with 'complementary' alphas ( meaning each plate will have an alpha of 1/number of plates ). The first plate will always have the current render, the second will have the previous frame, the third, the frame before that, etc. - all of them adding up to create an alpha 1 solid picture, but with the motion blur effect. Well, in theory, that would work great, but when I tried to actually do it, let's just say I "epically failed". Any comments/suggestions/acknowledgements/jokes? |
| ||
Okay, here's what I got so far, using the Plate theory (i'm just using 2, though): I guess no matter what you try, you'll always end up with those "ghosting trails". |
| ||
Project Offset uses shaders. I think they are transforming the meshes and then blurring the textures in the direction of movement. Atleast, that is one way of doing proper motion blur. Your "plate" technique is the default one on Blitz3D, but doesn't work that well as you can see. |
| ||
I think what I really want lies in the good usage of the Tween parameter of the Renderworld() function. Using different values to obtain the necessary frames to average, instead of using the ones which are being shown to the user. I wonder if rendering n times the screen will slow down the whole application. Some examples of CubeMap do 7 renders per loop (6 for the sides of the cubes, + 1 to show the screen) and with a nice FPS, so I guess we'll be able to do something like 4 passes of tweening to average and get the motion blur - thus utilizing the Accumulation Method. |
| ||
Okay, next tryout, this time using render Tweening to multisample each frame: That's using 8 plates. My computer is slow, so I got a 10 FPS average. Looks like a no-no for realtime gameplay. With 3 plates, you get less Motion Blur but at least the framerate goes up to 63! Gonna try it out on some 3D level. Post more pics later. |
| ||
It's starting to look really good though. I'd like to see a texture version as well. Even if this will never be fast enough for realtime gaming, perhaps you can use it for slow-motion action shots!? Might be cool ;) |
| ||
If you want 40fps for a game with 4 plates that means the computer must be fast enough to render it at about 160fps, so motion blur using this technique is a really slow way of doing it.. You can also use the previous frames etc but the result is just not that good. |
| ||
I made a simple mesh transform motion blur. If someone would add a texture blur to this it might look okay. I'm not fully sure if my code is perfect though. The object is simply scaled in the direction of movement.Graphics3D 800,600,0,2 SetBuffer BackBuffer() cam = CreateCamera() MoveEntity cam,0,0,-14 cube = CreateCube() EntityShininess cube,0.8 light = CreateLight(3) MoveEntity light,100,100,100 PointEntity light,cube Dim Pos#(2) motionblur = 1 Repeat angle = angle + 4 PositionEntity cube,Cos(angle)*5,0,0 RotateMesh cube,1,1,1 currentx# = EntityX#(cube) currenty# = EntityY#(cube) currentz# = EntityZ#(cube) scalex# = 1.0+Abs(currentx#-Pos#(0)) scaley# = 1.0+Abs(currenty#-Pos#(1)) scalez# = 1.0+Abs(currentz#-Pos#(2)) If KeyHit(57) Then motionblur=1-motionblur UpdateWorld If motionblur ScaleEntity cube,scalex#,scaley#,scalez# PositionEntity cube,(currentx#+Pos#(0))*0.5,(currenty#+Pos#(1))*0.5,(currentz#+Pos#(2))*0.5 EndIf RenderWorld If motionblur Then ScaleEntity cube,1/scalex#,1/scaley#,1/scalez# Text 0,0,"Press space - MOTION BLUR = ON" Text 0,15,"Scale factor ("+scalex#+","+scaley#+","+scalez#+")" Else Text 0,0,"Press space - MOTION BLUR = OFF" EndIf Flip Pos#(0) = currentx# Pos#(1) = currenty# Pos#(2) = currentz# Until KeyHit(1) |
| ||
Remember to do:Flip 0 To enable full speed :o) |
| ||
Full speed was useless on that demo which is why I didn't enable that. By the way, I have another idea for a proper motion blur, which I might code soon. |
| ||
couldn't you just create a 20x20 plane in front of the camera that grabs the render using copyrect, puts it on the cube, then transform random vertices in the direction of the blur? [edit] actually, now I think about it, it would have to be a very dense plane, I'm not sure how practical that would be in realtime... [/edit] |
| ||
Because in motionblur there are several objects going in different directions. Well, you could use that but I'm not sure it looks good at all. |
| ||
Okay, I've created my new motionblur. It actually blurs the pixels in the direction of motion, so the effect looks quite similar like on Crysis and other new games. Good: *Proper motionblur Bad: *Slow *Jagged corners on models Here's a screenshot. (The gray wall is just a test since it works even if the model is partially behind objects). EDIT: Optimized code ;Motionblur test by naw ;You need fighter.3ds and fighter.jpg from \blitz3d\Media\geometricks_models\fighter ;You can also use your own model ; ***************** For iterating all entities *********************************** ; Userlib declaration: RTLMoveMemory2 from the kernel32.dll is used, ; you need at least the following 2 lines in your kernel32.decls: ; .lib "kernel32.dll" ; RtlMoveMemory2%(Destination*,Source,Length) : "RtlMoveMemory" ; Note: The pivot that is created in the init section must be the first entity ; that was created or loaded. You may have to recreate it after a "ClearWorld()" ; ******************************************************************************** ;Const Const GfxWidth = 800,GfxHeight = 600 AppTitle "Motionblur test" Graphics3D GfxWidth,GfxHeight,0,2 SetBuffer BackBuffer() ;Type Type Ent Field ID Field Brush Field MB End Type Cam = CreateCamera() MoveEntity Cam,0,0,-10 ; init Cycles entity iteration------------------------ Global Cycle_bank=CreateBank(16) Const Cycle_NextEntity=4 Const Cycle_LastEntity=8 Global Cycle_FirstEntity=CreatePivot() Global Cycle_CurrentEntityPointer=Cycle_FirstEntity ;---------------------------------------------------- Obj = LoadMesh("fighter.3ds") NameEntity Obj,"mb_"+EntityName$(Obj) ScaleEntity Obj,0.01,0.01,0.01 EntityFX Obj,1 EntityShininess Obj,1.0 TurnEntity Obj,0,90,0 Wall = CreateCube() ScaleEntity Wall,5,1,0.1 MoveEntity Wall,0,-1,0 WhiteTexture = CreateTexture(1,1) SetBuffer TextureBuffer(WhiteTexture) Plot 0,0 SetBuffer BackBuffer() BlackBrush = CreateBrush() BrushFX BlackBrush,1 BrushColor BlackBrush,0,0,0 BrushTexture BlackBrush,WhiteTexture WhiteBrush = CreateBrush() BrushFX WhiteBrush,1 BrushColor WhiteBrush,255,255,255 BrushTexture WhiteBrush,WhiteTexture ;Create entities While MoreEntities() ob.Ent = New Ent ob\ID = NextEntity() ob\Brush = GetEntityBrush(ob\ID) If Left$(EntityName$(ob\ID),3) = "mb_" Then ob\MB=1 Wend OldProjectedX# = 0.0 OldProjectedY# = 0.0 Repeat If KeyDown(200) Then MoveEntity Obj,0,0,-0.2 If KeyDown(203) Then TurnEntity Obj,0,2,0 If KeyDown(205) Then TurnEntity Obj,0,-2,0 If KeyDown(208) Then MoveEntity Obj,0,0,0.2 If KeyDown(30) Then TurnEntity Obj,1.5,0,0 If KeyDown(44) Then TurnEntity Obj,-1.5,0,0 RotateEntity Obj,EntityPitch#(Obj),EntityYaw#(Obj),0 CameraProject Cam,EntityX#(Obj),EntityY#(Obj),EntityZ#(Obj) DeltaX# = Abs(ProjectedX#()-OldProjectedX#) DeltaY# = Abs(ProjectedY#()-OldProjectedY#) dist# = Sqr(DeltaX#*DeltaX#+DeltaY#*DeltaY#) Angle# = ATan2(DeltaY#, DeltaX#) OldProjectedX# = ProjectedX#() OldProjectedY# = ProjectedY#() FpsCalc = FpsCalc + 1 If MilliSecs()>FpsTimer+999 Then FpsTimer = MilliSecs() Fps = FpsCalc FpsCalc = 0 EndIf ;Render motionblur UpdateWorld For ob.Ent = Each Ent If ob\MB Then PaintEntity ob\ID,WhiteBrush Else PaintEntity ob\ID,BlackBrush EndIf Next Bank = CreateBank(GfxWidth*GfxWidth*0.25+GfxHeight) ScreenBank = CreateBank((GfxWidth*GfxWidth+GfxHeight)*4) lowx = GfxWidth-1 lowy = GfxHeight-1 maxx = 0 maxy = 0 RenderWorld ;Read mask LockBuffer For y=0 To GfxHeight*0.25-1 For x=0 To GfxWidth*0.25-1 If ReadPixelFast(x*4,y*4) = $FFFFFFFF Then If x<lowx Then lowx = x If y<lowy Then lowy = y If x>maxx Then maxx = x If y>maxy Then maxy = y PokeByte Bank,x*GfxWidth*0.25+y,1 EndIf Next Next UnlockBuffer For ob.Ent = Each Ent PaintEntity ob\ID,ob\Brush Next RenderWorld If DeltaX#+DeltaY#>0.0 LockBuffer Local nr,ng,nb,dx,dy For y=lowy To maxy For x=lowx To maxx If PeekByte(Bank,x*GfxWidth*0.25+y) = 1 Then For kx=0 To 3 For ky=0 To 3 iter# = 0.0 r=0:g=0:b=0 Repeat tx# = (x*4)+kx + Cos(Angle#)*iter# ty# = (y*4)+ky + Sin(Angle#)*iter# If tx<0 Or ty<0 Or tx>GfxWidth-1 Or ty>GfxHeight-1 Then Exit iter# = iter#+1 If PeekByte(ScreenBank,(Int(tx)*GfxWidth+ty)*4+3) = 0 Then rgb = ReadPixelFast(tx,ty) nr = ((rgb Shr 16) And $ff ) ng = ((rgb Shr 8) And $ff ) nb = (rgb And $ff) r=r+nr g=g+ng b=b+nb PokeByte ScreenBank,(Int(tx)*GfxWidth+ty)*4+0,nr PokeByte ScreenBank,(Int(tx)*GfxWidth+ty)*4+1,ng PokeByte ScreenBank,(Int(tx)*GfxWidth+ty)*4+2,nb PokeByte ScreenBank,(Int(tx)*GfxWidth+ty)*4+3,1 Else r = r+PeekByte(ScreenBank,(Int(tx)*GfxWidth+ty)*4+0) g = g+PeekByte(ScreenBank,(Int(tx)*GfxWidth+ty)*4+1) b = b+PeekByte(ScreenBank,(Int(tx)*GfxWidth+ty)*4+2) EndIf Until iter#>dist# rgb=(Int(b/iter#) Or (Int(g/iter#) Shl 8) Or (Int(r/iter#) Shl 16) Or ($ff000000)) dx = x*4+kx dy = y*4+ky If dx<GfxWidth And dy<GfxHeight And dx>0 And dy>0 Then WritePixelFast x*4+kx,y*4+ky,rgb EndIf Next Next EndIf Next Next UnlockBuffer EndIf Text 0,0,"FPS: "+Fps Text 0,15,"Delta " + DeltaX# + ","+DeltaY# Flip 0 FreeBank Bank FreeBank ScreenBank Until KeyHit(1) ;Functions to iterate all entities Function MoreEntities() ; check if there are further entities RtlMoveMemory2(Cycle_bank,Cycle_CurrentEntityPointer+Cycle_NextEntity,4) If PeekInt(Cycle_bank,0)<>0 Return True Else Cycle_CurrentEntityPointer=Cycle_FirstEntity EndIf End Function Function NextEntity() Local entity RtlMoveMemory2(Cycle_bank,Cycle_CurrentEntityPointer+Cycle_NextEntity,4) entity=PeekInt(Cycle_bank,0) Cycle_CurrentEntityPointer = entity Return entity End Function |
| ||
Nice coding Nawi, that looks really good! My mind is not really good to read codes right now, so if I may ask you, how are you using WritePixelFast for realtime, in that 'ScreenBank'? |
| ||
I read the pixels to a memory bank so I don't have to use readpixelfast more than once per each pixel. When I'm done calculating the new pixel value (it is just an average from many pixels) I just draw it with WritePixelFast. |
| ||
Where can I find that kernel file? And why is it needed? |
| ||
Because it uses the RtlMoveMemory2 function which Blitz doesn't have. You can just create the file and paste those 2 lines. You need to place the file in the directory userlibs, and restart Blitz3D. |
| ||
Hmm... I'm no good at these things, and can't get it to work. What, exactly, do I need to do? |
| ||
Create a file called kernel32.decls using notepad, paste two lines:.lib "kernel32.dll" RtlMoveMemory2%(Destination*,Source,Length) : "RtlMoveMemory" and save to Blitz3d\Userlibs |
| ||
I had already done that, and I still get a "Function 'rtlmovememory2' not found" error. Do I also need a file called kernel32.dll? |
| ||
Okay, got it. It turns out Notepad created kernel32.decls.txt instead of just the decls file. So this just moves pixels in the texture? It looks nice, but with that speed I'm not sure if it has a practical application at the moment... |
| ||
I'm working on a DLL for averaging my subframes, as blitz's speed isn't allowing it for realtime. There will be 3 functions: MB_Prepare() ;initializes the motion blur. The parameter is the BackBuffer() handle (simply typing in BackBuffer() inside the parentheses will work) MB_CaptureBuffer() ;no arguments here, it simply captures the backbuffer inside a loop where i'm rendering each tween\subframe to average in the end. How many times you loop will be the amount of subframes you'll include in the final image. The more subframes, the smoother the motion blur, but slower FPS in the end. MB_Render() ;after averaging all the images, this function copies the content of the averaged image to the backbuffer. Next on the main loop, right after MB_Render, will be the FLIP command that'll show the result and go back to the start of your main loop. Just a matter of time before I finish it :D |