2012.02.27 00:00 Android/OpenGL



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

텍스쳐 재료로 사용될 이미지는 기본적으로 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은 모든 도형을 삼각형을 합쳐서 그리기때문에 정점의 순서를 맞추어주지 않으면 텍스쳐가 제대로 나오지 않습니다~
한번해보세요 ~

오늘은 여기까지 ~ 



저작자 표시 비영리
신고
posted by 베라뷰

티스토리 툴바