投稿記事

2019年 12月の記事 (5)

Heliodor 2019/12/30 20:00

イラスト練習中


いつものです。

今回のは行為に入る前の絵なので完全に健全な絵になってしまいましたが、一応いつも通りフォロワー以上で。

フォロワー以上限定無料

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

無料

【 500円 】プラン以上限定 支援額:500円

このバックナンバーを購入すると、このプランの2019/12に投稿された限定特典を閲覧できます。 バックナンバーとは?

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2019/12/24 22:07

メリークリスマス

クリスマスですがぼちぼち作業しております。


雨テスト中。
この部室は天変地異が起こり過ぎですね。

特にクリスマス絵とか用意してませんが、赤いブルマー+白ソックスだとちょっとだけクリスマスっぽいですよね(雪じゃないのが残念ですが……)。



それと、FANZA様でもヴィータ大脱出の半額セール始まっていました。


https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_088354/
ヴィー○ちゃんの衣装もちょっとクリスマスっぽいですよね。赤ければなんでもクリスマスという単純思考
まだ持っていなかったFANZA派の方はこの機会に是非。


フォロワー以上で少しだけテスト動画(雨が降っているだけ)が観られます。

フォロワー以上限定無料

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

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2019/12/15 18:04

WideCharToMultiByteの罠

Visual Studio でワイド文字 → UTF8 な変換をしたい時にはこれまでWideCharToMultiByte を使っていたわけですが、これは当然 Windows 専用な訳です。

そして、C++標準の変換方法がやっと用意された (std::wstring_convert) かと思ったら、C++17で非推奨になってしまいました。

なので結局 WideCharToMultiByte を使ってワイド文字から UTF8 にすることになったのですが、ここで変なハマり方をしました。

自分のPCでは普通に動くのに、他のPCでは落ちるんですよ。


……なぜ??
(でもこれはワリと良くあること)


良く調べてみると、Windows10 のPCでは正常動作しますが、Windows7 と Windows8 なマシンでは落ちます。


なんでやねん!
(これはさすがに珍しい)


ログを取ってみたところ、ディレクトリを再帰検索する時に無限再帰になり、スタックオーバーフローで停止してるんですね。
それはまずいので、万が一のために再帰深さが一定以上になったら強○停止するようにしたんですが、そもそもなぜ無限再帰になったのかって話ですよ。

具体的にどのディレクトリを調べているときに落ちるかログを取って見てみたのですが、おかしいことに検索対象のディレクトリ名が常に空文字列 "" になっているのです。
空文字列を指定するということは、カレントディレクトリを指定するのと同じです。
どんなに再帰しても常に出発点がカレントディレクトリになるので、決して終わりまでたどり着きません。


で、調べてみたら文字コードの変換に失敗していた事が原因でした。
具体的には WideCharToMultiByte で wide → UTF8 をやっているところで、

int length = WideCharToMultiByte(CP_UTF8, 0, wide_string, -1, NULL, 0, "?", &used_default_char);

のようにしてたんです。ところがマイクロソフトの説明をよく見るとですね、

https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-widechartomultibyte
> CP_UTF8
> UTF-8. With this value set, lpDefaultChar and lpUsedDefaultChar must be set to NULL.

て書いてあったんです。CP_UTF8 を使う時は lpDefaultChar と lpUsedDefaultChar を NULL にしておけよ、と。
つまり、CP_UTF8 を使っているのに、余計な引数を指定したことで関数が失敗し、文字変換が行われず、ディレクトリ名が全て空文字列 "" になってしまっていたわけです。


ちなみに同様のことが MultiByteToWideChar にも当てはまります。この関数で CP_UTF8 を指定した場合、フラグは 0 または MB_ERR_INVALID_CHARS にしないといけません。それ以外だとエラーになります。

https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar
> For UTF-8 or code page 54936 (GB18030, starting with Windows Vista),
> dwFlags must be set to either 0 or MB_ERR_INVALID_CHARS. Otherwise,
> the function fails with ERROR_INVALID_FLAGS.

※余談ですが、APIのヘルプを見る時は日本語ではなく英語版を読むことをお勧めします。
翻訳の段階でかなり情報が落ちていることがあるからです。特に文字列関係の処理で size と書いてあった場合、これがバイト数を意味するのか、文字数を意味するのか、日本語の説明では不明瞭な場合があります。


これ、「変換不能な文字を見つけたときの挙動」を指定しているだけなので、変換不能でない限りはエラーにならず無視してくれればいいのに……とぶつくさ言いながらも、直しておきました。


結局最後まで不可解だったのは OS によって挙動が異なる点です。
Windows7 と Windows8 では確かにエラーになって戻り値が 0 になるんですが、Windows10で実行すると普通に成功するんですよね。
それがハマった主な原因なのですが。

