이전 2의 제곱수가 아닌 이미지 출력하기...와 함께 적으려고했는데..
길이가 너무 길어지는것 같아.. 따로 작성합니다.

대부분의 이미지를 일부러 2의 제곱수로 만들지 않으므로 2의 제곱수로 변환하고 텍스쳐를 생성하는 작업을 많이하게됩니다.
그래서 이미지가 많이사용되는 게임의 경우에는 1024*1024크기의 이미지에 전부 넣어놓고 특정 부분만 잘라서 사용하는 방식을 사용하더군요.. 팔라독, 룰더스카이 등등..

확실히 큰 이미지 몇개를 로딩하는것이 작은것 여러개를 변환하고 만들어 로딩하는것보다는 효율적인것 같습니다.

이번에는 텍스쳐의 부분부분을 출력하는 방법을 알아봅시다.
다시 512*512 크기의 팬더를 사용해서 해보겠습니다.

팬더의 왼쪽눈이 대충 71, 174 위치에 자리잡고 있군요.. 크기는 120, 80 정도로 잡겠습니다.

GLGameRenderer.c
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	updatePanda();
	//drawPanda();
	drawPandaClip(71, 174, 120, 80);
}
기존의 drawPanda()를 잠시 쉬게하고 drawPandaClip을 만듭니다.

