微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在渲染填充表面时在 BW 显示器上更好地着色

如何解决在渲染填充表面时在 BW 显示器上更好地着色

序言

我终于解决了 AT32UC3xxxxx 和 SSD1306 OLED I2C 显示器之间的硬件不兼容问题(两者都有使它们不兼容的硬件错误),使我能够以 400KBaud(每帧约 26.6 毫秒)使用硬件 I2C。所以我决定为这个 LCD 重写我的旧驱动程序,以通过添加填充的表面(三角形、四边形)而不仅仅是线条和图案线条(我已经实现)来利用新的速度。

问题是显示器是 128x64 像素,但没有颜色或灰色阴影,只有黑白开/关

因此,例如为了渲染旋转立方体,我需要以某种方式区分表面。我在考虑随机填充模式,其中表面填充到某个百分比而不是颜色。

这是我当前的代码(整个库,但没有错误,它可以正常工作):

LCD_SSD1306_I2C.h

//------------------------------------------------------------------------------------------
//--- SSD1306 I2C OLED LCD driver ver 2.000 ------------------------------------------------
//------------------------------------------------------------------------------------------
#ifndef _LCD_SSD1306_I2C_h
#define _LCD_SSD1306_I2C_h
//------------------------------------------------------------------------------------------
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_disPLAYALLON_RESUME 0xA4
#define SSD1306_disPLAYALLON 0xA5
#define SSD1306_norMALdisPLAY 0xA6
#define SSD1306_INVERTdisPLAY 0xA7
#define SSD1306_disPLAYOFF 0xAE
#define SSD1306_disPLAYON 0xAF
#define SSD1306_SETdisPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETdisPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR   0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_SWITCHCAPVCC 0x2
// Scrolling #defines
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
//------------------------------------------------------------------------------------------
//#define I2C_send(adr,buf,siz){}
//------------------------------------------------------------------------------------------
#ifndef _brv8_tab
#define _brv8_tab
static const U8 brv8[256] =     // 8 bit bit reversal
    {
    0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255
    };
