SSLR

スクリーンスペースローカルリフレクション(SSLR)実装してるんですが・・・。なんとなくでるところまではいったけど・・・。
うまくいかないわー。もーいやわー。

スクリーンショット 2014-10-07 10.28.12

 

レイトレースのfragシェーダ。だいぶ簡素化してあります。おかしいと思われる原因がありすぎて・・・ちょっと寝かせよう・・・。


vec4 RayTrace(in vec3 startP,in vec3 endP){

    vec4 startSSP = uProjectionMatrix*vec4(startP,1.0);
    startSSP.xy /= startSSP.w;
    startSSP.xy = startSSP.xy *  0.5 + 0.5; 

    vec4 endSSP = uProjectionMatrix*vec4(endP,1.0);
    endSSP.xy /= endSSP.w;
    endSSP.xy = endSSP.xy *  0.5 + 0.5; 

    vec3 vecSS = (endSSP.xyz - startSSP.xyz);
    vecSS.z=endP.z-startP.z;
    vecSS*=length(texelSize)/length(vecSS.xy);

    vec3 sampleP = vec3(0.0);
    float sampleDepth = 0.0;
    float diffZ = 0.0;
    vec4 color = vec4(0.0);

    for (int i = 1; i < 100 ; i++){
        sampleP.xy = startSSP.xy + vecSS*i;
        sampleP.z = startP.z + vecSS.z*i;

        if(sampleP.x < 0 || sampleP.x > 1 ||
	    sampleP.y < 0 || sampleP.y > 1 ||
	    sampleP.z < 0 || sampleP.z > uNearFar.y ){
	    break;
        }

        sampleDepth =linearizeDepth(texture(depthTexture, sampleP.xy).r, uNear,uFar);
        diffZ = sampleP.z - sampleDepth;
        float error = length(vecSS);

        if (diffZ>=0&& diffZ<error)
        {
            color = texture(colorBufferTexture, sampleP.xy);
            break;
        }
    }
    return color;
}

OpenGL サポート状況続き

前回調べた時にMacBookAirのBootcanp(Windows8)上、 Intel HD Graphic 4000で GLSL4.1 は使えないってことはわかった。調べてみると、どうやらOSXでは4.1まで使えるみたい。これってどうなのよー。Windowsの方もおねがいしますよー。

スクリーンショット-2014-02-10-09.22

こないだ作ったSMAAのサンプルもついでに対応してみた。 相変わらずwindowsでは意味不明な現象になるが、OSXではちゃんと動いた。Openframeworksのやつ、Windowsで動いてたのをOSXに持って行ったら、そのままだとうまく行かなくって、色々読んでたらOpenGLのバージョン指定が必要みたい。

ofSetCurrentRenderer(ofGLProgrammableRenderer::TYPE);

ってやるとOpenGL3.2が指定されてGLSL4.1が使えるようになった。Windowsでは何も言われなくても使えたよ・・・。まぁ最近のGLSL使うならこれ設定しておいたらいいのかな。

いろんな環境で試してみた感想。MacのShaderのコンパイラは結構うるさい。っていうかちゃんとしてる。ちゃんとしていないと動かない。ので、MACで作ればWindowsで基本的には動きそうな気がした。

OpenGLサポート状況(自分用)

GPUカードのドライバがアップデートされたりしてようやくRADEONとかGFORCEではGLSL4.3が実用的になってきてるっぽい。去年の8月くらいには4.2の機能使えねーじゃんとか思ってたのにな・・・。たまたまRADEONのアップデートが来てたので久しぶりにOpenGLのスペックをチェックしてみた。GEFORCE、RADEON共に最新のドライバではOpenGL4.4に入ってきてる。使ってるPCの利用状況調べてみた。

チェックはOpenGL Extensions Viewerでやるよ。
http://www.realtech-vr.com/glview/

メイン機のDell Presision GeForce GTX660ti  4.3まで100%対応済み。

スクリーンショット 2014-02-06 01.44.52

会社のiMacのやつ。RadeonHD6900M  同じく4.3 100%

スクリーンショット 2014-02-06 15.16.17

Macbook Air のIntel HD Graphics 4000
4.0まで100%だが、以降はかなり怪しい。もしかしてMACで見るとちがうのかな。

