dithering for semitransparent tiles

This commit is contained in:
minjaesong
2021-10-10 23:47:03 +09:00
parent 0925ec2580
commit 8b3f07eddd
7 changed files with 135 additions and 7 deletions

Binary file not shown.

View File

@@ -1,3 +1,5 @@
Air tile (tile 0,0) must have colour of 0x00000000, NOT 0xFFFFFF00. You can modify the tga file directly to correct bad exporter behaviour.
All TGA must have alpha premultiplied.
Semitransparency is rendered using dithering, so it is good idea to avoid them.
If you must add semitransparency to the tile, they must have alpha NOT premultiplied.

123
assets/tiling_dither.frag Normal file
View File

@@ -0,0 +1,123 @@
/*
*/
#version 120
#ifdef GL_ES
precision mediump float;
#endif
#extension GL_EXT_gpu_shader4 : enable
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform vec2 screenDimension;
uniform vec2 tilesInAxes; // size of the tilemap texture; vec2(tiles_in_horizontal, tiles_in_vertical)
uniform sampler2D tilemap; // RGBA8888
uniform sampler2D tilesAtlas; // terrain, wire, fluids, etc.
uniform sampler2D tilesBlendAtlas; // alternative terrain for the weather mix (e.g. yellowed grass)
uniform float tilesBlend = 0.0; // percentage of blending [0f..1f]. 0: draws tilesAtlas, 1: draws tilesBlendAtlas
uniform vec2 tilesInAtlas = vec2(256.0, 256.0);
uniform vec2 atlasTexSize = vec2(4096.0, 4096.0);
vec2 _tilesInAtlas = vec2(1.0, 1.0) / tilesInAtlas;
vec2 tileSizeInPx = atlasTexSize * _tilesInAtlas; // should be like ivec2(16.0, 16.0)
vec2 _tileSizeInPx = vec2(1.0, 1.0) / tileSizeInPx; // should be like ivec2(0.06125, 0.06125)
uniform vec4 colourFilter = vec4(1, 1, 1, 1); // used by WALL to darken it
uniform ivec2 cameraTranslation = ivec2(0, 0); // used to offset the drawing; it's integer because we want the drawing to be pixel-aligned
uniform float drawBreakage = 1.0; // set it to 0f to not draw breakage, 1f to draw it; NEVER set to any other values.
uniform float mulBlendIntensity = 1.0; // used my MUL-blending drawings; works about the same way as the Layer Opacity slider of Photoshop/Krita/etc.
const vec2 bc = vec2(1.0, 0.0); //binary constant
// man the traditional bayer crosshatch pattern looks really good on tiles...
int bayer[16] = int[](
0,8,2,10,
12,4,14,6,
3,11,1,9,
15,7,13,5
);
float bayerSize = 4.0;
float bayerDivider = 16;
ivec2 getTileXY(int tileNumber) {
return ivec2(tileNumber % int(tilesInAtlas.x), tileNumber / int(tilesInAtlas.x));
}
// return: int=0xaarrggbb
int _colToInt(vec4 color) {
return int(color.b * 255) | (int(color.g * 255) << 8) | (int(color.r * 255) << 16) | (int(color.a * 255) << 24);
}
// 0x0rggbb where int=0xaarrggbb
// return: [0..1048575]
int getTileFromColor(vec4 color) {
return _colToInt(color) & 0xFFFFF;
}
// 0x00r00000 where int=0xaarrggbb
// return: [0..15]
int getBreakageFromColor(vec4 color) {
return (_colToInt(color) >> 20) & 0xF;
}
void main() {
// READ THE FUCKING MANUAL, YOU DONKEY !! //
// This code purposedly uses flipped fragcoord. //
// Make sure you don't use gl_FragCoord unknowingly! //
// Remember, if there's a compile error, shader SILENTLY won't do anything //
// default gl_FragCoord takes half-integer (represeting centre of the pixel) -- could be useful for phys solver?
// This one, however, takes exact integer by rounding down. //
vec2 overscannedScreenDimension = tilesInAxes * tileSizeInPx; // how many tiles will fit into a screen; one used by the tileFromMap; we need this because screen size is not integer multiple of the tile size
vec2 flippedFragCoord = vec2(gl_FragCoord.x, screenDimension.y - gl_FragCoord.y) + cameraTranslation; // NO IVEC2!!; this flips Y
// get required tile numbers //
vec4 tileFromMap = texture2D(tilemap, flippedFragCoord / overscannedScreenDimension); // raw tile number
int tile = getTileFromColor(tileFromMap);
int breakage = getBreakageFromColor(tileFromMap);
ivec2 tileXY = getTileXY(tile);
ivec2 breakageXY = getTileXY(breakage + 5); // +5 is hard-coded constant that depends on the contents of the atlas
// calculate the UV coord value for texture sampling //
// don't really need highp here; read the GLES spec
vec2 uvCoordForTile = (mod(flippedFragCoord, tileSizeInPx) * _tileSizeInPx) * _tilesInAtlas; // 0..0.00390625 regardless of tile position in atlas
vec2 uvCoordOffsetTile = tileXY * _tilesInAtlas; // where the tile starts in the atlas, using uv coord (0..1)
vec2 uvCoordOffsetBreakage = breakageXY * _tilesInAtlas;
// get final UV coord for the actual sampling //
vec2 finalUVCoordForTile = uvCoordForTile + uvCoordOffsetTile;// where we should be actually looking for in atlas, using UV coord (0..1)
vec2 finalUVCoordForBreakage = uvCoordForTile + uvCoordOffsetBreakage;
// blending a breakage tex with main tex //
vec4 tileCol = texture2D(tilesAtlas, finalUVCoordForTile);
vec4 tileAltCol = texture2D(tilesBlendAtlas, finalUVCoordForTile);
vec4 finalTile = mix(tileCol, tileAltCol, tilesBlend);
vec4 finalBreakage = drawBreakage * texture2D(tilesAtlas, finalUVCoordForBreakage); // drawBreakeage = 0 to not draw, = 1 to draw
vec4 finalColor =mix(finalTile, finalBreakage, finalBreakage.a) * bc.xxxy + (finalTile * bc.yyyx);
vec4 undithered = mix(colourFilter, colourFilter * finalColor, mulBlendIntensity);
vec2 entry = mod(gl_FragCoord.xy, vec2(bayerSize, bayerSize));
float bayerThreshold = float(bayer[int(entry.y) * int(bayerSize) + int(entry.x)]) / bayerDivider;
gl_FragColor = undithered * bc.xxxy + vec4((undithered.a > bayerThreshold) ? 1.0 : 0.0) * bc.yyyx;
}

