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)を使用しています。

テクスチャ描画

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

裏でこっそりフレームバッファ2013/11/06

今までのサンプルではテクスチャなどを画面に直接描画していましたが、これを画面ではなくフレームバッファと呼ばれる自分で作成したバッファに描画することができます(画面に描画するのも、デフォルトのフレームバッファに描画するということらしいですが)。
そんなことをして何が嬉しいのかと言いますと…

昨日のサンプルでは描画しているテクスチャは一枚だけでした。これがテクスチャなどの枚数が増えてくると、描画にいくらかの時間がかかるようになります。そうすると描画途中の絵が画面に出ることになり、絵がちらついたりします。
フレームバッファを使用する場合、絵は一旦フレームバッファに描画して、全て描画した後に画面に表示します。描画途中の絵が画面に出ることはないのでちらつきはありません。オフスクリーンレンダリングと呼ばれる方法です。


フレームバッファクラス sys::FrameBufferを見てみます。
FrameBuffer.h
#ifndef	___FRAME_BUFFER_H___
#define	___FRAME_BUFFER_H___

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


namespace sys
{

/********************************
    フレームバッファテクスチャ
 ********************************/
class FrameBuffer : public Texture
{
	GLuint		frame_buffer;			// フレームバッファオブジェクト
	GLfloat		mat_projection[4*4];	// 透視変換行列

public :

		FrameBuffer(void)				// コンストラクタ
		{
			frame_buffer = 0;
		}
		FrameBuffer(int _w, int _h)
		{
			make(_w, _h);
		}
		~FrameBuffer()					// デストラクタ
		{
			clear();
		}

	void	make(int, int);				// 作成
	void	clear(void);				// 削除
	void	bind(void);					// 使用
};

}

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

FrameBuffer.cpp
/*********************************************

		フレームバッファテクスチャ

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

#include "FrameBuffer.h"
#include "Renderer.h"


namespace sys
{

/********************************
    作成
		引数	_width  = 幅
				_height = 高さ
 ********************************/
void	FrameBuffer::make(int _width, int _height)
{
	glGenFramebuffers(1, &frame_buffer);					// フレームバッファオブジェクト作成
	glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);

	format	= FORMAT_RGBA;			// テクスチャフォーマット
	width	= _width;				// 幅
	height	= _height;				// 高さ
	Texture::make(NULL);			// テクスチャ作成
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);	// フレームバッファにアタッチ

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	for (int i = 0; i < 4*4; i++) {							// 透視変換行列初期化
		mat_projection[i] = 0.0f;
	}
	mat_projection[0]  =  2.0f/width;
	mat_projection[5]  = -2.0f/height;
	mat_projection[10] =  1.0f;
	mat_projection[15] =  1.0f;
}

/**********
    削除
 **********/
void	FrameBuffer::clear(void)
{
	if ( frame_buffer ) {
		glDeleteFramebuffers(1, &frame_buffer);
		frame_buffer = 0;
		Texture::clear();
	}
}

/**********
    使用
 **********/
void	FrameBuffer::bind(void)
{
	glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer);
	glViewport(0, 0, width, height);						// ビューポート設定
	Renderer::mat_projection = mat_projection;				// 透視変換行列設定
	if ( Renderer::current_shader ) {
		glUniformMatrix4fv(Renderer::current_shader->projection, 1, GL_FALSE, mat_projection);
	}
}

}

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

テクスチャとしてカラーバッファを持っていますので、sys::Textureの派生クラスになっています。普通はデプスバッファやステンシルバッファといったレンダーバッファを持っているものなんですが、ここではカラーバッファしか使いません。

作成(make)時には幅と高さを指定しています。今回のように画面全体のフレームバッファとして使う場合はサイズは仮想画面と同じになりますが、描画(されること)のできるテクスチャとして他にも使いようがありますのでサイズは指定できるようになっています。
描画時の座標をサイズに合わせるために、座標変換の行列はフレームバッファで持っておきます。

描画クラス sys::Rendererの変更部分です。
Renderer.cpp
/************************************************
    初期化
		引数	width, height = 端末画面サイズ
 ************************************************/
