본문 바로가기

프로그래밍/Android

[Android] EGL : 4 - 안드로이드 렌더링

EGL Surface , SurfaceTexture

이전 포스트에서 언급한바와같이 안드로이드 카메라의 프레임 데이터를 수신하기 위한 안드로이드 Surface는 opengl es의 pbuffer 텍스처를 통해 생성하게 된다. pbuffer 텍스처 -> SurfaceTexture -> Surface 를 생성해 카메라 api에 전달하게 되면, 카메라는 해당 Surface에 필요한 렌더링을 하게된다. (카메라 api에 따라 SurfaceTexture를 요구할 수도 있음)

이 텍스처는 external 타입의 텍스처이고, 텍스처로의 렌더링(PBO)은 카메라에서 수행하므로 텍스처 생성과 바인딩, 서피스 생성 외에는 특별히 처리할 일은 없다. 렌더링된 이 텍스처를 이제 원하는 화면에 렌더링해야 해야 하는데, 이때 사용할 수 있는 케이스들을 살펴본다. 
SurfaceTexture 객체는 렌더링된 데이터가 있는 경우 onFrameAvailable() 콜백을 호출해 주며, 이 콜백에서 필요한 처리를 진행하면 된다.

특정 안드로이드뷰에 렌더링하기 위해서는 안드로이드 Surface 가 필요하며, surface 기반으로 구성된 뷰는 대표적으로 아래와 같다.

TextureView : TextureView.SurfaceTextureListener 를 통해 전달된 SurfaceTexture를 사용해 EGLSurface 를 생성
SurfaceView : SurfaceHolder.Callback 을 통해 전달된 Surface 를 사용해 EGLSurface 를 생성


프레임 버퍼

생성

int fbHandle = int[1];
GLES20.glGenFramebuffers( 1, fbHandle, 0);
GLES20.glBindFramebuffer( GLES20.GL_FRAMEBUFFER, fbHandle[0]);

// 깊이 버퍼가 필요한 경우
GLES20.glGenRenderbuffers(1, depthHandle, 0);
GLES20.glRenderbufferStorate( GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT, width, height );
GLES20.glFramebufferRenderbuffer( GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depthHandle[0]);

 

바인딩

GLES20.glBindFramebuffer( GLES20.GL_FRAMEBUFFER, fbHandle[0]);
GLES20.glViewport(0, 0, 너비, 높이);

 

텍스처바인딩
render to texture 를 위해 FBO에 텍스처를 추가

// 특정 index의 텍스처 바인딩
GLES20.glFramebufferTexture2D( GLES20.GL_FRAMEBUFFER, 
                               GLES20.GL_COLOR_ATTACHMENT0, 
                               GLES20.GL_TEXUTRE_2D,
                               textures[index],
                               0);
                               

 

삭제


// 깊이버퍼 해제
GLES20.glBindRenderbuffer( GLES20.GL_RENDERBUFFER, 0);

// 텍스처 해제
GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, 0);

// 프레임버퍼 제거
GLES20.glDeleteFramebuffers(1, fbHandle, 0);

 


픽셀 및 Texture UV 좌표

static final float RECTANGLE_COORDS[] = { 
   -0.5f, -0.5f,
    0.5f, -0.5f,
   -0.5f,  0.5f,
    0.5f,  0.5f
};

static final float RECTANGLE_TEX_COORDS[] = { 
    0.0f, 1.0f,
    1.0f, 1.0f,
    0.0f, 0.0f,
    1.0f, 0.0f
};

static final float FULL_RECTANGLE_COORDS[] = { 
   -1.0f, -1.0f,
    1.0f, -1.0f,
   -1.0f,  1.0f,
    1.0f,  1.0f
};

static final float FULL_RECTANGLE_TEX_COORDS[] = { 
    0.0f, 0.0f,
    1.0f, 0.0f,
    0.0f, 1.0f,
    1.0f, 1.0f
};


FloatBuffer createFloatBuffer( float[] coord ) {
    ByteBuffer bb = ByteBuffer.allocateDirect( coords.legth * 4 );
    bb.order( ByteOrder.nativeOrder());
    FloatBuffer fb = bb.asFloatBuffer();
    fb.put(coords);
    fb.position(0);
    return fb;
}

 

렌더링

// draw 시에 필요한 사용한 데이터
float[] mvpMatrix = new float[16];
Matrix.setIdentityM(IDENTITY_MATRIX, 0);

FloatBuffer vertexArray = createFloatBuffer( FULL_RECTANGLE_COORDS );
FloatBuffer texCoordArray = createFloatBuffer( FULL_RECTANGLE_TEX_COORDS);

int coordsPerVertex = 2;
int vertexStride = coordsPerVertex * 4;
int vertexCount = FULL_RECTANGLE_COORDS.length / coordsPerVertex;
int texStride = 2 * 4; // 2 * sizeof(float)



// Clear
GLES20.glClearColor(0f, 0f, 0f, 1f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);


// 입력 소스가 카메라인 경우 카메라에 연결된 surface texture update
someCameraSurfaceTexture.updateTexImage();
someCameraSurfaceTexture.getTransformMatrix(mvpMatrix);


// 타겟 바인딩
// 프레임 버퍼를 사용하는 경우 프레임버퍼 바인딩
FBO.bind()
FBO.bindTexture()




