TGA画像ファイルを扱う

■画像ファイルの種類

 画像形式のバイナリファイルは様々なフォーマットがあるが、主に使われているのが以下のものがある。 

BMP ・PNG ・JPG ・TGA

 

 簡単に各フォーマットの特性を挙げてみた。
BMP

→構造が単純で扱いやすい

→ファイルサイズが大きい

→半透明を扱えない


PNG

→データが圧縮される形式で扱いにくい

→ファイルサイズが小さい

→半透明を扱える


・JPG

→データが圧縮される形式で扱いにくい

→ファイルサイズが極めて小さい

→半透明を扱えない

 

そして今回はTGA形式について扱い方を紹介する。

・TGA形式とは

→Truevision Graphics Adapterの短縮語

→トゥルービジョン社が開発したラスタ―画像(ビットマップ画像とも呼ばれる、ピクセルを用いたもの)のファイル形式
→構造が単純で扱いやすい

→ファイルサイズが大きい

→半透明を扱える


・TGA形式の構造

→ファイルの先頭にTGAファイルヘッダと呼ばれる18バイトの画像情報が格納されている
オフセット0→ID Length→バイト数1→イメージIDデータのバイト数

オフセット1→Colormap Type→バイト数1→カラーマップの有無(0=無,1=有)

オフセット2→Image Type→バイト数1→画像記録形式( →0=画像データなし →1=圧縮無し、インデックスカラー画像 →2=圧縮無し、カラー画像 →3=圧縮無し、白黒画像 →9=圧縮あり、インデックスカラー画像 →10=圧縮あり、カラー画像 →11=圧縮あり、白黒画像)

オフセット3→First Entry Index→バイト数2→画像データのインデックス0に対するカラーマップのインデックス、カラーマップ無しの場合は未使用(常に0)

オフセット5→Colormap Length→バイト数2→カラーマップに登録されている色の数、同上

オフセット7→Colormap Entry Size→バイト数1→カラーマップに登録されている色一つのビット数、同上

オフセット8→X-origin→バイト数2→X座標をずらすピクセル

オフセット10→Y-origin→バイト数2→Y座標をずらすピクセル

オフセット12→Image Width→バイト数2→画像の横のピクセル

オフセット14→Image Height→バイト数2→画像の縦のピクセル

オフセット16→Pixel Depth→バイト数1→画像の1ピクセルのビット数

オフセット17→Image Descripter→バイト数1→画像の格納方向及び1ピクセルのアルファ要素のビット数


・TGA画像データからテクスチャ作成

→1.ファイルを開く 

→#include <fstream>  std::ifstream ifs;  ifs.open(filename, std::ios_base::binary);   

 2.TGAヘッダーを読み込む  

→uint8_t tgaHeader[18]; // TGAヘッダは18バイト   

 ifs.read(reinterpret_cast<char*>(tgaHeader), 18);


 3.イメージIDを読み込む/飛ばす  

→ifs.ignore(tgaHeader[0]);   

//ifs.read(char* buf_id, tgaHeader[0]);


 4.カラーマップを読み込む/飛ばす  

→要領は同じなので読み込む処理の詳細は割愛 

→if (tgaHeader[1]) {   

 const int colorMapLength = tgaHeader[5] + tgaHeader[6] * 0x100;   

 const int colorMapEntrySize = tgaHeader[7];   

 // エントリサイズはビット数なので、8で割ってバイト数に変換する   

 const int colorMapSize = (colorMapLength * colorMapEntrySize + 7) / 8;

 ifs.ignore(colorMapSize);   

}


 5.画像データを読み込む  

→const int width = tgaHeader[12] + tgaHeader[13] * 0x100;   

const int height = tgaHeader[14] + tgaHeader[15] * 0x100;   

const int pixelDepth = tgaHeader[16];   

const int imageSize = width * height * pixelDepth / 8;   

std::vector<uint8_t> buf(imageSize);   

ifs.read(reinterpret_cast<char*>(buf.data()), imageSize);


 6.読み込んだ画像データからテクスチャを作成する  

→// テクスチャオブジェクトを作成し、GPUメモリを確保する   

GLuint id;   

glCreateTextures(GL_TEXTURE_2D, 1, &id);   

glTextureStorage2D(id, 1, GL_RGBA8, width, height);   

