holzchopf.mojo2maskshaders - Image masking made easy

Holzchopf

Moderator
Staff member
Joined
Jul 31, 2017
Location
Bern, Switzerland
This module provides three shaders that can be used to mask images in different ways:

AlphaMaskShader

A shader that can be used to mask images depending on their pixels alpha values. The threshold value is set via SetAlpha.


RedMaskShader

A shader that can be used to mask images with a secondary image mask and the corresponding pixel's red value. The threshold value is set via SetAlpha.


StaticRedMaskShader

Similar to RedMaskShader, but the threshold is set by MaskShader.SetThreshold, thus allowing to use SetAlpha for additional transparency.

The Module comes with docs and examples. If the docs don't build, try Mikes newest makedocs_winnt.exe.


Here's an example of the three different shaders (AlphaMaskShader, RedMaskShader and StaticRedMaskShader with threshold set to 0.4):
mojo2maskshaders.png


Edit March 06, 2018
Forgot to mention: I hereby put the module including the glsl shaders under a CC0 license.

Edit March 09, 2018
The glsl files in the archive still contained huge commented blocks of code - removed them, uploaded updated archive.
 

Attachments

Last edited:

Holzchopf

Moderator
Staff member
Joined
Jul 31, 2017
Location
Bern, Switzerland
Good question! I'm thinking of three scenarios (two of which I'm planning to use, the other one I actually used for a tech-demo back in my BMax-time ;-) )

  1. Transitions between screens. When fading out and in slowly isn't exactly what you want, you might prefer a roll-in or an effect where the new screen pops up in tiny pieces.
  2. Visibility of objects. Right now I'm using it for object sprites that are supposed to be in the water. By adjusting their visibility, it looks like they're slowly going under water or coming out of it. With the shader I only need one image instead of a frame for every "pixel above water".
  3. Transitions between textures. This one I used once for a tech-demo. The alpha-fading from grass to cobbled pavement on a tile-map looked unnatural to me, I wanted to achieve a transition where more or less stones are visible. I replicated this case in the examples. Here's a shot of example 2, on top what it used to look like, bottom what I was aiming for:
20180219-MaskShader.jpg


Edit I forgot to mention that example 2 needs the DrawIndexedPrimitives with ability to set vertex color - atm only available in the mojo2 on GIT... :oops:
 

Dubbsta

Active Member
Joined
Jul 13, 2017
Way cool! Need to learn mojo2 not fully understanding the whole canvas thing:confused:
 

PixelPaladin

Active Member
CX Code Contributor
3rd Party Module Dev
Joined
Aug 27, 2017
Location
Germany
@Holzchopf: This looks awesome! Can you show the mask you are creating with your custom DrawIndexedPrimitives method because I am really curious :)
 

Holzchopf

Moderator
Staff member
Joined
Jul 31, 2017
Location
Bern, Switzerland
MaskShaders - Behind the Scenes

It's time to learn more about those MaskShaders. In this post, the theories behind the shaders are explained. There will also be a section dedicated to the creation of suitable mask images with hints and explanations. To make everything more comprehensive, a top-down approach is chosen, starting at a desired output and going down into the techniques.

Working With Meshes With Coloured Vertices as a Tile Map

The following map will be used to explain one use case of the RedMaskShader and its detailed discussion:

map_textured.png

Fig. 1: Textured map

The map shows three layers of tile maps (sand, grass and stones) with individual border transitions. As you can tell from the screenshot, five layers would be possible in this example map editor. However, to achieve this looks for every layer, there are multiple things needed.

Let's start by looking at what PixelPaladin pointed out
Can you show the mask you are creating with your custom DrawIndexedPrimitives method
Here's what the map looks like when textures are disabled:

map_untextured.png

Fig. 2: Untextured map

It's visible now that this map consists basically of three meshes with individually coloured vertices (including alpha), drawn with DrawIndexedPrimitives. The colour is the same per mesh, so there's the brownish sand layer, the green grass layer and on top the greyish stone layer. Only alpha values are different to create different shapes of landscapes. Those alpha values are - in combination with the RedMaskShader and the mask - what make the transitions of the textures as in Fig. 1.