// 프로그램 선택
GLES20.glUseProgram( mProgram );


// 값 설정 : 핸들러는 program 생성시의 각 변수 위치 참고.
GLES20.glUniformMatrix4fv( mvpMatrixHandler, 1, false, mvpMatrix, 0 );
GLES20.glUniformMatrix4fv( texMatrixHandler, 1, false, texMatrix, 0 );


// texture
GLES20.glUniform1i( texture, 0 );
GLES20.glActiveTexture( GLES20.GL_TEXTURE0 );
GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, textureID );


// draw

 

DRAW

// 버텍스
GLES20.glEnableVertexAttribArray( positionHandler );
GLES20.glVertexAttribPointer(
              positionHandler, 
              coordPerVertex,
              GLES20.GL_FLOAT,
              false,
              vertexStride,
              vertexArray );

// uv
GLES20.glEnableVertexAttribArray( uvHandler );
GLES20.glVertexAttribPointer( 
              uvHandler,
              2,
              GLES20.GL_FLOAT,
              false,
              texStride,
              texCoordArray );

// draw
GLES20.glDrawArrays( GLES20.GL_TRIANGLE_STRIP, 0, vertexCount );

 

해제

GLES20.glDisableVertexAttribArray( positionHandler );
GLES20.glDisableVertexAttribArray( texureHandler );
GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, 0 );
GLES20.glUseProgram(0);

 


카메라 프레임을 다른 Surface에 렌더링

카메라 API 를 사용하면, 정의된 surface 에 데이터가 전달된다. 이처럼 SurfaceTexture 로 입력된 데이터를 다른 SurfaceView, Surface로 출력하는 예를 한번 살펴보자.
렌더링을 위한 쉐이더는 EGL 2 포스트 참조. 

(EGL 요소들은 별도 클래스 변수들인데, 이해해기 쉽도록 로컬 변수로 표시)

 

렌더 서피스 생성

서피스뷰의 서피스 관련 콜백은 onCreate, onChange 등 서피스 생성과 변화에 대한 이벤트 콜백을 지원하고 있다. 서피스가 생성된 경우 해당 서피스를 기반으로 egl 서피스를 생성한다.

이후 쉐이더 생성, opengl 텍스처를 생성, 해당 텍스처를 통해 SurfaceTexture를 생성하게 된다.

SurfaceTexture는 안드로이드에서 버퍼와 큐를 제공해 외부 텍스처를 사용할 수 있도록 한 객체이다.

public void surfaceCreated( SurfaceHolder holder ) {
     EGLDisplay eglDisplay = createDisplay();
     EGLConfig eglConfig = createConfig( ConfigType.RECORDABLE, 3 );
     EGLContext eglContext = createContext( eglConfig );
     
     Surface surface = holder.getSurface();
     EGLSurface eglSurface = createSurface( surface );
     makeCurrent( eglSurface );
     
     // 쉐이더 생성
     CustomShader shader = new CustomShader();
     shader.createProgram( myVertexShader, myFragmentShader );
     
     // 데이터 입력을 위한 텍스처 생성
     int texId = shader.createTexture();
     SurfaceTexture inputTexture = new SurfaceTexture( texId );
     inputTexture.setOnFrameAvailableListener(this);
}

위에서 생성된 SurfaceTexture를 카메라 API 의 preview 텍스처로 설정하면 카메라 데이터가 큐로 전달되고, onFrameAvailableListener가 호출된다.

onFrameAvailableListener 에서 updateFrame을 사용해 두개의 Surface에 렌더링을 수행한다.

void updateFrame() {
    float[] matrix = new float[16];
    makeCurrent( previewSurface );
    
    // 카메라 프레임 데이터가 저장된 surface texture
    inputTexture.updateTexImage();
    inputTexture.getTransformMatrix( matrix );
    
    // layout surface view
    int width = mBindUtil.previewSurface.getWidth();
    int height = mBindUtil.previewSurface.getHeight();

    // 뷰 서피스에 그리기
    GLES20.glViewport( 0, 0, width, height );
    shader.draw( textureID, matrix );
    EGL14.eglSwapBuffers( eglDisplay, previewSurface );
    
    // 다른 서피스에 복사
    makeCurrent( otherEglSurface );
    GLES20.glViewport( 0, 0, videoWidth, videoHeight );
    shader.draw( textureID, matrix );
    EGLExt.eglPresentationTimeANDROID( eglDisplay, otherEglSurface, surfaceTexture.getTimestamp());
    EGL14.eglSwapBuffers( eglDisplay, otherEglSurface );
    
    
}

 

 

 

'프로그래밍 > Android' 카테고리의 다른 글

[Android] EGL : 3 - 쉐이더  (0) 2020.03.02
[Android] EGL : 2 - 텍스처  (0) 2020.03.02
[Android] EGL : 1 - 기본 구성  (0) 2020.03.02
안드로이드 ndk 테스트 코드  (0) 2020.02.20
[Android] gson , 복합적인 요소의 파싱  (0) 2019.09.23
[Android] gradle android 빌드 구성  (0) 2019.09.17
[Android] ConstraintSet  (0) 2019.08.22
Gradle Kotlin, AndroidX 설정  (0) 2019.08.17
[Android] androidX Camera  (0) 2019.07.15
[Android] Retrofit  (0) 2019.04.11