void drawPandaClip(int sx, int sy, int w, int h)
{
	GLfloat vertices[12] = {
			g_nX	,	g_nY+h	,	0.0f,	// LEFT  | BOTTOM
			g_nX+w	,	g_nY+h	,	0.0f,	// RIGHT | BOTTOM
			g_nX	,	g_nY	,	0.0f,	// LEFT  | TOP
			g_nX+w	,	g_nY	,	0.0f	// RIGHT | TOP
	};

	GLfloat texture[8] = {
			(GLfloat)sx/(GLfloat)g_nPandaWidth		, (GLfloat)(sy+h)/(GLfloat)g_nPandaHeight,
			(GLfloat)(sx+w)/(GLfloat)g_nPandaWidth	, (GLfloat)(sy+h)/(GLfloat)g_nPandaHeight,
			(GLfloat)sx/(GLfloat)g_nPandaWidth		, (GLfloat)sy/(GLfloat)g_nPandaHeight,
			(GLfloat)(sx+w)/(GLfloat)g_nPandaWidth	, (GLfloat)sy/(GLfloat)g_nPandaHeight
	};

	
	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
sx, sy
 팬더의 눈이 시작될 지점이 되겠군요.
w, h
 팬더 눈의 크기 입니다.
vertices[12]
 그려질 위치와 크기를 지정해 주는곳이죠.
 g_nX, g_nY는 그려질 위치이며, 입력받은 w, h만큼의 크기로 그려준다고 알려줍니다.
texture[8]
 이 부분이 실제 텍스쳐에서 그려질 부분을 지정하는 부분입니다.
 기존의 drawPanda()소스에서는
	GLfloat texture[8] = {
			0	, 1,
			1	, 1,
			0	, 0,
			1	, 0
	};
왼쪽이 width, 오른쪽이 height라고 말씀드렸었죠.
0 ~ 1까지가 0 ~ 100%를 표현한 것이라고도 말씀드렸었구요.
위 소스를 실행시켰을때가 이미지가 100%의 크기로 나왔으니 0은 텍스쳐의 시작을 1은 끝을 의미한다고 볼 수 있습니다.
그럼 0은 원하는 좌표(sx, sy)비율을 1은 원하는 크기까지(sx+w, sy+h)의 비율을 넣으면 됩니다.

왼쪽의 0은 width 부분의 시작 : 이미지 가로길이중에 sx가 차지하는 비율
왼쪽의 1은 width 부분의 끝    : 이미지 가로길이중에 sx+w가 차지하는 비율
오른쪽의 0은 height 부분의 시작 : 이미지 세로길이중에 sy가 차지하는 비율
오른쪽의 1은 height 부분의 끝    : 이미지 세로길이중에 sy+h가 차지하는 비율

빌드 후 실행..


깔끔하군요!


이번에는 원하는 좌표를 중심으로 갖는 이미지를 출력해보겠습니다.

GLGameRenderer.c
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	updatePanda();
	//drawPanda();
	//drawPandaClip(71, 174, 120, 80);
	drawPandaC(240, 400);
}
방금 작업한 clip도 휴식을 주고 drawPandaC()함수를 제작합니다.
void drawPandaC(int cx, int cy)
{
		GLfloat vertices[12] = {
			cx - (g_nPandaWidth>>1)	,	cy + (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | BOTTOM
			cx + (g_nPandaWidth>>1)	,	cy + (g_nPandaHeight>>1)	,	0.0f,	// RIGHT | BOTTOM
			cx - (g_nPandaWidth>>1)	,	cy - (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | TOP
			cx + (g_nPandaWidth>>1)	,	cy - (g_nPandaHeight>>1)	,	0.0f	// RIGHT | TOP
	};

	GLfloat texture[8] = {
			0				, g_textureHeight,
			g_textureWidth	, g_textureHeight,
			0				, 0,
			g_textureWidth	, 0
	};

	
	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
이미지의 중점 cx, cy를 받아오는군요.
vertices좌표에서 기존의 g_nX, g_nY가 받아온 중점으로 바뀌었습니다.
그리고 그 중점을 기준으로 이미지의 절반씩 더하거나 빼줍니다.

빌드 후 실행 ~

 
딱 가운데 왔지요~! 


회전을 해볼까요?
void drawPandaC(int cx, int cy, int deg)
방금사용한 함수에 파라메타를 하나 추가합니다.
	GLfloat vertices[12] = {
			- (g_nPandaWidth>>1)	,	+ (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | BOTTOM
			+ (g_nPandaWidth>>1)	,	+ (g_nPandaHeight>>1)	,	0.0f,	// RIGHT | BOTTOM
			- (g_nPandaWidth>>1)	,	- (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | TOP
			+ (g_nPandaWidth>>1)	,	- (g_nPandaHeight>>1)	,	0.0f	// RIGHT | TOP
	};
vertices 좌표가 달라졌습니다.
이유는 기존의 상태로 회전을 시키면 원하는대로 회전해주지 않으니까요 ~
0,0을 이미지 중점으로 놓고 그려줍니다.


	glLoadIdentity();
	glTranslatef(cx, cy, 0.0f);
	glRotatef(deg, 0.0f, 0.0f, 1.0f);
좌표계를 초기화 해주고 ( 이전 좌표계의 영향을 받을 수 있으므로 )
glTranslatef(cx, cy, 0.0f);
 기준 축을 cx, cy로 이동시킵니다.
glRotatef(deg, 0.0f, 0.0f, 1.0f);
  z축을 기준으로 회전을 시킵니다.

빌드 후 실행 ~
원하는 좌표(cx, cy)에서 빙글빙글 도는 팬더가 출력되는군요. 


확대와 축소를 해볼차례 입니다~!
회전하기전 drawPandaC() 소스를 기반으로 하겠습니다.
void drawPandaScale(int cx, int cy, GLfloat scale)
{
	GLfloat vertices[12] = {
			cx - (g_nPandaWidth>>1)/scale	,	cy + (g_nPandaHeight>>1)/scale	,	0.0f,	// LEFT  | BOTTOM
			cx + (g_nPandaWidth>>1)/scale	,	cy + (g_nPandaHeight>>1)/scale	,	0.0f,	// RIGHT | BOTTOM
			cx - (g_nPandaWidth>>1)/scale	,	cy - (g_nPandaHeight>>1)/scale	,	0.0f,	// LEFT  | TOP
			cx + (g_nPandaWidth>>1)/scale	,	cy - (g_nPandaHeight>>1)/scale	,	0.0f	// RIGHT | TOP
	};

	GLfloat texture[8] = {
			0				, g_textureHeight,
			g_textureWidth	, g_textureHeight,
			0				, 0,
			g_textureWidth	, 0
	};

	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
vertices만 바뀌었습니다.
scale 값이 0 < scale < 1.0 이면 확대
1.0 < scale 축소 입니다.

 
왼쪽은 scale이 0.5일때의 화면이며
오른쪽은 scale이 2.0일때의 화면입니다. 






먼저 이전에 텍스쳐는 2의 제곱수여야만 한다는 전제가 있었습니다.
그럼! 모든 이미지를 2의 제곱수로 만들어야 하나? 말도안되지요..

1. 2의 제곱수가 아닌 이미지 출력하기
안드로이드 프로젝트 생성시 포함되어있는 아이콘이 72*72군요. 2의 제곱수가 아닙니다~! 이걸로 해봅시다.

GLView.java
 onSurfaceCreated() 함수안에 만들어놓았던 소스를 변경하겠습니다.
Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher);
panda -> ic_launcher로 이미지를 바꾸어주었습니다.
빌드 후 실행해보면..

역시 이미지가 출력되지 않는군요~

GLGameRenderer.c
 setTextureData()함수에서 2의 제곱수만큼의 비어있는 버퍼를 생성하고 현재 픽셀값을 넣어주는 작업을 합니다.

int makeWidth, makeHeight;
char *makeBuf;
만들어질 버퍼의 가로, 세로 그리고 픽셀데이터를 저장할 버퍼를 선언합니다.

	makeWidth = makeHeight = 2;

	while( makeWidth < width )
		makeWidth = makeWidth << 1;

	while( makeHeight < height )
		makeHeight = makeHeight << 1;

	makeBuf = (char *)malloc((sizeof(char)*makeWidth*makeHeight)<<2);
가로와 세로는 2의 제곱수로 만들어야 하기 때문에 초기값을 2로 주겠습니다.
두개의 while문은 기존의 이미지를 모두 포함해야하기 때문에 기존 이미지보다 크면서 가장 가까운 2의 제곱수를 찾는 것입니다.
마지막으로 위에서 만든 가로, 세로만큼의 픽셀데이터를 가질 버퍼를 생성합니다.

RGBA 변환을 마친 후 데이터를 정리해보겠습니다.
아래 이미지중에서 검은색 배경부분은 새로 생성한 128*128크기의 버퍼입니다.
흰색 배경부분은 72*72이미지 입니다.
현재 검은색 버퍼를 완성한 상태이고, 아래 그림과같이 원하는 이미지를 정렬해서 넣어주는 작업이 필요합니다.



	int row, col;

	row = -1;
	col = 0;
	for ( i = 0; i < width*height*4; i += 4 )
	{
		if ( i % (width<<2) == 0 )
		{
			row++;
			col = 0;
		}

		makeBuf[(row*makeWidth<<2)+col] = buf[i];
		makeBuf[(row*makeWidth<<2)+col+1] = buf[i+1];
		makeBuf[(row*makeWidth<<2)+col+2] = buf[i+2];
		makeBuf[(row*makeWidth<<2)+col+3] = buf[i+3];
		col += 4;
	}
두개의 변수가 추가로 필요하네요. row(행)와 col(렬)
위 코드는 흰색배경부분의 픽셀을 검은색 배경 0,0위치부터 붙여나갑니다.
그러다가 흰배경부분 1줄을 다 그렸을때 2번째 줄을 이어서 그리면? 안되죠!
2번째 줄은 검은색 배경에도 2번째 줄부터 그려주어야 합니다. 

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, makeWidth, makeHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)makeBuf);
텍스쳐의 가로, 세로 그리고 버퍼 부분이 바뀌었습니다.
새롭게 생성한 makeWidth, makeHeight크기의 텍스쳐를 생성할 것이며, 픽셀 데이터는 변환된 makeBuf를 넣어주어야지요.

사용한 메모리를 해제해 주는것도 잊지맙시다.
free(makeBuf);

빌드 후 실행해보면 ~ 



이미지는 정상 출력되는데... 뭔가 이상합니다.. 뭔가가...

우리가 원하는것은 72*72 크기의 이미지만을 그려주는 것이였는데 말이죠..


	GLfloat	g_textureWidth;
	GLfloat	g_textureHeight;

	g_textureWidth = (GLfloat)g_nPandaWidth / (GLfloat)makeWidth;
	g_textureHeight = (GLfloat)g_nPandaHeight / (GLfloat)makeHeight;
전역 변수로 g_textureWidth와 g_textureHeight를 추가시켜줍니다.
실제 텍스쳐의 크기 비율을 저장할 것입니다.

그리고 계속해서 코드를 추가시킵니다.
새롭게(makeBuf) 만든 텍스쳐의 크기중에 실제 텍스쳐가 차지하는 비율을 계산하고있습니다.
전체가 1이라고 보면 0.xxxx가 되겠지요?

setTextureData()에서 해야할 일은 끝이났습니다!

이젠 drawPanda()함수로 이동합니다.
texture 배열 부분이 있죠?
	GLfloat texture[8] = {
			0				, g_textureHeight,
			g_textureWidth	, g_textureHeight,
			0				, 0,
			g_textureWidth	, 0
	};
0을 제외한 부분이 1이였는데 이 값을 텍스쳐에서 실제 이미지가 해당되는 비율로 바꾸어주면 됩니다.

빌드 후 실행!
 



뭔가... 높이가 안맞아 보이는듯하네요.. 폰에서는 제대로 보입니다.

그런데, 자꾸 뒷배경이 검은색으로 나오네요..
이유는 ? 알파값! 투명처리가 안되고있습니다.

onSurfaceChanged() 함수에 

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
두줄을 추가시킵니다.

glEnable(GL_BLEND); 
 블렌딩 효과를 활성화.
 블렌딩이 적용되면 새로지정된 색상과 기존색상이 결합되어 옵션에 따라 효과가 달라집니다.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 음... 두 인자에따라 RGB에 계산을 하는데... 위 소스는 알파값을 적용하기 위한 효과이고.. 나머지는 찾아보시길..

두줄을 onSurfaceChanged()에 추가시킨 이유는 다른 블렌딩 효과를 사용안하고 그냥 알파값을 항상 사용하기때문에...-,.- 

빌드 후 확인해보면..



드디어 원하던 이미지 그대로 나왔습니다!


setTextureData() 함수에 변형부분이 많아 최종 소스를
 

void setTextureData(char *data, int width, int height)
{
	int i;
	int row, col;
	int makeWidth, makeHeight;
	char *buf;
	char *makeBuf;

	makeWidth = makeHeight = 2;

	while( makeWidth < width )
		makeWidth = makeWidth << 1;

	while( makeHeight < height )
		makeHeight = makeHeight << 1;

	makeBuf = (char *)malloc((sizeof(char)*makeWidth*makeHeight)<<2);
	buf = (char *)malloc((sizeof(char)*width*height)<<2);

	for (i = 0; i < width*height*4; i += 4)
	{
		buf[i]   = data[i+2];
		buf[i+1] = data[i+1];
		buf[i+2] = data[i];
		buf[i+3] = data[i+3];
	}


	row = -1;
	col = 0;
	for ( i = 0; i < width*height*4; i += 4 )
	{
		if ( i % (width<<2) == 0 )
		{
			row++;
			col = 0;
		}

		makeBuf[(row*makeWidth<<2)+col] = buf[i];
		makeBuf[(row*makeWidth<<2)+col+1] = buf[i+1];
		makeBuf[(row*makeWidth<<2)+col+2] = buf[i+2];
		makeBuf[(row*makeWidth<<2)+col+3] = buf[i+3];
		col += 4;
	}

	g_nPandaWidth = width;
	g_nPandaHeight = height;

	g_textureWidth = (GLfloat)g_nPandaWidth / (GLfloat)makeWidth;
	g_textureHeight = (GLfloat)g_nPandaHeight / (GLfloat)makeHeight;

	glGenTextures(1, &g_textureName);
	
	glBindTexture(GL_TEXTURE_2D, g_textureName);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, makeWidth, makeHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)makeBuf);

	free(buf);
	free(makeBuf);
}
요래~



'Android > OpenGL' 카테고리의 다른 글

[NDK-OpenGL_06] 내맘대로 텍스쳐 ~  (0) 2012.03.06
[NDK-OpenGL_04] 쓰레드 생성  (0) 2012.03.04
[NDK-OpenGL_03] 텍스쳐 입히기  (5) 2012.02.27
[NDK-OpenGL_02] 해상도 지정하기  (3) 2012.02.26
[NDK-OpenGL_01] 프로젝트 준비  (3) 2012.02.26



쓰레드를 만들고 이미지를 움직여보겠습니다.

자바소스에서 GameThread.java를 만들겠습니다.

package pe.berabue.opengl;

import android.content.Context;

public class GameThread extends Thread {
		
	private boolean isRun;
	
	public GameThread(Context context) {
		isRun = true;
	}

	public synchronized void run() {
		while(isRun) {
			try	{
				MainActivity.mGLView.requestRender();
				Thread.sleep(10);
			}
			catch (InterruptedException e1)	{
				e1.printStackTrace();
			}
		}
	}
    }
}
딱 한 부분만 중요합니다.
MainActivity.mGLView.requestRender();
   requestRender()는 onDrawFrame()함수를 호출해줍니다. 쓰레드가 동작하는동안 지속적으로 onDrawFrame을 호출하겠지요.


