https://github.com/dlgmlals3/OpenGLES3.0_Example/blob/main/Chapter_3/app/src/main/cpp/Scene/Cube.cpp
OpenGLES3.0_Example/Chapter_3/app/src/main/cpp/Scene/Cube.cpp at main · dlgmlals3/OpenGLES3.0_Example
Contribute to dlgmlals3/OpenGLES3.0_Example development by creating an account on GitHub.
github.com
Cube.cpp, CreateUniformBufferObject(), Render(), RenderCubeWithMapBuffer 참조
==============================================================================
Layout Qualifier
GLSL(오픈지엘 셰이딩 언어)에서
layout 한정자는 셰이더 변수의 메모리 배치, 속성 위치, 바인딩 포인트 및 기타 설정을 명시적으로 지정하는 데 사용
이를 통해 GPU와 CPU 간 데이터 교환을 보다 효율적이고 명확하게 관리할 수 있으며,
glGetAttribLocation, glGetUniformLocation 등의 호출 없이도 원하는 위치에 변수를 직접 매핑할 수 있습니다.
버텍스셰이더 어트리뷰트에서 input 변수로 사용이 되며,
유니폼의 변수의 경우 사용할수 없으며, 대신 grouping uniform을 사용한다.
프래그 먼트 셰이더에서는 input 변수 "in" 에서는 layout을 쓸 수 없다. ( 사실 사용할 필요도 없다. )
왜냐하면 프래그먼트 셰이더에서 받을 변수는 버텍스 셰이더에서 out 키워드를 사용해야 하는데,
변수명이 똑같게 하면 연결되기 때문이다.
단 프래그먼트 셰이더에서 layout을 사용하는 경우가 있는데,
out 키워드를 사용할때, 렌더타겟으로 동작 할 수 있다.
아래 Multiple Render Targets (MRT) 예제를 참고하자.
=======================================================================
Layout Qualifier 사용하지 않는 버전
// shader
in vec3 aPos; // 위치 값 (Vertex Position)
in vec3 aColor; // 색상 값 (Vertex Color)
in vec2 aTexCoord; // 텍스처 좌표 (UV)
...
void main() {
gl_position = uProjection * uView * uModel * vec4(aPos, 1.0);
...
}
// 정점 속성 위치 가져오기
GLint posLoc = glGetAttribLocation(shaderProgram, "aPos");
GLint colorLoc = glGetAttribLocation(shaderProgram, "aColor");
GLint texCoordLoc = glGetAttribLocation(shaderProgram, "aTexCoord");
// 정점 속성 활성화 및 설정
glEnableVertexAttribArray(posLoc);
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, stride, (void*)0);
glEnableVertexAttribArray(colorLoc);
glVertexAttribPointer(colorLoc, 3, GL_FLOAT, GL_FALSE, stride, (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(texCoordLoc);
glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, stride, (void*)(6 * sizeof(float)));
Layout Qualifier 사용
// shader
layout(location = 0) in vec3 aPos; // 위치 값 (Vertex Position)
layout(location = 1) in vec3 aColor; // 색상 값 (Vertex Color)
layout(location = 2) in vec2 aTexCoord; // 텍스처 좌표 (UV)
...
void main() {
gl_position = uProjection * uView * uModel * vec4(aPos, 1.0);
...
}
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)(6 * sizeof(float)));
location = 0 → aPos에 해당하는 위치 값 데이터를 전달
location = 1 → aColor에 해당하는 색상 데이터를 전달
location = 2 → aTexCoord에 해당하는 텍스처 좌표 데이터를 전달
Layout Qualifier 장점
- 동적 바인딩: OpenGL이 실행될 때마다 유니폼의 위치가 달라질 수 있음.
- glGetUniformLocation은 런타임에 실행되며, 성능 비용이 크지는 않지만 불필요한 호출을 줄일수 있음
==============================================================================
Grouping Uniforms
UBO (Uniform Buffer Object)
UBO는 OpenGL에서 여러 셰이더가 공유할 수 있는 유니폼 데이터를 저장하는 버퍼 객체이다.
OpenGL ES 3.0 이전에는 유니폼 변수를 각각 셰이더 프로그램에 전달해야 했지만,
OpenGL ES 3.0부터는 **유니폼 블록(Uniform Block)**을 사용하여
// Uniform Block Declaration
uniform Transformation {
mat4 ModelMatrix;
mat4 ViewMatrix;
mat4 ProjectionMatrix;
} Obj1;
여러 유니폼 변수를 하나의 **유니폼 버퍼 객체(UBO)**로 그룹화할 수 있습니다.

