++이 자료는 OSS 스터디팀에서 공동작업한 문서이며, 추후 깃헙 레포 주소를 추가할 예정입니다.
5장. OpenGL ES Shading Language
이전 장에서 살펴본 것처럼 셰이더는 OpenGL ES 3.0 API의 핵심에 있는 기본 개념입니다. 모든 OpenGL ES 3.0 프로그램에는 의미 있는 그림을 렌더링하기 위해 버텍스 셰이더와 프래그먼트 셰이더가 모두 필요합니다. 셰이더 개념이 API에서 차지하는 중요성을 고려할 때 그래픽스 API에 대한 자세한 내용을 살펴보기 전에 셰이더 작성의 기본 사항을 숙지하고 있는지 확인하고자 합니다.
이 장의 목표는 다음 개념을 이해하는 것입니다:
- 변수 및 변수 유형
- 벡터 및 행렬 구성 및 선택
- 상수
- 구조체와 배열
- 연산자, 제어 흐름 및 함수
- 입력/출력 변수, 유니폼, 유니폼 블록, 레이아웃
- 전처리기 및 지시어
- 유니폼 및 보간기 패킹
- 정밀 한정자 및 불변성
OpenGL ES Shading 언어의 기본
셰이더의 구문은 c언어와 유사합니다. c코드를 이해할 수 있다면 셰이더의 코드를 이해하는데 큰 어려움이 없을 것입니다. 그러나 버전 사양과 지원되는 기본 데이터 유형부터 시작하여 두 언어 간에는 몇 가지 주요 차이점이 있습니다.
셰이더 버전 사양
OpenGL ES 3.0 버텍스 및 프래그먼트 셰이더의 첫 번째 줄에는 항상 셰이더 버전이 선언됩니다. 셰이더 컴파일러는 셰이더에 선언된 버전에 따라서 셰이더를 컴파일 합니다. (선언하지 않으면 1.00 버전 사용 Opengles 2.0) 셰이딩 언어 3.0에서는 새로운 기능이 많이 추가 되었습니다.
추가된 기능
non-square matrices, full integer support, interpolation qualifiers, uniform blocks, layout qualifiers, new built-in functions, full looping, full branching support, and unlimited shader instruction length
변수 및 변수 유형
컴퓨터 그래픽에서는 백터와 행렬이 (컴퓨터 그래픽에서의?)변환의 기초를 형성합니다. 이 두 데이터 타입은 셰이딩 언어의 핵심이기도 합니다.
변수 클래스 | 타입 | 설명 |
---|---|---|
Scalars (스칼라) | float, int, uint, bool | 부동 소수점, 정수, 부호 없는 정수 및 부울 값에 대한 스칼라 기반 데이터 유형 |
Floating-point-vectors (부동소수점 벡터) | float, vec2, vec3, vec4 | 1개, 2개, 3개 또는 4개의 구성요소의 부동 소수점 기반 벡터 유형 |
Integer vector (정수 백터) | int, ivec2, ivec3, ivec4 | 1개, 2개, 3개 또는 4개 성분의 정수 기반 벡터 유형 |
Unsigned integer vector (부호없는 정수 벡터) | uint, uvec2, uvec3, uvec4 | 1개, 2개, 3개 또는 4개 성분의 부호 없는 정수 기반 벡터 유형 |
Boolean vector (부울 벡터) | bool, bvec2, bvec3, bvec4 | 1개, 2개, 3개 또는 4개 성분의 부울 기반 벡터 유형 |
Matrics (행렬) | mat2, mat2x3, mat2x4, mat3x2, mat3, mat3x4, mat4x2, mat4x3, mat4 | 크기가 2×2, 2×3, 2×4, 3×2, 3×3, 3×3, 4×2, 4×3 또는 4×4인 부동 소수점 행렬 |
셰이딩 언어의 변수는 반드시 타입과 함께 선언해야합니다.
예시 :
float specularAtten; // 부동 소수점 기반 스칼라
vec4 vPosition; // 부동소수점 기반 4-튜플 벡터
mat4 mViewProgection; // 4 x 4 행렬 변수 선언
ivec2 vOffset; // 정수 기반 2-튜플 벡터
변수는 선언 때, 혹은 이후에 초기화할 수 있습니다. 초기화는 생성자를 사용하여 수행되며, 유형 변환에도 사용됩니다.
변수 생성자
규칙이 엄격함
변수는 같은 타입의 변수에만 할당하거나 조작할 수 있다.
암시적 타입 변환(implicit type conversion) 허용하지 않음 → 의도하지 않은 변환으로 인한 버그 방지하기 위해서
타입 변환이 자유롭지 않아서(?제약이 있어서?) 생성자를 사용할 수 있음
생성자를 통해서 변수 초기화 가능
다른 타입의 변수간의 형변환 가능
생성자를 통해서 변수를 선언할 때 (또는 셰이더에서 나중에) 초기화
내장된 각 변수 타입에는 관련 생성자들이 있음
초기화/캐스팅 예시
float myFloat = 1.0; float myFloat2 = 1; // ERROR: invalid type conversion bool myBool = true; int myInt = 0; int myInt2 = 0.0; // ERROR: invalid type conversion myFloat = float(myBool); // [myBool] Convert from bool -> float myFloat = float(myInt); // [myInt] Convert from int -> float myBool = bool(myInt); // [myInt] Convert from int -> bool
벡터 데이터 또한 생성자를 사용해서 변환과 초기화 가능함. 벡터 생성자에 대한 인수는 동일한 built in type (
float
,int
,bool
)로 변환됨. 벡터 생성자에 인자를 넘기는 방법 두 가지:생성자에 인수를 1개만 넣으면 그 값이 벡터의 모든 값을 설정하는데 사용됨
여러 스칼라 또는 벡터 인수가 제공되면 해당 인수를 왼쪽에서 부터 오른쪽 순서로 설정.
여러 스칼라 인수가 제공된 경우, 인수로 들어온 스칼라 컴포넌트 수(== 스칼라 값의 개수)가 벡터의 컴포넌트 수만큼 많아야함
행렬(매트릭스)의 경우 유연하게 구성할 수 있따(flexible하다)
행렬을 구성하는 기본 규칙
- 행렬 생성자에 스칼라 인수가 1개 들어오면, 해당 값은 행렬의 대각선에 배치됨.
예를들어 mat4(1.0)은 4*4 행렬을 생성함 - 행렬은 여러 벡터 인자로 구성할 수 있음.
예를들어 mat2는 2개의 vec2로 구성할 수 있음 - 행렬은 행렬의 각 값에 대해 왼쪽에서 오른쪽순으로 설정되는 여러 스칼라 값을 인자로 구성할 수 있음
- 행렬 생성자에 스칼라 인수가 1개 들어오면, 해당 값은 행렬의 대각선에 배치됨.
행렬의 구성은 위의 기본규칙보다 더 유연하다
행렬을 초기화 하기에 충분한 인자들만 있다면, 스칼라와 벡터의 조합들로 행렬을 구성할 수 있다.
인자가 들어오면 열(column)부터 채운다
벡터와 행렬 컴포넌트
“.”연산자를 사용하거나 배열 첨자 “[]”를 통해서 벡터 접근 가능함
“.”연산자 사용
벡터를 구성하는 컴포넌트의 수에 따라서 각 컴포넌트는 다음 문자들의 혼합(= 명명규칙)을 사용해서 접근할 수 있다. → {x, y, z, w}, {r, g, b, a}, {s, t, p, q}
3가지가 이름이 각각 다른 이유 : 벡터가 표현하는 데이터가 수학서 쓰는 수학, 색, Texture Coordinate 3가지여서.
벡터에 접근 할 때 1번에 1개의 명명규칙만 사용가능함
예시!
배열첨자 “[]” 사용
[0]은 x, [1]는 y, [2]는 z
행렬은 여러 벡터로 구성된 것으로 생각함
행렬에서 행은 배열 첨자 연산자를 사용해 표현 가능
예시!
상수 Constants
상수는 기본 타입은 다 상수 변수로 선언 가능합니다. 상수 변수는 셰이더 안에서 값이 바뀌지 않는 변수입니다. 상수를 선언하려면 const한정자를 추가하면 됩니다. 상수 변수는 선언시 초기화 해야합니다!
c나 c++과 마찬가지로 읽기전용이며 소스 내에서 수정할 수 없습니다.
구조체
기본 타입 외에도 C와 마찬가지로 변수들을 구조체로 만들 수 있습니다.
fogStruct
라는 구조체와 fogVar
라는 새 변수를 생성합니다. 구조체는 생성자를 사용하여 초기화 할 수 있습니다. 새 구조체가 정의되면 해당 구조체와 같은 이름으로 새 구조체 생성자도 정의됩니다. 구조체의 유형과 생성자는 일대일로 대응되어야합니다.
구조체의 요소에 접근 하는 방법은 c와 동일합니다.
배열
c와 매우 유사합니다. index 0부터 시작합니당
초기화 하기:
배열 생성자에 크기를 제공하는 것은 선택 사항입니다. 배열 생성자의 인자 수는 배열의 크기와 같아야 합니다.
연산자
거의 다 C랑 똑같이 동작합니다.
이진 연산자(*, /, +, -)의 경우 변수의 기본 유형은 부동 소수점 또는 정수여야 합니다.
곱하기는 float, vector, matrix의 조합들간에만 연산이 가능합니다.
비교 연산자는 ==와 !=(<, <=, >, >=)를 제외하고는 스칼라 값에만 사용할 수 있습니다.
벡터 비교하는 함수는 나중에 나와용
함수
- C언어와 같은 방법으로 선언
- 선언한 시점 보다 먼저 사용하려면 prototype 써줘야 함
- 매개변수 한정자 지원됨
- in : (지정되지 않은 경우 기본값) 이 한정자는 매개변수가 값으로 전달되며 함수에 의해 수정되지 않도록 지정합니다.
- inout : 이 한정자는 변수가 함수에 참조로 전달되고 값이 수정되면 함수 종료 후 변경되도록 지정합니다.
- out : 이 한정자는 변수 값이 함수에 전달되지 않고 함수에 전달되지 않지만 함수에서 반환될 때 수정됩니다. → 잘 안씀. return 값으로 대체
- 재귀호출 지원x
- 일부 상황에서는 inline함수 같이 함수를 call하지 않고 해당 코드로 대체
- stack을 지원하지 않는 GPU를 지원하기 위해 inline implementation지원
빌트인 함수
- 빌트인 함수 지원
- 엄청 많다고 한다
Control-Flow 구문
OpenGL ES Shading언어의 control flow syntax는 C 언어와 비슷합니다. 간단한 if-then-else 구문은 C와 같은 다음과 같은 형태로 작성 가능합니다.
조건문(conditional statement) 안에 있는 표현식은 반드시 boolean value이어야만 합니다. 즉, boolean value 또는 boolean value를 도출하는 표현식(예컨대 비교 연산자)을 사용해야 한다는 뜻입니다. 이것이 OpenGL ES Shading 언어에서 조건문이 표현되는 방식의 기저에 깔려있는 개념입니다.
조건문 이외에도, while, do-while loop를 작성 가능합니다. OpenGL ES 2.0에서는 loop를 사용할 때 엄격한 제약이 존재했었습니다. 오직 컴파일러에 의해 펼쳐지는(unrolled) loop만 사용가능했습니다. 이러한 제약은 더이상 3.0 버젼에서는 존재하지 않습니다. GPU 하드웨어는 loop와 flow control를 지원하는것으로 기대합니다. 그러므로 loop는 지원이 됩니다.
이것은 loop를 사용하는것이 performance에 영향이 아예 없다고 하는것을 말하고자 하는게 아닙니다. 대부분의 GPU 아키텍쳐는, vertices와 fragments는 여러개의 batch를 통해 parallel 하게 실행이 됩니다. GPU는 보통 하나의 batch안에 있는 모든 vertices와 fragment들은 흐름제어구문의 모든 분기들 (또는 loop)을 빠짐없이 수행하도록 요구합니다. Batch 사이즈는 GPU에 따라 다르며 특정 아키텍처에서 흐름 제어 사용이 성능에 미치는 영향을 확인하기 위해 프로파일링이 필요한 경우가 많습니다. 하지만 경험적으로 알려진 방법은, 너무 복잡한 흐름제어 구문을 피하라는 것입니다. (한줄로 적을수록 조금더 빨라짐)
Uniform
OpenGL ES Shading Language에서의 변수 타입 제어자 중 하나는 uniform변수입니다. Uniform 변수는 application에 의해서 OpenGL ES 3.0 API콜을 통해 전달된 read-only 값을 저장합니다.
- Uniform들은 transformation 행렬, 빛 관련 파라미터들, 그리고 color값 등을 저장합니다.
- 간단히 말하면 모든 vertex데이터, 또는 모든 fragment 들에 대해서 변하지 않는 값들이 있다면 uniform으로 전달되야 합니다.
- compile-time에 값이 정해지는 상수들은 성능상의 이유로 constant로 지정되면 좋습니다.
- Uniform 변수들은 vertex shader, fragment shader에서 공유될 수 있습니다. 이때 같은 type과 이름으로 선언되어서 서로 매칭이 되어야 합니다.
- “constant store”로 알려진 하드웨어 공간에 저장이 됩니다. 보통의경우 이 공간은 한정된 공간이라 저장될 수 있는 uniform 갯수는 한정되어 있습니다. gl_MaxVertexUniformVectors, gl_MaxFragmentUniformVectors 내장 변수를 읽음으로써 알아낼 수 있습니다. 최소로 보장하는 갯수는 256개의 vertex uniform vectors와 224개의 fragment uniform vectors입니다.
Uniform Blocks
- 버퍼 나오길래 이해 못해서 포기..
Vertex and Fragment Shader Inputs/Outputs
- opengl es의 특수변수타입(?!) Vertex input(또는 어트리뷰트)
- 버텍스 입력 변수는 버텍스 셰이더에 대한 버텍스별 입력을 지정하는 데 사용되며 in 키워드로 지정됩니다.
- 위치, 노말, 텍스처 좌표 색상등의 데이터 저장함
- 각 버텍스의 데이터를 나타냄
layout 한정자는 vertex attribute의 인덱스를 지정할 때 사용 (선택사항이며 안 하면 링커에서 자동할당)
- 6장에서 자세하게 나와요
vertex shader input과 달리 vertex shader output/fragment shader input 변수에서는 layout 한정자를 사용할 수 없습니다
하드웨어는 일반적으로 버텍스 셰이더 출력/프래그먼트 셰이더 입력의 수를 제한합니다(하드웨어에서는 보통 이를 보간기라고 합니다). 지원하는 버텍스 셰이더 출력의 수는 gl_MaxVertexOutputVectors 내장 변수에 의해 제공됩니다
- (glGetIntegerv를 사용하여 GL_MAX_VERTEX_OUTPUT_COMPONENTS를 쿼리하면 벡터 수가 아닌 총 컴포넌트 값의 수가 제공됩니다)
OpenGL ES 3.0 구현이 지원할 수 있는 버텍스 출력 벡터의 최소 개수는 16개
지원하는 프래그먼트 셰이더 입력의 수는 gl_MaxFragmentInputVectors에 의해 제공됩니다
- (glGetIntegerv를 사용하여 GL_MAX_FRAGMENT_INPUT_COMPONENTS를 쿼리하면 벡터 수가 아닌 총 컴포넌트 값의 수가 제공됩니다).
OpenGL ES 3.0 구현이 지원할 수 있는 조각 입력 벡터의 최소 개수는 15개
예제
프래그먼트 쉐이더는 1개 이상의 색을 출력할 수 있음
- 일반적으로 단일 색상 버퍼로만 렌더링, 이때 layout 한정자는 선택사항 (출력 변수는 0번째로 이동한다고 가정)
- 멀티 렌더 타깃(MRT)으로 렌더링 할 때는 layout 한정자를 사용해서 각 출력이 어떤 렌더 타깃으로 이동하는지 지정 가능.
보통 프래그먼트 쉐이더에 1개의 출력 변수가 있고, 이 값은 파이프라인 ‘per-fragment operations’로 전달 될 출력 색상이 됨
Interpolation Qualifiers
vertex shader output과 fragment shader input 에서는 한정자 사용가능함
한정자가 없는 경우 부드러운 쉐이딩(smooth shading)을 수행함
vertex shaer ouput의 출력변수는 (primitive 전체에 걸쳐) 선형보간되며, fragment shader는 선형 보간된 값을 입력으로 받음
예시
Flat Shading
opengl es 3.0에서 추가됨
vertex shader의 출력변수가 보간되지 않음
대신 vertex 중에 1개를 provoking vertex로 정하고(primitive유형에 따라 다름. 7장에서 자세히 설명), 이 vertex의 값 primitive의 모든 fragments에 사용함
예시
centroid
보간기에 centroid를 사용해서 다른 한정자를 추가할 수 있음
11장에 자세히 나옴
멀티샘플링으로 렌더링할 때, centroid 키워드를 사용하여 렌더링되는 primitive 내부에서 보간을 강제로 수행할 수 있음
- 안해주면 primitive 가장자리에 아티팩트 발생
예시
Preprocessor and Directives
- OpenGL ES Shading Language의 preprocessor는 대부분 C++ preprocessor관습을 따릅니다. Macro정의문, 조건문은 다음 지시자를 이용해서 표현될 수 있습니다.
- C++에서는 parameter없이 메크로가 define가능하지만 OpenGL ES Shading Language에서는 그렇지 않습니다.
- 다음 macro들은 미리 정의되어 있습니다.
- #error 지시자는 셰이더가 컴파일 될시 관련된 메세지를 info log에 저장시키면서 에러가 나게 합니다.
- #pragma 지시자는 특정 구현에 적용될 수 있는 지시들을 표현할때 쓰입니다.
- #extension 지시자는 vendor 또는 vendor들의 그룹이 OpenGL ES Shading 언어를 확장하는 기능이 있을때 해당 기능을 활성화할지 등의 행동양식을 정하는 기능입니다. Vendor들이 OpenGL ES Shading 언어를 확장할 시, 확장 사양을 생성합니다(예컨대, GL_NV_shadow_samplers_cube). 셰이더는 반드시 컴파일러에게 extension들을 사용할것인지, 사용하지 않는다면 어떤 일이 발생할것인지에 대한 행동양식을 알려줘야 합니다. 이것을 #extension 지시자를 통해 지시합니다. 다음은 예시코드입니다.
첫번째 인자는 extention의 이름 또는 all이 올수 있습니다. all인경우 모든 extention들에 적용됩니다.
구체적 행동 방식
Uniform과 Interpolator Packing
- Uniform들은 하드웨어의 constant store에 저장됨.
- Vertex shader output/fragment shader input들은 하드웨어의 interpolators라는 공간에 저장됨.
- Constant store나 interpolators모두 vector들의 array로 표현되는공간으로 생각할 수 있다.
- 셰이더안의 다양한 타입들(scalar, vector, matrix등)이 실제로 어떻게 물리적 공간(메모리)에 매핑이 되는가? → packing rule
Packing rule
셰이더 안의 uniform, interpolators변수가 어떻게 물리적 공간에 매핑될지를 관장하는 rule.
기본적으로 물리적 공간(메모리)이 4개의 column공간으로 나눠져있고, n개의 row가 각각 storage location으로 매핑이 된다고 가정합니다.
Packing rule은 변수들을 채워넣을때(pack variables) 생성된 코드(generated code)가 상수항에 비례해서 커질정도로 채워넣습니다. 다른말로 하면 packing으로 인해 추가적인 기계어 명령이 생기지 않을정도로 packing합니다. 오히려 패킹 규칙은 런타임 성능에 부정적인 영향을 미치지 않으면서 물리적 주소 공간의 사용을 최적화하려고 합니다.
구체적인 예시로 다음의 uniform 변수 3개를 packing rule을 적용하지 않았을때와 packing rule을 적용했을때 메모리 상에 어떻게 매핑되는지 살펴보겠습니다.
packing rule 없다면 상당한 공간이 낭비됩니다(Table 5-6).
packing rule을 적용하면 공간이 절약됩니다(Table 5-7). Array f의 경우 packing 후에도 여러 행들에 걸쳐져 있는데, 이는 GPU는 constant store를 index시 vector location index로 index하기 때문입니다.
OpenGL ES 셰이딩 언어에서 packing을 함에 따라 uniform, vertex shader outputs/fragment shader input들이 어떻게 세어지어야 하는 방식을 정해놓진 않았습니다. 따라서 모든 구현체에서 돌아가게 보장하려면, 패킹후의 uniform, interpolator들이 각각 최소 허용 저장소 크기를 넘지 않아야 합니다.
Precision Qualifiers
shader 변수에 대한 계산이 수행되는 정밀도 지정 가능.
low, medium, high 정밀도로 선언 가능.
컴파일러에 hint로 사용되어 더 낮은 범위, 정밀도로 변수에 대한 계산 수행가능하도록 설정.
정밀도 낮을 경우:
- shader 실행 속도 증가 또는 더 나은 전력 효율로 실행 가능.
- 일부 opengl es implementation의 경우에서만 해당.
- 일부를 제외하고 나머지에서는 모든 계산을 최고 정밀도로 수행하고 한정자를 무시해도 됨. opengl es 사양에서 기본 하드웨어에서 여러 정밀도를 지원해야한다고 명시되어 있지 않기 때문.
- 이런 효율성 향상에는 정밀도가 희생되므로 presicion qualifiers 사용하지 않으면 artifact 발생 가능.
- shader 실행 속도 증가 또는 더 나은 전력 효율로 실행 가능.
부동 소수점 / 정수 기반 변수의 정밀도 지정에 사용.
lowp, meduimp, highp 키워드를 사용한다.
highp vec4 position;
varyin lowp vec4 color;
mediump float specularExp;
- precision qualifier 없이 변수를 선언할 경우, default precision qualifier로 설정.
아래와 같이 vertex shader / fragment shader 맨 위에 지정.
precision highp float;
precision mediump int;
특정 변수 타입에 지정된 precision : 해당 타입을 기반으로 한 모든 변수들에 적용.
- int에 지정된 precision : 모든 int 변수 precision.
vertex shader에서 precision quailfier 없이 선언된 모든 변수 : 가장 높은 정밀도 가짐.
fragment shader : floating-point에 대한 default precision 정의되어 있지 않음.
- 모든 shader : default float precision 선언 / 모든 float 변수에 대한 precision 선언해야 함.
precision qualifier로 지정된 precision : 구현에 따라 범위 / 정밀도 달라짐.
- 주어진 구현 범위 / 정밀도 결정 위한 API 호출은 15장에서 다룸.
Invariance
“invariance(불변성)” : vertex shader의 모든 다양한 출력에 적용 가능.
문제 : shader 컴파일 → 컴파일러 최적화 후 instruction 순서 변경 → 두 shader 간 동등한 계산 보장되지 않음. (두 쉐이더가 동일한 연산을 해도 같은 결과 생성된다는 보장x.)
- multipass shader effect(알파 블렌딩을 사용하여 동일한 오브젝트를 그 위에 그리는 것)에서 특히 더 발생할 가능성이 높음.
- 이렇게 출력 위치 계산하는데 사용되는 값의 정밀도가 정확히 일치하지 않음 → artifact 발생.
- Z fighting으로 문제가 나타남. (픽셀당 작은 z 정밀도 차이로 인해 서로 다른 패스가 서로 반짝이는 것)
multipass shading시 불변성이 중요한 이유
fragment shader
- 첫 번째 패스에서 specular 조명을 계산.
- 두 번째 패스에서 ambient , diffuse 조명 계산.
vertex shader
불변성을 사용하지 않으므로 두 shader의 정밀도 차이로 인해 Z fighting 발생.
(figure 5-1. 불변성을 적용하지 않아서 발생한 Z Fighting Artifacts)
- 위치에 불변성을 적용한 동일한 multipass shader
(figure 5-2. 불변성을 이용해 피한 Z Fighting )
불변성의 도입
동일한 계산을 사용해 출력을 계산할 경우 그 값이 동일하도록 지정 가능.
해당 키워드를 가변(varying) 선언 / 선언된 varying에 이용 가능.
invariant gl_Position; invariant texCoord;
출력에 대한 불변성 선언
컴파일러 : shader에 동일한 계산, 입력을 할 때 결과가 동일하다는 것 보장.
(뷰 투영 행렬에 입력 위치를 곱하여 출력 위치를 계산하는 2개의 vertex shader가 주어질 경우, 해당 위치가 불변임 보장)
#version 300 es uniform mat4 u_viewProjMatrix; layout(location = 0) in vec4 a_vertex; invariant gl_Position; void main() { //동일한 viewProjcMatrix, vertex가 있는 모든 shader에서 동일한 값. gl_Position = u_viewProjMatrix * a_vertex; }
모든 변수를 전역적 불변으로 만들기 위해서는 #pragma 지시어 사용.
#pragma STDGL invariant(all)
- 주의점
- 컴파일러는 불변성 보장을 위해 최적화를 제한할 수 있음.
- 필요한 경우에만 불변 한정자 사용해야 함. (그렇지 않으면 성능 저하 초래)
- #pragma : 불변성이 실제로 필요한 경우에만 사용.
- 불변성 = 특정 GPU에서 계산 결과 동일.
- OpenGL ES의 모든 구현에서 계산이 불변이라는 의미는 아님.
'OSS' 카테고리의 다른 글
OpenGL ES 3.0 스터디 7주차 공부자료 (0) | 2023.09.04 |
---|---|
OpenGL ES 3.0 스터디 6주차 공부자료 (0) | 2023.08.31 |
OSS 8월 회고 후기 (0) | 2023.08.16 |
OpenGL ES 3.0 스터디 4주차 공부자료 (0) | 2023.08.14 |
OpenGL ES 3.0 스터디 3주차 공부자료 (0) | 2023.08.07 |