Meshes itself are grids of vertices, joined together to form tile maps:

tilemap_mesh.png

Fig. 3: Meshes

Meshes consist of vertices (shown blue and labelled with their index), which are used to form triangles. Two triangles together build one tile (shown red labelled with their x- and y-position). In Fig. 3, tile (0,0) is made from the two triangles with vertices (0,1,5) and (0,5,4). To be able to draw such a thing with DrawIndexedPrimitives, you have to provide
  • An array with float pairs of vertex coordinates
  • An array with float pairs of vertex texture coordinates
  • An array with 32bit ints representing vertex colour
  • An array with vertex indices that form the primitives, in this case triangles
Note that the count of vertices equals count of tiles + 1 in each direction.


Textures and Masks

Seamless textures can be found online easily (e.g. on https://opengameart.org). The masks however have to be hand-crafted. Below I show you how I did this for the stones texture. I use PhotoShop and a graphic tablet for this, but it should also be doable with other software and by marking the single features with another tool.

I start by pasting the texture into a new file and doubling the size of the canvas, leaving me with the texture freely in the middle:
masking-01-place_texture.png


Then I repeat the texture around the original on a new semi-transparent layer. This allows me to control if the texture really is seamless and is necessary to be able to create a seamless mask. Also, I reduce saturation and adapt brightness, because I want the texture to be coloured "in game".
masking-03-decrease_colour.png


Then I start masking the features. For this I use full colour layers, starting with a pure red (255,0,0). I put several features on one layer, otherwise there's no end to this work. Every time I add a new layer for a different mask value, I put it beneath the existing full colour layers and reduce red value by 10.
masking-04-start_masking.png


In order to create seamless masks it is essential to draw features, which overlap an edge, only once and then copy them to the opposing edge.
masking-05-features_overlapping_edges.png


In PhotoShop I copy such features by CTRL-clicking the colour layer mask, reducing the selection to the feature I'm interested in with the rectangle selection tool, move the selection via selection > transform... and "cut" the selection with white as selected background colour.
masking-06-select_mask.png
masking-07-transform_selection.png
masking-08-cut_selection.png
 
Last edited:

Holzchopf

Moderator
Staff member
Joined
Jul 31, 2017
Location
Bern, Switzerland
When encountering features overlapping a corner, they have to be copied three times (so four copies in total):
masking-09-repeat_corners.png


When there are no more recognizable features left, a final colour layer covering the rest is added:
masking-10-fill_background.png


Finally, colours have to be adjusted so that the lowest red (mask value) is just above the fade value which is going to be used with that mask. Otherwise - if the lowest mask value is lower than fade length - such pixels will never be drawn fully opaque:
masking-11-adjust_colours.png


maskshaders-fade.png

The latter graph explains the necessity of mask values above fade length: Mask values below fade value will never cause a resulting alpha of 1.0

Then you're all set.

Concerning Down Sampling

If you consider scaling down the texture files for saving, don't scale them using bicubic sampling. Use bilinear sampling. Bicubic sampling would result in overly sharp edges, rendering the texture futile for use as seamless texture. Here's an example to show this effect:

sampletex_unscaled.png

Original image (128x128 px)

sampletex_bicubic.png

Bicubic downsampled to 32x32 px, but displayed at 8x to make it more clear: Over- and undershooting along edges.

sampletex_bilinear.png

Bilinear downsampled to 32x32 px, but displayed at 8x to make it more clear: smooth contours, no visible edges where they souldn't be


Other Hints for Creating Masks

  • Don't put too many features per layer
  • You don't need to be 100% accurate. Sometimes, this isn't even possible. Just try to reproduce the "spirit" of your texture. You can even consider just painting around with a brush that is similar to the stuff in your texture
  • When masking textures that don't have clear features, the mask shouldn't have clear features either. Work with smooth transitions and use a relatively high fade length value in your shader


More About the Techniques

There's further literature about the technique:
http://www.holzchopf.ch/temp/worklogs/cerberus/20180306-Maskshaders.pdf


Appendix

The attached Maskshaders.playground.zip contains the "map editor" used in the beginning of this posting.
 

Attachments

Last edited:
Top Bottom