MainActivity.java에서 쓰레드를 동작시키겠습니다.
package pe.berabue.opengl;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class MainActivity extends Activity {
	
	public static GLSurfaceView mGLView;
	public static GameThread	mThread;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mGLView = new GLView(getApplicationContext());
        mThread = new GameThread(getApplicationContext());
        
        setContentView(mGLView);
        
        mThread.start();
    }
    
    static {
    	System.loadLibrary("berabueLib");
    }
}
public static GLSurfaceView mGLView; 
 private -> public static으로 변경하였습니다. 쓰레드에서 mGLView 객체에 접근하기위해서. 

쓰레드 객체를 생성하고 쓰레드를 시작합니다~!


GLGameRenderer.c 에서 이전에 띄운 이미지를 움직여 보도록하겠습니다.
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	updatePanda();
	drawPanda();
}

void updatePanda()
{
	g_nY = ( g_nY < 0 ) ? 800 - g_nPandaHeight : g_nY-5;
}
updateGameLoop()안에 updatePanda()를 추가시키고 좌표가 변화되는 코드를 입력합니다.

빌드 후 실행을해보면~!
팬더가 지속적으로 올라가는 화면이 출력됩니다! 





원래 점, 선, 면 부터 시작을 해야하지만 급한일이 생기는 관계로 텍스쳐입히는 방법으로 바로 들어가겠습니다!

