From 6476fe88bbd4e17d5d693cce4d890fe921ec76e7 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 17 Jul 2017 12:22:15 +0900 Subject: [PATCH] some bayer matrix things and my discoveries --- assets/4096_bayer.frag | 86 +++++++++++++++++-- assets/4096_bayer_skyboxfill.frag | 30 +++---- src/net/torvald/terrarum/ColorLimiterTest.kt | 23 +++-- work_files/odd_numbered_bayer_matrix_gen.py | 64 ++++++++++++++ work_files/the_accidental_bayer_matrix.xlsx | Bin 0 -> 18046 bytes 5 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 work_files/odd_numbered_bayer_matrix_gen.py create mode 100644 work_files/the_accidental_bayer_matrix.xlsx diff --git a/assets/4096_bayer.frag b/assets/4096_bayer.frag index 1669791d2..de37e2d34 100644 --- a/assets/4096_bayer.frag +++ b/assets/4096_bayer.frag @@ -22,7 +22,7 @@ uniform float acount = 1.0; {63,31,55,23,61,29,53,21} }; // fun fact: you can calculate bayer value on-the-fly but LUT is faster float bayerSize = 8.0;*/ -int bayer[12][12] = { +/*int bayer[12][12] = { {0 ,96 ,64 ,8 ,104,72 ,2 ,98 ,66 ,10 ,106,74 }, // 12x12 bayer ordered dithering {112,80 ,16 ,120,88 ,24 ,114,82 ,18 ,122,90 ,26 }, // pattern. Each input pixel {48 ,32 ,128,56 ,40 ,136,50 ,34 ,130,58 ,42 ,138}, // is scaled to the 0..143 range @@ -35,11 +35,87 @@ int bayer[12][12] = { {15 ,111,79 ,7 ,103,71 ,13 ,109,77 ,5 ,101,69 }, {127,95 ,31 ,119,87 ,23 ,125,93 ,29 ,117,85 ,21 }, {63 ,47 ,143,55 ,39 ,135,61 ,45 ,141,53 ,37 ,133}}; -float bayerSize = 12.0; +float bayerSize = 12.0;*/ + +/*int bayer[7][7] = { +{32,42,10,27,37,5,15}, +{1,18,28,45,13,23,40}, +{26,36,4,14,31,48,9}, +{44,12,22,39,0,17,34}, +{20,30,47,8,25,35,3}, +{38,6,16,33,43,11,21}, +{7,24,41,2,19,29,46} +}; // I kind of accidentally create it... +float bayerSize = 7.0;*/ + +int bayer[9][9] = { +{50,71,2,23,44,56,77,17,29}, +{72,12,33,45,66,6,18,39,60}, +{22,43,55,76,16,28,49,70,1}, +{53,65,5,26,38,59,80,11,32}, +{75,15,27,48,69,0,21,42,54}, +{25,37,58,79,10,31,52,64,4}, +{47,68,8,20,41,62,74,14,35}, +{78,9,30,51,63,3,24,36,57}, +{19,40,61,73,13,34,46,67,7} +}; +float bayerSize = 9.0; + +/*int bayer[14][14] = { +{128,168,40,108,148,20,60,130,170,42,110,150,22,62}, +{4,72,112,180,52,92,160,6,74,114,182,54,94,162}, +{104,144,16,56,124,192,36,106,146,18,58,126,194,38}, +{176,48,88,156,0,68,136,178,50,90,158,2,70,138}, +{80,120,188,32,100,140,12,82,122,190,34,102,142,14}, +{152,24,64,132,172,44,84,154,26,66,134,174,46,86}, +{28,96,164,8,76,116,184,30,98,166,10,78,118,186}, +{129,169,41,109,149,21,61,131,171,43,111,151,23,63}, +{5,73,113,181,53,93,161,7,75,115,183,55,95,163}, +{105,145,17,57,125,193,37,107,147,19,59,127,195,39}, +{177,49,89,157,1,69,137,179,51,91,159,3,71,139}, +{81,121,189,33,101,141,13,83,123,191,35,103,143,15}, +{153,25,65,133,173,45,85,155,27,67,135,175,47,87}, +{29,97,165,9,77,117,185,31,99,167,11,79,119,187} +}; +float bayerSize = 14.0;*/ + +/*int bayer[21][21] = { +{288,378,90,243,333,45,135,295,385,97,250,340,52,142,291,381,93,246,336,48,138}, +{9,162,252,405,117,207,360,16,169,259,412,124,214,367,12,165,255,408,120,210,363}, +{234,324,36,126,279,432,81,241,331,43,133,286,439,88,237,327,39,129,282,435,84}, +{396,108,198,351,0,153,306,403,115,205,358,7,160,313,399,111,201,354,3,156,309}, +{180,270,423,72,225,315,27,187,277,430,79,232,322,34,183,273,426,75,228,318,30}, +{342,54,144,297,387,99,189,349,61,151,304,394,106,196,345,57,147,300,390,102,192}, +{63,216,369,18,171,261,414,70,223,376,25,178,268,421,66,219,372,21,174,264,417}, +{294,384,96,249,339,51,141,293,383,95,248,338,50,140,290,380,92,245,335,47,137}, +{15,168,258,411,123,213,366,14,167,257,410,122,212,365,11,164,254,407,119,209,362}, +{240,330,42,132,285,438,87,239,329,41,131,284,437,86,236,326,38,128,281,434,83}, +{402,114,204,357,6,159,312,401,113,203,356,5,158,311,398,110,200,353,2,155,308}, +{186,276,429,78,231,321,33,185,275,428,77,230,320,32,182,272,425,74,227,317,29}, +{348,60,150,303,393,105,195,347,59,149,302,392,104,194,344,56,146,299,389,101,191}, +{69,222,375,24,177,267,420,68,221,374,23,176,266,419,65,218,371,20,173,263,416}, +{292,382,94,247,337,49,139,289,379,91,244,334,46,136,296,386,98,251,341,53,143}, +{13,166,256,409,121,211,364,10,163,253,406,118,208,361,17,170,260,413,125,215,368}, +{238,328,40,130,283,436,85,235,325,37,127,280,433,82,242,332,44,134,287,440,89}, +{400,112,202,355,4,157,310,397,109,199,352,1,154,307,404,116,206,359,8,161,314}, +{184,274,427,76,229,319,31,181,271,424,73,226,316,28,188,278,431,80,233,323,35}, +{346,58,148,301,391,103,193,343,55,145,298,388,100,190,350,62,152,305,395,107,197}, +{67,220,373,22,175,265,418,64,217,370,19,172,262,415,71,224,377,26,179,269,422} +}; +float bayerSize = 21.0;*/ float bayerDivider = bayerSize * bayerSize; + +vec4 gammaIn(vec4 col) { + return pow(col, vec4(2.2)); +} + +vec4 gammaOut(vec4 col) { + return pow(col, vec4(1.0 / 2.2)); +} + vec4 nearestColour(vec4 incolor) { vec4 rgbaCounts = vec4(rcount, gcount, bcount, acount); @@ -57,7 +133,7 @@ vec4 nearestColour(vec4 incolor) { color.a = 1.0; } - return color; + return (color); } void main(void) { @@ -65,10 +141,10 @@ void main(void) { // create texture coordinates based on pixelSize // - vec4 inColor = texture2D(u_texture, v_texCoords); + vec4 inColor = (texture2D(u_texture, v_texCoords)); vec2 entry = mod(gl_FragCoord.xy, vec2(bayerSize, bayerSize)); - gl_FragColor = nearestColour(inColor + spread * (bayer[int(entry.y)][int(entry.x)] / bayerDivider - 0.5)); + gl_FragColor = (nearestColour(inColor + spread * (bayer[int(entry.y)][int(entry.x)] / bayerDivider - 0.5))); //gl_FragColor = nearestColour(inColor); } \ No newline at end of file diff --git a/assets/4096_bayer_skyboxfill.frag b/assets/4096_bayer_skyboxfill.frag index 8e9562dc2..bdd8dac67 100644 --- a/assets/4096_bayer_skyboxfill.frag +++ b/assets/4096_bayer_skyboxfill.frag @@ -14,23 +14,23 @@ uniform float bcount = 64.0; -int bayer[12][12] = { -{0 ,96 ,64 ,8 ,104,72 ,2 ,98 ,66 ,10 ,106,74 }, // 12x12 bayer ordered dithering -{112,80 ,16 ,120,88 ,24 ,114,82 ,18 ,122,90 ,26 }, // pattern. Each input pixel -{48 ,32 ,128,56 ,40 ,136,50 ,34 ,130,58 ,42 ,138}, // is scaled to the 0..143 range -{12 ,108,76 ,4 ,100,68 ,14 ,110,78 ,6 ,102,70 }, // before looking in this table -{124,92 ,28 ,116,84 ,20 ,126,94 ,30 ,118,86 ,22 }, // to determine the action -{60 ,44 ,140,52 ,36 ,132,62 ,46 ,142,54 ,38 ,134}, -{3 ,99 ,67 ,11 ,107,75 ,1 ,97 ,65 ,9 ,105,73 }, -{115,83 ,19 ,123,91 ,27 ,113,81 ,17 ,121,89 ,25 }, -{51 ,35 ,131,59 ,43 ,139,49 ,33 ,129,57 ,41 ,137}, -{15 ,111,79 ,7 ,103,71 ,13 ,109,77 ,5 ,101,69 }, -{127,95 ,31 ,119,87 ,23 ,125,93 ,29 ,117,85 ,21 }, -{63 ,47 ,143,55 ,39 ,135,61 ,45 ,141,53 ,37 ,133}}; // fun fact: you can calculate bayer value on-the-fly but LUT is faster -float bayerSize = 12.0; -float bayerDivider = bayerSize * bayerSize; +int bayer[9][9] = { +{50,71,2,23,44,56,77,17,29}, +{72,12,33,45,66,6,18,39,60}, +{22,43,55,76,16,28,49,70,1}, +{53,65,5,26,38,59,80,11,32}, +{75,15,27,48,69,0,21,42,54}, +{25,37,58,79,10,31,52,64,4}, +{47,68,8,20,41,62,74,14,35}, +{78,9,30,51,63,3,24,36,57}, +{19,40,61,73,13,34,46,67,7} +}; +float bayerSize = 9.0; + +float bayerDivider = bayerSize * bayerSize; + vec4 nearestColour(vec4 incolor) { vec4 rgbaCounts = vec4(rcount, gcount, bcount, 1.0); diff --git a/src/net/torvald/terrarum/ColorLimiterTest.kt b/src/net/torvald/terrarum/ColorLimiterTest.kt index 6a891ff46..6e450675f 100644 --- a/src/net/torvald/terrarum/ColorLimiterTest.kt +++ b/src/net/torvald/terrarum/ColorLimiterTest.kt @@ -9,6 +9,7 @@ import com.badlogic.gdx.graphics.GL20 import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.glutils.ShaderProgram +import com.badlogic.gdx.graphics.glutils.ShapeRenderer import com.badlogic.gdx.math.Matrix4 import net.torvald.terrarum.gameactors.sqrt import net.torvald.terrarumsansbitmap.gdx.GameFontBase @@ -34,6 +35,7 @@ object ColorLimiterTest : ApplicationAdapter() { lateinit var shader4096: ShaderProgram lateinit var batch: SpriteBatch + lateinit var shapeRenderer: ShapeRenderer lateinit var font: GameFontBase @@ -42,14 +44,16 @@ object ColorLimiterTest : ApplicationAdapter() { shader4096 = ShaderProgram(Gdx.files.internal("assets/4096.vert"), Gdx.files.internal("assets/4096_bayer.frag")) shader4096.begin() - shader4096.setUniformf("rcount", 2f) - shader4096.setUniformf("gcount", 2f) - shader4096.setUniformf("bcount", 2f) + shader4096.setUniformf("rcount", 4f) + shader4096.setUniformf("gcount", 4f) + shader4096.setUniformf("bcount", 4f) shader4096.end() + //img = Texture("assets/test_gradient.tga") img = Texture("assets/test_texture.tga") batch = SpriteBatch() + shapeRenderer = ShapeRenderer() font = GameFontBase("assets/graphics/fonts/terrarum-sans-bitmap", flipY = false) @@ -84,19 +88,24 @@ object ColorLimiterTest : ApplicationAdapter() { batch.inUse { batch.shader = shader4096 - shader4096.setUniformf("rcount", dither) - shader4096.setUniformf("gcount", dither) - shader4096.setUniformf("bcount", dither) + shader4096.setUniformf("rcount", 6f)//dither) + shader4096.setUniformf("gcount", 6f)//dither) + shader4096.setUniformf("bcount", 6f)//dither) batch.color = Color.WHITE batch.draw(img, 0f, 0f) + } + /*shapeRenderer.inUse { + shapeRenderer.rect(512f, 0f, 512f, 512f, Color.BLACK, Color.BLACK, Color.WHITE, Color.WHITE) + }*/ + batch.inUse { batch.shader = null batch.color = Color.WHITE batch.draw(img, img.width.toFloat(), 0f) - + batch.shader = null font.draw(batch, "Dither level: ${dither.toInt()}", 10f, Gdx.graphics.height - 30f) } } diff --git a/work_files/odd_numbered_bayer_matrix_gen.py b/work_files/odd_numbered_bayer_matrix_gen.py new file mode 100644 index 000000000..ff584de8f --- /dev/null +++ b/work_files/odd_numbered_bayer_matrix_gen.py @@ -0,0 +1,64 @@ +def printMatrix(mat): + for n in range(matrixSize): + outstr = "" + for k in range(matrixSize): + outstr += str(mat[n][k]) + + if (k < matrixSize - 1): + outstr += "," + + + print(outstr) + + + +matrixSize = 9 +# Matrix sizes I've tested: +# slanted diagonal (gud!): 7, 8, 9 +# ortho pattern: 3, 4, 6 +# ortho diagonal: 5 + + +matrix = [x[:] for x in [[-1] * matrixSize] * matrixSize] + + +# init matrix +cellX = 0 +cellY = (matrixSize >> 1) +for num in range(matrixSize * matrixSize): + if (matrix[cellY][cellX] == -1): + matrix[cellY][cellX] = num + else: + thefuck = matrix[cellY][cellX] + error("Matrix position ("+str(cellX)+", "+str(cellY)+"is occupied by "+thefuck) + + if (matrix[(cellY - 1) % matrixSize][(cellX + 1) % matrixSize] == -1): + cellX = (cellX + 1) % matrixSize + cellY = (cellY - 1) % matrixSize + else: + cellY = (cellY + 1) % matrixSize + + +# vertical shifts +for xpos in range(0, matrixSize): + lookup = [-1] * matrixSize + + for ycursor in range(matrixSize): + lookup[ycursor] = matrix[ycursor][xpos] + + for ycursor in range(matrixSize): + matrix[(ycursor - xpos) % matrixSize][xpos] = lookup[ycursor] + + +# horizontal shifts +for ypos in range(0, matrixSize): + shift = (matrixSize - 1) - ypos + 1 + lookup = list(matrix[ypos]) + + for xcursor in range(matrixSize): + matrix[ypos][(xcursor + shift) % matrixSize] = lookup[xcursor] + + + + +printMatrix(matrix) \ No newline at end of file diff --git a/work_files/the_accidental_bayer_matrix.xlsx b/work_files/the_accidental_bayer_matrix.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..19c431d44d2f83d3c50e595ecdac3d18f76bdb44 GIT binary patch literal 18046 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 literal 0 HcmV?d00001