원래 점, 선, 면 부터 시작을 해야하지만 급한일이 생기는 관계로 텍스쳐입히는 방법으로 바로 들어가겠습니다!
텍스쳐 재료로 사용될 이미지는 기본적으로 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은 모든 도형을 삼각형을 합쳐서 그리기때문에 정점의 순서를 맞추어주지 않으면 텍스쳐가 제대로 나오지 않습니다~
한번해보세요 ~
오늘은 여기까지 ~