Heliodorさんをフォローして、最新情報をチェックしよう!

マイページへ

Ci-enはクリエイターに対して、金銭的な支援を送ることができるサービスです。

投稿記事

2019年 03月の記事(5)

イラスト練習中

今回は液タブの調子が悪かったのでドット絵です。
例によって18禁なのでフォロワー以上限定です。

フォロワー以上限定無料

まずは無料プランで様子見を。 お気軽にフォローしてみて下さい。

無料
【 白銀ベリル 】プラン以上限定 月額:500円

先月以前に投稿された記事のため、この限定特典を閲覧するには[ バックナンバー購入 ]する必要があります。

月額:500円
購入する
\いいね・ツイートで応援!/

小ネタとか

Twitterに投稿した動画です。



製作中に良くある悲劇。

https://twitter.com/helio_dor/status/1106529628842151936

開発PCではちゃんと動くのに別のPCでこんなになるとか、だいたいビデオカードのせい。……すみません自分のせいです。


そんなに大きな敵キャラではないですが、多関節+半透明で液体を撒き散らしながら動いているので無駄に処理の重い雑魚敵です。

https://twitter.com/helio_dor/status/1108341285616672768

あまりに重かったので根本の部分から高速化しました。
そして何匹ぐらい出せるだろう?と試したら50~60匹ぐらい処理落ちせず出せたので、とりあえず一安心。

しかしこれを撮影したPCは結構古いものですが、それでもCore-i7(2600)なので、重いんだか軽いんだか微妙なところです。


そして閃いた。

https://twitter.com/helio_dor/status/1108341515510673408

……とは言っても具体的にどうなるかは不明です。





関係無いですがついでにこれも。

https://twitter.com/helio_dor/status/1110034384130695168

DMM GAMESの18禁ゲームブランドとエロ同人サークルで名前被りとはややこしい……。

フォロワー以上限定無料

まずは無料プランで様子見を。 お気軽にフォローしてみて下さい。

無料
\いいね・ツイートで応援!/

シェーダー内での計算精度

前々回のピクセルシェーダーの話で思い出した話。

HLSLでの浮動小数精度...なんですが、ときどきハマるんですよコレ。
時間経過で変化するようなエフェクトをピクセルシェーダーで記述するときに、タイマーの値を使って云々というのはワリとよくあることだと思うのですが、問題は時間変化する値の渡し方です。

私が作っているゲームはすべて固定フレームで動いているので、時間変化する値としてそのままフレームカウンタを渡していました。
つまり1フレームで1インクリメントされる値です。ゲーム開始後から1秒後には60になり、1分後には3600になり、10分後には36000になっています。
このように、一定の速さで時間変化する値をピクセルシェーダーに渡していたわけです。

myPixelShader->SetParameterInt("u_Time", myFrameCount);

みたいな感じです。そんでもってピクセルシェーダー内で

uniform float u_Time; // 外部から与えられる時間値。アニメーション用

...

float t = u_Time * 0.001;
float4 color = tex2D(sampler, float2(uv.x, uv.y+frac(t)));


みたいな感じにして、適当な精度で時間変化する値 t を手に入れていました。ところがこれ、最初のうちはうまくいくんですが、これをしばらく動かしているうちにだんだんと t の値がとびとびというか、カクカクになってくるんですよ。
最初は1フレームにつき 0.001 刻みだったものが、そのうち 0.01 刻みになり、さらに時間がたつと 0.1 刻みになる...みたいな。
もちろん浮動小数での計算なので Timer の値がすごく大きくなると t の相対的な精度がどんどん悪くなるのは当たり前なのですが、それにしても、精度の悪化速度が速すぎるんですよね。具体的には1分ぐらい動かしていると... つまり Timer に代入しているフレームカウンタの値が4000とか5000とか、そのぐらいになると、もうすでに t の精度が悪すぎてアニメーションがカクカクになるんです。

これに最初に気づいたのは、ヴィータ大脱出の背景(砂漠ステージでの赤っぽい背景とか、異空間ステージでの青っぽい背景など)のスクロールが、最初は滑らかに動いていたのに次第にカクカク動くようになっていく、という現象でした。

DirectX では浮動小数点の演算の精度を意図的に落として速度を稼いでいるという話は聞いたことがありますが、こんなにも悪くなるもんなんですかね??

まあそんなわけなので、HLSL側では4桁を超える整数と少数同士の演算はなるべく控えるようにして、GPUの外側で、つまり C++ プログラム側で10未満程度の値に落としてからシェーダーに渡すようにしました。

