Post subject: Sat.: How should the mesh effect be handled in our encodes?
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
With the advent of the Saturnus core in BizHawk 2.0 and later, a new option has made its appearance (in that specific core): Horizontal blending. If this option is enabled, BizHawk stretches the output to twice the width with some filtering applied. I've done a bit of research regarding how the Sega Saturn handled transparency (I really recommend reading the article and watching the video): http://www.mattgreer.org/articles/sega-saturn-and-transparency/ https://www.youtube.com/watch?v=f_OchOV_WDg As it turns out, many Saturn games go for a mesh approach to handle transparency. This consists of drawing every other pixel of a sprite to simulate a transparency effect. This is where it gets interesting: On composite output, the mesh is blurred in such a way that e.g. the spotlights in Mega Man X4 appear transparent. However, the mesh is clearly visible on S-Video or RGB output. Conversely, I'm currently handling the publication of a Saturn game called Tryrush Deppy. I dumped it once without horizontal blending and once with horizontal blending. Now, I'm going to show 6 screenshots with different approaches, with pros and cons: Original dump without horizontal blending: Pros: Picture is crystal clear Cons: Mesh is clearly visible AviSynth code: N/A (well, aside from opening the AVIs) No horizontal blending, blur filter applied via AviSynth: Pros: No mesh visible Cons: Picture is obviously blurry AviSynth code: Blur(1.00) No horizontal blending, horizontal blur filter applied via AviSynth: Pros: No mesh visible Cons: Picture is obviously blurry (though not as blurry as the above one). AviSynth code: Blur(1, 0) #note the comma instead of the period Horizontal blending, resized back to original width with LanczosResize: Pros: Mesh is less visible. Cons: Picture isn't as clear. AviSynth code: LanczosResize(330, 240, taps=2) Horizontal blending, resized back to original width with PointResize: Pros: Picture is clear, mesh isn't as visible as in the non horizontally blended one. Cons: Some parts might appear overly squished, due to the nature of PointResize. AviSynth code: PointResize(330, 240) So, what would be the best approach for handling the mesh effect present in many Saturn games? I myself am torn between no horizontal filtering with absolutely no blurring applied and either one of the two horizontal blending ones. Something of note is that footage I found on YouTube (SGDQ 2017 speedrun, full playthrough) have the mesh clearly visible.
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
Site Admin, Skilled player (1251)
Joined: 4/17/2010
Posts: 11475
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Spikestuff
They/Them
Editor, Publisher, Expert player (2638)
Joined: 10/12/2011
Posts: 6437
Location: The land down under.
The Mesh was part of the Saturn's quirks. Keep the Mesh.
WebNations/Sabih wrote:
+fsvgm777 never censoring anything.
Disables Comments and Ratings for the YouTube account. Something better for yourself and also others.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
Language: avisynth

ImageSource("00.png", 0, 0) Crop(5, 0, -5, 0) f0 = BilinearResize(int(1.0 * Width * 1/2), Height).BilinearResize(Width, Height) f1 = BilinearResize(int(1.0 * Width * 2/3), Height).BilinearResize(Width, Height) f2 = BilinearResize(int(1.0 * Width * 3/4), Height).BilinearResize(Width, Height) f3 = last Interleave(f0, f1, f2, f3)
I prefer frame 1.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
It seems that mdapt or gdapt would be an alternative (depending on the game); unfortunately they're RetroArch shaders. https://www.reddit.com/r/emulation/comments/34jg1z/after_some_intense_debate_exodus_considered_the/cqvjk97/ http://www.sega-16.com/forum/showthread.php?32262
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
feos wrote:
Try AreaResize as well please. http://www.mediafire.com/download.php?kn56wh7r81vk2rx
After point-resizing the original non-horizontally blended dump to a factor of 8 or 16, then resizing it back to the original resolution with AreaResize, I still got a very visible mesh. As for the horizontally blended one, I got a result very close to the 5th screenshot (after first point-resizing it to a factor of 8, then downscaled straight to 330x240 with AreaResize): The following is with first downscaling to 660x240 with AreaResize, then point-resizing it to 330x240:
creaothceann wrote:
Language: AviSynth

