2015-03-11 16 views
5

Sto provando a creare una sfera 3D da un gruppo di triangoli con Haskell/GLUT. Funziona molto bene: quella verde è la "mia" sfera, quella rossa è fatta con renderObject Sphere di GLUT '. E posso vedere che la "mia" sfera è davvero 3D quando sposto la cinepresa, quindi va bene.Capire l'illuminazione in OpenGL

Quindi perché la GLUT ha una buona illuminazione e la mia no? (Sono un principiante e non so davvero che cosa sto facendo qui di seguito in InitGL, copiato quella roba dalla confezione a forma di parallelepipedo di Hackage ...)

Nice and not-so-nice Spheres

Ecco il codice:

module Main where 

import Graphics.UI.GLUT 

main :: IO() 
main = do 
    initGL 
    displayCallback $= render 
    mainLoop 

initGL :: IO() 
initGL = do 
    getArgsAndInitialize 
    initialDisplayMode $= [DoubleBuffered] 
    createWindow "Chip!" 
    initialDisplayMode $= [ WithDepthBuffer ] 
    depthFunc   $= Just Less 
    clearColor   $= Color4 0 0 0 0 
    light (Light 0) $= Enabled 
    lighting   $= Enabled 
    lightModelAmbient $= Color4 0.5 0.5 0.5 1 
    diffuse (Light 0) $= Color4 1 1 1 1 
    blend    $= Enabled 
    blendFunc   $= (SrcAlpha, OneMinusSrcAlpha) 
    colorMaterial  $= Just (FrontAndBack, AmbientAndDiffuse) 
    reshapeCallback $= Just resizeScene 
    return() 

