経緯
- 現状のゲームプログラムにおいて描画対象をGPUに投げるのがメインで、自前でレンダリング命令を構築するとかは行うことは少ない(=CPU側で描画を制御することは少なくなる)
- ただ基礎知識は必要
- 応用しやすい。例えば内部でテクスチャに描画だけを行って表示する等が可能。また、レイマーチングはGPU側で処理しているが、結局のところレンダラーを簡易的に作成している位置づけになる
- GPUでシェーダによる制御を行う場合1ピクセル単位なのでできることに限度があるが、CPU側では手続き的に記述を書くことができるので、アルゴリズムを試行するテスト環境としても最適
作成したフレームワークについて
- 特定のメモリ領域に色を置いて、それを自動的に描画領域として表示するプログラムを作成した
- OpenGL等3DAPIは使わない想定
- 大量に機能は追加せず、まずは枠組みを作ることにした
- ピクセルが描画できれば方法はなんでもいいんだけど、実装について楽したいのでライブラリを利用する。今回はSFMLを使うことにした
- メイン(ウィンドウ)スレッドと描画スレッドを分割した。どちらかの処理待ちによるボトルネックを軽減させる目的
- ついでに低解像度で試したいのでスケーリングも想定して実装済み
ソースコード
main.cpp
#include <SFML/Graphics.hpp>
#include <thread>
unsigned int x = 0;
unsigned int y = 0;
//描画イメージ。低解像度の場合ここを変更してもいいかも。
sf::Image image({ 800, 600 }, sf::Color::Black);
void renderingThread(sf::RenderWindow* window)
{
// ウィンドウのコンテキストを有効化する
window->setActive(true);
//描画した仮想領域の描画準備
sf::Texture texture;
//イメージをセットする
texture.loadFromImage(image);
//スプライト(2D描画)として描画する
sf::Sprite sprite(texture);
//仮想の描画領域の位置を指定(左上)
sprite.setPosition({ 0,0 });
//スケーリング(今回は8倍)
sprite.setScale({ 8,8 });
// レンダリングループ。メインとは別スレッドで動く
while (window->isOpen())
{
// ウィンドウ側の描画をクリアする。SFML上でダブルバッファが有効化になっているため、これでOKになってる
window->clear(sf::Color::Black);
// イメージ(メモリ上の描画領域)のクリア。一面に黒色を塗る実装
for (unsigned int i = 0; i < 200; i++) {
for (unsigned int j = 0; j < 150; j++) {
image.setPixel({ i, j }, sf::Color::Black);
}
}
//================================================
//以下、描画テスト。
//本来だとここは別のソースコードに書く等できる
//================================================
//適当にピクセル描画のテスト
sf::Color test(0, 25, 25, 255);
image.setPixel({ 1, 2 }, test);
//線分の描画(横)
for (unsigned int i = 0; i < 70; i++) {
int plus = i;
image.setPixel({10+i, 10 }, sf::Color::Red);
}
//線分の描画(縦)
for (unsigned int i = 0; i < 70; i++) {
image.setPixel({ 10, 10+i }, sf::Color::Red);
}
//線分の描画(斜め)
for (unsigned int i = 0; i < 70; i++) {
int plus = i;
image.setPixel({ 10 + i, 10+i }, sf::Color::Blue);
}
//メインスレッド上で更新した値を参照して描画する
image.setPixel({ x, y }, sf::Color::Green);
//================================================
//描画領域を更新する
texture.update(image);
//描画反映
window->draw(sprite);
// end the current frame
window->display();
}
}
int main()
{
// create the window (remember: it's safer to create it in the main thread due to OS limitations)
sf::RenderWindow window(sf::VideoMode({ 800, 600 }), "CPU drawing test framework");
// メインスレッドではウィンドウのコンテキストを無効にする
window.setActive(false);
// レンダリングスレッドを起動する(以降、メインスレッドとは関係なく動く)
std::thread thread(&renderingThread, &window);
// イベント処理用のループ
while (window.isOpen())
{
//イベント受信はメインスレッド側で行う
while (const std::optional event = window.pollEvent())
{
// クローズのリクエストが来たら、ウィンドウをクローズする
if (event->is<sf::Event::Closed>())
window.close();
}
//メインスレッド側で特定ピクセル更新
x = rand() % 200;
y = rand() % 150;
//ウェイト命令を入れる
sf::sleep(sf::milliseconds(100));
}
thread.join();
}
今後の予定
- ブレゼンハムのアルゴリズムについて調べてみる
- そもそも基本的な図形の描画ができないので実装してみる
- 高速な描画はどうすれば実現できるのか調べてみる
- 重ね合わせの描画順位について調べてみる
- レイトレーシング実装してみる