なんかOSによって挙動が違うという話もありますし
http://blog.livedoor.jp/blackwingcat/archives/976097.html



釈然としませんが、まあWindows7 はもうじきサポート終了するし、無償アップデートあるし、いずれは解決するのかなぁ……とも思うのですが、未だにXPでヴィータ大脱出動きますか?なんて問い合わせがあったりします。

昔のゲームを動かすためのスタンドアロン環境でも構築しているんですかね?
同人ゲーマー侮り難し……。

ちなみにヴィータ大脱出はDirectX9入ってればXPでもだいたい動きます

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2019/12/09 17:12

水の表現について

(おかしいな、エロゲーを作っているハズなのに……?)


先日、プール用のテスト動画を twitter で流したところ結構な数のRTいいねを頂いて、注目されすぎたらビビるディスコミュ系……じゃなくて、どんな風に実現しているのか少し解説します。

https://twitter.com/helio_dor/status/1198521698493952000
初期段階。
結構プールっぽく出来てると思ったんですが、今見るとちょっと微妙な感じ。プルプル。



https://twitter.com/helio_dor/status/1198923732149915648
後日、色々足したり調整したりしていかにもな感じに。
やはり水しぶきが付くと一気にそれらしくなりますね。



https://twitter.com/helio_dor/status/1199658082592124929
更に調整、水の色と水位変更機能搭載。
血の海やらLCLやらバスクリンも可能に(やりませんが)。


以前、3Dぽいものを2Dで表現するとかんなんとかって話をしたのですが、その後いろいろありまして、結局Zバッファやガチ3Dモデルは使わないでいます。

というわけで、今回のプールもZバッファとか使ってません(!)。
あと、水面の波ですが、波動方程式で解くとか難しい事はやらずに、加減乗除だけで表現しています。
プレイヤーが入るとそこを中心に円状に波が広がったりしますが、あれって三角関数すら使ってないんですよ。

でも結構それっぽく見えますよね?

プールにキャラクターが入り込む表現

プールにキャラクターが入るということは、水で満たされた箱にキャラクターが「めりこんで」いるという事です。
水位がプレイヤーの腰ぐらいだとしたら、プレイヤー下半身は水の中で、上半身は水の外です。
Zバッファも使わずにキャラクターよりも奥・手前の水をどうやって分けているのかというと、単にプールを薄くスライスして奥行き方向に重ねているだけです。
(テストで置いてあるハードルがおかしなことになっているのが分かりやすいかも?)

ちなみに、ひとつのスライスの奥行きが画面上で10ドットぐらいの幅があるので、キャラクターが10ドット以上奥行き方向に移動しないと、各スライスとキャラクターの前後関係が更新されません。

でも水面が揺れるので意外とバレないです。


水面の模様

水面に現れている独特の模様は「コースティクス」というもので、そのものズバリなテクスチャ画像を探してきて、水面に貼りつけているだけです。

1枚のコースティクスを張るだけだと模様が全く変化しないので、16枚のコースティクス画像をクロスフェードで切り替えながらアニメさせています。

このあたりは caustic, texture, animation, water, surface, public-domain などのキーワードを組み合わせて検索するといい感じでフリーなものが見つけられると思います。

この画像を自力で作る場合、元ネタとして使えそうなものに亀甲模様とボロノイ図があります。
どちらも様々なサイトで描き方が紹介されていますし、絵を描くツールを使ってる人なら、なんとなくこれを加工して作れるのではないでしょうか(というかクリスタならそういうプラグインあるかも?)。


水の向こう側の背景の歪み

これは適当な法線マップを使って背景をゆがませて描画しているだけです。
まず大前提として、プールの各スライスは奥から順番に描画しています(Zバッファつかってないし、半透明なので)。

この法線マップを自力で真面目に作ろうとしすると結構難しいと思うのですが、物理的に正確である必要は全くないので、単純にパーリンノイズ画像を用意するだけで充分それっぽいものが作れると思います。
(パーリンノイズぽい画像は Photoshop なら「雲模様」で作成できます)

各スライスを描画する直前に、その時点でバックバッファに書き込んである画像の内容を取得しておきます(これは各スライスを描画する直前に毎回行います)。
これは Unity でいえば GrabPass ですが、そんなモダンなものは使っていないので頑張って IDirect3DDevice9::GetRenderTargetData を使って取得します。

この時取得したバックバッファ画像は「そのスライスの向こう側に存在する景色」を表しているので、これをスライスのテクスチャとして使い、適当に歪ませて水色に染めてやります。

