3D engine: fragments
Alone Coder
1. Drawing a filled polygon
Classic scanlines for the training ground,
filled with texture:
- Second Breath:200..500 t
- Iris Ultrademo:377 t
- 3D Lame:>400 t
- Awaken demo:258 t(linear screen
buffer)
Our scanline is also for linear screen
buffer (l=Y ), the transfer time of which
will drown in the time of drawing the set by─
ligons. And it is optimized for the three most
common cases:
- width 1 byte (left and right sides
in one byte) =167t
- width 1+0+1 bytes =223t
- width 1+1+1 bytes =234t
And then adding more width with each byte─
available11t. It's faster than LDI.
DRSNO
inc e ;Y
exx
add hl,de ;left
add ix,bc ;right
dec hy
jp z,DRSKEEP;обработка конца текущего
;левого или правого ребра
ld a,h;XL
exx
ld l,a;XL
ld b,(hl);leftmask
inc h
ld a,(hl);endx(L)
ld d,hx;XR
ld l,d
ld d,(hl);beginx(R)
dec h
sub d;beginx
jp z,DRSNSAME;если лев.и прав. стороны
;в одном байте
jr nc,DRSNO;если отрицательная ширина
add a,a
ld (DRSjr),a
ld a,(de)
xor c;texture
cpl
or (hl)
cpl
xor c;texture
ld (de),a
dec d
ld a,c;texture
DRSjr=$+1
jp DRSLOOP
...
DRSLOOP ;лев.и пр.стороны не в одном байте
dup 31;максимальная ширина = 256 пикс
ld (de),a;texture
dec d
edup
ld a,(de)
xor c;texture
and l
xor c;texture
ld (de),a
...(на начало, реально 2 копии начала)
DRSNSAME ;лев.и прав.стороны в одном байте
ld a,(hl);rightmask
cpl
or b;leftmask
ld l,a
ld a,(de)
xor c;texture
and l
xor c;texture
ld (de),a
...(на начало, реально 2 копии начала)
Чтобы сделать текстуру не на интерлейс─
ном экране,достаточно продублировать цикл,
а во второй копии цикла вместо регистраc
использоватьly.
2. Проверка видимости полигона
Нам нужно узнать знак одной проекции
векторного произведенияone rib per
other:
x21 := vert[poly[i].v2].xscr
-vert[poly[i].v1].xscr;
x31 := vert[poly[i].v3].xscr
-vert[poly[i].v1].xscr;
y21 := vert[poly[i].v2].yscr
-vert[poly[i].v1].yscr;
y31 := vert[poly[i].v3].yscr
-vert[poly[i].v1].yscr;
poly[i].visible := ((x21*y31-x31*y21) >0);
Usually they actually do this
calls to signed multiplications (sometimes even
face normal rotation is used), but we
let's go the other way.
;bc=y1x1
;de=y2x2
;hl=yЗxЗ
ld a,h;y1
sub b;y3
ld h,a;-dy2 = -y31
add a,e;x2
sub c;x1
;a += (dx1 = x21)
;e*b => a (dx1*dy2)
ld e,a;(A/2-(-b)/2)
sub h
sub h;b+(A/2-(-b)/2)
ld h,a
ld a,d;y1
sub b;y2
ld d,a;-dy1 = -y21
add a,l;x3
sub c;x1
;a += (dx2 = x31)
;a*d => a (dx2*dy1)
ld b,a;(A/2-(-d)/2)
sub d
sub d;d+(A/2-(-d)/2)
ld l,a
ld d,h
ld h,tsqr/256;таблица квадратов
ld a,(hl)
ld l,b
sub (hl);a=(dx2*dy1)=-63..+63
;вычесть e*b => a (dx1*dy2)
ld l,d
sub (hl) ld l,e
add a,(hl);(dx1*dy2)=-63..+63
;a=-126..+126 (7th bit contains visibility)
Total123 clock cycles.
In reality, in the current code there are several
crutches for precision using our
technique of “zooming” an object.
And you can always turn it on instead
16-bit code(VISIBILITY16=1).
3. Drawing a short line
Usually they optimize the case of a long li─
nii, that is, they look at how many ticks the ear─
dit by one pixel. But on big stages
on the contrary - there are many short lines. Therefore
you need to look at how many clock cycles it takes
piping of the line - at the entrance and exit.
How long does strapping take in known
engines:
- Dies Irae =192..227t
- Second Breath =222..645t
- Iris Ultrademo = average458t
- X-Trade = on average533t (30..44t/pix
horizontal,40..49t/pixvertical)
- Cheburashka = on average244t(60t/pix)
We will do something extreme - for everyone
short line we will generate a sprite. And so that
don’t get up twice, we’ll generate it right away─
mo in the form of a procedure. Let's draw again in
linear screen buffer.
;de=xy1
;hl=xy2
;x=#80..#ff ;so we won’t go off scale in H
;y=#80..#ff
ld a,d;x1
sub h;a=dx=-31..+31
cp 8
jp p,line_slow_p
cp -7
jp m,line_slow_m
add a,jumptable_center/256
ld h,a
ld a,e;y1
sub l;a=dy=-31..+31
add a,a;a=2*dy=-62..+62
jp pe,line_slow_dx;overflow
add a,a;a=4*dy=-124..+124
jp pe,line_slow_dx;overflow
ld l,a;4*dy
ld c,d;for restoration in long
sra d
jp (hl);there jp drawspr_thisangle
;(with output in line_slow_dy)
The code for short lines looks like this:
...00
; 00 (set n,(hl):inc/dec...)
ret
...0x
sra h
jr nc,...00
; 01 (set n,(hl):inc/dec...)
ret
...10
; 10 (set n,(hl):inc/dec...)
ret
drawspr_thisangle
ex de,hl
;l=y=#80..#ff (since we draw only even
;lines for motion blur)
;h=x=#80..#ff (precision 2 pixels)
jr nc,0x
sra h
jr nc,10
;hl=scrbuf+...:
;h=f(x)=#e0..#ff,
;l=f(y)=#80..#ff (the remaining l are free)
; 11 (set n,(hl):inc/dec...)
ret
Some pieces of code can be optimized─
pass throughld a,(hl):or N:ld (hl),a and inc
(hl).
You can also draw on a non-linear screen,
if you add code like:
;e=x
;l=y
ld d,tX/256
ld a,(de) ;l(X)
ld h,tY/256
add a,(hl);l(Y)
inc h
ld h,(hl) ;h(Y)
ld l,a
ld a,h
or 7
And instead of incrementing vertically, use─
use, for example:
cp h
call z,dhlchr
inc h
For example, for an object from80polygons,
120 lines (10 pixels) and 40 vertices are possible
achieve the following indicators:
- vertices (without perspective):118*40=4720
- polygons:165*80 = 13200
- lines:41*60 + (79+460)*60 = 34800
- erase (128*128=2K)= 11264
Total63984 clock cycles, that is, you can even
frame.
Line code (more5 kilobytes) generated─
is a rather complex generator that
also combines similar lines. More4 kilo─
bytes (15 segments) are occupied by transitions.
This is for the maximum line size7
px. You can customize the maximum size
up to31 pixels.
True, in the current version of the 3D engine this
the code is generally turned off, because the emphasis is de─
lan to filled polygons, and memory to 48K
not rubber. All lines are considered long
and are drawn through slow code with clipiro─
vaniyam.
But in case someone needs it...
4. Rotate verticeswith forward correction
Let the rotation matrix be calculated (see
rotmatrix.asm - formulas are also given there
for the case of rotation along two and three axes),
and after that for each of its cells sge─
the multiplication table for this cleto is nerated─
point (each table has a total of32 values
coordinates in the object: from0 to 31 ). Let's call
these are "serif tables". Serif tables
are constructed by simple addition:
dup 15
ld c,h
push bc
add hl,de
ld b,h
add hl,de
edup ;from de*0 to de*29
ld c,h
push bc;from de*30 to de*31
Let the coordinates of the vertices in the object be about─
written directly in the code (with the addition of offset─
locating the beginning of the corresponding session table─
check). If something happens, the code can be patched.
We rotate along three axes. For two axes (then
yeswithout roll rotation) only lost─
to one term in one of the coordinates.
Let the negative coordinates of the vertices in
the object is registered with a code so as not to unravel─
double the serif tables.
ld de,#100*_z+_x
ld hl,CUTS+_y;serifs
ld a,(hl);or xor a:sub (hl)
ld l,d;_z
add/sub (hl);(=0 without roll)
ld l,e;_x
add/sub (hl)
ld c,a;x'
inc h
ld a,(hl);or xor a:sub (hl)
ld l,d;_z
add/sub (hl)
ld l,_y
add/sub (hl)
ld b,a;y'
inc h
ld a,(hl);or xor a:sub (hl)
ld l,d;_z
add/sub (hl)
ld l,e;_x
add/sub (hl)
;a = z'
;b = y'
;c = x'
call div8xinch
;bc = received screen coordinates
push bc
Procedurediv8xinch makes perspective─
correction taking into account the "zoom" of the object:
;a = z
;b = y
;c = x
div8xinch
inc h;table 1/z
;a=-127..+127
...;shift a,b,c through sra
;according to the current "zoom"
;a=-63..+63
add a,#40;Z object (patchable)
ld e,a
ld a,c;x
add a,0;X object (patchable)
;a/e => c
;f=flags after adding two signed ones
;+-8/+7 => +-1.6 (i.e. you can have a perspective in
;double targetscreen, but real
;depends on the divisor - at e=255 it will be
;only one screen section)
;for correct division you need a < 2*e
;(otherwise you need to generate an overflow)
jp po,div8xposjp;pe=overflow
;S=9th bit of the result is inverse
jp m,div8xpos
div8xneg
DIV8NEG div8xpos,div8x_128
jp div8y
div8x_128 ;output on overflow DIV8NEG
ld l,e ;Z+z
ld d,(hl) ;1/(Z+z)
ld h,trotsqrpos/256
ld c,128
jp div8y
div8x_127 ;output on overflow DIV8POS
ld l,e ;Z+z
ld d,(hl) ;1/(Z+z)
ld h,trotsqrpos/256
ld c,127
jp div8y
div8xposjp
;S=9th bit of the result
jp m,div8xneg
div8xpos
DIV8POS div8xpos,div8x_127
jp div8y
At this moment, the correctx in
screen taking into account the “flattening” of the object, front─
prospective correction and “flattening” of re─
division completion. We do the same fory:
div8y
ld a,b ;y
add a,0 ;Y object (patched)
;a/e => b
;f=flags after adding two signed ones
;+-8/+7 => +-1.6 (i.e. you can have a perspective in
;double screen, but real
;depends on the divisor - at e=255 it will be
;only one screen section)
;for correct division you need a < 2*e
;(otherwise you need to generate an overflow)
jp po,div8yposjp;pe=overflow
;S=9th bit of the result is inverse
jpm,div8ypos
div8yneg
DIV8NEG div8ypos,div8y_128
ret
div8y_128
ld b,128;переполнение DIV8NEG
ret
div8y_127
ld b,127;переполнение DIV8POS
ret
div8yposjp
;S=9-й бит результата
jp m,div8yneg
div8ypos
DIV8POS div8ypos,div8y_127
ret
Вот как выглядит деление(X+x)/(Z+z)
для случая положительных аргументов:
sub e
jr c,$+2+1+2;CY:a cp e
jr nc,div8_127addr;CY:a<2*e ;выход по
;переполнению
add a,e
ld l,e
ld d,(hl);1/(Z+z)
inc h
sub d
ld l,a;a-e
add a,d
add a,d
ld c,a;a+e
;a-e = -#6b..#70
ld a,(hl);-sqrsigned
inc h
ld l,c;a+e
;a+e = #11..#b5
add a,(hl);sqrpos
srl a;лишний бит точности
add a,XADD
ld c,a
Для(Y+y)/(Z+z) немного короче, потому
что1/(Z+z) ужепрочитано в регистр d:
sub e
jr c,$+2+1+2;CY:a cp e
jr nc,div8_127addr;CY:a<2*e ;выход по
;переполнению
add a,e
dec h
sub d;1/(Z+z)
ld l,a;a-e
add a,d
add a,d
ld d,a;a+e
;a-e = -#6b..#70
ld a,(hl);-sqrsigned
inc h
ld l,d;a+e
;a+e = #11..#b5
add a,(hl);sqrpos
srl a;лишний бит точности
add a,YADD
ld b,a
Of course, in real code they are written
crutches, macros and patches. Study locally.
But this is for the completely fearless :)
5. Rotate an object
Objects are converted to a coordinate system
cameras without taking into account the angle of rotation of the object from─
relative to the vertical axis (unlike
rotation of the vertices of the same object). But this
not the only reason why we spin
objects not according to the rotation matrix. Just have
this matrix is insufficiently accurate, and EC─
whether to calculate it in16 bits, then we only─
but we will lose in speed.
But the fact is that all objects are transformed─
are identical, allows you to patch yourself
16-bit multiplications that occur
turn!
Also, we don't need all the precision
sines-cosines, because in each from─
in a proper frame this is only reflected in the
camera positions.
In addition, if we rotate the camera then─
only on two axes (without roll), we need
only two cross multiplications, and
you can count two multiplications at once─
nom adder!
Code for multiplying simultaneously by two cons─
tants (rotation matrix cells):
;bc,de=+-10.0=%ssssssxx xxxxxxxx
;result in hl = k1*bc + k2*de
xor a; in theory 1 time out of 4, but you need CY=0
ld h,a
ld l,a
sbc hl,bc;или патчем #ed:nop
or a
sbc hl,de;или патчем #ed:nop
add hl,hl
dup 5;6
add hl,bc;или патчем nop
add hl,de;или патчем nop
add hl,hl
edup
org $-1
add hl,hl
sbc a,a
add hl,hl
rla
add hl,hl
rla
ld l,h
ld h,a
Для одного перекрёстного вращения нужно
два таких фрагмента.
Патчи нужно делать только один раз на
кадр примерно таким кодом:
...
rl e
sbc a,a
and c;"add hl,bc"
ld (hl),a
inc hl
rl d
sbc a,a
and b;"add hl,de"
ld (hl),a
inc hl
inc hl
...
В итоге со всеми входами-выходами всё
вращение объекта занимает 1184 такта, а
для объектов за спиной происходит экстрен─
ный выход всего за358 тактов! Это потому,
что для выхода нужна только половинка од─
ного перекрёстного вращения. Разумеется,
такое срабатывает только при ограниченном
угле тангажа. Если это не ваш случай, то
экстренный выход можно убрать.
Share your thoughts about the article