void	Renderer::init(int width, int height)
{
	initShader();								// シェーダ初期化
	frame_buffer = new FrameBuffer(SCREEN_WIDTH, SCREEN_HEIGHT);		// フレームバッファ作成

	if ( width*SCREEN_HEIGHT < height*SCREEN_WIDTH ) {		// 横長(上下カット)
		screen_rect.w = width;
		screen_rect.h = width*SCREEN_HEIGHT/SCREEN_WIDTH;
	}
	else {													// 縦長(左右カット)
		screen_rect.w = height*SCREEN_WIDTH/SCREEN_HEIGHT;
		screen_rect.h = height;
	}
	screen_rect.x = (width - screen_rect.w)/2;
	screen_rect.y = (height - screen_rect.h)/2;
}

まず初期化部分では、仮想画面サイズでフレームバッファを作成しています。
ビューポートの設定はフレームバッファの切り替えごとに行うので、ここでは値の計算だけしておきます。

/********************
    稼働(前処理)
 ********************/
void	Renderer::update(void)
{
	glEnable(GL_BLEND);					// αブレンド初期化
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	frame_buffer->bind();				// フレームバッファ使用
}

/********************
    描画(後処理)
 ********************/
void	Renderer::draw(void)
{
	static const
	GLfloat	_projection[4*4] =
			{
				1.0f, 0.0f, 0.0f, 0.0f,
				0.0f, 1.0f, 0.0f, 0.0f,
				0.0f, 0.0f, 1.0f, 0.0f,
				0.0f, 0.0f, 0.0f, 1.0f,
			};

	static const
	GLfloat	_texcoords[] =
			{
				0.0f, 0.0f,
				1.0f, 0.0f,
				0.0f, 1.0f,
				1.0f, 1.0f
			};

	static const
	GLfloat	_vertices[] =
			{
				-1.0f, -1.0f,
				 1.0f, -1.0f,
				-1.0f,  1.0f,
				 1.0f,  1.0f,
			};

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

	ShaderProgram*	_shader = use_shader(SHADER_TEXTURE);

	// フレームバッファテクスチャ描画
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glViewport(screen_rect.x, screen_rect.y, screen_rect.w, screen_rect.h);
	glUniformMatrix4fv(_shader->projection, 1, GL_FALSE, _projection);
	glBindTexture(GL_TEXTURE_2D, frame_buffer->texture);
	glVertexAttribPointer(_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, _texcoords);
	glVertexAttribPointer(_shader->position, 2, GL_FLOAT, GL_FALSE, 0, _vertices);
	glVertexAttribPointer(_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, _colors);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

今までは Rendererの処理はメインの処理前にだけ行っていましたが、最後にフレームバッファを画面に描画しなければならないので後にも処理を入れています。

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

閃の軌跡 再び2013/11/07


なかよし9人

新しいパッチでロードが早くなったということで、「閃の軌跡」の2週目を始めました。
まだ序盤ですが、確かに体感的に結構早くなっています。今までがひどかったので、やっと普通のゲーム並みというところでしょうか。最初からこのくらいで売ればいいのに。

1週目は先が気になるのと、街がだだっ広いのとであまり寄り道をせず進めましたが、今度はじっくりやってみようと思います。
今作で話は完結していないので、最後までじっくりやったところでどのみち先は気になるんですが。


続きがどうなるというより、どういう方向に進むのかもわからないような終わり方だったからなぁ…。

スプライトで描画2013/11/08

テクスチャで画像を描画できるようになりましたが、毎度OpenGLを直接叩くのでは手順が面倒です。
描画を管理するスプライトクラスを作って、手軽に描画できるようにしましょう。

・スプライトクラス(sys::Sprite)
スプライトはテクスチャと、描画する範囲(切り抜き)から成っています。つまり1枚のテクスチャには複数のスプライトの画像を収めることができます(描画時、テクスチャの切り替えにはコストがかかるので、なるべく少ないテクスチャにまとめておいた方が効率が良いです)。

Sprite.h
#ifndef	___SPRITE_H___
#define	___SPRITE_H___

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


namespace sys
{

/****************
    スプライト
 ****************/
class Sprite
{
protected :
	static u32 const*	spr_color;		// カラー

public :

// 原点位置
enum
{
	X_CENTER	= 0x00,			// センタリング
	X_LEFT		= 0x01,			// 左揃え
	X_RIGHT		= 0x02,			// 右揃え
	Y_CENTER	= 0x00,			// センタリング
	Y_TOP		= 0x10,			// 上揃え
	Y_BOTTOM	= 0x20,			// 下揃え
};

	static void		set_color(u32 _color = 0xffffffff);		// カラー設定
	static void		set_color(u8 _r, u8 _g, u8 _b, u8 _a = 0xff)
					{
						set_color(RGBA(_r, _g, _b, _a));
					}
	static void		set_color(u32 const*);


	Texture*	texture;			// テクスチャ
	GLfloat		texcoord[4][XY];	// UV座標
	short		width, height;		// 大きさ
	short		ox, oy;				// 原点

	void	set(Texture*, SRect const*, int _origin = X_CENTER | Y_CENTER);				// 設定
	void	set(Texture*, int _origin = X_CENTER | Y_CENTER);							// 設定(テクスチャに合わせた大きさ)
	void	set(short, short, int _origin = X_CENTER | Y_CENTER);						// 設定(テクスチャ無し)
	void	set(short, const void*, int _origin = X_CENTER | Y_CENTER);
	void	set_origin(int _origin = X_CENTER | Y_CENTER);								// 原点位置設定

	void	draw(GLfloat*);			// 描画
	void	draw(float, float);
	void	draw(float, float, float, float);
	void	draw(float _x, float _y, float _scl)
			{
				draw(_x, _y, _scl, _scl);
			}
	void	draw(float, float, float, float, float);
	void	draw(float const*);
};

}

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

Sprite.cpp
/*******************************

		スプライト

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

#include "Sprite.h"
#include "Renderer.h"


namespace sys
{

u32 const*	Sprite::spr_color;			// カラー


/**************************************
    設定
		引数	_tex    = テクスチャ
				_coord  = UV座標
				_origin = 原点位置
 **************************************/
void	Sprite::set(Texture* _tex, SRect const* _coord, int _origin)
{
	texture	= _tex;						// テクスチャ
	texcoord[0][X] = texcoord[2][X] = (GLfloat)_coord->x/_tex->width;		// UV座標
	texcoord[0][Y] = texcoord[1][Y] = (GLfloat)_coord->y/_tex->height;
	texcoord[1][X] = texcoord[3][X] = (GLfloat)(_coord->x + _coord->w)/_tex->width;
	texcoord[2][Y] = texcoord[3][Y] = (GLfloat)(_coord->y + _coord->h)/_tex->height;
	width	= _coord->w;				// 幅
	height	= _coord->h;				// 高さ
	set_origin(_origin);				// 原点位置設定
}

void	Sprite::set(Texture* _tex, int _origin)
{
	texture	= _tex;						// テクスチャ
	texcoord[0][X] = texcoord[2][X] = 0.0f;			// UV座標
	texcoord[0][Y] = texcoord[1][Y] = 0.0f;
	texcoord[1][X] = texcoord[3][X] = 1.0f;
	texcoord[2][Y] = texcoord[3][Y] = 1.0f;
	width	= _tex->width;				// 幅
	height	= _tex->height;				// 高さ
	set_origin(_origin);				// 原点位置設定
}

void	Sprite::set(short _width, short _height, int _origin)
{
	texture	= NULL;						// テクスチャ無し
	width	= _width;					// 幅
	height	= _height;					// 高さ
	set_origin(_origin);				// 原点位置設定
}

/************************************
    原点位置設定
		引数	_origin = 原点位置
 ************************************/
void	Sprite::set_origin(int _origin)
{
	switch ( _origin & 0x0f ) {
	  case X_CENTER :				// センタリング
		ox = width/2;
		break;

	  case X_LEFT :					// 左揃え
		ox = 0;
		break;

	  case X_RIGHT :				// 右揃え
		ox = width;
		break;
	}

	switch ( _origin & 0xf0 ) {
	  case Y_CENTER :				// センタリング
		oy = height/2;
		break;

	  case Y_TOP :					// 上揃え
		oy = 0;
		break;

	  case Y_BOTTOM :				// 下揃え
		oy = height;
		break;
	}
}


/***********************************
    カラー設定
		引数	_color = カラー値
 ***********************************/
void	Sprite::set_color(u32 _color)
{
	if ( _color == 0xffffffff ) {			// デフォルト
		static const
		u32		color_white[] =
				{
					0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
				};

		spr_color = color_white;
	}
	else if ( (_color != spr_color[0]) || (_color != spr_color[1]) || (_color != spr_color[2]) || (_color != spr_color[3]) ) {
		u32*	_p = (u32*)Renderer::get_prim_buffer(sizeof(u32)*4);		// カラーバッファ

		_p[0] = _color;
		_p[1] = _color;
		_p[2] = _color;
		_p[3] = _color;
		spr_color = _p;
	}
}

void	Sprite::set_color(u32 const* _color)
{
	u32*	_p = (u32*)Renderer::get_prim_buffer(sizeof(u32)*4);		// カラーバッファ

	_p[0] = _color[0];
	_p[1] = _color[1];
	_p[2] = _color[2];
	_p[3] = _color[3];
	spr_color = _p;
}


/***********************************
    描画
		引数	_x, _y   = 位置
				_sx, _sy = 拡大率
				_rot     = 回転角
 ***********************************/
void	Sprite::draw(GLfloat* _vertex)
{
	if ( texture ) {				// テクスチャ有り
		Renderer::use_shader(Renderer::SHADER_TEXTURE);		// シェーダ
		texture->bind();									// テクスチャ
		Renderer::set_texcoord(&texcoord[0][0]);			// UV座標
	}
	else {							// テクスチャ無し
		Renderer::use_shader(Renderer::SHADER_PLAIN);		// シェーダ
	}
	sys::Renderer::set_vertex(_vertex);						// 頂点
	Renderer::set_color((GLubyte*)spr_color);				// カラー

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);					// 描画
}

