Basic3D Tutorials Основы 3D
Основные графические алгоритмы.

Как рисовать полигоны

 

Рисунки с 1 по 3

 

Чтобы отобразить грани, которые находятся в нашем трехмерном мире, нам необходимо нарисовать их из многоугольников (полигонов). Есть разные способы закраски многоугольников: плоская закраска, закраска Гуро, Фонга, наложение текстур. Но мы начнем с азов. Вначале, мы должны узнать, как рисовать многоугольник с плоской закраской. Поэтому зададимся вопросом, что такое плоский многоугольник?

 

Плоский многоугольник - треугольник, который закрашен одним цветом. Очень просто закодировать вывод их на экран, и вывод на экран их происходит очень быстро. Как Вы видите на рисунках выше, треугольники заданы тремя точками, все точки соединены между собой отрезками прямых. Пусть красный отрезок будет номер 1, синий - номер 2 и зеленый - номер 3. Для начала выведем только эти отрезки ограничивающие треугольник. Мы выводим отрезок так:

 

        xslope = (max_x-min_x)/(max_y-min_y)

        x = min_x

 

        пока max_y больше чем y делать

        {

          x = x + xslope

          y = y + 1

          putpixel(x y color)

        }

 

Или на Cи:

 

void drawline(int x1, int y1, int x2, int y2, char color)

        {

          int   side = 0;

          float temp = x1;

          float xslope = (x2-x1)/(y2-y1);

 

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

          }

          for (y=y1;y<=y2;y++)

          {

            putpixel(x,y,color);

            x1 += xslope;

          }

        }

 

Причем эта функция не рисует весь отрезок, а только те его места, где значение y изменяется. Сплошной отрезок появится, если Вы рисуете от (0,0) до (0,10), но это также, приводит к разряженному отрезку, если Вы рисуете от (0,0) до (10,1). Следовательно, Вы не можете использовать это как обычную функцию вывода отрезка. Но в этом и состоит трюк. Почему этот алгоритм работает, я не буду комментировать сейчас, но я могу гарантировать Вам, что это прекрасно работает.

 

Теперь, почему мы должны использовать эту функцию? Во-первых, мы должны рисовать отрезки от точки к точке. Если Вы посмотрите на рисунок выше, Вы можете увидеть, что независимо от того, как посмотреть на треугольник, в нем всегда будет самая нижняя и самая верхняя точки. Это означает, что всегда будет точка, которая находится в середине. Примем, что отрезок между самой верхней и самой нижней точкой является основным отрезком. В нашем примере основной отрезок на рисунке 1 – красный, на рисунке 2 – синий, на рисунке 3 - синий. Так как эти отрезки самые длинные. На рисунке один и два Вы можете видеть, что средняя точка слева из основного отрезка, но в третьем примере, он справа. Далее мы расчленим наш треугольник на две половины, в одном только один отрезок, а именно основной отрезок, а в другом, который идет от нижней точки до средней точки и к самой верхней точке, два отрезка. Давайте назовем отрезок (скорее два отрезка), который включает средний отрезок – разделительный отрезок. Обратите внимание, что и разделительный отрезок и основной отрезок заканчиваются и начинаются в той же самой точке, а именно min_y и max_y. Теперь, что действительно мы хотели бы сделать, так это нарисовать не только сами отрезки, но закрасить сам треугольник. Чтобы сделать это проще, мы используем рисунок три как стандартный, так как в нем основной отрезок -  прямой. Теперь мы сделаем массив, давайте назовем его xpos. Так как многоугольник может закрывать весь экран, мы должны иметь [200] элементов этого массива (при разрешении экрана 320x200). В нем хранится значения X соответствующие значению Y. Давайте проиллюстрируем это:

 

 

 

Рисунок 4

 

Как Вы помните, функция вывода отрезка рисовала точку каждый раз, когда изменялось значение y. Но это великолепно подходит и в нашем случае, так как мы сделали массив, где значение x может измениться, но значение y изменяется всегда только на единицу (так как мы имеем [200] элементов, и каждый элемент представляет значение x в соответствующей y позиции). Давайте использовать рисунок четыре, чтобы разобраться в этом. Каждой строке y соответствует свой элемент в массиве, т.е. строке y соответствует xpos[y]. В первой строке нет точек, но во второй строке есть две. Как это обработать? Просто, мы делаем другой массив, при этом один массив для представления основного отрезка (синяя линия) и другой для представления разделительного отрезка (красная и зеленая линии). Давайте просто увеличим xpos, и изменим его на xpos[200][2]. Теперь, все, что мы должны сделать, так это нарисовать основной отрезок и разделительный отрезок в массиве. Давайте, модернизируем drawline (нарисовать линию), и преобразуем ее в drawedge (нарисовать грань):

 

void drawedge(int x1, int y1, int x2, int y2, char color)

        {

          int   side = 0;

          float temp = x1;

          float xslope = (x2-x1)/(y2-y1);

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

          }

          for (y=y1;y<=y2;y++)

          {

            xpos[y][side]=x;

            x1 += xslope;

          }

        }

 

Здесь мы помещаем рассчитанные точки в массиве xpos. Для полного формирования краев треугольника и заполнения массива xpos надо:

 

        drawedge(x1,y1,x2,y2);

        drawedge(x2,y2,x3,y3);

        drawedge(x3,y3,x1,y1);

 

Теперь, мы должны нарисовать целый треугольник из массива xpos. Для этого, всего лишь, нам надо рисовать горизонтальные отрезки из xpos [y] [0] до xpos [y] [1]. Вот функция рисования горизонтального отрезка для видеорежима 320x200x256, где не надо переключать видеостраницы (примечание переводчика: можно рисовать в фоновый буфер, а потом выводить его на экран, в других видеорежимах):

 

void BufPixel( int x, int y, int color )

        {

          *(char*)(0xa0000+(x)+((y)*320))=color;

        }

 

void hline(int x1,int x2,int y,char color)

        {

          int i;

          int wherey;

          if (x1>=x2)

          {

            i=x1;

            x1=x2;

            x2=i;

          }

          wherey=y*320;

          for (i=x1;i<=x2;i++)

            BufPixel(i, wherey, color );

        }

 

Теперь, давайте сделаем все вместе. Но вначале мы вычислим min_y и max_y. Вот вся функция вывода плоского закрашенного треугольника:

 

void drawedge(int x1, int y1, int x2, int y2)

        {

          int   side = 0;

          float temp = x1;

          float xslope = (x2-x1)/(y2-y1);

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

          }

          for (y=y1;y<=y2;y++)

          {

            xedge[y][side] = x1;

            x1 += xslope;

          }

        }

 

void hline(int x1,int x2,int y,char color)

        {

          int i;

          int wherey;

          if (x1>=x2)

          {

            i=x1;

            x1=x2;

            x2=i;

          }

          wherey=y*320;

          for (i=x1;i<=x2;i++)

            BufPixel(i, wherey, color);

        }

 

void polygon(int x1,int y1,int x2,int y2,int x3,int y3,char c)

        {

          int minx,maxx;

 

          drawedge(x1,y1,x2,y2);

          drawedge(x2,y2,x3,y3);

          drawedge(x3,y3,x1,y1);

          miny=y1;

          if (miny > y2)

            miny=y2;

          if (miny > y3)

            miny=y3;

          maxy=y1;

          if (maxy < y2)

            maxy=y2;

          if (maxy < y3)

            maxy=y3;

          minx=x1;

          if (minx > x2)

            minx=x2;

          if (minx > x3)

            minx=x3;

          maxx=x1;

          if (maxx < x2)

            maxx=x2;

          if (maxx < x3)

            maxx=x3;

          if (maxy==miny)

            hline(minx,maxx,miny,c);

          else

            for (y=miny;y<=maxy;y++)

              hline(xedge[y][0],xedge[y][1],y,c);

        };

 

Вот и весь фокус, и он отлично работает!

 

Закраска методом Гуро по глубине

 

 

рисунок 5

 

Закраску Гуро можно сделать двумя способами. Цвет полигонов может отражать z расстояние от глаз, или цвет может отражать цвет, который им дан источником освещения. Здесь мы рассмотрим только z-цвет Гуро.

 

