Для создания сглаженного шрифта можно воспользоваться полноэкранным сглаживанием (anti-aliasing'ом / multisampling'ом) и обычным системным шрифтом. Но сглаживание в OpenGL не поддерживается на достаточно хорошем уровне всеми видеокартами. К примеру, вот эта программа, несмотря на включённый multisampling...
...выглядит на моёи ПК так:
...хотя, может выглядеть и так:
Выходом может стать создание собственного шрифта на основе текстур с эффектом blend'а (смешивания), который имеет намного лучшую совместимость с "железом". Сначала надо напечатать отдельные буквы алфавита в jpeg'и (целая картинка со шрифтом, кажется, менее удобна и эффективна в плане производительности). Для удобства возьмём моноширинный (с одинаковой для всех букв шириной) шрифт, скажем "Courier New". Добавим к символам серый градиентный контур (для blend'а). Получится такая программа:
void paint ()
{
int x=33, y=7; int y2, x2;
WCHAR wFile [32];
char file [32], txt [2];
hDC=BeginPaint (hwnd, &PaintStruct);
hFont=CreateFont (150, 0, 0, 0, FW_BOLD, false, false,
false, RUSSIAN_CHARSET, false, false, 5, false, "Courier New");
hFontB=CreateFont (150, 0, 0, 0, FW_BOLD, false, false,
false, RUSSIAN_CHARSET, false, false, 3, false, "Courier New");
for (int i=32; i<256; i++)
{
if (!(i>126 && i<161))
{
hCompatibleDC=CreateCompatibleDC (hDC);
GetClientRect (hwnd, &Rect);
HBITMAP hbm=CreateCompatibleBitmap (hDC, Rect.right, Rect.bottom);
HBITMAP holdBM=(HBITMAP)SelectObject (hCompatibleDC, hbm);
HBRUSH hLinePen=CreateSolidBrush (RGB (0, 0, 255));
(HBRUSH)SelectObject (hCompatibleDC, hLinePen);
Rectangle (hCompatibleDC, -1, -1, Rect.right+2, Rect.bottom+2);
SetBkMode (hCompatibleDC, 0); txt [0]=i;
for (int ib=7; ib>=0; ib--)
{
if (ib>5)
SelectObject (hCompatibleDC, hFontB); else
SelectObject (hCompatibleDC, hFont); SetTextColor (hCompatibleDC, RGB (255-ib*32, 255-ib*32, 255-ib*32));
for (y2=y-ib; y2<=y+ib; y2++)
{
for (x2=x-ib; x2<=x+ib; x2++)
{
GetClientRect (hwnd, &Rect);
SetRect (&Rect, Rect.left+x2, Rect.top+y2, Rect.right, Rect.bottom);
DrawText (hCompatibleDC, txt, 1, &Rect, DT_SINGLELINE | DT_LEFT | DT_TOP | DT_NOPREFIX);
}
}
}
SelectObject (hCompatibleDC, hFont); SetTextColor (hCompatibleDC, RGB (255, 255, 255)); GetClientRect (hwnd, &Rect);
SetRect (&Rect, Rect.left+x, Rect.top+y, Rect.right, Rect.bottom);
DrawText (hCompatibleDC, txt, 1, &Rect, DT_SINGLELINE | DT_LEFT | DT_TOP | DT_NOPREFIX);
GetClientRect (hwnd, &Rect);
BitBlt (hDC, Rect.left, Rect.top, Rect.right, Rect.bottom, hCompatibleDC, 0, 0, SRCCOPY);
Gdiplus::Graphics g(hDC);
g.SetPageUnit(Gdiplus::UnitPixel);
Gdiplus::RectF bounds(0, 0, float(Rect.right), float(Rect.bottom));
Gdiplus::Bitmap bg(hbm,0);
CLSID imgClsid;
UINT num, size, j;
using namespace Gdiplus;
GetImageEncodersSize(&num, &size);
ImageCodecInfo* pArray = (ImageCodecInfo*)(malloc(size));
GetImageEncoders(num, size, pArray);
for(j=0; j< num; ++j)
{
if (pArray[j].FormatID==ImageFormatJPEG)
imgClsid=pArray[j].Clsid;
}
sprintf (file, "litera\\%d.jpg", i);
MultiByteToWideChar (CP_ACP, 0, file, -1, wFile, 32);
bg.Save (wFile, &imgClsid, NULL);
DeleteObject (hbm);
DeleteDC (hCompatibleDC);
}
}
EndPaint (hwnd, &PaintStruct);
PostQuitMessage (0); }
Далее загрузим полученный шрифт в тестовую программу, убрав фон символов и изменив градиент: выставим соответствующую цвету "альфу" (прозрачность) - чем темнее, тем прозрачнее. Примерно так:
void LoadGLTextures ()
{
HDC hdcTemp; HBITMAP hbmpTemp; IPicture *pPicture; OLECHAR wszPath [MAX_PATH+1]; long lWidth; long lHeight; long lWidthPixels; long lHeightPixels; GLint glMaxTexDim ; char texs [5] [32]={"blank.gif", "sky.jpg"};
char texd [16]={"data\\textures"};
char txt [_MAX_PATH];
int i2, xi, yi, cl, ri;
for (i2=0; i2<256; i2++)
{
if (!(i2>126 && i2<161) && !(i2>1 && i2<32))
{
if (i2<2)
sprintf (txt, "%s%s\\%s", AppDir, texd, texs [i2]);
else
sprintf (txt, "%sdata\\litera\\%d.jpg", AppDir, i2);
MultiByteToWideChar (CP_ACP, 0, txt, -1, wszPath, MAX_PATH); HRESULT hr=OleLoadPicturePath (wszPath, 0, 0, 0, IID_IPicture, (void**)&pPicture);
if (!FAILED (hr)) {
hdcTemp=CreateCompatibleDC (GetDC (0)); if (!hdcTemp) pPicture->Release(); else
{
glGetIntegerv (GL_MAX_TEXTURE_SIZE, &glMaxTexDim);
pPicture->get_Width (&lWidth); lWidthPixels=MulDiv (lWidth, GetDeviceCaps (hdcTemp, LOGPIXELSX), 2540);
pPicture->get_Height (&lHeight); lHeightPixels=MulDiv (lHeight, GetDeviceCaps (hdcTemp, LOGPIXELSY), 2540);
if (lWidthPixels<=glMaxTexDim)
lWidthPixels=1<<(int)floor((log ((double)lWidthPixels)/log (2.0f))+0.5f);
else
lWidthPixels=glMaxTexDim;
if (lHeightPixels<=glMaxTexDim)
lHeightPixels=1<<(int)floor ((log ((double)lHeightPixels)/log (2.0f))+0.5f);
else
lHeightPixels=glMaxTexDim;
BITMAPINFO bi={0}; DWORD *pBits=0; bi.bmiHeader.biSize=sizeof (BITMAPINFOHEADER); bi.bmiHeader.biBitCount=32; bi.bmiHeader.biWidth=lWidthPixels; bi.bmiHeader.biHeight=lHeightPixels;
bi.bmiHeader.biCompression=BI_RGB; bi.bmiHeader.biPlanes=1; hbmpTemp=CreateDIBSection (hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0);
if (!hbmpTemp) { DeleteDC (hdcTemp); pPicture->Release (); }
else
{
SelectObject (hdcTemp, hbmpTemp); pPicture->Render (hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0);
long i, n=lWidthPixels*lHeightPixels;
if (i2>=32) {
for (i=0; i<n; i++) {
BYTE* pPixel=(BYTE*)(&pBits[i]); BYTE temp=pPixel [0]; pPixel [0]=pPixel [2]; pPixel [2]=temp; if (pPixel [0]<25 && pPixel [1]<25 && pPixel [2]>55) {
pPixel [0]=0;
pPixel [1]=0;
pPixel [2]=0;
pPixel [3]=0;
}
else
{
pPixel [0]=(pPixel [0]+pPixel [1]+pPixel [2])/3;
pPixel [1]=(pPixel [0]+pPixel [1]+pPixel [2])/3;
pPixel [2]=(pPixel [0]+pPixel [1]+pPixel [2])/3;
pPixel [3]=(pPixel [0]+pPixel [1]+pPixel [2])/3;
}
}
}
else
{
for (i=0; i<n; i++) {
BYTE* pPixel=(BYTE*)(&pBits[i]); BYTE temp=pPixel [0]; pPixel [0]=pPixel [2]; pPixel [2]=temp; pPixel [3]=255; }
}
glGenTextures (1, &texture [i2]); glBindTexture (GL_TEXTURE_2D, texture [i2]); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D (GL_TEXTURE_2D, 0, 4, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits);
DeleteObject (hbmpTemp); DeleteDC (hdcTemp); pPicture->Release(); }
}
}
}
}
}
Для сравнения выведем обычный и только что созданный шрифты:
void glPrintB (const char *fmt, ...){
float length=0; char text [256]; va_list ap; if (fmt==NULL) return; va_start (ap, fmt); vsprintf (text, fmt, ap); va_end (ap); for (int i=0; i<strlen (text); i++) {
if (text [i]==17)
text [i]=37;
}
glPushAttrib (GL_LIST_BIT); glListBase (base); glCallLists (strlen (text), GL_UNSIGNED_BYTE, text);
glPopAttrib ();}
float literaScale=0.25f, literaX=0.0f, literaY=0.0f, literaZ=-2.4325f;
char literaText [256]="";
void glPrintLitera ()
{
glEnable (GL_BLEND);
for (int i=0; i<strlen (literaText); i++)
{
int t=literaText [i];
if (t<0)
t+=256;
if (!((t>126 && t<161) || (t>-1 && t<=32)))
{
glBindTexture (GL_TEXTURE_2D, texture [t]);
glBegin (GL_QUADS);
glTexCoord2f (0.0f, 0.0f); glVertex3f (-literaScale/2.0f+literaX+i*literaScale/1.85f,
-literaScale/2.0f+literaY, literaZ); glTexCoord2f (1.0f, 0.0f); glVertex3f ( literaScale/2.0f+literaX+i*literaScale/1.85f,
-literaScale/2.0f+literaY, literaZ); glTexCoord2f (1.0f, 1.0f); glVertex3f ( literaScale/2.0f+literaX+i*literaScale/1.85f,
literaScale/2.0f+literaY, literaZ); glTexCoord2f (0.0f, 1.0f); glVertex3f (-literaScale/2.0f+literaX+i*literaScale/1.85f,
literaScale/2.0f+literaY, literaZ); glEnd ();
}
}
glDisable (GL_BLEND);
}
...
void paint ()
{
if (pC<10 && !(pLoad>0.0f))
pC++;
glPushMatrix ();
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable (GL_DEPTH_TEST); glLoadIdentity (); glTranslatef (0.0f, 0.0f, -2.4f);
glRotatef (-bgAngle, 0.0f, 0.0f, 1.0f);
bgAngle+=0.05f*fpsTweak ();
if (bgAngle>180.0f)
bgAngle-=360.0f;
glColor3f (1.0f, 1.0f, 1.0f);
glCallList (bgList);
char txt [256];
sprintf (txt, "Lorem ipsum dolor sit amet, consectetur adipiscing elit");
strcat (txt, ", sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
glBindTexture (GL_TEXTURE_2D, texture [0]);
glLoadIdentity (); glTranslatef (-1.0f, 0.85f, literaZ);
glScalef (0.25f, 0.25f, 1.0f);
glColor3f (1.0f, 1.0f, 1.0f);
glPrintB ("%s", txt); glLoadIdentity (); glTranslatef (-1.0f, 0.65f, literaZ);
glScalef (0.18f, 0.18f, 1.0f);
glColor3f (1.0f, 0.0f, 0.5f);
glPrintB ("%s", txt); glLoadIdentity (); glTranslatef (-1.0f, 0.45f, literaZ);
glScalef (0.12f, 0.12f, 1.0f);
glColor3f (0.5f, 1.0f, 0.0f);
glPrintB ("%s", txt); glLoadIdentity (); glTranslatef (-1.0f, 0.25f, literaZ);
glScalef (0.07f, 0.07f, 1.0f);
glColor3f (1.0f, 1.0f, 1.0f);
glPrintB ("%s", txt); glLoadIdentity (); glTranslatef (-1.0f, 0.15f, literaZ);
glScalef (0.07f, 0.07f, 1.0f);
glColor3f (1.0f, 1.0f, 0.0f);
glPrintB ("%s", txt); glDisable (GL_MULTISAMPLE_ARB);
glLoadIdentity (); glColor3f (0.5f, 1.0f, 0.0f);
glBindTexture (GL_TEXTURE_2D, texture [0]);
glBegin (GL_QUADS);
glTexCoord2f (0.0f, 0.0f); glVertex3f (-1.25f, -0.01f, literaZ); glTexCoord2f (1.0f, 0.0f); glVertex3f (2.5f, -0.01f, literaZ); glTexCoord2f (1.0f, 1.0f); glVertex3f (2.5f, 0.01f, literaZ); glTexCoord2f (0.0f, 1.0f); glVertex3f (-1.25f, 0.01f, literaZ); glEnd ();
glLoadIdentity (); glColor3f (1.0f, 1.0f, 1.0f);
literaScale=0.25f; literaX=-1.0f; literaY=-0.15f, literaZ=-2.4325f; sprintf (literaText, txt);
glPrintLitera (); glColor3f (1.0f, 0.0f, 0.5f);
literaScale=0.18f; literaY=-0.35f;
glPrintLitera (); glColor3f (0.5f, 1.0f, 0.0f);
literaScale=0.12f; literaY=-0.55f;
glPrintLitera (); glColor3f (1.0f, 1.0f, 1.0f);
literaScale=0.07f; literaY=-0.75f;
glPrintLitera (); glColor3f (1.0f, 1.0f, 0.0f);
literaY=-0.85f;
glPrintLitera ();
statusBar ();
glEnable (GL_DEPTH_TEST); glPopMatrix ();
SwapBuffers (hDC);
}
Как видно, шрифт получился более плавным и засчёт чуть затемнённых краёв - контрастным. Наибольший эффект достигнут при большем соотношении оригинального шрифта к напечатанному. Кроме того, полученный шрифт будет отображаться независимо от того, есть ли на компьютере кириллица. Также данный шрифт потребляет почти столько же, что и обычный, ресурсов, давая в приведённом выше примере чуть больше FPS.
Скачать полные версии программ.
Скачать игру, ставшую причиной написания данной заметки, с пропатченным шрифтом.
P.S. Оказалось, что хранить шрифт в одной картинке лучше: программа быстрее распаковывается, копируется и загружается. Шрифт можно грузить не целиком (со смещениями) в разные текстуры, а распределять по небольшим текстурам из исходного изображения. Так сделано в играх: Repaint, CornersGL и Checkers.
|