void	Sprite::draw(float _x, float _y)
{
	GLfloat*	_vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY);		// 頂点バッファ

	_vertex[0*XY + X] = _vertex[2*XY + X] = _x - ox;
	_vertex[0*XY + Y] = _vertex[1*XY + Y] = _y - oy;
	_vertex[1*XY + X] = _vertex[3*XY + X] = _x + (width - ox);
	_vertex[2*XY + Y] = _vertex[3*XY + Y] = _y + (height - oy);
	draw(_vertex);
}

void	Sprite::draw(float _x, float _y, float _sx, float _sy)
{
	GLfloat*	_vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY);		// 頂点バッファ

	_vertex[0*XY + X] = _vertex[2*XY + X] = _x - _sx*ox;
	_vertex[0*XY + Y] = _vertex[1*XY + Y] = _y - _sy*oy;
	_vertex[1*XY + X] = _vertex[3*XY + X] = _x + _sx*(width - ox);
	_vertex[2*XY + Y] = _vertex[3*XY + Y] = _y + _sy*(height - oy);
	draw(_vertex);
}

void	Sprite::draw(float _x, float _y, float _sx, float _sy, float _rot)
{
	GLfloat*	_vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY);		// 頂点バッファ

	float	_cos = cosf(_rot), _sin = sinf(_rot),
			_cx0, _sx0, _cy0, _sy0, _cx1, _sx1, _cy1, _sy1;

	_cx0 = -_sx*ox; _sx0 = _sin*_cx0; _cx0 *= _cos;
	_cy0 = -_sy*oy; _sy0 = _sin*_cy0; _cy0 *= _cos;
	_cx1 = _sx*(width - ox); _sx1 = _sin*_cx1; _cx1 *= _cos;
	_cy1 = _sy*(height - oy); _sy1 = _sin*_cy1; _cy1 *= _cos;

	_vertex[0*XY + X] = _x + _cx0 - _sy0;
	_vertex[0*XY + Y] = _y + _cy0 + _sx0;
	_vertex[1*XY + X] = _x + _cx1 - _sy0;
	_vertex[1*XY + Y] = _y + _cy0 + _sx1;
	_vertex[2*XY + X] = _x + _cx0 - _sy1;
	_vertex[2*XY + Y] = _y + _cy1 + _sx0;
	_vertex[3*XY + X] = _x + _cx1 - _sy1;
	_vertex[3*XY + Y] = _y + _cy1 + _sx1;
	draw(_vertex);
}