スクリーンショット 2014-02-06 01.33.01

3機とも開発に使ってるので一個だけグレード低いのが結構きついなぁ。たまに挙動がHD4000だけちがうなと思ってたのはこの辺りが原因なのかも。
それにしても、GeForce,Radeonともにもう4.3の機能サポートが100%ってことは、知らないうちにCOMPUTE SHADERも使えるようになってたんだ・・・。COMPUTE SHADER使ったやつで名前忘れたけどモーションブラーのアルゴリズムとかFoward+レンダリングとかいろいろ事例あったのでそのうちためしてみたいな。

SMAA、FXAAのofExample

前回の続き。SMAAの実装の紹介。

ソースは最新のこっちにした。
https://github.com/iryoku/smaa

SMAAで3回分のシェーダが必要なんだけど、shader3つも作るのはナンセンスだから1つにまとめてみた。でも、これだと1passはrgチャンネルしか使わないのでbit数の最適化ができてない。まぁいいか・・・。

バーテックスシェーダ

#include "../common/version.glsl"

/* uStep
 * 0:Edge Detection
 * 1:Weight Calculation
 * 2:BlendingWeight
*/
uniform int uStep=0;

layout(binding=0) uniform sampler2D uInpTex;
vec2 texSize = vec2(textureSize(uInpTex, 0));
#define SMAA_RT_METRICS vec4(1.0/texSize,texSize)
#define SMAA_INCLUDE_PS 0
#define SMAA_GLSL_4 1
#define SMAA_PRESET_ULTRA 1
#include "SMAA.hlsl"

noperspective out vec2 texcoord;
noperspective out vec2 pixcoord;
noperspective out vec4 offset[3];

layout(location=0) in vec3 position;

void main(){
 texcoord = position.xy * 0.5 + 0.5;
 if(uStep==2){
 SMAANeighborhoodBlendingVS(texcoord, offset[0]);
 }else if(uStep==1){
 SMAABlendingWeightCalculationVS(texcoord, pixcoord, offset);
 }else{
 SMAAEdgeDetectionVS(texcoord, offset);
 }
 gl_Position = vec4(position.xy, 0.0, 1.0);
}

ピクセルシェーダ

#include "../common/version.glsl"

/* uStep
 * 0:Edge Detection
 * 1:Weight Calculation
 * 2:BlendingWeight
*/
uniform int uStep=0;

layout(binding=0) uniform sampler2D uInpTex;
layout(binding=1) uniform sampler2D uInpTex2;
layout(binding=2) uniform sampler2D uInpTex3;

vec2 texSize = vec2(textureSize(uInpTex, 0));
#define SMAA_RT_METRICS vec4(1.0/texSize,texSize)
#define SMAA_INCLUDE_VS 0
#define SMAA_GLSL_4 1
#define SMAA_PRESET_ULTRA 1
#include "SMAA.hlsl"

noperspective in vec2 texcoord;
noperspective in vec2 pixcoord;
noperspective in vec4 offset[3];
layout(location = 0) out vec4 fOut;

void main(){
 if(uStep==2){
 fOut = SMAANeighborhoodBlendingPS(texcoord, offset[0], uInpTex, uInpTex2);
 }else if(uStep==1){
 fOut = SMAABlendingWeightCalculationPS(texcoord, pixcoord, offset, uInpTex, uInpTex2, uInpTex3, ivec4(0));
 }else{
 fOut = vec4(SMAAColorEdgeDetectionPS(texcoord, offset, uInpTex),0.0,0.0);
 }
}

多分こんな感じ。GLSLは410以降が必要。
作業用のFBOはアルファチャンネルも使うのでRGBAが必要。あと、テクスチャ用のヘッダファイルがあるのでofTextureとして登録。


ofFbo::Settings defaultSetting;
 defaultSetting.numSamples=0;
 defaultSetting.textureTarget=GL_TEXTURE_2D;
 defaultSetting.height=768;
 defaultSetting.internalformat=GL_RGBA;
 defaultSetting.useDepth=false;
 defaultSetting.useStencil=false;
 defaultSetting.width=1024;
 defaultSetting.wrapModeHorizontal=GL_CLAMP_TO_EDGE;
 defaultSetting.wrapModeVertical=GL_CLAMP_TO_EDGE;
 defaultSetting.numSamples=false;

fboRender_.allocate(defaultSetting); //入力用
 fboAA_.allocate(defaultSetting); // AA結果表示用
 fboSmaaWk_.allocate(defaultSetting); //作業用

//SMAAテクスチャ
 texSmaaArea_.allocate(AREATEX_WIDTH,AREATEX_HEIGHT,GL_RG8,false);
 texSmaaArea_.loadData(areaTexBytes,AREATEX_WIDTH,AREATEX_HEIGHT,GL_RG);
 texSmaaArea_.setTextureMinMagFilter(GL_LINEAR,GL_LINEAR);

texSmaaSearch_.allocate(SEARCHTEX_WIDTH,SEARCHTEX_HEIGHT,GL_R8,false);
 texSmaaSearch_.loadData(searchTexBytes,SEARCHTEX_WIDTH,SEARCHTEX_HEIGHT,GL_RED);
 texSmaaSearch_.setTextureMinMagFilter(GL_NEAREST,GL_NEAREST);

処理はこんな感じ。blendモードとかちゃんときっておかないと動かない。

 glDisable(GL_BLEND);
 glClearColor(0,0,0,0);

shaderSmaa_.begin();
 fboAA_.begin(false);
 glColorMask(GL_TRUE,GL_TRUE,GL_FALSE,GL_FALSE);
 glClear(GL_COLOR_BUFFER_BIT);
 shaderSmaa_.setUniform1i("uStep",0);
 shaderSmaa_.setUniformTexture("uInpTex",fboRender_,0);
 ssObj.draw();
 glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
 fboAA_.end();

 fboSmaaWk_.begin(false);
 glClear(GL_COLOR_BUFFER_BIT);
 shaderSmaa_.setUniform1i("uStep",1);
 shaderSmaa_.setUniformTexture("uInpTex",fboAA_,0);
 shaderSmaa_.setUniformTexture("uInpTex2",texSmaaArea_,1);
 shaderSmaa_.setUniformTexture("uInpTex3",texSmaaSearch_,2);
 ssObj.draw();
 fboSmaaWk_.end();

 fboAA_.begin(false);
 shaderSmaa_.setUniform1i("uStep",2);
 shaderSmaa_.setUniformTexture("uInpTex",fboRender_,0);
 shaderSmaa_.setUniformTexture("uInpTex2",fboSmaaWk_,1);
 ssObj.draw();
 fboAA_.end();
 shaderSmaa_.end();

Openframeworksのサンプルコードはこちら(vs2012のみー)

ちなみにこのなかのofShaderHelperは、#includeとか使うために適当に作ったやつだから信用できないwGLSL4系だと結構モジュール化とかやりやすくなってるから需要あると思うので、ちゃんとしたやつをofShaderの中で強化してもらえるといいなぁ。

サンプルではFBOへの出力を単純にポストプロセスでAAかけてて、汎用的に使えると思う。最適かどうかはおいておいて、FXAA,SMAAを比較することで、メリット、デメリットが見えてくるう。SMAAは画質重視で重い。FXAAはディテールの破壊があるが高速。カメラワーク、オブジェクトの動きが激しいシーンではFXAAにスイッチするとかして使い分けるのもいいかもしれない。てかDofとか入れるとほとんどAA不要な気がする。

スクリーンショット 2014-02-04 12.01.35

ところでSMAAなんだけど、MacbookAir(Intel HD 4000)でどうしてもうまく動いてくれないのだ。なんでだろー。わからーん。

ポストプロセスアンチエイリアス実装(FXAA,SMAA)

Defferdの課題の一つ、アンチエイリアスを実装してみた。AAについて小一時間調べた結果、試したのはFXAAとSMAA。調べてみるとこの辺りはOpenGLでの実装実績も多く、思ったよりも簡単に実装ができた。最近の高解像度化でギザギザはあんまり気にならないんだけど。

スクリーンショット 2014-01-23 23.48.10これは拡大しないとわからないけど、No AA

まずはFXAA。

fastというだけあってとにかく高速。1PassだしFPSへの影響は3%ぐらい。下記の出力画像はちょっと例題としては良くないけど、AA自体はかなり綺麗に入ってる。ブラー効果が強いとは色々なとこに書かれていたが、確かに強い。木目のDetailとか、マテリアルの細かい陰影など、特にカメラに近づいたもので、はっきり見えるはずの部分が潰れてしまうことがあった。遠くに細かいオブジェクトをたくさん出すと、Dofっぽくていい感じになってる。

実装については、元ネタはこのあたりのようだけど、Processingに含まれてるコードが簡単に移植できた。ちなみにポストAAってどのタイミングでかければいいのか悩んだけど、とりあえず一番最後すべてのプロセスが終わったあとにRGB8のFboを用意して出力するようにしてみた。MSAAほどではないけど、簡単にできるのでオススメです・・・。

スクリーンショット 2014-01-23 23.47.55FXAA (Fast Approximate Anti-Aliasing)

次に、SMAA。

FXAAはソース読むと理解できたんだけど、SMAAは深く読んでない。けど、3passで出力結果のイメージを見た感じ、pass1 でエッジ抽出(ベクトル)。 pass2で1のエッジのなかでAAやるかどうか判定(強さ)、step3 で2の情報つかって実行みたいなかんじ?読んでないから適当だけど、pass2 の結果をみるとかなりエリアを絞っているので、AAがかかるべきところをより厳密に決定しているようだった。結果は評判通り、画質に関してはすごくいい感じ。ただし、全体の処理コストの中ではそれほど大きくないが、FXAAの倍近く負荷がありそう。

実装についても少し書いておくと、ソースはこれ。HLSLとGLSL両方でつかえるっていう斬新なコードでした・・・。

・・・ちょっと長くなりそうなので、ここらで一旦寝ます・・・w(続き)

スクリーンショット 2014-01-23 23.48.03Subpixel Morphological Antialiasing

結論:

ポストプロセスのAAはいわゆる画像処理なわけだけど、画像処理でここまで出来るということに普通に驚いた。ジャギった画像に対してAAできるわけだから、3Dだけじゃなくていろんな応用ができそう。そういえば、写真を2値化したりエッジ抜いたりするときにジャギるの、綺麗にしたかったんだよなぁ・・・。試してみよう。

CubeTexutureのシームレスなMipMap

くそハマった・・・。

IBL(image based ligting)における光源にCubeTextureのMipMapを使うとき。(もちろんIBLじゃなくても。)
CubeTextureはそのままではそれぞれのイメージで独立した画像としてMipMapを作成するため、高レベルのMipMapを使うとCubeが綺麗に繋がらない。もうこれがどうやったら回避できるのか、諦めてMipMap用のシェーダー書き始めたところで発見した・・・。

CubeTexutureのシームレスなMipMapは下記1行。

glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

IBL実装したら写実感がめっちゃアップした。

harfbuzzでAlabia語レンダリング

今回はRTLのレンダリングをやってみた。右寄せではなく、Right to Left、つまり右から左に書くarabia語などのレンダリングなど。(「はちにんこ」、みたいな。)

普通に hb_buffer_set_direction( buffer, HB_DIRECTION_LTR ); でやればいいだけなんだけど。この指定を行った場合、普通にイテレーションすると、文末から順番に表示されることになる。普段のLTRのロジックでレンダリングした場合は左寄せになる。改行もいれると、文末から文頭へと表示されて、一応RTLになってるんだけど、基準点によるレンダリングが難しい。(boundingboxを計算しているが2回計算することになり効率が悪い。)

そこで、レンダリングロジックでリバースイテレーションすると、右寄せになり、改行順も正しくなる。注意点はレンダリングとペンの位置の計算順。

LTRの場合は :  レンダリング( PenX+OffsetX ) -> ペン移動( PenX += AdvanceX )
の順で描画を行っていくが、リバースの場合はAdvanceXが示す値がそのキャラクター自身のPEN移動となる。
よってRTLの場合は :  ペン移動( PenX -= AdvanceX ) -> レンダリング( PenX+OffsetX )
このようにレンダリングすることで右寄せとなり、正確に表示された。

ここまででharfbuzzのLTRは完了。日本語や英語についてはリガチャも含めて綺麗に表示された。次にアラビア語を入れてみる。表示結果は以下のとおり。アラビア語でググって出てきた文章なのでなんて書いてあるかは知らない(過激な言葉だったらどうしよう)そして、これがぜんぜん正しいのかも分からないけど、元のタイポグラフィと比べるとおかしい。

スクリーンショット-2013-11-15-11a.46

テキストレイアウトでは色々必要とされる機能があってそれが各言語によって用意されている。これらはiso15924のタグで用意されていて、harfBuzzでもこのコードからスクリプトを適用することができる。
http://www.microsoft.com/typography/otspec/scripttags.htm
アラビア語を指定する場合は
hb_buffer_set_script(buffer,HB_SCRIPT_ARABIC);
これは、hb-common.hで定義されている。この指定を行うと、下記の通り。バラバラだった文字が組み合わされ、それっぽくなった。これは単純なリガチャではなく、合字のような機能だと思う。スクリプトは標準ではLatin用とかになってるのかなー。

スクリーンショット-2013-11-15-11.46

hb_buffer_guess_segment_propertiesみたいなのもあるんだけどうまくは行かなかった。
それで、今回は混合フォントのレンダリングクラスを書いているんだけど、フォント毎に”arab”とかiso15924を調べて指定するのはなんか面倒。なんとかフォントファイルからscript tagを調べられないかと、色々試行錯誤してみた。

まずはどこにタグがあるのかを調べる。
http://kanji-database.sourceforge.net/fonts/opentype.html
これによると
GSUB、GPOS、JSTFの第一層にある。まだ、頭のなかにopentypeの構造がぼんやりとしかイメージできていないので、どこやねんそれと思いながら試行錯誤してみた。結果、こんなかんじでよさげだなぁと。自信はないけど。

unsigned int cntGSUB = hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, 0, NULL, NULL);
hb_tag_t* listGSub = NULL;
listGSub = (hb_tag_t*) malloc(cntGSUB * sizeof(hb_tag_t));
hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, 0, &cntGSUB, lstGSub);
hb_script_t script = hb_script_from_iso15924_tag(*lstGSub);
free(listGSub);

大概のフォントは複数のスクリプト値が入っている。秀英体とかだと6ことか入ってたりする。で、どれを初期値にするのかというのが問題になるんだけど、どうやら一番はじめのものがデフォルトレイアウトとして使えそう。アラビア語の traditional arabicフォントも一番目に HB_SCRIPT_ARABIC が入っていた。上の例はHB_OT_TAG_GSUBだけだけど、GPOSのみということもありえるのでJSTFも含めて調べておく必要がある。それで取れなければHB_OT_TAG_INVALIDとかにしておけば、上手いことやってくれそうな気がした。

リガチャー処理 (harfbuzz + freeType)

リガチャーが実装できた。

freeTypeありきで実装していたから色々勘違いがあったが、ようやく理解できた。手順をおさらいしておく。

簡単に言うとFreeType自体はあくまでFontFaceを弄るものだから、文字の組み合わせによって整形されるリガチャーの判断はできるわけがなく。文章を表示する場合harfbuzzでグリフのコードポイントを取得することになるのであるが、この時点でリガチャのコードポイントが返ってくる。よってFreeTypeにおいてはhb_glyph_info_t から取得したグリフ位置で表示を行えリガチャ表示が可能となる。

リガチャはデフォルトで有効になっているようだが、下記のように指定できる。
hb_feature_t feature= {HB_TAG(‘l’,’i’,’g’,’a’),1,0,static_cast<unsigned>(-1)};

リガチャーキャラクタは複数文字をひとまとめにして表示するため、hb_buffer_get_lengthが戻す値は実際の文字長よりも短くなる。カーニング値も2文字(or 3文字)分のアドバンスが確保される。リガチャの判断は次のhb_glyph_info_tポインタ のcluster値がジャンプするかどうかで行える。混合フォントを設計していたため、文字のポインタが取りづらく処理が複雑になったが、クラスタをもとにカウンティングを行うことで実現できた。

いざ自分でやってみると、普段何気なく見ているPC上のテキストがだいたいリガチャー効いててスゲーってなった。先は長い・・・。

OpenFrameworksでカーニングやらなんやら2

先日からHarfBuzzとずっと格闘してる。
難しいけどコーディングはDEEPなのですごくエキサイティングだ。

結論からいうと、openframeworksにおいてのカーニングについてはHarfBuzzとFreeTypeの相性が抜群なので、
ofTrueTypeFontUC にちょっと手を加えるだけで 日本語のプロポーショナル(palt)については対応出来た。

やりたい機能である、混合フォントについては
bool loadSubFont(string filename,float sizeRate=1.0f, float baseLineRate=1.0f ,int unicodeRangeStart=0x0000,int unicodeRangeEnd=0x0000);
みたいな感じでサブフォントをunicodeレンジに対して追加できるようにしてみた。
unicodeレンジを指定しない場合は後で読み込まれたフォントで上書きするイメージ。
最初に日本語フォントを呼んで LATENフォントを読めば 対応文字のみ LATENフォントで表示ができる。
ついでにいうとこのクラスはTextureつくっちゃうのでオンスレッドで動作できないので
(アウトライン)パスをスレッドでガンガン呼び出して使いたいのでofImageに習ってbUseTextureみたいなフラグもつけた。

で、次はレンダリング時の改行制御だ。これについては、1つ外側のレベルの話だ。
ICUとかを導入するのが筋っぽいんだけど大げさすぎるので簡単に実装してみた。
(というかパス通し疲れた・・・)

各文字要素(クラスタ)に対して、こんなかんじで改行可能かどうかをマーキングしていく。

breakable=__CharsNotEOL.find(src[i])==string::npos &&
(isCJK(src[i])||(i<len-1?isCJK(src[i+1]):true)||__CharsBreakable.find(src[i])!=string::npos) &&
(i<len-1? __CharsNotSOL.find(src[i+1])==string::npos :true);

日本語にしてみると、

・行末禁則文字でない
かつ(&&)
・CJK文字である または(||) 次の文字がCJK文字である または(||) 区切り文字である
かつ(&&)
・次の文字が行頭禁則文字ではない
場合は改行可能(笑)

概ねこんなかんじだと思う。
後は禁則文字セットなどを充実しておけばわりとちゃんと動きそう。

マーキングだけしたら、実際の改行判断はレンダリング部分でやる(次回)。
どんどん日本語限定仕様になっていく気がするな・・・。
ちなみにちょっと手を加えれば縦書きに対応できる。

と、いろいろ順調だったんだけど、なんかharfbuzzのclusterが
ちょいちょいおかしい値を返してきて壮大に位置がずれるのである・・・。
調べたらどうやら ligature だ。これは盲点だった。
ちゃんとunicode文字コードで用意されてるんですねー。
hb_feature_tでligaをoffってみたらちゃんと(っていうか若干重なって)表示された。
が、全く美しくないので次はligatureに対応しなくてはならない。

レンダリング freetypeに依存しすぎ&合成フォントしてるしで大改修が必要である・・・。
\(^o^)/オワ・・・

OpenFrameworksでカーニングやらなんやら

OpenGLの環境下でフォントのレンダリングについて色々調べている。

まずは標準のofTruetypeFontをつかってたんだけど、いろいろ問題がある・・・。
このクラスはFreeTypeをベースにしてる。余談だけど、グリフで遊ぶならOF007とかにバンドルされているFreeTypeはVersion2くらいなので、日本語の大部分のフォントで正しいアウトラインがとれないし、ビットマップレンダリングも崩れる事がある。これは最新のFreeTypeを入れれば解消される。

問題なのはカーニング。一見詰めは効いてるように見えるが・・・

・欧文フォントなら文字幅での詰めはあるが、カーニングpairが効かない。
・日本語フォントはpaltがとれないので等幅になる。

これらはGPOSに含まれるカーニング情報であり、FreeTypeのDOCに書いてあるとおり。
※ただしkernテーブルにカーニング情報がある場合はFreeTypeでもカーニングされるらしい。

しらべたところ、ICUやPangoというテキストレイアウトライブラリがあればできるっぽいとのとこで、
まだ試してないけどofxFontとかofxPangoというアドオンが見つかった。

とりあえず自分の用途にはちょっとあってなさそうだったので、Pangoコンパイルしてみた。

なんか大変だった・・・。

これからofで使ってみる。

追記:

どうやらharfbuzzを内部で使っていてこれだけでできそう。ややこしいな・・・