1.はじめに
冬休みに入って、アニメやゲームを嗜みながらダラダラと過ごしている、どうもowapoteです。
DxLibで3Dのアクションゲームを作ろうと思い立ち、作り始めてから1ヶ月が経ちました。
ゲーム制作の中間発表も無事に終わって作るものに悩んでいたその時、先生より「シェーダーやってみたら?」とのご提言をいただいたので後々やっていこうと思います。今年はバイトとアニメで忙しいので_(┐「ε:)_
唐突なんですけど、「夜中に任務を遂行、期限は夜明けまで。」みたいなのちょっとエモくないですか?
某夜戦型軽巡洋艦も「そうだ そうだ」と言っています。
これを作ってみたくなりました。
というわけで、時間経過で影が現実のように動く仕組みを作ってみました。
左下の円はマインクラフトの時計みたいなイメージで大丈夫です。
でも結構重いです。これだけなのに50fps前後になっています...
shadowMapよりもシェーダー自作の方が効率は良さそうですね。ですが今回は勝手ながら省略のためにshadowMapでやらせていただきます。
2.どうやって作るの?
三角関数で太陽の位置を表現しましょう。
XZ平面に円を描くように。
↑XZ平面でのイメージ
Zを北としたとき、南から光が斜めに当たるようにしたいので実装時にはZへ-1を掛けましょう。
そして太陽の"高さ"をどのように表現するのかというと、この画像のZをYにするだけです。なので実質Zです。この画像で表現するなら「X,Z,-Z」です。
その後に本格的にshadowMapで影を投影します。シェーダーができるようになったらまたこの辺りを改造してやってみます。
↑この辺りをうまくやれば行けそう?
3.ヘッダーの作成
昼夜サイクルということで名前はDayNightCycleということにしましょう。
DayNightCycleクラスを作っていきます!まずは宣言などから。
#pragma once
#include "DxLib.h"
class DayNightCycle
{
public:
DayNightCycle();
~DayNightCycle();
/// <summary>
/// 太陽光の角度更新
/// </summary>
void CycleUpdate();
/// <summary>
/// UI描画(左下の時計)
/// </summary>
void DrawCycleImage();
//getter群
float getLightAngleX() { return lightAngleX; };
float getLightAngleZ() { return lightAngleZ; };
private:
int time; //タイマー
int oneDayLength; //一日の長さ(今回は1秒=60fpsとして計算)
float lightAngleX; //光のX軸回転
float lightAngleZ; //光のZ軸回転
int imgHandle; //画像ハンドル
float imgAngle; //画像の回転
Vector2D<int> pos; //時計の表示座標
};
変数の簡単な説明
timeで時間経過を表現します。
oneDayLengthで一日の長さを決めます。
ただし、fpsによって一日の長さは結構変わってしまうのでご注意を。
lightAngleX/Zでゲームループで太陽の差し込む光を表現します。
YはとりあえずZで代用します。
変数のimgHandleとimgAngleとpos、関数のDrawCycleImageはマインクラフトの時計みたいなやつを表示するためのものです。
Vector2Dに関しては過去に執筆した記事があるので下のリンクをご覧ください。
Vector2Dを使わなくても、別に
int posX, posY; //時計の表示座標
とかでも全く問題ないので気にしたら負けです。
関数の簡単な説明
CycleUpdateでは主にtimeの更新、lightAngleX/Zの更新をします。
そして
画像表示もしたければこの中で画像の回転だけ
CycleUpdate関数の中で画像の描画をすると将来的に描画順などの関係で実装が大変になるだろうと考慮して、時計の描画はDrawCycleImageに任せちゃいましょう。
4.ソースの作成
そのまま定義した関数の実装に移ります。
↑時計の画像はこれを代わりに使います。
#include "DayNightCycle.h"
DayNightCycle::DayNightCycle()
{
pos = { 32,500 }; //ここはウィンドウサイズによるので各自で調整をする
time = 0;
oneDayLength = 1200; //60fpsであれば、20秒で1日が終わる設定
//画像の相対パスは各自で調整をする
imgHandle = LoadGraph("Day_Night_Cycle.png");
imgAngle = 0.0f;
}
DayNightCycle::~DayNightCycle()
{
}
void DayNightCycle::CycleUpdate()
{
time++;
imgAngle = 2.0f * PI * time / oneDayLength; //画像は一日で一周回転する
lightAngleX = cosf(imgAngle);
lightAngleZ = sinf(imgAngle);
if (2.0f * PI < imgAngle) //一周したら戻す
{
time = 0;
}
}
void DayNightCycle::DrawCycleImage()
{
DrawCircle(pos.x, pos.y, 66, GetColor(0, 0, 0));
DrawRectRotaGraphFast(pos.x, pos.y, 0, 0, 64, 64, 2.0f, imgAngle + (PI / 2.0f), imgHandle, TRUE);
}
X軸の角度はcos、Z軸の角度はsinでやれば良いですね。
この辺りはサクサク作れます。
5.ゲームループでの処理など
ここからshadowMap系の関数を使います。
予めシャドウマップの作成と標準ライト、シャドウマップエリアの作成は済ませておきましょう。
僕の場合は解像度4096、シャドウマップエリアはxyzどれも-1000.0f~1000.0fでやっています。
マシンのスペックや読み込むモデルのポリゴン数などに応じて各自で調整をしてください。
DayNightCycle.hのインクルードはお忘れずに!
DayNightCycle cycleDayNight = new DayNightCycle();
int shadowMapHandle = MakeShadowMap(1024, 1024); //シャドウマップの作成
cycleDayNight->CycleUpdate(); //時間の更新
Vector3D<float> lightDirection = { cycleDayNight->getLightAngleX(),cycleDayNight->getLightAngleZ(),-cycleDayNight->getLightAngleZ() };
if (0.0f <= lightDirection.y) //陽が沈んでいる時
{
SetLightEnable(FALSE); //標準ライトのみの無効化
//陽が沈んでいればいるほど暗く描画する
float alpha = 1.0f - lightDirection.y;
SetGlobalAmbientLight(GetColorF(alpha, alpha, alpha, 0.0f));
}
else //陽が出ている時
{
SetLightEnable(TRUE);
}
SetLightDirection(VGet(lightDirection)); //標準ライトの方向設定
SetShadowMapLightDirection(shadowMapHandle, VGet(lightDirection)); //影を出す方向は標準ライトと同じにする
これで準備バッチリです!
続いてやることはシャドウマップへの描画です。
そのままいきます。
//...中略...
//シャドウマップにモデルを描画する
ShadowMap_DrawSetup(shadowMapHandle);
Map::getInstance()->DrawMap();
ShadowMap_DrawEnd();
そしたら最後に影を落とし込みたいモデルをセットします。
//...中略...
//この中で指定したモデルに影を落とし込む
SetUseShadowMap(0, shadowMapHandle);
Map::getInstance()->DrawMap();
SetUseShadowMap(0, -1);
6.完成!
これで影が落とし込まれ、なおかつ東から登って南を通って西に沈む太陽っぽい挙動ができました。
僕の作品、冬休みはあまり進捗がない気がするので記事とかで記録するしかないですね...
プログラマブルシェーダーでやれたら激軽な太陽作れそうなのでやってみたいですね~、来年あたりでね。
それでは、良いDxLibライフを(^^)/