gluSphere ()를 사용하지 않고 OpenGL에서 구 그리기?


81

OpenGL에서 사용하지 않고도 구를 그릴 수있는 방법을 설명하는 튜토리얼이 gluSphere()있습니까?

OpenGL 용 3D 튜토리얼의 대부분은 큐브에 있습니다. 나는 검색했지만 구를 그리는 대부분의 솔루션은 gluSphere(). 이 사이트에 구를 그리는 코드가있는 사이트도 있지만 구를 그리는 이면의 수학을 설명하지 않습니다. 해당 링크의 쿼드 대신 다각형으로 구를 그리는 방법에 대한 다른 버전도 있습니다. 그러나 다시 코드로 구체가 어떻게 그려 지는지 이해하지 못합니다. 필요한 경우 구를 수정할 수 있도록 시각화 할 수 있기를 원합니다.


3
수학 설명을 위해 구형 좌표를 찾습니다 (특히 구형 좌표에서 데카르트 좌표로의 변환).
Ned Bingham 2011 년

답변:


270

이를 수행 할 수있는 한 가지 방법은 삼각형면이있는 플라토닉 솔리드 ( 예 : 팔면체 )로 시작하는 것 입니다. 그런 다음 각 삼각형을 재귀 적으로 더 작은 삼각형으로 나눕니다.

재귀 적으로 그려진 삼각형

충분한 양의 점이 있으면 벡터가 모두 솔리드 중심에서 일정한 거리에 있도록 벡터를 정규화합니다. 이렇게하면 점의 수를 늘릴수록 부드러움이 증가하면서 측면이 구와 유사한 모양으로 돌출됩니다.

여기서 정규화는 다른 점에 대한 각도는 같지만 그 사이의 거리는 다르도록 점을 이동하는 것을 의미합니다. 다음은 2 차원 예입니다.

여기에 이미지 설명 입력

A와 B는 6 단위 떨어져 있습니다. 그러나 우리가 A에서 12 단위 떨어진 라인 AB에서 한 지점을 찾고 싶다고 가정 해 보겠습니다.

여기에 이미지 설명 입력

C는 거리가 12 인 A에 대해 B의 정규화 된 형태라고 말할 수 있습니다. 다음과 같은 코드로 C를 얻을 수 있습니다.

#returns a point collinear to A and B, a given distance away from A. 
function normalize(a, b, length):
    #get the distance between a and b along the x and y axes
    dx = b.x - a.x
    dy = b.y - a.y
    #right now, sqrt(dx^2 + dy^2) = distance(a,b).
    #we want to modify them so that sqrt(dx^2 + dy^2) = the given length.
    dx = dx * length / distance(a,b)
    dy = dy * length / distance(a,b)
    point c =  new point
    c.x = a.x + dx
    c.y = a.y + dy
    return c

이 정규화 과정을 같은 점 A와 같은 거리 R에 대해 많은 점에 대해 수행하면 정규화 된 점은 모두 중심 A와 반경 R을 가진 원호에 놓일 것입니다.

불룩한 선 세그먼트

여기에서 검은 색 점은 선에서 시작하여 호로 "부풀어집니다".

이 과정은 3 차원으로 확장 될 수 있으며,이 경우 원이 아닌 구를 얻게됩니다. 정규화 함수에 dz 구성 요소를 추가하기 만하면됩니다.

정규화 된 다각형

레벨 1 부풀어 오르는 팔면체 레벨 3 부풀어 오르는 팔면체

Epcot 에서 구를 보면이 기술이 작동하는 것을 볼 수 있습니다. 더 둥글게 보이게하기 위해 얼굴이 튀어 나온 십이 면체입니다.


1
차라리 epcot 영역에 대한 링크를 제거하고 싶습니다. 모든 삼각형이 다시 세 개의 이등변 삼각형으로 세분화되기 때문에 초보자에게 혼란을 줄 수 있습니다 (sqrt (3) -subdivision의 첫 부분과 유사). 나는 당신이 더 나은 예를 찾을 것이라고 확신합니다.
Christian Rau 2011 년

나는 가정용 컴퓨터에서 이것을 잘 구현했습니다. 퇴근 후 일부 스크린 샷을 편집 해 드리겠습니다.
Kevin

아이디어 주셔서 감사합니다. 그러나 벡터를 정규화하여 측면을 구와 유사한 모양으로 부 풀릴 수있는 방법에 대한 부분을 이해하지 못합니다. 측면을 부 풀리려면 어떻게해야합니까?
CARVEN

