ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • OpenGL 끄적임 - 4. Adding color and Shader
    Graphics/OpenGL ES 2.0 2018. 6. 17. 20:04
    반응형

    챕터4. 색상과 명암 추가 


    실제 생활에서 물체는 다양한 색상과 명암을 가지고 있다. 집에서 벽만 보더라도 햇빛이 어디를 비추냐에따라 명암이 달라진다. 우리의 두뇌는 이 명암과 색상 차이를 우리가 뭘 보고 있는지 이해하는데 이용한다. 이는 예술가들이 이용했던 기법이다. 

    우리는 이전 챕터에서 hockey table를 그리는데 점, 선 만 이용을 했는데 색과 명암도 스크린에 칠해보자. 이는 다른 예제에도 재활용할 수 있는 아주 쉬운 프레임워크이다. 


    물체에 단일컬러를 설정하기 보다는 여러가지 색상을 조합하는 법도 알아본다.


    그라데이션 넣기 (smooth shading)

    삼각형에 그라데이션을 넣으려면 어떻게 그려야할까? 하나의 삼각형 안에 수 백만개 삼각형으로 색을 표현한다고 한다면 오버헤드가 심할 것이다. 이 방법보다는 다른 색상 삼각형들을 겹쳐 그리는 방법이 있다. 삼각형들의 각 점에 다른 색을 혼합해서 그릴 수 있다. 


    점들 사이에 그라데이션 넣기

    우리 hockey 테이블을 가운데는 밝고 모서리는 어둡에 바꿔보자. 

    hockey 테이블 구조를 수정해서 중간점을 만든다. 


    // Triangle Fan

             0,     0,

          -0.5f, -0.5f,

           0.5f, -0.5f,

           0.5f,  0.5f,

          -0.5f,  0.5f,

          -0.5f, -0.5f,


    https://media.pragprog.com/titles/kbogla/code/AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java


    이제 효율적인 그라데이션 추가를 위해 삼각형팬(triangle fan)을 그릴건데 onDrawFrame() 부분에 아래를 추가하면 된다. 그러면 GL은 가운데 1번의 vertex부터 시작해서 마지막 6번의 vertex까지 이어서 그린다. 




    glDrawArrays(GL_TRIANGLE_FAN, 0, 6);

    https://media.pragprog.com/titles/kbogla/code/AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java


    이제 각 vertex에 새로운 두번째 attribute로 color를 추가해보자. 

    val tableVerticesWithTriangles = floatArrayOf(
                    // Order of coordinates: X, Y, R, G, B
                    // Triangle Fan
                    0f, 0f, 1f, 1f, 1f,
                    -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
                    0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
                    0.5f, 0.5f, 0.7f, 0.7f, 0.7f,
                    -0.5f, 0.5f, 0.7f, 0.7f, 0.7f,
                    -0.5f, -0.5f, 0.7f, 0.7f, 0.7f,
                    // Line 1
                    -0.5f, 0f, 1f, 0f, 0f, 
                    0.5f, 0f, 1f, 0f, 0f,
                    // Mallets
                    0f, -0.25f, 0f, 0f, 1f
                    , 0f, 0.25f, 1f, 0f, 0f)
    



    R,G,B 색상의 추가 점을 추가했다. 이는 특정 vertex를 위한 색상으로 표현된다. 각 R,G,B 는 0~1 사이 값으로 표현해야한다. 이 값은 Java 코드에서 계산해볼 수 있다. 


    float red = Color.red(parsedColor) / 255f; 

    float green = Color.green(parsedColor) / 255f; 

    float blue = Color.blue(parsedColor) / 255f;


    Java 코드에서는 0~255 값으로 표현되므로 OpenGL이 이해하는 값으로 구하기 위해서는 255로 나누면 된다. 


    이제 Shader를 변경해보자.

    color uniform을 제거하고 attribute로 추가한다. 

    attribute vec4 a_Position;  
    attribute vec4 a_Color;
    varying vec4 v_Color;
    
    void main()                    
    {                            
        v_Color = a_Color;
        gl_Position = a_Position;    
        gl_PointSize = 10.0;          
    }          
    



    attribute는 OpenGL에 전달할 변수 선언 타입이다. varying은 우리가 vertex 정의한 것으로부터 값을 읽어오는 타입이다. 


    OpenGL 파이프라인을 기반으로 varying을 이해하자. 우리는 선을 그리려면 두 점으로 그리는데 그 뒤에 fragment로 변환한다. 

    삼각형도 마찬가지인데 세 개의 점으로 물체를 생성하면, fragment가 생성될때마다 fragment shader는 실행된다. 


    varying 은 여러 색상을 받을 변수 타입으로 attribute인 a_Color가 받은 컬러가 vertex0이 Red, vertex1이 Green이라면 varying 타입인 v_Color에 각각 assign된다. 이 의미는 우리는 OpenGL에 각 fragment가 blended 색상을 받을 것을 말하고 있는 것이다. fragment가 vertex 0에서 1에 가까워질 때 붉은색이 되다가 녹색이 되면서 blended 색상이된다.

    precision mediump float; 				
    varying vec4 v_Color;      	  								
    void main()                    		
    {                              	
        gl_FragColor = v_Color;                                  		
    } 
    


    http://media.pragprog.com/titles/kbogla/code/AirHockey2/res/raw/simple_fragment_shader.glsl


    uniform 변수 타입을 varying으로 바꿨다. 선을 그리기 위해 OpenGL은 두 점을 사용할텐데, 이때 blended color를 계산한다. 또한 삼각형을 그리기 위해 OpenGL은 세 점을 사용하고, 이때도 blended color를 계산한다. 


    그릴 fragment가 선을 포함하고 있다면, OpenGL이 두 점을 색상으로 변환한다. 

    그릴 fragment가 삼각형을 포함하고 있다면, OpenGL이 세 점을 색상으로 변환한다.


    shader의 attribute인 a_Color에 값을 전달하기 위해 java 코드를 좀 수정해야한다. 


    이제 각 fragment에 섞인 색상을 정의해보자. fragment는 선에서도 나올 수 있고, 삼각형에서 나올 수도 있다. 

    선형으로 색깔을 섞는 법에 대해 알아보자. 

    선에 대해서 빨간색과 녹색이 섞인 선이라고 생각해보자. 선의 왼쪽의 fragment는 100% 빨간색이고 오른쪽의 fragment로 이동할 수록 100% 녹색이다. 중간은 빨간색과 녹색이 50%씩 옅어져서 섞여있다. 

    이 것을 간단히 말해서 선형 보간(linear interpolation) 이라고 한다. 각 점에 매핑된 색상이 거리에 따라 색상의 강도가 달라진다. 각 왼쪽/오른쪽 점의 색상은 선의 거리에 비례하므로 아래와 같은 수식을 얻을 수 있다. 

    정점 0의 값과 정점 1의 값을 안다면, 현재 fragment의 거리 비율을 계산할 수 있다. 거리의 비율은 간단히 0~100%라고 한다. 0%는 왼쪽 정점이고 100%는 오른쪽 정점이다. 0%에서 100%, 왼쪽에서 오른쪽으로 거리가 늘어날 때 거리 비율이 증가한다. 

    이 linear interpolation을 이용해 그라데이션을 구해보자. 

    blended_color_value = (vertex_0_color_value * (100% – distance_ratio)) + (vertex_1_color_value * distance_ratio)

    R,G,B,A 각각 컴포넌트별로 위의 수식으로 개별로 다 구해서 blended 값을 얻어야한다. 예를 들면 vertex_0_color_value는 Red = (1.0, 0.0, 0.0), vertex_1_color_value는 Green = (0.0, 1.0, 0.0) 값이라고 하자. 위 그림에서처럼 distance_ratio가 50% 라고 할 때, 가운데 blended되는 컬러 값은 (1.0, 0.0, 0.0) * (1-0.5) + (0.0, 1.0, 0.0) * 0.5 = (0.5, 0.5, 0.0) 이 된다. 


    이제 삼각형에 대한 linear interpolation을 구해보자. 

    이를 삼각형에 적용해보자. 삼각형 꼭지점에 3가지 색상이 있다고 하자. 사진을 보면 세 가지 색상이 섞였고 점쪽으로 갈수록 색이 진해지는 걸 볼 수 있다. 삼각형 꼭지점을 잇는 선을 그어보면 삼각형 내부에 또 다시 3개의 삼각형이 생기는 것을 볼 수 있다. 이는 weight를 사용한다. 


    삼각형 내부 비율에 따라 각 색상의 무게가 결정된다. 즉, 노란색은 노란색에 맵핑된 꼭지점에 가까울수록 더 진해진다. 


    blended_value = (vertex_0_value * vertex_0_weight) + (vertex_1_value * vertex_1_weight) + (vertex_2_value * (100% – vertex_0_weight – vertex_1_weight))


    이 공식으로 선의 blended 색상과 동일하게 구할수 있다. 2개의 점이 3개가 된 셈이다. 


    이제 코드상으로 나타내보자.

    우리는 vertex shader와 fragment shader를 우리의 color attribute를 읽도록 수정해야한다.

    private static final int POSITION_COMPONENT_COUNT = 2;
    private static final int COLOR_COMPONENT_COUNT = 3;    
    private static final int BYTES_PER_FLOAT = 4;
    private static final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT; 
    

    STRIDE 변수는 색상의 크기값으로 OpenGL이 다음 꼭지점을 읽을때 색상을 얼만큼 건너뛰어야되는지 알려준다. 


    Updating onSurfaceCreated() 에 다음 코드를 추가한다.

    aColorLocation = glGetAttribLocation(program, A_COLOR);

    

    STRIDE를 추가하기 위해 아래 코드도 넣는다.

    // position 값 전달
    glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, vertexData); 
    
    // color 값 전달
    vertexData.position(POSITION_COMPONENT_COUNT);
    glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GL_FLOAT,
    false, STRIDE, vertexData)
    // shader의 a_Color와 연관이 있다고 OpenGL에 알려준다.
    glEnableVertexAttribArray(aColorLocation);


    1.우리는 color attribute부터 시작할땐, 첫번째 포지션이 아닌 POSITION_COMPONENT_COUNT의 2부터 시작해야한다. 그래서 vertexData의 위치는 2부터 시작해야한다고 선언한 것이다. 

    2.glEnableVertexAttribArray를 호출하여 a_Color와 셰이더 색상데이터를 연결한다. 

    position 값, color 값을 전달하기 위해 한 개의 array를 사용했다. 그래서 Stride가 필요했지만, position, color 값을 각각의 array로 분리해 사용하면 Stride를 굳이 사용할 필요는 없다.





    마지막으로 uniform 한정자 변수는 없기 때문에 onDrawFrame()에서 glUniform4f() 호출을 지운다. 



    최종 결과는 다음과 같다.




    반응형
Designed by Tistory.