삽질하는플머

기존 게임으로 피드백

탐구생활/Uniscribe

글립 캐시를 외부에서 사용할 수 있도록 DLL로 만들고, 기존 게임의 랜더링엔진에 대한 아답터루틴을 구현한 뒤 간단한 문자열을 올려보았다. 입력기를 새로 만들는 것 보다 기존 게임에서 쓰이는 입력기의 글자출력부분만 바꿔치는 방법이 좀 더 빠른 작업이 될 것 같아서... 또 어차피 기존 게임도 아랍어 등의 다국어 처리는 필요하니까.




DLL의 크기는 일단 160kb. 폰트링크에 쓰인 MLang 이 COM 이므로, 이에 대응하는 코드를 LVCL에 짜넣으면 훨씬 작게도 만들 수 있을 것이다.

힌두어 + 아랍어 + 태국어도 테스트. 자알~ 출력된다. (뿌듯~ 뿌듯~)
예제문자열 : ठऑक्षझॉيُساوِيเข้าหน้าสู่หน้าหลัก




지나가는 팁 한조각.

구버전 델파이는 코드편집기에 안시문자열을 사용한다. 때문에 위와같은 문자열을 코드에디터에서 지정하기가 쉽지 않다.
UTF-8 편집을 지원하는 상위버전 델파이라면 #숫자 와 같은 형식을 WideString 에 바로 때려넣는것도 가능하지만 

  myWSTR: WideString =
    #2336#2321#2325#2381#2359#2333#2377#1610#1615#1587#1575#1608 +
    #1616#1610#3648#3586#3657#3634#3627#3609#3657#3634#3626#3641 +
    #3656#3627#3609#3657#3634#3627#3621#3633#3585;

구버전에서는 이또한 여의치 않다.

유니코드 구세주인 TNT컨트롤의 TntSystem.pas 에 정의된 다음 두 함수를 써보자. 

  function WideStringToUTF7(const W: WideString): AnsiString;
  function UTF7ToWideString(const S: AnsiString): WideString;

WideStringToUTF7 함수로 위의 다국어를 변환해주면 다음과 같은 안시문자열을 얻을 수 있다.

      '+CSAJEQkVCU0JNwkdCUkGSgZPBjMGJwZIBlAGSg5ADgIOSQ4yDisOGQ5JDjIOKg4' +
      '5DkgOKw4ZDkkOMg4rDiUOMQ4B';

코드를 안짜고 간단히 얻으려면, 빈 폼에 TTntEdit 를 하나 올리고 여기에 원하는 다국어를 넣어준 뒤 마우스 우측버튼을 눌러 "View as Text" 메뉴를 선택하면 Text_UTF7 프로퍼티에 생성된 문자열을 복사해올 수 있다.




이렇게 얻어진 문자열을 대충 다음과 같이 UCS2로 변환해쓰면 된다. 

const
  myASTR :  AnsiString =
      '+CSAJEQkVCU0JNwkdCUkGSgZPBjMGJwZIBlAGSg5ADgIOSQ4yDisOGQ5JDjIOKg4' +
      '5DkgOKw4ZDkkOMg4rDiUOMQ4B';
var
  myWSTR: WideString;
......
  myWSTR := UTF7ToWideString(myASTR);

뭐 구버전 델파이의 코드에디터에서 유니코드를 직접 지정할 일이 얼마나 될까 싶지만...
(적어도 오늘까지는 단 한번도 없었... ^^)

글립 랜더링 테스트.

탐구생활/Uniscribe
이미지 프로세싱을 거친 글립을 텍스쳐로 캐싱, 3D 화면에 올려보았다. 블러링된 그림자가 꽤 괜찮은 느낌을 주네.



블랜딩옵션과 색상을 조절하면 밝게 빛나는 느낌을 줄 수도 있다. 배경에 따라 적절히 써먹으면 좋을 듯 하다.



요렇게 해 주니 문질러서 군데군데 밝아진 느낌이 나는군.



한가지, 굴림 9~12 포인트 같은 놈들은 내부에 비트맵 정보를 따로 가지고 있다.
이 경우는 래스터라이징이 아닌 비트맵 폰트를 찍어주는 편이 훠얼씬 깔끔한데... 해당 크기에 비트맵정보가 있는지 여부를 판단하는 방법을 아직 찾지 못했다. 고민하다가 "Force Monochrome" 이라는 속성을 두었는데, 만들고 보니 이렇게 하는 편이 글자가 씌여지는 위치에 따라 적절한 선택을 하는 데 오히려 도움이 될 듯 하다. 캐릭터 머리위에서 늘어나고 줄어드는 글꼴은 항상 래스터라이징을, UI에서 고정된 글꼴은 모노크롬을 쓰는 방식으로 말이다.

굴림 12포인트.

굴림 12포인트. 모노크롬 강제모드. (앞의 것과 미묘하게 다름...)


테스트 프로그램은 여기. 글꼴은 10개, 문자열은 1000개, 폰트별 글립도 1000개 까지 캐싱하도록 설정되어있다.
기본글꼴은 Arial 이며 SetFont 버튼으로 변경할 수 있다. MLang에 의한 폰트링크를 시도하고 실패하면 폴백목록의 폰트를 뒤져 적절한 폰트를 찾는다. 폴백목록은 각 폰트별로 설정할 수도 있고 기본목록을 쓸 수도 있다. 여기서는 기본값으로 Tahoma, Arial, Verdana, Mangal, Gulim, Latha 를 지정해주었다. (명색이 3D인데 뒤에다 삼각형이라도 돌려줄 껄 그랬나... 조금 썰렁하군...)





유니코드 문자열을 입력하기 위해 TNT컨트롤을 쓰고 있는데... 터보델파이다보니 동적생성을 해야 해서 여러모로 귀찮다.
그래도 한번 손에 익으니 예전 환경으로 돌아가기가 싫으네~~

아무튼 이제 에디터를 만들어볼 차례~~

글립에 대한 간단한 이미지 처리 작업

탐구생활/Uniscribe
지금 게임에서는 글자의 가독성을 높이기 위해 그림자를 찍는 경우 동일한 글자를 검게 찍어준 뒤 그 위에 글자를 올리는 방법을 쓰고 있다.



그런데 사실 단순한 한개짜리 그림자는 게임 화면에 묻혀서 잘 보이지 않는다. 때문에 외곽에 테두리가 둘러진 글자를 많이 써먹게 된다.



위와 같은 이미지를 만들기 위해 상하좌우에 여덟번 검은글자를 찍고 가운데에 색깔글자를 찍어주는 방법을 쓰는데, 코드의 뽀대는 둘째치고 아무래도 효율이 떨어지게 마련이다. 채팅창같이 텍스트가 많이 쓰이는 곳에서는 검은색 상자를 블랜딩한 뒤 글자를  찍어주는 등 부하를 덜고는 있지만 내내 숙제처럼 머릿속에 남아있었다.

새로 만드는 글립엔진에서는 이런 부분을 개선해보기로 했다. 만들어지는 글립 하나하나에 적절한 처리를 하면 위에서 이야기한 비효율적인 상황이 나아질 듯 하다. 일단 글자를 뚱뚱하게 만들어 외곽을 그려주기 위한 '확장', 그리고 테두리를 추출하는 '아웃라인', 좀 더 뽀대나는 그림자를 위한 '가우시안블러' 를 구현해 보았다. 달구경 만들 때 구현했던 32비트 픽셀용 코드를 8비트용으로 고쳐 써먹고 있는데, 몇달 넘게 방치했던 놈들을 다시 들여다보려니 뭐가뭔지 한동안 헷갈...



위에서부터 차례로 기본, 픽셀확장, 픽셀확장+아웃라인, 픽셀확장+아웃라인, 픽셀확장+R2 짜리 가우시안 블러링된 글자이다.
블러링된 그림자 위에 기본글꼴을 찍어주면~~ 흠~~ 그럴듯하겠는데~~

부지런히 3D 환경에 찍어봐야겠다.

글립 캐시 고민중...

탐구생활/Uniscribe
폰트별로 글립을 천개씩 캐싱한다고 기준을 정하고 적절한 텍스쳐 크기 및 캐시 갯수를 결정했는데
혹시나 싶어 여기저기 검색하다가 이런 글을 발견했다.
http://www.delphipages.com/forum/showthread.php?t=117319

GetFontUnicodeRanges() API 를 써서 폰트에 설정된 유니코드 글립의 정보를 얻어내는 예제인데
혹시나싶어 돌려보니 헐~~
Webdings 는 탈탈 털어서 480개가 전부. Tunga는 달랑 134개, SimSun-PUA 는 95개밖에 안된다.

좀 더 깔끔한 코드를 만들고 싶은 욕심은 일단 뒤로 미루고, 생각나는 대로 발코딩 해 보았다.
최대 텍스쳐 크기는 1024로 제한. 사실 이런거 고민할 때가 제일 재미있다.

먼저 기준폰트인 Tahoma 8포인트의 경우.



이 폰트에는 가로폭이 넓은 아랍글립이 포함되어있어 높이에 비해 너비가 상당히 넓은 편이다.
512*512 텍스쳐에 828개의 글립을 캐싱. 텍스쳐에서 사용하지 않게되는 낭비공간은 3% 정도로 괜찮은 수준.

굴림 11포인트의 경우.



512*512 텍스쳐에 1024개 글립을 캐싱. 게다가 여백없이 꽉 채우므로 메모리 낭비가 없는 완전소중 아이템.

굴림 72포인트로 크기를 키울 경우는



1024*1024 크기의 텍스쳐 10장에 1000개의 글립을 캐싱한다.
후덜덜 하긴 하지만, 어차피 게임에서 이렇게 큰 폰트를 표시할 일은 거의 없고, 텍스쳐 역시 필요할 때만 생성할 것이므로
메모리 폭주가 일어나지 않도록 최대 텍스쳐 갯수를 3~5개로 제한하는 코드만 추가하면 쓸만할 듯 싶다.

이제 다시 테스트~ 테스트~~


2010.6.8
-----------------
유니스크라이브는 한마디로, 유니코드 문자열을 Run이라는 단위로 쪼개고 각 Run에 해당하는 정보를 제공하는 도우미.
게임에서 이 물건을 써먹기 위해서는 글자가 뿌려지는 폭, 캐럿위치와 화면위치의 변환, 다음 캐럿의 위치와 다음 단어의 위치 얻기, 그리고 워드랩을 위한 단어끊기 정도를 구현하면 될 것이라 생각된다. ScriptCPtoX(), ScriptXtoCP(), SCRIPT_LOGATTR 등을 가지고 일단 구현은 완료. 나머지는 실제 3D 환경에서 에디터를 만들며 생각해보자.

예상못한 복병. 중국어와 베트남어.

탐구생활/Uniscribe

가벼운 마음으로 중국 야후에 들러서 간체 몇 개를 복사해 찍어보았다. 결과는 허거덩~~



기껏 만들어 둔 폰트링크와 폴백은 다 건너뛰고 뜬금없이 등장한 네모박스라니...
코드를 따라가보니 굴림체에서 중국어 간체에 대해 ScriptShape 가 무조건 성공했다는 결과를 낸다.
글립에는 네모만 달랑 던져놓고 말이다.

꼽사리로 집어넣었던 베트남어 역시 굴림체에서 성공으로 리턴. 결과는 네모박스. 장난하냐???

ScriptShape 를 처리하기 전에 ScriptItemize 결과값에 대해 MLang으로 미리 체크해서 폰트변경이 필요하면 그만큼 ItemRun 을 나눠주기로 결정. Shape 루프에서 폰트 링크와 폴백을 처리하지 않고 Shape 전단계에서 폰트 링크를 처리하고 링크에 실패한 ItemRun 만 Shape 중간에 폴백을 시도한다.

이 경우 힌두어의 공백문자가 자체적인 넓적한 공백 대신 기본폰트의 공백으로 대체되는 문제가 생겨난다. 하지만 이건 나름 장점이 될 수 있겠다. 공백문자를 폰트별로 준비하지 않고 기준폰트의 글립만 사용하게 되므로 메모리를 절약할 수 있으니까.

또 한가지 문제는, 아랍어같은 RTL 문자열에서 중간에 들어간 공백이 ScriptShape를 거치면서 LTR로 속성이 바뀐다는 것이다. 이때문에 기껏 잡아줬던 아랍어의 레이아웃이 예전처럼 틀어져버린다. 고민하다가 Shape - Layout 의 순서를 Layout - Shape 로 뒤집어주니 해결된다. 혹시나싶어 catch22.net 의 강좌를 열어보니 거기도 Itemize - Layout - Shape 순서로 되어있군. 나 뭐한거지...

아무튼 구덩이 두 개 새로 파주니 이제야 제대로 동작하는 중국어와 베트남어.



어제까지 작업으로 대충 리서치는 마무리된 줄 알았는데... 앞으로 또 무슨 문제가 튀어나오려나...


한가지 팁. ScriptShape 사용중에 표준컨트롤을 참조하게되면 얼토당토않은 에러가 발생한다. 폰트 폴백 만들기가 귀찮아서 리치에디트 컨트롤을 써먹어보려고 잔머리 굴리다 Uniscribe가 이상하게 동작하는 바람에 실패를 했는데, 폴백폰트 목록을 리스트박스에 임시로 넣어두고 꺼내려고 하니 동일한 문제가 생긴다. 유니스크라이브 루프 내에서는 윈도컨트롤에 접근하지 말자.