1
@xEnOn, 정규화를 좀 더 설명하기 위해 내 대답을 편집했습니다. 문제는 정규화가 제가 설명하려고했던 프로세스에 대한 실제 기술 용어가 아니기 때문에 다른 곳에서는 더 많은 정보를 찾기가 어렵다는 것입니다. 미안합니다.
케빈

1
여기서 "정규화"과정을 설명하는 더 좋은 방법은 점이 구에 투영되는 것입니다. 또한 정규화 / 프로젝션이 마지막에 한 번만 적용되는지 (여기에서 제안 된 것처럼 보이는 모든 세분화 후) 또는 (재귀 적) 세분화 단계와 인터리브되는지 여부에 따라 결과가 다릅니다. 끝에서 한 번만 투영하면 초기 팔면체의 정점 근처에 클러스터 된 정점이 생성되는 반면 인터리브 된 세분화 및 투영은 정점 사이에 균일 한 거리를 생성합니다.
Tyler Streeter 2014 년

26

위도와 경도를 사용하여 구를 생성하는 인기있는 방법을 추가로 설명하겠습니다 (또 다른 방법 인 icospheres 는이 글을 쓰는 당시 가장 인기있는 답변에서 이미 설명되었습니다.)

구는 다음 매개 변수 방정식으로 표현할 수 있습니다.

F ( u , v ) = [cos (u) * sin (v) * r, cos (v) * r, sin (u) * sin (v) * r]

어디:

  • r 은 반경입니다.
  • u 는 0에서 2π까지의 경도입니다. 과
  • v 는 0에서 π까지의 위도입니다.

구를 생성하려면 고정 된 간격으로 파라 메트릭 함수를 평가해야합니다.

예를 들어, 16 개의 경도 라인을 생성하려면 u 축을 따라 π / 8 (2π / 16) 간격 으로 17 개의 그리드 라인이 있어야 합니다 (17 번째 라인이 둘러싸 임).

다음 의사 코드는 일정한 간격으로 파라 메트릭 함수를 평가하여 삼각형 메시를 생성합니다 (이는 구뿐만 아니라 모든 파라 메트릭 표면 함수에서 작동 함 ).

아래 의사 코드에서 UResolution 은 U 축 (여기서는 경도 선)을 따르는 격자 점의 수 이고 VResolution 은 V 축 (여기서는 위도 선)을 따르는 격자 점의 수입니다.

var startU=0
var startV=0
var endU=PI*2
var endV=PI
var stepU=(endU-startU)/UResolution // step size between U-points on the grid
var stepV=(endV-startV)/VResolution // step size between V-points on the grid
for(var i=0;i<UResolution;i++){ // U-points
 for(var j=0;j<VResolution;j++){ // V-points
 var u=i*stepU+startU
 var v=j*stepV+startV
 var un=(i+1==UResolution) ? EndU : (i+1)*stepU+startU
 var vn=(j+1==VResolution) ? EndV : (j+1)*stepV+startV
 // Find the four points of the grid
 // square by evaluating the parametric
 // surface function
 var p0=F(u, v)
 var p1=F(u, vn)
 var p2=F(un, v)
 var p3=F(un, vn)
 // NOTE: For spheres, the normal is just the normalized
 // version of each vertex point; this generally won't be the case for
 // other parametric surfaces.
 // Output the first triangle of this grid square
 triangle(p0, p2, p1)
 // Output the other triangle of this grid square
 triangle(p3, p1, p2)
 }
}

반대표는 약간 가혹 해 보입니다. 구의 파라 메트릭 방정식을 통해 이산 구성을 언급하는 유일한 답변 및 예제 중 하나입니다. 구는 극 근처에있을 때 축소되는 원의 스택으로 간주 될 수 있다는 점을 기반으로 이해하는 것이 더 쉬울 수도 있습니다.
Spacen Jasset 2015 년

2
안녕하세요, 저는 p0, p1, p2, p3의 각 값 중 두 번째 값이 u 또는 un이 아닌 v 또는 vn이어야한다는 점을 지적하고 싶었습니다.
nicole

9

샘플의 코드는 빠르게 설명됩니다. 함수를 살펴 봐야합니다 void drawSphere(double r, int lats, int longs).

