7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

libGDXの基礎18 Pixmapで動的にテクスチャを作る

Posted at

#Pixmapとは

用意された画像からではなくメモリで加工した画像をもとにTextureを作りたいことはよくあります。
そんなときに利用するのがPixmapです。

今回は画像ファイルを用意せずにコードのみでTextureを作成します。

注意点としていつものApplicationListenerインターフェースを実装するのではなくApplicationAdapterを継承しています。
これはSwingなどでもよくあるAdapterクラスのようにインターフェースを空実装しているクラスになります。必要な個所のみオーバーライドして利用することが可能です。主にこの特徴はイベントで発揮されます。ApplicationListenerもイベントといえますね。

package test.libgdx.basic18;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;


public class MainListener18 extends ApplicationAdapter{
    SpriteBatch batch;
    Texture tex;
    Pixmap pmap;
    
    @Override
    public void create() {
        batch = new SpriteBatch();
        
        //64x64のメモリイメージを作成
        pmap = new Pixmap(64, 64, Pixmap.Format.RGBA8888);
        pmap.setColor(Color.DARK_GRAY);
        pmap.fill();//塗りつぶし
        pmap.setColor(Color.RED);
        pmap.drawRectangle(0, 0, 64, 64);//四角
        pmap.setColor(Color.CYAN);
        pmap.drawLine(0, 0, 64, 64);//線
        pmap.setColor(Color.YELLOW);
        pmap.drawCircle(31, 31, 31);//円
        
        //それをもとにテクスチャ作成
        tex = new Texture(pmap);
    }


    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        //描画
        batch.begin();
        batch.draw(tex, 100, 100);
        batch.end();
    }

    @Override
    public void dispose() {
        tex.dispose();
        pmap.dispose();
    }

    

    
    public static void main(String[] args) {
        new LwjglApplication(new MainListener18(), "libGDX", 256, 256);
    }
}

###実行結果

20161120-01.png

ソースを見るとわかるように、基本的な描画命令はあります。これらの実装はJNIで各種プラットフォームごとにコンパイルされたバイナリによって実行されます。ユーザーはピクセルフォーマットなどを気にせず利用可能です。

#毎フレームテクスチャを更新

Pixmapによって生成されたTextureはそのもととなったPixmapを更新しても変更されません。
そんなときにはTexture#draw(pixmap, x, y) メソッドを利用します。

//~省略~
    int color = 0;//フィールドに追加

//~省略~

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        
        //pixmapへ書き込み
        pmap.setColor(color/100f,0,0,1f);
        pmap.drawRectangle(0, 0, 48, 48);
        //テクスチャへ転送
        tex.draw(pmap, 0, 0);
        
        color++;
        if(color > 100){
            color = 0;
        }

        //描画
        batch.begin();
        batch.draw(tex, 100, 100);
        batch.end();
    }

###実行結果

20161120-02.png

実行すると黒から赤い色が徐々に濃くなっていき最大まで赤くなった時また黒に戻るのを繰り返します。テクスチャが動的に更新されているのがわかります。

Texture#drawメソッドの引数にxとyがあることからわかるように、OpenGLの実装としてはglTexSubImage2Dが利用されています。そのためTextureのサイズとPixmapのサイズは一致しなくてもかまいません。ただし、Texture内に収まる必要があるため、TextureはPixmapより小さいサイズにはできません。

#Pixmapのメモリイメージをダイレクトに触る

実はPixmapは画像ファイルからTextureを作り出すときにも利用されていて、今までのサンプルでも内部的には利用されていました。

PixmapとはVRAMへ転送するテクスチャのフォーマットに合わせたメモリイメージそのものです。そのため、メモリはJavaのヒープ外にあります。Javaの配列などのように気軽に触ることはできません。そのかわりJNI経由でCの実装部分ががんばって処理しています。

とはいえ、JNI部分を何度も呼び出すのはオーバーヘッドです。1ピクセルごとに転送する命令はありますが、それを何度も呼び出していてはパフォーマンスにも影響します。

先ほどの点滅する赤い部分

        //pixmapへ書き込み
        pmap.setColor(color/100f,0,0,1f);
        pmap.fillRectangle(0, 0, 48, 48);

はおおよそ63マイクロ秒かかりました。

これと同じことをピクセル単位のメソッド経由で行うと以下のようになります。

        //pixmapへ書き込み
        pmap.setColor(color/100f,0,0,1f);
        
        for(int y=0; y<48 ;y++){
            for(int x=0; x<48 ;x++){
                pmap.drawPixel(x, y);
            }
        }

おおよそ126マイクロ秒かかります。やはりオーバーヘッドが大きいですね。

PixmapのメモリはNIOによってアクセス可能です。ネイティブのバイトオーダーになること、ダイレクトバッファなので配列アクセスはできないことなどに注意です。

        //pixmapへ書き込み
        int c = Color.rgba8888(color/100f, 0, 0, 1);
        IntBuffer buf = pmap.getPixels().order(ByteOrder.nativeOrder()).asIntBuffer();
        for(int y=0; y<48 ;y++){
            buf.position(y*64);
            for(int x=0; x<48 ;x++){
                buf.put(c);
            }
        }

これでおおよそ5マイクロ秒まで短縮されます。

さらにチューニングして1ライン単位でダイレクトバッファに転送するようにします。

    //フィールドに追加
    int[] src = new int[48];

//~省略~

    @Override
    public void render() {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        //pixmapへ書き込み
        int c = Color.rgba8888(color/100f, 0, 0, 1);
        int[] wk = src;

        //いったん配列に1ライン分生成
        for(int i=0; i<48 ;i++){
            wk[i] = c;
        }

        IntBuffer buf = pmap.getPixels().order(ByteOrder.nativeOrder()).asIntBuffer();
        for(int y=0; y<48 ;y++){
            buf.position(y*64);
            buf.put(wk);//48ピクセル分転送
        }
//~省略~

これでおおよそ2マイクロ秒になりました。

今回は一ライン単位、それも同じ色で塗りつぶしましたが、配列からのTexture生成ができるということは昔ながらのプログラムのように書けるということです。

説明のためわかりやすくRGBA8888フルカラーで行いましたが、そもそも転送時間や描画時間を気にするならばピクセルフォーマットRGBA4444などにするべきでしょう。パソコン版では問題がなくてもスマホではこの辺のちょっとしたことで大きく数値が変わります。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?