//GPUメモリに転送する   

glTextureSubImage2D(id, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buf.data());      

GLuint tex = id;

 

以上。他のフォーマットについてもおいおい追加する予定。

ビルドイン変数

■ビルドイン変数とは
・いくつかのシェーダーステージのためにOpenGL Shading Languageにより定義された特殊な変数
・特別な性質を持っている(予め用途が決められている)
・通常、いくつかの固定機能と通信するために使われる
・ルール上、全てのビルドイン変数は[gl_]から始め;ユーザーはこの接頭辞を使った変数名を宣言&定義することは出来ない

 

■ビルドイン変数の種類
・Vertex shader inputs/outputs
・Tessellation control shader inputs/outputs
・Tessellation evaluation shader inputs/outputs
・Geomatry shader inputs/outputs
・Fragment shader inputs/outputs
・Compute shader inputs/outputs
・Shader uniforms
・Constants

 

詳しくはOpenGLのレファレンスを参照してください。

Built-in Variable (GLSL) - OpenGL Wiki

VBOとは?

目次

  • VBOとは
  • VBOがどのようなデータを格納するのか?
  • VBOの作り方(従来バージョンと最新バージョン)
  • VBOの値を変更してみよう

 

一、VBOとは

→VBO=Vertex Buffer Object

→バッファオブジェクトであり、GPU上に作られる。

→格納するデータは頂点データ(座標、色情報など)

OpenGLにて描画するために最も基礎的部分(VBO→VAO→Vertex Puller)

 

 VBOはVertex Buffer Object(頂点バッファ―オブジェクト)の頭文字からなる。

 GPUメモリに作られるバッファオブジェクト(データを格納するメモリ上の領域)の一種である。データを格納するメモリ上の領域というのは、例えば、glGenBuffersを呼び出しでも、バッファーオブジェクトはまだ作られておらず、領域だけ確保されている状態である。

 OpenGLにて描画するためには、煩雑なプロセスを辿らないと描画できない。簡単に説明すると、頂点データを持っているVBOをたくさん集めて、このようなVBOの集まりをVertex Array Object(VAO)と呼ぶ。そしてOpenGLの描画手順Graphics PipelineのVertex Pullerにより、描画に必要なだけの頂点データが取られて描画に進むという流れになる。

 glDeleteBuffers

二、VBOにはどのようなデータが格納できるのか

 

三、VBOの作り方

方法①:

1.glGenBuffers(GLsizei n, GLuint *buffers);

 こちらの関数を呼び出してバッファーのID(names)を予約する(中身が空で領域も割り当てておらず、IDだけ生成される)。nには作成する個数、buffersにはバッファ―オブジェクトのID(names)の格納先のポインタを指定する。

 glGenBuffersは作成されたID(names)が連続であることを保証しない。例えば一気にバッファーオブジェクトを五個作成しても、必ずしも1から5という連続的な値が割り当てられるというわけではない。ただし、割り当てられたID(names)はglGenBuffersを呼び出した時点で必ず使用されていないことが保障されている。

 glDeleteBuffersによりバッファーオブジェクトのID(names)が削除されてから、glGenBuffersではこのID(names)を再使用することが可能となる。

 生成失敗することはあまりないが、個数に負の値を指定してしまうと、GL_INVALID_VALUEという値はエラーコールバックで返される。

 

2.glBindBuffer(GLenum target, GLuint bufffer);

 glGenBuffersでID(names)が生成されただけでは、ただID(names)が確保されただけで、中身や何のバッファーオブジェクトかが分からないので、用途を指定する必要がある。そのためにはglBindBufferを使用する。

 glBindBufferはバッファーオブジェクトを指定したバッファーバインディングポイントに割り当てる。targetにOpenGLが用意した定数の中から用途を指定する。bufferにはバッファーオブジェクトのID(names)へのポインタを指定する。glBindBufferは指定した用途にID(names)を割り当てる。もしID(names)が存在しなければ、新たにIDが作られ格納される。そして同じ用途を同時に二つ指定することができず、指定した用途がすでに他のバッファーオブジェクトに割り当てられている状態であれば、その割り当てが解除され、新たに指定したバッファーオブジェクトが適用される。

 バッファーオブジェクトのID(names)とそのバッファーオブジェクトのコンテンツは現在使用しているGL Rendering コンテンツのローカルに配置される。

 glBindBufferのtargetに指定できる用途は全部14個ある。

  • GL_ARRAY_BUFFER
  • GL_ATOMIC_COUNT_BUFFER
  • GL_COPY_READ_BUFFER
  • GL_COPY_WRITE_BUFFER
  • GL_DISPATCH_INDIRECT_BUFFER
  • GL_DRAW_INDIRECT_BUFFER
  • GL_ELEMENT_ARRAY_BUFFER
  • GL_PIXEL_PACK_BUFFER
  • GL_PIXEL_UNPACK_BUFFER
  • GL_QUERY_BUFFER
  • GL_SHADER_STORAGE_BUFFER
  • GL_TEXTURE_BUFFER
  • GL_TRANSFORM_FEEDBACK_BUFFER
  • GL_UNIFORM_BUFFER

 具体的にどれはどんな目的なのかについて、今回は割愛させてもらう。

