Revenir au plan du site

Effet d'interférences


Un vieil effet bien connu des plateformes Commodore repose sur le mélange de deux motifs très proches qui tournent autour de leur zone de phase. Bon, en phase, c'est moche, par contre, dès lors qu'on s'éloigne légèrement, les motifs se mélangent et avec une opération logique type XOR ou OR, on peut obtenir des résultats intéressants.

Si on part d'un motif de ce genre (au plus grand des hasards), contenant 8 couleurs. Et qu'on le mélange avec un deuxième motif plus simple, contenant l'encre 0 et 8.
 
Notre palette serait un dégradé allant des couleurs 0 à 7 et pour les couleurs 8 à 15, le même dégradé inversé.

Vous pouvez récupérer les fichiers [oeil.bin] et [oeil2.bin] avant d'assembler ce code
buildsna
bankset 0
org #100
run #100

ld bc,#7F80+%1100 : out (c),c
ld hl,palette
ld bc,#7F00
setPalette out (c),c : inc c : inc b : outi : ld a,(hl) : or a : jr nz,setPalette

ld hl,#4000
ld de,#8000+10
ld bc,#C000
OuLogique ld a,(de) : or (hl) : ld (bc),a : inc c : inc de : inc l : jr nz,OuLogique : inc h : inc b : jr nz,OuLogique
jr $

palette defb #54,#50,#55,#57,#5F,#53,#5B,#4B
        defb #4B,#5B,#53,#5F,#57,#55,#50,#54,0

org #4000 : incbin 'oeil.bin'
org #8000 : incbin 'oeil2.bin'

En décalant de 10 octets la deuxième image (et en se moquant clairement de mal mélanger les octets sur les bords), on obtient déjà une idée de l'effet et ça a l'air plutôt pas mal non?


