PNGでテクスチャ2013/11/05

今回はタイトル通り、PNG画像の読み込みとテクスチャの設定・描画を行います。

PNGの読み込み、つまりはPNGデータを読み込んでテクスチャデータに展開するのですが、OpenGLやAndroid NDKにはそのような機能はありません。そこで、libpngというPNG読み込み(と書き込み)のライブラリを使用します。
ありがたいことにlibpngにはAndroid用がありますので、そちらを使わせていただきます。

 libpng for Android

jni/sysの下に丸々コピーして、jni/Android.mkから jni/sys/libpng/jni/Android.mkをインクルードするようにします。これでlibpngがプロジェクトに組み込まれます。


それではテクスチャ管理クラス sys::Textureです。
PNGの読み込み部分(load_png)と、テクスチャの設定部分(make)があります。

Texture.h
#ifndef	___TEXTURE_H___
#define	___TEXTURE_H___

#include "common.h"

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>


namespace sys
{

/****************
    テクスチャ
 ****************/
class Texture
{
public :

// ピクセルフォーマット
enum
{
	FORMAT_RGBA,
	FORMAT_RGB,
};

	GLuint	texture;						// テクスチャオブジェクト
	short	format;							// テクスチャフォーマット
	short	width, height;					// サイズ

		Texture(void)						// コンストラクタ
		{
			texture = 0;
		}
		~Texture()							// デストラクタ
		{
			clear();
		}
	void	make(const u8*);				// 作成
	void	clear(void);					// 削除
	void	load_png(const u8*);			// PNG読み込み
};

}

#endif
/********************* End of File ********************************/

Texture.cpp
/***************************

		テクスチャ

 ***************************/

#include "Texture.h"

#include <libpng/jni/png.h>


namespace sys
{

/*****************************************
    作成
		引数	data = テクスチャデータ
 *****************************************/
void	Texture::make(const u8* data)
{
	clear();

	glGenTextures(1, &texture);						// テクスチャオブジェクト作成
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	switch ( format ) {
	  case FORMAT_RGBA :
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		break;

	  case FORMAT_RGB :
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		break;
	}
}

/**********
    削除
 **********/
void	Texture::clear(void)
{
	if ( texture ) {
		glDeleteTextures(1, &texture);
		texture = 0;
	}
}


/********************************
    メモリ読み込みコールバック
 ********************************/
static
void	png_read(png_structp png_ptr, png_bytep data, png_size_t length)
{
	u8**	_p = (u8**)png_get_io_ptr(png_ptr);			// データポインタ

	memcpy(data, *_p, length);
	*_p += length;
}

/**********************************
    PNG読み込み
		引数	data = PNGデータ
 **********************************/
void	Texture::load_png(const u8* data)
{
	png_structp		_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	assert(_png);

	png_infop		_info = png_create_info_struct(_png);
	assert(_info);

	png_set_read_fn(_png, NULL, png_read);
	png_init_io(_png, (png_FILE_p)&data);
	png_read_info(_png, _info);

	png_uint_32		_width, _height;
	int				_bit_depth, _color_type;

	png_get_IHDR(_png, _info, &_width, &_height, &_bit_depth, &_color_type, NULL, NULL, NULL);
	switch ( _color_type ) {
	  case PNG_COLOR_TYPE_RGB :
		format = FORMAT_RGB;
		break;

	  case PNG_COLOR_TYPE_PALETTE :
		png_set_palette_to_rgb(_png);
		png_read_update_info(_png, _info);
		png_get_IHDR(_png, _info, &_width, &_height, &_bit_depth, &_color_type, NULL, NULL, NULL);
		format = (_color_type == PNG_COLOR_TYPE_RGB) ? FORMAT_RGB : FORMAT_RGBA;
		break;

	  case PNG_COLOR_TYPE_GRAY :
		png_set_gray_to_rgb(_png);
		png_read_update_info(_png, _info);
		format = FORMAT_RGB;
		break;

	  case PNG_COLOR_TYPE_GRAY_ALPHA :
		png_set_gray_to_rgb(_png);
		png_read_update_info(_png, _info);
		format = FORMAT_RGBA;
		break;

	  default :
		format = FORMAT_RGBA;
		break;
	}

	width  = (short)_width;
	height = (short)_height;

	int			_row_bytes = width*((format == FORMAT_RGBA) ? 4 : 3);
	u8*			_buf = (u8*)memalign(4, _row_bytes*height);
	png_bytep	_rows[height];

	assert(_buf != NULL);
	for (int i = 0; i < height; i++) {
		_rows[i] = _buf + i*_row_bytes;
	}
	png_read_image(_png, _rows);

	make(_buf);				// テクスチャ作成

	png_destroy_read_struct(&_png, &_info, NULL);
	free(_buf);
}

}