텍스쳐 재료로 사용될 이미지는 기본적으로 2의 제곱수 이여야합니다. 2의 제곱수가 아니라면 텍스쳐가 정상출력되지 않습니다.
또한 이미지의 크기는 최대 1024 * 1024까지 사용할 수 있습니다. 가로와 세로는 같을필요가 없고 가로와 세로 모두 2의 제곱수이면 됩니다.

2의 제곱수인 이미지를 한 장 준비합시다. 이 팬더이미지는 512*512 입니다.




1. 자바에서 이미지 데이터 보내기
이미지를 drawable폴더에 넣고 GLView.java를 보겠습니다.

package pe.berabue.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.view.MotionEvent;

public class GLView extends GLSurfaceView implements Renderer {

	private Context mContext;
	
	private static native void nativeCreated(); 
    private static native void nativeChanged(int w, int h);
    private static native void nativeUpdateGame();
	private static native void nativeOnTouchEvent(int x, int y, int touchFlag);
	private static native void nativeSetTextureData(int[] pixels, int width, int height);
	
	public GLView(Context context) {
		super(context);
		this.setRenderer(this);
		this.requestFocus();
		this.setRenderMode(RENDERMODE_WHEN_DIRTY);
		this.setFocusableInTouchMode(true);
		
		mContext = context;
	}

	public void onDrawFrame(GL10 gl) {
		nativeUpdateGame();
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		nativeChanged(w, h);
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		nativeCreated();
		
		Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.panda);
		int[] pixels = new int[bmp.getWidth()*bmp.getHeight()];
		bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
		
