GL_POINTS로 그리는 포인트 클라우드

그래픽 뷰어에서 ​수만~수십만 개의 점 을 삼각형 메시로 올리면 정점과 드로우 비용이 불필요하게 커집니다. 이 글에서는 GL_POINTS, 셰이더의 gl_PointSize, 프래그먼트의 gl_PointCoord + discard 로 원형 포인트 스프라이트를 그리는 시각화 패턴을 설명합니다.

완성할 예제

다음을 한 드로우(Draw) 함수로 구성합니다.

  • VBO에 vec3 위치(+ 선택적 색)를 업로드

  • 버텍스 세이더(VS)에서 클립 공간 변환과 ​원근 보정 point size

  • 프래그먼트 세이더(FS)에서 사각형 포인트를 ​원으로 clip

  • glEnable(GL_PROGRAM_POINT_SIZE)로 셰이더 크기 제어

읽고 나면 “점이 네모로 보이거나, 멀어도 크기가 그대로인” 상태를 그릴 수 있습니다.

실행 예제: (데스크톱: GLFW+GLEW · · )

왜 point sprite인가

점(포인트)을 표현하려고 직사각형 평면(빌보드)을 CPU에서 직접 확장해서 만들면, 각 점마다 정점 개수가 4배로 늘어나고 정점 데이터 갱신도 덩달아 무거워집니다. 반면 GL_POINTS 방식은 정점 하나만으로도 화면상 사각형 영역이 자동 생성되고, 그 크기와 모양을 셰이더에서 간편하게 조절할 수 있어 훨씬 효율적입니다.

핵심 요소는 다음과 같습니다.

  1. 프리미티브 — glDrawArrays(GL_POINTS, …)

  2. 크기 — VS의 gl_PointSize (픽셀 단위)

  3. 좌표 — FS의 gl_PointCoord ∈ [0,1]²

  4. 모양 — 원 밖 프래그먼트 discard

  5. 깊이 — 일반 depth test로 메시와 섞기

정점 셰이더: 원근 point size

gl_PointSize​픽셀 단위입니다. “w”는 클립 공간에서의 정규화 계수로, 정점이 뷰 공간에서 멀어질수록(w가 커질수록) 화면상 크기가 작아지게 만듭니다.
즉, ​동일한 월드 반경을 화면에서 일관되게 보이게 하려면 투영행렬의 스케일과 함께 1/w를 곱해주어 원근법(Perspective)에 따라 실제 픽셀 크기를 조정해야 해요.

Glsl

#version 330 core layout(location = 0) in vec3 a_pos; layout(location = 1) in vec3 a_color; uniform mat4 u_mvp; uniform float u_point_radius_px; // 기준 픽셀 반경(또는 직경 정책에 맞게) uniform float u_viewport_y; // 픽셀 높이 uniform float u_proj_scale_y; // projection[1][1] 등 out vec3 v_color; void main() { gl_Position = u_mvp * vec4(a_pos, 1.0); v_color = a_color; // 대략: 화면 높이·투영 스케일·시야 거리로 픽셀 크기 보정 float dist_atten = 1.0 / max(gl_Position.w, 1e-4); gl_PointSize = u_point_radius_px * u_viewport_y * u_proj_scale_y * dist_atten; }

이렇게 하면 해당 GPU와 드라이버에서 clamp되는 최소·최대 값을 알 수 있습니다.

Literal-block

## 프래그먼트 셰이더: 원형 discard 래스터 결과는 기본적으로 **​사각형** 입니다. 중심에서 거리가 1을 넘는 조각을 버리면 원반처럼 보입니다. ```glsl #version 330 core in vec3 v_color; out vec4 frag_color; void main() { vec2 c = 2.0 * gl_PointCoord - 1.0; if (dot(c, c) > 1.0) { discard; } // 간단한 구형 느낌: 가장자리로 갈수록 약간 어둡게 float shade = sqrt(max(0.0, 1.0 - dot(c, c))); frag_color = vec4(v_color * shade, 1.0); }

포인트에 반투명 효과가 필요하다면 discard 대신 알파(투명도) 값을 조절하고 블렌딩(blending) 기능을 활성화하면 됩니다.
하지만 이럴 경우, 깊이 버퍼 처리(depth write)와 그에 따른 정렬 문제가 생길 수 있어서,
에디터 오버레이처럼 특별히 투명이 필요한 상황이 아니라면 불투명 처리와 discard를 사용하는 방식이 더 간단하고 안전합니다.

C++ 쪽 드로우

Cpp

glEnable(GL_PROGRAM_POINT_SIZE); glEnable(GL_DEPTH_TEST); glUseProgram(particle_prog); glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, mvp); glUniform1f(loc_radius, 8.0f); glUniform1f(loc_viewport_y, static_cast<float>(viewport_h)); glUniform1f(loc_proj_scale_y, proj[5]); // column-major면 [1][1] glBindVertexArray(vao); glDrawArrays(GL_POINTS, 0, point_count);

