Continuant sur avec OpenGL ES pour l'iPhone, nous allons parler de la lumière. Jusqu'ici, nous n'avons pas fait n'importe quoi avec la lumière. Heureusement, OpenGL permet de toujours nous voir ce qui se passe si nous ne faisons pas configurer toutes les lumières. Il fournit simplement un éclairage très plat globale pour que nous puissions voir les choses. Mais sans feux définis, les choses ont tendance à regarder plutôt plat, comme vous l'avez vu dans les applications que nous avons écrit dans le dos partie 2.


GL_FLAT rendu d'un icosaèdre. Etat de l'art en temps réel le rendu ... Il ya 15 ans.
GL_FLAT traite chaque pixel sur un triangle donné exactement les mêmes en termes d'éclairage. Chaque pixel d'un polygone ont la même couleur, ombre, tout. Il donne des repères visuels assez d'avoir quelques tridimensionnalité et c'est calculs beaucoup moins cher que le calcul de chaque pixel différemment, mais plat objets ombrés ont tendance à ne pas regarder très réel. Maintenant, il pourrait y avoir une certaine valeur à l'utilisation de rétro GL_FLAT parfois, mais pour rendre vos objets 3D regarder aussi réaliste que possible, vous allez vouloir pour aller avec le mode de dessin GL_SMOOTH, qui utilise un algorithme d'ombrage lisse mais assez rapide appelé Gouraud ombrage. GL_SMOOTH est la valeur par défaut.
La seule raison pour laquelle j'en parle est en hausse à tout simplement parce que certaines des choses discutées à la fin de cet article ne fonctionne pas exactement la même chose si vous utilisez l'ombrage GL_FLAT. Compte tenu de la manière dont GL_FLAT désuet et inutile pour la plupart des buts, je ne voulais pas passer du temps en le recouvrant.
La première chose que nous devons faire est d'activer les lumières. Par défaut, les feux manuellement spécifiée sont désactivées. Tournons cette fonctionnalité sur le présent. Allez dans le GLViewController.m et ajoutez le code en gras ci-dessous pour l'setupView: Méthode:
Typiquement, les lumières sont quelque chose que vous permettra une fois pendant l'installation, et de ne pas toucher à nouveau. Ce n'est pas une fonctionnalité, vous devez être allumer et éteindre avant et après le dessin. Il pourrait y avoir quelques rares cas où vous en avez activé ou désactivé pendant l'exécution de votre programme, mais la plupart du temps que vous venez d'activer lorsque votre application se lance. C'est simple ligne de code est tout ce qu'il faut pour permettre à l'éclairage en OpenGL ES. Toute idée de ce qui se passe si nous lançons le programme maintenant?

La façon dont les lumières sont activées est un peu étrange. OpenGL ES vous permet de créer jusqu'à 8 sources de lumière. Il ya une constante qui correspond à chacun de ces feux est possible, qui se dérouleront du GL_LIGHT0 d'GL_LIGHT7. Vous pouvez utiliser n'importe quelle combinaison de ces cinq lumières, mais il est d'usage de commencer par GL_LIGHT0 pour votre première source de lumière, et puis aller à GL_LIGHT1 pour le prochain, et ainsi de suite tant que vous avez besoin plus de lumières. Voici comment vous "mettez" la première lumière, GL_LIGHT0>:
Une fois que vous avez activé une lumière, vous devez spécifier certains attributs de cette lumière. Pour commencer, il ya trois couleurs différentes composantes qui sont utilisées pour définir une lumière. Regardons en premier lieu.
L'illustration suivante montre l'effet que chaque composant a sur l'image résultante.

