Из журнала Demo or Die #2, 1999 (c) by Wolf of eTc/Scene Phong Shading (fake phong - все просто и красиво). Истина - это заблуждение, длящееся столетие; заблуждение же - это истина, пpосуществовавшая минуту. (C) Головачев Как сказал мне один товарищ по Fido: "True phong - тупизна, это конечно imho. Фонг - это тот же Гуро, только интерполируются не значение интенсивности цвета, а значения вектора внешней нормали, которое затем используется для рассчета цвета." Хочу добавить, что интерполяция вектора внешней нормали происходит в пределах от -1 до 1 (Если нормаль нормирована, т.е. приведенная к единичной длине). 1. Прежде всего, немного линейной алгебры. Вектор нормали к плоскости - это такой вектор, который перпендикулярный плоскости относительно начала координат. Нормирование нормали - это приведение нормали к единичной длине, это можна делать вот так: Len=sqrt(x*x+y*y+z*z) x=x/len y=y/len z=z/len x,y,z - координаты вектора нормали. Но я так не делаю, лучше всего нормировать не от -1 до 1, а в каких-то других удобных пределах, например от -64 до 64, тогда все будет выглядить следующим образом: Len=sqrt(x*x+y*y+z*z) x=x*64/len y=y*64/len z=z*64/len И последнее, это векторное произведение. Формула конечно не без умножения, но что поделаешь: V3.x=V1.y*V2.z-V1.z*V2.y V3.y=V1.z*V2.x-V1.x*V2.z V3.z=V1.x*V2.y-V1.y*V2.x V3 - это векторное произведение векторов V1 и V2. 2. Precalculations for Phong Shading Исходя из того, что к любому трехмерному объекту, нормаль меняется в каждой грани, нам необходимо посчитать нормали к каждой вершине объекта. Нормаль в вершине считается как среднее арифметическое всех нормалей к тем граням, которые принадлежат данной вершине. А нормаль к грани считается так: Пусть есть у нас три вершины треугольника, заданные по часовой стрелке: V1(x,y,z) V2(x,y,z) V3(x,y,z), тогда V1'=V3-V1 V2'=V2-V1 А сама нормаль уже считается как векторное произведение двух векторов V1' и V2': normal=vector_mul(V1',V2') (умножение описано выше:) Вот процедура рассчета нормалей для каждой грани на C, т.к. на Basic'e это будет выглядеть извратно: // part of 3D-engine (c) by Wolf of eTc group/Scene // Рассчет нормалей к граням for (i=0;i<polygons;i++) { px1=points_e[faces[i][0]][0]; py1=points_e[faces[i][0]][1]; pz1=points_e[faces[i][0]][2]; px2=points_e[faces[i][1]][0]; py2=points_e[faces[i][1]][1]; pz2=points_e[faces[i][1]][2]; px3=points_e[faces[i][2]][0]; py3=points_e[faces[i][2]][1]; pz3=points_e[faces[i][2]][2]; v1x=px1-px2; v1y=py1-py2; v1z=pz1-pz2; v2x=px1-px3; v2y=py1-py3; v2z=pz1-pz3; nx=v1y*v2z-v1z*v2y; // Векторное // произведение ny=v1z*v2x-v1x*v2z; nz=v1x*v2y-v1y*v2x; // нормализация в пределах -127..127 len=sqrt(nx*nx+ny*ny+nz*nz); if(len!=0) { normals[i][0]=nx*127/len; normals[i][1]=ny*127/len; normals[i][2]=nz*127/len; } else { normals[i][0]=0; normals[i][1]=0; normals[i][2]=0; } } } 3. Процедура для рассчета нормалей в вершинах объекта. // part of 3D-engine (c) by Wolf of eTc group/Scene // Рассчет нормалей к вершинам объекта void GetNList(void) { int i,j,h,nx,ny,nz; // заполняем массив ноpмалей нулями for(i=0;i<vertex;i++) { Nlist[i][0]=0; Nlist[i][1]=0; Nlist[i][2]=0; } for(i=0;i<vertex;i++) { h=1; for(j=0;j<polygons;j++) { if(faces[j][0]==i || faces[j][1]==i || faces[j][2]==i) { Nlist[i][0]+=normals[j][0]; Nlist[i][1]+=normals[j][1]; Nlist[i][2]+=normals[j][2]; h++; } } Nlist[i][0]/=h; Nlist[i][1]/=h; Nlist[i][2]/=h; } } 4. Исходник рассчета нормали к треугольнику. ;(c) by Wolf of eTc/Scene PARAM EQU 32; NORMALIZE -61...32 ;Параметры нормализации, обычно зависит от ;размера ;текстуры освещенности ORG 25000 LD DE,FACES; список полигонов LD BC,1; количество полигонов GETC PUSH DE LD IX,X1 CALL GETPOLY; возьмем координаты ; первой точки треугольника LD IX,X2 CALL GETPOLY ; ... второй LD IX,X3 CALL GETPOLY ; ... третьей LD DE,X1 ; LD HL,X2 ; CALL SUBVEC ;разница ;векторов 1 и 2 LD DE,X1 ; LD HL,X3 ; CALL SUBVEC ;разница ;векторов 1 и 3 ; N_X = .... ;рассчет нормали n_x LD A,(X3+2) ;V2Z LD H,A CALL MOVZX LD A,(X2+1) ;V1Y CALL MUL ;произведение LD (T1),HL LD A,(X3+1) ;V2Y LD H,A CALL MOVZX LD A,(X2+2) ;V1Z CALL MUL ;произведение LD (T2),HL LD HL,(T1) LD DE,(T2) AND A SBC HL,DE SRA H RR L LD (N_X),HL ;сохраним результат ; произведем аналогию с векторами n_y, n_z ;таким же путем ; N_Y = .... LD A,(X3) ;V2X LD H,A CALL MOVZX LD A,(X2+2) ;V1Z CALL MUL LD (T1),HL LD A,(X3+2) ;V2Z LD H,A CALL MOVZX LD A,(X2) ;V1X CALL MUL LD (T2),HL LD HL,(T1) LD DE,(T2) AND A SBC HL,DE SRA H RR L LD (N_Y),HL ; N_Z = .... LD A,(X3+1) ;V2Y LD H,A CALL MOVZX LD A,(X2) ;V1X CALL MUL LD (T1),HL LD A,(X3) ;V2X LD H,A CALL MOVZX LD A,(X2+1) ;V1Y CALL MUL LD (T2),HL LD HL,(T1) LD DE,(T2) AND A SBC HL,DE SRA H RR L LD (N_Z),HL LD HL,(N_X) CALL ABS ;модуль числа LD L,A LD H,0 CALL UMUL; беззнаковое умножение LD (LEN),HL LD HL,(N_Y) CALL ABS LD L,A LD H,0 CALL UMUL LD DE,(LEN) ADD HL,DE LD (LEN),HL LD HL,(N_Z) CALL ABS LD L,A LD H,0 CALL UMUL LD DE,(LEN) ADD HL,DE LD BC,HL CALL SQRT ; квадратный корень LD (LEN),DE CALL NORMZ POP DE .3 INC DE ; INC DE три раза :) POP BC DEC BC LD A,B OR C JP NZ,GETC RET ; HL=H L=0 MOVZX LD L,0 BIT 7,H JR Z,POLOJ LD L,H LD H,#FF RET POLOJ LD L,H LD H,0 RET ;переменные для вычисления X1 DB 0 Y1 DB 0 Z1 DB 0 X2 DB 0 Y2 DB 0 Z2 DB 0 X3 DB 0 Y3 DB 0 Z3 DB 0 N_X DW 0 N_Y DW 0 N_Z DW 0 T1 DW 0 T2 DW 0 LEN DW 0 ;разница векторов ;DE=уменьшаемое ;HL=вычитаемое SUBVEC LD B,3 SUBV1 LD A,(DE) LD C,(HL) SUB C LD (HL),A INC HL INC DE DJNZ SUBV1 RET ;GET POLY COORDS GETPOLY LD A,(DE) LD L,A LD H,0 LD BC,HL ADD HL,HL ADD HL,BC LD BC,VERTEX ADD HL,BC LD A,(HL) LD (IX),A INC HL LD A,(HL) LD (IX+1),A INC HL LD A,(HL) LD (IX+2),A INC DE RET ;нормализация вектора в заданый пределах NORMZ LD HL,(N_X) LD A,PARAM CALL MUL LD DE,(LEN) CALL DIV LD (N_X),HL LD HL,(N_Y) LD A,PARAM CALL MUL LD DE,(LEN) CALL DIV LD (N_Y),HL LD HL,(N_Z) LD A,PARAM CALL MUL LD DE,(LEN) CALL DIV LD (N_Z),HL RET ;список полигонов ;полигон описывается тремя точками FACES DB 0,1,2 ;список вершин треугольников (x,y,z) VERTEX DB -10,1,4 DB 2,1,17 DB 30,15,70 ;умножение со знаком ;SIGNED MULTIPLY HL=HL*A MUL OR A JR NZ,NO_ZER LD L,A LD H,A RET NO_ZER PUSH AF LD A,H OR L JR NZ,NO2ZER POP AF RET NO2ZER POP AF EX AF,AF XOR A EX AF,AF BIT 7,A JR Z,NO_SIGA NEG EX AF,AF CPL EX AF,AF NO_SIGA BIT 7,H JR Z,NO_SIGH PUSH AF LD A,L NEG LD L,A LD A,H CPL LD H,A POP AF EX AF,AF CPL EX AF,AF NO_SIGH EX DE,HL LD H,0 LD L,H ADD A,A JR NC,MUL1 ADD HL,DE MUL1 ADD HL,HL ADD A,A JR NC,MUL2 ADD HL,DE MUL2 ADD HL,HL ADD A,A JR NC,MUL3 ADD HL,DE MUL3 ADD HL,HL ADD A,A JR NC,MUL4 ADD HL,DE MUL4 ADD HL,HL ADD A,A JR NC,MUL5 ADD HL,DE MUL5 ADD HL,HL ADD A,A JR NC,MUL6 ADD HL,DE MUL6 ADD HL,HL ADD A,A JR NC,MUL7 ADD HL,DE MUL7 ADD HL,HL ADD A,A JR NC,MEXIT ADD HL,DE MEXIT EX AF,AF OR A JR Z,UNSIG LD A,L NEG LD L,A LD A,H CPL LD H,A UNSIG EX AF,AF RET ;Деление со знаком ;HL.DE=HL/DE DIV EX AF,AF XOR A EX AF,AF BIT 7,H JR Z,NOSIGH PUSH AF LD A,L NEG LD L,A LD A,H CPL LD H,A POP AF EX AF,AF CPL EX AF,AF NOSIGH BIT 7,D JR Z,NOSIGD PUSH AF LD A,E NEG LD E,A LD A,D CPL LD D,A POP AF EX AF,AF CPL EX AF,AF NOSIGD EX DE,HL LD A,L OR H JR Z,DIVZERO XOR A LD B,A LD C,A INC A DIV2 BIT 7,H JR NZ,DIV1 ADD HL,HL INC A JR DIV2 DIV1 EX DE,HL DIV4 OR A SBC HL,DE CCF JR C,DIV3 ADD HL,DE OR A DIV3 RL C RL B RR D RR E DEC A JR NZ,DIV4 PUSH BC PUSH HL POP DE POP HL OR A EX AF,AF OR A JR Z,UNSIGD LD A,L NEG LD L,A LD A,H CPL LD H,A LD A,E NEG LD E,A LD A,D CPL LD D,A UNSIGD EX AF,AF RET DIVZERO SCF RET ;умножение без учета знака ;UNSIGNED MULTIPLY HL=HL*A UMUL OR A JR NZ,NO_ZEU LD L,A LD H,A RET NO_ZEU PUSH AF LD A,H OR L JR NZ,NO2ZEU POP AF RET NO2ZEU POP AF EX DE,HL LD H,0 LD L,H ADD A,A JR NC,MUL1U ADD HL,DE MUL1U ADD HL,HL ADD A,A JR NC,MUL2U ADD HL,DE MUL2U ADD HL,HL ADD A,A JR NC,MUL3U ADD HL,DE MUL3U ADD HL,HL ADD A,A JR NC,MUL4U ADD HL,DE MUL4U ADD HL,HL ADD A,A JR NC,MUL5U ADD HL,DE MUL5U ADD HL,HL ADD A,A JR NC,MUL6U ADD HL,DE MUL6U ADD HL,HL ADD A,A JR NC,MUL7U ADD HL,DE MUL7U ADD HL,HL ADD A,A JR NC,MEXITU ADD HL,DE MEXITU RET ;модуль числа ;A=ABS(HL) ABS BIT 7,H JR Z,POL1 XOR A SUB L LD L,A LD H,0 POL1 LD A,L RET ;модуль числа ;HL=ABSHL(A) ABSHL BIT 7,A JR Z,POL1HL CPL POL1HL LD L,A LD H,0 RET ;увеличение на 1, с учетом знака INCSIGN BIT 7,A JR Z,INC_ DEC A RET INC_ INC A RET ;квадратный корень ;BC=X ;DE=SQRT(X) SQRT LD DE,0 EXX LD BC,#4000 EXX LP1 LD HL,BC PUSH BC EXX PUSH BC EXX POP BC AND A SBC HL,BC POP BC JR C,LP2 AND A SBC HL,DE JR C,LP2 LD BC,HL SRL D RR E PUSH BC EXX PUSH BC EXX POP BC LD A,B OR D LD D,A LD A,C OR E LD E,A POP BC EXX SRL B RR C SRL B RR C LD A,B OR C EXX JR NZ,LP1 JP EXIT LP2 SRL D RR E EXX SRL B RR C SRL B RR C LD A,B OR C EXX JR NZ,LP1 EXIT RET 5. Создание текстуры для Fake Phong. Теперь рассчитавши нормали этим скучным исходником, вам необходимо будет произвести рассчет нормалей к вершинам полигонов (это показано в пункте 3). Имея список нормалей к каждой точке треугольника, а точнее к каждой точке 3D-объекта, можна смело реализовывать Fake Phong. Для этого нам нужно создать текстуру, которую мы будем накладывать на объект. Она будет состоять из концентрических кругов, переходя от самого яркого цвета к самому темному.
В данном случае тектура размером 64x64, но так как разрешение "чанковского" экрана 64x48, то мы видим что верхняя и нижняя часть тектуры обрезана, но речь не о этом. Текстуру будем создавать следующим образом: for(y=0;y<len_y;y++) { for(x=0;x<len_x;x++) { texture[x][y]=max_color*sqrt(x*x+y*y); } } len_x, len_y - это размер текстуры по координате X и Y. max_color - максимальный цвет в текстуре (для "чанков" он равен 16). texture[][] - массив, где будет хранится текстура. Хочу заметить, что координаты X и Y необходимо изменять от отрицательного значения к положительному (если у нас текстура размером 64, то менять Y надо в таких пределах как Y=-31...32). 6. Fake Phong Ну а теперь можна смело приступать к реализации Фонга. Для этого нужно на объект накладывать недавно созданную :) текстуру. Координаты в текстуре при векторе нормали считаются так: U=n_x V=n_y где U,V - координаты в текстуре n_x,n_y - координаты вектора нормали. это конечно упрощенный вариант, но коректнее было бы так: len=sqrt(n_x*n_x+n_y*n_y+n_z*n_z); n_x/=len; n_y/=len; U=(n_x)*32; V=(n_y)*32; где константа 32 - это длина текстуры (например по координате X), деленная на 2. Но это слишком тормозно и для нашего случая и не надо, так как еще при "прекалькуляции" мы осуществляли нормирование вектора в заданых пределах. 7. Заключение Если вам захотелось сделать environment mapping (как это сделано в Refresh и 5thElement), то вам ничего не остается сделать, как поменять текстуру освещенности на какую-то картинку. Доволно таки интерестный результат (текстура "обтянет" объект, например камень) можна наблюдать когда при вращении 3D-объекта, мы будем вращать и нормали. Только лучше всего эти нормали нормировать при вычислении координат в текстуре. Alexander Kulik (Wolf) 2:4635/8.18 __________________________________________