ImageSource("00.png", 0, 0) Crop(5, 0, -5, 0) f0 = BilinearResize(int(1.0 * Width * 1/2), Height).BilinearResize(Width, Height) f1 = BilinearResize(int(1.0 * Width * 2/3), Height).BilinearResize(Width, Height) f2 = BilinearResize(int(1.0 * Width * 3/4), Height).BilinearResize(Width, Height) f3 = last Interleave(f0, f1, f2, f3)
I'm....not sure if I like the end result (especially since it quadruples the amount of frames). Like, it ends up being a bit blurry as the end result. (it also results in the video being kinda shaky, but that might be because the video is 59.88 FPS normally, and conventional monitors usually have a refresh rate of around 60 Hz)
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
Site Admin, Skilled player (1251)
Joined: 4/17/2010
Posts: 11475
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
Does anyone have a guy with a Saturn and a CRT anywhere? I haven't yet watched the video, probably it clearly shows that mesh is completely blended and half-transparent, but having a real TV photo and human experience will help a lot. If it's in the video already, ignore me.
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
From the video: Mega Man X4 (Saturn version): (click to open a Dropbox page where you can enlarge) Virtua Fighter Kids: (click to open a Dropbox page where you can enlarge)
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
fsvgm777 wrote:
creaothceann wrote:
Language: AviSynth

ImageSource("00.png", 0, 0) Crop(5, 0, -5, 0) f0 = BilinearResize(int(1.0 * Width * 1/2), Height).BilinearResize(Width, Height) f1 = BilinearResize(int(1.0 * Width * 2/3), Height).BilinearResize(Width, Height) f2 = BilinearResize(int(1.0 * Width * 3/4), Height).BilinearResize(Width, Height) f3 = last Interleave(f0, f1, f2, f3)
I'm....not sure if I like the end result (especially since it quadruples the amount of frames).
You're supposed to choose only one of these methods... the Interleave call is just to compare these frames in AvsPmod / VirtualDub.
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
creaothceann wrote:
You're supposed to choose only one of these methods... the Interleave call is just to compare these frames in AvsPmod / VirtualDub.
Oh, I see. My bad.
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
Language: AviSynth

BilinearResize(int(1.0 * Width * 1/2), Height).BilinearResize(Width, Height)
Language: AviSynth

BilinearResize(int(1.0 * Width * 2/3), Height).BilinearResize(Width, Height)
Language: AviSynth

BilinearResize(int(1.0 * Width * 3/4), Height).BilinearResize(Width, Height)
The first one is insanely blurry. The second and third ones are less blurry, but aren't exactly the best, either.
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
The second frame was a good compromise between "remove the mesh / vertical lines" and blurriness. Anyway, check out this code:
Language: AviSynth

ImageSource("00.png", 0, 0).ConvertToRGB32.Crop(5, 0, -5, 0) f0 = Method_1.Zoom.Subtitle("bilinear resize to 2/3" , text_color=$FFFFFF) f1 = Method_2.Zoom.Subtitle("bilinear resize to 3/4" , text_color=$FFFFFF) f2 = Method_3.Zoom.Subtitle("add left neighbor pixel at 50% intensity", text_color=$FFFFFF) f3 = last .Zoom.Subtitle("original" , text_color=$FFFFFF) Interleave(f0, f1, f2, f3) function Method_1(clip c) {c.BilinearResize(int(1.0 * c.Width * 2/3), c.Height).BilinearResize(c.Width, c.Height)} function Method_2(clip c) {c.BilinearResize(int(1.0 * c.Width * 3/4), c.Height).BilinearResize(c.Width, c.Height)} function Method_3(clip c) {c.Layer(c, level=128, x=1)} function Zoom (clip c) {c.PointResize(c.Width * 3, c.Height * 3)}
Personally I think method 3 is close to perfect. (It even reduces jaggies a bit.) Note that it requires RGB32.
Fog
Emulator Coder, Experienced player (641)
Joined: 4/5/2014
Posts: 459
Because the mesh is visible with S-Video and RGB, and the console officially supported these two output types, there should be no reason why the original dump should not be used.
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
Language: AviSynth