肝心の歪ませる方法ですが、法線マップを使い、そのRGB値に従って背景テクスチャを適当に歪ませます。
これはもちろんシェーダー内で行います。この時使う法線マップですが、これもコースティクス画像と同様にwater, normal-map, texture, public-domain などのキーワードを組み合わせて検索すると見つけられます(便利ですねぇ……)。


波の表現

水面部分の上下運動は、格子状に並んだ点を用意してそれぞれの点の上下運動をシミュレートした結果を用います。
とはいっても理屈は簡単で、点として用意するのは


struct POINT {
 float y; // 点の高さ(水深ではなく、平均水面からの高さを表す)
 float speed; // 点の移動速度(上下方向のみ)
};


これだけです。これを 10x10 とか、20x20 とか、必要なだけ格子状に並べることを考えます。各点は、その前後左右の点からの影響だけを受けます。

「影響」とはどんな影響かと言えば、「隣接する点と自分の点の高低差」です。
高低差が小さければ点は遅く動き、大きければ早く動きます。要するに高低差に比例した加速度を得るという事です。

具体的にはこんな感じ。


float K = 適当な比例定数 (0 以上 1 未満)
for (int z=0; z<ZCount; z++) for (int x=0; x<XCount; x++) {
 POINT p = GetPoint(x, z); // 自分
 POINT
L = GetPoint(x-1, z); // 左の点
 POINT R = GetPoint(x+1, z); // 右の点
 POINT
F = GetPoint(x, z-1); // 前の点
 POINT *B = GetPoint(x, z+1); // 奥の点

 p->speed += (L->y - p->y) K; // 左の点との高低差に比例して加速する
 p->speed += (R->y - p->y)
K; // 右の点との高低差に比例して加速する
 p->speed += (F->y - p->y) K; // 前の点との高低差に比例して加速する
 p->speed += (B->y - p->y)
K; // 奥の点との高低差に比例して加速する
}


こうすると各点の速度が求まるので、それに従って各点の y 座標を更新します。

for (int z=0; z<ZCount; z++) for (int x=0; x<XCount; x++) {
 POINT *p = GetPoint(x, z);
 p->y += p->speed; // y 座標を更新
}


で、このままだと発散してしまったり、いつまでたっても水面が落ち着かない事があるので、各点の y 座標が平均水面高さに戻るように小細工します


float WATER_DEPTH = 100.0f; // 水面高さ
float R = 0.01f; // 適当な定数 (0 以上 1 未満。小さい数にする)
for (int z=0; z<ZCount; z++) for (int x=0; x<XCount; x++) {
 POINT p = GetPoint(x, z);
 p.y += (WATER_DEPTH - p.y)
R; // 水面高さに戻るように
}


これであとは放っておけば、ワリとそれっぽい水面運動ができます。
キャラクターが水の中に入った時は、そのキャラクターの位置に対応する点の y 座標をぐいっと持ち上げたり、押し下げたりしてやるだけです。あとは勝手に波紋が広がっていきます。


float WAVE_HEIGHT = 20.0f; // 波の高さ
POINT *p = GetPointAtCharacter(); // 波紋の生成位置にある点を得る
p->y = WATER_DEPTH + WAVE_HEIGHT; // 適当な高さまで持ち上げる


端の点はどうするんだとか、そういう説明は省きましたが 水面、波、格子法、シミュレーション、アルゴリズムなどの単語で検索するとそれっぽいサイトが見つかると思いますので、参考にしてみてください。


その他

細かい所では、水面の傾きに応じて水面メッシュの頂点色を調整して陰影をつけてみたり、キャラクターが水の中を一定以上の速度で移動しているときはドット絵の水しぶきや泡を出してみたり、水底部分にコースティック模様を黒色で重ねて影っぽくしてみたりと、いろいろやっています。


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


……で、結局なんでプール作ってるのかって?
いやーそれはまだ秘密デスヨー(棒)

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

Heliodor 2019/12/06 17:10

また雑談

今回もゲーム製作とは関係無い話です。


只今DLsiteにて割引セール中です。
ヘリオドールの過去作、ヴィータ大脱出滅びの国の王女も今なら半額!


https://www.dlsite.com/maniax/work/=/product_id/RJ120676.html


https://www.dlsite.com/maniax/work/=/product_id/RJ197990.html

ヴィータ大脱出はもう6年前になるんですね……時の流れは早いものです(発売日を見てちょっと動揺しました)。
興味はあるけど買ってなかった、という方はこの機会に是非。




おまけ

例の体操着差分、結局描きたくなって描きました。
フォロワー以上で閲覧可能です。

フォロワー以上限定無料

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

無料

【 500円 】プラン以上限定 支援額:500円

このバックナンバーを購入すると、このプランの2019/12に投稿された限定特典を閲覧できます。 バックナンバーとは?

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

月別アーカイブ

記事のタグから探す

限定特典から探す

記事を検索