СПЕКТРУМ + 3D #1
(c)1997 Dark/X-trade and -STS-/VolgaSoft
─────────────────────────────────────────
Данным циклом статей авторы поставили
перед собой задачу показать способы ре-
ализации на спеке "быстрой" 3D графики.
В принципе, эти статьи ориентированы на
начинающего кодера, так как кодер со
стажем и так всё это, вероятно, знает.
Хотя, возможно и он сможет найти для се-
бя что-нибудь интересное (по крайней ме-
ре в алгоритмах). Наша цель - не столько
дать готовые рецепты и шаблоны, сколько
подтолкнуть читателя к дальнейшему само-
стоятельному изучению предмета.
Ничего особо сложного мы здесь объяс-
нять не будем, но тем не менее, понаде-
емся, что Ваше Серое Вещество не выйдет
из берегов, прежде чем Вы достигнете
окончания текста.
A начнем мы с решения несложной задачи:
вращение проволочного обьекта (без отсе-
чения вышедших за экран линий). Для пер-
вой статьи вполне достаточно.
Все что нам нужно, чтобы объект закру-
тился, это его задать и накодить прос-
тенький вычислитель, да быструю процеду-
ру линии.
─────────────────────────────────────────
Те строчки кода, которые Вы увидите в
тексте, писаны для того, чтобы просто
пояснить суть вещей, и НЕ БЫЛИ подверг-
нуты какой-либо капитальной оптимизации.
Рабочий код Вы можете найти на диске в
файле 3DROTATE. Там есть еще 2 файла -
ALGORITM и DIVTABS, подробности о кото-
рых смотрите в конце статьи, в разделе
"Алгоритмы". Все исходники написаны с
использованием специфического синтаксиса
"STORM TURBO ASSEMBLER'а" (который, по
возможности, Вы можете найти тут же или
в ZX-Format'е #7). Кое-что о синтаксисе
Storm'а Вы также сможете найти разделе
"Алгоритмы". Файлы даны в текстовом ви-
де, чтобы посмотреть их можно было бы
вне ассемблера. Примеры в тексте, по
возможности, даны в классическом синтак-
сисе. Для загрузки их в STORM следует
воспользоваться функцией импорта тексто-
вого файла - BREAK+Т.
─────────────────────────────────────────
Итак, зададим обьект. Пусть это будет
простая пирамида.
Мы будем описывать его как набор ребер
(edge) каждое из которых протянуто меж-
ду двух вершин (vertex). Вершины заданы
в т.н. обьектном пространстве, т.е. от-
носительно центра вращения обьекта.
Массив выглядит примерно так:
PIRAMID DW edge
vertex ;DB хп, yn, zn
DB 0,40,-20 ;основание
DB 60,-20,-20 ;//
DB -60,-20,-20 ;/
DB 0,10,40 ;пик
DB #80 ;конец
edge ;DB из_точки_М, в_точку_N
DB 0,1 ;соединение основания
DB 1,2 ;//
DB 2,0 ;/
DB 0,3 ;соед. пика с основанием
DB 1,3 ;//
DB 2,3 ;/
DB -1 ;конец
Объект вроде задали. Теперь займемся вы-
числителем.
Для вращения обьекта достаточно провер-
нуть его вершины. Вращение происходит
относительно нуля в том же самом объект-
ном пространстве по следующим формулам:
воркуг оси Z
X' =х*cos AZ + y*sin AZ
Y' =y*cos AZ - х*sin AZ
воркуг оси Y
Z' =z*cos AY + х'*sin AY
X''=х'*cos AY - z*sin AY
воркуг оси X
Y''=y'*cos AX + z'*sin AX
Z''=z'*cos AX - y'*sin AX
Естественно, мы будем вычислять данные
формулы в реалтайме и без помощи всяких
там гигантских таблиц типа A=В*SIN C,
для всех возможных значений В и C, так
как, естественно, это годится лишь для
интры или небольшой демы.
Пишем сначала процедуру вида:
Rotate (а,b,angle)
{
ат=а*cos+b*sin
b=b*cos-а*sin
а=ат
}
Допустим, входные и выходные параметры у
неё будут 8-битные:
IN :D,Е=Y,X (SGN) C=ANGLE
OUT:D,Е=Y,X (SGN)
;эта процедура представлена просто для
иллюстрации.
;так как в 3DROTATE применена более
быстрая.
ROTATE LD Н,COSTB[
LD L,C
LD C,(HL)
LD A,L:SUB #40:LD L,A
LD В,(HL)
;В=sin C=cos
LD L,C:LD Н,Е
CALL MULS ;Н=Н*L (со знаками)
LD A,Н:ЕХА
;A'=X*COS
LD L,В:LD Н,D
CALL MULS
ЕХА:ADD A,Н:LD LX,A
;LX=X*COS+Y*SIN
LD L,C:LD Н,D
CALL MULS
LD A,Н:ЕХА
;A'=Y*COS
LD L,В:LD Н,Е
CALL MULS
ЕХА:ADD A,Н:LD D,A
;D=Y*COS-X*SIN
LD Е,LX
;Е=X*COS+Y*SIN
RET
Пишем финальную процедуру:
Rot3D (х,y,z,rotX,rotY,rotZ)
{
Rotate (х,y,rotZ)
Rotate (z,х,rotY)
Rotate (y,z,rotX)
}
IN :D,Е,C=Y,X,Z
OUT:D,Е,C=Y,X,Z
ROT3D PUSH ВС
LD ВС,(ROTZ)
;вращаем х,y на угол rotZ
CALL ROTATE;XY
РОР ВС
;заменяем t=y; y=х; х=z
LD В,D:LD D,Е:LD Е,C
;вращаем z,х на угол rotY
PUSH ВС
LD ВС,(ROTY)
CALL ROTATE
РОР ВС
;заменяем z=y; y=z; х=t
LD C,D:LD D,Е:LD Е,В
;вращаем y,z на угол rotX
PUSH ВС
LD ВС,(ROTX)
CALL ROTATE
РОР ВС
;заменяем t=y; Y=х; X=z; Z=t
LD A,D:LD D,Е:LD Е,C:LD C,A
RET
ROTX DB 0
ROTY DB 0
ROTZ DB 0
Итак, у нас есть повернутая вершина.
Для достижения нашей цели достаточно
перевести её в экранное пространство
(спроецировать на экран). Будем считать,
что ось Z у нас направлена от наблюдате-
ля вглубь экрана.
Наиболее часто используемых проекций
две:
- Параллельная
Величина Z просто не учитывается.
Xr=X''+Xoffset
Yr=Y''+Yoffset
- Перспективная
Обычно используется формула
Xr=((X''+Xcnt)*Scale/(Z+Zcnt))+Xoffset
Yr=((Y''+Ycnt)*Scale/(Z+Zcnt))+Yoffset
! Z+Zcenter у видимой точки не может
быть <=0
Scale - коэффициент масштабирования.
Он влияет на угол обзора и подбирается
по вкусу.
X/Yoffset - центр экрана (#80,#60)
X/Y/Z cnt (center) - центр обьекта в 3D.
X/Yr (real)- координаты точки на экране.
Полученные значения Xr и Yr складывают-
ся в буфер спроецированных вершин.
DOTS2D ds количество вершин*2
Воспользуемся перспективной проекцией.
Коэффициент масштабирования (scale) яв-
ляется константой, поэтому разумно изба-
вится от умножения на него, заменив ум-
ножение сдвигом или перекодировкой по
таблице. Мы примем коэффициент равным
256.
Все эти действия (начиная с вращения и
до сих пор) выполняются для всех вершин,
коих у нас 4.
Всё это делает процедура:
PROJECT ;PROJECT ALL VERTEXES
LD HL,(OBJECT)
INC HL:INC HL
;HL=вершины
LD IX,DOTS2D
PROJ LD A,(HL):INC HL
СР #80
;признак конца
RET Z
LD Е,A
LD D,(HL):INC HL
LD C,(HL):INC HL
PUSH HL
CALL ROT3D
LD A,CENTERZ:ADD A,C:LD L,A
LD A,CENTERY:ADD A,D:LD Н,A
PUSH HL
LD A,CENTERX:ADD A,Е:LD Н,A
CALL FDIVB; Н.L(SGN)=Н(SGN)/L
LD A,OFFSETX:ADD A,L
;L=Н.L*256 (Н=0)
LD (IX),A:INC LX
;отписали Xr
РОР HL
CALL FDIVB
;Y координата экрана идет сверху вниз
;а в преобразованиях снизу вверх
LD A,CENTERY:SUB L
;а не выходит ли Yr за экран?
;отсечем заранее, чтоб потом не
;заботиться
СР #C0:JR C,$+4:LD A,#BF
LD (IX),A:INC LX
;отписали Yr
РОР HL
JR PROJ
Массив DOTS2D должен быть расположен
по красивому адресу (младший байт=0),
Осталась самая малость - лишь соединить
точки линиями, однако эта малость отни-
мает львиную долю времени. Для этого для
каждого ребра берем из массива edge (см.
выше) номера двух вершин - N1 и N2
X1=DOTS2D[N1*2]
Y1=DOTS2D[N1*2+1]
X2=DOTS2D[N2*2]
Y2=DOTS2D[N2*2+1]
и рисуем линию между точками (х1,y1) и
(х2,y2).
Для этого существует процедура RENDER,
которая выводит объект на экран (предва-
рительно очищенный). В принципе эта про-
цедура должна заботится об отсечении ли-
ний, уходящих за экран, однако мы этого
делать не будем. Поэтому заранее ограни-
чили диапазоны чисел, тех, что в буфере.
;Вывод обьекта на экран
RENDER ;RENDER OBJECT (EDGES)
LD HL,(OBJECT)
LD Е,(HL):INC HL
LD D,(HL)
;DE=ребра
RENDR LD Н,DOTS2D[
LD A,(DE):INC DE
;из вершины ...
ADD A,A:LD L,A
;признак конца
RET C
LD C,(HL):INC L
LD В,(HL)
;получили х1,y1
LD A,(DE):INC DE
;в вершину ...
ADD A,A:LD L,A
PUSH DE
LD Е,(HL):INC L
LD D,(HL)
;получили х2,y2
CALL LINE
РОР DE
JR RENDR
Прорисовка линии занимает львиную долю
времени.
В Алгоритмах Вы найдете самую быструю
из всех существующих на_данный момент
на Спектруме реализацию линии (причем,
с возможностью дальнейшего увеличения
быстродействия).
----------------------------------------
Итак, главный цикл должен быть примерно
такой:
LD HL,PIRAMID
LD (OBJECT),HL
MAIN CALL EXGVIEW;Обмен экранов
CALL PROJECT;Проекция
CALL RENDER ;Вывод в теневой
;экран
LD HL,ROTX ;Изменение углов
.1 INC (HL)
INC HL
.3 DEC (HL)
INC HL
.2 INC (HL)
LD A,#7F:IN A,(#FE)
RRA:JR C,MAIN
RET
ROTX DB 0
ROTY DB 0
ROTZ DB 0
OBJECT DW 0
Как видите, все это очень просто, проще
и быть не может.
Вообще-то, для объектов с большим коли-
чеством вершин и, особенно, со сложными
преобразованиями (поворотами вокруг про-
извольной оси, масштабированиями) очень
выгодно строить матрицу преобразований,
тогда для все что нам надо сделать с
вершиной - это выполнить 9 умножений и
записать X и Y в буфер... Как нибудь,
возможно, мы поработаем и с матрицами.
Забегая вперед, скажем, что в следую-
щей статье будет описан метод, который
позволяет с легкостью обрабатывать объ-
екты, состоящие из сотни вершин, но для
такого простого обьекта он не эффективен.
Other articles: