游戏中汉字显示的实现与技巧

Proteus仿真进阶:8×8 LED点阵的动态图案与汉字显示 本文详细介绍了在Proteus仿真环境中实现8×8 LED点阵的动态图案与汉字显示技术。从基础原理到动态扫描算法,再到汉字显示方案和常见问题排查,全面解析了LED点阵的应用技巧与注意事项,帮助开发者快速掌握这一实用技能。 阅读详情

游戏中汉字显示的实现与技巧

作者:炎龙工作室 千里马肝

版本:v1.0

最后更新日期: 2002-3-30

绪言

在游戏中,因为我们是中国人麻,通常都需要显示汉字,比方说交待剧情。而对于文字的显示,英文的显示要较其简单得多,因为只有26个字母,就算再加一些标点、符号什么的,用一张位图,就可以足以显示所有的单词了,而相关实现技巧,也比较轻松。

而中文的显示方法,要复杂得许多。记得原来在DOS下,汉字的显示都是读的UCDOS的点阵字库,而点阵字库的读取方法,在UCDOS SDK中都有源代码可以参考。但是自从Windows操作系统开始,我们开始了解到一种更好的字库,它就是TTF。

注:以下我所指的开发环境,除非明确说明,默认的平台是VC6.0+DirectX8.1,使用D3D来加速2D。然后使用的STL是用的SGI实现的那一套STL。

点阵字库

包括现在,有很多游戏都还是使用的点阵字库。因为操作起来比较方便,加上这方面的经验已经积累了好几年了。通常如果只是一种字体就可以满足需要的话,它会是一个比较好、快的解决办法。但是它有3个缺点:

1.       如果放大显示,不做处理的话,显示出来的汉字,是很难看的。

2.       像是UCDOS所提供的点阵字库,只有24点阵的有几种字体,如:宋体、黑体、揩体…,而16点阵的好象就只有宋体一种。

3.       点阵字库,通常是有版权的,尤其是第三方制作的汉字库(如:方正)。

在这样的情况下,当我们写好这样的一个显示函数,就算是解决了如:放大、快速显示等问题的话,可供选择的字体还是太过于局限了。所以,在字体的要求比较强的情况下,点阵字库并不是一个好的解决方法,他不够灵活。尽管我们对于它的操作是如此得熟练,可以写出优美的代码来展示我们的编程技巧。

TTF

TTF是True Type Font的简称。在Windows/Fonts目录下面,我们可以看到许多后缀为ttf的文件,它就是接下来我们接下来所要谈到的。

TTF是一种矢量字库。我们经常可以听到矢量这个词,像是FLASH中的矢量图形,在100*100分辨率下制作的flash,就算它放大为全屏,显示出的画面也不会出现马赛克。所谓矢量,其实说白了就是用点和线来描述图形,这样,在图形需要放大的时候,只要把所有这个图形的点和线放大相应的倍数就可以了。而且,在网站上有很多的TTF字库可以下载,或者你可以去买一些专门的字库光盘。然后在你发行你精心制作的游戏时,可以顺便捎上这些后缀为.ttf的文件就行了。包括Quake这样的惊世之作,也都是用的TTF字库。

这样,我们就可以解决点阵汉字的一些问题。通过TTF,我们在字体的质量和字库的数量上获得了暂时性的胜利。

字库的读取和显示

先前谈到点阵字库,只需要很简单的一些操作,就可以显示出想要的汉字。下面我给出一个读取hzk16的函数,它需要一个Surface以供显示用:

#include <io.h>

#include <stdio.h>

#include <conio.h>

 

// 读取16x16

void DispHZ16(int x, int y, BYTE *Str, LPDIRECTDRAWSURFACE surf)

{

       const int Mask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };

       FILE *HzkFp;

       WORD i, j, k=0, m;

       WORD HzNum;

       WORD QuHao;

       WORD WeiHao;

       long offset;

       BYTE dotBuffer[32];    

 

       HzkFp = fopen("HZK16", "rb");

      

       HzNum = strlen((const char *)Str)/2;

 

       DDSURFACEDESC       ddsd;

       LPWORD              lpSurface;

       HRESULT             ddrval;

   

       ddsd.dwSize = sizeof(ddsd);

      

       while((ddrval=surf->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING);

       if(ddrval == DD_OK)

              lpSurface = (LPWORD)ddsd.lpSurface;

 

       for(i = 0; i<HzNum; i++)

       {    

              QuHao = Str[i*2]-160;

              WeiHao = Str[i*2+1]-160;

 

              offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;

 

              fseek(HzkFp, offset, SEEK_SET);

              fread(dotBuffer, 32, 1, HzkFp);

 

              for(j=0;j<16;j++)

                     for(k=0;k<2;k++)

                            for(m=0;m<8;m++)

                                   if(dotBuffer[j*2+k] & Mask[m])

                                   {

                                          lpSurface[ddsd.lPitch*(y+j+1) + x+k*8+m] = 0x000000;

                                   }

              x+=16;

       }

 

       surf->Unlock(NULL);

 

       fclose(HzkFp);

}

其实原理很简单:

1.         打开字库

2.         计算字符串长度(这个函数只支持中文),并且Lock Surface

3.         依次计算出每个汉字所对应的区码和位码(汉字的第1个字节是区码,第2个字节就是位码),然后通过公式计算出这个汉字在字库中的偏移量:
offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;

4.         读出一个32个字节的点阵

5.         绘制到Surface上

以上只是16*16点阵字库的显示方法,24*24的读取方法与之类似,大家可以参照相关资料来书写出自己的代码。

 

如何显示TTF字库呢,有很多种手段,下面我按从简单到复杂的的顺序依次介绍:

1.       使用Windows API,也就是大家所熟悉的TextOut。通过它,还需要一个HDC(设备句柄),我们就可以随意地在屏幕任何地方显示出文字了。

2.       在http://www.freetype.org,有一个FreeType的免费库,而且是OpenSource的。它目前有2个版本:1.0和2.0。其区别在于,1.0只能读取TTF格式的,而2.0支持更多的文件格式,在使用它之前请详细阅读所要遵循的Licence,以下是摘自FreeType2.0对字库的支持列表:

    • TrueType fonts (and collections)
    • Type 1 fonts
    • CID-keyed Type 1 fonts
    • CFF fonts
    • OpenType fonts (both TrueType and CFF variants)
    • SFNT-based bitmap fonts
    • X11 PCF fonts
    • Windows FNT fonts

3.       自己研究TTF的格式,然后自己来操作。
晕
.......
╮╮
      /█/倒!

      ●

虽然我们想要把每一件事情都做好,但是也不是每一件事情都要亲历亲为。如果你非要这样,也行^____^,但是过不了多久,你就会陷入泥沼,到时候你会发现自己的热情正在慢慢被磨灭,什么叫做抓狂,相信你很快就会知道^_^。

 

在有多种选择可以取舍的情况下,我们需要考虑一下,对比一下各种解决方法的优劣。

 

在DirectDraw时代,我们都不自觉地喜欢上了GetDC,因为……多方便啊。可是现在已经到了DirectX8.1时代了(我要使劲地摇那些还沉醉于DirectX7中,为如何在使用alpha时提升那可怜的1、2个fps的朋友们:醒醒,该起床了!),HDC已经被M$列为禁用品。怎么办呢?是的,你可能已经想到了,我们还一直保存着窗口的hWnd呢,可以通过它来得到hdc,从而调用那些需要hdc的API,可是,这样做是更为愚蠢的,这样对你是没有一点好处的,不信,你就试试吧。有一句话,请牢记:要想你的游戏有更快的速度的话,请不要再去碰HDC了。

我们非常清楚hdc是一个超慢的解决办法,它无法在我们的高速游戏中满60分及格。下面来看看FreeType,它更像是一个Service。它的解决方法是,先通过一系列的初始化和设置,告诉FreeType字体的名字和大小等,然后它会动态地申请一个Graphic,再把我们要显示的字画到这个Graphic上,你还可以把它保存为tga格式。不过我们最终所想要的不是这个,所以可能我们还需要从这个Graphic上逐点读取或者用CopyRect,然后再画到我们的画面上。其实它已经是很方便的了,可是需要你去学习如何配置和使用它,这是很花时间的一件事情,而且它最大的优点是可以跨平台,我们需要它吗?如果有一个更为简单的办法,像是如果Textout不是那么慢的话,就好了……

在这里,顺便谈一下另2个字体显示类:ID3DXFont和CD3DFONT。可能早就有人会说怎么在上面的列表中没有它们?原因我会在下面慢慢地说明:

ID3DXFont,它存在于D3DX库中,一个现成的字体类,不过对于它的处理方法……我实在不敢恭维,就引用一位大师所说的话来表达我的看法吧: 在内部实现中, ID3DXFont::DrawText()函数确实做了我上面讨论的工作,先建立一张GDI兼容的位图,把文本绘制到位图上,而后把位图拷贝到纹理贴图上去,最后把纹理渲染到屏幕上。这样你就聚齐了所有的龟速的原始GDI函数,还包括了一大堆的额外开销 — 最终,这个函数比原来GDI的DrawTextEx()函数要慢上超过六倍……

CD3DFONT,是由M$在D3D的框架代码中提供。不过它只能显示英文,有很多朋友通过自己定制和修改这个类,来实现自己的中文显示。不过效果都不是很好。其实原理,跟ID3DXFont的方法差不多,不过处理方法要聪明了一点。

分析与思考

那么我们应该怎么办呢?通常我们会幻想,如果可以像处理英文那样,把所有的汉字都保存在一张位图里,该有多好。这样,显示的速度就不是问题了,直接可以CopyRect上去。可是,这样可能吗?首先,必须每一种字体都要生成这样的一个巨型位图。而且据说在GB2312中,一共有6000多个汉字,就算是用16*16,oh my god,这个位图该有多大啊(据说会有 2.5M ^__^)!!!而且在DirectX8.1中,对于Texture(显示的最小单位,就好象是原来DirectSurface的概念一样。说过多少遍了,不要再用DirectX8以前的东西了。不要试着去回忆那些美好的过去,我很明白,要你一下子放弃原来多年所获得的成就,是一件很痛苦的事情,但是包袱太重,是会影响进步的。就像是我们的国家……扯远了),不同的显卡,支持的最大容量也是不同的。比方说早期的Voodoo,只支持256*256大小的Texture。而在我的显卡(Geforce2 MX 200)上测试,支持最大2048*2048大小的Texture。对于这样的硬件不确定性,我们只能取其最小值,也就是256*256。

