Урок Инициализация в Windows я начинаю это пособие непосредственного с кода, разбитого на секции, каждая из которых будет подробно комментироваться. Первое, что вы должны сделать это создать проект в Visual C++



страница13/27
Дата17.11.2018
Размер7.85 Mb.
ТипУрок
1   ...   9   10   11   12   13   14   15   16   ...   27
enemy[loop2].spin на steps[adjust] (текущая скорость игры, которая зависит отadjust).

Затем мы проверяем, является ли значение fx противника больше, чем позиция x противника умноженная на 60 и если это так, мы перемещаем противника влево и вращаем противника влево.

То же самое мы делаем при перемещении противника вверх и вниз. Если позиция y противника меньше, чем позицияfy противника умноженная на 40 (40 пикселей размер ячейки по вертикали) мы увеличиваем fy, и вращаем противника, чтобы казалось, что он катится вниз. Наконец, если позиция y больше, чем позиция fy умноженная на 40, мы уменьшаем значение fy, чтобы переместить противника вверх. Снова, вращаем противника, чтобы казалось, что он катится вверх.

 

              // Точная позиция по оси X меньше чем назначенная позиция?



              if (enemy[loop2].fx

              {

                enemy[loop2].fx+=steps[adjust];    // Увеличим точную позицию по оси X

                enemy[loop2].spin+=steps[adjust];  // Вращаем по часовой

              }

              // Точная позиция по оси X больше чем назначенная позиция?

              if (enemy[loop2].fx>enemy[loop2].x*60)

              {

                enemy[loop2].fx-=steps[adjust];    // Уменьшим точную позицию по оси X

                enemy[loop2].spin-=steps[adjust];  // Вращаем против часовой

              }

              // Точная позиция по оси Y меньше чем назначенная позиция?

              if (enemy[loop2].fy

              {

                enemy[loop2].fy+=steps[adjust];    // Увеличим точную позицию по оси Y

                enemy[loop2].spin+=steps[adjust];  // Вращаем по часовой

              }

              // Точная позиция по оси Y больше чем назначенная позиция?

              if (enemy[loop2].fy>enemy[loop2].y*40)

              {

                enemy[loop2].fy-=steps[adjust];    // Уменьшим точную позицию по оси Y

                enemy[loop2].spin-=steps[adjust];  // Вращаем против часовой

              }

            }

          }

 

После перемещения противников мы проверяем, попал ли кто-нибудь из них в игрока. Для точности мы сравниваем точные позиции противников с точной позицией игрока. Если позиция противника fx равна точной позиция fx игрока, и позиция fy противника равна fy игрока, то игрок МЕРТВ :).



Если игрок мертв, то мы уменьшаем его количество жизней. Затем мы проверяем, что у игрока еще есть жизни. Это можно сделать сравнением lives с 0. Если lives равно нулю, то мы присваиваем gameover ИСТИНА.

Затем мы сбрасываем наши объекты, вызывая ResetObjects(), и проигрываем звук смерти.

Вывод звука новый материал в этом уроке. Я решил использовать наиболее простую процедуру вывода звука ...PlaySound()PlaySound() имеет три параметра. В первом параметре мы передаем ей название файла, который мы хотим проиграть. В нашем случае мы хотим, чтобы проиграл звук из файла Die.WAV в каталоге Data. Второй параметр можно проигнорировать. Мы установим его в NULL. Третий параметр – флаг для проигрывания звука. Два наиболее часто используемых типа флага: SND_SYNC, который приостанавливает выполнение программы пока звук не проиграет, и SND_ASYNC, который запускает проигрывание звука, но не останавливает программу. Мы хотим иметь небольшую задержку после того, как игрок умер, поэтому мы используем SND_SYNC. Довольно просто!

Я забыл рассказать об одной вещи в начале программы: для того чтобы PlaySound() и таймер работали, Вы должны подключить файл winmm.lib в проект (в Visual C++ это делается в PROJECT / SETTINGS / LINK). winmm.lib – мультимедийная библиотека Windows. Если Вы не включите эту библиотеку, Вы получите сообщения об ошибках, когда Вы пробуете откомпилировать программу.

 

          // Кто-нибудь из противников сверху игрока?



          if ((enemy[loop1].fx==player.fx) && (enemy[loop1].fy==player.fy))

          {

            lives--;            // Уменьшим жизни

 

            if (lives==0)       // Нет больше жизней?



            {

              gameover=TRUE;    // gameover равно TRUE

            }

 

            ResetObjects();      // Сброс позиций игрока / противников



            PlaySound("Data/Die.wav", NULL, SND_SYNC);  // Играем звук смерти

          }

        }

 

Теперь мы можем переместить игрока. В первой строке кода ниже мы проверяем, нажата ли стрелка вправо, иplayer.x меньше, чем 10 (не хотим выйти из сетки), и player.fx равно player.x умноженное на 60, и player.fy равноplayer.y умноженное на 40, т.е. находится в месте пересечения X и Y линий сетки.



Если мы не проверим, что игрок был в месте пересечения, и разрешим игроку перемещать как угодно, то игрок срежет правый угол ячейки, точно так же как противники сделали бы, если бы мы не проверяли, что они выровнены с вертикальной или горизонтальной линией. Эта проверка также делается, для того чтобы проверить, что игрок закончил, передвигаться прежде, чем мы переместим его в новое местоположение.

Если игрок в месте пересечения сетки (где встречаются вертикальные и горизонтальные линии) и он не за правым краем, мы помечаем, что текущая горизонтальная линия пройдена. Затем мы увеличиваем значение player.x на единицу, что вызывает перемещение игрока на одну клетку вправо.

Далее мы делаем также при перемещении влево, вниз и вверх. Когда перемещаем влево, мы проверяем, что игрок не вышел за левый край сетки. Когда перемещаем вниз, мы проверяем, что игрок не покинул сетку снизу, и при перемещении вверх мы проверяем, что игрок не вылетел за верх сетки.