/**************** End of File ******************************************/

・PNG読み込み(load_png)
大体は libpngの普通の使い方になっていますが、ちょっと変えているのは以下の2点。
libpngは基本的にデータをファイルから読み込むようになっていますが、メモリから読み込むように読み込みコールバックを差し替えています(76行目)。
また、ピクセルフォーマットを24ビットRGB(不透明)か、32ビットRGBA(α付き)に揃えています(84行目から)。

・テクスチャ設定(make)
glGenTexturesでテクスチャオブジェクトを作成して、glTexImage2Dでデータの設定という、これまた普通の使い方です。
GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_Tを GL_CLAMP_TO_EDGEに設定していますが、こうするとOpenGL ES 2.0ではテクスチャの幅や高さが2の累乗でなくても良くなるそうです。


テクスチャ描画のために、新しくシェーダプログラムを追加しています。
Renderer.cpp
	{						// SHADER_TEXTURE(テクスチャ有り)
		static const
		char	gVertexShader[] = 					// 頂点シェーダプログラム
					"attribute vec4 position;"
					"attribute vec4 color;"
					"varying vec4 vColor;"
					"attribute vec2 texcoord;"
					"varying vec2 vTexcoord;"
					"uniform mat4 projection;"
					"void main() {"
						"gl_Position = projection*position;"
						"vColor = color;"
						"vTexcoord = texcoord;"
					"}";

		static const
		char	gFragmentShader[] = 				// フラグメントシェーダプログラム
					"precision mediump float;"
					"varying vec4 vColor;"
					"varying vec2 vTexcoord;"
					"uniform sampler2D texture;"
 					"void main() {"
						"gl_FragColor = texture2D(texture, vTexcoord)*vColor;"
					"}";

		vert_shader = loadShader(GL_VERTEX_SHADER, gVertexShader);			// 頂点シェーダ
		frag_shader = loadShader(GL_FRAGMENT_SHADER, gFragmentShader);		// フラグメントシェーダ

		shader[SHADER_TEXTURE].init(vert_shader, frag_shader, TRUE);		// シェーダプログラム作成
		glDeleteShader(vert_shader);
		glDeleteShader(frag_shader);
	}

シェーダが増えたことで切り替え処理など他にも変更点はあるのですが、ここでは省略します.


最後に実施にテクスチャを描画する部分です。
AppMain.cpp

#include "common.h"
#include "Renderer.h"
#include "Texture.h"


static sys::Texture		texture;			// テクスチャ

/************
    初期化
 ************/
void	init_app(void)
{
	u8*		_buf = (u8*)sys::load_asset("photo.png");			// テクスチャ用PNG読み込み

	texture.load_png(_buf);					// テクスチャ設定
	free(_buf);
}

/******************************
    稼働
		戻り値	アプリ続行か
 ******************************/
Bool	update_app(void)
{
	static const
	float	texcoord[] =
			{
				0.0f, 0.0f,
				1.0f, 0.0f,
				0.0f, 1.0f,
				1.0f, 1.0f
			};

	static const
	float	vertex[] =
			{
				-320.0f, -480.0f,
				 320.0f, -480.0f,
				-320.0f,  480.0f,
				 320.0f,  480.0f,
			};

	static const
	GLubyte	color[] =
			{
				255, 255, 255, 255,
				255, 255, 255, 255,
				255, 255, 255, 255,
				255, 255, 255, 255,
			};

	sys::ShaderProgram*		_shader;

	_shader = sys::Renderer::use_shader(sys::Renderer::SHADER_TEXTURE);					// テクスチャ用シェーダ

	glBindTexture(GL_TEXTURE_2D, texture.texture);										// テクスチャ
	glVertexAttribPointer(_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, texcoord);		// UV座標
	glVertexAttribPointer(_shader->position, 2, GL_FLOAT, GL_FALSE, 0, vertex);			// 頂点座標
	glVertexAttribPointer(_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, color);		// カラー
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	return	TRUE;
}

/**************** End of File *************************************************/

初期化部でテクスチャを読み込み、稼働部で描画しています。
描画は生ポリゴンの時の処理に、テクスチャのバインドとUV座標の設定が追加された位です。四角形の描画なので、トライアングルストリップ(GL_TRIANGLE_STRIP)を使用しています。

テクスチャ描画

プロジェクト一式はこちらです。