昨日は1億個の水滴をポイントスプライトで描画しました。
今日は水滴をシェーダーで描画するための準備をしていました。
1.ポイントサイズをプログラムから渡せるように調整
2.シェーダをポイントスプライト用と自力描画用に分割
1については明日コードを記載しようと思います。もう寝る時間になってしまったので・・・
∩∩
(´・ω・)
_| ⊃/(___
/ └-(____/
 ̄ ̄ ̄ ̄ ̄ ̄ ̄
昨日はウィンドウリサイズ時の処理を実装しました。
本日は乱数を持ちいて
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がどう変化するか確認します。
昨日に引き続き、本日もちょっとした作業としてウィンドウリサイズ時の処理を実装しました。
以下コードの赤字部分です。ウィンドウサイズに合わせてビューポートを変更しています。
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);
}
}
},
_ => {}
}
}
昨日はポイントスプライトの描画を行いました。
今日は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();
}
}
昨日はこのザマでしたが、今日は失敗した原因を特定して水分子画像をアルファ値を考慮して正しく描画出来ました。
(੭ ˃̣̣̥ ω˂̣̣̥)੭ु⁾⁾

コードは以下です。一部このページのライブラリを使っています。
#![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));
}
}
今日は、14日目に作成した三角形のコードを変更して
点
を描画しました。
赤い箇所が変更/追加部分です。
gl::DrawArrays(
gl::POINTS, // mode
0, // starting index
3, // number of indices to be rendered
);
gl::Enable(gl::VERTEX_PROGRAM_POINT_SIZE);
#version 330 core
layout (location = 0) in vec3 Position;
void main()
{
gl_Position = vec4(Position, 1.0);
gl_PointSize = 30.0;
}
次回以降は、この点を
ポイントスプライト
に置き換えて、当初の目的である水分子を描画します。
昨日から、描画部分をOpenGLとDirectXのどちらにするか悩んでいました。
OpenGLとDirectXを比較すると、
・OpenGLはWindows, Mac, Linuxなど色んなOSに対応
・DirectXはWindowsのみ対応
・ゲームによるが、だいだいOpenGLはDirectXの7割~9割のパフォーマンス(OpenGLは影の描画が汚かったり画質が悪いことがある)
でした。
思ったより差が無かったです。パフォーマンスが倍くらい違えば間違いなくDirectXにしたんですが、困りました。ちなみにOpenGL後継のVulkanは思ったよりパフォーマンス上がってないですね。
WebGLでブラウザ経由によりいろんな環境に対応できそうなので、明日からは引き続きOpenGLで開発を継続したいと思います。
昨日まではOpenGLでの描画基礎処理を行ってきましたが、今日はこのままOpenGLで開発すべきか悩んでいました。
過去にWebGLベースで開発してWebアプリとiOSアプリの両対応したことがあるのですが、iOSアプリについては「売れていない&アップデートされていない」という理由で
Appleにアプリストアからアプリを削除されてしまいました。
OpenGLで開発して将来的にはiOS対応の選択肢も残しておこうと思っていましたが、Windowsだけ対応すれば良いじゃないかという気になってきました。iOSではOpenGL非推奨で、Metalが推されてもいますしね。