myPixelShader->SetParameterInt("u_Time", myFrameCount * 0.0001f);

みたいな感じですね。こうすると時間がたっても精度があまり落ちなくなります。ただ、「あまり落ちない」ってだけで確実に落ちてはいくので、

myPixelShader->SetParameterInt("u_Time", (myFrameCount % 3600) / 3600.0f);

のようにして、一定周期で 0 から 1 へ変化するようなタイマー値にしておきます。もちろん、1 から 0 に戻る瞬間と
エフェクト計算の繰返し周期を一致させておいて、1から0にタイマー値が飛んでもエフェクトが滑らかにつながるようにしておきます。



余談ですがこの背景の一番奥(右側の画像は例によって手前のキャラを消した状態)もやはりピクセルシェーダーで実現しています。
元はPhotoShopの「雲模様」で作った画像を、無限書庫と同じくピクセルシェーダーで歪めているだけですね。
(なんとなくゼク○クスの背景みたいなの出来るかなーとテストしたら上手くいったのでそのまま採用w)
\いいね・ツイートで応援!/

だいじなこと

いつも練習で描いているキャラの話です。

フォロワー以上限定無料

まずは無料プランで様子見を。 お気軽にフォローしてみて下さい。

無料
\いいね・ツイートで応援!/

ピクセルシェーダー

過去作品の話です。

ヴィータ大脱出のステージ3「無限書庫」は巨大な円筒形の部屋にいるという設定で、プレイヤーが移動するにつれ、まるで部屋が回転するように背景が変化するようプログラムしてありました。これは結構インパクトがあったらしく、開発者としてはニヤリといった感じでした。某所では、あの部分だけ3Dでやっているとか、円筒形のポリゴンに画像を張り付けて回転させているとか予想されていましたが、どれもハズレです。

あれは普通の長方形の板ポリにベタッとテクスチャを張っているだけで、円筒形のように見えているのは、そう見えるようにプログラマブルシェーダー(以下ピクセルシェーダー)で歪ませているからです。試しにオプションで描画モードを簡易描画に切り替えると、シェーダーを通さなくなるので歪みが無くなり、無限書庫の背景はただの平面になるのがわかると思います。

このように、ピクセルシェーダー内でテクスチャの取得座標をうまい具合に計算すれば円筒形のように歪んで見せることができますが、ここでややこしいのは、ピクセルシェーダー内で指定するのはテクスチャの座標であって画面の座標ではないということです。もしもピクセルシェーダーの引数で渡されるのがテクスチャ座標で、それを画面のどの位置に表示するべきかを計算せよ、というのであれば話は簡単ですが、そうではありません。引数で渡されるのは画面内の座標であり、その場所にテクスチャのどの部分を表示するのかを指定するのがピクセルシェーダーでの座標指定です。

コツは、まず普通に「長方形を入力すると内側から見た円筒(ぽい)形に歪む」関数を考えて、それから逆関数を導くというものです。まず長方形から円筒(ぽい)形への座標変換ですが、これは割と簡単で、次のように考えれば大まかな式を得られます。

まず話を簡単にするために、テクスチャ座標の原点はテクスチャ中央で、Y軸は上向きであるとします。普通に数学でやったような座標の取り方です。画面座標も同様にします。

さて、円筒形の内側にベタッと貼った画像は、まるで凹の字のように画面中央では原寸に近く、左右の端に近づくにつれて上下に引き延ばされるように歪んで見えます。

もう少し具体的に考えます。元の長方形の画像を、いくつもの細かい縦長の短冊に区切ってみます。シュレッダーにかけるようなイメージです。

これを並べた時、左右の端に近い短冊にほど上下にぐいっと引き伸ばし、中央に近い短冊は少しだけ引き伸ばし、ど真ん中の短冊はなにもいじりません。

こうすると四角形だった画像を、上下対称に凹のような形に歪ませることが出来ます。

これを実際のテクスチャと画面で考えます。前述の例と同じように、テクスチャを幅1ピクセルの短冊に区切って考えます。テクスチャ座標はU, Vとしましょう。Uは、注目している点がどの短冊に含まれているのかを表します。ゼロなら中央の短冊、正の値なら中央右側の短冊、負なら中央左側の短冊です。Vは、注目している点が短冊のどの場所にあるのかを示します。0なら上下の真ん中、正なら上側、負なら下側です。

