ゲーム制作 環境原猫 29日目

昨日水分子を、画像を使わずにシェーダのみで描画するように変更しました。

今日は水分子を、空気中を飛び回っているイメージでグリグリ動かしました。

コードの変更箇所は以下の通りです。

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のコード上で描画位置を変更していますが、これだとポイント数が増えた場合に負荷がヤバそうです。

明日からはなるべくシェーダに仕事をさせられないか検討します。

次の日

ゲーム制作 環境原猫 28日目

昨日に引き続き、今日は円の半径に伴いアルファ値が変わるようにシェーダを調整しました。

#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で一生懸命?作りましたが、シェーダ方式を採用することにしました。透明度や色、様々なエフェクトなどシェーダの方が自由度も高いですしね。

次の日

ゲーム制作 環境原猫 27日目

昨日は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で色を付けないようにしています。結果は以下のようになりました。

明日はアルファ値を調整します。

次の日

ゲーム制作 環境原猫 26日目

本日は昨日に引き続き、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

でした。

次の日

ゲーム制作 環境原猫 25日目

昨日はシェーダを調整しました。

今日は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でも開発可能になりました。

うれし〜〜😍

(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

次の日

ゲーム制作 環境原猫 24日目

昨日作業した、シェーダへユニフォーム変数を渡すコードを記載します。

この日のコードからの差分になります。

まず頂点シェーダです。

 #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())
     }
 }

  
    ∩∩ 
   (´・ω・) 
   _| ⊃/(___ 
 / └-(____/ 
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

次の日

ゲーム制作 環境原猫 23日目

昨日は1億個の水滴をポイントスプライトで描画しました。
今日は水滴をシェーダーで描画するための準備をしていました。

1.ポイントサイズをプログラムから渡せるように調整
2.シェーダをポイントスプライト用と自力描画用に分割

1については明日コードを記載しようと思います。もう寝る時間になってしまったので・・・

  
    ∩∩ 
   (´・ω・) 
   _| ⊃/(___ 
 / └-(____/ 
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

次の日

ゲーム制作 環境原猫 22日目

昨日はウィンドウリサイズ時の処理を実装しました。
本日は乱数を持ちいて

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がどう変化するか確認します。

次の日

ゲーム制作 環境原猫 21日目

昨日に引き続き、本日もちょっとした作業としてウィンドウリサイズ時の処理を実装しました。

以下コードの赤字部分です。ウィンドウサイズに合わせてビューポートを変更しています。

for event in event_pump.poll_iter() {
    match event {
        Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
            break;
        },
        Event::Window { timestamp: _timestamp, window_id: _window_id, win_event: sdl2::event::WindowEvent::Resized (resized_w, resized_h) } => {
            //println!("リサイズ {} x {}", resized_w, resized_h);
            if resized_w as u32 != window_w || resized_h as u32 != window_h {
                window_w = resized_w as u32;
                window_h = resized_h as u32;
                unsafe {
                    gl::Viewport(0, 0, window_w as i32, window_h as i32);
                }
            }
        },
        _ => {}
    }
}

次の日

ゲーム制作 環境原猫 20日目

昨日はポイントスプライトの描画を行いました。

今日は60FPSを保つためのちょっとした調整を行いました。

具体的なコードは以下の通りで、イベント処理や描画処理を除いて、1フレームが1/60秒で完了するように調整してウェイトを入れています。

    let mut event_pump = sdl_context.event_pump().unwrap();
    let time_for_wait = 1_000_000_000u32 / 63;  // 60 で割ると60FPS出ないので3の余裕を持たせておく
    
    'running: loop {
        let start = Instant::now();

        for event in event_pump.poll_iter() {
            ~イベント処理~
        }
        unsafe {
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }
        // draw
        shader_program.set_used();
        unsafe {
            gl::BindVertexArray(vao);
            gl::DrawArrays(
                gl::POINTS, // mode
                0,             // starting index in the enabled arrays
                3,             // number of indices to be rendered
            );
        }

        let end = start.elapsed();
        let time_nanos: u32 = end.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));
        }
        // swap window
        window.gl_swap_window();
    }
}

次の日