1. UBO를 사용하지 않는 코드
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
gl_position = projection * view * model * vec4(aPos, 1.0);
...
}
// 셰이더 프로그램 사용
glUseProgram(shaderProgram);
// 각 행렬의 위치를 가져옴
GLint modelLoc = glGetUniformLocation(shaderProgram, "model");
GLint viewLoc = glGetUniformLocation(shaderProgram, "view");
GLint projectionLoc = glGetUniformLocation(shaderProgram, "projection");
// 변환 행렬 생성
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f));
glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
// 애니메이션 경우 프레임마다 유니폼 값을 업데이트
for (int frame = 0; frame < 1000; ++frame) {
// uniform 개수만큼 glUniformX 호출
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
// 드로우 콜 (예시)
glDrawArrays(GL_TRIANGLES, 0, 36);
}
2. UBO를 사용하는 코드 - 2 (bindingPoint 설정)
#version 310 es
layout(location = 0) in vec4 VertexPosition;
layout(location = 1) in vec4 VertexColor;
out vec4 Color;
// Uniform Block Declaration
uniform Transformation {
mat4 ModelMatrix;
mat4 ViewMatrix;
mat4 ProjectionMatrix;
} Obj1;
void main()
{
gl_Position = Obj1.ProjectionMatrix * Obj1.ViewMatrix * Obj1.ModelMatrix * VertexPosition;
Color = VertexColor;
}