При перемещении влево и вправо мы помечаем горизонтальную линию (hline[][]) ИСТИНА, что означает, что она пройдена. При перемещении вверх и вниз мы помечаем вертикальную линию (vline[][]) ИСТИНА, что означает, что она пройдена.

 

        if (keys[VK_RIGHT] && (player.x<10) && (player.fx==player.x*60) && (player.fy==player.y*40))



        {

          // Пометить текущую горизонтальную границу как пройденную

          hline[player.x][player.y]=TRUE;

          player.x++;        // Переместить игрока вправо

        }

        if (keys[VK_LEFT] && (player.x>0) && (player.fx==player.x*60) && (player.fy==player.y*40))

        {

          player.x--;        // Переместить игрока влево

          // Пометить текущую горизонтальную границу как пройденную

          hline[player.x][player.y]=TRUE;

        }

        if (keys[VK_DOWN] && (player.y<10) && (player.fx==player.x*60) && (player.fy==player.y*40))

        {

          // Пометить текущую вертикальную границу как пройденную

          vline[player.x][player.y]=TRUE;

          player.y++;        // Переместить игрока вниз

        }

        if (keys[VK_UP] && (player.y>0) && (player.fx==player.x*60) && (player.fy==player.y*40))

        {

          // Пометить текущую вертикальную границу как пройденную

          player.y--;        // Переместить игрока вверх

          vline[player.x][player.y]=TRUE;

        }

 

Мы увеличиваем / уменьшаем точные fx и fy переменные игрока, так же как мы увеличиваем / уменьшаем точные fxи fy переменные противника.



Если значение fx игрока, меньше чем значение x игрока умноженное на 60, мы увеличиваем fx игрока, на шаг скорости нашей игры в зависимости от значения adjust.

Если значение fx игрока больше, чем x игрока умноженное на 60, мы уменьшаем fx игрока, на шаг скорости нашей игры в зависимости от значения adjust.

Если значение fy игрока, меньше чем y игрока умноженное на 40, мы увеличиваем fy игрока, на шаг скорости нашей игры в зависимости от значения adjust.

Если значение fy игрока, больше чем y игрока умноженное на 40, мы уменьшаем fy игрока, на шаг скорости нашей игры в зависимости от значения adjust.

        if (player.fx

        {

          player.fx+=steps[adjust]; // Увеличим точную позицию X

        }

        if (player.fx>player.x*60)  // Точная позиция по оси X больше чем назначенная позиция?

        {

          player.fx-=steps[adjust]; // Уменьшим точную позицию X

        }

        if (player.fy

        {

          player.fy+=steps[adjust]; // Увеличим точную позицию Y

        }

        if (player.fy>player.y*40)  // Точная позиция по оси Y больше чем назначенная позиция?

        {

          player.fy-=steps[adjust]; // Уменьшим точную позицию X

        }

      }

 

Если игра завершена, то будет запущен следующий небольшой раздел кода. Мы проверяем, нажатие клавиши пробел. Если это так, то мы присваиваем переменной gameover ЛОЖЬ (повторный запуск игры). Мы задаем значение переменной filled ИСТИНА. Это означает, что стадия окончена, вызывая сброс переменных игрока, вместе с противниками.



Мы задаем стартовый уровень равным 1, наряду с реальным отображенным уровнем (level2). Мы устанавливаем значение переменной stage равной 0. Мы делаем это, потому что после того, как компьютер видит, что сетка была заполнена, он будет думать, что Вы закончили стадию, и увеличит stage на 1. Поскольку мы устанавливаем stage в 0, то затем stage увеличивается, и станет равной 1 (точно, что мы хотим). Наконец мы устанавливаем lives обратно в 5.

 

      else                // Иначе



      {

        if (keys[' '])    // Если пробел нажат

        {

          gameover=FALSE; // gameover равно FALSE

          filled=TRUE;    // filled равно TRUE

          level=1;        // Стартовый уровень установим обратно в один

          level2=1;       // Отображаемый уровень также установим в один

          stage=0;        // Стадию игры установим в ноль

          lives=5;        // Количество жизней равно пяти

        }

      }

 

Код ниже проверяет, равен ли флаг filled ИСТИНА (означает, что сетка была заполнена). Переменная filled может быть установлена в ИСТИНУ одним из двух путей. Или сетка заполнена полностью и filled равно ИСТИНА, когда игра закончена, а пробел был нажат, чтобы перезапустить ее (код выше).



Если filled равно ИСТИНА, вначале мы проигрываем крутую мелодию завершения уровня. Я уже объяснял, как работает PlaySound(). На сей раз, мы будем проигрывать файл Complete.WAV из каталога DATA. Снова, мы используем SND_SYNC для реализации задержки перед запуском следующей стадии.

После того, как звук был проигран, мы увеличиваем stage на один, и проверяем, что stage не больше чем 3. Еслиstage больше чем 3, мы устанавливаем stage в 1, и увеличиваем внутренний уровень и видимый уровень на один.

Если внутренний уровень больше чем 3, мы устанавливаем внутренний уровень (level) равным 3, и увеличиваем livesна 1. Если Вы достаточно быстры, и закончили уровень с 3, Вы заслуживаете бесплатную жизнь :). После увеличения жизней мы проверяем, что игрок не имеет больше чем 5 жизней. Если жизней больше чем 5, мы сбрасываем число жизней до 5.

 

      if (filled)         // Если сетка заполнена?



      {

        PlaySound("Data/Complete.wav", NULL, SND_SYNC);  // Играем звук завершения уровня

        stage++;          // Увеличиваем Stage

        if (stage>3)      // Если Stage больше чем 3?

        {

          stage=1;        // Тогда Stage равно 1

          level++;        // Увеличим уровень

          level2++;       // Увеличим отображаемый уровень

          if (level>3)    // Если уровень больше чем 3?

          {

            level=3;      // Тогда Level равно 3

            lives++;      // Добавим игроку лишнюю жизнь

            if (lives>5)  // Если число жизней больше чем 5?

            {

              lives=5;    // Тогда установим Lives равной 5

            }

          }

        }

 

Затем мы сбрасываем все объекты (такие как игрок и враги). При этом игрока помещаем снова в левый верхний угол сетки, а противникам присваиваются случайные позиции на сетке.



Мы создаем два цикла (loop1 и loop2) для обхода сетки. В них мы присваиваем значения всем вертикальным и горизонтальным линиям в ЛОЖЬ. Если бы мы этого не делали, то, когда была бы запущенна следующая стадия, то игра бы думала, что сетка все еще заполнена.

Заметьте, что код, который мы используем, чтобы очистить сетку, похож на код, который мы используем, чтобы вывести сетку. Мы должны проверить, что линии не будут рисоваться за правым и нижним краем. Именно поэтому мы проверяем, что loop1 меньше чем 10 прежде, чем мы сбрасываем горизонтальные линии, и мы проверяем, чтоloop2 меньше чем 10 прежде, чем мы сбрасываем вертикальные линии.

 

        ResetObjects();                    // Сброс позиции Игрока / Противника



 

        for (loop1=0; loop1<11; loop1++)   // Цикл по X координатам сетки

        {

          for (loop2=0; loop2<11; loop2++) // Цикл по Y координатам сетки

          {

            if (loop1<10)                  // Если X координата меньше чем 10

            {

              hline[loop1][loop2]=FALSE;   // Задаем текущее горизонтальное значение в FALSE

            }

            if (loop2<10)                  // Если Y координата меньше чем 10

            {

              vline[loop1][loop2]=FALSE;   // Задаем текущее вертикальное значение в FALSE

            }

          }

        }

      }


 