汉字虽然很多,但是常用的汉字,其实也就只有那么几百个。像这样的字:鬯、鞴,你一辈子会看到多少次呢?如果可以做一个类似于Cache的东西,保存着常用的那些个汉字,在需要显示的的时候,先在Cache中查找,如果有的话,就马上画上去;如果没有,就从字库中提取到Cache中。这样的话,在使用Texture来保存汉字的位图信息的同时,对于每个汉字,我们还要定义一个结构,然后用一个东西把它串起来,综合它们2个,也就实现了我们所要的Cache了。刚开始,我所定义的结构是这样的:

struct Char{

  char hz[3];   // 保存汉字

  int frequency;// 使用频率

  RECT rect;    // 这个字对应位图的区域

  Bool isUsing; // 是否使用

}

对于汉字和英文,我在这里大概地讲一下原理:汉字是由2个字节保存,而英文只需要1个。而判断一个字是否是汉字,只需判断第1个byte是否>128(在原来的GB2312中,汉字的2个字节都是>128的。而新的GBK字库,汉字的第2个字节不一定>128,我想这是扩大了字库容量的原因。我的意思是说,如果给一个字符串你,随机给其中一个位置,然后我问你这个位置是什么?你的回答只能是:1 英文 2 汉字的首字节 3 汉字的尾字节。而这个问题的解法,为了稳妥起见,你必须从字符串的开始判断起)。也就是说在char[3]中,如果保存的是汉字,则char[0]保存汉字第1个字节,char[1]保存汉字第2个字节,第3个存放’/ 0’ ;如果是英文的话,则只用到char[0],其它的全部为’/ 0’ 。

接下来,对于使用char[3]来保存汉字,是否真的很合适呢?因为如果把它当作一个字符串来看的话,在查找时就需要使用 strcmp 来比较字符串了,这样一定是会影响速度的。如果不把它看作字符串(字符串的最后一个字节需要以’/ 0’ 结尾),只用char[2]的话,我们可以只是简单地调用宏MAKEWORD,把2个byte压成1个WORD。当把文字作为一个WORD来看的时候,这样查找比较时可以用WORD内建的==操作,这样要比调用strcmp函数要快得多。

int frequency用来标志每个WORD的使用频率。设想,如果一个字已经存在于Cache中,以后每对它调用一次,就让frequency++。这样做还有一个用意是,是否可以在一个合适的时候,以frequency为参照来对这整个Cache排个序,把常用的字放在前面。那么在显示时,可以先在Cache中查找所要显示的字是否已经存在于Cache中,如果有则直接显示,没有的话才需要采取某种手段将字加入到Cache中。一些常用的字(像:我、的、着、了、过……),使得显示的速度将会大大提高。

其实上面说了半天的Cache,它具体是什么呢?其实就是指的最小绘制单位,在DirectX7里是Surface,而在DirectX8中就是Texture。使用它来存放显示过的汉字,这样,就不用每次都从字库中读取或是调用如TextOut这类GDI超慢的函数了。因为每次在绘制一个文字之前,都会先在这个Cache中找,有的话就直接画上去,没有才会调用TextOut操作。而这样做的原因,我们先设想一下:游戏一般会控制为30fps或是60fps的速度不停地刷新,如果在GameLoop中有任何的代码是龟速级的话,这样就会导致fps的最大数的降低,也就意味着在保证30fps或60fps的同时,能绘制到屏幕上的物体的数量减少了。这就是我们为什么要使用Texture来作为Cache的实现的原因。再一个,文字在屏幕上显示时一般会保持一段时间,这个时间可能是1秒-3秒,我们的游戏也就会相应地更新60fps或180fps,这是因为人们需要阅读它们。或者是一些如标题这样的文字,它们总是不会更新的,或是更新得很慢。我们完全可以在第一时间,比方说我们的画面有60fps,在第1个fps时,我们得知要显示文字”唐”,然后先在Cache中找,结果很糟:没有找到!这时马上用TextOut写到Texture上(现在还是属于第1个fps的时间范围内),而接下来的59个fps(甚至更多),都不用再调TextOut了,而是直接从我们的Cache:Texture上Copy到屏幕上,速度得到了保障。谈到GDI的函数,为了实现设备无关性,它们的速度都很慢。其实它们也不像说得那么慢,如果不是每一帧都要调用它们,也算是蛮快的^_^。那么这个RECT rect,就代表着这个文字所对应在Texture上的区域位置。

使用什么东西来把这n个Char串起来呢,一般会想到的是链表,原因无非有2个:1 随时有新的字加进来,而内存是不连续的 2 它几乎没有容量的限制(除非是内存用完了)。不过链表的访问速度是很慢的,如果使用像数组这样的东西就好了。仔细想想,在这里,我们用来存储的Cache,最大也就是256*256(理由上面说了),所以大小应该会是固定的。我们只需要在数组中的给每一个汉字加上一个标志,说明这个位置的使用情况。那么就使用数组吧,这样的话,访问的速度要更快一些,直接首地址+偏移量就够了,不必像链表,在查找时需要逐node访问。当然,我绝不会想到用new  Char来申请这个数组。因为这样做实在没有必要,请不要过于迷信自己的能力,在STL中已经有vector了,为什么还要自己写呢?^_^最后的一个bool成员变量isUsing,也就是上面所说,用来标志使用情况的。

实际的操作

上面考虑了那么多,我认为都是实际操作之前所应该有的。先谈谈如何显示吧,因为在DirectX8.1中已经将DirectDraw和Direct3D融合为DirectGraphics了。所以无法像原来那样了…………哦,实在有太多东西要讲了,我还是推荐几篇文章给你吧^_^:

http://vip.6to23.com/mays/develop/directx/200201/Geczy3Din2D.htm

http://vip.6to23.com/mays/develop/directx/200201/GESurface.htm

http://vip.6to23.com/mays/develop/directx/200112/2DGtoDX8.htm

http://vip.6to23.com/mays/develop/directx/200201/DX8adv2D.htm

接下来,我会假设你已经具备了在DirectX8.1中绘图的基本概念了,所以在你继续往下阅读之前,请务必先仔细阅读以上推荐的文章。

前面提到,需要一个vector来对应Texture上各个位置文字的信息,上面已经创建了一个结构Char,则这个vector的定义为:

    vector <Char> _vBuf;                    // 记录缓冲中现有的文字情况

首先,由于可以利用硬件的放大缩小机能,所以字体的大小精度要求不是很高,只需要支持16*16和24*24大小的字体就可以了。我们需要一个这样的初始化函数:

bool CFont::

/*-------------------------------------------------------------

LPDIRECT3DDEVICE8 pd3dDevice  ---  D3DDevice设备

char szFontName[]             ---  字体名(如: 宋体)

int nSize                     ---  字体大小, 只支持16和24

int nLevel                    ---  纹理的大小级别

-------------------------------------------------------------*/

Init( LPDIRECT3DDEVICE8 pd3dDevice, char szFontName[], int nSize, int nLevel )。

 

在DirectX8.1中,由SetTexture(…)所贴的图的大小,也就是Texture的大小,是有大小限制的,长和宽都必须是2^n,而且位图越大,所花费的显存越大,这样留给其他显示用的显存就少了。所以,必须根据需求的不同,来自定Texture(也就是Cache)的大小。因为汉字点阵大小的原因,所以从实用角度而言(比方说只是显示fps或是短小的标题),开辟一个64*64大小的Texture,才能满足最低情况下的需要(这时如果选择16点阵的话可以存放16个汉字,24点阵可以存放7个,依次类推……)。

根据设置,创建Texture:

    _TextureSize = 32 << nLevel;        // 纹理大小

    _TextSize   = nSize;               // 文字大小

    _TextureSize = 32 << nLevel;        // 纹理大小

   

    _RowNum = _TextureSize / _TextSize; // 计算一行可以容纳多少个文字

    _Max = _RowNum * _RowNum;           // 计算缓冲最大值

 

创建字体,还是需要使用Win32 API。也就是先创建一个HDC:

    _hDc = CreateCompatibleDC(NULL);

 

然后创建一个BITMAP和一个FONT,将它们与HDC关联起来。

    LOGFONT LogFont;

    ZeroMemory( &LogFont, sizeof(LogFont) );

    LogFont.lfHeight            = -_TextSize;

    LogFont.lfWidth             = 0;

    LogFont.lfEscapement        = 0;

    LogFont.lfOrientation       = 0;

    LogFont.lfWeight            = FW_BOLD;

    LogFont.lfItalic            = FALSE;

    LogFont.lfUnderline         = FALSE;

    LogFont.lfStrikeOut         = FALSE;

    LogFont.lfCharSet           = DEFAULT_CHARSET;

    LogFont.lfOutPrecision      = OUT_DEFAULT_PRECIS;

    LogFont.lfClipPrecision     = CLIP_DEFAULT_PRECIS;

    LogFont.lfQuality           = DEFAULT_QUALITY;

    LogFont.lfPitchAndFamily    = DEFAULT_PITCH;

    lstrcpy( LogFont.lfFaceName, szFontName );

   

    _hFont = CreateFontIndirect( &LogFont );

    if ( NULL == _hFont )

    {

        DeleteDC( _hDc );

        return false;

    }

   

(只需要创建一个字体大小的BITMAP即可)

    BITMAPINFO bmi;

    ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));

    bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);

    bmi.bmiHeader.biWidth       = _TextSize;

    bmi.bmiHeader.biHeight      = -_TextSize;

    bmi.bmiHeader.biPlanes      = 1;

    bmi.bmiHeader.biBitCount    = 32;

    bmi.bmiHeader.biCompression = BI_RGB;

   