		nativeSetTextureData(pixels, bmp.getWidth(), bmp.getHeight());
	} 
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		nativeOnTouchEvent((int)event.getX(), (int)event.getY(), event.getAction());
		return true;
	}
}
비트맵을 사용하기위한 Context와 텍스쳐데이터를 보내기위한 nativeSetTextureData();
그리고 onSurfaceCreated() 부분의 코드가 추가되었습니다.
가장 중요한 onSurfaceCreated(); 부분을 보겠습니다.

Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.panda);
 위에서 저장한 panda이미지를 불러옵니다.
int[] pixels = new int[bmp.getWidth()*bmp.getHeight()];
 픽셀데이터를 담을수 있도록 위 이미지의 가로*세로 크기의 int배열을 생성합니다.
bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
 해당 이미지의 픽셀데이터를 읽어와 배열에 저장합니다.
nativeSetTextureData(pixels, bmp.getWidth(), bmp.getHeight());
 해당 픽셀데이터, 가로, 세로 크기를 전달합니다 ~


2. jni-ndk.c 에서 받기

void Java_pe_berabue_opengl_GLView_nativeSetTextureData(JNIEnv* env, jobject thiz, jintArray arr, jint width, jint height)
{
	char *data = (*env)->GetByteArrayElements(env, arr, 0);

	setTextureData(data, width, height);

	(*env)->ReleaseByteArrayElements(env, (jbyteArray)arr, data, JNI_ABORT);
}
char *data = (*env)->GetByteArrayElements(env, arr, 0);
 자바에서 넘어온 배열을 통째로 복사합니다.
setTextureData(data, width, height);
 픽셀데이터와, 이미지의 가로, 세로 크기를 보내버리고 ~
(*env)->ReleaseByteArrayElements(env, (jbyteArray)arr, data, JNI_ABORT);
 자바에서 넘어온 배열을 해제시켜줍니다!

JNI 부분은 아직도 이해가안되서 사용하기가 어렵네요... 대충 기능만 적었습니다..ㅜㅜ


3. GLGameRenderer.c에서 나머지를 해결해봅시다.

먼저 변수 몇개가 필요할것같군요.
GLuint	g_textureName;
int		g_nX;
int		g_nY;
int		g_nPandaWidth;
int		g_nPandaHeight;
g_textureName;
 각각의 텍스쳐마다 이름을 지정해주어 해당텍스쳐를 불러내는 용도입니다.
텍스쳐의의 시작점(LT기준)과 텍스쳐 크기에 사용될 변수가 있습니다. ( 적당히 초기화를 해주시면됩니다. )
void setTextureData(char *data, int width, int height)
{
	g_nPandaWidth = width;
	g_nPandaHeight = height;

	glGenTextures(1, &g_textureName);
	
	glBindTexture(GL_TEXTURE_2D, g_textureName);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)data);
}
텍스쳐를 만들어내는 곳입니다.

glGenTextures(1, &g_textureName);
 void glGenTextured(GLsizei n, GLuint *텍스쳐);
 OpenGL에서는 텍스쳐에 각각의 번호를 매겨사용을합니다.
 생성할 텍스쳐의 수(n)와 텍스쳐의 이름(번호)으로 사용될 배열을 넘겨주면 배열에 값이 들어옵니다.
glBindTexture(GL_TEXTURE_2D, g_textureName);
 사용할 텍스쳐를 선택합니다. 2차원이면서 g_textureName의 이름(번호)를 가진 텍스쳐를 사용할겁니다.
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 축소시에 부드럽게
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 확대시에 픽셀을 유지
 위 두 함수의대한 설명은 http://mrhoya.tistory.com/entry/Filtering-MIPMAP%EC%9D%98-%EC%9D%B4%ED%95%B4 
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,(void*)data);
 
 glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height
       , GLint border, GLenum format, GLenum type, const GLvoid *pixels)
 target : 2차원 텍스쳐를 생성합니다.
 level : 디테일 레벨, 밉맵핑을 사용하지 않으면 보통 0
 internalformat : 이미지 데이터의 내부 포맷
 width : 텍스쳐 가로폭
 height : 텍스쳐 세로폭
 border : 테두리두께로 0 ~ 2 인듯싶습니다(?) 보통 0
 format : 픽셀 데이터의 포맷
 pixels : 픽셀 데이터


텍스쳐를 생성했으니 그려봅니다!


void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	drawPanda();
}

