Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Fog culling

Compatible Director. Cliquer pour en savoir plus sur les compatibilités.Par retro2, le 21 août 2006

Afficher un grand environnement pose un problème: il faut pouvoir, au fur et à mesure que la caméra se déplace, masquer les objets qui ne sont pas visibles.

Pour cela on commence par découper l'espace en objets plus petits qui seront cachés ou affichés. Cette ensemble de technique s'appelle “culling”.

Shockwave fait automatiquement un “frustum culling”, il ne rend pas les models en dehors du champ de vision de la caméra, en revanche il continue à rendre les models cachés par d'autres faces.

Il existe diverses techniques pour dissimuler les models cachés (“occlusion culling”) et elles s'appuient sur des structures de données complexes (portails, antiportails, etc). Ces calculs étant extrêmement complexes et longs à mettre en oeuvre, en général la plupart des studios de développement de jeux vidéo choisissent de se baser sur un format de map standard qui règle ces calculs. L'approche la plus connue en shockwave consiste à convertir une map du standard id_tech (moteur de quake) en données exploitables par shockwave.

Il existe cependant une technique beaucoup plus facile pour optimiser l'affichage, qu'on peut appeler “fog culling”: on se contente de cacher les models trop éloignés de la caméra et on cache le tout avec un brouillard.

Voici un exemple en 80 lignes de script seulement.

Note: Ici on utilise pas de scènegraph particulier pour diviser l'espace, on lance un bête rayon vertical pour localiser le model sous la camera, et on affiche les models les plus proches. Pour un système de rangement plus propre vous pouvez stocker vos models dans une grille (2d ou octree) et localiser la camera dans les cases.

Exercice