В этом случае отличие закраски Гуро от плоской закраски не большое. Нам необходима та же функция сканирования краев, почти та же самая hline и почти те же переменные. Для начала, давайте, рассмотрим некоторые примеры закраски Гуро. Пожалуйста, обратите внимание, что на рисунке выше, чем дальше точка треугольника от Вас, наблюдателя, тем более темной она кажется, и это фактически и есть z-Гуро. Но как мы можем это сделать?

 

Для начала мы нуждаемся в новой переменной, называемой zpos. Zpos имеет тот же самый тип, как и xpos, но в ней отражается позиция z в позиции x, которую мы берем из xpos массива. Вот пример. Посмотрите на рисунок выше, точки xpos слева и справа имеют свой z-цвет. Но что это дает? Да, правильно, мы изменим нашу функцию hline, чтобы рисовать разные цвета от xpos [y] [0] до xpos [y] [1] в зависимости от глубины. Это не должно быть трудно, хотя мы должны исправить наши функции, но очень немного, я могу гарантировать Вам.

 

Для начала, давайте возьмем функцию сканирования отрезка. В этой функции, мы должны отразить zedge, и, следовательно, мы также нуждаемся в значениях z для двух точек, из которых мы рисуем край. Поэтому мы включим эти значения в перечень параметров этой функции:

 

void drawedge(int x1, int y1, int z1, int x2, int y2, int z2)

 

Так как мы будем учитывать и координату z, то нам нужен еще один наклон - zslope. Переменная zslope отслеживает, как z изменяется относительно y. Следовательно, zslope вычисляется как (z2-z1) / (y2-y1). Это значение мы добавляем к другому значению, которое берем из массива zpos. Вот так:

 

for (y=y1;y<y2;y++)

        {

          x += xslope;

          z += zslope;

          xpos[y][side] = x;

          zpos[y][side] = z;

        }

 

Вот вся функция:

 

void drawedge(int x1, int y1, int z1, int x2, int y2, int z2, char color)

        {

          int   side = 0;

          float temp = x1;

          float xslope = (x2-x1)/(y2-y1);

          float zslope = (z2-z1)/(y2-y1);

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

            temp = z1;

            z1 = z2;

            z2 = temp;

          }

          for (y=y1;y<=y2;y++)

          {

            xpos[y][side]=x;

            x1 += xslope;

            zpos[y][side]=z;

            z1 += zslope;

          }

        }

 

Теперь нам надо исправить функцию hline. Мы должны сделать те же самые изменения, какие мы сделали в функции drawedge, для того чтобы учесть значение z для каждой строки, и нам также необходима переменная zslope, поскольку мы должны знать, как z изменяется относительно х. Следовательно, наш новый способ вычислить zslope должен быть (z2-z1) / (x2-x1). Это значение увеличивает наше значение цвета каждый раз, когда изменяется x. Вот функция:

 

void drawline(int x1,int x2,int y,char c)

        {

          int i;

          float zinc;

          float z1,z2;

          char  ch;

 

          z1 = zpos[y][0];

          z2 = zpos[y][1];

          if (x1>x2)

          {

            i=x1;

            x1=x2;

            x2=i;

            i=z1;

            z1=z2;

            z2=i;

          }

          zinc = (z2-z1)/(x2-x1);

          for (i=x1;i<=x2;i++)

          {

            BufPixel(i,y,z1+c);

            z1 += zinc;

          }

        }

 

Функция BufPixel выводит пиксель на экран в заданную координату, при этом цвет меняется от 0 до 255, где 0 цвет – это белый цвет, т.е. наиболее близкий, а цвет под номером 255 – черный цвет, т.е. самый дальний цвет. Также можно сделать выбор оттенка основного цвета в зависимости от глубины, учитывая текущую палитру.

 

Но мы еще не все сделали. Нам также необходимо изменить и основную функцию. Но в ней немного изменений, поэтому она приведена вся:

 

