uka.apple のすべての投稿

ゲーム制作 環境原猫 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();
    }
}

次の日

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

昨日はこのザマでしたが、今日は失敗した原因を特定して水分子画像をアルファ値を考慮して正しく描画出来ました。

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

コードは以下です。一部このページのライブラリを使っています。

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
extern crate sdl2;
extern crate gl;
extern crate image;

pub mod render_gl;

use std::path::Path;
use std::os::raw::c_void;

use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::video::GLProfile;

fn main() {
    let window_w: u32= 800;
    let window_h: u32= 600;

    let sdl_context = sdl2::init().unwrap();
    let video_subsystem = sdl_context.video().unwrap();
    
    let gl_attr = video_subsystem.gl_attr();
    gl_attr.set_context_profile(GLProfile::Core);
    gl_attr.set_context_version(4, 5);

    let window = video_subsystem.window("環境原猫", window_w, window_h)
        .opengl()
        .resizable()    // .fullscreen_desktop()
        .build()
        .unwrap();


    let _ctx = window.gl_create_context().unwrap();
    gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _);
    
    debug_assert_eq!(gl_attr.context_profile(), GLProfile::Core);
    debug_assert_eq!(gl_attr.context_version(), (4, 5));

    // shader program
    use std::ffi::CString;
    let vert_shader = render_gl::Shader::from_vert_source(&CString::new(include_str!("triangle.vert")).unwrap()).unwrap();
    let frag_shader = render_gl::Shader::from_frag_source(&CString::new(include_str!("triangle.frag")).unwrap()).unwrap();
    let shader_program = render_gl::Program::from_shaders(&[vert_shader, frag_shader]).unwrap();

    // texture
    let img = image::open(&Path::new("resources/textures/drop.png")).expect("Failed to load texture");
    let format = match img {
        image::DynamicImage::ImageLuma8(_) => gl::RED,
        image::DynamicImage::ImageLumaA8(_) => gl::RG,
        image::DynamicImage::ImageRgb8(_) => gl::RGB,
        image::DynamicImage::ImageRgba8(_) => gl::RGBA,
        _ => panic!("unknown format.")
    };
    let mut texture = 0;
    unsafe {
        gl::GenTextures(1, &mut texture);
        gl::BindTexture(gl::TEXTURE_2D, texture);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
        let data = img.as_bytes();
        gl::TexImage2D(gl::TEXTURE_2D,
                       0,
                       format as i32,
                       img.width() as i32,
                       img.height() as i32,
                       0,
                       format,
                       gl::UNSIGNED_BYTE,
                       &data[0] as *const u8 as *const c_void);
        gl::GenerateMipmap(gl::TEXTURE_2D);
    }

    // vertex buffer object
    let vertices: Vec<f32> = vec![-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0];
    let mut vbo: gl::types::GLuint = 0;
    unsafe {
        gl::GenBuffers(1, &mut vbo);
    }
    unsafe {
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(
            gl::ARRAY_BUFFER,                                                       
            (vertices.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr,
            vertices.as_ptr() as *const gl::types::GLvoid,
            gl::STATIC_DRAW,
        );
        gl::BindBuffer(gl::ARRAY_BUFFER, 0);
    }
    // vertex array object
    let mut vao: gl::types::GLuint = 0;
    unsafe {
        gl::GenVertexArrays(1, &mut vao);
    }
    unsafe {
        gl::BindVertexArray(vao);
        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::EnableVertexAttribArray(0); // layout (location = 0)
        gl::VertexAttribPointer(
            0,         // index
            3,         // number of components
            gl::FLOAT, // data type
            gl::FALSE, // normalized
            (3 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride 
            std::ptr::null(),                                     // offset 
        );
        gl::BindBuffer(gl::ARRAY_BUFFER, 0);
        gl::BindVertexArray(0);
    }

    // set up
    unsafe {
        gl::Viewport(0, 0, window_w as i32, window_h as i32);
        gl::ClearColor(0.6, 0.0, 0.8, 1.0);
        gl::Enable(gl::VERTEX_PROGRAM_POINT_SIZE);
        gl::Enable(gl::BLEND);
        gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::ONE, gl::ONE);
    }

    let mut event_pump = sdl_context.event_pump().unwrap();

    'running: loop {
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                    break 'running
                },
                _ => {}
            }
        }
        unsafe {
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }
        // draw point sprites
        shader_program.set_used();
        unsafe {
            gl::BindVertexArray(vao);
            gl::DrawArrays(
                gl::POINTS, // mode
                0,             // starting index
                3,             // number of indices
            );
        }
        // swap window
        window.gl_swap_window();

        ::std::thread::sleep(::std::time::Duration::new(0, 1_000_000_000u32 / 60));
    }
}

次の日