(这里需要定义一个指针指向位图的数据:

    DWORD *     _pBits;         // 位图的数据指针)

 

    _hBmp = CreateDIBSection( _hDc, &bmi, DIB_RGB_COLORS,

        (void **) &_pBits, NULL, 0 );

    if ( NULL == _hBmp || NULL == _pBits )

    {

        DeleteObject( _hFont );

        DeleteDC( _hDc );

        return false;

    }

   

    // 将hBmp和hFont加入到hDc

    SelectObject( _hDc, _hBmp );

    SelectObject( _hDc, _hFont );

 

接着设置背景色和文字色:

    SetTextColor( _hDc, RGB(255,255,255) );

    SetBkColor( _hDc, 0 );

 

设置文字为上对齐:

    SetTextAlign( _hDc, TA_TOP );

 

创建Texture所需要的顶点缓冲:

    if ( FAILED( _pd3dDevice->CreateVertexBuffer( _Max * 6 * sizeof(FONT2DVERTEX),

        D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,

        D3DPOOL_DEFAULT, &_pVB ) ) )

    {

        DeleteObject( _hFont );

        DeleteObject( _hBmp );

        DeleteDC( _hDc );

        return false;

    }

   

创建Texture

    if ( FAILED( _pd3dDevice->CreateTexture( _TextureSize, _TextureSize, 1, 0,

        D3DFMT_A4R 4G 4B4, D3DPOOL_MANAGED, &_pTexture ) ) )

    {

        DeleteObject( _hFont );

        DeleteObject( _hBmp );

        DeleteDC( _hDc );

        SAFE_RELEASE(_pVB);

        return false;

    }

 

设置渲染设备的渲染属性:

    _pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE,   TRUE );

    _pd3dDevice->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA );

    _pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

    _pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE,     TRUE );

    _pd3dDevice->SetRenderState( D3DRS_ALPHAREF,            0x08 );

    _pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC,           D3DCMP_GREATEREQUAL );

    _pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );

       

    _pd3dDevice->SetTexture( 0, _pTexture );

    _pd3dDevice->SetVertexShader( D3DFVF_FONT2DVERTEX );

    _pd3dDevice->SetStreamSource( 0, _pVB, sizeof(FONT2DVERTEX) );

 

设置缓冲的最大容量

    _vBuf.resize( _Max );

 

这样,初始化完成了。接下来是如何把一个汉字写到Texture中,以及如何进行管理。定义函数:

// 得到文字在纹理中的位置

void CFont::

/*-------------------------------------------------------------

char c1   ---  文字的第1个字节

char c2   ---  文字的第2个字节

int & tX  ---  写入纹理中的坐标x

int & tY  ---  写入纹理中的坐标y

-------------------------------------------------------------*/

Char2Texture( char c1, char c2, int & tX, int & tY )

{

    WORD w = MAKEWORD(c1, c2);      // 把此字变为WORD

    vector<Char>::iterator it = find( _vBuf.begin(), _vBuf.end(), w );

    if ( it == _vBuf.end() )        // 如果没找到

    {

        it = find( _vBuf.begin(), _vBuf.end(), 0 ); // 查找空闲位置

        if ( it == _vBuf.end() )    // 缓冲已满

        {

            for(; it!=_vBuf.begin(); it-- )

            {

                it->hz = 0;

            }

//          Log.Output( "字体缓冲已满, 清空!" );

        }

 

        // 计算当前空闲的Char在缓冲中是第几个

        int at = it-_vBuf.begin();

 

        // 得到空闲位置的坐标

        tX = (at % _RowNum) * _TextSize;

        tY = (at / _RowNum) * _TextSize;

 

        // 设置这个Char为使用中

        (*it).hz = w;

 

        RECT rect = {0, 0, _TextSize, _TextSize};

        char sz[3] = {c1, c2, '/0'};

        // 填充背景为黑色(透明色)

        FillRect( _hDc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );

        // 往hBitmap上写字

        ::TextOut( _hDc, 0, 0, sz, c1 & 0x80 ? 2 : 1 );

       

        // 锁定表面, 把汉字写入纹理, 白色的是字(可见), 黑色为背景(透明)

        D3DLOCKED_RECT d3dlr;

        _pTexture->LockRect(0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);

        BYTE * pDstRow = (BYTE*)( (WORD *)d3dlr.pBits + tY * _TextureSize + tX );

       

        for (DWORD y=0; y<_TextSize; y++)

        {

            WORD * pDst16 = (WORD*)pDstRow;

            for (DWORD x=0; x<_TextSize; x++)

            {

                BYTE bAlpha = (BYTE)((_pBits[_TextSize * y + x] & 0xff) >> 4);

                if (bAlpha > 0)

                    *pDst16++ = (bAlpha << 12) | 0x0fff;

                else

                    *pDst16++ = 0x0000;

            }

            pDstRow += d3dlr.Pitch;

        }

        _pTexture->UnlockRect( NULL );

    }

    else

    {

        // 计算当前空闲的Char在缓冲中是第几个

        int at = it-_vBuf.begin();

 

        // 得到这个字的坐标

        tX = (at % _RowNum) * _TextSize;

        tY = (at / _RowNum) * _TextSize;

    }

}

以上代码中的注释已经很清楚了,相信无须我多言。这里唯一需要声明的是:原来所定义的Char结构是这样的

struct Char{

  char hz[3];   // 保存汉字

  int frequency;// 使用频率

  RECT rect;    // 这个字对应位图的区域

  Bool isUsing; // 是否使用

}