동적으로 움직이는 포인트 클라우드라면, 매 프레임마다 glBufferSubData로 위치 데이터를 갱신하거나, 혹은 버퍼 orphaning(=기존 버퍼의 데이터를 폐기하고 새로운 버퍼 메모리를 할당해서, 이전 버퍼와의 동기화 지연 없이 빠르게 데이터를 교체하는 기법)을 통해 위치만 효율적으로 갱신할 수 있습니다.

메시 형상과 한 뷰포트에 섞을 때

포인트 클라우드는 보통 메시 형상을 그린 ​이후 에 그립니다. 순서를 바꾸면 깊이는 맞더라도, 입체적으로 보조선이나 축을 표시해주는 '기즈모(예: XYZ 축, 원형 회전 핸들)'와 포인트가 반투명하게 겹칠 때는 렌더 순서에 따라 겹침 결과가 달라집니다.

실무에서는 프레임을 아래와 같이 세 단계로 나누어 그리는 것을 권장합니다.

  1. 먼저, 불투명 메시(예: Phong 셰이딩)가 전체 장면의 기본 입체감을 책임지고, 깊이 버퍼를 세팅합니다.

  2. 그다음, 포인트 스프라이트(불투명 처리, 즉 discard 사용 및 depth write on)를 메시 위에 얹어줌으로써 포인트 클라우드를 명확하게 시각화합니다. 이때 각 포인트가 메시보다 더 앞으로 오거나 뒤에 가려질 경우 깊이 테스트가 자동으로 처리됩니다.

  3. 마지막으로, XYZ 축 가이드라인, 보조선, 선택 하이라이트 등 라인 기반 기즈모 요소들을 오버레이로 그려줍니다.

여기서 ​디버그 포인트 란, 예를 들어 메시 충돌 위치, 추적 경로, 센서 입력 등 알고리즘의 중간 결과나 상태를 빠르게 눈으로 확인하기 위해 화면에 찍어보는 점(점 구름)을 말합니다.
이렇게 여러 개의 서로 다른 색상의 디버그 포인트 클라우드(예: 빨간색은 교차점, 파란색은 경계 박스의 코너 등)를 동시에 시각화하고 싶을 때,
매번 셰이더 프로그램을 새로 바인딩하는 것보다 ​정점 색상 attribute 나 ​유니폼 tint 값을 바꿔주는 방식이 더 안전하고 효율적이에요.
이 방식을 쓰면 GL 상태가 불필요하게 꼬일 위험이 줄어듭니다.

설계 체크리스트

  1. GL_PROGRAM_POINT_SIZE를 반드시 활성화한 후에만 VS에서 지정한 gl_PointSize 값이 제대로 적용됩니다.

  2. 원근에 따라 포인트 크기를 바꿀지, 아니면 고정된 픽셀 크기로 디버깅할지 목적을 먼저 명확히 정하세요.

  3. FS(프래그먼트 셰이더)에서는 gl_PointCoord를 활용해서 원형으로 클리핑하는 방식을 기본으로 사용합니다.

  4. Depth test(깊이 테스트)는 메시 렌더링과 같은 방식으로 일관성 있게 켜거나 꺼주세요.

  5. 포인트 크기의 최대치(GL_POINT_SIZE_RANGE 등)는 프로그램 실행 시 한 번 체크해 두는 것이 좋습니다.

  6. 만약 피킹(선택)이 필요하다면 동일한 포인트에 대해 ID 색상 패스도 함께 준비해 주세요

자주 발생하는 문제

점이 전혀 안 보이거나 1픽셀로만 보임

GL_PROGRAM_POINT_SIZE 누락, gl_PointSize 미작성, 또는 size가 0에 가깝게 감쇠한 경우입니다. 임시로 gl_PointSize = 10.0을 넣어 파이프라인을 분리해 봅니다.

네모난 점만 보임

FS discard가 없거나, gl_PointCoord를 쓰는 셰이더가 아닌 프로그램이 바인딩된 상태입니다.

가까이 가면 점이 사라짐

near plane clipping 또는 size clamp의 문제입니다. 카메라 near와 GL_POINT_SIZE_RANGE를 확인합니다.

메시와 깊이 처리가 예상과 다름

포인트 스프라이트는 빌보드 사각형의 깊이 값을 사용하기 때문에, 화면에서 보면 실제 구 형태의 경계선(실루엣)과 깊이 정보가 일치하지 않는 경우가 있습니다.
정확한 위치 판정이나 교차 여부가 필요하다면, 레이 피킹이나 메시 충돌 검사 등 보다 정밀한 방법을 함께 고려해 주세요.

Docutils System Messages

On this page