そして、U, V にあるピクセルを画面上のどこに表示するのか、その場所をX, Y で表します。Xの正と負が画面の右と左に対応し、Yの正と負が画面の上と下に対応します。

たとえばX=U, Y=V とすると、テクスチャと画面の画像は全く同じになります。

さて、先ほどの短冊の話を思い出してください。各短冊は上下には引き伸ばしますが、左右に移動させたりはしません。つまり、短冊の水平方向の位置は、変換前と変換後で変わりません。Xは常にUと同じです。増えも減りもしません。なので、X=Uです。これは簡単です。

次に上下方向の式です。中心にある短冊は原寸のまま変化せず、左右の端になるほど、つまり中心から離れるほど上下に引き伸ばされていきます。つまりら上下方向の拡大です。拡大というのはつまり元の座標に係数をかける事ですから、Y=AVと書くことができます。Aが1.0なら原寸、1.0より大きければ拡大、小さければ縮小です。

しかし、短冊の上下の拡大率は場所によって異なります。中央の短冊は原寸なのでAを1.0にしないといけませんし、中心から離れるに従ってAの値は1.0より大きくなっていかなければなりません。つまり、AはUの値がゼロの時に1.0になり、Uの絶対値が大きくなるにつれ、1.0より大きくなっていって欲しいのです。上記の条件を満たす式をすごく単純に考えると、A=1+|U|と置けば良い事になります。Xがゼロの時、Aは1です。Uの絶対値が大きくなると、Aの値も大きくなります。つまり、

Y = (1+|U|)V

ですね。

ところがこの式をよく見ると、拡大率AはUに関する1次式になっていますから、Aは直線的に変化することになります。中心から外側にむかって、2つの台形が向かい合って置いてあるかのように歪んで見えるのです。これだとまずいので、適当に曲線を描くようにしてみます。一番簡単そうなのは二次式にすることです。A = (1+U^2) とするんです。そうすると、

Y = (1+U^2)V

となります。ただ、そのままUを2乗するだけだとかなりキツイ曲線になって歪みすぎてしまうので、適当な係数をかけて調整します。

Y = (1+kU^2)V です。

kを1.0未満で小さめの値にすると、なんとなくそれっぽく見えるように歪むと思います。

さて、忘れてはいけないのは、ここで考えた式は長方形→円筒(ぽい)形に歪ませる式であるという事です。つまり入力はテクスチャ座標で、出力は画面座標です。しかし実際に必要なのはその逆で、画面上の座標から元のテクスチャ座標を求める式です。なので、この式を逆関数にして使わないといけません。上で求めた式は

X = U
Y = (1+kU^2)V

ですから、これを

U = ???
V = ???

の形にしないといけません。逆関数を求めるのです。まず、X=Uでしたから、当然U=Xです。これは楽勝ですね。次にVの式ですが、

Y = (1+kU^2)V

の左右を入れ替えて

(1+kU^2)V = Y

で、ここから両辺に同じ計算を施していって、左辺にある余計なものを右辺に移動させていきます。今回は簡単で、両辺を (1+kU^2)で割ればよいです。

(1+kU^2)V/(1+kU^2) = Y/(1+kU^2)

すなわち

V = Y/(1+kU^2)

になります。これで、

U = X
V = Y/(1+kU^2)

という式を得ることができました。これをピクセルシェーダーで使ってやれば思った通りに歪んでくれます。具体的には、上式の結果を使って tex2D(U, V) とかやるわけです。と、まあこんな感じで式を決定していくのですが、わりと試行錯誤しながら砂漠ステージなどの背景を作った気がします。

ちなみにさっきからしつこく「円筒形」ではなく「円筒(ぽい)形」と書いていますが、本当に円筒形に見えるように歪ませるなら、二次関数ではダメで、楕円の式を元にしないといけません。ax^2 + by^2 = c みたいなやつです。これをいろいろいじれば、だいぶそれっぽくなるのではないでしょうか。


これは参考のために手前のキャラを消した状態。円筒(ぽい)形。


最後に、あまり関係ない話なんですが、ピクセルシェーダー内で、a % b (a, b は共にint)の結果が期待通りにならない、というバグ(仕様?)に驚愕したことがあります。この時 b は正の奇数だったのですが、計算結果が想定したものと全然違っていたのです。どうやらHLSLの整数同士の剰余演算子 % では、割る数(b)が2^n (nは正の整数) でないと正しく動かないみたいです。なので、a % b ではなく、(int)fmod(a, b) を使ってしのぎました。

\いいね・ツイートで応援!/

記事を検索