void drawedge(int x1, int y1, int z1, int x2, int y2, int z2, char color)

        {

          int   side = 0;

          float temp = x1;

          float xslope = (x2-x1)/(y2-y1);

          float zslope = (z2-z1)/(y2-y1);

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

            temp = z1;

            z1 = z2;

            z2 = temp;

          }

          for (y=y1;y<=y2;y++)

          {

            xpos[y][side]=x;

            x1 += xslope;

            zpos[y][side]=z;

            z1 += zslope;

          }

        }

 

        void hline(int x1,int x2,int y,char c)

        {

          int i;

          float zinc;

          float z1,z2;

          char  ch;

 

          z1 = zpos[y][0];

          z2 = zpos[y][1];

          if (x1>x2)

          {

            i=x1;

            x1=x2;

            x2=i;

            i=z1;

            z1=z2;

            z2=i;

          }

          zinc = (z2-z1)/(x2-x1);

          for (i=x1;i<=x2;i++)

          {

            BufPixel(i,y,z1+c);

            z1 += zinc;

          }

        }

 

        void gpoly(int x1,int y1,int z1,int x2,int y2,int z2,int x3,int y3,int z3,char c)

 

        {

          if (y1<=0)

            y1=0;

          if (y2<=0)

            y2=0;

          if (y3<=0)

            y3=0;

 

          if (y1>=200)

            y1=199;

          if (y2>=200)

            y2=199;

          if (y3>=200)

            y3=199;

 

          if (x1<=0)

            x1=0;

          if (x2<=0)

            x2=0;

          if (x3<=0)

            x3=0;

 

          if (x1>=320)

            x1=319;

          if (x2>=320)

            x2=319;

          if (x3>=320)

            x3=319;

 

          drawedge(x1,y1,z1,x2,y2,z2);

          drawedge(x2,y2,z2,x3,y3,z3);

          drawedge(x3,y3,z3,x1,y1,z1);

 

          miny=y1;

          if (miny > y2)

            miny=y2;

          if (miny > y3)

            miny=y3;

          maxy=y1;

          if (maxy < y2)

            maxy=y2;

          if (maxy < y3)

            maxy=y3;

 

          for (y=miny;y<=maxy;y++)

            hline(xedge[y][0],xedge[y][1],y,c);

        };

 

Z-буфер (буфер глубины)

 

 

Для работы Z-буфера используется сортировка по глубине, и она позволяет выводить правильным образом на экран два или большое количество объектов. Для этого используются координаты z (по глубине). Есть два способа реализации z-буфера. В первом используются растровые строки, и находятся видимые грани, в другом способе проверяется z-координата пикселя с соответствующим значением в массиве z-буфера, и если значение z-координаты пикселя ниже, чем соответствующие значение в массиве z-буфера, то пиксель выводится. Первый способ мы не будем рассматривать, остановимся на втором, так как он очень прост для объяснения.

 

Вначале создадим новый буфер, давайте назовем его - zbuffer. Этот массив должен иметь столько же элементов, сколько пикселей на экране. Размер каждого элемента можно изменять от одного байта до числа с вещественной запятой, в зависимости от размера объекта и насколько точным должен быть Z-буфер. Этот буфер содержит все z значения на экране, а это означает, что это идеально подходит для закраски Гуро. Нам надо только внести следующие изменения в нашу подпрограмму hline: заносить значения z-значения в z-буфер и проверять пиксель, который мы собираемся рисовать, имеет ли он значение ниже, чем соответствующие значение в Z-буфере. Это выглядит следующим образом:

 

for (y=y1;y<y2;y++)

   {

      x += xslope;

      z += zslope;

      xpos[y][side] = x;

      if (z < zbuffer[x+y*320])

      {

         zpos[y][side] = z;

         zbuffer[x+y*320] = z;

      }

   }

 

Каждый раз перед выводом всех объектов надо очистить Z-буфер, присвоив всем его элементам наибольшее значение по глубине. Так же можно использовать и обратное значение z, в этом случае очистку Z-буфера надо выполнять нулем.

 

for (y=y1;y<y2;y++)

   {

      x += xslope;

      z += zslope;

      z1 = 1/z;

      xpos[y][side] = x;

      if (z1 > zbuffer[x+y*320])

      {

         zpos[y][side] = z;

         zbuffer[x+y*320] = z1;

      }

   }

 

Наложение текстуры

 

Есть два способа наложения текстур. В первом способе, вы вычисляете нормали для вершин грани, и затем нормаль грани. Затем Вы выполняете нормальную процедуру аппроксимации по Фонгу, без вращения нормалей. В другом способе наложения текстур U и V координаты текстуры берутся из файла описания объекта, или надо сделать утилиту, для того чтобы вычислить их самостоятельно. Теперь, что нужно для использования U и V координат в вашей программе. Вначале надо добавить некоторые новые переменные для каждой из ваших вершин, так как каждая вершина имеет собственный набор UV координат. Но что такое UV координаты?

 