void drawSphere(double r, int lats, int longs) {
    int i, j;
    for(i = 0; i <= lats; i++) {
        double lat0 = M_PI * (-0.5 + (double) (i - 1) / lats);
        double z0  = sin(lat0);
        double zr0 =  cos(lat0);

        double lat1 = M_PI * (-0.5 + (double) i / lats);
        double z1 = sin(lat1);
        double zr1 = cos(lat1);

        glBegin(GL_QUAD_STRIP);
        for(j = 0; j <= longs; j++) {
            double lng = 2 * M_PI * (double) (j - 1) / longs;
            double x = cos(lng);
            double y = sin(lng);

            glNormal3f(x * zr0, y * zr0, z0);
            glVertex3f(r * x * zr0, r * y * zr0, r * z0);
            glNormal3f(x * zr1, y * zr1, z1);
            glVertex3f(r * x * zr1, r * y * zr1, r * z1);
        }
        glEnd();
    }
}

매개 변수 lat는 구에 포함 할 수평선 수와 수직선 수를 정의합니다 lon. r구의 반경입니다.

이제 lat/에 대한 이중 반복이 있으며 lon간단한 삼각법을 사용하여 정점 좌표가 계산됩니다.

계산 된 정점이 이제를 사용하여 GPU glVertex...()GL_QUAD_STRIP전송됩니다. 즉, 이전에 전송 된 두 정점과 함께 쿼드를 형성하는 각 정점 두 개를 전송합니다.

이제 이해해야 할 것은 삼각 함수가 작동하는 방식 뿐이지 만 쉽게 알아낼 수있을 것 같습니다.


@PintoDoido : 어느 시점에서 죽었던 OP의 원래 링크에서 나왔습니다. 나는 링크를 Archive.org'd & 명확성을 위해이 답변으로 기능을 편집했습니다.
genpfault

2
반경이 없습니다.
tomasantunes 19

1
첫 번째 매개 변수 "double r"은 사용되지 않습니다.
ollydbg23

1
맞아요. 코드 샘플은 내 원래 답변의 일부가 아닙니다. @genpfault : 편집에 코드 샘플을 추가했습니다. 예를 고칠 수 있습니까?
Constantinius

1
덕분에 무리 :
Constantinius


1

여우처럼 교활하고 싶다면 GLU의 코드를 반 인치로 줄일 수 있습니다. MesaGL 소스 코드 (http://cgit.freedesktop.org/mesa/mesa/)를 확인하세요.


4
이 맥락에서 "반 인치"의 의미를 이해했지만, 콕니 운율 속어에 능숙하지 않은 다른 95 %의 독자를 위해 편집하고 싶을 것 같습니다 !
Flexo

1

'삼각형 스트립'을 사용하여 "극성"구를 그리는 방법의 예는 쌍의 드로잉 포인트로 구성됩니다.

const float PI = 3.141592f;
GLfloat x, y, z, alpha, beta; // Storage for coordinates and angles        
GLfloat radius = 60.0f;
int gradation = 20;

for (alpha = 0.0; alpha < GL_PI; alpha += PI/gradation)
{        
    glBegin(GL_TRIANGLE_STRIP);
    for (beta = 0.0; beta < 2.01*GL_PI; beta += PI/gradation)            
    {            
        x = radius*cos(beta)*sin(alpha);
        y = radius*sin(beta)*sin(alpha);
        z = radius*cos(alpha);
        glVertex3f(x, y, z);
        x = radius*cos(beta)*sin(alpha + PI/gradation);
        y = radius*sin(beta)*sin(alpha + PI/gradation);
        z = radius*cos(alpha + PI/gradation);            
        glVertex3f(x, y, z);            
    }        
    glEnd();
}

입력 된 첫 번째 점 (glVertex3f)은 다음과 같은 매개 변수 방정식이고 두 번째 점은 알파 각도의 단일 단계 (다음 평행선에서)만큼 이동합니다.


1

받아 들여진 대답으로 문제가 해결되지만 끝에는 약간의 오해가 있습니다. 정 십이 면체 는 모든면이 동일한 면적을 갖는 정다면체입니다. 그것은 Epcot의 경우 인 것 같습니다 (그런데, 이것은 십이 면체 가 아닙니다 ). @Kevin이 제안한 솔루션은 이러한 특성을 제공하지 않기 때문에 이러한 특성을 추가 할 수 있다고 생각했습니다.

모든 정점이 같은 구에 있고 모든면이 비슷한 면적 / 표면을 갖는 N면 다면체를 생성하는 좋은 방법 은 이십 면체로 시작하고 삼각형면을 반복적으로 세분화하고 정규화하는 것입니다 (수용된 답변에서 제 안됨). ). 예를 들어 12 면체는 실제로 잘린 20 면체 입니다.

