Vẽ Sphere trong OpenGL mà không sử dụng gluSphere ()?


81

Có bất kỳ hướng dẫn nào giải thích cách tôi có thể vẽ một hình cầu trong OpenGL mà không cần phải sử dụng gluSphere()không?

Nhiều hướng dẫn 3D cho OpenGL chỉ là trên các hình khối. Tôi đã tìm kiếm nhưng hầu hết các giải pháp để vẽ một hình cầu là sử dụng gluSphere(). Cũng có một trang web có mã để vẽ một hình cầu tại trang này nhưng nó không giải thích toán học đằng sau việc vẽ hình cầu. Tôi cũng có các phiên bản khác về cách vẽ hình cầu trong đa giác thay vì tứ trong liên kết đó. Nhưng một lần nữa, tôi không hiểu các quả cầu được vẽ bằng mã như thế nào. Tôi muốn có thể hình dung để có thể sửa đổi hình cầu nếu tôi cần.


3
tra cứu tọa độ cầu để giải toán (cụ thể là chuyển từ tọa độ cầu sang tọa độ Đề-các).
Ned Bingham

Câu trả lời:


270

Một cách bạn có thể làm là bắt đầu với một vật rắn platonic có các cạnh là hình tam giác - ví dụ như một hình bát diện . Sau đó, lấy từng tam giác và chia nhỏ một cách đệ quy thành các tam giác nhỏ hơn, như sau:

tam giác được vẽ đệ quy

Khi bạn đã có đủ số lượng điểm, bạn chuẩn hóa các vectơ của chúng để chúng cách tâm vật rắn một khoảng không đổi. Điều này làm cho các cạnh phình ra thành hình dạng giống hình cầu, với độ mịn ngày càng tăng khi bạn tăng số điểm.

Chuẩn hóa ở đây có nghĩa là di chuyển một điểm sao cho góc của nó so với một điểm khác là như nhau, nhưng khoảng cách giữa chúng là khác nhau. Đây là một ví dụ hai chiều.

nhập mô tả hình ảnh ở đây

A và B cách nhau 6 đơn vị. Nhưng giả sử chúng ta muốn tìm một điểm trên đoạn thẳng AB cách A. 12 đơn vị.

nhập mô tả hình ảnh ở đây

Chúng ta có thể nói rằng C là dạng chuẩn hóa của B đối với A, với khoảng cách 12. Chúng ta có thể thu được C với mã như sau:

#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

Nếu chúng ta thực hiện quá trình chuẩn hóa này trên nhiều điểm, tất cả đều thuộc cùng một điểm A và có cùng khoảng cách R, thì các điểm chuẩn hóa sẽ nằm trên cung của một đường tròn có tâm A và bán kính R.

đoạn đường phồng

Ở đây, các điểm đen bắt đầu trên một đường thẳng và "phình ra" thành một vòng cung.

Quá trình này có thể được mở rộng thành ba chiều, trong trường hợp đó bạn nhận được một hình cầu chứ không phải hình tròn. Chỉ cần thêm một thành phần dz vào hàm chuẩn hóa.

đa giác chuẩn hóa

hình bát diện phồng cấp 1 hình bát diện phồng cấp 3

Nếu bạn nhìn vào quả cầu ở Epcot , bạn có thể thấy kỹ thuật này đang hoạt động. đó là một khối đa diện với các mặt lồi ra để làm cho nó trông tròn hơn.


1
Tôi muốn xóa liên kết đến quả cầu epcot. Nó có thể khiến người mới bắt đầu bối rối vì mỗi tam giác lại được chia nhỏ thành ba tam giác cân (tương tự như phần đầu tiên của sqrt (3)-subdivision). Tôi chắc rằng bạn tìm thấy một ví dụ tốt hơn.
Christian Rau

Tôi có một triển khai tốt của điều này trên máy nhà của tôi. Tôi rất sẵn lòng chỉnh sửa một số ảnh chụp màn hình sau giờ làm việc.
Kevin

Cảm ơn vì ý tưởng. Nhưng tôi không hiểu phần làm thế nào bằng cách chuẩn hóa các vectơ, tôi có thể phình ra các cạnh thành một hình dạng giống hình cầu? Làm thế nào để tôi phồng hai bên ra?
Carven

1
@xEnOn, tôi đã chỉnh sửa câu trả lời của mình để giải thích thêm một chút về quá trình chuẩn hóa. Tôi nghĩ rằng vấn đề là chuẩn hóa không phải là thuật ngữ kỹ thuật thực tế cho quy trình mà tôi đang cố gắng giải thích, vì vậy sẽ rất khó để bạn tìm thêm thông tin về nó ở bất kỳ nơi nào khác. Xin lỗi vì điều đó.
Kevin

1
Có thể một cách tốt hơn để giải thích quá trình "chuẩn hóa" ở đây là các điểm đang được chiếu lên một hình cầu. Ngoài ra, hãy lưu ý rằng các kết quả khác nhau tùy thuộc vào việc chuẩn hóa / chiếu chỉ được áp dụng một lần vào cuối (sau tất cả chia nhỏ, dường như là những gì đang được đề xuất ở đây) hoặc xen kẽ với các bước chia nhỏ (đệ quy). Dường như phép chiếu chỉ một lần ở cuối sẽ tạo ra các đỉnh được nhóm lại gần các đỉnh của khối bát diện ban đầu, trong khi phép chiếu và chia nhỏ xen kẽ tạo ra khoảng cách đồng đều giữa các đỉnh.
Tyler Streeter

26

Tôi sẽ giải thích thêm về một cách phổ biến để tạo ra một hình cầu bằng cách sử dụng vĩ độ và kinh độ (một cách khác, các quả cầu , đã được giải thích trong câu trả lời phổ biến nhất tại thời điểm viết bài này.)

Một hình cầu có thể được biểu diễn bằng phương trình tham số sau:

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

Ở đâu:

  • r là bán kính;
  • u là kinh độ, nằm trong khoảng từ 0 đến 2π; và
  • v là vĩ độ, nằm trong khoảng từ 0 đến π.

Việc tạo hình cầu sau đó liên quan đến việc đánh giá hàm tham số ở những khoảng thời gian cố định.

Ví dụ, để tạo ra 16 đường kinh độ, sẽ có 17 đường lưới dọc theo trục u , với bước là π / 8 (2π / 16) (đường thứ 17 bao quanh).

Mã giả sau tạo lưới tam giác bằng cách đánh giá một hàm tham số theo các khoảng thời gian đều đặn (điều này hoạt động cho bất kỳ hàm bề mặt tham số nào , không chỉ hình cầu).

Trong mã giả bên dưới, Độ phân giải là số điểm lưới dọc theo trục U (ở đây, đường kinh độ) và Độ phân giải là số điểm lưới dọc theo trục V (ở đây, đường 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)
 }
}

Cuộc bỏ phiếu xuống có vẻ hơi khắc nghiệt. Đây là một trong những câu trả lời duy nhất có và ví dụ đề cập đến cấu tạo rời rạc thông qua phương trình tham số của hình cầu. Nó cũng có thể dễ hiểu hơn trên cơ sở rằng một hình cầu có thể được coi là một chồng các vòng tròn co lại khi chúng ở gần các cực.
Spacen Jasset

2
Xin chào, tôi chỉ muốn chỉ ra rằng giá trị thứ hai của mỗi giá trị p0, p1, p2, p3 phải là v hoặc vn, trái ngược với u hoặc un.
nicole

9

Mã trong mẫu được giải thích nhanh chóng. Bạn nên xem xét hàm 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();
    }
}

Các tham số latxác định bao nhiêu đường ngang bạn muốn có trong hình cầu của mình và lonbao nhiêu đường thẳng đứng. rlà bán kính của hình cầu của bạn.

Bây giờ có một phép lặp kép trên lat/ lonvà tọa độ đỉnh được tính bằng cách sử dụng lượng giác đơn giản.

Các đỉnh được tính toán hiện được gửi đến GPU của bạn bằng cách sử dụng glVertex...()dưới dạng a GL_QUAD_STRIP, có nghĩa là bạn đang gửi mỗi hai đỉnh tạo thành một tứ với hai đỉnh đã được gửi trước đó.

Tất cả những gì bạn phải hiểu bây giờ là cách hoạt động của các hàm lượng giác, nhưng tôi đoán bạn có thể dễ dàng tìm ra nó.


@PintoDoido: Đó là từ liên kết ban đầu của OP đã chết tại một số điểm; Tôi Archive.org đã liên kết và chỉnh sửa chức năng thành câu trả lời này cho rõ ràng.
genpfault

2
Bán kính bị thiếu.
tomasantunes

1
Tham số đầu tiên "double r" không được sử dụng.
ollydbg23

1
Đúng rồi. Mẫu mã không phải là một phần của câu trả lời ban đầu của tôi. @genpfault: bạn đã thêm mẫu mã trong một chỉnh sửa. Bạn có thể vui lòng sửa chữa ví dụ?
Constantinius,

1
Cảm ơn rất nhiều :)
Constantinius


1

Nếu bạn muốn trở nên ranh mãnh như một con cáo, bạn có thể nửa inch mã từ GLU. Kiểm tra mã nguồn MesaGL (http://cgit.freedesktop.org/mesa/mesa/).


4
Trong khi tôi hiểu ý nghĩa của từ "nửa inch" trong ngữ cảnh này, tôi nghĩ bạn có thể muốn chỉnh sửa nó cho 95% độc giả khác không thông thạo tiếng lóng có vần điệu cockney !
Flexo

1

Ví dụ của tôi về cách sử dụng 'dải tam giác' để vẽ một hình cầu "cực", nó bao gồm các điểm vẽ theo cặp:

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();
}

Điểm đầu tiên được nhập (glVertex3f) theo phương trình tham số và điểm thứ hai được dịch chuyển theo một bước của góc alpha (từ song song tiếp theo).


1

Mặc dù câu trả lời được chấp nhận giải quyết được câu hỏi, nhưng cuối cùng vẫn có một chút hiểu lầm. Khối đa diện là (hoặc có thể là) khối đa diện đều trong đó tất cả các mặt đều có cùng diện tích. Đó dường như là trường hợp của Epcot (nhân tiện, không phải là một khối tứ diện nào cả). Vì giải pháp do @Kevin đề xuất không cung cấp đặc điểm này, tôi nghĩ tôi có thể thêm một cách tiếp cận.

Một cách hay để tạo ra một hình đa diện N mặt trong đó tất cả các đỉnh nằm trong cùng một hình cầu tất cả các mặt của nó có diện tích / bề mặt tương tự nhau là bắt đầu bằng một khối icosahedron và phép chia con lặp đi lặp lại và chuẩn hóa các mặt tam giác của nó (như được đề xuất trong câu trả lời được chấp nhận ). Dodecahedrons, ví dụ, đang thực sự cắt ngắn icosahedrons .

Hình tứ diện thông thường có 20 mặt (12 đỉnh) và có thể dễ dàng xây dựng từ 3 hình chữ nhật vàng; vấn đề chỉ là có điểm này như một điểm bắt đầu thay vì một khối bát diện. Bạn có thể tìm thấy một ví dụ ở đây .

Tôi biết điều này hơi lạc đề nhưng tôi tin rằng nó có thể hữu ích nếu ai đó ở đây tìm kiếm trường hợp cụ thể này.


0

Một cách là tạo một hình tứ giác đối diện với máy ảnh và viết một công cụ đổ bóng đỉnh và phân mảnh để hiển thị thứ gì đó trông giống như một hình cầu. Bạn có thể sử dụng các phương trình cho một hình tròn / hình cầu mà bạn có thể tìm thấy trên internet.

Một điều thú vị là hình bóng của một quả cầu trông giống nhau từ mọi góc độ. Tuy nhiên, nếu hình cầu không nằm ở trung tâm của một hình chiếu phối cảnh thì có lẽ nó sẽ giống hình elip hơn. Bạn có thể tính ra các phương trình cho điều này và đặt chúng vào phần tô bóng phân mảnh. Sau đó, bóng mờ cần thay đổi khi người chơi di chuyển, nếu bạn thực sự có người chơi di chuyển trong không gian 3D xung quanh hình cầu.

Bất cứ ai có thể nhận xét nếu họ đã thử điều này hoặc nếu nó sẽ là quá đắt để thực tế?


Điều đó chỉ đúng dưới phép chiếu song song. Nếu bạn sử dụng phép chiếu phối cảnh, hình bóng của hình cầu trong đầu ra kết xuất thường không phải là hình tròn.
Reto Koradi

0

Thích ứng Python của câu trả lời @Constantinius:

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();
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.