后来因为将char hz[3]合成为WORD,所以改为WORD hz。然后对于int frequency,这个词频应该如何表现,我一直没有想到很好的方法。frequency应该在何时++呢?是在每次被使用的时候吗?但是这样的话,上面说过,游戏是以60fps的速度在刷新,如果停上1分钟的话,变量很快就会溢出了。就算是使用像是DWORD或__int64这样的巨型变量保存,也是不安全的。除非能在某个合适的时候将frequency清零,但是这个“时候”是什么时候呢?或者设置一个最大值,如65535,但是这样也基本上没什么用途,很快,所有在vector中的Char中的frequency都会++成65535的。回忆一下最初,是因为想把常用字放到vector的前面,以便每次find操作可以最快返回结果的。而经过我的测试,即使不做这样的优化操作,速度也是很快的,毕竟Cache不是很大,加上vector是连续内存空间。所以可以放弃使用int frequency。

然后对于RECT rect,因为没有了int frequency,意味着一旦将汉字写入到Texture,其位置就不会变动了。所以,很容易根据find函数操作后的iterator,直接计算出这个汉字所在Texture的位置。这样,RECT rect也不再必须。

而bool isUsing,它本身就是个鸡肋,要也可以,这样结构更加清晰。不过,直接通过观察WORD hz为0或非0,即可实现isUsing的作用了。

为什么要对结构Char这么精雕细琢呢?

1.     既然没有必要的东西,就应该删除

2.     Char结构的大小越大,vector所要求的内存越大

3.     小的结构,find可以更快地查找出所结果

为什么find会正常工作呢?这里我要大概地讲一下find是如何查找出所需的位置的:它只是简单地使用while从vector的begin一直遍历到end,逐个判断,直到找到为止。find要求必须实现自己的operator ==(),进一步跟踪到find的源码中,发现也是这样。于是前面的结构Char变成了现在这样:

    struct Char{

        WORD    hz;             // 文字

 

        Char() : hz(0) {}

 

        // 用作查找文字

        inline bool operator == ( WORD h ) const

        {

            return hz==h ? true : false;

        }

    };

是不是很简单?^___^

 

终于到了显示的函数了:

// 得到文字在纹理中的位置

bool CFont::

/*-------------------------------------------------------------

char szText[]  ---  显示的字符串

int x          ---  屏幕坐标x

int y          ---  屏幕坐标y

D3DCOLOR       ---  颜色及alpha值

int nLen       ---  字符串长度

float fScale   ---  放大比例

-------------------------------------------------------------*/

TextOut( char szText[], int x, int y, D3DCOLOR color, int nLen, float fScale )

{

    Assert( szText!=NULL );

 

    float sx = x, sy = y,

          offset=0, w=0, h=0, tx1=0, ty1=0, tx2=0, ty2=0;

    w = h = (float)_TextSize * fScale;

 

    char ch[3] = {0,0,0};

    FONT2DVERTEX * pVertices = NULL;

    UINT wNumTriangles = 0;

    _pVB->Lock(0, 0, (BYTE**)&pVertices, D3DLOCK_DISCARD);

 

    if ( -1 == nLen ||              // 默认值-1

         nLen > lstrlen( szText ) ) // 如果nLen大于字符串实际长度, 则nLen=实际长度

        nLen = lstrlen( szText );

    for (int n=0; n<nLen; n++ )

    {

        ch[0] = szText[n];

 

        if ( ch[0]=='/n' )

        {

            sy+=h;

            sx=x;

            continue;

        }

 

        if ( ch[0] & 0x80 )

        {

            n++;

            ch[1] = szText[n];

            offset = w;

        }

        else

        {

            ch[1] = '/0';

            offset = w / 2 ;

        }

 

        int a, b;

        Char2Texture( ch[0], ch[1], a, b );

   

        // 计算纹理左上角 0.0-1.0

        tx1 = (float)(a) / _TextureSize;

        ty1 = (float)(b) / _TextureSize;

        // 计算纹理右上角 0.0-1.0

        tx2 = tx1 + (float)_TextSize / _TextureSize;

        ty2 = ty1 + (float)_TextSize / _TextureSize;

 

        // 填充顶点缓冲区

        *pVertices++ = FONT2DVERTEX(sx,     sy + h, 0.9f , color, tx1, ty2);

        *pVertices++ = FONT2DVERTEX(sx,     sy,     0.9f , color, tx1, ty1);

        *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f , color, tx2, ty2);

        *pVertices++ = FONT2DVERTEX(sx + w, sy,     0.9f , color, tx2, ty1);

        *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f , color, tx2, ty2);

        *pVertices++ = FONT2DVERTEX(sx,     sy,     0.9f , color, tx1, ty1);

 

        wNumTriangles+=2;

 

        sx+=offset; // 坐标x增量

    }

    _pVB->Unlock();

    _pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, wNumTriangles );

 

    return true;

}

结束语

记得有一句名言: Keep it simple and stupid.在实现功能的同时,保持代码简单、清晰是非常重要的一件事。相信在往后的日子里,在不论是别人阅读或是你自己回顾的时候,你都会发现一如既往地遵守这个守则,是多么得重要!

相信通过上面我那无数的废话,加上代码中还算足够的注释,聪明的你一定能够明白这其中的原理了吧。如果以上的内容还不足以让你完全搞清楚的话,你可以登录我的主页:

炎龙工作室

上面不仅包括了上面所写的程序代码,还有一个用来演示效果的一个很简单的demo。

说明,以上所实现的CFont是包含在我的游戏引擎中的一个部件,而目前已经实现的部件包括有:

1.     CGameFrame(游戏框架类)  -----  封装了窗口及D3D设备的建立,需要派生出自己的子类