U и V – это просто другие слова для X и Y, но так как мы уже использовали символы XY так много раз, то использование их и в этом случае запутало бы еще больше. Так, UV – это XY координаты, но для чего? Хорошо, они – указывают на координаты пикселей в картинке текстуры. Вообще-то текстура может быть картинкой любого размера, но так как мы хотели бы сделать наш пример как можно проще, то лучше всего использовать для текстур размер 256x256. Каждая вершина грани имеет собственную позицию на этой текстуре, и на грани мы имеем три вершины, которые имеют три различных UV координаты. Теперь, мы должны нарисовать линии от каждой точки на текстуре заданной UV координатами к другой, таким же способом, как мы делали плоскую закраску. Для этого мы выводим два новых наклона Uslope и Vslope. Они определяются, скажем: uslope = (u2-u1) / (x2-x1) и vslope = (v2-v1) / (x2-x1). Эти строки надо включить в нашу процедуру сканирования краев, параметры которой выглядели бы следующим образом:

 

void drawedge

(int x1,int y1,float u1,float v1,int x2,int y2,float u2,float v2)

 

Далее нам необходимо два новых массива. Пусть это будут uedge [200] [2] и vedge [200] [2]. Эти массивы содержат края, которые мы заполним u и v значениями наклона, как это мы делали в других закрасках. Не забудьте, что мы все еще нуждаемся в крае x, так как мы все еще должны знать, где края граней, поскольку u и v края только декларируют, где край находятся на текстуре, но не на экране. Это важно помнить. Теперь, как это смотрело бы в коде?

 

 void drawedge

(int x1,int y1,float u1,float v1,int x2,int y2,float u2,float v2)

        {

          int   side = 0;

          float temp = x1;

          float xslope;

          float uslope;

          float vslope;

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

            temp = u1;

            u1 = u2;

            u2 = temp;

            temp = v1;

            v1 = v2;

            v2 = temp;

          }

          if (y2-y1==0)

          {

             xslope = (x2-x1);

             uslope = (u2-u1);

             vslope = (v2-v1);

          }

          else

          {

             xslope = (x2-x1)/(y2-y1);

             uslope = (u2-u1)/(y2-y1);

             vslope = (v2-v1)/(y2-y1);

          }

          for (y=y1;y<=y2;y++)

          {

            xedge[y][side] = x1;

            x1 += xslope;

            uedge[y][side] = u1;

            u1 += uslope;

            vedge[y][side] = v1;

            v1 += vslope;

          }

        }

 

Отлично, теперь мы заполнили наши U и V края значениями, но мы также должны рисовать линии. Что мы просто должны делать, так это рисовать из xedge [200] [0] к xedge [200] [1] пикселями из texture [mapx + mapy*256]. Mapx и mapy мы возьмем как значения u и vedge [200] [0], и затем вычисляем другие два наклона, которые является u и v наклонами. Теперь, наша hline подпрограмма должна быть изменена также, но в этом случае, давайте поместим hline в нашу основную процедуру. Нет никакой причины, почему она должна быть вне основной процедуры. Теперь, давайте посмотрим, как вся подпрограмма выглядела бы:

 

void drawedge

