Czy są dostępne samouczki, które wyjaśniają, jak narysować kulę w OpenGL bez konieczności używania gluSphere()
?
Wiele samouczków 3D dla OpenGL dotyczy tylko kostek. Szukałem, ale większość rozwiązań do rysowania kuli ma zastosowanie gluSphere()
. Istnieje również witryna, która ma kod do rysowania kuli w tej witrynie, ale nie wyjaśnia matematyki związanej z rysowaniem kuli. Mam również inne wersje tego, jak narysować kulę w wielokącie zamiast quadów w tym łączu. Ale znowu nie rozumiem, jak kule są rysowane za pomocą kodu. Chcę mieć możliwość wizualizacji, abym mógł zmodyfikować kulę, jeśli zajdzie taka potrzeba.
Odpowiedzi:
Jednym ze sposobów jest rozpoczęcie od bryły platońskiej o trójkątnych bokach - na przykład ośmiościanu . Następnie weź każdy trójkąt i rekurencyjnie podziel go na mniejsze trójkąty, na przykład:
Gdy masz wystarczającą liczbę punktów, normalizujesz ich wektory, tak aby wszystkie znajdowały się w stałej odległości od środka bryły. Powoduje to, że boki wybrzuszają się do kształtu przypominającego kulę, z rosnącą gładkością w miarę zwiększania liczby punktów.
Normalizacja oznacza tutaj przesunięcie punktu tak, aby jego kąt w stosunku do innego punktu był taki sam, ale odległość między nimi była inna. Oto dwuwymiarowy przykład.
A i B są oddalone od siebie o 6 jednostek. Ale przypuśćmy, że chcemy znaleźć punkt na linii AB, który jest oddalony o 12 jednostek od punktu A.
Możemy powiedzieć, że C jest znormalizowaną postacią B w odniesieniu do A, z odległością 12. Możemy otrzymać C za pomocą następującego kodu:
#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
Jeśli wykonamy ten proces normalizacji na wielu punktach, wszystkie w odniesieniu do tego samego punktu A i przy tej samej odległości R, wówczas znormalizowane punkty będą leżeć na łuku koła o środku A i promieniu R.
Tutaj czarne punkty zaczynają się na linii i „wybrzuszają” w łuk.
Ten proces można rozszerzyć do trzech wymiarów, w którym to przypadku otrzymujesz kulę zamiast koła. Po prostu dodaj składnik dz do funkcji normalizacji.
Jeśli spojrzysz na kulę w Epcot , możesz w pewnym sensie zobaczyć, jak działa ta technika. to dwunastościan z wybrzuszonymi twarzami, aby wyglądał bardziej okrągło.
źródło
Wyjaśnię dalej popularny sposób generowania kuli za pomocą szerokości i długości geograficznej (inny sposób, icosfery , został już wyjaśniony w najpopularniejszej odpowiedzi w czasie tego pisania).
Kulę można wyrazić za pomocą następującego równania parametrycznego:
F ( u , v ) = [cos (u) * sin (v) * r, cos (v) * r, sin (u) * sin (v) * r]
Gdzie:
Generowanie sfery obejmuje następnie ocenę funkcji parametrycznej w ustalonych odstępach czasu.
Na przykład, aby wygenerować 16 linii długości geograficznej, wzdłuż osi u będzie 17 linii siatki z krokiem π / 8 (2π / 16) (17 linia jest zawijana).
Poniższy pseudokod generuje siatkę trójkątów, oceniając funkcję parametryczną w regularnych odstępach czasu (działa to dla każdej parametrycznej funkcji powierzchni, nie tylko dla sfer).
W poniższym pseudokodzie UResolution to liczba punktów siatki wzdłuż osi U (tutaj linie długości geograficznej), a VRrozwiązanie to liczba punktów siatki wzdłuż osi V (tutaj linie szerokości geograficznej)
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) } }
źródło
Kod w przykładzie jest szybko wyjaśniony. Powinieneś przyjrzeć się funkcji
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(); } }
Parametry
lat
określają, ile poziomych linii chcesz mieć w swojej sferze ilon
ile linii pionowych.r
to promień twojej kuli.Teraz jest podwójna iteracja nad
lat
/lon
i współrzędne wierzchołków są obliczane za pomocą prostej trygonometrii.Obliczone wierzchołki są teraz wysyłane do twojego GPU
glVertex...()
jako plikGL_QUAD_STRIP
, co oznacza, że wysyłasz każde dwa wierzchołki, które tworzą quad z poprzednimi dwoma wysłanymi.Teraz musisz tylko zrozumieć, jak działają funkcje trygonometryczne, ale myślę, że możesz to łatwo zrozumieć.
źródło
Zobacz czerwoną książkę OpenGL: http://www.glprogramming.com/red/chapter02.html#name8 Rozwiązuje problem przez podział na wielokąty.
źródło
Jeśli chcesz być przebiegły jak lis, możesz o pół cala kod z GLU. Sprawdź kod źródłowy MesaGL (http://cgit.freedesktop.org/mesa/mesa/).
źródło
Mój przykład użycia 'paska trójkąta' do narysowania sfery „biegunowej” polega na rysowaniu punktów parami:
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(); }
Pierwszy wprowadzony punkt (glVertex3f) jest następujący po równaniu parametrycznym, a drugi jest przesuwany o jeden krok kąta alfa (od następnego równoległego).
źródło
Chociaż zaakceptowana odpowiedź rozwiązuje pytanie, na końcu jest małe nieporozumienie. Dwunastościany są (lub mogą być) regularnymi wielościanami, w których wszystkie ściany mają ten sam obszar. Wydaje się, że tak jest w przypadku Epcot (który, nawiasem mówiąc, wcale nie jest dwunastościanem ). Ponieważ rozwiązanie zaproponowane przez @Kevin nie zapewnia tej cechy, pomyślałem, że mógłbym dodać podejście, które to robi.
Dobrym sposobem na wygenerowanie wielościanu o powierzchni N, w której wszystkie wierzchołki leżą w tej samej sferze, a wszystkie jej ściany mają podobne pole / powierzchnię, jest rozpoczęcie od dwudziestościanu i iteracyjne dzielenie i normalizowanie jego trójkątnych ścian (zgodnie z przyjętą odpowiedzią ). Na przykład dwunastościany są w rzeczywistości ściętymi dwudziestościanami .
Regularne dwudziestościany mają 20 ścian (12 wierzchołków) i można je łatwo zbudować z 3 złotych prostokątów; chodzi tylko o to, aby to było punktem wyjścia zamiast ośmiościanu. Możesz znaleźć przykład tutaj .
Wiem, że to trochę nie na temat, ale myślę, że może pomóc, jeśli ktoś przyjdzie tutaj, szukając tego konkretnego przypadku.
źródło
Jednym ze sposobów jest zrobienie quada skierowanego w stronę kamery i napisanie Vertex and Fragment Shader, który renderuje coś, co wygląda jak sfera. Możesz użyć równań dla koła / kuli, które możesz znaleźć w Internecie.
Jedną fajną rzeczą jest to, że sylwetka kuli wygląda tak samo pod każdym kątem. Jeśli jednak kula nie znajduje się w środku widoku perspektywicznego, może wyglądać bardziej jak elipsa. Możesz opracować równania do tego i umieścić je w cieniowaniu fragmentów. Następnie cieniowanie światła musi się zmieniać wraz z ruchem gracza, jeśli rzeczywiście gracz porusza się w przestrzeni 3D wokół kuli.
Czy ktoś może skomentować, czy tego próbował, czy też byłoby to zbyt drogie, aby było praktyczne?
źródło
Adaptacja Pythona odpowiedzi @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()
źródło
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(); } }
źródło