L'image Source Wikimedia Commons, modifié comme autorisé par la licence.
La composante spéculaire définit la lumière directe qui est très directe et ciblée et qui tend à refléter le dos au spectateur de telle sorte qu'il forme un "hot spot" ou briller sur l'objet. La taille de la tache peut varier en fonction d'un certain nombre de facteurs, mais si vous voyez une zone de lumière plus marquée, comme les deux points blancs sur la sphère jaune ci-dessus, qui est généralement va être venant de la composante spéculaire d'un ou plusieurs lumières.
La composante diffuse définit plat, léger, même assez directionnelles qui brille sur le côté qui fait face à des objets de la source lumineuse.
La lumière ambiante est la lumière sans source apparente. C'est la lumière qui a rebondi de tant de choses que sa source d'origine ne peut être identifié. La composante ambiante définit lumière qui brille régulièrement hors de tous les côtés de tous les objets de la scène.
Voici comment vous devez spécifier la composante ambiante de la lumière à une lumière blanche très faible:
En utilisant une faible valeur ambiante comme celle-ci fera l'éclairage de votre scène plus dramatique, mais cela signifie aussi que le côté d'un objet qui n'est pas face à un feu, ou des objets qui ont d'autres objets entre eux et la lumière ne sera pas vu très bien dans votre scène.
Voici un exemple de spécification de la composante diffuse pour la première lumière dans une scène:
Voici un exemple de configuration de la composante spéculaire:
Cette position aurait lieu la première lumière derrière le spectateur un peu à droite et un peu de moyens en place (ou beaucoup vers la droite et vers le haut si les unités OpenGL représentent des valeurs importantes dans votre monde virtuel).
Tels sont les attributs que vous aurez défini pour presque toutes les lumières. Si vous ne définissez pas une valeur pour l'une des composantes, il sera par défaut de ce composant au noir {0.0, 0.0, 0.0, 1.0}. Si vous n'avez pas de définir une position, il va tomber à l'origine, ce qui n'est généralement pas ce que vous voulez.
Vous pourriez vous demander ce que la composante alpha fait pour les lumières. La réponse à la lumière ambiante et spéculaire est pas une chose de reprise. Il est utilisé, cependant, dans les calculs de la lumière diffuse pour déterminer comment la lumière se reflète en arrière. Nous allons discuter de la manière comment cela fonctionne, après on commence à parler des matériaux parce que les deux valeurs matérielles et de la lumière aller dans cette équation. Nous allons discuter de matières prochaine fois, donc pour l'instant, ne vous inquiétez pas trop sur l'alpha, il vient de mettre 1.0. Changer qu'il n'aura pas d'incidence sur le programme que nous écrivons dans cette tranche, mais il pourrait, au moins pour la composante diffuse, sous forme de versements futurs.
Il ya quelques autres composants légers que vous pourriez envisager de mettre en option.
Maintenant, bien sûr, tout le monde sait qu'il faut deux points pour définir un segment de droite, alors comment peut un seul point dans l'espace éventuellement identifier un sens? C'est parce qu'il ya un deuxième point qui est utilisé implicitement comme point de départ, qui est à l'origine. Si vous tracez une ligne entre l'origine et le point défini dans le vecteur, c'est la direction représentée par le vecteur. Les vecteurs peuvent aussi être utilisées pour représenter la vitesse ou la distance, avec un point plus éloigné de l'origine représente une rapide vitesse ou distance plus grande. Dans la plupart des utilisations en OpenGL, la distance de l'origine n'est pas réellement utilisée. En fait, dans la plupart des cas où nous allons utiliser un vecteur, nous allons normalize que le vecteur de sorte qu'il a une longueur de 1,0. Normaliser les vecteurs. Nous allons parler des vecteurs plus que nous avançons, mais pour l'instant, si vous voulez définir une lumière directionnelle, vous devez créer un vecteur pour définir la direction qu'elle brille po Vous pourriez faire cela comme ça, qui créerait une lumière qui brille vers le bas l'axe Z:
Maintenant, si vous voulez ce que la lumière pointant vers un objet spécifique? C'est en fait assez facile à comprendre. Prenez la position de la lumière et la position de l'objet et nourrir ceux à l'Vector3DMakeWithStartAndEndPoints function () qui peuvent être trouvés dans OpenGLCommon.h fourni avec mon modèle de projet d'OpenGL, et il sera de retour un vecteur normalisé pointant de la lumière au point spécifié. Ensuite, vous pouvez nourrir que dans la valeur GL_SPOT_DIRECTION pour votre lumière.

Voici comment vous limiter l'angle d'une 90 ^ 0 champ de vision (en utilisant un angle de 45 ^ coupure 0):
Il ya plus de trois attributs de lumière qui peuvent être définies. Ils travaillent ensemble, et ils sont bien au-delà de la portée de ce blogue, mais je peut faire un post à l'avenir sur atténuation de la lumière, Ce qui est de savoir comment la lumière "tombe" comme il s'éloigne de sa source. Vous pouvez créer des effets très soignée en jouant avec les valeurs d'atténuation, mais cela devra attendre pour un poste futur.
Assez simple, non? Nous venons de mettre toutes les pièces du dessus ensemble, donc nous devrions être bon d'aller, non? Eh bien, essayez de l'exécuter et à voir.

Aw, maintenant, allez! Ce n'est pas juste! Nous avons les lumières, pourquoi ne voyons-nous pas quelque chose maintenant? Les impulsions de forme entre noir et gris, mais il ne semble pas plus comme une forme en 3D, puis il l'a fait auparavant.

OpenGL n'a pas besoin de connaître les normales pour rendre une forme, mais il ne leur avez besoin quand vous commencez à utiliser un éclairage directionnel. OpenGL a besoin des normales de surface de savoir comment la lumière interagit avec les polygones individuels.
OpenGL nous oblige à fournir une normale pour chaque sommet, nous avons utilisé. Maintenant, le calcul des normales de surface pour des triangles est assez facile, c'est tout simplement le produit croisé des deux côtés du triangle. Dans le code, il pourrait ressembler à ceci:
Comme vous l'avez vu avant, Vector3DMakeWithStartAndEndPoints () prend deux sommets et calcule un vecteur normalisé d'eux. Donc, si c'est si facile de calculer les normales de surface. Alors, pourquoi ne pas l'OpenGL ES le faire pour nous? Eh bien, pour deux raisons. D'abord et avant tout, c'est coûteux. Il ya un peu de flaoting de multiplication et de division point de passe, ainsi que d'un appel coûteux à sqrtf () pour chaque polygone.
Deuxièmement, parce que nous sommes en utilisant le rendu GL_SMOOTH, alors OpenGL ES a besoin de savoir normales des sommets ne pas les normales de surface, qui est ce que la fonction ci-dessus calcule. Calcul des normales sommet est encore plus coûteux car il nécessite de calculer un vecteur qui est la moyenne de toutes les normales de surface pour tous les polygones dont un sommet est utilisé.
Regardons un exemple (ce qui est recyclé à partir d'un poste plus tôt, n'hésitez pas à sauter à l'avance si vous êtes déjà à l'aise avec ce que les normales des sommets sont)