(int x1,int y1,float u1,float v1,int x2,int y2,float u2,float v2)

        {

          int   side = 0;

          float temp = x1;

          float xslope;

          float uslope;

          float vslope;

          if (y1 >= y2)

          {

            side = 1;

            x1 = x2;

            x2 = temp;

            temp = y1;

            y1 = y2;

            y2 = temp;

            temp = u1;

            u1 = u2;

            u2 = temp;

            temp = v1;

            v1 = v2;

            v2 = temp;

          }

          if (y2-y1==0)

          {

             xslope = (x2-x1);

             uslope = (u2-u1);

             vslope = (v2-v1);

          }

          else

          {

             xslope = (x2-x1)/(y2-y1);

             uslope = (u2-u1)/(y2-y1);

             vslope = (v2-v1)/(y2-y1);

          }

          for (y=y1;y<=y2;y++)

          {

            xedge[y][side] = x1;

            x1 += xslope;

            uedge[y][side] = u1;

            u1 += uslope;

            vedge[y][side] = v1;

            v1 += vslope;

          }

        }

 

        void texturepoly(int x1,int y1,float u1,float v1,

                         int x2,int y2,float u2,float v2,

                         int x3,int y3,float u3,float v3)

        {

 

          int   minx,maxx;

          int   i;

          float mapx, mapy;

          float uslope,vslope;

          float temp;

          int x;

          int xpos1,xpos2;

 

          texturedrawedge(x1,y1,u1,v1,x2,y2,u2,v2);

          texturedrawedge(x2,y2,u2,v2,x3,y3,u3,v3);

          texturedrawedge(x3,y3,u3,v3,x1,y1,u1,v1);

 

          miny=y1;

          if (miny > y2)

            miny=y2;

          if (miny > y3)

            miny=y3;

          maxy=y1;

          if (maxy < y2)

            maxy=y2;

          if (maxy < y3)

            maxy=y3;

          minx=x1;

          if (minx > x2)

            minx=x2;

          if (minx > x3)

            minx=x3;

          maxx=x1;

          if (maxx < x2)

            maxx=x2;

          if (maxx < x3)

            maxx=x3;

 

          for (y=miny;y<=maxy;y++)

          {

            if (xedge[y][1]-xedge[y][0] != 0)

            {

          uslope = (uedge[y][1]-uedge[y][0])/(xedge[y][1]-xedge[y][0]);

          vslope = (vedge[y][1]-vedge[y][0])/(xedge[y][1]-xedge[y][0]);

            }

            else

            {

              uslope = (uedge[y][1]-uedge[y][0]);

              vslope = (vedge[y][1]-vedge[y][0]);

            }

            mapxf=uedge[y][0];

            mapyf=vedge[y][0];

            for (x=xedge[y][0];x<=xedge[y][1];x++)

            {

              BufPixel(x,y,texture[(mapx)+(mapy<<8)]);

              mapx += uslope;

              mapy += vslope;

            }

          }

        }

 

Наложение окружения

 

Наложение окружения иногда называют аппроксимацией Фонга, но это не одно и тоже. Наложение окружения – это отражения на объекте окружающих его предметов, например с помощью, заранее подготовленного изображения этих предметов. Аппроксимация Фонга напротив часто используется, когда вы отображаете некоторый вид карты, например световую карту (lightmap). Это дает эффект освещение, которое выглядит как освещение рассчитанное по алгоритму Фонга, но выполняется намного быстрее. Чтобы понять наложение окружения, необходимо знать, что такое нормали (они были рассмотрены в математических основах 3D).

 

Зная, как работает нормаль, мы можем начинать делать наложение окружения. Наложение окружения использует ту же самую подпрограмму для наложения текстуры, которую мы создали прежде, но помимо этого нам еще будет необходимо вычислить угол на источник освещения. Когда мы имеем этот угол, мы используем это как наши U и V координаты. Вот таким способом, мы получаем наложение окружения. Далее мы рассмотрим все это поподробнее, а сейчас попробуем представить себе, что мы хотим получить.

 

Возьмите в руки световое перо, куб, или что-нибудь такое, что хорошо отражает свет. Если у вас нет этого под рукой, то попробуйте включить лампу, и направить свет ее прямо на любой другой объект. Теперь, давайте посмотрим на наш объект. Мы видим, что свет от лампы отражается объектом. Это тот эффект, который мы хотим копировать, но давайте исследует его сначала. Мы можем видеть, что поверхность, на которую непосредственно падает свет, отражает весь свет. Теперь, вращаем поверхность, немного влево вокруг оси Y, мы видим, что свет сдвинулся вправо. Почему это так? Мы знаем, что нормали всегда направлены от вершин во вне. Вообразите такие нормали на вашей поверхности. Они все выходят непосредственно из каждой вершины и угол между источником освещения и нормалями, был вначале ноль градусов. Вращая поверхность, влево вокруг оси Y мы находим, что свет перемещается вправо. Это, потому что угол между вершинами слева и источником света уменьшается, в то время как угол между вершинами справа и источником света увеличивается. Следовательно, карта света фактически сдвигается вправо. Вот один из самых лучших способов описания наложения окружения. (Примечание переводчика: фактически это описан способ применения световых карт, а не на наложения окружения, если бы использовался не источник света, а наблюдатель, а вместо карты использовалась карта окружения, то это и было бы отображение окружения).

 