Method_3() function Method_3(clip c) {c.Layer(c, level=128, x=1)}
I still notice a little bit of blurring when comparing with the original (non-horizontally blended) AVI dump.
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
Well, of course it is a bit blurry. Perfect detection of the pattern seems to be impossible.
Site Admin, Skilled player (1251)
Joined: 4/17/2010
Posts: 11475
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
Does the motion with that last method look blurred too? I mean, while the screen and the objects are moving, is it obvious that the whole thing is filtered, or it mostly resembles the clean footage? I think none of the methods used allows to only filter the mesh itself. Probably that might be hacked into the emulator core itself. Until then, I wouldn't object against mesh too much, but I want to give the filter a chance as well.
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
fsvgm777
She/Her
Senior Publisher, Player (226)
Joined: 5/28/2009
Posts: 1213
Location: Luxembourg
Here's a video of the first stage. Top one is unfiltered, bottom one is filtered with the last of creaothceann's methods (with Layer()). While the bottom one seems to resemble the top footage, the HUD is clearly blurrier on the filtered footage. It only gets more obvious when I switch between unfiltered and filtered on VirtualDub. In conclusion, judging from the replies, I believe we're going to settle for the mesh.
Steam Community page - Bluesky profile Oh, I'm just a concerned observer.
Site Admin, Skilled player (1251)
Joined: 4/17/2010
Posts: 11475
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
Yeah. The filtered one looks interesting, but sharp pixels is just what most of us expect to see from a tasvideos encode.
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
feos wrote:
Does the motion with that last method look blurred too? I mean, while the screen and the objects are moving, is it obvious that the whole thing is filtered, or it mostly resembles the clean footage? I think none of the methods used allows to only filter the mesh itself. Probably that might be hacked into the emulator core itself. Until then, I wouldn't object against mesh too much, but I want to give the filter a chance as well.
There is no motion blur because it works spatially (in 2D), not temporally. The problem is that games with very small text will have that blurred. Example: Castlevania
Language: AviSynth

