先日の日記でOpenGLのシェーダーの話を書きましたが、
今度はみんな大好き
[要出典]DXライブラリのシェーダーの話です。
DXライブラリで3Dの描画をするときは、普通(標準関数で)はポリゴンを大量に描画して球を表現すると思います。
しかし、今回のプログラムでは、カメラの「視線」をシミュレーションすることにより、画像を描画します。
そのため、大量のポリゴンを用意しなくても、球なら「球」を1個置くだけで表現できます。
やることのイメージとしては、こんな感じです。

- 視線シミュレーションのイメージ
- sisen_simulation_image.png (30.07 KiB) 閲覧数: 314 回
今回の実験の前に、C言語で「視線シミュレーション」を行うプログラムを書いていました。
その出力の例がこれです。

- 視線シミュレーションにより生成した画像のサンプル
(4096×4096で出力したものをImagemagickで512×512に縮小したもの)
これをシェーダーに移植。長いのでps_2_0ではなくps_3_0にしないといけませんでした。
► スポイラーを表示
CODE:
float4 spherePos : register(c10); // {x,y,z,r}
float4 sphereColor : register(c11); // {r,g,b,alpha}
float4 lightPos : register(c12); // {x,y,z,nil}
float4 lightColor : register(c13); // {r,g,b,alpha}
float4 floorColor1 : register(c14); // {r,g,b,nil}
float4 floorColor2 : register(c15); // {r,g,b,nil}
float4 backGroundColor : register(c16); // {r,g,b,a}
float4 cameraPos : register(c17); // {x,y,z,nil}
float4 cameraDir : register(c18); // {x,y,z,nil}
float4 cameraX : register(c19); // {x,y,z,nil}
float4 cameraY : register(c20); // {x,y,z,nil}
#define EPS (1e-2f)
struct LineSphereHitPointInfo {
int n; // 交点の個数
float t[2]; // 交点のt
};
/*
* 直線P=ins+t*indと球面の交点を求める
* @param ins 入力直線の基準となる点
* @param ind 入力直線の方向ベクトル
* @param inc 入力球の中心と半径
* @return 交点の情報
*/
LineSphereHitPointInfo getLineSphereHitPoint(float3 ins,float3 ind,float4 inc) {
LineSphereHitPointInfo ret={0,{0,0}};
float3 t;
float a,b,c;
float d;
float s;
t=ins-inc.xyz;
a=dot(ind,ind);
b=2.0f*dot(ind,t);
c=dot(t,t)-inc.w*inc.w;
d=b*b-4.0f*a*c;
if(d0なので、ot[0]EPS || hp.t[1]>EPS)) {
float3 np;
float3 n,nn;
float3 nd;
float4 nextColor;
if(hp.t[0]>EPS) {
np=sp+hp.t[0]*d;
} else {
np=sp+hp.t[1]*d;
}
n=np-spherePos.xyz;
nn=normalize(n);
nd=reflect(d,nn);
nextColor=float4(0,0,0,1);
col=mixColor(sphereColor.rgb,nextColor.rgb,sphereColor.a);
doCalculateLight=true;
colPoint=np;
colN=nn;
} else if(sp.z*d.z=10)p++;
}
if(xxyy.y=10)p++;
}
if(p%2) {
col=floorColor2;
} else {
col=floorColor1;
}
col.a=1.0f;
doCalculateLight=true;
colPoint=sp+t*d;
colN=float3(0,0,1);
} else {
col=backGroundColor;
col.a=1.0f;
}
if(doCalculateLight) {
float3 toLight=lightPos.xyz-colPoint;
float3 hwv;
float lightValue;
LineSphereHitPointInfo sotp;
sotp=getLineSphereHitPoint(colPoint,toLight,spherePos);
if(sotp.n==2 && ((EPSEPS || hp.t[1]>EPS)) {
float3 np;
float3 n,nn;
float3 nd;
float4 nextColor;
if(hp.t[0]>EPS) {
np=sp+hp.t[0]*d;
} else {
np=sp+hp.t[1]*d;
}
n=np-spherePos.xyz;
nn=normalize(n);
nd=reflect(d,nn);
nextColor=getColorOfOnePoint2(np,nd);
col=mixColor(sphereColor.rgb,nextColor.rgb,sphereColor.a);
doCalculateLight=true;
colPoint=np;
colN=nn;
} else if(sp.z*d.z=10)p++;
}
if(xxyy.y=10)p++;
}
if(p%2) {
col=floorColor2;
} else {
col=floorColor1;
}
col.a=1.0f;
doCalculateLight=true;
colPoint=sp+t*d;
colN=float3(0,0,1);
} else {
col=backGroundColor;
col.a=1.0f;
}
if(doCalculateLight) {
float3 toLight=lightPos.xyz-colPoint;
float3 hwv;
float lightValue;
LineSphereHitPointInfo sotp;
sotp=getLineSphereHitPoint(colPoint,toLight,spherePos);
if(sotp.n==2 && ((EPS<sotp.t[0] && sotp.t[0]+EPS<1.0f) || (EPS<sotp.t[1] && sotp.t[1]+EPS<1.0f))) {
lightValue=0.0f;
} else {
hwv=normalize(-normalize(d)+toLight);
lightValue=dot(hwv,colN);
if(lightValue<EPS) {
lightValue=0.0f;
} else {
lightValue=saturate(pow(saturate(lightValue),1.2f));
}
}
col=mixColor(lightColor.rgb*col.rgb*lightValue,col.rgb,lightColor.a);
}
return col;
}
struct PS_INPUT {
float4 DiffuseColor : COLOR0;
float4 SpecularColor : COLOR1;
float2 TextureCoord0 : TEXCOORD0;
float2 TextureCoord1 : TEXCOORD1;
};
struct PS_OUTPUT {
float4 Output : COLOR0;
};
PS_OUTPUT main(PS_INPUT PSInput) {
PS_OUTPUT PSOutput;
float xoff=PSInput.TextureCoord0.x*2.0f-1.0f;
float yoff=PSInput.TextureCoord0.y*2.0f-1.0f;
float3 vec;
vec=cameraDir.xyz+cameraX.xyz*xoff+cameraY.xyz*yoff;
PSOutput.Output=getColorOfOnePoint(cameraPos.xyz,vec);
return PSOutput;
}
このシェーダーをDXライブラリを使ったプログラムから呼び出し、
無事先ほどの画像と同じような画像(もう少し荒いですが)を作成することに成功したのですが、
せっかくDXライブラリ+シェーダーにしたので、やはり画面を動かしたいです。
というわけで、球・カメラ・ライトの操作を可能にしました。
球には力(プログラム的には加速度)を加えることができるようにし、
カメラ・ライトは直接速度を与えて動かせるようにしました。
その結果がこれです。
[youtube]
[/youtube]
動画だと録画ソフトの影響かFPSが低いですが、録画しなければ640x480、1倍描画で約60FPS出ました。
動画で使用しているプログラムではまだ実装していませんでしたが、
ここで配布しているプログラムではn倍描画(n=2,4,8)にGraphFilterを使用することができ、
使用すると4倍描画および8倍描画の時は画質が改善しました。
ただし、自分の環境では640x480、8倍描画でGraphFilterを使用しようとすると、
1フレームの描画に1秒以上かかり、実用レベルではなくなってしまいました。
今回作成したプログラムを添付しました。
DXライブラリを自分でビルドし、使わない機能を省くことで、実行ファイルのサイズを減らしています。
副作用として、ログも文字化けしなくなりました。