스마트폰이 대중화되고 그에 맞춰 CPU단가도 점차 낮아지면서 임베디드 분야에서도(MCU를 주로 다루는 쪽이 아니라면..) 화면과 함께 GUI 을 제공하는 것이 최근 추세인것같다.
또한 프로세서에 GPU가 기본적으로 장착되면서 고급화된 그래픽기술을 이용한 소프트웨어를 개발할 수 있게 되었고 이렇게 GPU와 함께 강력한 그래픽기술을 이용할 수 있게 하는 것이 OpenGL이나 Direct3D 같은 그래픽 API이며 이를 이용해 더 다양한 사용자 경험을 제공할 수 있게된다.
셰이더
이런 그래픽 API들의 고급 기능을 이용하려면 셰이더라고 부르는 GPU내에서 동작되는 프로그램을 필요로 한다.
이는 적합한 Vertex(정점)셰이더와 Fragment(픽셀)셰이더의 구현과 적재 없이는 화면을 나타낼 수 없다는 것을 의미한다.
OpenGL은 스테이지(stage)로 분할된 렌더링 파이프라인(Rendering Pipeline)을 사용한다. 아래 그림은 단순화된 OpenGL 파이프라인인데 vertex shader와 fragment shader가 바로 프로그램 가능한 부분이다.
vertex shader는 vertex data를 받아서 해당 루틴이 끝날 때 내부 변수인 gl_position에 변환된 정점위치를 저장한다. 다음 스테이지에서 기본요소(점,선,삼각형)들이 2차원 프래그먼트 집합으로 변환되고, 이렇게 래스터 변환 단위에 생성된 정점 셰이더 데이터는 프래그먼트 셰이더에 입력되어 프래그먼트 연산을 수행하여 픽셀의 단일 컬러 gl_FragColor를 출력하게 된다.
OpenGL에서는 이 셰이더를 GLSL(OpenGL Shading Language)로, Direct3D에서는 HLSL(High Level Shader Language)로 작성하는데 Qt Qml에서는 백엔드에 따라서 둘 다 지원한다.
Qml로 GUI Application을 개발하는 경우 이렇게 사용자정의 셰이더를 구현하고 적재하기 위해 ShaderEffect와 ShaderEffectSource QML Type을 제공하며 이 타입을 이용하면 고급 그래픽효과들을 Qml내에서 직접 구현 할 수 있다.
아래는 간단한 ShaderEffect 및 GLSL 예제이다.
import QtQuick 2.11 import QtQuick.Window 2.11 Window { visible: true width: 350 height: 200 title: qsTr("Hello https://makersweb.net") Rectangle { anchors.fill: parent Row { anchors.centerIn: parent Image { id: img source: "qrc:/makersweb.png" width: 153 height: 69 } ShaderEffect { width: img.width; height: img.height property variant src: img vertexShader: " uniform highp mat4 qt_Matrix; attribute highp vec4 qt_Vertex; attribute highp vec2 qt_MultiTexCoord0; varying highp vec2 coord; void main() { coord = qt_MultiTexCoord0; gl_Position = qt_Matrix * qt_Vertex; }" fragmentShader: " varying highp vec2 coord; uniform sampler2D src; uniform lowp float qt_Opacity; void main() { lowp vec4 tex = texture2D(src, coord); gl_FragColor = vec4(vec3(dot(tex.rgb, vec3(0.344, 0.5, 0.156))), tex.a) * qt_Opacity; }" } } } }
ShaderEffect를 이용하면 간단하게 그래픽 효과를 줄 수 있다.
셰이더에서 변수의 타입과 한정자
기본변수 타입들 (더 많은 타입 정보는 다음 링크에 있다.)
Type |
Meaning |
void |
for functions that do not return a value |
bool |
a conditional type, taking on values of true or false |
int |
a signed integer |
uint |
an unsigned integer |
float |
a single-precision floating-point scalar |
double |
a double-precision floating-point scalar |
vec2 |
a two-component single-precision floating-point vector |
vec3 |
a three-component single-precision floating-point vector |
vec4 |
a four-component single-precision floating-point vector |
mat2 |
a 2 × 2 single-precision floating-point matrix |
mat3 |
a 3 × 3 single-precision floating-point matrix |
mat4 |
a 4 × 4 single-precision floating-point matrix |
sampler2D |
a handle for accessing a 2D texture |
sampler3D |
a handle for accessing a 3D texture |
lowp, mediump, highp
정밀도 한정자는 실수나 정수에 기반한 모든 변수의 정밀도를 지정하는 데 사용될 수 있다. 변수가 정밀도 한정자 없이 선언되면 그 변수는 기본 정밀도를 가진다.
Uniform
uniform 변수는 "외부"에서 정점 또는 프래그먼트 셰이더와 통신하는 데 사용되며 셰이더에서는 uniform 한정자를 사용하여 변수를 선언한다.
uniform 변수는 읽기 전용이며 모든 정점에서 동일한 값을 가진다.
Attribute
버텍스 Attributes은 "외부"에서 버텍스 셰이더로 통신하는 데 사용되며 uniform변수와는 달리 정점마다 값이 제공된다.(그리고 모든 정점에 대해 전역 적으로 제공되지않음) 법선이나 위치와 같은 내장 된 정점 attribute이 있거나 사용자 정의의 정점 attribute을 지정할 수 있다.
마지막으로 fragment shader에서는 attribute를 정의할 수 없다.
Varying
Varying 변수는 Vertex와 Fragment Shader 사이의 공유할 수 있는 변수를 제공한다.
버텍스 셰이더에서 Varying 변수를 정의하면 렌더링되는 primitive에 대해 해당 값이 보간되고 프래그먼트 셰이더에서는 보간된 값에 액세스할 수 있다.
미리 정의된 변수
위의 Vertex와 Fragment Shader 예제에서 qt_ 접두어가 붙은 변수들을 볼수있는데 이것들은 Qt에서 기본으로 제공하는 변수들이며 아래와 같이 몇가지가 미리정의 되어있다.
qt_Matrix - 조합 된 변환 행렬(모델-뷰-투영 행렬), 이 행렬은 3D그래픽에서 3개의 매우 중요한 변환 행렬의 결과이다.
qt_Opacity - 결합 된 불투명도
qt_Vertex - 정점 위치
qt_MultiTexCoord0 - 텍스쳐 좌표. 좌측 상단은 (0, 0), 우측 하단은 (1, 1)
qt_TexCoord0 - 셰이더간 공유되는 텍스쳐 좌표. 기본 버텍스 셰이더에서 텍스처 좌표가 highp vec2 qt_TexCoord0으로 변하고, source라는 이름의 sampler2D에서 샘플링 할 것으로 예상한다.