Bon, on va linéariser nos écrans pour en faire des sprites (plus d'entrelacement) et on va piocher dans les données en ne partant pas toujours du même endroit. On va aussi limiter la largeur affichée et la hauteur affichée. L'idée, c'est qu'à partir d'un gros sprite de la taille d'un écran (80 octets sur 200 lignes), on affiche un sprite d'une taille de 40 octets sur 160 lignes. Ça nous laisse une amplitude énorme de 40 octets en X et 40 lignes en hauteur.
Dans un premier temps, un des deux sprites sera fixe. Pour l'autre, on va suivre la trajectoire d'un cercle (cosinus/sinus).
Vous pouvez récupérer les fichiers [oeilFlat.bin] et [oeil2Flat.bin] avant d'assembler ce code
buildsna
bankset 0
org #100
run #100

ld bc,#7F80+%1100 : out (c),c
ld hl,palette
ld bc,#7F00
setPalette out (c),c : inc c : inc b : outi : ld a,(hl) : or a : jr nz,setPalette

ld ix,monCercle
AfficheInterference
ld hl,#4000+20 ; on se centre un peu en X
ld de,(ix+0) : inc xl : inc xl ; chope le cercle et incrémente
ld bc,#C000
OuLogique
ld yh,160
laHauteur
ld yl,40 : push bc
laLigne ld a,(de) : or (hl) : ld (bc),a : inc bc : inc de : inc hl : dec yl : jr nz,laLigne
ld bc,40 : add hl,bc : ex hl,de : add hl,bc : ex hl,de ; nos sprites sont +larges que la zone affichée!
pop bc : ld a,8 : add b : ld b,a : jr nc,.novf
ld a,80 : add c : ld c,a : ld a,#C0 : adc b : ld b,a : .novf
dec yh : jr nz,laHauteur
jr AfficheInterference

palette defb #54,#50,#55,#57,#5F,#53,#5B,#4B
        defb #4B,#5B,#53,#5F,#57,#55,#50,#54,0

align 256
monCercle
ang=0
repeat 128
defw #8000+20+cos(ang)*13+20*80+int(sin(ang)*19)*80
ang+=720/128
rend

org #4000 : incbin 'oeilFlat.bin'
org #8000 : incbin 'oeil2Flat.bin'

Alors c'est pas le plus rapide jamais réalisé, mais ce code laisse la place à des tas d'optimisations :
- Dérouler le code!!!
- Réduire la taille de l'écran physique à 64 octets pour faire des incréments 8 bits
- Augmenter la largeur physique des lignes (avec du rien) pour les aligner sur 128 octets et profiter aussi d'incréments 8 bits
- Faire du code généré, exploser la mémoire pour gagner encore en vitesse


On se lâche?

Le motif bi-couleur ne contient que les valeurs 0, 1, 2 ou 3 sur tout le fichier, extrait :
...
00002fc0 00 00 00 03 03 02 00 00 01 03 03 00 00 01 03 03 |................|
00002fd0 01 03 03 00 00 01 03 03 02 00 00 03 03 03 00 00 |................|
00002fe0 01 03 03 02 00 00 01 03 03 03 00 00 00 01 03 03 |................|
00002ff0 03 03 00 00 00 00 00 00 00 00 00 00 00 00 01 03 |................|
00003000 03 03 03 00 00 00 01 03 03 03 00 00 00 03 03 03 |................|
00003010 00 00 01 03 03 02 00 00 03 03 03 00 00 01 03 03 |................|
...

Si on fixe ce motif sur le fond, ça voudrait dire qu'on pourrait générer des lignes de code beaucoup plus rapides qui ressembleraient à ça :
ld a,(de) : ld (bc),a : inc e : inc c ; pas de OR car OR 0 ne fait... rien!
ld a,(de) : or 1 : ld (bc),a : inc e : inc c

Notre registre HL étant libre, on peut l'utiliser en préchargeant H et L avec 3 et 2. Quant au 1 il serait réalisé par un INC car les données de l'autre fichier n'en contienne jamais, ça reviendrait au même que le OR 1.

Notre dessin étant symétrique, on ne génèrera que les 80 lignes du haut et on exécutera en ordre inverse pour faire les 80 lignes du bas.

L'autre motif aussi est symétrique, on pourra le couper en deux si nous avons besoin de place à force de s'étaler en mémoire comme des gorets.

Enfin, comme on le voit sur la capture plus haut, l'effet occupe moins de la moitié de l'écran, ce qui veut dire qu'on pourrait faire un double buffer dans la même bank en redimensionnant l'écran grâce aux registres 1 et 6 (respectivement largeur/hauteur visible), ainsi que les registres 2 et 7 pour placer l'écran au centre du moniteur

Les curieux pourront toujours me demander la moulinette qui a généré le code des lignes, ça fait 20 lignes de C à tout casser. On aurait pu le faire en Basic (et même en assembleur) mais une fois de plus, je voulais démontrer que l'important est la préparation des données, et le code généré fait partie des 'données' utiles à préparer.

Concentrons-nous plutôt sur ce qui va changer dans notre programme général avec nos nouvelles données [oeilFlat128.bin] et [interferenceCodgen.asm]

Notez que les routines de passage de ligne et de calcul de cercle ont été adaptées pour l'écran de 40 octets ainsi que les données alignées sur 128 octets
buildsna
bankset 0
org #100
run #100

ld sp,#100
ld bc,#7F80+%1100 : out (c),c
ld hl,palette
ld bc,#7F00
setPalette out (c),c : inc c : inc b : outi : ld a,(hl) : or a : jr nz,setPalette

ld bc,#BC00+1 : out (c),c : ld bc,#BD00+20 : out (c),c ; largeur écran réduite
ld bc,#BC00+2 : out (c),c : ld bc,#BD00+36 : out (c),c ; positionner l'écran en X
ld bc,#BC00+6 : out (c),c : ld bc,#BD00+20 : out (c),c ; 160 lignes = 20 lignes de blocs de 8 lignes
ld bc,#BC00+7 : out (c),c : ld bc,#BD00+28 : out (c),c ; positionner l'écran en Y

ld ix,monCercle
Reboucle
ld de,(ix+0) : inc xl : inc xl ; chope le cercle et incrémente
ld bc,#C000+20*40
call AfficherInterferences
call WaitVBL : ld bc,#BC00+12 : out (c),c : ld bc,#BD31 : out (c),c : ld bc,#BC00+13 : out (c),c : ld bc,#BD90 : out (c),c

ld de,(ix+0) : inc xl : inc xl ; chope le cercle et incrémente
ld bc,#C000
call AfficherInterferences
call WaitVBL : ld bc,#BC00+12 : out (c),c : ld bc,#BD30 : out (c),c : ld bc,#BC00+13 : out (c),c : inc b : out (c),0
jp Reboucle

WaitVBL ld b,#F5
.loop in a,(c) : rra : jr nc,.loop : ret

AfficherInterferences
; BC=destination écran
ld iy,interference_codlist ; notre liste de routines
ld a,160
.afficherLignes
exa ; compteur dans A'
ld hl,(iy+0) : inc yl : inc iy : push bc : jp (hl)
.retour ; nos routines générées reviennent toutes ici!
; notre sprite est toujours +large et en prime aligné sur 128 octets
ld a,e : add 88 : ld e,a : ld a,d : adc 0 : ld d,a
pop bc : ld a,8 : add b : ld b,a : jr nc,.novf
ld a,40 : add c : ld c,a : ld a,#C0 : adc b : ld b,a : .novf
exa : dec a : jr nz,.afficherLignes
ret

palette defb #54,#50,#55,#57,#5F,#53,#5B,#4B
        defb #4B,#5B,#53,#5F,#57,#55,#50,#54,0

align 256
monCercle
ang=0
repeat 128
defw #1000+20+cos(ang)*13+20*128+int(sin(ang)*19)*128 ; ajusté pour des données alignées sur 128
ang+=720/128
rend

org #1000 : incbin 'oeilFlat128.bin' ; données graphiques alignées gonflées à 25k!
org #8000 : include 'interferenceCodgen.asm' ; moins de 16k!

Nettement mieux en termes de fluidité non? ;)


Et si vous aimez les interférences originales, vous pouvez jeter un oeil à [cette production]

Avant de charger la démo, faire
poke &0128,128 : run"arpoilu