render :: DisplayCallback 
render = do 
    clear [ ColorBuffer, DepthBuffer ] 

    loadIdentity 

    color $ Color3 (1 :: GLdouble) 1 1 
    position (Light 0) $= Vertex4 0 50 (50) 1 

    preservingMatrix $ do 
     translate $ Vector3 ((-0.5) :: GLfloat) (-0.5) (-5) 
     color green 
     ball 12 8 0.03 

    preservingMatrix $ do 
     translate $ Vector3 (0.5 :: GLfloat) 0.5 (-5) 
     color red 
     renderObject Solid (Sphere' 0.25 20 20) 

    flush 
    swapBuffers 
    where green = Color4 0.8 1.0 0.7 0.9 :: Color4 GLdouble 
      red = Color4 1.0 0.7 0.8 1.0 :: Color4 GLdouble 

vertex3f :: (GLfloat, GLfloat, GLfloat) -> IO() 
vertex3f (x, y, z) = vertex $ Vertex3 x y z 

upperInnerCircle :: Int -> [(GLfloat, GLfloat)] 
upperInnerCircle numSegs = 
    concat [[(0,0) 
      ,(cos a, sqrt(1-(cos a)*(cos a))) 
      ,(cos b, sqrt(1-(cos b)*(cos b)))] 
       | (a,b)<-as ] 
    where 
     seg'=pi/(fromIntegral numSegs) 
     as = [(fromIntegral n * seg', fromIntegral (n+1) * seg') | n<-[0..numSegs-1]] 

lowerInnerCircle :: Int -> [(GLfloat, GLfloat)] 
lowerInnerCircle numSegs = 
    map (\(x,y) -> (x,-y)) $ upperInnerCircle numSegs 

innerCircle :: Int -> [(GLfloat, GLfloat)] 
innerCircle numSegs = upperInnerCircle numSegs ++ (lowerInnerCircle numSegs) 

upperOutSegment :: Int -> Int -> Int -> [(GLfloat, GLfloat)] 
upperOutSegment numSegs ring seg = 
    [x,y,u, u,v,y] 
    where 
     seg'=pi/(fromIntegral numSegs) 
     (a, b) = (fromIntegral seg * seg', fromIntegral (seg+1) * seg') 
     x = (fromIntegral ring * cos a, fromIntegral ring * sqrt(1-(cos a)*(cos a))) 
     y = (fromIntegral ring * cos b, fromIntegral ring * sqrt(1-(cos b)*(cos b))) 
     u = (fromIntegral (ring+1) * cos a, fromIntegral (ring+1) * sqrt(1-(cos a)*(cos a))) 
     v = (fromIntegral (ring+1) * cos b, fromIntegral (ring+1) * sqrt(1-(cos b)*(cos b))) 

lowerOutSegment :: Int -> Int -> Int -> [(GLfloat, GLfloat)] 
lowerOutSegment numSegs ring seg = 
    map (\(x,y) -> (x,-y)) $ upperOutSegment numSegs ring seg 

outSegment :: Int -> Int -> Int -> [(GLfloat, GLfloat)] 
outSegment numSegs ring seg = upperOutSegment numSegs ring seg ++ (lowerOutSegment numSegs ring seg) 

outerRing :: Int -> Int -> [(GLfloat, GLfloat)] 
outerRing numSegs ring = 
    concat [outSegment numSegs ring n | n<-[0..numSegs-1]] 

ball numSegs numRings factor = 
    let ips = innerCircle numSegs 
     ops = concat [outerRing numSegs i | i<-[1..numRings]] 
     height dir ps = 
      map (\(x,y) -> 
        let dist = sqrt(x*x+y*y)/(fromIntegral (numRings+1)) 
         height' = sqrt(1.001-dist*dist)*factor*(fromIntegral (numRings+1)) 
        in (x*factor,y*factor,dir*height')) $ ps 
     ups = height 1 $ ips ++ ops 
     lps = height (-1) $ ips ++ ops 
    in renderPrimitive Triangles $ mapM_ vertex3f (ups++lps) 


resizeScene :: Size -> IO() 
resizeScene (Size w 0) = resizeScene (Size w 1) -- prevent divide by zero 
resizeScene [email protected](Size width height) = do 
    viewport $= (Position 0 0, s) 
    matrixMode $= Projection 
    loadIdentity 
    perspective 45 (w2/h2) 1 1000 
    matrixMode $= Modelview 0 
    flush 
where 
    w2 = half width 
    h2 = half height 
    half z = realToFrac z/2 

EDIT: Funziona ora, grazie a Spektre!

Ecco la foto:

Nice!

Ed ecco il codice:

module Main where 

import Graphics.UI.GLUT 

main :: IO() 
main = do 
    initGL 
    displayCallback $= render 
    mainLoop 

initGL :: IO() 
initGL = do 
    getArgsAndInitialize 
    initialDisplayMode $= [DoubleBuffered] 
    createWindow "Chip!" 
    initialDisplayMode $= [ WithDepthBuffer ] 
    depthFunc   $= Just Less 
    clearColor   $= Color4 0 0 0 0 
    light (Light 0) $= Enabled 
    lighting   $= Enabled 
    lightModelAmbient $= Color4 0.5 0.5 0.5 1 
    diffuse (Light 0) $= Color4 1 1 1 1 
    blend    $= Enabled 
    blendFunc   $= (SrcAlpha, OneMinusSrcAlpha) 
    colorMaterial  $= Just (FrontAndBack, AmbientAndDiffuse) 
    reshapeCallback $= Just resizeScene 
    return() 

render :: DisplayCallback 
render = do 
    clear [ ColorBuffer, DepthBuffer ] 

    loadIdentity 

    color $ Color3 (1 :: GLdouble) 1 1 
    position (Light 0) $= Vertex4 0 50 (50) 1 

    preservingMatrix $ do 
     translate $ Vector3 ((-0.5) :: GLfloat) (-0.5) (-5) 
     color green 
     ball 12 8 0.03 

    preservingMatrix $ do 
     translate $ Vector3 (0.5 :: GLfloat) 0.5 (-5) 
     color red 
     renderObject Solid (Sphere' 0.25 20 20) 

    flush 
    swapBuffers 
    where green = Color4 0.8 1.0 0.7 0.9 :: Color4 GLdouble 
      red = Color4 1.0 0.7 0.8 1.0 :: Color4 GLdouble 

pushTriangle :: ((GLfloat, GLfloat, GLfloat) 
       ,(GLfloat, GLfloat, GLfloat) 
       ,(GLfloat, GLfloat, GLfloat)) -> 
       IO() 
pushTriangle (p0, p1, p2) = do 
    let (_,d0,_)=p0 
    let (_,d1,_)=p1 
    let (_,d2,_)=p2 

    --if it points upwards, reverse normal 
    let d=if d0+d1+d2>0 then (-1) else 1 
    let n = cross (minus p1 p0) (minus p2 p1) 
    let nL = 1/lenVec n 
    let (n1, n2, n3) = scaleVec n (nL*d) 
    normal $ Normal3 n1 n2 n3 

    vertex3f p0 
    vertex3f p1 
    vertex3f p2 

vertex3f :: (GLfloat, GLfloat, GLfloat) -> IO() 
vertex3f (x, y, z) = 
    vertex $ Vertex3 x y z 

lenVec (a1,a2,a3) = sqrt $ a1*a1 + a2*a2 + a3*a3 

scaleVec (a1,a2,a3) x = (a1*x,a2*x,a3*x) 

cross (a1,a2,a3) (b1,b2,b3) = 
    (a2*b3-a3*b2 
    ,a3*b1-a1*b3 
    ,a1*b2-a2*b1) 

minus (a1,a2,a3) (b1,b2,b3) = 
    (a1-b1, a2-b2, a3-b3) 

upperInnerCircle :: Int -> [(GLfloat, GLfloat)] 
upperInnerCircle numSegs = 
    concat [[(cos a, sqrt(1-(cos a)*(cos a))) 
      ,(0,0) 
      ,(cos b, sqrt(1-(cos b)*(cos b)))] 
       | (a,b)<-as ] 
    where 
     seg'=pi/(fromIntegral numSegs) 
     as = [(fromIntegral n * seg', fromIntegral (n+1) * seg') | n<-[0..numSegs-1]] 

lowerInnerCircle :: Int -> [(GLfloat, GLfloat)] 
lowerInnerCircle numSegs = 
    map (\(x,y) -> (x,-y)) $ upperInnerCircle numSegs 

innerCircle :: Int -> [(GLfloat, GLfloat)] 
innerCircle numSegs = upperInnerCircle numSegs ++ (lowerInnerCircle numSegs) 

upperOutSegment :: Int -> Int -> Int -> [(GLfloat, GLfloat)] 
upperOutSegment numSegs ring seg = 
    [x,y,u, v,u,y] 
    where 
     seg'=pi/(fromIntegral numSegs) 
     (a, b) = (fromIntegral seg * seg', fromIntegral (seg+1) * seg') 
     x = (fromIntegral ring * cos a, fromIntegral ring * sqrt(1-(cos a)*(cos a))) 
     y = (fromIntegral ring * cos b, fromIntegral ring * sqrt(1-(cos b)*(cos b))) 
     u = (fromIntegral (ring+1) * cos a, fromIntegral (ring+1) * sqrt(1-(cos a)*(cos a))) 
     v = (fromIntegral (ring+1) * cos b, fromIntegral (ring+1) * sqrt(1-(cos b)*(cos b))) 

lowerOutSegment :: Int -> Int -> Int -> [(GLfloat, GLfloat)] 
lowerOutSegment numSegs ring seg = 
    map (\(x,y) -> (x,-y)) $ upperOutSegment numSegs ring seg 

outSegment :: Int -> Int -> Int -> [(GLfloat, GLfloat)] 
outSegment numSegs ring seg = upperOutSegment numSegs ring seg ++ (lowerOutSegment numSegs ring seg) 

outerRing :: Int -> Int -> [(GLfloat, GLfloat)] 
outerRing numSegs ring = 
    concat [outSegment numSegs ring n | n<-[0..numSegs-1]] 

ball numSegs numRings factor = 
    let ips = innerCircle numSegs 
     ops = concat [outerRing numSegs i | i<-[1..numRings]] 
     height dir ps = 
      map (\(x,y) -> 
        let dist = sqrt(x*x+y*y)/(fromIntegral (numRings+1)) 
         height' = sqrt(1.001-dist*dist)*factor*(fromIntegral (numRings+1)) 
        in (x*factor,y*factor,dir*height')) $ ps 
     ups = height 1 $ ips ++ ops 
     lps = height (-1) $ ips ++ ops 
    in renderPrimitive Triangles $ mapM_ pushTriangle (toTriples (ups++lps)) 

toTriples :: [a] -> [(a,a,a)] 
toTriples [] = [] 
toTriples (a:b:c:rest) = (a,b,c):toTriples rest 

resizeScene :: Size -> IO() 
resizeScene (Size w 0) = resizeScene (Size w 1) -- prevent divide by zero 
resizeScene [email protected](Size width height) = do 
    viewport $= (Position 0 0, s) 
    matrixMode $= Projection 
    loadIdentity 
    perspective 45 (w2/h2) 1 1000 
    matrixMode $= Modelview 0 
    flush 
where 
    w2 = half width 
    h2 = half height 
    half z = realToFrac z/2 
+6

non si fornisce alcun normali alla superficie. – derhass

+1

@derhass Farò una supposizione selvaggia e suppongo che non saprà cosa significa. – Cubic

+1

Indovinato correttamente, ma so che ho qualcosa da google :-) Grazie – martingw

risposta

4
  1. normali alla superficie sono fondamentali per le equazioni di illuminazione

    normale alla superficie è v ettore perpendicolare alla superficie. Perché il triangolo è calcolato dal prodotto incrociato di uno qualsiasi dei suoi 2 vettori di vertici, quindi se i punti del triangolo sono p0,p1,p2 allora normale è n=cross(p1-p0,p2-p1) o qualsiasi altra combinazione.

    Le normali indicano in che modo pixel/faccia/poligono diventano di solito il prodotto di punti con direzione della luce viene calcolato dal motore di rendering che fornisce uno cos(angle_between light and surface normal). Questo numero è la scala della quantità di luce che colpisce la superficie quando moltiplicata con la forza della sorgente luminosa hai ottenuto il colore chiaro ... con combinazione di renderizzazione del colore della superficie ottenere il colore del pixel ci sono molti modelli di luce questo era molto semplice (ombreggiatura normale).

    Per rendere il prodotto scalare lavoro normale dovrebbe essere versore così dividerlo per la lunghezza n=n/|n|

    Qui piccolo esempio di normali

    example

    Per sfera normale è facile normale n per qualsiasi punto p è n=(p-center)/radius

  2. Se la norma non corrisponde alla superficie

    , allora è possibile eseguire effetti di luce come bordi taglienti visivamente lisci.per esempio come vedere qui:

    anche il contrario può essere raggiunto (maglia liscia ma spigolo rendering)

  3. interfaccia OpenGL

    vecchio gl stile utilizza qualcosa come glNormal3f(nx,ny,nz); Il VBO/VAO/array conosce anche le normali. Nel nuovo stile glNormal è depreceated come la maggior parte dei parametri, quindi è necessario associarlo al vostro layout personalizzato sul proprio

  4. direzione normale

    qualsiasi superficie ha 2 possibili direzione perpendicolare ad essa normale. Di solito viene usato quello che punta verso l'esterno dalla maglia. A volte per le curve 3D viene utilizzato materiale a doppia faccia, il che significa che il prodotto con punti viene gestito come valore abs, quindi non importa in quale modo la normale punta. Senza questo lato opposto della superficie sarà sempre buio

    Quindi, se avete normali e senza illuminazione è visibile quindi provare a negare normali