본문 바로가기

프로그래밍/Android

[Android] EGL : 3 - 쉐이더

grafika 의 Texture2DProgram 을 통해 간단한 쉐이더를 파악해 본다.

 

쉐이더코드들은 코드 전체가 문자열로 저장한 뒤 컴파일해서 사용하게 된다.
Fragment Shader의 경우 TextureTarget 별로 쉐이더 항목이 달라지므로, 필요한 처리를 위한 쉐이더 코드를 작성해 사용한다.
VS Code 등에는 쉐이더 관련 익스텐션이 있으므로,  쉐이더 작성후에 안드로이드로 옮기는것도 방법.

참고사항 : 쉐이더의 각 변수는 변수앞에 형식을 정의하는데, 아래와 같다.

uniform : 읽기전용으로 어플리케이션에서 전달하는값으로 vertex, fragment shader 공유
attribute : vertex shader 에 데이터를 전달할때 사용
varying : vertex shader 출력, fragment shader 입력

uniform, attribute 형식의 변수는 쉐이더 외부에서 해당 변수의 핸들을 얻어 직접 데이터를 지정할 수 있도록 되어있다.

 

Vertex Shader

uniform mat4 mvpMatrix;
uniform mat4 texMatrix;

attribute vec4 inPosition;
atrribute vec4 inTexCoord;

varying vec2 textureCoord;

void main() {
    gl_Position = mvpMatrix * inPosition;
    textureCoord = ( texMatrix * inTexCoord ).xy;
}

 

YUV Fragment Shader : I420 format texture

픽셀포맷이 YUV 인 경우 변환 루틴을 포함해야 한다.

precision mediump float;
varying vec2 texCoord;

uniform sampler2D yTex;
uniform sampler2D uTex;
uniform sampler2D vTex;

void main() {
    float y = texutre2D( yTex, texCoord).r;
    float u = texture2D( uTex, texCoord).r - 0.5;
    float v = texture2D( vTex, texCoord).r - 0.5;
    
    gl_FragColor = vec4( y + 1.403 * v,
                        y - 0.344 * u - 0.714* v,
                        y + 1.77 * u, 1 );
}

 

RGB Fragment Shader : normal 2d texture

TextureTarget : GLES20.GL_TEXTURE_2D

precision mediump float;
varying vec2 texCoord;
uniform sampler2D rgbTex;

void main() {
    gl_FragColor = texture2D( rgbTex, texCoord);
}

 

 

OES Framgment Shader : external 2d texture

TextureTarget : GLES11Ext.GL_TEXTURE_EXTERNAL_OES

안드로이드의 SurfaceTexture를 사용한 Surface에 렌더링 하는 경우 OpenGL ES 의 External Texture를 사용하게 된다.
별도의 익스텐션이 필요하며, 코드 상단에 #extension GL_OES_EGL_image_extenal : require 을 추가한다.

텍스처의 타입은 sampler2D가 아닌 samplerExternalOES 이고, 텍스처 생성시 GL_TEXTURE_EXTERNAL_OES 로 바인딩되어야 한다.

#extension GL_OES_EGL_image_external : require
precision mediump float;

uniform samplerExternalOES oesTex;
varying vec2 texCoord;

void main() {
    gl_FragColor = texture2D( oesTex, texCoord );
}

 

 


쉐이더 프로그램

 

쉐이더에 값 할당

각 요소의 인덱스를 얻어 특정 함수를 사용해 값을 설정하게 된다.

// 버텍스 쉐이더의 각 요소별 인덱스
int positionHandler = GLES20.glGetAttribLocation( program, "inPosition" );
int uvHandler = GLES20.glGetAttribLocation( program, "inTexCoord" );
int mvpMatrixHandler = GLES20.glGetUniformLocation( program, "mvpMatrix" );
int texMatrixHandler = GLES20.glGetUniformLocation( program, "texMatrix" );


// 프레그먼트 쉐이더의 각 요소별 인덱스
int textureHandler = GLES20.glGetUniformLocation( program, "oesTex" );


 

쉐이더에 값을 할당하기 위한 메쏘드들
타입과 인자수에 따라 glUniform1f ~ glUniform4f 등과 같이 나뉘어짐
uniform : glUniform{N}{type}() , glUniformMatrix{NxN}fv()
attribute : glVertextAttrib{N}{type}(), glVertexAttribPointer()