Ce n'est pas un cube, par la manière dont, pour simplifier, nous recherchons dans un plat, en deux dimensions de maille de six triangles. Il ya un total de sept sommets utilisés pour faire la forme. Ce sommet a marqué un est partagé par tous les six triangles, donc la normale au vertex pour ce sommet est la moyenne des normales de surface pour les sept triangles que cela fait partie du. Moyenne est faite par élément vectoriel, donc les valeurs de x sont en moyenne, les valeurs y sont en moyenne, et les valeurs z sont en moyenne, et les valeurs obtenues sont re-combinés pour faire le vecteur moyenne.
Alors, comment pouvons-nous obtenir les vecteurs de nos sommets de l'icosaèdre? Eh bien, c'est une forme assez simple que nous pourrions réellement sortir avec le calcul de la normales des sommets au moment de l'exécution sans retard notable. Habituellement, vous ne serez pas travailler avec si peu de sommets et sera traiter avec des objets beaucoup plus complexe, et plusieurs d'entre eux. En conséquence, vous voulez éviter de calculer les normales à l'exécution, sauf quand il n'ya pas d'alternative. Dans ce cas, j'ai décidé d'écrire un programme petite ligne de commande en boucle à travers les sommets et les indices de triangle et de calculer la normale au vertex pour chacun des sommets dans l'icosaèdre. Ce programme déversés les résultats à la console comme une struct C, et j'ai juste copié dans mon programme OpenGL.
Un peu rude, peut-être, mais il fait le travail et nous permet de pré-calculer les normales des sommets de telle sorte que nous n'avons pas à faire ce calcul plusieurs fois ou à l'exécution. Lorsque le programme est exécuté, la sortie est la suivante:
La manière dont nous alimentons le tableau normal à OpenGL utilise cet appel:
Et c'est tout ce qu'il ya trop. Ajoutons à ces éléments à notre drawSelf: méthode, qui nous donne ceci:

Mais ce qui est arrivé à nos couleurs?
Voilà, mes amis, c'est notre segue dans le prochain épisode de cette série: Matériaux OpenGL ES. Lorsque vous utilisez l'éclairage et l'ombrage lisse, OpenGL attend de vous d'offrir materials (Ou les textures, mais nous ne sommes pas prêts à enchaîner encore là) pour les polygones. Les matériaux sont plus complexes que les couleurs simples que nous avons fourni dans le tableau de couleur. Matériaux, comme les lumières, sont faites de multiples composants, et peut être utilisé pour créer une variété de traitements de surface différents. L'apparence réelle des objets est déterminée par les attributs des lumières de la scène et les matériaux les polygones ".
Mais, nous ne voulons pas laisser hors d'un icosaèdre gris, alors en attendant, je vais vous présenter un autre paramètre de configuration OpenGL ES qui peuvent être activés: GL_COLOR_MATERIAL. En activant cette option comme ceci:
OpenGL va utiliser le tableau de couleur fournis pour créer des documents simples pour nos polygones basés sur le tableau de couleur, donnant un résultat plutôt comme ceci:

Si vous n'avez pas envie de taper tout (ou copier-coller), vous pouvez consulter le projet final ici.

Modèles Shade
Avant d'entrer dans la manière dont traite OpenGL ES avec la lumière, il est important de savoir que l'OpenGL ES définit en fait deux modèles d'ombre, GL_FLAT et GL_SMOOTH. Nous ne sommes même pas à vraiment parler GL_FLAT, parce que vous ne l'utilisez que si vous vouliez vos applications à regarder comme ils sont venus dans les années 1990:
GL_FLAT traite chaque pixel sur un triangle donné exactement les mêmes en termes d'éclairage. Chaque pixel d'un polygone ont la même couleur, ombre, tout. Il donne des repères visuels assez d'avoir quelques tridimensionnalité et c'est calculs beaucoup moins cher que le calcul de chaque pixel différemment, mais plat objets ombrés ont tendance à ne pas regarder très réel. Maintenant, il pourrait y avoir une certaine valeur à l'utilisation de rétro GL_FLAT parfois, mais pour rendre vos objets 3D regarder aussi réaliste que possible, vous allez vouloir pour aller avec le mode de dessin GL_SMOOTH, qui utilise un algorithme d'ombrage lisse mais assez rapide appelé Gouraud ombrage. GL_SMOOTH est la valeur par défaut.
La seule raison pour laquelle j'en parle est en hausse à tout simplement parce que certaines des choses discutées à la fin de cet article ne fonctionne pas exactement la même chose si vous utilisez l'ombrage GL_FLAT. Compte tenu de la manière dont GL_FLAT désuet et inutile pour la plupart des buts, je ne voulais pas passer du temps en le recouvrant.
Activation Lumières
Aux fins du présent article, je vais supposer que vous êtes de continuer le projet final du chapitre 2, l'icosaèdre plat regardant filer. Si vous n'avez pas créé le projet, puis, et que vous voulez suivre, vous pouvez saisir le projet Xcode ici.La première chose que nous devons faire est d'activer les lumières. Par défaut, les feux manuellement spécifiée sont désactivées. Tournons cette fonctionnalité sur le présent. Allez dans le GLViewController.m et ajoutez le code en gras ci-dessous pour l'setupView: Méthode:
-(void)setupView:(GLView*)view
{
const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
GLfloat size;
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
CGRect rect = view.bounds;
glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
(rect.size.width / rect.size.height), zNear, zFar);
glViewport(0, 0, rect.size.width, rect.size.height);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_LIGHTING);
glLoadIdentity();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}Typiquement, les lumières sont quelque chose que vous permettra une fois pendant l'installation, et de ne pas toucher à nouveau. Ce n'est pas une fonctionnalité, vous devez être allumer et éteindre avant et après le dessin. Il pourrait y avoir quelques rares cas où vous en avez activé ou désactivé pendant l'exécution de votre programme, mais la plupart du temps que vous venez d'activer lorsque votre application se lance. C'est simple ligne de code est tout ce qu'il faut pour permettre à l'éclairage en OpenGL ES. Toute idée de ce qui se passe si nous lançons le programme maintenant?