Теперь мы проверяем, попал ли игрок в песочные часы. Если точная позиция fx игрока равна позиции x песочных часов умноженная на 60, и точная позиция fy игрока равна позиции y песочных часов умноженная на 40, иhourglass.fx равно 1 (т.е. песочные часы есть на экране), то тогда код ниже будет выполнен.

Первая строка кода - PlaySound("Data/freeze.wav",NULL, SND_ASYNC | SND_LOOP). В этой строке проигрывается файл freeze.WAV из каталога DATA. Обратите внимание на то, что мы на этот раз используем SND_ASYNC. Мы хотим, чтобы звук замораживания играл без остановки игры. Флаг SND_LOOP позволяет циклично повторять звук, пока мы не сообщим, что пора прекратить играть, или пока не будет запущен другой звук.

После того, как мы запустили проигрывание звука, мы задаем hourglass.fx в 2. Когда hourglass.fx равно 2, песочные часы исчезнут, враги замрут, и звук будет непрерывно играть.

Мы также устанавливаем hourglass.fy в 0. Переменная hourglass.fy - счетчик. Когда она достигнет некоторого значения, значение переменной hourglass.fx изменится.

 

      // Если игрок попал в песочные часы и они на экране



      if ((player.fx==hourglass.x*60) && (player.fy==hourglass.y*40) && (hourglass.fx==1))

      {


        // Играть звук замораживания

        PlaySound("Data/freeze.wav", NULL, SND_ASYNC | SND_LOOP);

        hourglass.fx=2;          // Задать hourglass fx значение 2

        hourglass.fy=0;          // Задать hourglass fy значение 0

      }

 

В этой небольшой части кода увеличивает значение вращения игрока наполовину скорости выполнения игры. Еслиplayer.spin больше чем 360.0f, мы вычитаем 360.0f из player.spin. Это предохраняет значение player.spin от переполнения.



 

      player.spin+=0.5f*steps[adjust]; // Вращение игрока по часовой

      if (player.spin>360.0f)          // Значение spin больше чем 360?

      {


        player.spin-=360;              // Тогда вычтем 360

      }


 

Код ниже уменьшает значение вращения песочных часов на 1/4 скорости выполнения игры. Если hourglass.spinменьше чем 0.0f, мы добавляем 360.0f. Мы не хотим, чтобы hourglass.spin принимало отрицательные значения.

 

      hourglass.spin-=0.25f*steps[adjust]; // Вращение часов против часовой



      if (hourglass.spin<0.0f)             // spin меньше чем 0?

      {


        hourglass.spin+=360.0f;            // Тогда добавим 360

      }


 

В первой строке ниже увеличивается счетчик песочных часов, как я говорил об этом. Переменная hourglass.fyувеличивается на скорость игры (она равна значению шага в зависимости от значения корректировки).

Во второй линии проверяется, равно ли hourglass.fx значению 0 (не видимы) и счетчик песочных часов (hourglass.fy) больше чем 6000 деленное на текущий внутренний уровень (level).

Если значение fx равно 0, и счетчик больше чем 6000 деленное на внутренний уровень, то мы проигрываем файл hourglass.WAV из каталога DATA. Мы не хотим, чтобы игра остановилась, поэтому мы используем SND_ASYNC. Мы не будем повторять звук на этот раз, поэтому после того как звук проиграл, он не будет играть снова.

После того, как мы проиграли звук, мы задаем песочным часам случайное положение по оси X. Мы добавляем единицу к случайному значению, для того чтобы песочные часы не появились на стартовой позиции игрока в верхнем углу сетки. Мы также задаем песочным часам случайное положение по оси Y. Мы устанавливаемhourglass.fx в 1, это заставит песочные часы появиться на экране в этом новом местоположении. Мы также сбрасываем hourglass.fy в ноль, поэтому можно запустить счетчик снова.

Это приведет к тому, что песочные часы появятся на экране после заданного времени.

 

      hourglass.fy+=steps[adjust];    // Увеличим hourglass fy



      // Если hourglass fx равно 0 и fy больше чем 6000 деленное на текущий уровень?

      if ((hourglass.fx==0) && (hourglass.fy>6000/level))

      {

        // Тогда играем звук песочных часов



        PlaySound("Data/hourglass.wav", NULL, SND_ASYNC);

        hourglass.x=rand()%10+1;      // Случайная позиция часов по X

        hourglass.y=rand()%11;        // Случайная позиция часов по Y

        hourglass.fx=1;               // Задать hourglass fx значение 1 (стадия часов)

        hourglass.fy=0;               // Задать hourglass fy значение 0 (счетчик)

      }


 

Если hourglass.fx равно нолю, и hourglass.fy больше чем 6000 деленное на текущий внутренний уровнь (level), мы сбрасываем hourglass.fx назад в 0, что приводит к тому, что песочные часы исчезают. Мы также устанавливаемhourglass.fy в 0, потому что можно начать счет снова.

Это приводит к тому, что песочные часы исчезнут, если Вы не получаете их после некоторого времени.

 

      // Если hourglass fx равно 1 и fy больше чем 6000 деленное на текущий уровень?



      if ((hourglass.fx==1) && (hourglass.fy>6000/level))

      {


        hourglass.fx=0;          // Тогда зададим fx равным 0 (Обратим часы в ноль)

        hourglass.fy=0;          // Задать fy равным 0 (Сброс счетчика)

      }

 

Теперь мы проверяем, окончилось ли время 'замораживания противников' после того, как игрок коснулся песочных часов.



Если hourglass.fx равняется 2, и hourglass.fy больше чем 500 плюс 500 умноженное на текущий внутренний уровень, мы прерываем звук заморозки, который беспрерывно проигрывается. Мы прерываем звук командойPlaySound(NULL, NULL, 0). Мы устанавливаем hourglass.fx снова в 0, и hourglass.fy в 0. После присваивания fx иfy к 0 происходит запуск цикла работы песочных часов снова. Значение fy будет равняться 6000 деленное на текущий внутренний уровень прежде, чем песочные часы появятся снова.

 

      // Переменная песочных часов fx равно 2 и переменная fy



      // больше чем 500 плюс 500 умноженное на текущий уровень?

      if ((hourglass.fx==2) && (hourglass.fy>500+(500*level)))

      {

        PlaySound(NULL, NULL, 0);// Тогда прерываем звук заморозки



        hourglass.fx=0;          // Все в ноль

        hourglass.fy=0;

      }

 

Последнее что надо сделать - увеличить переменную задержки. Если Вы помните, задержка используется, чтобы обновить передвижение и анимацию игрока. Если наша программа финишировала, нам надо уничтожить окно и произвести возврат на рабочий стол.



 

      delay++;         // Увеличение счетчика задержки противника

    }

  }


 

  // Shutdown

  KillGLWindow();      // Уничтожить окно

  return (msg.wParam); // Выход из программы

}

 

Я потратил много времени при написании этого урока. Вначале это был урок по линиям, а в дальнейшем он перерос в небольшую интересную игру. Буду надеяться, если Вы сможете использовать то, что Вы узнали в этом уроке в ваших проектах с OpenGL. Я знаю, что Вы часто просили рассказать об играх на основе мозаики (tile). Отлично Вы не сможете сделать что-то более мозаичное, чем это :). Я также получил много писем, в которых меня спрашивали, как сделать точное по пиксельное рисование. Я думаю, что охватил и это :). Наиболее важно, что этот урок не только преподает Вам новые сведения о OpenGL, но также рассказывает Вам, как использовать простые звуки, чтобы добавить немного возбуждения в ваши визуальные произведения искусства! Я надеюсь, что Вам понравился этот урок. Если Вы чувствуете, что я неправильно прокомментировал кое-что или что код мог быть лучше в некоторых разделах, пожалуйста, сообщите мне об этом. Я хочу сделать самые хорошие уроки по OpenGL, я могу и я заинтересованным в общении с вами.



Пожалуйста, обратите внимание, это был чрезвычайно большой проект. Я пробовал комментировать все настолько ясно, насколько это возможно, но облекать мысли в слова, не столь просто, как это кажется. Знать о том, почему это все работает, и пробовать это объяснить – это совершенно разные вещи :). Если Вы прочитали урок, и можете объяснить все лучше, или если Вы знаете, способы помочь мне, пожалуйста, пошлите мне свои предложения. Я хочу, чтобы этот урок был прост. Также обратите внимание, что этот урок не для новичка. Если Вы не читали предыдущие уроки, пожалуйста, не задавайте мне вопросов. Большое спасибо.

Урок 22. Наложение микрорельефа методом тиснения, мультитекстурирование и использование расширений OpenGL

Этот урок, написанный Дженсом Шнайдером (Jens Schneider), основан на материале Урока 6, но содержит существенные изменения и дополнения. Здесь вы узнаете:


  • как управлять функциями мультитекстурирования видеокарты;

  • как выполнять "поддельное" наложение микрорельефа методом тиснения;

  • как, используя смешивание, отображать эффектно смотрящиеся логотипы, "летающие" по просчитанной сцене;

  • как просто и быстро выполнять преобразование матриц;

  • познакомитесь с основами техники многопроходной визуализации.

По меньшей мере, три из перечисленных пунктов могут быть отнесены к "продвинутым техникам текстурирования", поэтому для работы с ними нужно хорошо понимать основы функционирования конвейера визуализации в OpenGL. Требуется знать большинство команд, изученных в предыдущих уроках и быть достаточно близко знакомым с векторной математикой. Иногда будут попадаться блоки, озаглавленные "начало теории(…)" и оканчивающиеся фразой "конец теории(…)". В таких местах рассматриваются теоретические основы вопросов, указанных в скобках, и если вы их знаете, то можете пропустить. Если возникают проблемы с пониманием кода, лучше вернуться к теоретической части и попробовать разобраться. И еще: в уроке более 1200 строк кода, значительные фрагменты которого очевидны и скучны для тех, кто читал предыдущие главы. Поэтому я не стал комментировать каждую строку, пояснил только главное. Если встретите что-то вроде >…<, это значит, что строки кода были пропущены. 

Итак:


 

#include          // Файл заголовков функций Windows

#include            // Файл заголовков для библиотеки ввода-вывода

#include            // Файл заголовков для библиотеки OpenGL32

#include           // Файл заголовков для библиотеки GLu32

#include         // Файл заголовков для библиотеки GLaux

#include "glext.h"           // Файл заголовков для мультитекстурирования

#include           // Файл заголовков для работы со строками

#include             // Файл заголовков для математической библиотеки

 

Параметр GLfloat MAX_EMBOSS задает "интенсивность" эффекта рельефности. Увеличение этого числа значительно усиливает эффект, но приводит к снижению качества и появлению так называемых "артефактов" изображения по краям поверхностей.



 

// Коэффициент рельефности. Увеличьте, чтобы усилить эффект

#define MAX_EMBOSS (GLfloat)0.008f

 

Давайте подготовимся к использованию расширения GL_ARB_multitexture. Это просто.



 

В настоящее время подавляющая часть акселераторов имеет более одного блока текстурирования на чипе. Чтобы определить, верно ли это для используемой карточки, надо проверить ее на поддержку опции GL_ARB_multitexture, которая позволяет накладывать две или более текстур на примитив за один проход. Звучит не слишком впечатляюще, но на самом деле это мощный инструмент! Практически любая сцена выглядит гораздо красивее, если ее на объекты наложено несколько текстур. Обычно для этого требуется сделать несколько "проходов", состоящих из выбора текстуры и отрисовки геометрии; при увеличении числа таких операций работа серьезно тормозится. Однако не беспокойтесь, позже все прояснится.

 