2.     CAudio和CSound(声音类) -----  支持wav/mid/mp3的播放

3.     CDirectInput(控制类)    -----  键盘、鼠标操作

4.     CDirectShow(视频类)     -----  支持avi/mpg/mov等的播放

5.     CSpriteX(精灵类)        -----  方便游戏中对精灵的控制

6.     CFont(字体类)           -----  中英文字体的显示

7.     CTimer(时间类)          -----  高精度时间的控制

8.     FPS(fps 类)             -----  fps的计算

9.     LOG(日志类)             -----  游戏中的错误反应以及状态记录

最重要的是,这个Game Engine完全是开放源代码的。关于更新的情况、版本说明以及源码下载,请随时关注我的主页!

接下来,我将会继续完善这个Engine,可能加入的有:高效粒子系统、斜45度角地图……

不只是玩游戏:用yuzu模拟器在PC上备份、测试和汉化你的Switch游戏库(附资源管理技巧) 本文详细介绍了如何利用yuzu模拟器在PC上备份、测试和汉化Switch游戏库,包括合法备份实体卡带、智能分类与元数据管理、DLC与更新的预测试、汉化补丁安全加载方案以及Mod管理最佳实践。通过yuzu模拟器,玩家可以高效管理数字资产、测试兼容性和实现内容定制,提升游戏体验。 阅读详情

相关推荐

TextOut显示多行文本的方法

发信人: enjoysoft(Enjoy — 享受生活), 信区: VC 标  题: Re: 请教SetDlgItemText与TextOut的用法区别 发信站: 饮水思源 (2006年10月12日21:55:43 星期四) 这个做法比较妥当,因为一旦窗口被重画,你用CClientDC画的字符串就不见了。响应WM_ PAINT才是正道! TextOut不支持换行的,反而可以把Tab、

xjbclz的专栏 1506

游戏中汉字显示的实现与技巧[ZZ]

作者:炎龙工作室 千里马肝 版本:v1.0 最后更新日期:2002-3-30 绪言 在游戏中,因为我们是中国人麻,通常都需要显示汉字,比方说交待剧情。而对于文字的显示,英文的显示要较其简单得多,因为只有26个字母,就算再加一些标点、符号什么的,用一张位图,就可以足以显示所有的单词了,而相关实现技巧,也比较轻松。 而中文的显示方法,要复杂得许多。记得原来在DOS下,汉字的显示都是读的UCDOS的点...

weixin_33895516的博客 180

三维(点云)语义分割概述以及方法总结

语义分割 语义分割需要两部分 classification: 需要全局信息 segmentation:更加依赖全局信息和局部信息 一般的网络结构是: 提特征-特征映射-特征图压缩(降维)-全连接-分类,其实就是encoder-decoder的过程, 比如在二维的的pspnet,fcn等等 0. 三维表示的数据结构 Point cloud 无序性 点云实际上是无序的,比如有8个点云,你放到...

renhaofan的博客 3万+

游戏中缓存加载纹理- 游戏渲染机制

A: 一书,动画加载AppDelegate中 1: 从文件中直接加载    CCSprite::spriteWithFile("文件完整名称") 2,间接加载 A:有些经常被用到或者需要频繁加载移除的一些资源,如果每次都从文件中加载, 程序的效率会变得非常低,通常的做法是将这些文件先读到缓存中,然后再从缓存中加载。 CCTexture2D *pTexture = CCTextureCa

u013321328的专栏 1007

ESP32-CAM入门教程

ArduinoESP32-CAM下载模块(接上USB就能用,很方便,如果没有买就只有资金用USB-TTL了)如下图两个合在一起如下图。

qq_48778364的博客 3万+

STM32N6570-DK ISP调试

将下载线接入STLINK V3EC接口,将BOOT1拨码拨至右边,即将其置为H,上电或已经上电的按复位,打开STM32CubeProgrammer,设置start address:0x70000000。板子消耗电流较大,即使缩短数据线接了USB供电依然不能正常点亮显示屏,最终通过板上CN2端子加了外部5V供电,仍然需要560mA的外部电流。3.烧录bin文件:STM32N6_ISP_IQTune_App_revC01-v1.1.0-trusted.bin。

liu的博客 1063

OpenGL_ES|WinCE纹理贴图的方式绘制字符串

网上看了几个例子,不是编译一堆错误,就是运行没反映 对OpenGL_ES还是不属性,估计是哪里设置不对。 尤其是坐标,搞晕了。但有时候又觉得其实很简单。思路:1: 创建内存DC ,为DC选择需要的字体,计算字符串在内存DC中的长宽; 2:创建与字符串长宽对应的设备无关位图,选入内存DC,并把字符串DrawText入内存DC;         3:处理设备无关位图的数据 (设置位图数据的alpha值,置换R/B值)  4:用设备无关位图数据生成纹理。         5:贴图......注:BMP图片的字节对

ezhong0812的专栏 1421

ZYNQ裸机LWIP双网口实现

文章目录前言一、LWIP双网口二、使用步骤1.部分说明2.部分代码总结 前言 一、LWIP双网口 由于项目需要多通讯口,查阅资料发现lwip支持多网口操作。于是,就开搞了。 二、使用步骤 1.部分说明 利用xemac_add函数在网卡链表中添加一个网卡,即使用netif块重复xemac_add操作。 先用轮询的方式实现双网口,后期修改为中断模式 2.部分代码 导入官方例程LWip echo server, 修改main函数,增加一个netif块 #if LWIP_IPV6==0 #if L..

