СПЕКТРУМ + 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 в буфер... Как нибудь, возможно, мы поработаем и с матрицами. Забегая вперед, скажем, что в следую- щей статье будет описан метод, который позволяет с легкостью обрабатывать объ- екты, состоящие из сотни вершин, но для такого простого обьекта он не эффективен.