Shader "CrispyPin/QRCode" { Properties { [HideInInspector] _("",2D)=""{} _Version("Version", Range(1, 40)) = 1 _Mask("Mask type", Range(0, 7)) = 1 [Toggle] _DisableMask("Hide mask", Integer) = 0 [Toggle] _Animate("Animate debug worm", Integer) = 0 _AnimationSpeed("Speed", Range(1, 100)) = 0 _TimeSlider("Time", Range(0, 200)) = 0 _TimeSliderCoarse("Time Coarse", Range(0, 250)) = 0 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 CGPROGRAM #pragma surface s Standard struct Input{float2 uv_;}; uint _Version; uint _Mask; bool _DisableMask; bool _Animate; float _TimeSlider; float _TimeSliderCoarse; float _AnimationSpeed; #define VERSION _Version #define WIDTH (17 + VERSION * 4) #define PIXEL_WIDTH WIDTH // #define PIXEL_WIDTH 177 #define ALIGNERS ((VERSION / 7) + 2) #define ALIGNER_SPACING_IDEAL ((WIDTH - 13) / (ALIGNERS-1)) #define ALIGNER_SPACING (ALIGNER_SPACING_IDEAL + ALIGNER_SPACING_IDEAL % 2) #define MISALIGNMENT ((WIDTH - 13) - ALIGNER_SPACING * ALIGNERS) #define BIT_COUNT (WIDTH * WIDTH - 225 - (WIDTH - 17)*2 - 25 * (ALIGNERS * ALIGNERS - 3) * (VERSION > 1)) #define TIME_MANUAL (_TimeSlider + _TimeSliderCoarse*100) #define TIME (uint)(_Animate ? _Time.y * _AnimationSpeed + TIME_MANUAL : TIME_MANUAL) #define EC_LEVEL_L 1 #define EC_LEVEL_M 0 #define EC_LEVEL_Q 3 #define EC_LEVEL_H 2 #define EC_LEVEL EC_LEVEL_L #define MASK_TYPE _Mask // ECI_MODE 0100 = byte #define ECI_MODE 4 static const uint FORMAT_BIT_SETS[32] = {0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b, 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed}; #define FORMAT_BITS_RAW ((EC_LEVEL << 3) | MASK_TYPE) #define FORMAT_BITS FORMAT_BIT_SETS[FORMAT_BITS_RAW] #define WHITE 1 #define BLACK 0 #define PINK float3(1, .3, .5) #define BLUE float3(0, .4, .7) float3 finder_pattern(uint x, uint y) { if (x < 6 && x > 0 && y < 6 && y > 0) return !(x < 5 && x > 1 && y < 5 && y > 1); return (x > 6 || y > 6); } float3 main (float2 uv){ // "hell", "o wo", "rld!" const uint data[] = {0x68656c6c,0x6f20776f,0x726c6421, // 4 bits 0 padding to align to byte (4 bit ECI_MODE is inserted before data and length) // then ec11ec11... until some version-specific length reached? // then EC data 0x0ec11ec1, 0x1ec2d631, 0x95423722, 0xe0000000 }; const uint data_len = 3*4; const uint total_len = 7*4; uv.y = 1 - uv.y; uv = uv * 1.2 - 0.1; // Quiet zone/frame if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) return 1; uint px = uv.x * PIXEL_WIDTH; uint py = uv.y * PIXEL_WIDTH; // Finder patterns if (px < 8 && py < 8) return finder_pattern(px, py); if (px < 8 && py > WIDTH - 9) return finder_pattern(px, py - WIDTH + 7); if (px > WIDTH - 9 && py < 8) return finder_pattern(px - WIDTH + 7, py); // filler pixel if (px == 8 && py == WIDTH - 8) return 0; // Timing patterns if (px == 6) return py & 1; if (py == 6) return px & 1; // Aligners if (VERSION > 1 && (px > 12 || py > 12) // top left && (px < WIDTH - 13 || py > 12) // top right && (py < WIDTH - 13 || px > 12) // bottom left ) { uint x = px + ALIGNER_SPACING - 4; uint y = py + ALIGNER_SPACING - 4; if (px > 12) x -= MISALIGNMENT; if (py > 12) y -= MISALIGNMENT; x %= ALIGNER_SPACING; y %= ALIGNER_SPACING; if (x < 5 && y < 5) { return (x < 4 && x > 0 && y < 4 && y > 0 && !(x == 2 && y == 2)); } } // Format bits // left if (px < 6 && py == 8) return (FORMAT_BITS & (1 << (14 - px))) == 0; if (px == 7 && py == 8) return (FORMAT_BITS & (1 << 8)) == 0; // right if (px > WIDTH - 9 && py == 8) return (FORMAT_BITS & (1 << (WIDTH - 1 - px))) == 0; // top if (px == 8 && py < 6) return (FORMAT_BITS & (1 << py)) == 0; if (px == 8 && py < 9) return (FORMAT_BITS & (1 << (py-1))) == 0; // bottom if (px == 8 && py > WIDTH - 8) return (FORMAT_BITS & (1 << py + 15 - WIDTH)) == 0; // data bit layout uint column = (WIDTH - px - (px < 6)-1)/2; uint direction_up = column % 2 == 0; uint full_columns = column > 4 ? column - 4 : 0; full_columns = min(full_columns, VERSION*2); // the 4 columns under the right finder uint short_columns = min(column, 4); // between the left two finder patterns uint tiny_columns = column > (VERSION*2 + 4) ? column - (VERSION*2 + 4) : 0; uint bit_index = full_columns * (WIDTH - 1) * 2; bit_index += short_columns * (WIDTH - 9) * 2; bit_index += tiny_columns * (WIDTH - 17) * 2; bit_index += (px + (px < 6)) % 2; uint column_progress; uint y_relative; if (direction_up) { y_relative = WIDTH - py - 1; column_progress = WIDTH - py - 1 - (py < 7); } else { y_relative = py; column_progress = py - ((full_columns == 0) ? 9 : (py > 6)); } bit_index += column_progress * 2; if (px < 9) bit_index -= 16; // rightmost tiny column // data bit layout - aligners if (VERSION > 1 && column > 1) { // aligners always cover a half column to the left and two full columns to the right // outer aligners are always 4 from all edges uint aligner_col = (column - 2) / (ALIGNER_SPACING/2); uint aligner_col_mod = (column - 2) % (ALIGNER_SPACING/2); if (aligner_col > 0) { // full columns passed bit_index -= (ALIGNERS - 1) * aligner_col*25; if (aligner_col > 1) { // top row only obstructs 20 since it overlaps the timing stripe bit_index -= (aligner_col - 1)*20; } } uint passed_up = (y_relative - 9 + ALIGNER_SPACING - (!direction_up * (MISALIGNMENT+ALIGNER_SPACING))) / ALIGNER_SPACING; passed_up = min(passed_up, ALIGNERS-1); if (aligner_col_mod == 0) { bit_index -= passed_up * 10; if (direction_up && py < 5){ bit_index -= 8; } if (!direction_up && py > 5){ bit_index += 2; if (py > WIDTH-5){ bit_index -= 10; } } } else if (aligner_col_mod == 1) { uint passed_down = (y_relative - 8 + ALIGNER_SPACING - !direction_up*MISALIGNMENT) / ALIGNER_SPACING; bit_index -= (passed_down + ALIGNERS - 1) * 10; if (!direction_up) bit_index += 10; if (aligner_col > 0) { bit_index -= 8; } else { bit_index += 8; } if (direction_up ? py < 5 : py > 5){ bit_index += 2; } } else if (aligner_col_mod == 2) { bit_index -= (ALIGNERS - 1) * 20; if (aligner_col > 0) { bit_index -= 16; // most of the current column top aligner, excluding the left 4 pixels if (py < 9) {// top aligner if (direction_up){ bit_index -= min(9-py, 5) - (py < 6); } else if (py > 3) { bit_index -= py - 3 - (py > 6); } } else { bit_index += !direction_up; } if (!direction_up && py > WIDTH-5){ bit_index-=5; } } uint offset = (column_progress + ALIGNER_SPACING - 3 - !direction_up*0 + (!direction_up * (-MISALIGNMENT+1))) % ALIGNER_SPACING; // left side of regular aligners if (offset < 6 && py > 9 && px > 9) { bit_index -= offset; } bit_index -= passed_up * 5; } else { // include the full row bit_index -= (ALIGNERS - 1)*25; if (aligner_col > 0){ // return PINK; bit_index -= 20; } } } // data uint bit = 0; if (bit_index < 4){ bit = (ECI_MODE >> (3-bit_index))&1; } else if (bit_index < 12) { bit = (data_len >> (7 - (bit_index - 4))) & 1; } else if (bit_index < total_len*8 + 12){ uint data_bit_index = bit_index - 12; bit = ((data[data_bit_index/32] >> (31-(data_bit_index % 32))) & 1); } // mask uint mask; switch (_Mask){ case 0: mask = (px+py) % 2 == 0; break; case 1: mask = py % 2 == 0; break; case 2: mask = px % 3 == 0; break; case 3: mask = (px+py) % 3 == 0; break; case 4: mask = (py/2 + px/3) % 2 == 0; break; case 5: mask = (py*px)%2 + (px*py)%3 == 0; break; case 6: mask = ((py*px)%2 + (px*py)%3) % 2 == 0; break; case 7: mask = ((py+px)%2 + (px*py)%3) % 2 == 0; break; } if (!_DisableMask) { bit ^= mask; } // return !bit; // bit index debugging worm uint worm = TIME % BIT_COUNT - bit_index; worm %= 200; const uint length = 6; if (worm == 0) return float3(3,0,0); if (worm < length) return lerp(PINK, BLUE, (worm/(float)length)); if (bit_index > BIT_COUNT) return float3(1,0,0); // #define BIT_COUNT 24 return (float)(bit_index%BIT_COUNT)/(float)BIT_COUNT; } void s (Input IN, inout SurfaceOutputStandard o) { o.Albedo = main(IN.uv_); } ENDCG }}