View File

@@ -34,6 +34,8 @@ import kotlin.system.exitProcess
* NOTE: config "fx_dither" only controls the skybox (which is capable of having more than 256 colours
* thanks to the hardware linear intp.) because this dithering shader is somewhat heavy.
*
* Semitransparency is rendered using dithering, so it is good idea to avoid them.
* If you must add semitransparency to the tile, they must have alpha NOT premultiplied.
* Actors' transparency (and not an UI) still uses its own lightweight ditherrer
*/
object IngameRenderer : Disposable {
@@ -282,8 +284,6 @@ object IngameRenderer : Disposable {
)
}
// blending is correct... somewhat. Alpha must be premultiplied
}
// something about RGB
else if (KeyToggler.isOn(Input.Keys.F6) &&

View File

@@ -11,9 +11,9 @@ import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.blockproperties.Block
import net.torvald.terrarum.gameitem.ItemID
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.gameworld.WorldSimulator
import net.torvald.terrarum.gameworld.WorldTime
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.roundToInt
@@ -85,7 +85,7 @@ internal object BlocksDrawer {
private lateinit var tilesQuad: Mesh
private val shader = App.loadShaderFromFile("assets/4096.vert", "assets/tiling.frag")
private val shader = App.loadShaderFromFile("assets/4096.vert", "assets/tiling_dither.frag")
init {

View File

@@ -1,4 +1,4 @@
All the images must be exported as .tga or .tga.gz. All the alpha must be premultiplied.
All the images must be exported as .tga or .tga.gz. All the alpha must be not premultiplied. (It is good idea to avoid semitransparency alltogether)
It is recommended to use ImageMagick command line tool to convert PSD to TGA directly.

Binary file not shown.