Вернемся к коду: __ARB_ENABLE используется, чтобы при необходимости отключить мультитекстурирование. Если хотите видеть OpenGL-расширения, раскомментируйте строку #define EXT_INFO. Доступность расширений будет проверяться во время выполнения, чтобы сохранить переносимость кода, поэтому нужны будут несколько переменных строкового типа — они заданы двумя следующими строками. Кроме того, желательно различать доступность мультитекстурирования и его использование, то есть нужны еще два флага. Наконец, нужно знать, сколько блоков текстурирования доступно (хотя мы будем использовать всего два). По меньшей мере один такой блок обязательно присутствует на любой OpenGL-совместимой карте, так что переменную maxTexelUnits надо инициализировать единичкой.



 

#define __ARB_ENABLE true          // Используется, чтобы полностью отключить расширения

// #define EXT_INFO        // Раскомментируйте, чтобы увидеть при запуске доступные расширения

#define MAX_EXTENSION_SPACE 10240  // Символы строк-описателей расширений

#define MAX_EXTENSION_LENGTH 256   // Максимальное число символов в одной строке-описателе

bool multitextureSupported=false;  // Флаг, определяющий, поддерживается ли мультитекстурирование

bool useMultitexture=true;         // Использовать его, если оно доступно?

GLint maxTexelUnits=1;             // Число текстурных блоков. Как минимум 1 есть всегда

 

Следующие строки нужны, чтобы сопоставить расширениям соответствующие вызовы функций C++. Просто считайте, что PFN-и-как-там-дальше — предварительно определенный тип данных, нужный для описания вызовов функций. Мы не уверены, что к этим прототипам будут доступны функции, а потому установим их в NULL. Команды glMultiTexCoordifARB задают привязку к хорошо известным glTexCoordif, описывающим i-мерные текстурные координаты. Заметьте, что они могут полностью заменить команды, связанные с glTexCoordif. Мы пользуемся версиями с GLfloat, и нам нужны прототипы тех команд, которые оканчиваются на "f"; другие команды при этом также остаются доступны ("fv", "i" и т.д.). Два последних прототипа задают функцию выбора активного блока текстурирования (texture-unit), занятого привязкой текстур ( glActiveTextureARB() ), и функцию, определяющую, какой из текстурных блоков связан с командой выбора указателя на массив (glClientActiveTextureARB). К слову: ARB — это сокращение от "Architectural Review Board", "комитет по архитектуре". Расширения, содержащие в имени строку ARB, не требуются для реализации системы, соответствующей спецификации OpenGL, но ожидается, что такие расширения найдут широкую поддержку у производителей. Пока ARB-статус имеют только расширения, связанные с мультитекстурированием. Такая ситуация, скорее всего, указывает на то, что мультитекстурирование наносит страшный удар по производительности, когда дело касается некоторых продвинутых техник визуализации.



 

Пропущенные строки относятся к указателям на контекст GDI и прочему.

 

PFNGLMULTITEXCOORD1FARBPROC  glMultiTexCoord1fARB  = NULL;



PFNGLMULTITEXCOORD2FARBPROC  glMultiTexCoord2fARB  = NULL;

PFNGLMULTITEXCOORD3FARBPROC  glMultiTexCoord3fARB  = NULL;

PFNGLMULTITEXCOORD4FARBPROC  glMultiTexCoord4fARB  = NULL;

PFNGLACTIVETEXTUREARBPROC  glActiveTextureARB  = NULL;

PFNGLCLIENTACTIVETEXTUREARBPROC  glClientActiveTextureARB= NULL;

 

Создаем глобальные переменные:



  • filter задает используемый фильтр (см. Урок 06). Обычно берем GL_LINEAR, поэтому инициализируем переменную единичкой.

  • texture хранит текстуру, три копии, по одной на фильтр.

  • bump хранит карты микрорельефа.

  • invbump хранит инвертированную карту микрорельефа. Причина объясняется позже, в теоретическом разделе.

  • Переменные, относящиеся к логотипам, в имени которых есть слово "Logo" - хранят текстуры, добавляемые к сцене на последнем проходе.

  • Переменные, относящиеся к свету, в имени которых есть слово “Light” - хранят параметры источника света.

GLuint  filter=1;           // Какой фильтр использовать

GLuint  texture[3];         // Хранит 3 текстуры

GLuint  bump[3];            // Рельефы

GLuint  invbump[3];         // Инвертированные рельефы

GLuint  glLogo;             // Указатель на OpenGL-логотип

GLuint  multiLogo;          // Указатель на мультитекстурированный логотип

GLfloat LightAmbient[]  = { 0.2f, 0.2f, 0.2f};  // Фоновое освещение — 20% белого

GLfloat LightDiffuse[]  = { 1.0f, 1.0f, 1.0f};  // Рассеянный свет —  чисто белый

GLfloat LightPosition[]  = { 0.0f, 0.0f, 2.0f}; // Положение источника — перед экраном

GLfloat Gray[]    = { 0.5f, 0.5f, 0.5f, 1.0f};

 

Очередной фрагмент кода содержит числовое описание текстурированного куба, сделанного из GL_QUADS-ов. Каждые пять чисел представляют собой пару из двумерных текстурных и трехмерных вершинных координат. Это удобно для построения куба в цикле for…, учитывая, что нам потребуется сделать это несколько раз. Блок данных заканчивается прототипом функции WndProc(), хорошо известной из предыдущих уроков.



 

// Данные содержат грани куба в формате "2 текстурные координаты, 3 вершинные".

// Обратите внимание, что мозаичность куба минимальна.

 

