Из журнала Demo Or Die #1, 1999 __________________________________________ (C) by Wolf/eTC/Scene Заливка треугольника. Гуpо shading. Наложение текстуры. Исходя из того, что в компьютерной 3D-графике все 3D-объекты в основном состоят из элементарных треугольников, в данной статье будет описываться алгоритм заливки одноцветного треугольника. Так как на Спектруме в нормальном режиме (50fps), в каждой точке не может быть свой цвет, то мы рассмотрим треугольник залитый белым цветом.
Возьмем любой треугольник с точками ABC. Где A.X - координата X точки A, а A.Y - координата Y точки A соответственно и т.д. Заливать его будем горизонтальными линиями, т.к. этот метод является самым быстрым и простым. Прежде всего нам необходимо отсортировать точки треугольника ABC по координате Y по возрастанию. Минимальную координату Y имеет точка A, максимальную точка C, а среднее между ними значение точка B. Теперь зная что необходимо заливать треугольник горизонтальными линиями от точки A до точки C, необходимо визуально построить этот треугольник по линиям (не заливая его), но не рисуя его на экран, а сохраняя в буфер координаты X всех трех сторон образующих треугольник. Т.е. мы будем иметь два массива edge1[hgt_screen] и edge2[hgt_screen], где hgt_screen - это высота экрана. В этих массивах и будут располагаться координаты X, которые и необходимо соединять горизонтальными линиями между собой. Для простоты и понимания, мы будем строить визуально линию путем интерполяции X координат. Для этого пусть у нас будет процедура интерполяции inter(x1,x2,len,buffer), где x1 - первая координата (от куда), x2 - вторая координата (куда), len - кол ичество шагов, buffer - адрес буфера, куда процедура будет помещать интерполированные значения. Для тех кто не знает, то интерполяция (в данном случае применяется линейная интерполяция) - это отыскание значений функций, соответствующим промежуточным значениям аргумента, которые отсутствуют в таблице (это нам так в институте говорили). Ну а если объяснять на "пальцах", то вот пример интерполяции: X1 = 3 X2 = 9 len = 3 При таких начальных значениях, интерполяционные значения будут следующими 3,6,9. Вот даже вам математическая формула: K=(1-step)*X1+step*X2 где K - промежуточное недостающее значение, step - шаг, который должен меняться в пределах [0...1], а X1 и X2 - начальное и конечное значение соответственно. Вот пpимеp на асме: ;++++++++++++++++++++++++++++++++++++++; ; INTERPOLATIONS ; ; ; ; best idea: Wolf/eTc/ScEnE ; ; perfect code: Devil/eTc/sCeNE ; ; ; ; HL = BUFFER ; ; E = LEN OF BUFFER ; ; B = X1 ; ; A = X2 ; ;++++++++++++++++++++++++++++++++++++++; INTER LD HX,B CP B LD C,A LD D,A LD A,#04 JR NC,INTER6 LD A,#05 LD D,B LD B,C INTER6 LD (INTER5),A LD (INCDEC),A LD A,D SUB B LD B,HX CP E JR C,INTER0 JR Z,INTER0 INTER3 LD C,A DEC E LD D,E LD (HL),B INTER4 SUB E JR NC,INTER5 INC L INC L LD (HL),B ADD A,C DEC D RET Z INTER5 INC B JR INTER4 RET INTER0 INC A LD C,A LD D,E LD A,E INTER1 SUB C JR NC,INTER2 ADD A,E INCDEC INC B INTER2 LD (HL),B INC L INC L DEC D JR NZ,INTER1 RET Итак, построим визуально линии треугольников (только координаты X) в массивы: {для понимания, на Паскале} procedure visual_draw; begin inter(A.x,C.x,abs(A.y-C.y),edge1); inter(A.x,B.x,abs(A.y-B.y),edge2); inter(B.x,C.x,abs(B.y-C.y),edge2+ abs(A.y-B.y)); end; {для тех кто не знает, функция abs() - это модуль числа} Проинтерполировав все эти координаты, мы имеем два массива с координатами X-ов всех трех сторон треугольника. Причем в одном массиве одна сторона, а в другом две остальные стороны. Теперь нам ничего не остается делать, как написать процедуру построения горизонтальной линии. Назовем ее Hline(x1,x2,y), где x1 и x2 - горизонтальные координаты линии, а y - собственно координата y этой линии. И затем "залить" треугольник горизонтальными линиями, следующим циклом: y:=A.y; for cycle:=1 to abs(C.y-A.y) do begin Hline(edge1[cycle],edge2[cycle],y); inc(y); end; Вот и все, теперь если вы все поняли, вы умеете заливать треугольники каким либо цветом. А теперь я расскажу алгоритм заливки того же самого треугольника по Гуро. Гуро - это линейная интерполяция освещенности по изображению грани на экране. Для этого нам необходимо создать еще два массива color1[hgt_screen] и color2[hgt_screen], которые будут хранить интерполяционные значения освещенности. А также назначить каждой точки треугольника интенсивность освещенности (A.c, B.c, C.c, где c - интенсивность освещенности). Вот процедура заполнения граничных массивов для треугольника по Гуро, она такая же самая как и для заливки треугольника произвольным цветом, но с добавлением интерполяции освещенности: procedure visual_draw_gouround; begin inter(A.x,C.x,abs(A.y-C.y),edge1); inter(A.x,B.x,abs(A.y-B.y),edge2); inter(B.x,C.x,abs(B.y-C.y),edge2+ abs(A.y-B.y)); inter(A.c,C.c,abs(A.y-C.y),color1); inter(A.c,B.c,abs(A.y-B.y),color2); inter(B.c,C.c,abs(B.y-C.y),color2+ abs(A.y-B.y)); end; Имея процедуру построения горизонтальной линии с линейной интерполяцией цветов Hline_gouround(x1,x2, c1,c2,y), где x1 и x2 - начальное и конечное значение горизонтальной координаты, а c1 и c2 - начальный и конечный цвет, мы напишем процедуру заливки треугольника по модели освещения Гуро: y:=A.y; for cycle:=1 to abs(C.y-A.y) do begin Hline_gouround(edge1[cycle], edge2[cycle],color1[cycle], color2[cycle],y); inc(y); end; С таким же успехом сюда же можно "прицепить" и наложение текстуры, но при этом необходимо каждой точке треугольнику назначить координаты текстуры (u,v), где u - координата X в текстуре, а v - координата Y в текстуре. Ну еще и четыре массива создать. edge1u[hgt_screen]; edge1v[hgt_screen]; edge2u[hgt_screen]; edge2v[hgt_screen]; Тогда процедура визуализации для текстурного треугольника будет выглядеть следующим образом: procedure visual_draw_texture; begin inter(A.x,C.x,abs(A.y-C.y),edge1); inter(A.x,B.x,abs(A.y-B.y),edge2); inter(B.x,C.x,abs(B.y-C.y),edge2+ abs(A.y-B.y)); inter(A.u,C.u,abs(A.y-C.y),edge1u); inter(A.u,B.u,abs(A.y-B.y),edge2u); inter(B.u,C.u,abs(B.y-C.y),edge2u+ abs(A.y-B.y)); inter(A.v,C.v,abs(A.y-C.y),edge1v); inter(A.v,B.v,abs(A.y-B.y),edge2v); inter(B.v,C.v,abs(B.y-C.y),edge2v+ abs(A.y-B.y)); end; А теперь осталось только написать процедуру заливки треугольника необходимой текстурой, но для этого нам еще понадобится процедура построения горизонтальной линии со значениями цветов из текстуры Hline_texture(x1,x2, u1,v1,u2,v2,y), где x1 и x2 - начальное и конечное значение горизонтальной координаты, u1 и u2 - начальное и конечное значение X в текстуре, v1 и v2 - начальное и конечное значение Y и координата y, которая указывает где выводить горизонтальную линию на экране. А вот и сама процедура заливки: y:=A.y; for cycle:=1 to abs(C.y-A.y) do begin Hline_texture(edge1[cycle], edge2[cycle],edge1u[cycle], edge2u[cycle], edge1v[cycle], edge2v[cycle],y); inc(y); end; Конечно в данной статьи я не описывал методы оптимизации данных процедур(это будет в следующем номере), но все таки хотелось бы сказать пару слов. Учитывая то, что на Спектруме - Гуро, наложение текстуры и т.д. делается при помощи "чанков" 4*4, можно заметить что текстуры для "чанков" сильно "большими" не бывают (т.к. "чанковкий" экран имеет размер 64*48). Из этого следует, что лучше всего располагать текстуру в памяти по адресам кратным 256 (#c000,#c100,#c200 ...). Я это делаю так: org 25000 call init ... start: halt call action ... jp start org ($-1)/256*256+256 texture1 incbin "morda" Это необходимо для того, что бы вы интерполировали не просто координаты текстуры, а сразу же их адреса. Старший байт адреса будет соответствовать координате Y, а младший байт координате X текстуры. И тогда проинтерполировав старшие и младшие адреса текстуры в буфер, вы уже имеете абсолютные их адреса. Вот вроде пока все на сегодня. Если я как-то не правильно выразился или у вас возникли вопросы, то пишите мне по таким адресам: 2:4635/8.18 или scence#usa.net. __________________________________________