void	Sprite::draw(float const* vertex)
{
	GLfloat*	_vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY);		// 頂点バッファ

	for (int i = 0; i < 4*XY; i++) {
		_vertex[i] = vertex[i];
	}
	draw(_vertex);
}

}

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

・初期化
void set(Texture* _tex, SRect const* _coord, int _origin)
void set(Texture* _tex, int _origin)
テクスチャ _tex、描画範囲座標 _coordでスプライトを初期化します。描画範囲を省略すると、テクスチャ自体と同じ大きさ(1枚のテクスチャ = 1枚のスプライト)になります。
_originはスプライトの原点を指定します。省略すると画像の中心が原点になります(sys::Sprite::X_CENTER | sys::Sprite::Y_CENTER)。

void set(short _width, short _height, int _origin)
テクスチャを指定しないと、幅 _width、高さ _heightの単色ポリゴンになります。

・描画
void draw(float _x, float _y)
座標(_x, _y)に描画します。

void draw(float _x, float _y, float _sx, float _sy)
縦と横の拡大率を指定して描画します。

void Sprite::draw(float _x, float _y, float _sx, float _sy, float _rot)
角度 _rot(単位:ラジアン)で回転して描画します。

void draw(float const* vertex)
直接4頂点(vertex[4][XY])を指定して描画します。


さて描画時に頂点座標をOpenGLに設定しますが、このデータをスプライト自体に持っておくと1度に1枚のスプライトしか描画できなくなります。よってRendererの方で汎用的なバッファを持っておいて、描画の度にそこから使用するようにします。
なお、UV座標はスプライトごとに固定なのでスプライト側で持っておきます。

Rendererの汎用的なプリミティブバッファの管理部分です。
def.hにバッファサイズ PRIM_BUF_SIZEを定義しておきます。

Renderer.cpp
	prim_buffer = (u8*)memalign(4, PRIM_BUF_SIZE);			// プリミティブ用汎用バッファ
	prim_p = 0;

/****************************************
    プリミティブバッファ取得
			引数	_size = 使用サイズ
			戻り値	バッファ
 ****************************************/
void*	Renderer::get_prim_buffer(u32 _size)
{
	if ( prim_p + _size > PRIM_BUF_SIZE ) {
		prim_p = 0;
	}

	void*	_ret = (void*)(prim_buffer + prim_p);

	prim_p += _size;
	return	_ret;
}


また、テクスチャのバインドなど変化していないのに設定し直すのは効率が悪いので、現在の値を覚えておくようにします。
ただし、頂点は無条件で設定しています。全く同じ座標に何枚もプリミティブを描画することは少ないと思われますので。
/**********************************************
    テクスチャ使用
		引数	tex = テクスチャオブジェクト
 **********************************************/
void	Renderer::bind_texture(GLuint tex)
{
	if ( tex != current_texture ) {
		glBindTexture(GL_TEXTURE_2D, tex);
		current_texture = tex;
	}
}

/********************************
    カラー設定
		引数	color = カラー
 ********************************/
void	Renderer::set_color(GLubyte const* color)
{
	if ( color != current_color ) {
		glVertexAttribPointer(current_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, color);
		current_color = color;
	}
}

/********************************
    テクスチャUV座標設定
		引数	coord = UV座標
 ********************************/
void	Renderer::set_texcoord(GLfloat const* coord)
{
	if ( coord != current_texcoord ) {
		glVertexAttribPointer(current_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, coord);
		current_texcoord = coord;
	}
}

/***********************************
    頂点座標設定
		引数	vertex = 頂点座標
 ***********************************/
void	Renderer::set_vertex(GLfloat const* vertex)
{
	glVertexAttribPointer(current_shader->position, 2, GL_FLOAT, GL_FALSE, 0, vertex);
}


それでは、実際の使用例です。
AppMain.cpp

#include "common.h"
#include "Sprite.h"


// テクスチャ
enum
{
	TEX_PHOTO = 0,			// 背景
	TEX_BALL,				// ビー玉
	TEX_MAX,
};

// スプライト
enum
{
	SPR_PHOTO = 0,			// 背景
	SPR_BALL_BLUE,			// 青ビー玉
	SPR_BALL_GREEN,			// 緑ビー玉
	SPR_BALL_RED,			// 赤ビー玉
	SPR_BALL_YELLOW,		// 黄ビー玉
	SPR_MAX,
};

static sys::Texture		texture[TEX_MAX];			// テクスチャ
static sys::Sprite		sprite[SPR_MAX];			// スプライト

static int	cnt;			// カウンタ


/************
    初期化
 ************/
void	init_app(void)
{
	static const
	char*	tex_name[TEX_MAX] =
			{
				"photo.png",
				"biidama.png",
			};
	u8*		_buf;

	for (int i = 0; i < TEX_MAX; i++) {				// テクスチャ読み込み
		_buf = (u8*)sys::load_asset(tex_name[i]);
		texture[i].load_png(_buf);
		free(_buf);
	}


	struct SprDef
	{
		short	tex_num;			// テクスチャ番号
		SRect	coord;				// UV座標
	};

	static const
	SprDef	spr_def[SPR_MAX] =
			{
				{TEX_PHOTO,		{  0,   0, 640, 960}},			// 背景
				{TEX_BALL,		{  0,   0,  60,  60}},			// 青ビー玉
				{TEX_BALL,		{ 60,   0,  60,  60}},			// 緑ビー玉
				{TEX_BALL,		{  0,  60,  60,  60}},			// 赤ビー玉
				{TEX_BALL,		{ 60,  60,  60,  60}},			// 黄ビー玉
			};

	for (int i = 0; i < SPR_MAX; i++) {				// スプライト設定
		sprite[i].set(&texture[spr_def[i].tex_num], &spr_def[i].coord);
	}

	cnt = 0;			// カウンタ
}

/******************************
    稼働
		戻り値	アプリ続行か
 ******************************/
Bool	update_app(void)
{
	sprite[SPR_PHOTO].draw(0.0f, 0.0f);			// 背景
	for (int i = 0; i < 4; i++) {				// ビー玉
		int		t = ((cnt + i*15) % 60) - 30;

		sprite[SPR_BALL_BLUE + i].draw(i*128 - 192, 400 - (30*30 - t*t)/2);
	}
	cnt++;

	return	TRUE;
}

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

跳ねるビー玉

※ビー玉の画像は「かわいいフリー素材集 いらすとや」様から使わせていただきました。どうもありがとうございます。


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

HTML5ゲーム:かえすがえす2013/11/09

いつもAndroidのプログラミングの話をしていますが、HTML5なんかにも手を出していたりします。
投稿型ゲームサイト9leapで公開済みのものですが、にぎやかしということで制作物を紹介致します。

パズル
かえすがえす


・ルール
隣の足場へ移動すると両脇のパネルがひっくり返り、消えていたものは出現、出現していたものは消えます。
ただし、一度乗った足場は消えてしまい、もう一度乗ることはできません。
全てのパネルを出現させて、絵を完成させればクリアです。

・操作方法
キャラクタの上下左右をタッチして、その方向に足場があれば移動します。
移動をキャンセルする場合は、来た方向に戻ってください。


スリザーリンクの亜流みたいなものです。
問題はランダムで作っているので、ときどき「NORMAL」や「HARD」でもやたらと簡単だったりしますがご容赦ください。

プログラムの制作には、enchant.jsを使用しています。