GLfloat data[]= {



  // ЛИЦЕВАЯ ГРАНЬ

  0.0f, 0.0f,    -1.0f, -1.0f, +1.0f,

  1.0f, 0.0f,    +1.0f, -1.0f, +1.0f,

  1.0f, 1.0f,    +1.0f, +1.0f, +1.0f,

  0.0f, 1.0f,    -1.0f, +1.0f, +1.0f,

  // ЗАДНЯЯ ГРАНЬ

  1.0f, 0.0f,    -1.0f, -1.0f, -1.0f,

  1.0f, 1.0f,    -1.0f, +1.0f, -1.0f,

  0.0f, 1.0f,    +1.0f, +1.0f, -1.0f,

  0.0f, 0.0f,    +1.0f, -1.0f, -1.0f,

  // ВЕРХНЯЯ ГРАНЬ

  0.0f, 1.0f,    -1.0f, +1.0f, -1.0f,

  0.0f, 0.0f,    -1.0f, +1.0f, +1.0f,

  1.0f, 0.0f,    +1.0f, +1.0f, +1.0f,

  1.0f, 1.0f,    +1.0f, +1.0f, -1.0f,

  // НИЖНЯЯ ГРАНЬ

  1.0f, 1.0f,    -1.0f, -1.0f, -1.0f,

  0.0f, 1.0f,    +1.0f, -1.0f, -1.0f,

  0.0f, 0.0f,    +1.0f, -1.0f, +1.0f,

  1.0f, 0.0f,    -1.0f, -1.0f, +1.0f,

  // ПРАВАЯ ГРАНЬ

  1.0f, 0.0f,    +1.0f, -1.0f, -1.0f,

  1.0f, 1.0f,    +1.0f, +1.0f, -1.0f,

  0.0f, 1.0f,    +1.0f, +1.0f, +1.0f,

  0.0f, 0.0f,    +1.0f, -1.0f, +1.0f,

  // ЛЕВАЯ ГРАНЬ

  0.0f, 0.0f,    -1.0f, -1.0f, -1.0f,

  1.0f, 0.0f,    -1.0f, -1.0f, +1.0f,

  1.0f, 1.0f,    -1.0f, +1.0f, +1.0f,

  0.0f, 1.0f,    -1.0f, +1.0f, -1.0f

};

 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        // Объявление WndProc



 

В следующем блоке кода реализована проверка поддержки расширений во время выполнения.

 

Во-первых, предположим, что у нас есть длинная строка, содержащая список всех поддерживаемых расширений, представленных в виде подстрок, разделенных символом ‘\n’. Таким образом, надо провести поиск этого символа и начать сравнение string с search до достижения либо очередного ‘\n’, либо отличия в сравниваемых строках. В первом случае вернем в "найдено" значение true, во втором — возьмем следующую подстроку, и так до тех пор, пока не кончится string. Со string придется немного повозиться, поскольку она начинается не с символа ‘\n’.



 

Кстати: проверку доступности любого данного расширения во время выполнения программы надо выполнять ВСЕГДА!

 

bool isInString(char *string, const char *search) {



  int pos=0;

  int maxpos=strlen(search)-1;

  int len=strlen(string);

  char *other;

  for (int i=0; i

    if ((i==0) || ((i>1) && string[i-1]=='\n')) { // Новые расширения начинаются здесь!

      other=&string[i];

      pos=0; // Начать новый поиск

      while (string[i]!='\n') { // Поиск по всей строке расширения

        if (string[i]==search[pos]) pos++; // Следующий символ

        if ((pos>maxpos) && string[i+1]=='\n') return true; // А вот и она!

        i++;

      }

    }


  }

  return false; // Простите, не нашли!

}

 

Теперь извлечем строку расширений и преобразуем ее в строки, разделенные символом ‘\n’, чтобы провести поиск. Если будет обнаружена строка ”GL_ARB_multitexture”, значит, эта опция поддерживается. Но чтобы ее использовать, нужно, во-первых, чтобы __ARB_ENABLE была установлена в true, а во-вторых, чтобы карточка поддерживала расширение GL_EXT_texture_env_combine, которое указывает, что аппаратура разрешает некоторые новые способы взаимодействия между своими текстурными блоками. Это необходимо, поскольку GL_ARB_multitexture обеспечивает лишь вывод обработанных данных последовательно с текстурного блока с меньшим номером на блок с большим, а поддержка GL_EXT_texture_env_combine означает возможность использования уравнений смешивания повышенной сложности, эффект от которых совсем другой. Если все необходимые расширения поддерживаются и мы не запретили их сами, определим количество доступных текстурных блоков. Это число будет храниться в maxTexelUnits. Затем установим связь между функциями и их именами, для этого воспользуемся вызовом wglGetProcAdress(), передавая ей в качестве параметра строку-имя искомой функции и проводя преобразование типа результата, чтобы гарантировать совпадение ожидаемого и полученного типов.



 

bool initMultitexture(void) {

  char *extensions;

  extensions=strdup((char *) glGetString(GL_EXTENSIONS)); // Получим строку расширений

  int len=strlen(extensions);

  for (int i=0; i

    if (extensions[i]==' ') extensions[i]='\n';

 

#ifdef EXT_INFO



  MessageBox(hWnd,extensions,"поддерживаются расширения GL:",MB_OK | MB_ICONINFORMATION);

#endif


 

  if (isInString(extensions,"GL_ARB_multitexture") // Мультитекстурирование поддерживается?

    && __ARB_ENABLE  // Проверим флаг

    // Поддерживается среда комбинирования текстур?

    && isInString(extensions,"GL_EXT_texture_env_combine"))

  {      

    glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexelUnits);

    glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC) wglGetProcAddress("glMultiTexCoord1fARB");

    glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMultiTexCoord2fARB");

    glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC) wglGetProcAddress("glMultiTexCoord3fARB");

    glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC) wglGetProcAddress("glMultiTexCoord4fARB");

    glActiveTextureARB   = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB");

    glClientActiveTextureARB= (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB");

              

#ifdef EXT_INFO

    MessageBox(hWnd,"Будет использовано расширение GL_ARB_multitexture.",

     "опция поддерживается!",MB_OK | MB_ICONINFORMATION);

#endif


 

    return true;

  }

  useMultitexture=false;// Невозможно использовать то, что не поддерживается аппаратурой



  return false;

}

 



InitLights() инициализирует освещение OpenGL, будучи вызвана позже из InitGL().

 

void initLights(void) {



        // Загрузка параметров освещения в GL_LIGHT1

        glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);

        glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);

        glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);

        glEnable(GL_LIGHT1);

}

 