정 이십 면체 는 20 개의면 (12 개의 꼭지점)을 가지며 3 개의 금색 직사각형으로 쉽게 구성 할 수 있습니다. 이것은 팔면체가 아닌 시작점으로하는 문제 일뿐입니다. 여기 에서 예를 찾을 수 있습니다 .

나는 이것이 약간 주제에서 벗어난 것을 알고 있지만 누군가이 특정 사례를 찾고 여기에 오면 도움이 될 것이라고 생각합니다.


0

한 가지 방법은 카메라를 향하는 쿼드를 만들고 구체처럼 보이는 것을 렌더링하는 정점 및 조각 셰이더를 작성하는 것입니다. 인터넷에서 찾을 수있는 원 / 구에 대한 방정식을 사용할 수 있습니다.

한 가지 좋은 점은 구의 실루엣이 모든 각도에서 동일하게 보인다는 것입니다. 그러나 구가 투시도의 중심에 있지 않으면 아마도 타원처럼 보일 것입니다. 이에 대한 방정식을 계산하고 조각 음영에 넣을 수 있습니다. 그런 다음 플레이어가 실제로 구 주위의 3D 공간에서 움직이는 경우 플레이어가 움직일 때 라이트 셰이딩을 변경해야합니다.

누군가가 이것을 시도했는지 또는 너무 비싸서 실용적이지 않은지에 대해 언급 할 수 있습니까?


그것은 평행 투영에서만 사실입니다. 원근 투영을 사용하는 경우 렌더링 출력에서 ​​구의 실루엣은 일반적으로 원 이 아닙니다 .
레토 Koradi

0

@Constantinius 답변의 Python 적응 :

lats = 10
longs = 10
r = 10

for i in range(lats):
    lat0 = pi * (-0.5 + i / lats)
    z0 = sin(lat0)
    zr0 = cos(lat0)

    lat1 = pi * (-0.5 + (i+1) / lats)
    z1 = sin(lat1)
    zr1 = cos(lat1)

    glBegin(GL_QUAD_STRIP)
    for j in range(longs+1):
        lng = 2 * pi * (j+1) / longs
        x = cos(lng)
        y = sin(lng)

        glNormal(x * zr0, y * zr0, z0)
        glVertex(r * x * zr0, r * y * zr0, r * z0)
        glNormal(x * zr1, y * zr1, z1)
        glVertex(r * x * zr1, r * y * zr1, r * z1)

    glEnd()

0
void draw_sphere()
{

    //              z
    //              |
    //               __
    //             /|          
    //              |           
    //              |           
    //              |    *      \
    //              | _ _| _ _ _ |    _y
    //             / \c  |n     /                    p3 --- p2
    //            /   \o |i                           |     |
    //           /     \s|s      z=sin(v)            p0 --- p1
    //         |/__              y=cos(v) *sin(u)
    //                           x=cos(v) *cos(u) 
    //       /
    //      x
    //


    double pi = 3.141592;
    double di =0.02;
    double dj =0.04;
    double du =di*2*pi;
    double dv =dj*pi;


    for (double i = 0; i < 1.0; i+=di)  //horizonal
    for (double j = 0; j < 1.0; j+=dj)  //vertical
    {       
        double u = i*2*pi;      //0     to  2pi
        double v = (j-0.5)*pi;  //-pi/2 to pi/2

        double  p[][3] = { 
            cos(v)     * cos(u)      ,cos(v)     * sin(u)       ,sin(v),
            cos(v)     * cos(u + du) ,cos(v)     * sin(u + du)  ,sin(v),
            cos(v + dv)* cos(u + du) ,cos(v + dv)* sin(u + du)  ,sin(v + dv),
            cos(v + dv)* cos(u)      ,cos(v + dv)* sin(u)       ,sin(v + dv)};

        //normal
        glNormal3d(cos(v+dv/2)*cos(u+du/2),cos(v+dv/2)*sin(u+du/2),sin(v+dv/2));

        glBegin(GL_POLYGON);
            glTexCoord2d(i,   j);    glVertex3dv(p[0]);
            glTexCoord2d(i+di,j);    glVertex3dv(p[1]);
            glTexCoord2d(i+di,j+dj); glVertex3dv(p[2]);
            glTexCoord2d(i,   j+dj); glVertex3dv(p[3]);
        glEnd();
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.