void drawPanda()
{
	GLfloat vertices[12] = {
			g_nX				,	g_nY+g_nPandaHeight	,	0.0f,	// LEFT  | BOTTOM
			g_nX+g_nPandaWidth	,	g_nY+g_nPandaHeight	,	0.0f,	// RIGHT | BOTTOM
			g_nX				,	g_nY				,	0.0f,	// LEFT  | TOP
			g_nX+g_nPandaWidth	,	g_nY				,	0.0f	// RIGHT | TOP
	};
	
	GLfloat texture[8] = {
			0	, 1,
			1	, 1,
			0	, 0,
			1	, 0
	};

	
	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
drawPanda(); 팬더를 그립니다 ~ 
vertices
 텍스쳐가 그려질 좌표(정점)입니다.
texture
 그려질 텍스쳐의 영역입니다. 0 ~ 1까지 값이 들어가며 0 ~ 100%로 보시면됩니다.
 위 배열에서 왼쪽은 w이며, 오른쪽은 h입니다.
 만약, 이미지의 좌상단 1/4만을 출력하고싶다면 1을 0.5로 바꾸어주면 됩니다.
glEnable(GL_TEXTURE_2D);
 2D텍스쳐를 활성화시킵니다.
glBindTexture(GL_TEXTURE_2D, g_textureName);
 바로 위에서 만들어주었던 이름(번호)과 동일한 텍스쳐를 그릴것이구요~
glEnableClientState(GL_VERTEX_ARRAY);
 해당 기능 활성화시 glVertexPointer에 설정한 vertices를 참고하여 렌더링
glVertexPointer(3, GL_FLOAT, 0, vertices);
 
버텍스 데이터 좌표배열을 지정 
 3 : 좌표 당 버텍스 수. 2~4
 GL_FLOAT : 타입
 0 : 배열 내의 버텍스 간격. ( 여기서는 배열하나의 여러개의 좌표를 갖고있지 않으므로 0 )
 vertices : 데이터 포인터 
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 해당 기능 활성화시 glTexCoordPointer 에 설정한 texture 를 참고하여 렌더링 
glTexCoordPointer(2, GL_FLOAT, 0, texture);
 텍스쳐 좌표 배열을 지정
 2 : 배열당 좌표 수 1 ~ 4
 GL_FLOAT : 타입
 0 : 배열 내의 좌표 간격
 texture : 데이터 포인트
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 GL_TRIANGLE_STRIP(연속 삼각형) : 해당 모양으로 그리기
 0 : 배열의 첫번째 인덱스
 4 : 사용할 인덱스 수
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

 비활성화


여기까지! 입니다!!

빌드 후 실행해보면 ~ 짜잔!!
























으헉... 빼먹은게 있습니다.
자바에서 픽셀데이터를 보내왔을때 데이터의 색상순서가 RGBA가 아닌 BGRA순서입니다.
그럼, 순서를 바꾸어줍시다~!
void setTextureData(char *data, int width, int height)
{
	int i;
	char *buf;

	buf = (char *)malloc((sizeof(char)*width*height)<<2);

	for (i = 0; i < width*height*4; i += 4)
	{
		buf[i]   = data[i+2];
		buf[i+1] = data[i+1];
		buf[i+2] = data[i];
		buf[i+3] = data[i+3];
	}

	g_nPandaWidth = width;
	g_nPandaHeight = height;

	glGenTextures(1, &g_textureName);
	
	glBindTexture(GL_TEXTURE_2D, g_textureName);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)buf);

	free(buf);
}
변수 두개와 메모리할당, for문, 그리고 아래쪽에 메모리를 해제시켜주는 부분이 추가되었습니다.
픽셀데이터와 같은 크기의 메모리를 할당받고 BGRA -> RGBA순서로 변경해줍니다.
glTexImage2D() 마지막 값이 data -> buf로 바뀌었내요.

다시 빌드 후 실행해보면 ~

























정상출력되었습니다!

 
위의 vertices와 texture 배열을 이용하면
원하는 위치를 중심으로 출력하거나, 좌표, 그려질 영역등을 마음대로 조절할 수 있습니다.
또한 값의 순서를 바꾸면 이미지가 정상출력이 되지 않습니다.
OpenGL은 모든 도형을 삼각형을 합쳐서 그리기때문에 정점의 순서를 맞추어주지 않으면 텍스쳐가 제대로 나오지 않습니다~
한번해보세요 ~

오늘은 여기까지 ~ 





기능별로 나누기 위해 소스를 약간 수정하였습니다.

jni-ndk.c는 java <-> c의 통신에 사용될 예정이라 OpenGL소스를 따로 빼내겠습니다.
GLGameRenderer.c
GLGameRenderer.h
두 파일을 만들고 jni폴더에 넣습니다.

jni-ndk.c
#include <jni.h>

/**
 * Created by berabue on 12. 2. 23..
 * berabue@gmail.com
 */

void Java_pe_berabue_opengl_GLView_nativeCreated(JNIEnv*  env)
{	
	onSurfaceCreate();
}

void Java_pe_berabue_opengl_GLView_nativeChanged(JNIEnv* env, jobject thiz, jint w, jint h)
{
	onSurfaceChanged(w, h);
}
   
void Java_pe_berabue_opengl_GLView_nativeUpdateGame(JNIEnv* env)
{
	updateGameLoop();
}

void Java_pe_berabue_opengl_GLView_nativeOnTouchEvent(JNIEnv* env, jobject thiz, jint x, jint y, jint touchFlag)
{
	onTouchEvent(x, y, touchFlag);
}
 