Здесь грузится УЙМА текстур. Поскольку у функции auxDIBImageLoad() есть собственный обработчик ошибок, а LoadBMP() труднопредсказуема и требует блока try-catch, я отказался от нее. Но вернемся к процедуре загрузки. Сначала берем базовую картинку и создаем на ее основе три фильтрованных текстуры (в режимах GL_NEAREST, GL_LINEAR и GL_LINEAR_MIPMAP_NEAREST). Обратите внимание, для хранения растра используется лишь один экземпляр структуры данных, поскольку в один момент открытой нужна лишь одна картинка. Здесь применяется новая структура данных, alpha — в ней содержится альфа-слой текстур. Такой подход позволяет хранить RGBA-изображения в виде двух картинок: основного 24-битного RGB растра и 8-битного альфа-канала в шкале серого. Чтобы индикатор состояния работал корректно, нужно удалять Image-блок после каждой загрузки и сбрасывать его в NULL.

 

Еще одна особенность: при задании типа текстуры используется GL_RGB8 вместо обычного "3". Это сделано для совместимости с будущими версиями OpenGL-ICD и рекомендуется к использованию вместо любого другого числа. Такие параметры я пометил оранжевым.



 

int LoadGLTextures() {           // Загрузка растра и преобразование в текстуры

  bool status=true;              // Индикатор состояния

  AUX_RGBImageRec *Image=NULL;   // Создадим место для хранения текстур

  char *alpha=NULL;

 

  // Загрузим базовый растр



  if (Image=auxDIBImageLoad("Data/Base.bmp")) {

    glGenTextures(3, texture);    // Создадим три текстуры

 

    // Создаем текстуру с фильтром по ближайшему



    glBindTexture(GL_TEXTURE_2D, texture[0]);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0,

      GL_RGB, GL_UNSIGNED_BYTE, Image->data);

 

    // Создаем текстуру с фильтром усреднения



    glBindTexture(GL_TEXTURE_2D, texture[1]);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0,

       GL_RGB, GL_UNSIGNED_BYTE, Image->data);

 

    // Создаем текстуру с мип-наложением



    glBindTexture(GL_TEXTURE_2D, texture[2]);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, Image->sizeX, Image->sizeY,

      GL_RGB, GL_UNSIGNED_BYTE, Image->data);

  }


  else status=false;

 

  if (Image) { // Если текстура существует



    if (Image->data) delete Image->data; // Если изображение существует

    delete Image;

    Image=NULL;

  }


 

Загрузим рельеф. По причинам, объясняемым ниже, текстура рельефа должна иметь 50% яркость, поэтому ее надо промасштабировать. Сделаем это через команды glPixelTransferf(), которые описывают попиксельное преобразование данных растра в текстуру. Если вы до сих пор не пользовались командами семейства glPixelTransfer(), рекомендую обратить на них пристальное внимание, поскольку они часто бывают очень удобны и полезны.

 

Теперь учтем, что нам не нужно, чтобы базовая картинка многократно повторялась в текстуре. Чтобы получить картинку единожды, растянутой в нужное количество раз, ее надо привязать к текстурным координатам с (s,t)=(0.0f, 0.0f) по (s,t)=(1.0f, 1.0f). Все остальные координаты привязываются к чистому черному цвету через вызовы glTexParameteri(), которые даже не требуют пояснений.



 

  // Загрузим рельефы

  if (Image=auxDIBImageLoad("Data/Bump.bmp")) {

    glPixelTransferf(GL_RED_SCALE,0.5f);   // Промасштабируем яркость до 50%,

    glPixelTransferf(GL_GREEN_SCALE,0.5f); // поскольку нам нужна половинная интенсивность

    glPixelTransferf(GL_BLUE_SCALE,0.5f);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);  // Не укладывать паркетом

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

    glGenTextures(3, bump);  // Создать три текстуры

 

    // Создать текстуру с фильтром по ближайшему



    >…<

 

    // Создать текстуру с фильтром усреднения



    >…<

 

    // Создать текстуру с мип-наложением



    >…<

 

С этой фразой вы уже знакомы: по причинам, объясненным ниже, нужно создать инвертированную карту рельефа с той же 50% яркостью. Для этого вычтем полученный ранее растр из чистого белого цвета {255, 255, 255}. Поскольку мы НЕ возвращали RGB-масштабирование на 100% уровень (я три часа разбирался, пока понял, что здесь скрывалась основная ошибка первой версии урока!), инверсный рельеф тоже получится 50% яркости.



 

    for (int i=0; i<3*Image->sizeX*Image->sizeY; i++) // Проинвертируем растр

      Image->data[i]=255-Image->data[i];

 

    glGenTextures(3, invbump); // Создадим три текстуры



 

    // с фильтром по ближайшему

    >…<

 

    // с фильтром усреднения



    >…<

 

    // с мип-наложением



    >…<

  }


  else status=false;

  if (Image) { // Если текстура существует

    if (Image->data) delete Image->data; // Если изображение текстуры существует

    delete Image;

    Image=NULL;

  }


 

Загрузка изображения логотипа очень проста, кроме, разве что, фрагмента рекомбинации RGB-A. Он, впрочем, тоже достаточно очевиден. Заметьте, что текстура строится на основе alpha-, а не Image-блока! Здесь применена только одна фильтрация.

 

  // Загрузка картинки логотипа



  if (Image=auxDIBImageLoad("Data/OpenGL_ALPHA.bmp")) {

    alpha=new char[4*Image->sizeX*Image->sizeY];

    // Выделим память для RGBA8-текстуры

    for (int a=0; asizeX*Image->sizeY; a++)

      alpha[4*a+3]=Image->data[a*3]; // Берем красную величину как альфа-канал

    if (!(Image=auxDIBImageLoad("Data/OpenGL.bmp"))) status=false;

    for (a=0; asizeX*Image->sizeY; a++) {

      alpha[4*a]=Image->data[a*3];        // R

      alpha[4*a+1]=Image->data[a*3+1];    // G

      alpha[4*a+2]=Image->data[a*3+2];    // B

    }

 

    glGenTextures(1, &glLogo);  // Создать одну текстуру



 

    // Создать RGBA8-текстуру с фильтром усреднения

    glBindTexture(GL_TEXTURE_2D, glLogo);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Image->sizeX, Image->sizeY, 0,

       GL_RGBA, GL_UNSIGNED_BYTE, alpha);

    delete alpha;

  }

  else status=false;



 

  if (Image) { // Если текстура существует

    if (Image->data) delete Image->data; // Если изображение текстуры существует

    delete Image;

    Image=NULL;

  }


 

  // Загрузим логотип "Extension Enabled"

  if (Image=auxDIBImageLoad("Data/multi_on_alpha.bmp")) {

    alpha=new char[4*Image->sizeX*Image->sizeY]; // Выделить память для RGBA8-текстуры

    >…<

    glGenTextures(1, &multiLogo); // Создать одну текстуру



    // Создать RGBA8-текстуру с фильтром усреднения

    >…<


    delete alpha;

  }


  else status=false;

 

  if (Image) { // Если текстура существует



    if (Image->data) delete Image->data; // Если изображение текстуры существует

    delete Image;

    Image=NULL;

  }


  return status;  // Вернем состояние

}

 