Activation Lumières
Egads! Nous avons activé l'éclairage, mais n'ont pas créé de lumières. Tout nous attirons l'exception de la couleur grise, nous avons utilisé pour nettoyer les tampons seront rendus en noir oreille absolue. Ce n'est pas beaucoup d'amélioration, est-il? Ajoutons une lumière à la scène.La façon dont les lumières sont activées est un peu étrange. OpenGL ES vous permet de créer jusqu'à 8 sources de lumière. Il ya une constante qui correspond à chacun de ces feux est possible, qui se dérouleront du GL_LIGHT0 d'GL_LIGHT7. Vous pouvez utiliser n'importe quelle combinaison de ces cinq lumières, mais il est d'usage de commencer par GL_LIGHT0 pour votre première source de lumière, et puis aller à GL_LIGHT1 pour le prochain, et ainsi de suite tant que vous avez besoin plus de lumières. Voici comment vous "mettez" la première lumière, GL_LIGHT0>:
glEnable(GL_LIGHT0);
Une fois que vous avez activé une lumière, vous devez spécifier certains attributs de cette lumière. Pour commencer, il ya trois couleurs différentes composantes qui sont utilisées pour définir une lumière. Regardons en premier lieu.
Les trois composantes de la lumière
En OpenGL ES, les feux sont constitués de trois couleurs composant appelé composante ambiante, Le composante diffuse, Et le composante spéculaire. Il peut sembler étrange que nous sommes en utilisant une couleur pour spécifier les composants d'une lumière, mais cela fonctionne réellement bien, car il vous permet de spécifier la couleur et l'intensité relative de chaque composante de la lumière au même moment. Une lumière blanche serait définie comme blanc ({1.0, 1.0, 1.0, 1.0}), où une faible lumière blanche serait spécifiée comme une nuance de gris ({0,3, 0,3, 0,3 1,0}). Vous pouvez aussi donner votre lumière une dominante de couleur en faisant varier les pourcentages du rouge, du vert, et bleu.L'illustration suivante montre l'effet que chaque composant a sur l'image résultante.