3.glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);

 glBufferDataは指定している目的用のバッファーオブジェクトのデータストアを作成する。もしこの関数を呼び出した時点で既にデータストアが存在しているのであれば、それを削除する。データストアは指定したサイズと使用方法によりつくられている。もしdataはNULLでなければ、そのdataで初期化される。初期化時にはmap pointerにNULLが設定されており、mapped accessはGL_READ_WRITEと設定される。

 usageはデータストアはどのようにアクセスされるのかを指定する。usageを指定することで、バッファーオブジェクトのパフォーマンスを著しく上げることができる。usageは二つのパートに分けられる。一つはアクセスの頻度で、もう一つはアクセスの目的である。

  • 頻度

  STREAM  データストアコンテンツは一回しか変更しない、使用回数少ない

  STATIC     データストアコンテンツは一回しか変更しない、使用回数多い

  DYNAMIC  データストアコンテンツは何度も変更する、使用回数も多い

  • 目的

  DRAW   データストアコンテンツはアプリにより変更され、描画や画像指定として使われる

  READ    データストアコンテンツはGL側の読み込みデータにより変更され、アプリに要求される時にデータを転送する

  COPY    データストアコンテンツはGL側の読み込みデータにより変更され、描画や画像指定として使われる

 

4.glBindBuffer(GLenum target, 0);

 バッファーオブジェクトのIDは符号なしのint型で、ID(names)に0を指定することが可能だが、デフォルトではどの用途のバッファーオブジェクトにも割り当てることができない。したがって、一般的に前の割り当てを解除する目的で0を指定する。0を指定することで、もう一つの効果として、もしデバイスは0を指定する以前のバッファーオブジェクトをサポートしているのであれば、そのバッファーオブジェクトのクライアントメモリを保存することができる。

 

以上の流れでバッファーオブジェクトは作成される。

 

方法②:

 今までは方法①のやり方でVBOを作成していた。しかし、これらの関数は現代的なGPUの特性に合っておらず、GPUの性能を発揮しにくくなってきたので、OpenGL4.5では、新しい方法が実装された。

 1.glCreateBuffers(GLsizei n, GLuint *buffers);

 2.glNamedBufferStorage(GLuint buffer, GLsizeiptr size, const void *data, GLbitdield flags);

 やっていることは方法①と変わらないが、用途の指定が必要なくなったみたい。バッファーオブジェクトを発行して、設定やデータを送り込むだけになった。

 

四、VBOのデータを変更してみよう

 VBOはクライアント側のメモリスペースにバッファーをマッピングすれば、データの修正が可能となる。

 glBindBuffer(GLenum target, GLuint bufffer);

 glMapBuffer(GLenum target, GLenum access);

 データを修正

 glUnMapBuffer();

 

超実用cocos2d!見えないノードの居場所を突き止めよう!

 見えないノードを配置することって結構あるんだよね。。

 実際に配置してみて、あれ?見えないから確認できない!ってなったことは何回もあった。

 そこで対応する方法は見えないノードを見えるようにするだけ!

 試しに

node->setColor(Color3B(255,0,0));

 と書いても何も変わらない。。。ってことはsetColorは背景色を設定する関数ではないことになる。

 

 そしてノードの背景色を設定するにはレイヤーを使う!

 考え方はとても簡単!新規にノードと同じ大きさのレイヤーを一枚作成して、ノードと同じ場所に配置すれば、レイヤーはノードを隠すようになる。そしてレイヤーの背景色を変更すれば、見えないノードの居場所もわかるようになる。

 ソースはこちら!

LayerColor* lc = LayerColor::create(Color4B(255,0,0,128),button->getContentSize().width, button->getContentSize().height);
lc->setAnchorPoint(Vec2::ANCHOR_TOP_LEFT);
lc->setPosition(Vec2(344 + (2 - 1 - i) * 48, -(124 + 62 * j)) + offset);
parent->addChild(lc);

 以上。

超実用cocos2d!数秒後に特定な処理を呼び出したい!

 自分が良く使って、他人のコードにてもよく見かけられるcocosの超実用な機能なので、忘れないようにメモメモ。

runAction(Sequence::create(DelayTime::create(3), CallFunc::create([this](){

    //呼び出したい処理...

}), NULL));

 

 シーケンスで遅延処理とコールバックを組み合わせてランアクションで呼び出しただけ、簡単で使いやすい。

 最後の引数にNULLを渡して終わりを告げる。

 

 関係薄いがcocosのレファレンスは大体こんな感じになっている。

static Sequence* create(M m1, M m2, M m3, ..., nullptr_t listEnd);

 

 以上。

「参照が曖昧です」とコンパイラーに怒られる原因と対応

目次

  • 経緯
  • 原因と対応

 

一、経緯

 ビルド時に「Rect」と「Point」の参照が曖昧ですとコンパイラーに怒られた。

 調査するとなぜかヘッダーファイルにUSING_NS_CCが書かれている。ヘッダーファイルのUSING_NS_CCを全部削除して、cocosの型を参照している箇所にcocos2d::を追加すると、無事にコンパイルできた。

 

二、原因と対応

 では、なぜ「Rect」と「Point」が曖昧ですって言われたのかというと、ここには全部二つの「Rect」と「Point」型がある。一つはcocos2dに、もう一つはiOS側に定義されている。今回は、コンパイラーはどっちの「Rect」と「Point」を参照しているのかが分からないから、問題が発生した。

 c++言語で名前空間の関数や型を使用する場合は二種類の方法がある。一つは接頭辞を使う方法(cocos2d::のように)、もう一つは予め名前空間を使うと宣言する(using namespace cocos2d/USING_NS_CC)。そして、後者の書き方では今回の問題を起こさせることになる。

 ヘッダーファイルに#include <MacTypes.h>とUSING_NS_CCが同時に書かれていると、どっちを参照しているのかとコンパイラーが混乱してしまうので、基本的にヘッダーファイルでは接頭辞を使用することが望ましい(ヘッダーファイルが長いインクルードチェインに含まれた場合、見つかりにくい欠点も)。

Undefined symbols for architecture x86の原因

目次

  • 経緯
  • 原因

 

一、経緯

 Windowsで開発したプロジェクトをXcodeでビルドする時に発生(Windowsでは問題なかった)。

 ログを確認すると未定義とされている箇所はごく最近に追加した部分である(追加する前には問題なくコンパイルを通っていた)。

 その部分について、ちゃんとインクルードされているのかを確認したら、Xcode側のプロジェクトに新規実装したファイルを導入していないことを判明(実にバカのミス)。

 Xcodeのプロジェクトに新規追加したファイルを導入したら、エラーが消失して問題解決。めでたしめでたし。

 

二、原因

 そもそも、Undefined symbols for architecture x86が起きる原因は、ソース内で使用されている関数やグローバル変数の定義が見つからないことにある。

 今回はXcodeのプロジェクトに新規追加したファイルを導入していないので、その部分の関数を呼び出すと、関数が見つからずエラーになった。

 

 Undefined symbols for architecture x86になり得る原因は他にもいくつかある。

 こちらのサイトにて詳しく書かれているので、必要な方は参照してください(筆者も大変助かった)

Undefined symbols for architecture x86_64:の原因(複数) | MaryCore

 原因になり得るのは、以下になる。

  • 関数名が間違っている
  • 関数が定義されていない
  • ヘッダーファイルがインクルートされていない
  • 外部ライブラリーがリンク出来ていない
  • c言語c++のファイルが混在している
  • テンプレートの実態は存在しない

 以上。