Modélisez un très grand décor puis découpez-le en plusieurs morceaux, peu importe comment vous les nommez mais surtout évitez les doublons. Ne découpez pas trop fin pour ne pas afficher trop de models à la fois (essayez d'avoir environ 8 models dans le champ de vision).

Positionnez la camera à son point de départ

Exportez en w3d

Copiez-collez y ce code en modulant la variable Tolerance (ici à 80%), qui est proportionnelle à la profondeur du champ de vision.

 
property rigidList,cam,mem,sectors,lights,actors,cpt,i,tolerance,currentSector
 
on beginsprite me
 
  mem=member("3d")
  mem.resetWorld()
 
  cam=sprite(1).camera
 
  cam.transform.rotation.x=90
  cam.transform.rotation.y=0  
 
 
  cam.fog.enabled=true
  cam.fog.near=2000  
  cam.fog.far=2000
 
  sectors=[:]
 
  rigidList=[:]
 
  --on range les models dans des tableaux à part: secteurs, etc...
  i=0  
  repeat while i < mem.model.count
    i=i+1
    checkModel(me,i)
  end repeat
 
 
  --on gere les proximités des secteurs adjacents:
  i=0
  tolerance=0.80
  cpt=#checkAdjacent
 
 
end
 
 
 
--tableaux
 
on checkModel me,mo
  checkSector(me,mo)
end
 
on checkSector me,mo
  sectors.addprop(mem.model(mo).name,mem.model(mo))
  mem.model(mo).userData.addprop(#active,"none")
end
 
on checkLight me,li
 
end
 
 
 
--proximités
 
on checkAdjacent me  
 
  --pour chaque secteur on recenses les secteurs adjecents
 
  i=i+1
  s=sectors[i]
 
  nom=s.name
  ud=s.userData
 
  ud.addprop(#adj,[])
 
  b1=s.boundingSphere
 
 
  r1=b1[2]
  v1=b1[1]
 
  j=0
  repeat while j<sectors.count
    j=j+1    
    if j<>i then            
      b2=sectors[j].boundingSphere      
      r2=b2[2]
      v2=b2[1]      
      d=(v1-v2).magnitude      
      if d-(r1+r2)*tolerance<0 then
        ud[#adj].add(sectors[j].name)
      end if      
    end if
  end repeat
 
  put s&" "&ud
 
  if i>=sectors.count then
    cpt=void
    proximityDone(me)
  end if
end
 
 
on proximityDone me
 
  put "proximités OK"
 
 
  --on choppe le secteur courant
 
  checkCurrentSector()
 
  --on efface tous les models
 
  i=0  
  repeat while i < mem.model.count
    i=i+1
    mem.model(i).removeFromWorld()
  end repeat
 
 
  --on charge la bonne zone
 
  loadCurrentZone()
 
end
 
 
on checkCurrentSector
 
  --on lance un rayon sur le secteur principal
  options=[#maxNumberOfModels: 1, #levelOfDetail: #detailed, #maxDistance: 100000]
  info=mem.modelsunderray(cam.transform.position,vector(0,0,-1), options)
 
  currentSector=info[1][#model].name
 
  put "currentSector: "& currentSector
 
end
 
 
 
on loadCurrentZone me
 
  --load current sector
  loadSector(me,currentSector)
 
 
  --load proxies
  adj=mem.model(currentSector).userData.adj
 
  repeat with k in adj
    loadSector(me,k)
  end repeat
 
 
  put rigidList
 
  cpt=#gameLoop
 
end
 
 
 
on loadSector me,s  
  s=mem.model(s)
  ud=s.userData
 
  if ud.active="none" then
    ud.active="rigid"
    s.addtoworld()
    rigidList.addprop(s.name,s)
  end if 
end
 
 
on unLoadSector me,s
  s=mem.model(s)
  ud=s.userData
 
  if ud.active="rigid" then
    ud.active="none"
    s.removeFromWorld()
    rigidList.deleteprop(s.name)
  end if 
end
 
 
on updateTerrain me,ns
 
  --unload last proxies
  adj=mem.model(currentSector).userData.adj
 
  repeat with k in adj
    if k<>ns then
      unLoadSector(me,k)
    end if
  end repeat 
 
 
  currentSector=ns
 
 
  --load new proxies
  adj=mem.model(currentSector).userData.adj
 
  repeat with k in adj
    loadSector(me,k)
  end repeat  
 
 
  put rigidList
 
 
end
 
 
 
 
on gameLoop me
 
 
  options=[#maxNumberOfModels: 1, #levelOfDetail: #detailed, #maxDistance: 100000, #modelList:rigidList]  
 
 
 
  if keypressed(123) then
    cam.rotate(0,5,0)
  else if keypressed(124) then
    cam.rotate(0,-5,0)    
  end if
 
  if keypressed(126) then
 
    cam.translate(0,0,-10) 
 
    --on lance un rayon sur le mur d'en face
    info=mem.modelsunderray(cam.transform.position,-cam.transform.zAxis, options)    
    if info <> [] then
      dd=info[1].distance-30
      if dd<0 then
        cam.translate(-dd*info[1].isectNormal,#world)
      end if
    end if
 
    --on lance un rayon sur le mur de gauche
    info=mem.modelsunderray(cam.transform.position,-cam.transform.xAxis, options)    
    if info <> [] then
      dd=info[1].distance-30
      if dd<0 then
        cam.translate(-dd*info[1].isectNormal,#world)
      end if
    end if
 
    --on lance un rayon sur le mur de droite
    info=mem.modelsunderray(cam.transform.position,cam.transform.xAxis, options)    
    if info <> [] then
      dd=info[1].distance-30
      if dd<0 then
        cam.translate(-dd*info[1].isectNormal,#world)
      end if
    end if
 
 
  end if
 
 
 
  --on lance un rayon sur le sol
  info=mem.modelsunderray(cam.transform.position,vector(0,0,-1), options)
 
  if info<>[] then
 
    cam.transform.position.z=info[1].isectPosition.z+100
 
    g=info[1][#model].name
    if g<>currentSector then
      updateTerrain(me,g)
    end if    
 
  end if
 
 
 
 
 
end
 
 
on exitframe me
  if cpt<>void then
    call(cpt,me)
  end if
 
  go to the frame
end

source: secteurs.zip

Le culling des objets contenus dans les secteurs doit être parallèle à celle des secteurs: quand un secteur est caché, les personnages qu'il contient sont désactivés.