Revenir au plan du site

Toi aussi, deviens demomaker avec les shadebobs!


Le shadeBob est un effet d'addition/saturation de couleur sur l'écran. L'algorithme peut se résumer à :
- Pour chaque pixel non transparent du sprite, on augmente de 1 le numéro de l'encre écran sur lequel on doit poser ce pixel.
- Si le résultat de l'addition dépasse 15, on revient à zéro

Dans un premier temps, ça sera déjà pas mal. Pour réaliser cet effet, il va nous falloir une table de lookup. Ainsi on pourra faire du MMX! Enfin presque. On fera deux additions en même temps (un octet en mode 0 contenant 2 pixels, on aurait deux additions à faire, une par pixel, voir [table dans l'article sur la structure écran]). Préparons notre table.
align 256 ; on va lire les données directement avec le poids faible comme index
shadeINC
startingindex 0 ; pour démarrer la valeur du compteur du REPEAT à zéro
repeat 256,x
; à partir de la valeur de l'octet dans x, on éclate les bits pour connaitre l'index d'encre
 pixleft=((x&128)>>7)|((x&8)>>2)|((x&32)>>3)|((x&2)<<2)
pixright=((x&64)>>6)|((x&4)>>1)|((x&16)>>2)|((x&1)<<3)
; on ajoute 1 à l'encre, on gère la saturation si on fait le tour du compteur
pixleft+=1 : if pixleft==16 : pixleft=0 : endif
pixright+=1 : if pixright==16 : pixright=0 : endif
; enfin on recombine nos bits pour construire l'octet
pl=((pixleft&1)<<7)|((pixleft&2)<<2)|((pixleft&4)<<3)|((pixleft&8)>>2)
pr=((pixright&1)<<6)|((pixright&2)<<1)|((pixright&4)<<2)|((pixright&8)>>3)
; et on écrit pour chaque octet/valeur, un résultat correspondant à l'ajout de 1 sur chaque pixel
defb pl|pr
rend

On va reprendre le même genre de sinus que l'article sur les sprites illimités pour définir une petite trajectoire et appeler une routine qui parcourt un petit rectangle de 2 octets de large sur 4 lignes de haut. Cette routine sera du code pur, pas de données!
shadePut
; HL=adresse écran de départ
ld b,hi(shadeINC) : ld d,4
.loop
ld c,(hl) : ld a,(bc) : ld (hl),a : inc hl : ld c,(hl) : ld a,(bc) : ld (hl),a : dec hl
ld a,h : add 8 : ld h,a ; ajouter #800 à HL
jr nc,.next ; pas de retenue, on est toujours dans le même bloc de lignes
ld a,80 : add l : ld l,a ; on ajoute 80 (largeur d'une ligne en octets) pour passer au bloc suivant
ld a,#C0 : adc h : ld h,a ; et on enlève #4000 (additionner #C000 c'est comme enlever #4000)
.next
dec d : jr nz,.loop
ret

BUILDSNA : BANKSET 0
ORG #100
RUN #100

; création d'une macro pour le passage à la ligne, ça évitera de copié-coller du code
macro NextLineHL
ld a,h : add 8 : ld h,a ; ajouter #800 à HL
jr nc,@next ; pas de retenue, on est toujours dans le même bloc de lignes
ld a,80 : add l : ld l,a ; on ajoute 80 (largeur d'une ligne en octets) pour passer au bloc suivant
ld a,#C0 : adc h : ld h,a ; et on enlève #4000 (additionner #C000 c'est comme enlever #4000)
@next
mend

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

BouclePrincipale
ld b,#F5
.vbl in a,(c) : rra : jr nc,.vbl
call ExecuteBob
jr BouclePrincipale

ExecuteBob
lireX ld hl,sinuzix : ld c,(hl) : inc l : ld b,0
phase ld a,0 : inc a : and 15 : ld (phase+1),a : jr nz,.noADD : inc l : .noADD ld (lireX+1),hl
lireY ld hl,sinuziy : ld a,(hl) : inc l : ld (lireY+1),hl : ld l,a : ld h,0
CalculeAdressePixel
; BC=coordonnée X (0-159)
; HL=coordonnée Y (0-199)
; adresse de la ligne dans HL en résultat
ld de,tableau
add hl,hl ; adresses 16 bits, il faut indexer de 2 en 2
add hl,de
ld a,(hl) : inc hl
ld h,(hl) : ld l,a
add hl,bc ; ajouter la position en OCTETS !
shadePut
; HL=adresse écran de départ
ld b,hi(shadeINC) : ld d,2
; plutôt que revenir à la ligne, on va dessiner en zig-zag, un coup à droite, un coup à gauche
.loop
ld c,(hl) : ld a,(bc) : ld (hl),a : inc hl : ld c,(hl) : ld a,(bc) : ld (hl),a
NextLineHL (void) ; macro de passage à la ligne suivante
ld c,(hl) : ld a,(bc) : ld (hl),a : dec hl : ld c,(hl) : ld a,(bc) : ld (hl),a
NextLineHL (void) ; macro de passage à la ligne suivante
dec d : jr nz,.loop
ret

;-------------------
adresse_ecran=#C000
largeur_ecran=80
tableau
repeat 25
repeat 8
defw adresse_ecran
adresse_ecran+=#800
rend
adresse_ecran+=largeur_ecran
adresse_ecran-=#4000
rend
align 256 ; on va lire les données directement avec le poids faible comme index
shadeINC
startingindex 0 ; pour démarrer la valeur du compteur du REPEAT à zéro
repeat 256,x
 pixleft=((x&128)>>7)|((x&8)>>2)|((x&32)>>3)|((x&2)<<2)
pixright=((x&64)>>6)|((x&4)>>1)|((x&16)>>2)|((x&1)<<3)
pixleft+=1 : if pixleft==16 : pixleft=0 : endif
pixright+=1 : if pixright==16 : pixright=0 : endif
pl=((pixleft&1)<<7)|((pixleft&2)<<2)|((pixleft&4)<<3)|((pixleft&8)>>2)
pr=((pixright&1)<<6)|((pixright&2)<<1)|((pixright&4)<<2)|((pixright&8)>>3)
defb pl|pr
rend
align 256
sinuziy
ang=0
repeat 256
defb sin(ang)*80+80 : ang=ang+360/256
rend
sinuzix
ang=0
repeat 256
defb sin(ang)*35+35 : ang=ang+360/256
rend

shade_palette defb #54,#5C,#58,#4C,#4E,#47,#4A,#43,#4B,#5B,#53,#5F,#57,#5D,#55,#44,#54,0

Pas mal pour un début non?


Mais pour limiter les pâtés, il faudrait nous faire deux autres tables :
Une avec seulement le pixel droit d'incrémenté, l'autre pour le gauche. En les utilisant dans notre routine de sprite, on pourrait arrondir les angles en haut et en bas de notre rectangle. Ça parait rien comme ça d'enlever 4 pixels, mais ça va tout lisser!
Histoire de compléter les améliorations, on va augmenter la taille des tables de sinus, on va augmenter leur nombre, on va basculer d'une table à l'autre! Bon, ok, on pousse un peu plus que d'habitude, ne vous inquiétez pas, vous reviendrez plus tard sur ce dernier source ;)
BUILDSNA : BANKSET 0
ORG #38 : EI : RET
ORG #100
RUN #100

macro NextLineHL
ld a,h : add 8 : ld h,a ; ajouter #800 à HL
jr nc,@next ; pas de retenue, on est toujours dans le même bloc de lignes
ld a,80 : add l : ld l,a ; on ajoute 80 (largeur d'une ligne en octets) pour passer au bloc suivant
ld a,#C0 : adc h : ld h,a ; et on enlève #4000 (additionner #C000 c'est comme enlever #4000)
@next
mend

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

BouclePrincipale
.phase1init ld a,hi(sinuzix) : ld (lireX+2),a : ld (resetX+1),a : add 2 : ld (cmpx+1),a : call clearScreen
ld hl,0 : ld (.phase1+1),hl
.phase1 ld hl,0 : inc hl : ld a,h : and 15 : cp 15: ld (.phase1+1),hl : jr z,.phase2init
halt : call ExecuteBob : jr .phase1
.phase2init ld a,hi(sinuzix)+2 : ld (lireX+2),a : ld (resetX+1),a : add 2 : ld (cmpx+1),a : call clearScreen
ld hl,0 : ld (.phase2+1),hl
.phase2 ld hl,0 : inc hl : ld a,h : and 15 : cp 15: ld (.phase2+1),hl : jr z,.phase3init
halt : call ExecuteBob : jr .phase2
.phase3init ld a,hi(sinuzix)+4 : ld (lireX+2),a : ld (resetX+1),a : add 2 : ld (cmpx+1),a : call clearScreen
ld hl,0 : ld (.phase3+1),hl
.phase3 ld hl,0 : inc hl : ld a,h : and 15 : cp 15: ld (.phase3+1),hl : jr z,.phase4init
halt : call ExecuteBob : jr .phase3
.phase4init ld a,hi(sinuzix)+6 : ld (lireX+2),a : ld (resetX+1),a : add 2 : ld (cmpx+1),a : call clearScreen
ld hl,0 : ld (.phase4+1),hl
.phase4 ld hl,0 : inc hl : ld a,h : and 15 : cp 15: ld (.phase4+1),hl : jp z,.phase1init
halt : call ExecuteBob : jr .phase4

clearScreen ld hl,#C000 : ld de,#C001 : ld bc,16335 : ld (hl),l : ldir : ret

ExecuteBob
lireX ld hl,sinuzix : ld c,(hl) : inc l : ld b,0 : phase ld a,0 : inc a : and 15 : ld (phase+1),a : jr nz,.noADD : inc l : .noADD
ld a,h : cmpx cp #12 : jr z,noReset : resetX ld h,#12 : noReset ld (lireX+1),hl
lireY ld hl,sinuziy : ld e,(hl) : inc hl : ld a,hi(sinuziy)+2 : cp h : jr nz,.pokeY : ld h,hi(sinuziy) : .pokeY ld (lireY+1),hl
ld l,e : ld h,0
CalculeAdressePixel
; BC=coordonnée X (0-159)
; HL=coordonnée Y (0-199)
; adresse de la ligne dans HL en résultat
ld de,tableau
add hl,hl ; adresses 16 bits, il faut indexer de 2 en 2
add hl,de
ld a,(hl) : inc hl
ld h,(hl) : ld l,a
add hl,bc ; ajouter la position en OCTETS !
shadePut
; HL=adresse écran de départ
ld b,hi(shadeINC) : ld d,2
ld c,(hl) : ld a,(bc) : ld (hl),a : inc hl : ld c,(hl) : inc b : ld a,(bc) : inc b : ld (hl),a : NextLineHL (void)
.loop
ld c,(hl) : ld a,(bc) : ld (hl),a : dec hl : ld c,(hl) : ld a,(bc) : ld (hl),a : NextLineHL (void)
ld c,(hl) : ld a,(bc) : ld (hl),a : inc hl : ld c,(hl) : ld a,(bc) : ld (hl),a : NextLineHL (void)
dec d : jr nz,.loop
ld c,(hl) : dec b : ld a,(bc) : ld (hl),a : dec hl : ld c,(hl) : dec b : ld a,(bc) : ld (hl),a
ret


;-------------------
adresse_ecran=#C000
largeur_ecran=80
tableau
repeat 25
repeat 8
defw adresse_ecran
adresse_ecran+=#800
rend
adresse_ecran+=largeur_ecran
adresse_ecran-=#4000
rend
align 256 ; on va lire les données directement avec le poids faible comme index
shadeINC
startingindex 0 ; pour démarrer la valeur du compteur du REPEAT à zéro
repeat 256,x
 pixleft=((x&128)>>7)|((x&8)>>2)|((x&32)>>3)|((x&2)<<2)
pixright=((x&64)>>6)|((x&4)>>1)|((x&16)>>2)|((x&1)<<3)
pixleft+=0 : if pixleft==16 : pixleft=1 : endif
pixright+=1 : if pixright==16 : pixright=1 : endif
pl=((pixleft&1)<<7)|((pixleft&2)<<2)|((pixleft&4)<<3)|((pixleft&8)>>2)
pr=((pixright&1)<<6)|((pixright&2)<<1)|((pixright&4)<<2)|((pixright&8)>>3)
defb pl|pr
rend
repeat 256,x
 pixleft=((x&128)>>7)|((x&8)>>2)|((x&32)>>3)|((x&2)<<2)
pixright=((x&64)>>6)|((x&4)>>1)|((x&16)>>2)|((x&1)<<3)
pixleft+=1 : if pixleft==16 : pixleft=1 : endif
pixright+=0 : if pixright==16 : pixright=1 : endif
pl=((pixleft&1)<<7)|((pixleft&2)<<2)|((pixleft&4)<<3)|((pixleft&8)>>2)
pr=((pixright&1)<<6)|((pixright&2)<<1)|((pixright&4)<<2)|((pixright&8)>>3)
defb pl|pr
rend
repeat 256,x
 pixleft=((x&128)>>7)|((x&8)>>2)|((x&32)>>3)|((x&2)<<2)
pixright=((x&64)>>6)|((x&4)>>1)|((x&16)>>2)|((x&1)<<3)
pixleft+=1 : if pixleft==16 : pixleft=1 : endif
pixright+=1 : if pixright==16 : pixright=1 : endif
pl=((pixleft&1)<<7)|((pixleft&2)<<2)|((pixleft&4)<<3)|((pixleft&8)>>2)
pr=((pixright&1)<<6)|((pixright&2)<<1)|((pixright&4)<<2)|((pixright&8)>>3)
defb pl|pr
rend

align 512
sinuziy
ang=0
repeat 512
defb sin(ang)*80+80 : ang=ang+180/256
rend
sinuzix
ang=0
repeat 512
defb sin(ang)*35+35 : ang=ang+360/256
rend
ang=0
repeat 512
defb sin(ang)*35+35 : ang=ang+720/256
rend
ang=0
repeat 512
defb 76-abs(sin(ang)*75) : ang=ang+360/256
rend
ang=0
repeat 512
defb sin(ang)*35+35 : ang=ang+888/256
rend

shade_palette defb #54,#5C,#58,#4C,#4E,#47,#4A,#43,#4B,#5B,#53,#5F,#57,#5D,#55,#44,#54,0

Voilà, c'est un peu mieux et ça passe d'une courbe à l'autre, just for fun :)