Open("castlevaniasotn-tas-maria-arandomgametaser.mkv") sh1 = StackHorizontal(Method_0.RightBorder, Method_1.RightBorder, Method_2).BottomBorder sh2 = StackHorizontal(Method_3.RightBorder, Method_4.RightBorder, Method_5) StackVertical(sh1, sh2) FullScreen(1920, 1080) # enter your screen size; intended for viewing with MPC-HC (or other players) in fullscreen function Method_0(clip c) {c .PointResize(c.Width * 2, c.Height * 2).Subtitle("original (no change)" , text_color=$FFFFFF)} function Method_1(clip c) {c.Layer(c, level=128, x=1) .PointResize(c.Width * 2, c.Height * 2).Subtitle("method 1 (add left neighbor pixel at 4/8 intensity)" , text_color=$FFFFFF)} function Method_2(clip c) {c.Layer(c, op="lighten", level=160, x=1) .PointResize(c.Width * 2, c.Height * 2).Subtitle("method 2 (lighten with left neighbor pixel at 5/8 intensity)" , text_color=$FFFFFF)} function Method_3(clip c) {c.Layer(c, op="lighten", level= 64, x=1).Layer(c, op="lighten", level= 64, x=-1).PointResize(c.Width * 2, c.Height * 2).Subtitle("method 3 (lighten with left and right pixels at 2/8 intensity)", text_color=$FFFFFF)} function Method_4(clip c) {c.Layer(c, op="lighten", level= 96, x=1).Layer(c, op="lighten", level= 96, x=-1).PointResize(c.Width * 2, c.Height * 2).Subtitle("method 4 (lighten with left and right pixels at 3/8 intensity)", text_color=$FFFFFF)} function Method_5(clip c) {c.Layer(c, op="lighten", level=128, x=1).Layer(c, op="lighten", level=128, x=-1).PointResize(c.Width * 2, c.Height * 2).Subtitle("method 5 (lighten with left and right pixels at 4/8 intensity)", text_color=$FFFFFF)} function FullScreen(clip c, int Screen_Width, int Screen_Height) { c x = (Screen_Width - Width ) / 2 y = (Screen_Height - Height) / 2 AddBorders(x, y, x, y) } function Open(string f) { v = DSS2(f, pixel_type="RGB32") a = DirectShowSource(f, video=false) AudioDub(v, a) } function BottomBorder(clip c) {c.Crop(0, 0, 0, -1).AddBorders(0, 0, 0, 1, color=$FFFFFF)} function RightBorder(clip c) {c.Crop(0, 0, -1, 0).AddBorders(0, 0, 1, 0, color=$FFFFFF)} function Show(clip a, clip b, string "text_a", string "text_b") { text_a = default(text_a, "") text_b = default(text_b, "") a = a.ConvertToRGB32 b = b.ConvertToRGB32 c = Subtract(a, b) x = a.Width / 2 y = a.Height / 2 a1 = a.Crop(0, 0, 0, -y).Crop(1, 1, -1, -1).AddBorders(1, 1, +1, +1, $FF0000).Subtitle(text_a, text_color=$FFFFFF) a2 = a.Crop(0, +y, 0, 0).Crop(1, 1, -1, -1).AddBorders(1, 1, +1, +1, $0000FF) b1 = b.Crop(0, 0, 0, -y).Crop(1, 1, -1, -1).AddBorders(1, 1, +1, +1, $FF0000).Subtitle(text_b, text_color=$FFFFFF) b2 = b.Crop(0, +y, 0, 0).Crop(1, 1, -1, -1).AddBorders(1, 1, +1, +1, $0000FF) c1 = c.Crop(0, 0, 0, -y).Crop(1, 1, -1, -1).AddBorders(1, 1, +1, +1, $FF0000).Subtitle("top" , text_color=$FFFFFF, halo_color=$FF0000) c2 = c.Crop(0, +y, 0, 0).Crop(1, 1, -1, -1).AddBorders(1, 1, +1, +1, $0000FF).Subtitle("bottom", text_color=$FFFFFF, halo_color=$0000FF) a = StackVertical (a1, a2) b = StackVertical (b1, b2) c = StackHorizontal(c1, c2) StackVertical(StackHorizontal(a, b), c) }
Result (view in fullscreen / download and view with fullscreen viewers like IrfanView)
  • [Picture 01] Method_1 completely removes the mesh but blurs the small text (e.g. the "START" button).
  • [Picture 02] The "lighten" methods preserve the text, but cannot remove the mesh completely. (They also make the picture a little bit brighter.)
  • [Picture 04] As you can see the algorithm works purely horizontally.
  • [Picture 05] Removing static meshes is the most important job of such a filter. As said above the "lighten" filters fail here.
For games where the meshes are only used for small, fast-moving objects (like Maria in the examples) I wouldn't apply a filter. I suspect that what we'd need is a "bloom" filter which makes only the bright pixels bleed into the dark pixels.
creaothceann
He/Him
Editor
Joined: 4/7/2005
Posts: 1874
Location: Germany
Just had an idea about using convolution:
Language: Avisynth

f = "castlevaniasotn-tas-maria-arandomgametaser.mkv" DSS2(f, pixel_type="RGB32") v0 = Method_0.Subtitle("original" , text_color=$FFFFFF) v1 = Method_1.Subtitle("add left neighbor at 50% intensity", text_color=$FFFFFF) v2 = Method_2.Subtitle("convolution" , text_color=$FFFFFF) v3 = Method_3.Subtitle("bilinear resize" , text_color=$FFFFFF) Interleave(v0, v1, v2, v3) function Method_0(clip c) { c PointResize(Width * 3, Height * 3) } function Method_1(clip c) { c.Layer(c, level=128, x=1) PointResize(Width * 3, Height * 3) } function Method_2(clip c) { c GeneralConvolution(0, " 0 1 0 1 0 1 0 1 0") c.Layer(last, level=128) PointResize(Width * 3, Height * 3) } function Method_3(clip c) { c BilinearResize(Width * 3, Height * 3) }
(Note that all methods except #3 don't modify the size of the picture.) Result: http://imgur.com/a/IpX59 It removes the mesh, but keeps the text more readable by strengthening vertical structures. It also works vertically (so there's also a bit of vertical blurring). I don't like how it makes the text a bit darker even when it shouldn't be, but that's for another day. EDIT: Unfortunately it's not suited for dithering that consists of columns only, which can be seen in the very first post in the bottom left corner.