attribute의 경우 배열 형태로 전달하기 위해 glEnableVertexAttribArray() 로 활성화 해 주어야한다.

// uniform
GLES20.glUniform4f( GLint location,
                    GLfloat v0,
                    GLfloat v1,
                    GLfloat v2,
                    GLfloat v3);
                        
GLES20.glUniform4i( GLint location,
                    GLint v0,
                    GLint v1,
                    GLint v2,
                    GLint v3);
                    
GLES20.glUniform4fv( GLint location,
                     GLsizei count,
                     const GLfloat *value);


// matrix (4x4)
GLES20.glUniformMatrix4fv( GLuint location, 
                           GLsizei count, 
                           GLboolean transpose,
                           const GLfloat *value);


// 단일 버텍스
GLES20.glVertexAttrib4fv( GLuint location, const GLfloat *values);


// 픽셀, uv 포지션
GLES20.glEnableVertexAttribArray(GLuint location);
GLES20.glVertexAttribPointer(
                    GLuint location, 
                    GLint size,
                    GLenum type,
                    GLboolean normalized,
                    GLsizei stride,
                    const GLvoid* pointer );

// GLenum type
// GL_BYTE, GL_UNSIGED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT
// GL_HALF_FLOAT, FL_FLOAT, FL_FIXED, FL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV




// 프레그먼트 쉐이더의 텍스처의 경우 unit 값을 지정하고, 바인딩으로 데이터 연결
GLES20.glUniform1i( GLuint location, GLuint unit );
GLES20.glActiveTexture(GL_TEXTURE1)
GLES20.glBindTexture( GL_TEXTURE_2D, 텍스처 번호 );

 


쉐이더 컴파일

// 쉐이더 컴파일
// shaderType = GLES20.GL_VERTEX_SHADER, GLES20.GL_FRAGMENT_SHADER
// source = 쉐이더 코드(String)
int[] result = new int[] { GLES20.GL_FALSE };

int shader = GLES20.glCreateShader( shaderType );
if( shader != 0 ) {
    GLES20.glShaderSource( shaderType, source );
    GLES20.glCompileShader( shader );
    GLES20.glGetShaderiv( shader, GLES20.GL_COMPILE_STATUS, result, 0);
    if( result[0] != GLES20.GL_TRUE ) {
        Log.e(ELSE20.glGetShaderInfoLog(shader) );
        GLES20.glDeleteShader( shader );
        throw new RuntimeException("");
    }
}

 

 

프로그램 생성

각 쉐이더 컴파일, 프로그램생성, 프로그램에 쉐이더 붙이기, 프로그램을 opengles에 링크 의 순서로 이루어진다.

하단과 같이 쉐이더와 연결할 변수들의 핸들 정보를 저장해 둔다. 


// 컴파일 : 쉐이더 컴파일 항목 참조
int vertexShader = compileShader( GLES20.GL_VERTEX_SHADER, vertexSource );
int fragmentShader = compileShader( GLES20.GL_FRAGMENT_SHADER, fragmentSource );

// programe
int program = GLES20.glCreateProgram();
if( program == 0 ) {
    throw new RuntimeException("");
}

// 쉐이더 적용
GLES20.glAttachShader( program, vertexShader );
GLES20.glAttachShader( program, fragmentShader );
GLES20.glLinkProgram( program );

int[] linkStatus = new int[] { GLES20.GL_FALSE };
GLES20.glGetProgramiv( program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if( linkStatus != GLES20.GL_TRUE ) {
    Log.e("", GLES20.glGetProgramInfoLog( program ));
    GLES20.glDeleteProgram( program );
    throw new RuntimeException("");
}


// draw 에 사용할 각 항목에 대한 location
int textureID;
int mvpMatrixHandler;
int texMatrixHandler;
int positionHandler;
int uvHandler;
        .
        .




 

프로그램 활성화

GLES20.glUseProgram( program );

 

프로그램 삭제

GLES20.glDeleteShader( fragmentShader );
GLES20.glDeleteShader( vertexShader );
GLES20.glDeleteProgram( program );