기존의 소스에서 OpenGL관련 헤더파일 두개와 색상과 관련되었던 소르를 제거하고
onSurfaceCreate,  onSurfaceChanged, updateGameLoop, onTouchEvent 함수를 적어놓습니다.


GLGameRenderer.c
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "GLGameRenderer.h"

void onSurfaceCreate()
{
	glClearColor(0.4f, 0.4f, 0.4f, 0.4f);
}

void onSurfaceChanged(int width, int height)
{
}
   
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT);
}

void onTouchEvent(int x, int y, int touchFlag)
{
}
 
jni-ndk.c 에서 지웠던 헤더파일을 붙여넣고 함수를 작성해줍니다.
GLGameRenderer.h 파일에 함수원형을 적어주세요. ( 이부분은 앞으로 말하지 않겠습니다. )


onSurfaceChanged() 함수에 소스를 추가시키겠습니다.
void onSurfaceChanged(int width, int height)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	glOrthof(0.0f, 480, 800, 0.0f, 1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glViewport(0, 0, width, height);
}
glMatrixMode(GL_PROJECTION);
 이 명령뒤에 나오는 행렬과 관련된 것은 투영행렬에 영향을 줍니다.
glLoadIdentity();
 행렬을 초기화시킵니다.
glOrthof(0.0f, 480, 800, 0.0f, 1.0f, -1.0f);
 우리는 2D를 만들것이므로 직교투영으로 화면을 지정합니다. 개발자가 원하는 영역을 지정해줍니다.
glMatrixMode(GL_MODELVIEW);
 이 명령뒤에 나오는 행렬과 관련된 것은 모델뷰행렬에 영향을 줍니다.
glLoadIdentity();
 초기화.
glViewport(0, 0, width, height);
 실제 기기에 보여질 화면영역을 지정해줍니다.

여기서 중요한것은 glOrthof()와 glViewport()인데요.
먼저 glOrthof()에 원하는 작업공간크기를 지정해줍니다.
"나는 480*800 화면을 기준으로 할꺼야." 그러면 위와같이 480, 800을 넣어주시면됩니다.
glViewport()에서는 실제 기기의 해상도를 입력해주면 모든 해상도에서 올바른 자리에 출력되는 이미지를 보실 수 있습니다.
1280*720 단말에서 돌린다면 480*800을 늘려서 1280*720해상도에 맞게 그리고 그보다 작다면 축소시켜서 그려줍니다.

이제 해상도 관련 세팅이 완료되었습니다.
 





먼저, 해당 프로젝트는 2D를 기준으로 진행합니다.
또한, 잘못알고있는 부분이나 틀린점이 있을 수 있습니다.
잘못된 부분에대해서는 코멘트해주시면 감사하겠습니다 ^^ 

Android NDK를 이용하여 OpenGL을 사용하기전에 ndk환경을 마련해야합니다.
ndk개발환경 구축방법은 따로 설명드리지 않겠습니다.

그럼, 프로젝트 생성부터 시작하겠습니다.
1. Android Project 생성


쭈욱 입력해줍니다.
프로젝트 버전은 2.1이상으로 작성하였습니다.






























2. GLView.java 생성
package pe.berabue.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;

public class GLView extends GLSurfaceView implements Renderer {

	public GLView(Context context) {
		super(context);
		// GLSurfaceView extends시 생성하여야함
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		// Renderer implements시 생성하여야함
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		// Renderer implements시 생성하여야함		
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		// Renderer implements시 생성하여야함		
	}

}
새로 만들어준 GLView class에 GLSurfaceView를 extends하고 Renderer를 implements해줍니다.
위와같이 하나의 생성자와 3개의 함수를 구현해주어야합니다.

public void onDrawFrame(GL10 gl) {}
public void onSurfaceChanged(GL10 gl, int w, int h) {}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {}
 onSurfaceChanged()와 onSurfaceCreated()는 일반 SurfaceView와 같습니다.
 onDrawFrame() 이함수를 호출하여 실질적으로 그림을 그리게됩니다. 


3. GLView 생성자 부분을 작성합니다.
public GLView(Context context) {
		super(context);
		this.setRenderer(this);
		this.requestFocus();
		this.setRenderMode(RENDERMODE_WHEN_DIRTY);
		this.setFocusableInTouchMode(true);
	}
setRenderer(this);
 렌더러를 세팅합니다. ( 뭐라고 설명해야될지 모르겠습니다. )
setRenderMode(RENDERMODE_WHEN_DIRTY);
 렌더러모드를 ( 1회 그리기 )모드로 세팅합니다. onDrawFrame이 1회 불림  


4. C로 작성된 함수를 호출할 native함수를 작성하고 적절한 위치에서 함수를 호출합니다.
package pe.berabue.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.view.MotionEvent;

public class GLView extends GLSurfaceView implements Renderer {

	private static native void nativeCreated(); 
    private static native void nativeChanged(int w, int h);
    private static native void nativeUpdateGame();
	private static native void nativeOnTouchEvent(int x, int y, int touchFlag);
	
