隣の田所さんのサーバが止まっていたので復旧してました。
「隣の田所さん」(ゲーム名)はWebサーバにRust言語で作られたものを使っており性能は良いのですが、いつの間にか止まっていることが多いんですよね・・・
ログインできない場合はこのページにコメント入れていただければ復旧しますね。
あと、このゲーム制作日記(主に制作者のサボり防止用)ですが、毎日だとあまり書くことがないので今後は1週間に1度、月曜日にまとめて書くことにします。
昨日は水分子をグリグリ動かしましたが、今日はその処理負荷をGPUに任せるために、Transform Feedbackが使えないか検討していました。
昨日は水分子を、画像を使わずにシェーダのみで描画するように変更しました。
今日は水分子を、空気中を飛び回っているイメージでグリグリ動かしました。
コードの変更箇所は以下の通りです。
let mut vertices: Vec<f32> = Vec::with_capacity(POINTS * 3);
let mut vertices_speed: Vec<f32> = Vec::with_capacity(POINTS * 2);
let mut rng = rand::thread_rng();
for _i in 0..POINTS {
vertices.push(rng.gen_range(-1.0..1.0)); // x
vertices.push(rng.gen_range(-1.0..1.0)); // y
vertices.push(0.0); // z
vertices_speed.push(rng.gen_range(-0.01..0.01)); // speed x
vertices_speed.push(rng.gen_range(-0.01..0.01)); // speed y
}
// move points
for i in 0..POINTS {
let base_pos = i * 3;
let base_speed = i * 2;
// x方向
if 1.0 < vertices[base_pos + 0] || vertices[base_pos + 0] < -1.0 {
vertices_speed[base_speed + 0] *= -1.0;
}
vertices[base_pos + 0] += vertices_speed[base_speed + 0];
// y方向
if 1.0 < vertices[base_pos + 1] || vertices[base_pos + 1] < -1.0 {
vertices_speed[base_speed + 1] *= -1.0;
}
vertices[base_pos + 1] += vertices_speed[base_speed + 1];
}
unsafe {
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(
gl::ARRAY_BUFFER, // target
(vertices.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
vertices.as_ptr() as *const gl::types::GLvoid, // pointer to data
gl::STREAM_DRAW, // usage
);
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
}
// draw points
unsafe {
gl::BindVertexArray(vao);
gl::DrawArrays(
gl::POINTS, // mode
0, // starting index in the enabled arrays
POINTS as i32, // number of indices to be rendered
);
}
unsafe {
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(
gl::ARRAY_BUFFER, // target
(vertices.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
vertices.as_ptr() as *const gl::types::GLvoid, // pointer to data
gl::STREAM_DRAW, // usage
);
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
}
Rustのコード上で描画位置を変更していますが、これだとポイント数が増えた場合に負荷がヤバそうです。
明日からはなるべくシェーダに仕事をさせられないか検討します。
昨日に引き続き、今日は円の半径に伴いアルファ値が変わるようにシェーダを調整しました。
#version 330 core
out vec4 Color;
void main()
{
vec3 target;
target.xy = (gl_PointCoord - 0.5) * 2.0; // 座標値変換 (0, 1) -> (-1, 1)
float r2 = target.x * target.x + target.y * target.y; // 半径の2乗
if (1.0 < r2) {
discard;
}
Color = vec4(0.0f, 0.0f, 1.0f, 0.4 * (1.0 - r2));
}
これだけですが効果は抜群で、水分子画像を使わずにシェーダだけで事が済んでしまいました。むしろ水分子画像を拡大したときはシェーダの方が綺麗に描画できます。
そして気になるパフォーマンスですが、画像を使わずにシェーダだけで描画したほうが、1FPSだけですが上回りました。(1億と1000万ポイント描画時に計測)
せっかくGIMPで一生懸命?作りましたが、シェーダ方式を採用することにしました。透明度や色、様々なエフェクトなどシェーダの方が自由度も高いですしね。
昨日はMacでも開発できるよう環境を整えました。
本日は水分子を画像を使わずにシェーダで描画するようにしてみます。まずポイントスプライトを円形にするため、フラグメントシェーダを以下のように改変しました。
#version 330 core
out vec4 Color;
void main()
{
vec3 target;
// 座標値変換 (0, 1) -> (-1, 1)
target.xy = (gl_PointCoord - 0.5) * 2.0;
// 半径の2乗
float r2 = target.x * target.x + target.y * target.y;
if (1.0 < r2) {
discard;
}
Color = vec4(0.0f, 0.0f, 1.0f, 0.3f);
}
円外の場合はdiscardで色を付けないようにしています。結果は以下のようになりました。
明日はアルファ値を調整します。
本日は昨日に引き続き、Macにおける開発環境を整えるため、VSCodeに「rust-analyzer」「CodeLLDB」「Shader languages support for VS Code」の3つの拡張機能を入れました。
また、FPS計測ロジックを実装し、Macで
1億個のポイントスプライト
を描画して何FPS出るか確認しました。
FPS計測ロジックは以下のような実装です。
const B_FPS: bool = true; // FPS 表示フラグ
let mut draw_count_for_fps = 0;
let mut start_for_fps = Instant::now();
const B_LIMIT_60FPS: bool = true; // 60FPS制限フラグ
let time_for_wait = 1_000_000_000u32 / 64; // 60 で割ると60FPS出ないので余裕を持たせておく
let mut start_loop = Instant::now();
’running: loop {
if B_LIMIT_60FPS {
start_loop = Instant::now();
}
~描画処理省略~
// swap window
window.gl_swap_window();
// calc fps
if B_FPS {
draw_count_for_fps += 1;
let time_for_fps_ms = start_for_fps.elapsed().as_millis();
if 1000 <= time_for_fps_ms {
println!("{:.2} FPS", (draw_count_for_fps as f64 * 1000.0) / time_for_fps_ms as f64);
draw_count_for_fps = 0;
start_for_fps = Instant::now();
}
}
if B_LIMIT_60FPS {
let time_nanos: u32 = start_loop.elapsed().as_nanos() as u32;
//println!("{}ナノ秒経過しました。", time_nanos);
if time_nanos < time_for_wait {
//println!("{}ナノ秒待機しました。", time_for_wait - time_nanos);
::std::thread::sleep(::std::time::Duration::new(0, time_for_wait - time_nanos));
}
}
}
1億個描画した結果は
Windows(9900K + RTX3070)では20FPS
Mac(M1)では1FPS未満
でした。
ちなみに1000万個の場合、
Windows(9900K + RTX3070)では60FPS張付き
Mac(M1)では8FPS
でした。
昨日はシェーダを調整しました。
今日はMacでも開発できるように環境を整えました。
※あくまで開発環境であり、ゲームをMacに対応させるつもりはありません。
※以下手順は、MacにGit/Rust/Homebrewがインストール済みの前提です。
まず、昨日までWindows環境で開発していたソースをgithubから持ってきます。
git clone https://github.com/あなたのソースパス
次に、SDLのライブラリをHomebrewでインストールします。
brew install sdl2
インストールしたライブラリへパスを通すため、.zshenvに以下を追記します。
export LIBRARY_PATH="$LIBRARY_PATH:$(brew --prefix)/lib"
Homebrewがインストールされているパス(brew –prefix)は、以下のコマンドで分かります。
brew --prefix
で、最後にgit cloneでソースを持ってきたパスに移動してcargo runを実行します。
thread ‘main’ panicked at ‘called `Result::unwrap()` on an `Err` value: “Failed creating OpenGL context at version requested”‘, src/main.rs:45:43
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
エラーが出てしまいました。
「要求されたバージョンでOpenGLコンテキストの作成に失敗しました。」というエラーのようです。
わたしはM1のMacを使ってますが、この場合OpenGLのサポートは4.1までのようです。
そこでOpenGLのバージョン指定を4.5から4.1に変更したら成功しました。
gl_attr.set_context_version(4, 1); // Set the OpenGL context version
〜略〜
debug_assert_eq!(gl_attr.context_version(), (4, 1));
VSCodeはMac上でも使えます。
これでWindowsでもMacでも開発可能になりました。
うれし〜〜😍
(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾
昨日作業した、シェーダへユニフォーム変数を渡すコードを記載します。
この日のコードからの差分になります。
まず頂点シェーダです。
#version 330 core
layout (location = 0) in vec3 Position;
uniform float pointSize;
void main()
{
gl_Position = vec4(Position, 1.0);
gl_PointSize = pointSize;
}
pointSizeという名前の、float型のユニフォーム変数です。
次に、この変数に値を渡すRustのコードです。
〜略〜
shader_program.set_used();
let point_size: f32 = 64.0;
unsafe {
let point_size_loc = gl::GetUniformLocation(shader_program.id(), c_str!("pointSize").as_ptr());
gl::Uniform1f(point_size_loc, point_size);
}
〜略〜
c_strはマクロで、以下のように定義されています。
macro_rules! c_str {
($literal:expr) => {
CStr::from_bytes_with_nul_unchecked(concat!($literal, "\0").as_bytes())
}
}
∩∩
(´・ω・)
_| ⊃/(___
/ └-(____/
 ̄ ̄ ̄ ̄ ̄ ̄ ̄
昨日はウィンドウリサイズ時の処理を実装しました。
本日は乱数を持ちいて
1億個のポイントスプライト
を描画した際のFPSを計測しました。
const POINTS: usize = 100_000_000;
let mut vertices: Vec<f32> = Vec::with_capacity(POINTS * 3);
let mut rng = rand::thread_rng();
for _i in 0..POINTS {
vertices.push(rng.gen_range(-1.0..1.0)); // x
vertices.push(rng.gen_range(-1.0..1.0)); // y
vertices.push(0.0); // z
}
~略~
unsafe {
gl::BindVertexArray(vao);
gl::DrawArrays(
gl::POINTS, // mode
0, // starting index in the enabled arrays
POINTS as i32, // number of indices to be rendered
);
}
CPUは9900K、GPUはRTX3070です。さあ、何FPS出たと思いますか?
・・・
・・
・
正解は、20FPSでした。1億個も描画すれば画面固まるかと思いましたがちゃんと動きました。
さすがRust、素晴らしい。
・・・・・・いや、これRTX3070の力が大きのかな。
・・・
・・
・
明日以降は、ポイントスプライトの代わりにシェーダーを用いて水分子を描画した場合、FPSがどう変化するか確認します。