void Cube::CreateUniformBufferObject()
{
// Get the index of the uniform block
char blockIdx = glGetUniformBlockIndex(program->ProgramID, "Transformation");
// Bind the block index to BindPoint
GLint bindingPoint = 3;
glUniformBlockBinding(program->ProgramID, blockIdx, bindingPoint);
// Get Shader Uniform Block Size
GLint blockSize;
glGetActiveUniformBlockiv(program->ProgramID, blockIdx, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
LOGI("UBO BlockSize : %d\n", blockSize);
// Create Uniform Buffer Object(UBO) Handle
glGenBuffers(1, &UBO);
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferData(GL_UNIFORM_BUFFER, blockSize, 0, GL_DYNAMIC_DRAW);
// Bind the UBO handle to BindPoint
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, UBO);
}
void Cube::RenderCube()
{
// This Section
{
glBindBuffer( GL_UNIFORM_BUFFER, UBO );
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, UBO);
glm::mat4 matrices[3];
matrices[0] = *TransformObj->TransformGetModelMatrix();
matrices[1] = *TransformObj->TransformGetViewMatrix();
matrices[2] = *TransformObj->TransformGetProjectionMatrix();
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(matrices), matrices);
}
....
glBindBuffer( GL_UNIFORM_BUFFER, 0 );
glBindBufferBase(GL_UNIFORM_BUFFER, 0, 0);
}
단계 1: Uniform Block의 인덱스를 가져오기
char blockIdx = glGetUniformBlockIndex(program->ProgramID, "Transformation");
- glGetUniformBlockIndex 함수는 셰이더 프로그램(program->ProgramID)에서 "Transformation"이라는 이름의 Uniform Block을 찾고, 그 인덱스를 반환합니다.
- 여기서 blockIdx는 셰이더 프로그램 내에서 Transformation이라는 Uniform Block을 식별하는 번호입니다.
- 이는 Uniform Block이 여러 개 있을 경우 어떤 블록을 참조해야 하는지 결정하는 데 사용됩니다.
단계 2: Uniform Block의 크기(데이터 크기) 가져오기
GLint blockSize;
glGetActiveUniformBlockiv(program->ProgramID, blockIdx, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
- glGetActiveUniformBlockiv 함수는 활성화된 Uniform Block의 특정 속성을 쿼리합니다.
- 여기서는 GL_UNIFORM_BLOCK_DATA_SIZE 속성을 쿼리하여, Uniform Block이 차지하는 메모리 크기를 blockSize에 저장합니다.
- 이 크기는 나중에 UBO에 할당할 메모리 공간을 결정하는 데 사용됩니다.
단계 3: Uniform Block을 바인딩 포인트에 연결하기
GLint bindingPoint = 0;
glUniformBlockBinding(program->ProgramID, blockIdx, bindingPoint);
- glUniformBlockBinding 함수는 blockIdx를 특정 바인딩 포인트에 연결합니다.
- 바인딩 포인트는 OpenGL에서 Uniform Buffer Object와 Shader Uniform Block을 연결하기 위한 자리 번호입니다.
- 위 코드에서는 bindingPoint는 0으로 설정되어 있어, 셰이더 프로그램 내에서 "Transformation" Uniform 블록이 바인딩 포인트 0번에 연결되도록 합니다.
단계 4: UBO 생성 및 메모리 할당
LOGI("UBO BlockSize : %d\n", blockSize);
glGenBuffers(1, &UBO);
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferData(GL_UNIFORM_BUFFER, blockSize, 0, GL_DYNAMIC_DRAW);
- glGenBuffers 함수는 새로운 UBO를 생성하고, UBO 변수에 그 ID를 저장합니다.
- glBindBuffer 함수는 생성한 UBO를 현재 활성화된 GL_UNIFORM_BUFFER로 설정합니다.
- glBufferData 함수는 UBO에 메모리를 할당하는 함수로, blockSize만큼의 메모리를 할당합니다. 0은 초기 데이터를 제공하지 않는다는 뜻입니다.
- GL_DYNAMIC_DRAW는 버퍼가 자주 업데이트될 것이라는 힌트를 OpenGL에 제공하는 것입니다. 이 값에 따라 OpenGL은 메모리 관리를 최적화할 수 있습니다.
단계 5: UBO를 바인딩 포인트에 연결
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, UBO);
- glBindBufferBase 함수는 GL_UNIFORM_BUFFER 타입의 버퍼(UBO)를 특정 바인딩 포인트(bindingPoint)에 연결합니다.
- bindingPoint가 0으로 설정되어 있으므로, 0번 바인딩 포인트에 UBO를 연결하는 것입니다.
- 이제 셰이더 프로그램에서 Uniform Block이 참조할 수 있는 데이터를 UBO를 통해 접근할 수 있게 됩니다.
=========================================================================
렌더링 단계
단계 1 : UBO 바인딩
glBindBuffer( GL_UNIFORM_BUFFER, UBO );
단계 2 : UBO를 바인딩 포인터 연결
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, UBO);
단계 3 : Uniform 버퍼에 보낼 데이터 설정 후 버퍼 채움.
glm::mat4 matrices[3];
matrices[0] = *TransformObj->TransformGetModelMatrix();
matrices[1] = *TransformObj->TransformGetViewMatrix();
matrices[2] = *TransformObj->TransformGetProjectionMatrix();
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(matrices), matrices);
단계 4 : 렌더링
glDrawElements(..)
======================================================================
3. UBO + glMapBufferRange 를 사용하는 코드
**그룹핑 유니폼(Grouping Uniforms)**과 **버퍼 객체 매핑(Buffer Mapping)**은
OpenGL ES 3.0에서 유니폼 데이터를 보다 효율적으로 관리하고 업데이트하기 위한 기능입니다.
이를 통해 **유니폼 버퍼 객체(Uniform Buffer Object, UBO)**를 사용하여 유니폼을 그룹화하고,
**버퍼 매핑(Buffer Mapping)**을 통해 GPU 메모리의 데이터를 직접 수정할 수 있습니다.
이 두 기능은 함께 사용하면 복잡한 셰이더 프로그램에서도 성능을 크게 향상시킬 수 있습니다.
glBufferSubData | glMapBufferRange | |
작동 방식 | CPU에서 데이터를 복사 후 GPU에 전송 | CPU가 직접 GPU 메모리를 수정 |
속도 | 비교적 느림 (매번 데이터 복사 필요) | 빠름. |
메모리 복사 비용 | 추가적인 메모리 복사 비용 발생 | 불필요한 복사 작업 제거 |
동기화 문제 | 이전 프레임이 끝날 때까지 기다려야 할 수도 있음 | GL_MAP_UNSYNCHRONIZED_BIT 사용 시 동기화 문제 줄일 수 있음 |
사용 추천 | 작은 크기의 데이터 업데이트 | 큰 크기의 UBO 데이터 변경 |
void Cube::CreateUniformBufferObject()
{
// Get the index of the uniform block
GLuint blockIdx = glGetUniformBlockIndex(program->ProgramID, "Transformation");
// Buffer space allocation
GLint blockSize;
glGetActiveUniformBlockiv(program->ProgramID, blockIdx, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
LOGI("shader block size : %d", blockSize);
//Bind the block index to BindPoint
bindingPoint = 3;
glUniformBlockBinding(program->ProgramID, blockIdx, bindingPoint);
// Create Uniform Buffer Object(UBO) Handle
glGenBuffers(1, &UBO);
glBindBuffer(GL_UNIFORM_BUFFER, UBO);
glBufferData(GL_UNIFORM_BUFFER, blockSize, 0, GL_DYNAMIC_DRAW);
// Bind the UBO handle to BindPoint
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, UBO);
}
void Cube::RenderCubeWithMapBuffer() {
// This Section
{
glBindBuffer( GL_UNIFORM_BUFFER, UBO );
glm::mat4 *matrixBuf = (glm::mat4 *) glMapBufferRange(GL_UNIFORM_BUFFER, 0,
sizeof(glm::mat4) * 3,
GL_MAP_WRITE_BIT);
matrixBuf[0] = *TransformObj->TransformGetModelMatrix();
matrixBuf[1] = *TransformObj->TransformGetViewMatrix();
matrixBuf[2] = *TransformObj->TransformGetProjectionMatrix();
glUnmapBuffer(GL_UNIFORM_BUFFER);
}
glEnableVertexAttribArray(VERTEX_LOCATION);
glEnableVertexAttribArray(COLOR_LOCATION);
glBindBuffer( GL_ARRAY_BUFFER, vId );
glVertexAttribPointer(VERTEX_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glVertexAttribPointer(COLOR_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, (void*)size);
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, iId );
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, (void*)0);
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}
함수 설명
void* glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
- target: 매핑할 버퍼의 종류를 지정합니다.
- GL_ARRAY_BUFFER: 정점 버퍼에 접근.
- GL_ELEMENT_ARRAY_BUFFER: 인덱스 버퍼에 접근.
- GL_UNIFORM_BUFFER: Uniform 버퍼에 접근.
- 기타 다른 버퍼 타입도 지원됩니다.
- offset: 매핑 시작 위치를 지정하며, 바이트 단위입니다. 버퍼의 시작점에서 몇 바이트 떨어진 위치를 의미합니다.
- length: 매핑할 데이터 크기를 지정하며, 바이트 단위입니다. 이 크기만큼의 메모리를 매핑합니다.
- access: 버퍼에 대해 어떤 액세스를 허용할지 설정하는 플래그입니다. 여러 비트플래그를 OR 연산으로 조합하여 사용할 수 있습니다.
- GL_MAP_READ_BIT: 버퍼를 읽기 전용으로 매핑.
- GL_MAP_WRITE_BIT: 버퍼를 쓰기 전용으로 매핑.
- GL_MAP_INVALIDATE_RANGE_BIT: 매핑된 범위의 기존 데이터를 무효화하여 성능 최적화.
- GL_MAP_INVALIDATE_BUFFER_BIT: 전체 버퍼 데이터를 무효화.
- GL_MAP_FLUSH_EXPLICIT_BIT: 변경 내용을 수동으로 반영하도록 지정.
- GL_MAP_UNSYNCHRONIZED_BIT: 동기화 없이 매핑하여 빠르게 접근.
반환 값
- 버퍼의 매핑된 메모리 주소를 반환하며, void* 타입입니다. 이 주소를 통해 CPU가 직접 메모리에 접근할 수 있습니다.
- 매핑에 실패하면 NULL이 반환됩니다.
- 작업이 완료된 후에는 반드시 glUnmapBuffer 함수를 호출하여 매핑을 해제해야 합니다.
참고
=====================================================================
Multi Target Example
# Fragment Shader
#version 300 es
precision mediump float;
in vec2 TexCoord;
layout(location = 0) out vec4 FragColor0; // 첫 번째 렌더 타겟
layout(location = 1) out vec4 FragColor1; // 두 번째 렌더 타겟
void main() {
FragColor0 = vec4(TexCoord, 0.0, 1.0); // 첫 번째 렌더 타겟 (UV 좌표 저장)
FragColor1 = vec4(1.0, 0.0, 0.0, 1.0); // 두 번째 렌더 타겟 (고정된 빨간색)
}
layout(location = n)과 GL_COLOR_ATTACHMENTn은 자동으로 1:1 연결
출력은 당연히 텍스처로 ..
GLuint fbo, colorTexture1, colorTexture2;
// 📌 1. FBO 생성
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// 📌 2. 첫 번째 렌더 타겟 (컬러 텍스처)
glGenTextures(1, &colorTexture1);
glBindTexture(GL_TEXTURE_2D, colorTexture1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 800, 600, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture1, 0);
// 📌 3. 두 번째 렌더 타겟 (컬러 텍스처)
glGenTextures(1, &colorTexture2);
glBindTexture(GL_TEXTURE_2D, colorTexture2);
.....
// 📌 4. 어떤 렌더 타겟을 활성화할지 설정 (GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 둘 다 사용)
GLenum drawBuffers[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, drawBuffers);
// 📌 5. FBO가 정상적으로 생성되었는지 체크
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "ERROR: Framebuffer is not complete!" << std::endl;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); // FBO 바인딩 해제
1. std140의 개념
std140은 OpenGL ES에서 유니폼 블록의 메모리 레이아웃 규칙을 정의하는 한정자입니다. std140 레이아웃을 사용하면, 유니폼 블록에 포함된 데이터가 특정한 메모리 배치 규칙에 따라 배열되어, CPU와 GPU 간의 데이터 전송이 일관되게 이루어지도록 보장됩니다.
유니폼 블록 내의 데이터는 GPU 메모리에 특정한 메모리 배치를 따라야 합니다. std140은 이러한 메모리 배치 규칙을 정의하여, 유니폼 블록의 각 멤버가 메모리에서 어떻게 정렬되는지를 명확하게 규정합니다.
layout(std140) uniform MyUniformBlock {
float a; // 4바이트 (정렬: 4바이트)
vec3 b; // 16바이트 (패딩 포함, 정렬: 16바이트)
mat4 c; // 64바이트 (4개의 vec4, 각 16바이트씩, 정렬: 16바이트)
vec2 d[3]; // 48바이트 (각 요소가 16바이트 정렬됨)
};
위의 예시에서:
- float a는 4바이트로 정렬됩니다.
- vec3 b는 16바이트의 메모리 공간을 차지하며, 12바이트의 데이터와 4바이트의 패딩이 포함됩니다.
- mat4 c는 4개의 vec4로 이루어져 있으며, 각각 16바이트의 메모리 공간을 차지하여 총 64바이트입니다.
- vec2 d[3]은 배열의 각 요소가 16바이트로 정렬되므로 총 48바이트입니다.
std140을 사용하는 이유
- 일관성: std140을 사용하면, 유니폼 블록의 메모리 레이아웃이 명확하게 정의되어 CPU에서 데이터를 준비할 때 GPU에서 요구하는 형식과 일치하도록 할 수 있습니다.
- 호환성: 서로 다른 하드웨어와 드라이버에서 유니폼 블록의 메모리 레이아웃이 동일하게 유지되므로, 크로스 플랫폼 개발에서 일관성을 유지할 수 있습니다.
- 성능 최적화: 데이터 정렬이 보장되기 때문에, 메모리 접근이 더 효율적입니다.
2. glBindAttribLocation vs glGetAttribLocation
void glBindAttribLocation(GLuint program, GLuint index, const char* name);
- 프로그램 링크 전에 사용
- 속성의 위치를 지정(설정)하는 함수
- 애플리케이션이 위치 값을 결정
GLint glGetAttribLocation(GLuint program, const char* name);
- 프로그램 링크 후에 사용
- 속성의 위치를 조회(가져오는) 함수
- OpenGL이 자동으로 할당한 위치 값을 반환
사용 예시:
// 1. glBindAttribLocation 사용
GLuint program = glCreateProgram();
// 링크 전에 위치 지정
glBindAttribLocation(program, 0, "position");
glBindAttribLocation(program, 1, "color");
glLinkProgram(program);
// 2. glGetAttribLocation 사용
GLuint program = glCreateProgram();
glLinkProgram(program);
// 링크 후에 위치 조회
GLint posLoc = glGetAttribLocation(program, "position");
GLint colorLoc = glGetAttribLocation(program, "color");
// posLoc과 colorLoc을 사용해서 버텍스 속성 설정
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, 0);
'Opengles 3.0 with Android' 카테고리의 다른 글
Chapter 3.3 Primitive Restart < Skip > (2) | 2025.02.24 |
---|---|
Chapter 3.2 Instancing 렌더링 (0) | 2025.02.24 |
Chapter 2.5 VAO ? VBO ? ( 참고 ) (1) | 2025.02.22 |
Chapter 2.1 Shader Build (0) | 2025.02.22 |
Chapter 2.4 Culling (0) | 2025.02.21 |