Но как это закодировать? Это не так трудно. Как упоминалось можно использовать наложение текстуры. Для начала надо вычислить нормали прежде, чем мы начинаем наш главный цикл, и затем удостоверится, что мы вращаем их, каждый раз, когда мы вращаем наши вершины. Если не делать этого, каждая грань, будет заполнена той же самой текстурой все время. Это так, потому что, все нормали имеют те же самые углы все время, и, следовательно, нет перемещения по карте света, как если бы мы вращали нормали. Во всяком случае, не вращение нормалей приводит к наложению текстуры без координат uv, это второй способ выполнения этого, который был, упомянут в описании наложения текстуры. Это знак того, что это сферическое наложение, и не плоское (как с координатами uv).

 

Как вы знаете, нормали нормализованы. Это очень удобно, так как значение нормали не может быть больше, чем 1 или меньше чем -1. Но какие координаты нормали надо использовать? Фактически мы используем только x и y координаты вектора нормали, поскольку нет необходимости использовать значения z координаты.

 

 

Новый алгоритм заполнения

 

До сих пор, алгоритм, который мы использовали, был с буфером развертки. Это самый простой способ заполнения, но не с такой скоростью, c какой мы могли бы хотеть. Теперь, давайте попробуем выполнить заполнение по-другому. И снова пристально посмотрим на треугольник.

 

Можно заметить, что треугольник можно представить состоящим из двух треугольников, если разделить его медианой от одной из вершин. При этом в один треугольник входят вершины 1 и 2, а в другой вершины 2 и 3, третьи вершины в обоих треугольниках совпадают с точкой, которую пересекла медиана. Если мы хотим нарисовать верхний треугольник, то мы должны иметь наклоны, которые мы увеличиваем на константу, одна константа для вершин 1 и 2, а другая константа для вершин 1 и 3. Если мы хотим рисовать нижний треугольник, то надо сохранить нашу позицию на правой стороне треугольника, которая имеет тот же самый наклон, но мы должны вычислить новый наклон на левой стороне, а именно от 2 до 3. Фактически это не сложно, и даже проще, чем наш старый метод. Давайте посмотрим это все в коде, только для верхнего треугольника.

 

        xslope1 = (x2-x1)/(y2-y1);

        xslope2 = (x3-x1)/(y3-y1);

        leftx=x1;

        rightx=x1;

        for (y=y1;y< y2;y++)

        {

           leftx+=xslope1;

           rightx+=xslope2;

           for (x=leftx;x<=rightx;x++)

             BufPixel(x,y,color);

        }

 

Но надо кое-что еще исправить. Для начала, мы не уверены, что левая сторона меняется от x1 до x2 или что правая сторона самая длинная из трех. Следовательно, нам нужна проверка:

 

        if (leftx>rightx)

        {

          temp = leftx;

          leftx = rightx;

          rightx = leftx;

        }

 

Далее надо, чтобы y1 было меньше, чем y2 и y2 меньше чем y3. Также надо, чтобы x1 не была координатой x точки y3. Давайте посмотрим, как это сделать:

 

        if (y1>y2)

        {

          temp = y1;

          y1 = y2;

          y2 = temp;

          temp = x1;

          x1 = x2;

          x2 = temp;

        }

        if (y2>y3)

        {

          temp = y2;

          y2 = y3;

          y3 = tem3;

          temp = x2;

          x2 = x3;

          x3 = temp;

        }

        if (y1>y3)

        {

          temp = y1;

          y1 = y3;

          y3 = temp;

          temp = x1;

          x1 = x3;

          x3 = temp;

        }

 

Теперь, мы делаем с нижней частью полигона тот же самое, что и с верхней. Все, что мы должны здесь сделать, так это вычислить новый наклон slopex2x3.

 

The coding place

 

PMG  18 ноября 2004 (c)  Сергей Анисимов