	public GLView(Context context) {
		super(context);
		this.setRenderer(this);
		this.requestFocus();
		this.setRenderMode(RENDERMODE_WHEN_DIRTY);
		this.setFocusableInTouchMode(true);
	}

	public void onDrawFrame(GL10 gl) {
		nativeUpdateGame();
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		nativeChanged(w, h); 
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		nativeCreated();
	} 
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		nativeOnTouchEvent((int)event.getX(), (int)event.getY(), event.getAction());
		return true;
	}
}
GLView에서 해주어야할 일이 끝났습니다!


5. GLView를 메인View에 연결해볼 차례입니다. MainActivity로 가서..
package pe.berabue.opengl;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class MainActivity extends Activity {
	
	private GLSurfaceView mGLView;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mGLView = new GLView(getApplicationContext());
        setContentView(mGLView);
    }

    static {
    	System.loadLibrary("berabueLib");
    }
}
mGLView = new GLView(getApplicationContext());
setContentView(mGLView);

 GLSurfaceView를 만들고 메인View로 지정해줍니다. 
System.loadLibrary("berabueLib"); 
 jni폴더를 컴파일하였을때 나오는 berabueLib.so 라이브러리파일을 사용하겠다고 알려줍니다. 

여기까지가 자바에서 해야할 일입니다!


6. 이제 프로젝트 폴더에 jni 폴더를 생성해줍시다.

7. Android.mk 스크립트 파일을 작성합니다.
Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := berabueLib
LOCAL_CFLAGS := -DANDROID_NDK
LOCAL_SRC_FILES := jni-ndk.c
LOCAL_LDLIBS := -lGLESv1_CM\
				-llog

include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH := $(call my-dir)
 컴파일하고자 하는 소스파일 위치를 알려줍니다. Android.mk를 jni에 놔두었으니 프로젝트/jni폴더가 됩니다.
include $(CLEAR_VARS)
 
  LOCAL로 시작하는것들을 초기화시킨다고합니다. LOCAL_PATH는 제외.
LOCAL_MODULE := berabueLib
 
  생성될 so파일명입니다.
LOCAL_CFLAGS := -DANDROID_NDK
 
  컴파일 옵션설정. 자세히 모름.
LOCAL_SRC_FILES := jni-ndk.c
 
  컴파일할 소스파일을 추가합니다.  
LOCAL_LDLIBS := -lGLESv1_CM\ -llog
 해당 라이브러리를 사용합니다.
include $(BUILD_SHARED_LIBRARY)
 공유 라이브러리를 생성합니다.

  
8. Application.mk 파일을 작성합니다.
APP_PLATFORM	:= android-7
APP_PLATFORM := android-7
 프로젝트 버전을 입력해줍니다.


9. jni-ndk.c 작성
실질적으로 Java에서 작성한 native 함수가 있는곳입니다.
#include <jni.h>
#include <GLES/gl.h>
#include <GLES/glext.h>

void Java_pe_berabue_opengl_GLView_nativeCreated(JNIEnv*  env)
{	
	glClearColor(0.4f, 0.4f, 0.4f, 0.4f);
}

void Java_pe_berabue_opengl_GLView_nativeChanged(JNIEnv* env, jobject thiz, jint w, jint h)
{
}
   
void Java_pe_berabue_opengl_GLView_nativeUpdateGame(JNIEnv* env)
{
	glClear(GL_COLOR_BUFFER_BIT);
}

void Java_pe_berabue_opengl_GLView_nativeOnTouchEvent(JNIEnv* env, jobject thiz, jint x, jint y, jint touchFlag)
{
}
먼저 jni헤더와 OpenGL에 사용할 헤더파일을 include합니다.

jni을 사용하여 함수명을 작성하는방법은 아래와같습니다.
Java_패키지명_native함수원형을적은클래스명_함수명();

c -> java함수를 호출하거나 데이터를 보내는것은 다음에 설명해드리겠습니다.
우선 OpenGL 함수들을 봅시다.

nativeCreated() 함수를 먼저 보겠습니다.
glClearColor(0.4f, 0.4f, 0.4f, 0.4f);
 RGBA 색상으로 화면을 칠합니다. 0.0 ~ 1.0f
glClear(GL_COLOR_BUFFER_BIT);
 위에서 입력한 색상으로 화면을 초기화합니다. 

c소스를 빌드 후 어플을 실행시키면 아래와같이 회색화면의 화면을 가득채울 것입니다. 



이것으로 프로젝트 준비단계가 끝이났습니다.


간단한 프로젝트 설명.


src 폴더
현재 MainActivity와 GLView 두개의 클래스로만 되어있습니다.
추후 Thread와 JNI를 이용해 Java와 C가 통신을할 별도의 클래스를 두개정도를 더 추가할 예정입니다.

jni 폴더
Android.mk : native lib을 생성하기위한 정보를 담고있는 스크립트 파일
Application.mk : native 모듈 생성파일
jni-ndk.c : GLView클래서에서 c소스를 호출할때 사용할 c파일

libs, obj 폴더
jni폴더 빌드시 libs, obj폴더가 만들어지고 so파일이 생성됩니다.















+ Recent posts