Далее идет практически единственная неизмененная функция ReSizeGLScene(), и ее я пропустил. За ней следует функция doCube(), рисующая куб с единичными нормалями. Она задействует только текстурный блок №0, потому что glTexCoord2f(s,t) делает то же самое, что и glMultiTexCoord2f(GL_TEXTURE0_ARB,s,t). Обратите внимание, что куб нельзя создать, используя чередующиеся массивы, но это тема для отдельного разговора. Кроме того, учтите, что куб НЕВОЗМОЖНО создать, пользуясь списками отображения. Видимо, точность внутреннего представления данных, используемая в этих списках, не соответствует точности, применяемой в GLfloat. Это ведет к неприятным эффектам, которые называются проблемами деколирования (когда источник света не влияет на закрашивание объекта), поэтому от списков я решил отказаться. Вообще, я полагаю, что надо либо делать всю геометрию, пользуясь списками, либо не применять их вообще. Смешивание разных подходов приводит к проблемам, которые где-нибудь да проявятся, даже если на вашей аппаратуре все пройдет успешно.

 

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)



// Изменить размер и инициализировать окно GL

>…<


 

void doCube (void) {

  int i;

  glBegin(GL_QUADS);

    // Передняя грань

    glNormal3f( 0.0f, 0.0f, +1.0f);

    for (i=0; i<4; i++) {

      glTexCoord2f(data[5*i],data[5*i+1]);

      glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

    }


    // Задняя грань

    glNormal3f( 0.0f, 0.0f,-1.0f);

    for (i=4; i<8; i++) {

      glTexCoord2f(data[5*i],data[5*i+1]);

      glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

    }


    // Верхняя грань

    glNormal3f( 0.0f, 1.0f, 0.0f);

    for (i=8; i<12; i++) {

      glTexCoord2f(data[5*i],data[5*i+1]);

      glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

    }


    // Нижняя грань

    glNormal3f( 0.0f,-1.0f, 0.0f);

    for (i=12; i<16; i++) {

      glTexCoord2f(data[5*i],data[5*i+1]);

      glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

    }


    // Правая грань

    glNormal3f( 1.0f, 0.0f, 0.0f);

    for (i=16; i<20; i++) {

      glTexCoord2f(data[5*i],data[5*i+1]);

      glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

    }


    // Левая грань

    glNormal3f(-1.0f, 0.0f, 0.0f);

    for (i=20; i<24; i++) {

      glTexCoord2f(data[5*i],data[5*i+1]);

      glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

    }


  glEnd();

}

 



Время инициализировать OpenGL. Все как в Уроке 06, кроме вызова initLights() вместо прямой инициализации источников света в теле функции. Да, и еще одно: я выполняю здесь настройку мультитекстурирования.

 

int InitGL(GLvoid)                      // Все настройки OpenGL проходят здесь



{

  multitextureSupported=initMultitexture();

  if (!LoadGLTextures()) return false;  // Переход к процедуре загрузки текстур

  glEnable(GL_TEXTURE_2D);              // Включить привязку текстур

  glShadeModel(GL_SMOOTH);              // Включит сглаживание

  glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Черный фон

  glClearDepth(1.0f);                   // Установка буфера глубины

  glEnable(GL_DEPTH_TEST);              // Включить проверку глубины

  glDepthFunc(GL_LEQUAL);               // Тип проверки глубины

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Наилучшая коррекция перспективы

 

  initLights();                         // Инициализация освещения OpenGL



  return true                           // Инициализация закончилась успешно

}

 



95% всей работы содержится здесь. Все, что упоминалось под грифом "по причинам, объясненным ниже", будет расписано в этом теоретическом блоке.

Начало теории ( Наложение микрорельефа методом тиснения )

 

Если у вас установлен просмотрщик Powerpoint-презентаций, я настоятельно рекомендую скачать следующую презентацию:



 

"Emboss Bump Mapping" by Michael I. Gold, nVidia Corp. [.ppt, 309K]  

 

Для тех, у кого нет просмотрщика, я попытался перевести презентацию в html-формат. Вот она:



 

Наложение микрорельефа методом тиснения

 

Майкл И. Голд



 

Корпорация Nvidia

 

Наложение микрорельефа (bump mapping)



 

Действительное наложение микрорельефа использует попиксельные вычисления освещенности



  • Вычисление освещенности в каждой точке базируется на возмущенном векторе нормали.

  • Вычисления весьма ресурсоемкие.

  • Более детальное описание читайте здесь: Blinn, J. : Simulation of Wrinkled Surfaces (Моделирование складчатых поверхностей), Computer Graphics. 12,3 (August 1978) 286-292.

  • Информация в сети: на http://www.objectecture.com/   лежит Cass Everitt’s Orthogonal Illumination Thesis (Диссертация по ортогональному освещению Касса Эверитта).

Наложение микрорельефа методом тиснения (emboss bump mapping)

 

Микрорельеф, наложенный тиснением, похож на резьбу по материалу



  • Учитывается только рассеянный свет, нет зеркальной составляющей

  • Возможны артефакты изображения из-за недостаточного размера текстуры рельефа (в результате, например, движение приводит к сильному размытию — прим. Дженса)

  • Выполнение возможно на пользовательском оборудовании современного уровня (как показано — прим. Дженса)

  • Если рельеф выглядит хорошо, используйте его!

Расчет рассеяния света

 

C=(L*N) x Dl x Dm


1   ...   9   10   11   12   13   14   15   16   ...   27


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница