シェーダアート_2 視野角と影
wgld.org | GLSL: 視野角を考慮したレイの定義 |
wgld.org | GLSL: 法線の算出と簡単なライティング |
wgldさんのコードをみつつやっていきます。
wgldさんの、視野角を考慮したレイの定義 >フラグメントシェーダのコード
シェーダアート_1 球体レンダリング - 人生初心者の雑記
視野角については第一回で触れた。targetDepthを使わずに直接視野角を出したい。
mainの中
28,31行目
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
vec3 ray = normalize(vec3(sin(fov) * p.x, sin(fov) * p.y, -cos(fov)));
pの話は第一回でやった。
ray ここで視野角を使っている。fov は コードの 7,8行目に書かれている。angleを 60°として fov はその半分の角度のラジアンだ。
ー1<=p.y<=1
-resolution.x/resolution.y <= p.x <= resolution.x/resolution.y//スクリーンの横幅が縦幅より広いとき。
というのは、第一回で説明した。よって、y座標の視野角(図の、黒矢印の動く範囲)が確かに60度でおさまっていることがわかる。x座標については、60度よりも少し視野角が大きい。これはスクリーンの縦横比問題なのであんまり気にしない。normalizeはベクトルの大きさを1にするだけだ。
これで視野角とピクセル座標から、飛ばすべきrayの方向が計算できる。*1
33~41行目
レイマーチング。第一回で説明した。distancefuncが、物体との最短距離を表すものだということを確認しよう。
法線の計算
45,17~24行目
vec3 normal = getNormal(rPos);
「光が当たった」時、物体の法線を出している。17~24行目に定義した getNormal 関数 が法線を取るための関数である。原理を見ていく。
vec3 getNormal(vec3 p){
float d = 0.0001;
return normalize(vec3(
distanceFunc(p + vec3( d, 0.0, 0.0)) - distanceFunc(p + vec3( -d, 0.0, 0.0)),
distanceFunc(p + vec3(0.0, d, 0.0)) - distanceFunc(p + vec3(0.0, -d, 0.0)),
distanceFunc(p + vec3(0.0, 0.0, d)) - distanceFunc(p + vec3(0.0, 0.0, -d))
));
}
発想はこうだ。
法線のx軸成分について考えてみよう。
p(main内ではrPosを入れている、つまり物体表面にとても近い座標)のベクトルを、x軸方向へ+d,-d動かしたベクトルを用意する。(このdは極めて小さい数。コードでは0.0001となっている。)。それぞれのベクトルをA,Bとよぶ。Aについてdistancefunc,Bについてdistancefuncを呼ぶ。
それぞれの戻り値をAd,Bdとする。|Ad-Bd|の大きさ、そしてAd-Bdの向きが何を表す指標となるだろうか。*2
|Ad-Bd|が小さいとき、つまり「pを少しずらしても,物体との最短距離はさほど変わらない」ということだ。これは、その物体表面がx軸に対して平行、すなわちx軸方向の法線成分が少ないということである。
また図のように,|Ad-Bd|が大きいとき,逆にx軸方向の法線成分が多いのだ。
Ad-Bdの引き算の順序と符号に注意しよう。この符号はそのまま、x軸方向の法線成分の正負を表す。
同様のことがy,z方向にも言える。
たとえばpの座標が円の真上だった場合、法線は、xとy方向の成分はほとんどなく、z方向の成分だけが残るだろうし、計算してみればgetNormal関数もそのように返すのがわかる。
これで法線が取得できた。
光の計算
46行目
float diff = clamp(dot(lightDir, normal), 0.1, 1.0);
clampは、第一引数の値の範囲を 第二引数~第三引数 にする。はみ出す場合は、はみ出した側の境界の値になる。
物体に光が当たった場合、「光の方向に対して遮るように面をなしている部分」が明るい。言い換えると、「光の方向と法線の内積が-1に近い」部分が明るい。
だから、wgldさんのコードではそのままdotを取ってしまっているので間違いだと思う。そもそも,lightDirと物体、カメラの位置を考えると、逆光になっているはずなので、物体は全体的に暗くなるはずだが、そう,,,だよな?
float diff = clamp(-dot(lightDir, normal), 0.1, 1.0);
これで逆光であることが正しく表示されるはずだ。逆光を直すべく、光の方向を//11行目
const vec3 lightDir = vec3(-0.577, 0.577, -0.577);
とすると、いい感じに光が当たる。
こんな感じだ。wgldさんのところでも書いてるように、法線の出し方は他にもある(俺はこれしか知らんが。)まだまだ勉強することがあるなぁ。
次は オブジェクトの複製 かなぁ。俺全然理解してないのに。
シェーダアート_1 球体レンダリング
wgld.org | GLSL: レイマーチングで球体を描く |
GLSL Sandbox Gallery
3Dオブジェクトを使わずに球を描こう。
wgldさんのコードを題材にさせていただきます。
GLSLの知識はほとんどいらない。
wgldさんのリンクの、>フラグメントシェーダのコード を見ていく。
GLSL SandboxのCreate new Effect からGLSLのソースコードを書く場所にコピペして、記事を読みながらコードを書き換えて確認。
上から四行
precision mediump float;//わからん。おまじないと。計算精度について書かれているらしい。
uniform float time;//時間を取得できる特別な変数。今回は使ってない。
uniform vec2 mouse;//マウス座標を取得できる特別な...。今回は使ってない。
uniform vec2 resolution;//スクリーンの幅をry。使う。
本題 main()を見ていく
スクリーンのピクセル一つ一つにmain()が適用されていく
14行目
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
gl_FragCoord 四次元ベクトル。.xyとつけると、gl_FragCoord一次元目と二次元目の値をもつ二次元ベクトルを取得できる。
glFragCoordの値は{今mainが適用されているピクセルのx座標,y座標,z,w} である。zはデプス,wはクリップとからしいけどよくわからんし、ここでは使っていない。
gl_FragCoordのx,yの値は
0<=x<=resolution.x
0<=y<=resolution.y
よって p の値は、スクリーンが横に広いとすると、
ーresolution.x/resolution.y <= p.x <= resolution.x/resolution.y
ー1<=p.y<=1
まぁ、この値自体にさほど意味はない。重要なのは、スクリーンの中心の値がp.xy = {0,0}となったことだ*1
17~21行目
カメラの定義である
CPos カメラ位置
CDir カメラの向いている方向
CUp カメラの上方向
CSide カメラから見た座標
CSide = cross(CDir,CUp) に注目。カメラの視線方向のベクトルと、カメラの頭の方向のベクトルとの外積を取ると、カメラの右方向にベクトルができる。
つまり、カメラから見ると、CSideとCUpがそれぞれx軸、y軸方向のベクトルに見える。
21,24行目
float targetDepth = 1.0;
vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);
targetDepth は、視野角に関係する値である。rayというのは、カメラからでる光の方向である。
ここからの話は、スクリーン上のピクセルの色というのが、3d空間における何なのかについてよく考えていないと難しい(俺もよく理解できていない。(俺もというか俺だけかもしれないが))。
スクリーンのピクセルの色=カメラからスクリーン上のピクセルに光を飛ばしたときに当たった物体の色
targetDepth=スクリーンの3次元空間上の位置
と考えたら、俺はわかりやすかった
下の二枚が、物体とカメラとtargetDepthの関係を表したもの。大球が物体、小球がカメラ、平行四辺形がスクリーンのつもり
上はtargetDepthがカメラに近い場合。(具体的にはtargetDepth = 0.5ぐらい)スクリーン上の円が小さい。
下はtargetDepthがカメラに遠い場合。(具体的にはtargetDepth = 1.5ぐらい) スクリーン上の円が大きい。
視野角の話は次の記事にも出そう。
26~34行目
やっと本題だ。レイマーチング法。
レイトレーシングの一種だとか。よくしらん。原理はこうだ。
1.カメラから出た光の先端座標を記録する(最初はカメラの位置と同じ)
2.その光の先端と物体との直線最短距離を出す。その長さをLとする。
3.2のLの長さだけ光の進む方向に、光の先端を移動させる。
4.1~3を繰り返して、Lが一定値以下になれば当たったとみなす。
全能感UP! GLSLで進めレイマーチング « demoscene.jp
このリンク先でレイマーチングの処理が1ステップごとに見ることができる。
覚えておきたいレイマーチングの性質がある。
物体外部にある光の先端は、レイマーチング処理中に物体内部に入り込むことはないこと(計算誤差を無視すれば)
光の直線状に物体があれば、Lの値は必ず一定値以下になること(無限回ループできれば)
まぁ直感的にわかると思う。
wgldさんのコードでは、16ループでレイマーチングを終わらせている。
変数の役割はコードに書いてあるので省く。一番重要なのは、distancefuncの処理だ。
31,8~10行目
distanceFuncは、光の先端と物体(この例では半径1、座標{0,0,0}の球)との距離を出すものだ
distanceFuncの中身は超簡単。球の中心座標が原点にあるから、光の先端と球との最短距離は
「原点から光の先端までの距離」-「球の半径」
で求まる
37行以下
ループの最後に得た光の先端と物体との最短距離(distance)の値が、ある値以下の時、当たっているとみなす。
gl_FragColor = vec4(vec3(1.0), 1.0);
gl_FragColor に代入された値が、今mainを適用しているスクリーン上のピクセルの出力色となる。
これで終わった。この記事の内容は、レイマーチング等のアルゴリズムとはあまり関係ない部分も突っ込んだので冗長になったが、次からの記事はもっとアルゴリズムに焦点を絞っていけるようになると思う。
具体的には、影の付け方、視野角についての覚書になるとおもう。
*1:p.x,p.yの値をそれぞれをresolution.x,resolution.yの値で割ってないことに注意。上のコードでは、pの要素を両方とも「resolution.xとresolution.yのうち小さい方」で割っている。ためしに vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / resolution.xy; と書いてみよう。p = {(gl_FragCoord.x*2.0-resolution.x)/resolution.x,(gl_FragCoord.y*2.0-resolution.y)/resolution.y}となるが、この時表示されているのは楕円になるはずだ。これは、スクリーンにおけるx軸、y軸のスケールが異なってしまうためである。この意味は、この場合のスクリーン上のp = {0.5,0.5}の座標が、スクリーン中心から見て水平と45度を為していないことに注意するとわかると思う。
ピクセルシェーダ面白そう
こいつをみてくれ
https://www.shadertoy.com/view/4sjXzG
Inigo Quilez さんが作ったシェーダアート
なんとこれ、3Dオブジェクトは一切使っていない。ウィンドウ上のピクセル座標データのみ使用して描かれている。
俺は一時期MMdのシェーダ(hlsl)に興味を持ってたんだけど、文法がよくわかんないし、そもそもシェーダの知識技術が皆無だったから、全然さわらなくなった。でも、こういう記事を見つけて
GLSL Sandboxで遊ぼう
http://notargs.com/blog/?p=384
はえ~
なんだこれ。渋すぎる。リンク先記事の下側にある、黄色いビル群が迫ってくるような映像は、なんと百行以下のコードで書かれているのだから、ほんとに衝撃的だ。
こういう分野について詳しく調べると、シェーダアートとか、レイトレーシング、レイマーチングとか、例によって(レイだけに)よくわからん単語が出てくるけど、まあ置いといて。でもこういうムービーって、昔からひっそりと盛んだったらしい(小さな巨人的な)
Tokyo Demo Fest 2015 - 日本で唯一のデモパーティ
このサイトは2015年に正式に出てきたんだけど、他のdemosceneに関する日本語サイトは
ことごとく古いし、少ない
だからなかなか勉強の仕方がわからないくて困っている。そこで、いろんなGLSLシェーダコードを見つつ、勉強としてここに(初心者目線から)解説()を付けていこうと思う
参考にするサイト
wgld.org | GLSL: 様々な図形を描く |
全能感UP! GLSLで進めレイマーチング « demoscene.jp
Iñigo Quilez - fractals, computer graphics, mathematics, demoscene and more
ブラウザ上でシェーダの実行ができる
GLSL Sandbox
boost/coroutine編_Dxlibの非同期処理をstd::threadやboost/coroutineでやる
boostのcoroutineってライブラリがある。弾幕風とかluaとかにyieldってのがあって(あるよね?)関数を途中で止めて、再開するときはその止めたところから始めるってのが、c++でもできる。
これに関してなんだけど、日本語で他のブログとかにある情報探したんだけど、
古い。もうそのコードでは動かない
仕方ないから本家のサンプルを引っ張ってくる。
Asymmetric coroutine - 1.58.0
はい、さっぱりわからん。けどサンプルを実際に動かすと、たしかにfor文とかで、途中で戻ってくる。再開するときは、もとのループ変数を保持したままだ。
#include<boost/coroutine/all.hpp> #include<iostream> struct X { X(){ std::cout << "X()" << std::endl; } ~X(){ std::cout << "~X()" << std::endl; } }; int main(){ int i = 0; boost::coroutines::asymmetric_coroutine<void>::push_type sink( [&](boost::coroutines::asymmetric_coroutine<void>::pull_type& source){ X x; for (i = 0;; ++i){ std::cout << "fn(): " << i << std::endl; // transfer execution control back to main() source(); } }); sink(); sink(); sink(); sink(); sink(); std::cout << "sink is complete: " << std::boolalpha << !sink << "\n"; return 0; }
output: X() fn(): 0 fn(): 1 fn(): 2 fn(): 3 fn(): 4 fn(): 5 sink is complete: false ~X()
coroutineはスレッドを生成しているわけではないので、変数にvolatileはいらない。(たぶんね。もしかしたら見当違いなこといってるかもしれない。)
boost::coroutines::asymmetric_coroutine<void>::push_type sink( [&](boost::coroutines::asymmetric_coroutine<void>::pull_type& source){ X x; for (i = 0;; ++i){ std::cout << "fn(): " << i << std::endl; // transfer execution control back to main() source(); } });
ミソは、
boost::coroutines::asymmetric_coroutine
と
source()(↑でいうとforループの中)が呼ばれると、処理が戻ってくる。
ということ。それだけ。それさえ知っとけばできる。詳しいことはしらん
#include<boost/coroutine/all.hpp> #include<iostream> std::function<void(boost::coroutines::asymmetric_coroutine<void>::pull_type&)> Cor_generator(int& progress){ return [&](boost::coroutines::asymmetric_coroutine<void>::pull_type& source){ progress = 0; for (int i = 0;; ++i){ std::cout << "fn(): " << i << std::endl; // transfer execution control back to main() progress += 1; source(); } }; } int main(){ int progress; boost::coroutines::asymmetric_coroutine<void>::push_type sink( Cor_generator(progress) ); while (progress < 100){ sink(); } std::cout << "sink is complete: " << std::boolalpha << !sink << "\n"; return 0; }
boost::coroutines::asymmetric_coroutine
std::thread編_Dxlibの非同期処理をstd::threadやboost/coroutineでやる
std::threadってのを使ってみよう。
C++11で始めるマルチスレッドプログラミングその1 ~std::thread事始め~ - 冬でもスイカバーが食べたい
std::threadで検索すると、スレッドセーフがどーたらstd::mutexがかんたらでるけど、難しいことを覚えるのは難しいことをするときでええねん。いまからやるのはそんな難しいことじゃねえ。
std::threadの変数に関数オブジェクトを入れて、detachを呼び出すと、そのラムダ式の処理がが完全に管理外で動く。だから、自作画像ロード関数とかをstd::threadに渡してやると、勝手にホイホイやってくれる。
#include "DxLib.h" #include <iostream> #include <string> #include<future> // プログラムは WinMain から始まります int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { ChangeWindowMode(TRUE); if (DxLib_Init() == -1) // DXライブラリ初期化処理 { return -1; // エラーが起きたら直ちに終了 } bool volatile finished = false; int volatile progress = 0; std::thread th{ [&]()->int{ int max = 5000000; for (int i = 0; i < 5000000; i++){ auto p = new long(); delete p; progress = i * 100 / max; } finished = true; return 8; } }; SetDrawScreen(DX_SCREEN_BACK); while (ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0 && !CheckHitKey(KEY_INPUT_ESCAPE)) { if (!finished){ DrawString(30, 50, ("not yet finished:"+std::to_string(progress)).c_str(), GetColor(255, 255, 255)); } else{ DrawString(30, 50, "finished", GetColor(255, 255, 255)); } } DxLib_End(); // DXライブラリ使用の終了処理 return 0; // ソフトの終了 }
volatileってのが、変数のコンパイラ最適化を防ぐ役目があるらしい。無くてもちゃんと動く場合があるかもしれないし、あってもダメな場合があるかもしれないけど、そんな難しいことわからん。
Dxlibの非同期処理をstd::threadやboost/coroutineでやる
Dxlibでゲームのプログラムをいじいじしてる(全然できてないけど)
画像とかを読み込むとき非同期でやってみたいわけですよ。
でDxlibが非同期用の関数を提供してるんだけど、画像をCreateGraphとかで読み込むときだけにしか使えない。
だから、たとえば
で、Sffデコーダ作ったけど、自作関数そのものを非同期に出来るわけではないのよね
この機会に
std::threadやboost/coroutineってのをつかって、非同期処理をやってみようというわけ。Winapiに非同期処理のためのスレッドやらあるらしいんだけど、せっかくなら汎用的にc++がもともと持ってるやつをつかいたいしね。
std::thread編_Dxlibの非同期処理をstd::threadやboost/coroutineでやる - 人生初心者の雑記
boost/coroutine編_Dxlibの非同期処理をstd::threadやboost/coroutineでやる - 人生初心者の雑記
Githubにpushしてみる
してみた
https://github.com/Gitmoko/Sff
MUGENのSffv1をデコードしてBMP文字列をつくり、ユーザが、ハンドルを返すような画像処理関数をわたして、Group,Imageごとにmap配列をつくる
おしまい