#endif
//------------------------------------------------------------------------------------------
class LCD_SSD1306_I2C           // max 96 lines
    {
public:
    // screen
    int adr;                    // I2C adr
    int xs,ys,sz;               // resoluiton
    U8 _scr[((128*96)>>3)+1];   // screen buffer
    U8 *scr;
    U8 *pyx[96];                // scan lines
    // pattern
    U32 pat,pat_m,pat_b;        // binary pattern,max used bit mask,actual bit mask
    // filling
    U32 seed;
    int bufl[96];
    int bufr[96];

    // system api
    void init(int _adr,int _xs,int _ys);                    // initialize LCD: I2C_adr,xs,ys
    void _command(U8 cmd);                                  // *internal* do not cal it (sends command to LCD over I2C)
    void rfsscr();                                          // copy actual screen buffer to LCD (by I2C)
    // gfx rendering col = <0,1>
    void clrscr();                                          // clear screen buffer
    void rotate(int ang);                                   // rotate 180 deg
    void pixel(int x,int y,bool col);                       // set/res pixel
    bool pixel(int x,int y);                                // get pixel
    void line(int x0,int y0,int x1,int y1,bool col);        // line
    void triangle(int x0,int x2,int y2,bool col);// triangle
    void quad(int x0,int x3,int y3,bool col);
    void rect(int x0,bool col);        // rectangle using diagonal points
    // patern rendering
    void pat_set(char *s);                                  // set binary pattern from bianry number string MSB renders first
    void pat_beg();                                         // set pattern state to start of pattern
    void pat_line(int x0,bool col);
    void pat_triangle(int x0,bool col);
    void pat_quad(int x0,bool col);
    void pat_rect(int x0,bool col);
    // filled polygons col = <0,255>
    void _fill_line(int x0,int y1);           // *internal* do not call it (render line into bufl/bufr)
    void _fill_seed();                                      // *internal* do not call it (reset seed)
    U8   _fill_rand();                                      // *internal* do not call it (get pseudo random number)
    void fill_triangle(int x0,U8 col);
    void fill_quad(int x0,U8 col);
    // text rendering
    void prnchr(int x,char c);                        // render char at x,y (y is rounded to multiple of 8)
    void prntxt(int x,const char *txt);               // render text at x,y (y is rounded to multiple of 8)
    };
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_command(U8 cmd)
    {
    U8 buf[2]=
        {
        0x00,// 0x40 data/command
        cmd,};
    I2C_send(adr,2);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::init(int _adr,int _ys)
    {
    int y;
    adr=_adr;
    xs=_xs;
    ys=_ys;
    sz=xs*(ys>>3);
    const bool _external_Vcc=false;
    // VRAM buffer
    scr=_scr+1;                                         // skip first Byte (VRAM/command selection)
    for (y=0;y<ys;y++) pyx[y]=scr+((y>>3)*xs);          // scanlines for fast direct pixel access
    clrscr();
    // Init sequence
    U8 comPins = 0x02;
    U8 contrast = 0x8F;
         if((xs == 128) && (ys == 32)) { comPins = 0x02; contrast = 0x8F; }
    else if((xs == 128) && (ys == 64)) { comPins = 0x12; contrast = (_external_Vcc) ? 0x9F : 0xCF; }
    else if((xs ==  96) && (ys == 16)) { comPins = 0x02; contrast = (_external_Vcc) ? 0x10 : 0xAF; }
    else {} // Other screens
    static U8 init0[27]=
        {
        0x00,// commands
        SSD1306_disPLAYOFF,// 0xAE
        SSD1306_SETdisPLAYCLOCKDIV,0x80,// 0xD5
        SSD1306_SETMULTIPLEX,ys-1,// 0xA8
        SSD1306_SETdisPLAYOFFSET,0x00,// 0xD3 no offset
        SSD1306_SETSTARTLINE | 0x0,// line 0
        SSD1306_CHARGEPUMP,(_external_Vcc)?0x10:0x14,// 0x8D
        SSD1306_MEMORYMODE,// 0x20 horizontal (scanlines)
        SSD1306_SEGREMAP | 0x1,SSD1306_COMSCANDEC,SSD1306_SETCOMPINS,comPins,SSD1306_SETCONTRAST,contrast,SSD1306_SETPRECHARGE,(_external_Vcc)?0x22:0xF1,// 0xd9
        SSD1306_SETVCOMDETECT,0x40,// 0xDB
        SSD1306_disPLAYALLON_RESUME,// 0xA4
        SSD1306_norMALdisPLAY,// 0xA6
        SSD1306_DEACTIVATE_SCROLL,SSD1306_disPLAYON,// Main screen turn on
        };
    I2C_send(adr,init0,sizeof(init0));
    // init default pattern
    pat_set("111100100");
    // clear filling buffers
    for (y=0;y<96;y++)
        {
        bufl[y]=-1;
        bufr[y]=-1;
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::clrscr()
    {
    for (int a=0;a<sz;a++) scr[a]=0x00;
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rotate(int ang)
    {
    U8 a0,a1;
    int x0,y0,x1,y1;
    if (ang==180)
     for (y0=0,y1=ys-8;y0<y1;y0+=8,y1-=8)
      for (x0=0,x1=xs-1;x0<xs;x0++,x1--)
          {
          a0=brv8[pyx[y0][x0]];
          a1=brv8[pyx[y1][x1]];
          pyx[y0][x0]=a1;
          pyx[y1][x1]=a0;
          }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rfsscr()
    {
    static const U8 init1[] =
        {
        0x00,// commands
        SSD1306_MEMORYMODE,// horizontal addresing mode
        SSD1306_COLUMNADDR,xs-1,// Column start/end address (0/127 reset)
        SSD1306_PAGEADDR,(ys>>3)-1,// Page start/end address (0 reset)
        };
    I2C_send(adr,(U8*)init1,sizeof(init1));

    _scr[0]=0x40;                       // 0x40 VRAM
    // SW I2C can pass whole VRAM in single packet
//  I2C_send(adr,_scr,sz+1);

    // HW I2C must use packets up to 255 bytes so 128+1 (as TWIM0 on UC3 has 8 bit counter)
    int i,n=128; U8 *p=_scr,a;
    for (i=0;i<sz;i+=n){ a=p[0]; p[0]=0x40; I2C_send(adr,p,n+1); p[0]=a; p+=n; }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pixel(int x,bool col)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return;
    // add or remove bit
    if (col) pyx[y][x] |= (1<<(y&7));
    else     pyx[y][x] &= (255)^(1<<(y&7));
    }
//------------------------------------------------------------------------------------------
bool LCD_SSD1306_I2C::pixel(int x,int y)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return false;
    // get bit
    return ((pyx[y][x]>>(y&7))&1);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::line(int x0,bool col)
    {
    int i,n,cx,cy,sx,sy;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n){ pixel(x0,col); return; }
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        pixel(x0,col);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_line(int x0,bool col)
    {
    bool ccc;
    int i,i=0;i<n;i++)
        {
        ccc=(pat&pat_b); ccc^=(!col);
        pat_b>>=1; if (!pat_b) pat_b=pat_m;
        pixel(x0,ccc);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill_line(int x0,int y1)
    {
    int i,sy,*buf;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n)
        {
        if ((y0>=0)&&(y0<ys))
            {
            bufl[y0]=x0;
            bufr[y0]=x0;
            }
        return;
        }
    // target buffer depend on y direction
    if (sy>0) buf=bufl; else buf=bufr;
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        if ((y0>=0)&&(y0<ys)) buf[y0]=x0;
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill_seed()
    {
    seed=0x017A357E1;
//  RandSeed=0x017A357E1;
    }
//------------------------------------------------------------------------------------------
U8 LCD_SSD1306_I2C::_fill_rand()
    {
    U32 a,b,c;
    a= seed     &0x0FFFF;
    b=(seed>>16)&0x0FFFF;
    seed<<=11;
    seed^=(a<<16);
    seed&=0x0FFFF0000;
    seed|=b+17;
    return seed&255;
//  return Random(256);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::triangle(int x0,bool col)
    {
    line(x0,y1,col);
    line(x1,x2,y2,col);
    line(x2,x0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_triangle(int x0,bool col)
    {
    pat_line(x0,col);
    pat_line(x1,col);
    pat_line(x2,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_triangle(int x0,U8 col)
    {
    int x,y,X0,X1,Y0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y1);
    _fill_line(x1,y2);
    _fill_line(x2,y0);
    // fill horizontal lines
    _fill_seed();
    for (y=Y0;y<=Y1;y++)
        {
        // x range to render
        X0=bufl[y];
        X1=bufr[y];
        if (X0>X1){ x=X0; X0=X1; X1=x; }
        // clip to screen in y axis
        if ((X1<0)||(X0>=xs)) continue;
        if (X0<  0) X0=   0;
        if (X1>=xs) X1=xs-1;
             if (col==  0) for (x=X0;x<=X1;x++) pixel(x,0);
        else if (col==255) for (x=X0;x<=X1;x++) pixel(x,1);
        else for (x=X0;x<=X1;x++) pixel(x,(_fill_rand()<=col));
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::quad(int x0,x3,y3,col);
    line(x3,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_quad(int x0,col);
    pat_line(x3,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_quad(int x0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    if (Y0>y3) Y0=y3;
    if (Y1<y3) Y1=y3;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y3);
    _fill_line(x3,(_fill_rand()<=col));
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rect(int x0,col);
    line(x0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_rect(int x0,col);
    pat_line(x0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prnchr(int x,char c)
    {
    y&=0xFFFFFFF8;  // multiple of 8
    if ((y<0)||(y>ys-8)) return;
    int i,a;
    a=c; a<<=3;
    for (i=0;i<8;i++,x++,a++)
     if ((x>=0)&&(x<xs))
      pyx[y][x]=font[a];
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prntxt(int x,const char *txt)
    {
    for (;*txt;txt++,x+=8) prnchr(x,*txt);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_set(char *s)
    {
    int i=1;
    pat=0;
    if (s!=NULL)
     for (i=0;(*s)&&(i<32);s++,i++)
        {
        pat<<=1;
        if (*s=='1') pat|=1;
        }
    if (!i) i=1;
    pat_m=1<<(i-1);
    pat_beg();
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_beg()
    {
    pat_b=pat_m;
    }
//------------------------------------------------------------------------------------------
#undef I2C_send
//------------------------------------------------------------------------------------------
#endif
//------------------------------------------------------------------------------------------

它是为 AVR32 studio 2.7 编写的,因此在没有 U8/U16/U32 的平台上使用 unsigned int 而不是相同(或更大)的位宽。

代码没有经过优化,并且是故意按照它的方式编写的(不是为了速度,我也在我的讲座中使用它,这样学生就可以理解我在做什么)

现在,当我使用 2D 填充四边形 using this technique 渲染旋转立方体(在 PC 上的 win32 VCL 测试应用程序上)时:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
const int sz=4;         // LCD pixel size
int xs,ys;              // LCD resolution * sz
int *psz;               // screen pixel to LCD pixel
Graphics::TBitmap *bmp; // screen buffer
//---------------------------------------------------------------------------
typedef BYTE U8; // here use data types you got like unsigned __int8_t ...
typedef WORD U16;
typedef DWORD U32;
void I2C_send(int adr,U8 *buf,int siz){}
//#include "font_8x8.h"  // font file
static U8 font[256<<3]; // empty font instead (no printing used)
#include "LCD_SSD1306_I2C.h"
LCD_SSD1306_I2C lcd;
//---------------------------------------------------------------------------
const float cube_pos[]=
    {
//  x    y    z     //ix
    -1.0,+1.0,-1.0,//0
    +1.0,//1
    +1.0,//2
    -1.0,//3

    -1.0,//4
    +1.0,//5
    +1.0,//6
    -1.0,//7

    -1.0,//3
    +1.0,//2
    +1.0,//5
    -1.0,//4

    +1.0,//6
    +1.0,//5

    +1.0,//1
    -1.0,//0
    -1.0,//7
    +1.0,//6

    -1.0,//3
    -1.0,//4
    -1.0,//7
    };

const float cube_nor[]=
    {
//   nx   ny   nz   //ix
     0.0,0.0,//0
     0.0,//1
     0.0,//2
     0.0,//3

     0.0,//4
     0.0,//5
     0.0,//6
     0.0,//7

     0.0,//5

     0.0,//3
     0.0,//7
     0.0,//7
    };
//---------------------------------------------------------------------------
void  matrix_mul_pos(float *c,const float *a,const float *b)
    {
    float q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
void  matrix_mul_dir(float *c,const float *b)
    {
    float q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
void  matrix_mul_mat(float *c,float *a,float *b)
    {
    float q[16];
    q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
    q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
    q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
    q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
    q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
    q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
    q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
    q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
    q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
    q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
    q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
    q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
    q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
    q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
    q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
    q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
    for(int i=0;i<16;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
float deg=M_PI/180.0,angx=0.0,angy=0.0,angz=5.0*deg;
float view_FOVx=128.0*tan(30.0*deg)*0.5;
void obj2scr(int &x,int &y,const float *m,const float *pos)
    {
    float p[3],d;
    x=0; y=0;
    matrix_mul_pos(p,m,pos);
    if (fabs(p[2])>1e-3) d=view_FOVx/p[2]; else d=0.0;
    p[0]*=d; x=2.5*p[0]; x+=64;
    p[1]*=d; y=2.5*p[1]; y+=32;
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    int i,j,nz;
    int x,y3;
    lcd.clrscr();

    // modelview
    float p[3],c,s,m[16],m0[16]=
        {
         1.0,1.0,7.0,};
    c=cos(angx); s=sin(angx);
    float rx[16]= { 1,-s,1 };
    c=cos(angy); s=sin(angy);
    float ry[16]= { c,1 };
    c=cos(angz); s=sin(angz);
    float rz[16]= { c,1 };
    matrix_mul_mat(m,rx,ry);
    matrix_mul_mat(m,rz);
    matrix_mul_mat(m,m0);
    angx=fmod(angx+1.0*deg,2.0*M_PI);
    angy=fmod(angy+5.0*deg,2.0*M_PI);
    angz=fmod(angz+2.0*deg,2.0*M_PI);

    n=6*4*3;
    for (i=0;i<n;)
        {
        matrix_mul_dir(p,cube_nor+i);
        nz=float(-255.0*p[2]*fabs(p[2]));
        obj2scr(x0,cube_pos+i); i+=3;
        obj2scr(x1,cube_pos+i); i+=3;
        obj2scr(x2,cube_pos+i); i+=3;
        obj2scr(x3,cube_pos+i); i+=3;
        if (nz>0)
            {
            nz=100+((150*nz)>>8);
            lcd.fill_quad(x0,nz);
            }
        }
    lcd.rfsscr();

    // copy LCD to Canvas to see result
    if (1)
        {
        DWORD col[2]={0x00100018,0x00FFFFFF},*p;
        int x,xx,yy;
        for (                           y=0,yy=psz[y];y<ys;y++,yy=psz[y])
         for (p=(DWORD*)bmp->ScanLine[y],x=0,xx=psz[x];x<xs;x++,xx=psz[x])
          p[x]=col[lcd.pixel(xx,yy)];
        }
    Canvas->Draw(0,bmp);
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    lcd.init(0x3C,64);
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    xs=lcd.xs*sz;
    ys=lcd.ys*sz;
    bmp->SetSize(xs,ys);
    ClientWidth=xs;
    ClientHeight=ys;

    int i,n;
    n=xs; if (n<ys) n=ys;
    psz=new int[n+1];
    for (i=0;i<n;i++) psz[i]=i/sz; psz[n]=0;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    delete[] psz;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------

忽略 VCL 的东西。该应用程序具有单个计时器,可定期重绘屏幕。 lcd 需要先初始化。 lcd 使用 I2C_send 函数通过 I2C 进行通信,因此您必须实现它才能与真正的 LCD 一起使用,如果您只是将屏幕复制到图像或视图(仿真)中,那么像我一样的空函数就足够了。打印需要 this font 的文本也是如此(它不适合这里),所以我使用了空文本(因为该示例无论如何都不会打印任何内容)。

我得到了这个输出(使用随机着色填充和基本法线着色):

preview

正如你所看到的,由于分辨率低,立方体几乎无法区分,我希望有更好的东西。

最后我的问题是:

如何在视觉上改善这种阴影

我正在考虑某种更类似于 Freescape 引擎输出的阴影或预定义模式,如下所示:

predefined patterns

您对如何进行这种二维凸多边形填充有任何想法或建议吗?

限制是低分辨率 128x64 1bpp 图像,低内存使用率,因为目标 AVR32 UC3 平台只有 (16+32+32) KB RAM,如果有人想使用 AVR8 芯片,那么只有 2 KB(你知道 Arduino使用那些)。

速度不是主要问题,因为目标平台有 ~91 MIPS。

我对 BW 着色不是很熟练(过去我主要使用线框进行 BW 输出),所以即使是经验丰富的用户提示,例如:

  • 多少种色调16/32/256
  • 有多大4x4/8x8/16x16
  • 如何生成模式(硬编码或某种算法)
  • 如何处理多边形移动以避免噪音/闪烁(现在我在每个多边形上重置种子)

可能对我有很大帮助。

这是用于测试的示例输入图像:

sample input

解决方法

我设法让这个工作。我最终得到了大小为 8x8 像素的硬编码 17 个阴影图案(在 Paint 中手动创建)。这些是阴影:

17 shades

从那里我只使用渲染像素 x,ymod 8 作为阴影 LUT 中的坐标。结果如下:

shaded cube

如您所见,它比基于 PRNG 的着色要好得多。这是更新的代码:

//------------------------------------------------------------------------------------------
//--- SSD1306 I2C OLED LCD driver ver 2.001 ------------------------------------------------
//------------------------------------------------------------------------------------------
/*
 [Notes]
 + I2C transfere size is not limitted on LCD side
 - No memory address reset command present so any corruption while VRAM transfere permanently damages output untill power on/off but somehow after some timeout or overflow the adress is reseted
 - UC3 HW I2C limits up to 255 bytes per packet
 - UC3 glitch on SDA before ACK which confuse LCD to read instead of write operation and do not ACK
 */
#ifndef _LCD_SSD1306_I2C_h
#define _LCD_SSD1306_I2C_h
//------------------------------------------------------------------------------------------
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR   0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_SWITCHCAPVCC 0x2
// Scrolling #defines
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
//------------------------------------------------------------------------------------------
//#define I2C_send(adr,buf,siz){}
//------------------------------------------------------------------------------------------
#ifndef _brv8_tab
#define _brv8_tab
static const U8 brv8[256] =     // 8 bit bit reversal
    {
    0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255
    };
#endif
static const U8 shade8x8[17*8]= // 17 shade patterns 8x8 pixels
    {
      0,255,255
    };
//------------------------------------------------------------------------------------------
class LCD_SSD1306_I2C           // max 96 lines
    {
public:
    // screen
    int adr;                    // I2C adr
    int xs,ys,sz;               // resoluiton
    U8 _scr[((128*96)>>3)+1];   // screen buffer
    U8 *scr;
    U8 *pyx[96];                // scan lines
    // pattern
    U32 pat,pat_m,pat_b;        // binary pattern,max used bit mask,actual bit mask
    // filling
    int bufl[96];
    int bufr[96];

    // system api
    void init(int _adr,int _xs,int _ys);                    // initialize LCD: I2C_adr,xs,ys
    void _command(U8 cmd);                                  // *internal* do not cal it (sends command to LCD over I2C)
    void rfsscr();                                          // copy actual screen buffer to LCD (by I2C)
    // gfx rendering col = <0,1>
    void clrscr();                                          // clear screen buffer
    void rotate(int ang);                                   // rotate 180 deg
    void pixel(int x,int y,bool col);                       // set/res pixel
    bool pixel(int x,int y);                                // get pixel
    void line(int x0,int y0,int x1,int y1,bool col);        // line
    void triangle(int x0,int x2,int y2,bool col);// triangle
    void quad(int x0,int x3,int y3,bool col);
    void rect(int x0,bool col);        // rectangle using diagonal points
    // patern rendering
    void pat_set(char *s);                                  // set binary pattern from bianry number string MSB renders first
    void pat_beg();                                         // set pattern state to start of pattern
    void pat_line(int x0,bool col);
    void pat_triangle(int x0,bool col);
    void pat_quad(int x0,bool col);
    void pat_rect(int x0,bool col);
    // filled polygons col = <0,255>
    void _fill_line(int x0,int y1);           // *internal* do not call it (render line into bufl/bufr)
    void _fill(int Y0,int Y1,U8 col);                       // *internal* do not call it (render bufl/bufr onto screen)
    void fill_triangle(int x0,U8 col);
    void fill_quad(int x0,U8 col);
    // text rendering
    void prnchr(int x,char c);                        // render char at x,y (y is rounded to multiple of 8)
    void prntxt(int x,const char *txt);               // render text at x,y (y is rounded to multiple of 8)
    };
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_command(U8 cmd)
    {
    U8 buf[2]=
        {
        0x00,// 0x40 data/command
        cmd,};
    I2C_send(adr,2);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::init(int _adr,int _ys)
    {
    int y;
    adr=_adr;
    xs=_xs;
    ys=_ys;
    sz=xs*(ys>>3);
    const bool _external_Vcc=false;
    // VRAM buffer
    scr=_scr+1;                                         // skip first Byte (VRAM/command selection)
    for (y=0;y<ys;y++) pyx[y]=scr+((y>>3)*xs);          // scanlines for fast direct pixel access
    clrscr();
    // Init sequence
    U8 comPins = 0x02;
    U8 contrast = 0x8F;
         if((xs == 128) && (ys == 32)) { comPins = 0x02; contrast = 0x8F; }
    else if((xs == 128) && (ys == 64)) { comPins = 0x12; contrast = (_external_Vcc) ? 0x9F : 0xCF; }
    else if((xs ==  96) && (ys == 16)) { comPins = 0x02; contrast = (_external_Vcc) ? 0x10 : 0xAF; }
    else {} // Other screens
    static U8 init0[27]=
        {
        0x00,// commands
        SSD1306_DISPLAYOFF,// 0xAE
        SSD1306_SETDISPLAYCLOCKDIV,0x80,// 0xD5
        SSD1306_SETMULTIPLEX,ys-1,// 0xA8
        SSD1306_SETDISPLAYOFFSET,0x00,// 0xD3 no offset
        SSD1306_SETSTARTLINE | 0x0,// line 0
        SSD1306_CHARGEPUMP,(_external_Vcc)?0x10:0x14,// 0x8D
        SSD1306_MEMORYMODE,// 0x20 horizontal (scanlines)
        SSD1306_SEGREMAP | 0x1,SSD1306_COMSCANDEC,SSD1306_SETCOMPINS,comPins,SSD1306_SETCONTRAST,contrast,SSD1306_SETPRECHARGE,(_external_Vcc)?0x22:0xF1,// 0xd9
        SSD1306_SETVCOMDETECT,0x40,// 0xDB
        SSD1306_DISPLAYALLON_RESUME,// 0xA4
        SSD1306_NORMALDISPLAY,// 0xA6
        SSD1306_DEACTIVATE_SCROLL,SSD1306_DISPLAYON,// Main screen turn on
        };
    I2C_send(adr,init0,sizeof(init0));
    // init default pattern
    pat_set("111100100");
    // clear filling buffers
    for (y=0;y<96;y++)
        {
        bufl[y]=-1;
        bufr[y]=-1;
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::clrscr()
    {
    for (int a=0;a<sz;a++) scr[a]=0x00;
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rotate(int ang)
    {
    U8 a0,a1;
    int x0,y0,x1,y1;
    if (ang==180)
     for (y0=0,y1=ys-8;y0<y1;y0+=8,y1-=8)
      for (x0=0,x1=xs-1;x0<xs;x0++,x1--)
          {
          a0=brv8[pyx[y0][x0]];
          a1=brv8[pyx[y1][x1]];
          pyx[y0][x0]=a1;
          pyx[y1][x1]=a0;
          }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rfsscr()
    {
    static const U8 init1[] =
        {
        0x00,// commands
        SSD1306_MEMORYMODE,// horizontal addresing mode
        SSD1306_COLUMNADDR,xs-1,// Column start/end address (0/127 reset)
        SSD1306_PAGEADDR,(ys>>3)-1,// Page start/end address (0 reset)
        };
    I2C_send(adr,(U8*)init1,sizeof(init1));

    _scr[0]=0x40;                       // 0x40 VRAM
    // SW I2C can pass whole VRAM in single packet
//  I2C_send(adr,_scr,sz+1);

    // HW I2C must use packets up to 255 bytes so 128+1 (as TWIM0 on UC3 has 8 bit counter)
    int i,n=128; U8 *p=_scr,a;
    for (i=0;i<sz;i+=n){ a=p[0]; p[0]=0x40; I2C_send(adr,p,n+1); p[0]=a; p+=n; }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pixel(int x,bool col)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return;
    // add or remove bit
    if (col) pyx[y][x] |= (1<<(y&7));
    else     pyx[y][x] &= (255)^(1<<(y&7));
    }
//------------------------------------------------------------------------------------------
bool LCD_SSD1306_I2C::pixel(int x,int y)
    {
    // clip to screen
    if ((x<0)||(x>=xs)||(y<0)||(y>=ys)) return false;
    // get bit
    return ((pyx[y][x]>>(y&7))&1);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::line(int x0,bool col)
    {
    int i,n,cx,cy,sx,sy;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n){ pixel(x0,col); return; }
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        pixel(x0,col);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_line(int x0,bool col)
    {
    bool ccc;
    int i,i=0;i<n;i++)
        {
        ccc=(pat&pat_b); ccc^=(!col);
        pat_b>>=1; if (!pat_b) pat_b=pat_m;
        pixel(x0,ccc);
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill_line(int x0,int y1)
    {
    int i,sy,*buf;
    // line DDA parameters
    x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++;           n=x1;
    y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
    // single pixel (not a line)
    if (!n)
        {
        if ((y0>=0)&&(y0<ys))
            {
            bufl[y0]=x0;
            bufr[y0]=x0;
            }
        return;
        }
    // target buffer depend on y direction
    if (sy>0) buf=bufl; else buf=bufr;
    // ND DDA algo i is parameter
    for (cx=cy=n,i=0;i<n;i++)
        {
        if ((y0>=0)&&(y0<ys)) buf[y0]=x0;
        cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
        cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::_fill(int Y0,U8 col)
    {
    U8 shd;
    int x,y,X0,X1,i;
    // select shade pattern
    i=col;
    if (i< 0) i=0;
    if (i>17) i=17;
    i<<=3;
    // fill horizontal lines
    for (y=Y0;y<=Y1;y++)
        {
        shd=shade8x8[i+(y&7)];
        // x range to render
        X0=bufl[y];
        X1=bufr[y];
        if (X0>X1){ x=X0; X0=X1; X1=x; }
        // clip to screen in y axis
        if ((X1<0)||(X0>=xs)) continue;
        if (X0<  0) X0=   0;
        if (X1>=xs) X1=xs-1;
             if (col==  0) for (x=X0;x<=X1;x++) pixel(x,0);
        else if (col==255) for (x=X0;x<=X1;x++) pixel(x,1);
        else for (x=X0;x<=X1;x++) pixel(x,shd&(1<<(x&7)));
        }
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::triangle(int x0,bool col)
    {
    line(x0,y1,col);
    line(x1,x2,y2,col);
    line(x2,x0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_triangle(int x0,bool col)
    {
    pat_line(x0,col);
    pat_line(x1,col);
    pat_line(x2,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_triangle(int x0,U8 col)
    {
    int y,Y0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y1);
    _fill_line(x1,y2);
    _fill_line(x2,y0);
    // fill horizontal lines
    _fill(Y0,Y1,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::quad(int x0,x3,y3,col);
    line(x3,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_quad(int x0,col);
    pat_line(x3,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::fill_quad(int x0,Y1;
    // y range to render
    Y0=Y1=y0;
    if (Y0>y1) Y0=y1;
    if (Y1<y1) Y1=y1;
    if (Y0>y2) Y0=y2;
    if (Y1<y2) Y1=y2;
    if (Y0>y3) Y0=y3;
    if (Y1<y3) Y1=y3;
    // clip to screen in y axis
    if ((Y1<0)||(Y0>=ys)) return;
    if (Y0<  0) Y0=   0;
    if (Y1>=ys) Y1=ys-1;
    // clear buffers
    for (y=Y0;y<=Y1;y++)
        {
        bufl[y]=xs;
        bufr[y]=-1;
        }
    // render circumference
    _fill_line(x0,y3);
    _fill_line(x3,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::rect(int x0,col);
    line(x0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_rect(int x0,col);
    pat_line(x0,col);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prnchr(int x,char c)
    {
    y&=0xFFFFFFF8;  // multiple of 8
    if ((y<0)||(y>ys-8)) return;
    int i,a;
    a=c; a<<=3;
    for (i=0;i<8;i++,x++,a++)
     if ((x>=0)&&(x<xs))
      pyx[y][x]=font[a];
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::prntxt(int x,const char *txt)
    {
    for (;*txt;txt++,x+=8) prnchr(x,*txt);
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_set(char *s)
    {
    int i=1;
    pat=0;
    if (s!=NULL)
     for (i=0;(*s)&&(i<32);s++,i++)
        {
        pat<<=1;
        if (*s=='1') pat|=1;
        }
    if (!i) i=1;
    pat_m=1<<(i-1);
    pat_beg();
    }
//------------------------------------------------------------------------------------------
void LCD_SSD1306_I2C::pat_beg()
    {
    pat_b=pat_m;
    }
//------------------------------------------------------------------------------------------
#undef I2C_send
//------------------------------------------------------------------------------------------
#endif
//------------------------------------------------------------------------------------------

我把所有的填充都移到了成员函数 _fill 中,只是看看 LUT shade8x8 是如何使用的...

[Edit1] Floyd–Steinberg 抖动

感谢 Scheff,我想尝试这种抖动。遗憾的是,他的代码不能用于多边形光栅化(由于其特殊性和缺乏输入图像),所以我自己实现了。我努力让它正常工作了一段时间,但按预期工作的唯一方法是:

  • 颜色阈值为最大强度的 50%
  • 错误传播是在有符号整数上完成的

第二个要求带来了巨大的性能损失,因为不能再使用简单的位移,但我认为硬编码的 LUT 将克服这一点。

这是我当前使用展位抖动的实现:

void LCD_SSD1306_I2C::_fill(int Y0,U8 col)
    {
    int x,i;
    const U8 colmax=17;
    // bayer like dithering using precomputed shader patterns
    if (dither_mode==0)
        {
        U8 shd;
        // select shade pattern
        i=col;
        if (i< 0) i=0;
        if (i>17) i=17;
        i<<=3;
        // fill horizontal lines
        for (y=Y0;y<=Y1;y++)
            {
            shd=shade8x8[i+(y&7)];
            // x range to render
            X0=bufl[y];
            X1=bufr[y];
            if (X0>X1){ x=X0; X0=X1; X1=x; }
            // clip to screen in y axis
            if ((X1<0)||(X0>=xs)) continue;
            if (X0<  0) X0=   0;
            if (X1>=xs) X1=xs-1;
                 if (col==     0) for (x=X0;x<=X1;x++) pixel(x,0);
            else if (col==colmax) for (x=X0;x<=X1;x++) pixel(x,1);
            else for (x=X0;x<=X1;x++) pixel(x,shd&(1<<(x&7)));
            }
        }
    // Floyd–Steinberg dithering
    if (dither_mode==1)
        {
        int c0,c1,c2,rows[256+4],*r0=rows+1,*r1=rows+128+3,*rr,thr=colmax/2;
        // clear error;
        c0=0; for (x=0;x<256+4;x++) rows[x]=0;
        // fill horizontal lines
        for (y=Y0;y<=Y1;y++)
            {
            // x range to render
            X0=bufl[y];
            X1=bufr[y];
            if (X0>X1){ x=X0; X0=X1; X1=x; }
            // clip to screen in y axis
            if ((X1<0)||(X0>=xs)) continue;
            if (X0<  0) X0=   0;
            if (X1>=xs) X1=xs-1;
                 if (col==     0) for (x=X0;x<=X1;x++) pixel(x,1);
            else for (x=X0;x<=X1;x++)
                {
                c0=col; c0+=r0[x]; ; r0[x]=0;
                if (c0>thr){ pixel(x,1); c0-=colmax; }
                 else        pixel(x,0);
                c2=c0;
                c1=(3*c0)/16; r1[x-1] =c1; c2-=c1;
                c1=(5*c0)/16; r1[x  ] =c1; c2-=c1;
                c1=(  c0)/16; r1[x+1] =c1; c2-=c1;
                              r0[x+1]+=c2;
                }
            rr=r0; r0=r1; r1=rr;
            }
        }
    }

dither_mode 只是决定使用哪种抖动的 int。这里并排预览。左边是 Bayer ditheringlike(带有我画的图案),右边是 Floyd-Steinberg 抖动:

side by side

Floyd–Steinberg 抖动有时会在某些旋转中产生难看的(平行线)图案,而恒定图案在所有情况下看起来都很平滑,因此我将坚持我的原始渲染。

,

Dithering

...一直是我一直想更详细地探索的主题之一。尽管 OP 总是提供一个 self-answer,但在它出现之前我已经开始了我自己的摆弄。因此,我想展示我所得到的。

我实施了

  • 拜耳抖动和
  • 弗洛伊德-斯坦伯格抖动

并做了一个

  • 演示(在 Qt 中)
  • 视觉输出和一些
  • 时间比较

这还不足以称其为基准,但可以提供提示。

Bayer Dithering

链接的维基百科文章提到了可以直接在源代码中定义的 2×2、4×4 和 8×8 拜耳矩阵的阈值映射。

2×2 Bayer matrix

4×4 Bayer matrix

8×8 Bayer matrix

相反,我使用了拜耳矩阵的递归定义

Recursive definition of Bayer matrix

因此,应用相同的函数来初始化静态拜耳矩阵以进行查找以及在需要时即时计算相关元素。

我的实现:ditherBayer.h

// Bayer Dithering (Ordered Dithering)
// https://en.wikipedia.org/wiki/Ordered_dithering

#ifndef DITHER_BAYER_H
#define DITHER_BAYER_H

// standard C++ header:
#include <cassert>
#include <algorithm>

// own header:
#include "types.h"

// a function for recursive definition of Bayer matrix
template <uint Q>
uint8 mBayer(uint i,uint j);

// specialization for Q == 1
template <>
uint8 mBayer<1>(uint i,uint j)
{
  assert(i < 2); assert(j < 2);
  static uint8 m[2][2] {
    { 0,2 },{ 3,1 }
  };
  return m[i][j];
}

// general case: recursive definition
template <uint Q>
uint8 mBayer(uint i,uint j)
{
  const uint q_1 = Q - 1; // for recursive descent
  const uint mask = ~(1 << q_1);
  return 4 * mBayer<q_1>(i & mask,j & mask)
    + mBayer<1>(i >> q_1,j >> q_1);
}

/* Bayer Dithering
 *
 * Q   : quality 1,4
 *       The Bayer matrix has dimension 2^Q x 2^Q.
 * MAP : true ... stored matrix for look-up\n
 *       false ... compute matrix values on-the-fly in each call
 *
 * w,h: width and height of images
 * imgMono: buffer for mono image (output)
 *          1 bit per pixel
 * bytesPerRowMono: bytes per row in the mono image
 *          to consider row alignment in case
 * imgGray: buffer for graylevel image (input)
 *          8 bits per pixel
 * bytesPerRowGray: bytes per row in the graylevel image
 *          to consider row alignment in case
 */
template <uint Q,bool MAP>
void ditherBayer(
  uint w,uint h,uint8 *imgMono,uint bytesPerRowMono,const uint8 *imgGray,uint bytesPerRowGray)
{
  static_assert(Q >= 1); static_assert(Q <= 4);
  const uint n = 1 << Q; // width/height of Bayer matrix (a power of 2)
  const uint mask = n - 1; // a bit mask,used for % n in bit-arith.
  // get pixel (x,y) from gray image
  auto getPixel = [&](uint x,uint y) {
    assert(y < h); assert(x < bytesPerRowGray);
    return imgGray[y * bytesPerRowGray + x];
  };
  // set pixel (x,y) in mono image
  auto setPixel = [&](uint x,uint y,bool value) {
    assert(y < h);
    const uint xByte = x >> 3,xBit = 7 - (x & 7);
    assert(xByte < bytesPerRowMono);
    imgMono[y * bytesPerRowMono + xByte] |= value << xBit;
  };
  // init mono image (clear all bits initially)
  std::fill(imgMono,imgMono + w * h / 8,0);
  // apply dithering
  if (MAP) { // use look-up matrix
    static uint8 m[n][n];
    if (static bool init = false; !init) {
      for (uint i = 0; i < n; ++i) {
        for (uint j = 0; j < n; ++j) {
          m[i][j] = mBayer<Q>(i,j) << (8 - 2 * Q);
        }
      }
      init = true;
    }
    for (uint y = 0; y < h; ++y) {
      for (uint x = 0; x < w; ++x) {
        setPixel(x,getPixel(x,y) > m[y & mask][x & mask]);
      }
    }
  } else { // compute matrix values on-the-fly
    for (uint y = 0; y < h; ++y) {
      for (uint x = 0; x < w; ++x) {
        setPixel(x,y) > mBayer<Q>(y & mask,x & mask) << (8 - 2 * Q));
      }
    }
  }
}

#endif // DITHER_BAYER_H

因此,我将拜耳矩阵的元素缩放到输入像素范围 [0,255]。因此,输入像素可以直接与当前的拜耳矩阵元素进行比较,以产生 0(黑色)或 1(白色)输出像素值。

Floyd-Steinberg Dithering

链接的维基百科文章强调了 Floyd-Steinberg 抖动可用于就地抖动(即,就地修改输入图像)这一事实。 在我的情况下,这不是目的,因为我打算保持输入图像不变。 (在 OP 的情况下,像素值是 3d 渲染的结果,也没有可用的输入图像。)当我从输入图像的副本开始时,我意识到一次总是只需要连续两行当算法在抖动时。因此,我将我的实现更改为仅管理两个行缓冲区,这些缓冲区可在每一行中重复使用。

此外,这些行缓冲区可以使用有符号且大于输入像素类型的像素类型,以实现更精确的错误处理。

最后,行缓冲区比行大一点,这样就无需处理边界情况(行的第一个和最后一个像素)而无需任何特殊考虑。

我的实现:ditherFloydSteinberg.h

// Floyd-Steinberg Dithering
// https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering

#ifndef DITHER_FLOYD_STEINBERG_H
#define DITHER_FLOYD_STEINBERG_H

// standard C++ header:
#include <cassert>
#include <algorithm>
#include <vector>

/* Floyd-Steinberg Dithering
 *
 * w,h: width and height of images
 * imgMono: buffer for mono image (output)
 *          1 bit per pixel
 * bytesPerRowMono: bytes per row in the mono image
 *          to consider row alignment in case
 * imgGray: buffer for graylevel image (input)
 *          8 bits per pixel
 * bytesPerRowGray: bytes per row in the graylevel image
 *          to consider row alignment in case
 */
void ditherFloydSteinberg(
  uint w,uint bytesPerRowGray)
{
  // get pixel (x,xBit = 7 - (x & 7);
    assert(xByte < bytesPerRowMono);
    imgMono[y * bytesPerRowMono + xByte] |= value << xBit;
  };
  // two row buffers
  // for cumulative error compensation
  // and to simplify handling of border cases (hence: w + 2)
  std::vector<int> buffer0(w + 2),buffer1(w + 2);
  int *buffers[2] = { &buffer0[1],&buffer1[1] };
  // copy pixel row y into buffer
  auto initBuffer = [&](int *buffer,uint y)
  {
    buffer[-1] = getPixel(0,y);
    for (uint i = 0; i < w; ++i) buffer[i] = getPixel(i,y);
  };
  // init mono image (clear all bits initially)
  std::fill(imgMono,0);
  // process pixels
  initBuffer(buffers[0],0);
  for (uint y = 0; y < h; ++y) {
    if (y + 1 < h) initBuffer(buffers[1],y); // init buffer for next row
    for (int x = 0; x < (int)w; ++x) {
      const int valueOld = buffers[0][x];
      const bool valueNew = valueOld >= 128;
      setPixel(x,valueNew);
      const int error = valueOld - valueNew * 0xff;
      buffers[0][x + 1] += error * 7 / 16;
      buffers[1][x - 1] += error * 3 / 16;
      buffers[1][x    ] += error * 5 / 16;
      buffers[1][x + 1] += error * 1 / 16;
    }
    std::swap(buffers[0],buffers[1]); // next row -> current row
  }
}

/* Floyd-Steinberg Dithering in serpentines
 *
 * Every second row is processed from right to left.
 *
 * w,h: width and height of images
 * imgMono: buffer for mono image (output)
 *          1 bit per pixel
 * bytesPerRowMono: bytes per row in the mono image
 *          to consider row alignment in case
 * imgGray: buffer for graylevel image (input)
 *          8 bits per pixel
 * bytesPerRowGray: bytes per row in the graylevel image
 *          to consider row alignment in case
 */
void ditherFloydSteinbergSerp(
  uint w,buffers[1]); // next row -> current row
    // next iteration (unrolled)
    if (++y == h) break;
    if (y + 1 < h) initBuffer(buffers[1],y);
    for (int x = w; x--;) {
      const int valueOld = buffers[0][x];
      const bool valueNew = valueOld >= 192;
      setPixel(x,valueNew);
      const int error = valueOld - valueNew * 0xff;
      buffers[0][x - 1] += error * 7 / 16;
      buffers[1][x + 1] += error * 3 / 16;
      buffers[1][x    ] += error * 5 / 16;
      buffers[1][x - 1] += error * 1 / 16;
    }
    std::swap(buffers[0],buffers[1]); // next row -> current row
  }
}

#endif // DITHER_FLOYD_STEINBERG_H

演示(在 Qt 中)

为了演示演示,我编写了一个小型 Qt 程序。 Qt 提供开箱即用的基本图像处理(包括 PNG 和 JPEG 文件加载器)。因此,为此提供 MCVE 所需的代码相当少。

types.h

#ifndef TYPES_H
#define TYPES_H

#include <cstdint>

// convenience types

using uint = unsigned;
using ulong = unsigned long;
using uint8 = std::uint8_t;
using uint16 = std::uint16_t;

#endif // TYPES_H

testQImageDithering.cc

// Qt header:
#include <QtWidgets>

// own header:
#include "types.h"
#include "ditherBayer.h"
#include "ditherFloydSteinberg.h"

/* ensures that the output image has proper size and format
 *
 * qImgMono output image (mono image)
 * qImgGray input image (graylevel image)
 */
void adjustMonoImage(
  QImage &qImgMono,const QImage &qImgGray)
{
  if (qImgMono.format() != QImage::Format_Mono
    || qImgMono.width() != qImgGray.width()
    || qImgMono.height() != qImgGray.height()) {
    qImgMono
      = QImage(qImgGray.width(),qImgGray.height(),QImage::Format_Mono);
  }
}

// convenience types for std::chrono

using Clock = std::chrono::steady_clock;
using Time = Clock::time_point;
using USecs = std::chrono::microseconds;

/* provides a wrapper function to apply the dither algorithms
 * to QImage objects.
 *
 * return: duration in microseconds
 */
template <void (*FUNC)(uint,uint,uint8*,const uint8*,uint)>
ulong dither(
  QImage &qImgMono,const QImage &qImgGray)
{
  adjustMonoImage(qImgMono,qImgGray);
  const Time t0 = Clock::now();
  FUNC((uint)qImgGray.width(),(uint)qImgGray.height(),qImgMono.bits(),qImgMono.bytesPerLine(),qImgGray.bits(),qImgGray.bytesPerLine());
  const Time t1 = Clock::now();
  return std::chrono::duration_cast<USecs>(t1 - t0).count();
}

// main application
int main(int argc,char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc,argv);
  // setup data
  QImage qImg("test.jpg");
  QImage qImgG = qImg.convertToFormat(QImage::Format_Grayscale8);
  QImage qImgDith(qImgG.width(),qImgG.height(),QImage::Format_Mono);
  // dithering algorithms
  using DitherFunc = ulong(QImage&,const QImage&);
  using DitherSlot = std::function<DitherFunc>;
  using FuncEntry = std::pair<QString,DitherSlot>;
  FuncEntry tblDither[] = {
    { "Bayer (2 x 2,table)",&dither<ditherBayer<1,true>> },{ "Bayer (4 x 4,&dither<ditherBayer<2,{ "Bayer (8 x 8,&dither<ditherBayer<3,{ "Bayer (2 x 2,func.)",false>> },{ "Floyd Steinberg",&dither<ditherFloydSteinberg> },{ "Floyd Steinberg (serp.)",&dither<ditherFloydSteinbergSerp> }
  };
  // setup GUI
  QWidget qWinMain;
  qWinMain.setWindowTitle("Test Dithering");
  //qWinMain.resize(800,600);
  QGridLayout qGrid;
  QHBoxLayout qHBoxImgOrig;
  QLabel qLblImgOrig("Original Image:");
  qHBoxImgOrig.addWidget(&qLblImgOrig);
  QCheckBox qTglImgOrigGray("Gray");
  qTglImgOrigGray.setChecked(true);
  qHBoxImgOrig.addWidget(&qTglImgOrigGray,1);
  qGrid.addLayout(&qHBoxImgOrig,0);
  QHBoxLayout qHBoxImgDith;
  QLabel qLblImgDith("Dithered Image:");
  qHBoxImgDith.addWidget(&qLblImgDith);
  QComboBox qCBoxFuncDith;
  for (const FuncEntry &entry : tblDither) {
    qCBoxFuncDith.addItem(entry.first);
  }
  qHBoxImgDith.addWidget(&qCBoxFuncDith,1);
  qGrid.addLayout(&qHBoxImgDith,1);
  QLabel qViewImgOrig;
  qGrid.addWidget(&qViewImgOrig,0);
  QLabel qViewImgDith;
  qGrid.addWidget(&qViewImgDith,1);
  QLabel qLblDTDith("Duration:");
  qGrid.addWidget(&qLblDTDith,1);
  qGrid.setRowStretch(1,1);
  qWinMain.setLayout(&qGrid);
  qWinMain.show();
  // install signal handlers
  auto updateImgOrig = [&]() {
    qViewImgOrig.setPixmap(
      QPixmap::fromImage(qTglImgOrigGray.isChecked() ? qImgG : qImg));
  };
  auto updateImgDith = [&]() {
    const int i = qCBoxFuncDith.currentIndex();
    const ulong dt = i < 0
      ? qImgDith.fill(0),0ul
      : tblDither[i].second(qImgDith,qImgG);
    qViewImgDith.setPixmap(QPixmap::fromImage(qImgDith));
    qLblDTDith.setText(QString::fromUtf8("Duration: %1 \xce\xbcs").arg(dt));
  };
  updateImgOrig();
  updateImgDith();
  QObject::connect(&qTglImgOrigGray,&QCheckBox::toggled,[&](bool) { updateImgOrig(); });
  QObject::connect(&qCBoxFuncDith,QOverload<int>::of(&QComboBox::currentIndexChanged),[&](int) { updateImgDith(); });
  // runtime loop
  return app.exec();
}

为了比较不同算法的视觉结果,我使用了我的一只猫的照片。我选择这个是因为猫是黑色的。因此,照片由许多灰度组成,我认为这是抖动算法的一项雄心勃勃的任务。

Moritz,the cat
Moritz,the cat (256×256 pixel) – 转换为灰度测试

视觉输出

Snapshot of testQImageDithering.exe - Bayer (2×2)
拜耳抖动 (2×2)

Snapshot of testQImageDithering.exe - Bayer (4×4)
拜耳抖动 (4×4)

Snapshot of testQImageDithering.exe - Bayer (8×8)
拜耳抖动(8×8)

Snapshot of testQImageDithering.exe - Floyd-Steinberg
弗洛伊德-斯坦伯格抖动

Snapshot of testQImageDithering.exe - Floyd-Steinberg (serp.)
Floyd-Steinberg 抖动(蛇形)

虽然这可能会给人一种不同抖动方法的视觉效果的印象,但应该对时间持保留态度。

时间比较

在玩这个演示时,我注意到计算持续时间的输出中有大量的噪音。 因此,我使用另一个 1024×576 像素的图像来获得一些时间。 下表提供了一个总体印象,并没有声称被视为严肃的基准。

Method                  │ Duration (μs)
────────────────────────┼──────────────
Bayer (2 x 2,table)    │     1371
Bayer (4 x 4,table)    │     1368
Bayer (8 x 8,table)    │     1371
Bayer (2 x 2,func.)    │     1481
Bayer (4 x 4,func.)    │     1862
Bayer (8 x 8,func.)    │     2473
Floyd Steinberg         │     4037
Floyd Steinberg (serp.) │     4396

发现:

  • 对于使用预计算表的拜耳抖动,拜耳矩阵的大小影响较小。
  • 在动态计算拜耳矩阵元素时(使用递归函数),情况并非如此。递归应该在编译时展开并不会改变这一事实。
  • Floyd-Steinberg 抖动一般较慢(尽管我认为视觉效果更好)。
  • Floyd-Steinberg 和 Floyd-Steinberg (serp.) 之间的差异可以忽略不计。 (在玩弄时,我也经常看到相反的关系。)

Snapshot of testQImageDithering.exe - with two cats to gain a better measurable performance impact


OP 的示例图像:

Sample image of OP

输出:

Snapshot of testQImageDithering.exe - Bayer (2×2)

Snapshot of testQImageDithering.exe - Bayer (4×4)

Snapshot of testQImageDithering.exe - Bayer (8×8)

Snapshot of testQImageDithering.exe - Floyd-Steinberg

Snapshot of testQImageDithering.exe - Floyd-Steinberg (serp.)

注意:

在考虑 OP 3d 渲染之后…… 应用 Bayer 抖动可能很容易,而 Floyd-Steinberg 抖动(其中每个像素都会影响右邻和下邻)就不是那么容易了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。