La composante spéculaire définit la lumière directe qui est très directe et ciblée et qui tend à refléter le dos au spectateur de telle sorte qu'il forme un "hot spot" ou briller sur l'objet. La taille de la tache peut varier en fonction d'un certain nombre de facteurs, mais si vous voyez une zone de lumière plus marquée, comme les deux points blancs sur la sphère jaune ci-dessus, qui est généralement va être venant de la composante spéculaire d'un ou plusieurs lumières.
La composante diffuse définit plat, léger, même assez directionnelles qui brille sur le côté qui fait face à des objets de la source lumineuse.
La lumière ambiante est la lumière sans source apparente. C'est la lumière qui a rebondi de tant de choses que sa source d'origine ne peut être identifié. La composante ambiante définit lumière qui brille régulièrement hors de tous les côtés de tous les objets de la scène.
Composante ambiante
Le composant le plus votre lumière ambiante a des effets moins dramatiques de l'éclairage que vous obtenir le sera. Composantes de toute lumière ambiante sont multipliés ensemble, ce qui signifie que l'ensemble de la scène de la lumière ambiante sera la lumière ambiante combinée de toutes les sources de lumière activé. Si vous utilisez plus d'une lumière, vous pouvez spécifier votre composant ambiante sur un seul d'entre eux et de laisser la composante ambiante sur tous les autres à noir ({0.0, 0.0, 0.0, 1.0}) afin qu'il soit plus facile à ajuster la quantité de lumière ambiante dans la scène, parce que vous n'aurez qu'à le régler en un seul endroit.Voici comment vous devez spécifier la composante ambiante de la lumière à une lumière blanche très faible:
const GLfloat light0Ambient[] = {0.05, 0.05, 0.05, 1.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);En utilisant une faible valeur ambiante comme celle-ci fera l'éclairage de votre scène plus dramatique, mais cela signifie aussi que le côté d'un objet qui n'est pas face à un feu, ou des objets qui ont d'autres objets entre eux et la lumière ne sera pas vu très bien dans votre scène.
Composante diffuse
La deuxième composante de la lumière que vous pouvez spécifier en OpenGL ES est la composante diffuse. Dans le monde réel, la lumière diffuse la lumière serait, par exemple, qui a passé à travers un tissu léger, ou rebondir sur un mur blanc. Les rayons de lumière diffuse se sont éparpillés, ce qui donne une apparence plus douce que la lumière directe avec moins de chance de hot spots ou la brillance. Si vous avez déjà regardé un photographe professionnel en utilisant les lumières de studio, vous avez probablement vu leur utilisation softboxes ou reflétant leurs lumières dans les parapluies. Les deux passant à travers un matériau léger comme une étoffe blanche et reflétant sur un matériau de couleur claire se diffuse la lumière pour donner une photographie plus agréable. En OpenGL ES, la composante diffuse est similaire, en ce sens qu'il est la lumière qui reflète de façon très homogène hors de l'objet. Ce n'est pas la même que celle ambiante, cependant, parce que c'est une lumière directionnelle, alors que les côtés d'un objet qui fait face à la source de lumière va réfléchir la lumière diffuse alors que tous les polygones de la scène sera frappé par la lumière ambiante.Voici un exemple de spécification de la composante diffuse pour la première lumière dans une scène:
const GLfloat light0Diffuse[] = {0.5, 0.5, 0.5, 1.0};
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);Composante spéculaire
Enfin, nous arrivons à la composante spéculaire. Ceci est la composante de lumière qui est très directe et a tendance à rebondir à la vue sous la forme de points chauds et l'éblouissement. Si vous vouliez donner l'apparence d'un projecteur, vous devez spécifier une composante spéculaire très élevé, et très faible composantes diffuse et ambiante (vous auriez également définir d'autres paramètres, comme vous le verrez dans un instant).Note: La valeur spéculaire de la lumière n'est qu'un facteur dans la détermination de la taille de la lumière spéculaire, comme vous le verrez dans le prochain épisode. La première version de ce blogue avait mal brillance comme un attribut de la lumière, c'est en fait un attribut seul matériau, désolé. Brillance est quelque chose que nous allons discuter la prochaine fois quand nous avons commencer à créer des matériaux
Voici un exemple de configuration de la composante spéculaire:
const GLfloat light0Specular[] = {0.7, 0.7, 0.7, 1.0};Position
Il ya un autre attribut important d'une lumière qui a besoin d'être ensemble, qui est la position de la source de lumière dans l'espace 3D. Cela n'affecte pas la composante ambiante, mais les deux autres composantes sont directionnels dans la nature et ne peuvent être utilisées dans les calculs de lumière si OpenGL ne sait où la lumière est en relation avec les objets de la scène. Nous pouvons préciser la position de la lumière comme ceci: const GLfloat light0Position[] = {10.0, 10.0, 10.0, 0.0};
glLightfv(GL_LIGHT0, GL_POSITION, light0Position);Cette position aurait lieu la première lumière derrière le spectateur un peu à droite et un peu de moyens en place (ou beaucoup vers la droite et vers le haut si les unités OpenGL représentent des valeurs importantes dans votre monde virtuel).
Tels sont les attributs que vous aurez défini pour presque toutes les lumières. Si vous ne définissez pas une valeur pour l'une des composantes, il sera par défaut de ce composant au noir {0.0, 0.0, 0.0, 1.0}. Si vous n'avez pas de définir une position, il va tomber à l'origine, ce qui n'est généralement pas ce que vous voulez.
Vous pourriez vous demander ce que la composante alpha fait pour les lumières. La réponse à la lumière ambiante et spéculaire est pas une chose de reprise. Il est utilisé, cependant, dans les calculs de la lumière diffuse pour déterminer comment la lumière se reflète en arrière. Nous allons discuter de la manière comment cela fonctionne, après on commence à parler des matériaux parce que les deux valeurs matérielles et de la lumière aller dans cette équation. Nous allons discuter de matières prochaine fois, donc pour l'instant, ne vous inquiétez pas trop sur l'alpha, il vient de mettre 1.0. Changer qu'il n'aura pas d'incidence sur le programme que nous écrivons dans cette tranche, mais il pourrait, au moins pour la composante diffuse, sous forme de versements futurs.
Il ya quelques autres composants légers que vous pourriez envisager de mettre en option.
Créer un Spotlight
Si vous voulez créer un spot de lumière directionnelle - une lumière qui pointe dans une direction particulière et ne brille la lumière dans un certain angle de vue, en substance, créant une lumière qui brille dans la forme d'un tronc (rappelez-vous ce que c'est?) par opposition à briller dans toutes les directions comme une ampoule nue qui, alors vous devez définir deux paramètres supplémentaires. Réglage GL_SPOT_DIRECTION vous permet d'identifier la direction de la lumière est pointant po Réglage GL_SPOT_CUTOFF définit la propagation de la lumière, semblable à l'angle utilisé pour calculer le champ de vision dans le dernier versement. Un angle étroit crée un spot très serré, un angle plus large crée quelque chose de plus comme un projecteur.Spécifier Direction de la lumière
La façon GL_SPOT_DIRECTION fonctionne est que vous spécifiez un x, y et z élément pour définir la direction c'est pointant po La lumière n'est pas destiné à la point de l'espace que vous définissez, cependant. Les trois coordonnées que vous fournissez sont une vector, Pas un sommet. Or, cette distinction est subtile mais importante. Un vecteur est représenté par une structure de données qui est identique à un sommet - il faut trois GLfloats, un pour chacun des trois axes cartésiens, pour définir un vecteur. Cependant, les chiffres sont utilisés pour représenter un sens de la marche plutôt que d'un point dans l'espace.Maintenant, bien sûr, tout le monde sait qu'il faut deux points pour définir un segment de droite, alors comment peut un seul point dans l'espace éventuellement identifier un sens? C'est parce qu'il ya un deuxième point qui est utilisé implicitement comme point de départ, qui est à l'origine. Si vous tracez une ligne entre l'origine et le point défini dans le vecteur, c'est la direction représentée par le vecteur. Les vecteurs peuvent aussi être utilisées pour représenter la vitesse ou la distance, avec un point plus éloigné de l'origine représente une rapide vitesse ou distance plus grande. Dans la plupart des utilisations en OpenGL, la distance de l'origine n'est pas réellement utilisée. En fait, dans la plupart des cas où nous allons utiliser un vecteur, nous allons normalize que le vecteur de sorte qu'il a une longueur de 1,0. Normaliser les vecteurs. Nous allons parler des vecteurs plus que nous avançons, mais pour l'instant, si vous voulez définir une lumière directionnelle, vous devez créer un vecteur pour définir la direction qu'elle brille po Vous pourriez faire cela comme ça, qui créerait une lumière qui brille vers le bas l'axe Z:
const GLfloat light0Direction = {0.0, 0.0, -1.0};
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction);Maintenant, si vous voulez ce que la lumière pointant vers un objet spécifique? C'est en fait assez facile à comprendre. Prenez la position de la lumière et la position de l'objet et nourrir ceux à l'Vector3DMakeWithStartAndEndPoints function () qui peuvent être trouvés dans OpenGLCommon.h fourni avec mon modèle de projet d'OpenGL, et il sera de retour un vecteur normalisé pointant de la lumière au point spécifié. Ensuite, vous pouvez nourrir que dans la valeur GL_SPOT_DIRECTION pour votre lumière.
Spécification Angle d'éclairage
Spécification de la direction de la lumière n'aura pas d'effet notable, sauf si vous limiter l'angle que la lumière brille po Si vous spécifiez un angle de coupure, ça doit être inférieure à 180 ^ 0, car l'angle que vous spécifiez pour GL_SPOT_CUTOFF définit comment de nombreux degrés de la ligne médiane des deux côtés de la ligne médiane, donc si vous spécifiez 45 ^ 0, vous êtes réellement créer un spot avec un champ total de 90 ^ 0. Cela signifie que la valeur maximale que vous pouvez spécifier pour GL_SPOT_CUTOFF est de 180 ^ 0. Voici une illustration du concept:
Voici comment vous limiter l'angle d'une 90 ^ 0 champ de vision (en utilisant un angle de 45 ^ coupure 0):
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);Il ya plus de trois attributs de lumière qui peuvent être définies. Ils travaillent ensemble, et ils sont bien au-delà de la portée de ce blogue, mais je peut faire un post à l'avenir sur atténuation de la lumière, Ce qui est de savoir comment la lumière "tombe" comme il s'éloigne de sa source. Vous pouvez créer des effets très soignée en jouant avec les valeurs d'atténuation, mais cela devra attendre pour un poste futur.
Rassemblement des
Prenons tout ce que nous avons appris et de l'utiliser pour mettre en place une lumière dans le setupView: méthode. Remplacez votre setupView: méthode avec cette nouvelle:-(void)setupView:(GLView*)view
{
const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
GLfloat size;
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
CGRect rect = view.bounds;
glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
(rect.size.width / rect.size.height), zNear, zFar);
glViewport(0, 0, rect.size.width, rect.size.height);
glMatrixMode(GL_MODELVIEW);
// Enable lighting
glEnable(GL_LIGHTING);
// Turn the first light on
glEnable(GL_LIGHT0);
// Define the ambient component of the first light
const GLfloat light0Ambient[] = {0.1, 0.1, 0.1, 1.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
// Define the diffuse component of the first light
const GLfloat light0Diffuse[] = {0.7, 0.7, 0.7, 1.0};
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
// Define the specular component and shininess of the first light
const GLfloat light0Specular[] = {0.7, 0.7, 0.7, 1.0};
const GLfloat light0Shininess = 0.4;
glLightfv(GL_LIGHT0, GL_SPECULAR, light0Specular);
// Define the position of the first light
const GLfloat light0Position[] = {0.0, 10.0, 10.0, 0.0};
glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
// Define a direction vector for the light, this one points right down the Z axis
const GLfloat light0Direction[] = {0.0, 0.0, -1.0};
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction);
// Define a cutoff angle. This defines a 90^0 field of vision, since the cutoff
// is number of degrees to each side of an imaginary line drawn from the light's
// position along the vector supplied in GL_SPOT_DIRECTION above
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
glLoadIdentity();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}Assez simple, non? Nous venons de mettre toutes les pièces du dessus ensemble, donc nous devrions être bon d'aller, non? Eh bien, essayez de l'exécuter et à voir.

