Effet de lentille déformante
(rediff partielle et modifiée d'un article paru dans 64Nops n°1) Cet effet a été utilisé dans la démo [ Sphere ] Je ne sais pas si la démo Second Reality vous parle, mais c'est une des premières démos PC qui a marqué les esprits (il faut voir qu'à cette époque les démos Amiga étaient au même niveau avecmoins de puissance, que la catégorie PC était naissante, principalement à cause du manque d'outils pour faire des musiques et gérer les cartes sons). Un des effets marquants de cette démo était une bulle passant devant un visage de démon, faisant une jolie déformation. Cet effet de zoom déformant ne représente pourtant pas du tout ce qui se passe quandon regarde au travers d'une boule de cristal. Regarder à travers une boule de cristal n'est pas pratique, c'est un peu comme regarder dans un objectif fish-eye qui inverseraithaut, bas, gauche et droite... L'effet n'est pas du tout grossissant mais au contraire, rend tout minuscule! Bon, comme on est demomaker, on va envoyer bouler les lois de la physique et faire un effet déformant parce que c'est plus intéressant à regarder :)

I'm not an Atomic playboy! - Second Realiy (Future Crew) Une vraie sphère ne fait pas ça du tout ^_^ Je m'épuise car Thalès est toujours à faire L'idée générale de l'effet va être d'utiliser une table de correspondance ou "offset map" et d'afficher un sprite carré (oui, carré, vous avez bien lu!). Le contenu de cette tableva être un déplacement d'adresse. Si les points sont à l'intérieur d'un cercle, on va se déplacer en accord avec la distance rapport au centre pour obtenir un effet bombé. Siles points sont à l'extérieur du cercle, on ne fait pas de déplacement et c'est en fait le fond qui sera copié. Ainsi, notre routine d'affichage est simple et générique mais à l'écran, on aura bien une sphère qui se déplace et qui efface autour d'elle. Les mathématiciens cachent leurs bornes sous un flot de formules Pour la texture, il ne s'agit pas d'une simple image par dessus laquelle on passe, chaque octet ne va contenir qu'un seul pixel! Et en lisant la texture, on vareconstruire l'octet avec le pixel d'où qu'il soit. On ne stockera que des pixels à droite. Le pixel de gauche s'obtient (voir format écran) en décalant d'un coup versla gauche, avec un simple ADD. Bien que notre écran fasse 64 octets de large, la texture devra avoir des lignes de 256 octets de long (pour qu'un INC H ou un DEC H passe à la ligne suivante ou précédente) À cause de la longueur des lignes, il n'est pas possible de faire tout tenir en mémoire centrale alors nous allons réduire la résolution verticale par deux (notre texture fera donc 32K, ça passe!). 
Alors histoire de me faciliter la conversion, je vais ajouter du blanc à droite et à gauche pour que la texture fasse 256 pixels de large. Ensuite avec convGeneric, il existe uneoption pour extraire 1 pixel vers 1 octet, notre textureMap sera prête sans avoir la manipuler. convgeneric.exe -m 0 -single -g face.png -flatOn se servira de cette texture pour afficher l'écran de départ.affiche_reference ld hl,texturemap+64 ld de,#C000ld xl,128 ; parcourir 128 lignes de la texture .affiche_reference ld b,64 ; on va construire 64 octets push de .dispr ld a,(hl) : add a : inc l : or (hl) : inc l : ld (de),a : inc e : djnz .dispr ; à partir de 128 pixels exx : pop de : ld a,e : ld hl,#800 : add hl,de : ex hl,de : ld bc,64 : ldir : exx ; duplication des lignes ld e,a ex hl,de ld bc,2*#800 : add hl,bc : jr nc,.pas_doverflow ; sauter 2 lignes ld bc,64-#4000 : add hl,bc .pas_doverflow ex hl,de inc h : ld a,l : sub 128 : ld l,a ; sauter le blanc dans le sprite dec xl jr nz,.affiche_reference retLa boule est formelle Comme nous avons organisé notre texture en lignes de 256 octets, le tableau correspondant aux vecteurs déplacement va mettre le déplacement vertical dans le poids fort d'une valeur 16 bitset le déplacement horizontal dans le poids faible. En chaque point du tableau, on calcule si on est dans le cercle, ensuite on fait une mise au carré de la valeur, c'est purementempirique, la division par 2.3 aussi (vous pourrez jouer avec cette valeur). Si vous voulez jouer sur l'effet de zoom, c'est ce calcul qu'il faudra modifier. BULLE_SIZE=64 BULLE_HALF=BULLE_SIZE>>1 boule_carree repeat BULLE_SIZE+2,y repeat BULLE_SIZE+2,x cx=(x-BULLE_HALF-2)/BULLE_HALF cy=(y-BULLE_HALF-2)/BULLE_HALF no=sqrt(cx*cx+cy*cy) ; distance rapport au centre if no>1 || x<=2 || x>=BULLE_SIZE+1 || y==BULLE_SIZE+2; ça veut dire qu'on est hors du cercle, on met simplement la valeur normale lowbyte=x-BULLE_HALF-2 highbyte=hi(texturemap)+y-1 ;64+y-BULLE_HALF-2 else coef=1+no*no ; va nous donner une valeur entre 1 et 2 coef=coef*BULLE_HALF/2.3 ; Ici on peut jouer avec une valeur entre 2 et 5 if coef>BULLE_HALF : coef=BULLE_HALF : endif lowbyte=cx*coef highbyte=hi(texturemap)+cy*coef+BULLE_HALF ;+64 endif ; inscrire la valeur 16 bits dans le tableau defb lowbyte+33+64,highbyte rend rendL'effet de la translation L'affichage se fait presque comme celui d'un sprite, on va parcourir le rectangle de notre sprite, récupérer avec des POP les vecteurs translation et faire un ADD pour se déplacer. ; SP = données de translation de notre sprite ; DE = adresse écran des lignes paires ; HL'= adresse écran des lignes paires ; DE'= adresse écran des lignes impaires affiche_boule ld sp,boule_carree repeat BULLE_HALF+1 pop hl : add hl,bc : ld a,(hl) : add a : pop hl : add hl,bc : or (hl) : ld (de),a : inc e ; on traite les pixels 2 à 2 pour remplir les octets rend exx repeat BULLE_HALF+1 : ldi : rend ld bc,2*#800-BULLE_HALF-1 : add hl,bc : jr nc,.padov ld bc,64-#4000 : add hl,bc : .padov ld a,h : add 8 : ld d,a : ld e,l exx ld a,-BULLE_HALF-1 : add e : ld l,a : ld a,d : add #10 : ld h,a : jr nc,.pas_doverflow ld de,64-#4000 : add hl,de .pas_doverflow ex hl,de dec xl jp nz,affiche_bouleJe vous invite à télécharger la [ texture ] avant de compiler ce programme.BUILDSNA : BANKSET 0 org #100 : run #100 textureMap equ #4000 BULLE_SIZE=64 BULLE_HALF=BULLE_SIZE>>1 ld sp,#100 ld bc,#7F00 : ld e,17 : ld hl,palette setPal out (c),c : inc c : inc b : outi : dec e : jr nz,setPal ld bc,#7F80+%1100 : out (c),c ; MODE 0 ld bc,#BC01 : out (c),c : ld a,32 : inc b : out (c),a ; largeur visible ld bc,#BC02 : out (c),c : ld a,42 : inc b : out (c),a ; position X ld bc,#BC06 : out (c),c : ld a,32 : inc b : out (c),a ; hauteur visible ld bc,#BC07 : out (c),c : ld a,34 : inc b : out (c),a ; position Y ; *** affiche_reference *** ld hl,texturemap+64 ld de,#C000ld xl,128 ; parcourir 128 lignes de la texture .affiche_reference ld b,64 ; on va construire 64 octets push de .dispr ld a,(hl) : add a : inc l : or (hl) : inc l : ld (de),a : inc e : djnz .dispr ; à partir de 128 pixels exx : pop de : ld a,e : ld hl,#800 : add hl,de : ex hl,de : ld bc,64 : ldir : exx ; duplication des lignes ld e,a ex hl,de ld bc,2*#800 : add hl,bc : jr nc,.pas_doverflow ; sauter 2 lignes ld bc,64-#4000 : add hl,bc .pas_doverflow ex hl,de inc h : ld a,l : sub 128 : ld l,a ; sauter le blanc dans le sprite dec xl jr nz,.affiche_reference ;************************************************************* LaBoucle ;************************************************************* ld de,#C000 : exx : ld hl,#C000 : ld de,#C800 : exx ld sp,boule_carree ld xl,BULLE_SIZE+1 ld bc,63 ; pixel de la texture correspondant au coin supérieur gauche de l'écran affiche_boule repeat BULLE_HALF+1 pop hl : add hl,bc : ld a,(hl) : add a : pop hl : add hl,bc : or (hl) : ld (de),a : inc e ; on traite les pixels 2 à 2 pour remplir les octets rend exx repeat BULLE_HALF+1 : ldi : rend ld bc,2*#800-BULLE_HALF-1 : add hl,bc : jr nc,.padov ld bc,64-#4000 : add hl,bc : .padov ld a,h : add 8 : ld d,a : ld e,l exx ld a,-BULLE_HALF-1 : add e : ld l,a : ld a,d : add #10 : ld h,a : jr nc,.pas_doverflow ld de,64-#4000 : add hl,de .pas_doverflow ex hl,de dec xl jp nz,affiche_boule jr $ palette defb #54,#44,#5C,#58,#46,#5E,#40,#4C,#4E,#45,#47,#5F,#43,#5B,#4F,#4B,#4B
boule_carree repeat BULLE_SIZE+2,y repeat BULLE_SIZE+2,x cx=(x-BULLE_HALF-2)/BULLE_HALF cy=(y-BULLE_HALF-2)/BULLE_HALF no=sqrt(cx*cx+cy*cy) ; distance rapport au centre if no>1 || x<=2 || x>=BULLE_SIZE+1 || y==BULLE_SIZE+2; ça veut dire qu'on est hors du cercle, on met simplement la valeur normale lowbyte=x-BULLE_HALF-2 highbyte=hi(texturemap)+y-1 ;64+y-BULLE_HALF-2 else no*=sqrt(BULLE_HALF) noref=no no*=no no=(noref+no+no)/2.7 if no>BULLE_HALF : no=BULLE_HALF : endif lowbyte=cx*no highbyte=hi(texturemap)+cy*no+BULLE_HALF ;+64 endif ; inscrire la valeur 16 bits dans le tableau defb lowbyte+BULLE_HALF+2,highbyte rend rend org textureMap : incbin 'face.bin'On devine déjà quelque chose, mais ça ne bouge pas!
On va devoir ajouter quelques petites routines pour cela. L'idée générale du déplacement de notre sprite, c'est qu'on va devoir déplacer la boule ET en même temps, changer la valeur de notre décalage pour qu'il corresponde à la position de la boule.On ira poker notre initialisation dans le code aux adresses destinationEcran et decalageTexture. Tout se passe au chapitre "deplacement", vous verrez, ce n'est pas violent. BUILDSNA : BANKSET 0 org #100 : run #100 textureMap equ #4000 BULLE_SIZE=64 BULLE_HALF=BULLE_SIZE>>1 ld sp,#100 ld bc,#7F00 : ld e,17 : ld hl,palette setPal out (c),c : inc c : inc b : outi : dec e : jr nz,setPal ld bc,#7F80+%1100 : out (c),c ; MODE 0 ld bc,#BC01 : out (c),c : ld a,32 : inc b : out (c),a ; largeur visible ld bc,#BC02 : out (c),c : ld a,42 : inc b : out (c),a ; position X ld bc,#BC06 : out (c),c : ld a,32 : inc b : out (c),a ; hauteur visible ld bc,#BC07 : out (c),c : ld a,34 : inc b : out (c),a ; position Y ; *** affiche_reference *** ld hl,texturemap+64 ld de,#C000ld xl,128 ; parcourir 128 lignes de la texture .affiche_reference ld b,64 ; on va construire 64 octets push de .dispr ld a,(hl) : add a : inc l : or (hl) : inc l : ld (de),a : inc e : djnz .dispr ; à partir de 128 pixels exx : pop de : ld a,e : ld hl,#800 : add hl,de : ex hl,de : ld bc,64 : ldir : exx ; duplication des lignes ld e,a ex hl,de ld bc,2*#800 : add hl,bc : jr nc,.pas_doverflow ; sauter 2 lignes ld bc,64-#4000 : add hl,bc .pas_doverflow ex hl,de inc h : ld a,l : sub 128 : ld l,a ; sauter le blanc dans le sprite dec xl jr nz,.affiche_reference ;************************************************************* LaBoucle ;************************************************************* ld sp,#100 ; besoin d'une pile valide pour notre PUSH/POP ld de,#C000 : destinationEcran=$-2 : push de : exx : pop hl : ld e,l : ld a,h : add 8 : ld d,a : exx ld sp,boule_carree ld xl,BULLE_SIZE+2 ld bc,0 : decalageTexture=$-2 ; pixel de la texture correspondant au coin supérieur gauche de l'écran ;**************** affiche_boule ;**************** repeat BULLE_HALF+1 pop hl : add hl,bc : ld a,(hl) : add a : pop hl : add hl,bc : or (hl) : ld (de),a : inc e ; on traite les pixels 2 à 2 pour remplir les octets rend exx repeat BULLE_HALF+1 : ldi : rend ld bc,2*#800-BULLE_HALF-1 : add hl,bc : jr nc,.padov ld bc,64-#4000 : add hl,bc : .padov ld a,h : add 8 : ld d,a : ld e,l exx ld a,-BULLE_HALF-1 : add e : ld l,a : ld a,d : add #10 : ld h,a : jr nc,.pas_doverflow ld de,64-#4000 : add hl,de .pas_doverflow ex hl,de dec xl jp nz,affiche_boule ;**************** deplacement ;**************** ld bc,1 : incrementX=$-2 ld hl,(destinationEcran) : add hl,bc : ld (destinationEcran),hl ld hl,(decalageTexture) : add hl,bc : add hl,bc : ld (decalageTexture),hl ; double pour la texture car 1 pixel par octet! .compteurX ld a,28 : dec a : jr nz,.suite ld h,a : ld l,a : or a : sbc hl,bc : ld (incrementX),hl ; inverser l'incrément ld a,28 ; on repart pour une série de 28! .suite ld (.compteurX+1),a ld sp,#100 ; besoin d'une pile valide pour le CALL ld a,1 : incrementY=$-1 or a : jr z,remonte ; descend ld hl,(destinationEcran) : ld a,h : add 8 : ld h,a : call NextLineHL : ld (destinationEcran),hl ld hl,(decalageTexture) : inc h : ld (decalageTexture),hl jr compteurY remonte ld hl,(destinationEcran) : call PreviousLineHL : res 3,h : ld (destinationEcran),hl ld hl,(decalageTexture) : dec h : ld (decalageTexture),hl compteurY ld a,61 : dec a : jr nz,.suiteY ld a,(incrementY) : inc a : and 1 : ld (incrementY),a ; 1, 0, 1, 0, ... ld a,61 ; on repart pour une série de 41! .suiteY ld (compteurY+1),a jp LaBoucle
NextLineHL ld a,h : add 8 : ld h,a : ret nc ld a,64 : add l : ld l,a : ld a,#C0 : adc h : ld h,a : res 3,h : ret PreviousLineHL ld a,h : sub 8 : ld h,a : and #38 : cp #38 : ret nz ld a,lo(16384-64) : add l : ld l,a : ld a,#3F : adc h : ld h,a : set 3,h : ret palette defb #54,#44,#5C,#58,#46,#5E,#40,#4C,#4E,#45,#47,#5F,#43,#5B,#4F,#4B,#4B boule_carree repeat BULLE_SIZE+2,y repeat BULLE_SIZE+2,x cx=(x-BULLE_HALF-2)/BULLE_HALF cy=(y-BULLE_HALF-2)/BULLE_HALF no=sqrt(cx*cx+cy*cy) ; distance rapport au centre if no>1 || x<=2 || x>=BULLE_SIZE+1 || y==BULLE_SIZE+2; ça veut dire qu'on est hors du cercle, on met simplement la valeur normale lowbyte=x-BULLE_HALF-2 highbyte=hi(texturemap)+y-1 ;64+y-BULLE_HALF-2 else coef=1+no*no ; va nous donner une valeur entre 1 et 2 coef=coef*BULLE_HALF/2.3 ; Ici on peut jouer avec une valeur entre 2 et 5 if coef>BULLE_HALF : coef=BULLE_HALF : endif lowbyte=cx*coef highbyte=hi(texturemap)+cy*coef+BULLE_HALF ;+64 endif ; inscrire la valeur 16 bits dans le tableau defb lowbyte+33+64,highbyte rend rend org textureMap : incbin 'face.bin'Et voici la preview du résulat que vous obtiendrez avec ce source. N'hésitez pas à venir changer la valeur 2.3 dans la génération des données de déformation et la remplacer par une valeurentre 2 et 5, ça change le bombé de la déformation. 
Roudoudou CPCrulez[Content Management System] v8.732-desktop/c Page créée en 451 millisecondes et consultée 30 foisL'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko. |
|