weixin_35865280的博客 1万+

CEGUI中的汉字显示实现

几日前,用 CEGUI做界面,发现无法应用CEGUI的window中setText()函数直接显示中文。上网google一下,原来经过简单的字符转化可以显示中文(偷着乐,CEGUI太方便了)。 方法如下(引用):http://blog.youkuaiyun.com/kun1234567/archive/2008/04/11/2282761.aspxCEGUI使用utf8编码格式。这就意味着我们可以

OneView的专栏 760

每周技巧tc 问题处理技巧Teamcenter

(内部)Dataset和Tool相关之2.1——Tool轮询Contents Type和Type Name-赖良顺.docx。(内部)Dataset和Tool相关之3.2——编写Batch和Perl为客户端自动注册相关内容-赖良顺.docx。(内部)Dataset和Tool相关之1.1——Tools和Datasets参数定义-赖良顺.docx。(内部)Dataset和Tool相关之1.2——相关首选项定义及修改-赖良顺.docx。(内部)Dataset和Tool相关之0——概述-赖良顺.docx。

TC技术专栏 1229

【专栏导引贴】100+智能优化算法:从背景原理到代码实战(持续更新)

本专栏系统整理100+种现代优化算法,涵盖粒子群优化(PSO)、遗传算法(GA)、鲸鱼优化(WOA)等经典方法,以及白鲸优化(BWO)、非洲秃鹫优化(AVOA)等新兴算法。每篇详解包含生物启发原理、数学模型、代码实现及工程应用,采用统一编程风格,提供可直接运行的实例代码。专栏持续更新,旨在为研究者提供系统化的智能优化算法参考资料,实现从理论到实践的完整知识链。目前已收录23种算法,包括蚁群系统(ACS)、蝙蝠算法(BA)、天牛须搜索(BAS)等自然启发式优化方法。

qq_42212808的博客 3649

从LED点阵到汉字显示:74HC595与Arduino的创意玩法大全

本文详细介绍了如何利用74HC595移位寄存器与Arduino驱动8×8 LED点阵,实现从基础图案显示到复杂汉字动画的创意应用。内容涵盖硬件连接原理、扫描显示算法、字模提取与滚动显示技术,并提供了音频电平表和蓝牙信息牌等进阶实战项目,是学习嵌入式显示系统的实用指南。

3c4x5z6v7b的博客 589

GStreamer Plugin之Gst-nvdewarper

支持三种投影类型 NVDS_META_SURFACE_FISH_PUSHBROOM, NVDS_META_SURFACE_FISH_VERTCYL 和NVDS_META_SURFACE_PERSPECTIVE_PERSPECTIVE。关于每个平面的NvDewarperSurfaceMeta参数(projection_type, surface_index, and source_id)以及内存中有效的矫正平面(num_filled_surfaces)包含RGBA格式的一帧的缓存。矫正后的RGBA格式。

pvmsmfchcs的博客 366

C++ SFML实战:从wstring到动态汉字显示的完整避坑指南

本文详细解析了在C++ SFML开发中实现动态汉字显示的关键技术与避坑指南。从字体文件选择、字符编码处理到wstring转换,提供了完整的解决方案和优化技巧,帮助开发者解决中文显示乱码问题,并实现流畅的文本动画效果。

weixin_42520580的博客 228

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

汉字英雄游戏项目(C#为例)思路建议

通过拖动26字母至指定地点组成答案的汉字英雄游戏的制作建议

m0_64392501的博客 2390

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

UCGUI做汉字显示

1、弄个包含中文的字体,在这里我借用大多数例子里的 “C:/windows/Font/simhei.ttf”文件。把这个文件拷贝到Datafiles文件夹的Font文件夹里。 2、随便照着一个 .Font文件,自己写一个simhei.font文件。可以用TXT写,然后保存,有的朋友说需要保存为utf8编码格式,实际上是不需要的。 3、同时注意修改你加载到程序里的scheme文件,将里面的字体文...

weixin_33906657的博客 324

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

1.54英寸OLED模块从硬件到软件:驱动、汉字显示与菜单设计全解析

OLED(有机发光二极管)作为一种自发光显示技术,因其高对比度、低功耗和快速响应等特性,在嵌入式系统和物联网设备中广泛应用。其核心原理是通过控制每个像素的发光状态来呈现图像,无需背光模块。在技术实现上,驱动芯片如SSD1306或SH1106通过I2C或SPI接口与主控通信,管理显存数据。对于开发者而言,掌握OLED驱动不仅意味着能实现基础信息显示,更是构建高效人机交互界面的关键。例如,通过U8g2这类强大的图形库,可以轻松实现抗锯齿字体和复杂图形绘制,而针对汉字显示,则需要理解点阵取模原理,将字符转换为位图

weixin_34248705的博客 471
上一篇: 安防IT化 势在必行——访北京互信互通技术有限公司总裁李阳先生
下一篇: 基于Linux的VoIP开放协议和专有协议
messagebox
博客等级 码龄25年 15粉丝 3原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值