Aw, maintenant, allez! Ce n'est pas juste! Nous avons les lumières, pourquoi ne voyons-nous pas quelque chose maintenant? Les impulsions de forme entre noir et gris, mais il ne semble pas plus comme une forme en 3D, puis il l'a fait auparavant.
Ne vous inquiétez pas, c'est normal
Et en normal, je ne veux pas dire typique ou moyenne. Je veux dire normal dans le sens des mathématiques; pour signifier «perpendiculaire à». C'est ce qui nous manque. Normales. Une surface normale (ou un polygone normal) n'est rien de plus un vecteur (ou ligne) qui est perpendiculaire à la surface d'un polygone donné. Voici une belle illustration du concept (celui-ci est tirée de Wikipedia, et non pas de moi:)OpenGL n'a pas besoin de connaître les normales pour rendre une forme, mais il ne leur avez besoin quand vous commencez à utiliser un éclairage directionnel. OpenGL a besoin des normales de surface de savoir comment la lumière interagit avec les polygones individuels.
OpenGL nous oblige à fournir une normale pour chaque sommet, nous avons utilisé. Maintenant, le calcul des normales de surface pour des triangles est assez facile, c'est tout simplement le produit croisé des deux côtés du triangle. Dans le code, il pourrait ressembler à ceci:
static inline Vector3D Triangle3DCalculateSurfaceNormal(Triangle3D triangle)
{
Vector3D u = Vector3DMakeWithStartAndEndPoints(triangle.v2, triangle.v1);
Vector3D v = Vector3DMakeWithStartAndEndPoints(triangle.v3, triangle.v1);
Vector3D ret;
ret.x = (u.y * v.z) - (u.z * v.y);
ret.y = (u.z * v.x) - (u.x * v.z);
ret.z = (u.x * v.y) - (u.y * v.x);
return ret;
}Comme vous l'avez vu avant, Vector3DMakeWithStartAndEndPoints () prend deux sommets et calcule un vecteur normalisé d'eux. Donc, si c'est si facile de calculer les normales de surface. Alors, pourquoi ne pas l'OpenGL ES le faire pour nous? Eh bien, pour deux raisons. D'abord et avant tout, c'est coûteux. Il ya un peu de flaoting de multiplication et de division point de passe, ainsi que d'un appel coûteux à sqrtf () pour chaque polygone.
Deuxièmement, parce que nous sommes en utilisant le rendu GL_SMOOTH, alors OpenGL ES a besoin de savoir normales des sommets ne pas les normales de surface, qui est ce que la fonction ci-dessus calcule. Calcul des normales sommet est encore plus coûteux car il nécessite de calculer un vecteur qui est la moyenne de toutes les normales de surface pour tous les polygones dont un sommet est utilisé.
Regardons un exemple (ce qui est recyclé à partir d'un poste plus tôt, n'hésitez pas à sauter à l'avance si vous êtes déjà à l'aise avec ce que les normales des sommets sont)
Ce n'est pas un cube, par la manière dont, pour simplifier, nous recherchons dans un plat, en deux dimensions de maille de six triangles. Il ya un total de sept sommets utilisés pour faire la forme. Ce sommet a marqué un est partagé par tous les six triangles, donc la normale au vertex pour ce sommet est la moyenne des normales de surface pour les sept triangles que cela fait partie du. Moyenne est faite par élément vectoriel, donc les valeurs de x sont en moyenne, les valeurs y sont en moyenne, et les valeurs z sont en moyenne, et les valeurs obtenues sont re-combinés pour faire le vecteur moyenne.
Alors, comment pouvons-nous obtenir les vecteurs de nos sommets de l'icosaèdre? Eh bien, c'est une forme assez simple que nous pourrions réellement sortir avec le calcul de la normales des sommets au moment de l'exécution sans retard notable. Habituellement, vous ne serez pas travailler avec si peu de sommets et sera traiter avec des objets beaucoup plus complexe, et plusieurs d'entre eux. En conséquence, vous voulez éviter de calculer les normales à l'exécution, sauf quand il n'ya pas d'alternative. Dans ce cas, j'ai décidé d'écrire un programme petite ligne de commande en boucle à travers les sommets et les indices de triangle et de calculer la normale au vertex pour chacun des sommets dans l'icosaèdre. Ce programme déversés les résultats à la console comme une struct C, et j'ai juste copié dans mon programme OpenGL.
Note: La plupart des programmes en 3D va calculer les normales pour vous, mais soyez prudents en utilisant ceux - les plus 3D formats de fichiers normales de surface magasin, pas de normales au sommet, vous aurez donc encore généralement être responsable au moins de la moyenne des normales de surface pour créer des normales des sommets. Nous allons voir comment créer et charger des objets en versements plus tard, ou vous pouvez aller lire certains de mes messages antérieurs sur l'écriture d'un chargeur pour le format de fichier Wavefront OBJ.Voici le programme en ligne de commande que j'ai écrit pour calculer les normales des sommets de notre icosaèdre:
#import <Foundation/Foundation.h>
#import "OpenGLCommon.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableString *result = [NSMutableString string];
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
};
static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
};
Vector3D *surfaceNormals = calloc(20, sizeof(Vector3D));
// Calculate the surface normal for each triangle
for (int i = 0; i < 20; i++)
{
Vertex3D vertex1 = vertices[icosahedronFaces[(i*3)]];
Vertex3D vertex2 = vertices[icosahedronFaces[(i*3)+1]];
Vertex3D vertex3 = vertices[icosahedronFaces[(i*3)+2]];
Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3);
Vector3D surfaceNormal = Triangle3DCalculateSurfaceNormal(triangle);
Vector3DNormalize(&surfaceNormal);
surfaceNormals[i] = surfaceNormal;
}
Vertex3D *normals = calloc(12, sizeof(Vertex3D));
[result appendString:@"static const Vector3D normals[] = {\n"];
for (int i = 0; i < 12; i++)
{
int faceCount = 0;
for (int j = 0; j < 20; j++)
{
BOOL contains = NO;
for (int k = 0; k < 3; k++)
{
if (icosahedronFaces[(j * 3) + k] == i)
contains = YES;
}
if (contains)
{
faceCount++;
normals[i] = Vector3DAdd(normals[i], surfaceNormals[j]);
}
}
normals[i].x /= (GLfloat)faceCount;
normals[i].y /= (GLfloat)faceCount;
normals[i].z /= (GLfloat)faceCount;
[result appendFormat:@"\t{%f, %f, %f},\n", normals[i].x, normals[i].y, normals[i].z];
}
[result appendString:@"};\n"];
NSLog(result);
[pool drain];
return 0;
}
Un peu rude, peut-être, mais il fait le travail et nous permet de pré-calculer les normales des sommets de telle sorte que nous n'avons pas à faire ce calcul plusieurs fois ou à l'exécution. Lorsque le programme est exécuté, la sortie est la suivante:
static const Vector3D normals[] = {
{0.000000, -0.417775, 0.675974},
{0.675973, 0.000000, 0.417775},
{0.675973, -0.000000, -0.417775},
{-0.675973, 0.000000, -0.417775},
{-0.675973, -0.000000, 0.417775},
{-0.417775, 0.675974, 0.000000},
{0.417775, 0.675973, -0.000000},
{0.417775, -0.675974, 0.000000},
{-0.417775, -0.675974, 0.000000},
{0.000000, -0.417775, -0.675973},
{0.000000, 0.417775, -0.675974},
{0.000000, 0.417775, 0.675973},
};Spécifier les normales des sommets
Vous avez vu ci-dessus le tableau des normales que nous allons fournir à OpenGL. Avant que nous pouvons faire cela, cependant, nous avons pour permettre aux tableaux normaux. Cela se fait avec l'appel suivant: glEnableClientState(GL_NORMAL_ARRAY);La manière dont nous alimentons le tableau normal à OpenGL utilise cet appel:
glNormalPointer(GL_FLOAT, 0, normals);Et c'est tout ce qu'il ya trop. Ajoutons à ces éléments à notre drawSelf: méthode, qui nous donne ceci:
- (void)drawView:(GLView*)view;
{
static GLfloat rot = 0.0;
// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
};
static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
};
static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
};
static const Vector3D normals[] = {
{0.000000, -0.417775, 0.675974},
{0.675973, 0.000000, 0.417775},
{0.675973, -0.000000, -0.417775},
{-0.675973, 0.000000, -0.417775},
{-0.675973, -0.000000, 0.417775},
{-0.417775, 0.675974, 0.000000},
{0.417775, 0.675973, -0.000000},
{0.417775, -0.675974, 0.000000},
{-0.417775, -0.675974, 0.000000},
{0.000000, -0.417775, -0.675973},
{0.000000, 0.417775, -0.675974},
{0.000000, 0.417775, 0.675973},
};
glLoadIdentity();
glTranslatef(0.0f,0.0f,-3.0f);
glRotatef(rot,1.0f,1.0f,1.0f);
glClearColor(0.7, 0.7, 0.7, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;
}
lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}Voil'a, presque
Maintenant, si nous l'exécuter, nous avons en effet obtenir une forme tournante qui ressemble à un véritable dieu-honnête à la bonté objet tridimensionnel.
Mais ce qui est arrivé à nos couleurs?
Voilà, mes amis, c'est notre segue dans le prochain épisode de cette série: Matériaux OpenGL ES. Lorsque vous utilisez l'éclairage et l'ombrage lisse, OpenGL attend de vous d'offrir materials (Ou les textures, mais nous ne sommes pas prêts à enchaîner encore là) pour les polygones. Les matériaux sont plus complexes que les couleurs simples que nous avons fourni dans le tableau de couleur. Matériaux, comme les lumières, sont faites de multiples composants, et peut être utilisé pour créer une variété de traitements de surface différents. L'apparence réelle des objets est déterminée par les attributs des lumières de la scène et les matériaux les polygones ".
Mais, nous ne voulons pas laisser hors d'un icosaèdre gris, alors en attendant, je vais vous présenter un autre paramètre de configuration OpenGL ES qui peuvent être activés: GL_COLOR_MATERIAL. En activant cette option comme ceci:
glEnable(GL_COLOR_MATERIAL);
OpenGL va utiliser le tableau de couleur fournis pour créer des documents simples pour nos polygones basés sur le tableau de couleur, donnant un résultat plutôt comme ceci:

Si vous n'avez pas envie de taper tout (ou copier-coller), vous pouvez consulter le projet final ici.
Aucun commentaire:
Enregistrer un commentaire