Bump mapping (pas)


Содержание

Bump mapping (pas)

Подскажите как предать текстуре обёмность! Как в DOOM3!
Если можно пример!
Зарание спасибо!

а посмотреть пример из демо гордость мешает?

The bump shader runs an ambient light pass and a pass for
each light shining in the scene. There are currently 2
bump methods: a dot3 texture combiner and a basic ARB
fragment program.

The dot3 texture combiner only supports diffuse lighting
but is fast and works on lower end graphics adapters.

The basic ARBFP method supports diffuse and specular
lighting

Both methods pick up the light and material options
through the OpenGL state.

The normal map is expected as the primary texture.

Diffuse textures are supported through the secondary
texture and can be enabled using the boDiffuseTexture2
bump option.

Specular textures are supported through the tertiary
texture and can be enabled using the boSpecularTexture3
bump option and setting the SpecularMode to smBlinn or
smPhong (smOff will disable specular in the shader).

With the boLightAttenutation flag set the shader will
use the OpenGL light attenuation coefficients when
calculating light intensity.

взято из bunnybump.dpr

http://glscene.nm.ru/down/bumpearth.zip
Пример реализующий эффект bumpmapping»а.Рельефный земной шар.

Я наверное слепой невидел этот пример!
Спасибо!


> http://glscene.nm.ru/down/bumpearth.zip
> Пример реализующий эффект bumpmapping»а.Рельефный земной
> шар.

При компиляции вылазиет ошибка «Your driver/hardware does not support GLSL»
уменя Radeon 9000!
дрова последние!

А там и нету..
Они на радеонах только с 9800 если не ошибаюсь.. Это разновидность 2.0 шейдеров. Кажется.


> DeadMeat © (12.07.05 15:11) [5]

у меня на Radeon 9700pro mobility все работает.

у меня на 9600Про GLSL пахал как надо.

Ну один хер на 9000 нету.
А на 9600 я проверял.. Не было.. И еще на каких то проверял.. Вот правда делал я это давно.. Мож в дровах дело было.


. Death Is Only The Begining.

Пример работает с ATI Radeon начиная с 9600,у NVIDIA с FX5200 т.к. необходим шейдерный конвейр.

кста вышли дрова nVidia 77.какие-то, после их установки пдсцена не работает ибо в этих дровах OpenGL 2.0 и глсцена ошибку дает, то что ей нужен OpenGL 1.1

2 Xenon:
Достаточно перекомпилить проект

На обновленной версии.

2 DeadMeat:
Ну да, либо убрать флаг проверки в старой ;)

Проверка на версию (если снимок не очень старый) в файле GLScene.pas строчка в процедуре TGLSceneBuffer.CreateRC:
if not GL_VERSION_1_1 then

1 Frost aka Freak
Флаг проверки?
Это где?


> у меня на 9600Про GLSL пахал как надо.


> у меня на Radeon 9700pro mobility все работает.

9550 :) Работает

Bump mapping

Автор lafugix Дополнил FantomICW Тип статьи руководство Актуальность ТЧ, ЧН, ЗП Необходимый софт Adobe Photoshop, NVIDIA Texture Tools, X-Ray SDK

Эта статья [1] о создании правильных карт bump и bump# с помощью Adobe Photoshop и X-Ray SDK. С этими дополнительными составляющими текстурами много кто сталкивался, много кто искал пути правильного их создания. На самом деле, чтобы правильно создать бамп-карты, необходимо освоить как техническую сторону вопроса (делать, так сказать, «по науке»), так и практическую (различные методики работы в фотошопе, обработка текстуры, исправление возможных косяков и т. д.). В данной статье больше всего внимания будет уделяться именно первому пункту, ведь в любом деле лучше сначала выучить основы, а потом, в том числе, самостоятельно обучаясь, получить опыт в практике, улучшить свое мастерство.

Немного о структуре и стилистике статьи, дабы все было в дальнейшем более понятно
Многие игровые текстуры (в Сталкере — все) находятся в формате RGB. Если кто не знает, есть понятие «каналы». Их вкладка в Photoshop находится справа, «Слоев». В RGB это:

  • R — red (красный)
  • G — green (зеленый)
  • B — blue (синий)

При их «смешивании», получаем нормальную окраску изображения. В общем, указания по каналам в данной статье будут даваться в виде, как блок выше. Иногда в RGB каналов больше (можете сами попробовать добавлять в фотошопе). В игре используется канал Альфа 1 (его еще просто «Альфой» кличут). У него есть, разумеется, свои особые функции. Итого, вот так будут выглядеть указания по каналам:

  • R — что должно быть на красном канале
  • G — что на зеленом
  • B — что на синем
  • Alpha — что на Альфе

Статья разделена на три основные части:
1. Теория. Здесь будут описаны сами карты bump и bump#, а также некоторые другие рабочие карты и элементы (не забудьте заглянуть в подраздел «Мини-словарь терминов статьи»).
2. Работа в фотошопе. Описание действий в фотошопе, которые необходимы для подготовки элементов бампа.
3. SDK. Завершающая часть работы по бампу. Здесь собираются, регистрируются, настраиваются составляющие бампа, а в последствии, — создаются bump и bump#

Теория

Для начала о структуре основных карт и основных понятиях статьи. Всё ниже написанное не обязательно верно на все 100%.

Height Map (карта высот)

Карта высот — сугубо служебная карта в Сталкере. Как отдельная текстура не встречается, однако, если вы делаете карту нормалей в фотошопе, то для начала нужно будет создать или «запечь» в специальных программах карту высот. В карте высот все построено на белых (высоты), черных (впадины), серых (плюс-минус ровные плоскости) оттенках. В целом, в основном выходит, что основная часть изображения — серая, в выпуклых местах оно светлее, а на месте впадин — темнее.

Normal Map (карта нормалей)

Карту нормалей можно получить следующими способами:
— сгенерировать с высокополигональной модели в 3D-редакторе, в роде 3ds Max, zBrush.
— сгенерировать с ранее созданной карты высот с помощью одного из плагинов для Photoshop (плагин NVIDIA, плагин PixPlant, плагин xNormal)
— сгенерировать с ранее созданной карты высот в спец-прогах (xNormal, PixPlant, CrazyBump)
Если разбирать по каналам схематически, то выглядит так:

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

Карта bump представляет собой обычную карту нормалей в формате A(BGR) (типично для формата сжатия DXT5_nm). Разработчики использовали такой порядок по очень простой причине — DXT компрессия гораздо меньше «портит» текстуру, так как альфа-канал не подвергается компрессии и остается практически в исходном виде. Тут нужно пояснить: еще раз гляньте на каналы карты нормалей. А теперь представьте, что на пустое изображение с каналами RGBA поставили все ту же карту нормалей, только перевернутую по каналам. То есть наполнение R с карты нормалей перешло на канал Альфа (А) карты bump, G перешло в B, B перешло в G. Что касается канала R в бампе, туда поставили Specular Map (карту бликов). О ней можете подробней почитать в мини-словаре терминов.

Также стоит напомнить, что в своем полном виде карта bump создается из карты нормалей + текстурой с спекуляром на Альфе в SDK. Такая-вот текстурная солянка. Думаю, вы знаете, что она в игре делает, но если вы столкнулись с данными терминами первый раз, то все довольно просто: карта делает текстуру объекта более красивой, объемной, с выпуклостями и впадинами, добавляет или запрещает отражение текстурой света от солнца, лампы, фонарика и т. д. Вот, например, новенькому листу металла было бы неплохо придать возможность отражать блики, а кирпичной текстуре задать впадины между кирпичиками. А вот дереву, допустим, никаких отражений света не надо, хотя высоты (впадины и выпуклости) добавить таки можно. Еще замечу, что бампы не нужны для текстур партиклов, текстур интерфейса и еще ряда специфических изображений. К основной текстуре привязывается через файл текстура.thm. Но по поводу этого не беспокойтесь, ведь файл *.thm появляется при создании бампа/подключении бампа к текстуре в SDK.

Каналы в карте bump

bump# — очень интересная карта, назначение которой для многих непонятно. Так как найти какую-либо информацию насчет правильного названия не удалось, назовём ее просто «Normal error map«. Неизвестно, используются ли подобные технологии в других играх, но встречать ее где-либо, кроме сталкера, не приходилось. Эта карта «исправляет» ошибки сжатия DXT у бампа и генерируется только с помощью SDK, поэтому всякие непонятные действия некоторых пользователей, вроде вставки вместо нее обычной или обесцвеченной карты нормалей, а также описанного в статье Argus’a заливания серым со вставкой в альфа-канал карты высот, использовать нельзя. Выглядит как серое зашумленное изображение. Еще раз стоит напомнить, bump# создается автоматически при генерировании bump в SDK.

C bump# Без bump#, заметны артефакты

Мини-словарь терминов

Здесь будет находиться описание некоторых терминов, которые встречаются в статье.

Diffuse texture aka диффузная текстура ака диффуз — основная исходная текстура, для которой делаются дополнительные карты.

Specular Map aka карта бликов — карта, которая в последствии работы в SDK станет частью бампа (на красном канале). Отвечает за отражение текстурой света. Изготавливается либо с диффуза в Photoshop, либо параллельно с диффузом в 3D-редакторе. На карте черным (и оттенками черного) обозначаются места, которые не будут отсвечивать свет), а белым (и оттенками) — которые будут отсвечивать.

Работа в Photoshop

Здесь также понадобится Normal Map Filter от NVIDIA, если вы уже работали с текстурами, он должен быть у вас установлен, так как поставляется в комплекте с *.dds-плагином или другой удобный плагин (озвучены выше, в теории карты нормалей).

Все файлы сохраняются в tga 24 bits/pixel (если нету альфа-канала) или 32 bits/pixel (если есть альфа-канал). Перед сохранением следует перевести изображение из 16-битного в 8-битное.

Карта высот

На карте высот белым цветом обозначены выпуклости, черным — впадины. Если бамп делается для текстур окружения, то можно начать работу с оригинальной (диффузной) текстуры, если же бамп делается для персонажа, то такой способ обычно неприемлем, зачастую из-за камуфляжа, который никак не должен сказываться на рельефе ткани (хотя, в принципе, если есть определенный опыт работы, прямые руки и старание, то подобных косяков можно избежать).

По сути, создание карты высот — процесс творческий, есть лишь несколько советов:


  • Над картой высот работа должна проводиться строго в 16bits, причем с самого начала до финального экспорта в SDK
Перевод в 16-битное изображение
  • Качественного результата не будет, если просто взять и засунуть диффузную текстуру в генератор, не делайте этого. Придется тщательно поработать над картой высот.
  • Необязательно пытаться показать на карте высот все детали и рельеф сразу, их можно добавить позднее, когда будет сгенерирована карта нормалей.
Пример добавления деталей
  • Для карты высот, если делаете ее с диффуза, можно взять один из каналов RGB диффузной текстуры или же взять обесцвеченную. При первом варианте, выберите желаемый канал, выделяйте изображения, копируйте, вставляйте его в новый документ. Новое изображение будет в режиме «Градация серого». Рекомендуется перевести опять в RGB, так как не все плагины и фильтры читают «Градацию». При втором варианте, просто создайте дубликат изображения и нажмите Ctrl+Shift+U («Обесцветить»).
  • На текстуре карты высот можно сделать слой поверх фонового, на который нужно скопировать обесцвеченную инвертированную исходную текстуру. Звучит не очень, но это все не сложно. Берем диффуз, нажимаем Ctrl+I (инвертируем). Потом обесцвечиваем, как показано выше. Копируем на новый слой карты высот. Режим наложения выставляем «Перекрытие» («Overlay»). Дальше еще можно сделать слой более прозрачным. В целом, смотрите, чтоб текстура была сбалансированной в плане плоскостей. Ровная часть должна быть заполнена серым оттенком.
  • Очень пригодятся в этом случае инструменты «Осветлитель» и «Затемнитель».
  • Поверх всего можно добавить еще один слой, на котором будете использовать инструменты в роде «Кисти».

Metro 2033 (если кому интересно)

Пример получаемой карты нормалей из Метро 2033

В метро вместо карт нормалей, как обычно бывает в большинстве игр, используется карта высот — файлы с постфиксом _bump. Они есть только в том случае, если вы распаковывали игру самостоятельно, большая часть сборщиков «Архивов моделей» их в архив не добавляет. «Состав» карты высот Метро 2033:

Карта нормалей

Создание происходит следующим образом: берем карту высот, открываем фильтр, задаем настройки по усмотрению. Далее получаем готовую карту нормалей. Нужно осмотреть карту, чтоб там не было мелких не нужных горбиков и прочих недостатков. Скорее всего, все можно будет решить размытием в некоторых местах. Еще, как вариант, чудесная методика размытия, при которой изображение все равно остается более четким:
1. Дублируем фоновый слой.
2. Дубликату выставляем режим «Перекрытие».
3. Применяем один из фильтров размытия (к примеру, «Размытие по гауссу»).
4. Если надо, дублируем этот слой еще пару раз.

Specular Map

Данную карту создавать не сложно: взять обесцвеченный диффуз и обработать темным оттенком те регионы, которые не будут отражать свет, а светлым — которые будут. Чем интенсивней оттенок, тем сильнее эффект.

Карта нормалей

При наличии уже готовой карты нормалей, изготовленной вами, из другой игры, zBrush или любой другой программы высокополигонального 3D моделирования, остается только добавить ее в SDK. Для чего регистрировать в SDK? Для того, чтоб потом совместить Normal Map с заготовкой для бампа и получить бамп. Параметры:

Type: 2D Texture
Format: 32 bit (8:8:8:8)
MipMap Filter: Kaiser (здесь уже по желанию, рекомендуется Kaiser)

Type: Normal Map
MipMap Filter: Kaiser

С использованием отдельной карты нормалей bump карта изготавливается очень просто (заготовку для бампа лучше назвать текстура_bump.tga и поместить в editors/import):

Параметры добавления в SDK не зависят от версии:
Type: Bump Map, в Special Normal Map указываем добавленную ранее карту нормалей.

В Photoshop Добавление в SDK

Если же вдруг возникло желание обойтись без дополнительной карты нормалей, то добавление в SDK и каналы — меняются:

Это единственный вариант, когда параметр Virtual height действительно влияет на интенсивность получаемого бампа.

Бамп, смещение, и карты нормалей

Для детализации поверхности ваших моделей LuxRender поддерживает bump, displacement и microdisplacement.

Важно помнить, какие единицы измерения использует LuxRender. 1 unit в LuxRender равен одному метру. Поэтому, если вам нужен небольшой рельеф, начинайте со значений 0.01 или даже 0.001.

В отличие от диффузных и спекулярных каналов, а также каналов прозрачности, bump и displacement maps просто используют яркость любой текстуры, а не значение цвета. Если ваша текстура имеет переключатель color/float, не забудьте установить его на «float».

Bump Maps

Bump mapping — это процесс локального переопределения затенения поверхности, основанного на текстуре без изменения геометрии. Таким способом можно создавать множество мелких деталей без изменения излишне сложных моделей.

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

При использовании положительных значений карты рельефа среднее значение яркости принимается за базовый уровень поверхности; более светлые оттенки будут формировать выпуклости, а более тёмные — углубления.

Normal Maps (карты нормалей)

Normal maps — это расширенная версия bump maps, которая может деформировать фиктивную поверхность в любом направлении, а не только вверх или вниз, как это делает bump map. Для использования normal maps в LuxRender создаются специальные текстуры «normal map» в канале «bump mapping». При использовании запечённой карты нормалей (например, из высокополигонального меша) необходимо включить опцию «generate tangents» для этого меша.

В настоящий момент LuxRender поддерживает только карты нормалей касательного пространства.

Displacement (смещение)

Displacement похож на bump mapping, но он изменяет реальную геометрию поверхности объекта, основываясь на текстуре. Дополнительно поверхность объекта может быть подразделена, в результате чего получается более детальная форма.

Единицы измерения для смещения идентичны единицам измерения для bump mapping, где 1 единица равна 1 метру. Лучше начинать с небольших значений.

Поскольку displacement обычно требует очень высокого уровня подразделения, оно является требовательным к памяти. Если это является проблемой, вместо этого вы можете использовать microdisplacement.

Microdisplacement

Microdisplacement — это метод смещения поверхности на лету.

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

Работа подразделения Microdisplacement отличается от работы обычного подразделения поверхности, используемого для displacement или сглаживания сетки.

Потребуется 5-20 или больше уровней подразделения для достижения приемлемого эффекта.

Bump mapping (pas)

Сфера IT всегда в поиске квалифицированных специалистов. Зарплаты высокие, задачи интересные, есть возможность работать удаленно или на фрилансе, но за красивые глаза всего этого не получить. Нужно учиться, нарабатывать опыт и портфолио. Получать второе (да и первое) высшее в сфере IT — решение сомнительное. Долго, дорого, а учиться, скорее всего, будете по бумажным учебникам (нужно объяснять, почему это плохо?). Рассказываем о трех специальностях в веб-разработке, которые реально освоить на онлайн-курсах.

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

Работая в связке с бэкенд-разработчиком и дизайнером, фронтенд собирает все компоненты с помощью HTML-разметки, а после через CSS соединяет их с графическими элементами: фонами, кнопками, картинками, шрифтами и прочим. Увидеть, как выглядят HTML и CSS изнутри, вы можете прямо сейчас, если читаете этот пост на компьютере. Нажмите F12 в браузере и любуйтесь. Да, ни черта не понятно, но на этих строках все и держится.

Когда разметка и код готовы, в дело вступает язык программирования JavaScript. Он соединяет оформление с набором функций. В результате у нас рабочий сайт.

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

Стоимость обучения: 69 000 рублей

Длительность: 6 месяцев

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

Full-stack веб-разработчик на Python

Full-stack разработчик — мастер на все руки. Он и программный код сможет написать, и страницу сверстает, и даже дизайн нарисует, если уж очень надо. Но, в сущности, он скорее организовывает работу и руководит проектом, потому что обладает знаниями по каждому этапу разработки. Хотя способен и сам включиться в работу и сделать что-то руками.

Специальность подразумевает, что владеть придется многими инструментами, но универсальный солдат эффективнее, когда пользуется универсальным оружием. Для фулл-стека им становится язык программирования Python. Он позволяет анализировать данные в больших таблицах, писать ботов для Telegram, создавать код сайта и вообще много чего. Дополнительных баллов в пользу Python можно накинуть за его простоту и распространенность. Но курс не только про Python. Вы немного познакомитесь с HTML, CSS и JavaScript, узнаете, как работать с веб-серверами и операционной системой Linux. Отдельный модуль курса посвящен грамотной организации рабочего процесса на фрилансе, поиску клиентов и другим штукам, которые пригодятся вам не только в веб-разработке.

Стоимость обучения: 73 900 рублей

Длительность: 9 месяцев

Без тестировщика невозможно запустить ни один крупный проект. Сайт, игра или приложение с прогнозом погоды — без разницы. Основная задача тестировщика — поиск слабых мест продукта. И тут речь не только о багах, но и о системе безопасности, удобстве и функциональности.

Искать недочеты вручную не придется. Эта работа скорее аналитическая и техническая. Важно понимать, как работает продукт, и четко формулировать запрос при тестировании продукта, а после корректно его считывать. Тестировщик создает и адаптирует программное обеспечение, которое будет одновременно передавать большое количество запросов к продукту (в нашем случае — к сайту) и выдавать некий результат.

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

Стоимость обучения: 59 900 рублей


Длительность: 4 месяца

Нет! Веб-разработка не ограничивается этими специальностями. Когда приступите к поиску работы, начнете читать профильные сайты, общаться с людьми из индустрии, для вас откроется простор для развития в совершенно разных направлениях. Можно углубиться в тестирование, автоматизировать процессы и ловить кайф от «чистых» проектов без багов. А можно совместить знания из курса по full-stack разработке с веб-дизайном и стать ультимативным фрилансером, которого хотят все.

Курсы SkillFactory — это удобный и эффективный способ получить специальность в сфере IT. За пару месяцев обучения вы наработаете портфолио и займетесь своими первыми коммерческими проектами. По специальностям, перечисленным в посте, дают скидки новым студентам или при оплате до определенной даты. Также есть полугодовая отсрочка и рассрочка на 12 месяцев.

Bump mapping (pas)

Бампмаппинг — это техника симуляции неровностей (или моделирования
микрорельефа, как больше нравится) на плоской поверхности без больших
вычислительных затрат и изменения геометрии. Для каждого пикселя
поверхности выполняется вычисление освещения, исходя из значений в
специальной карте высот, называемой bumpmap. Это обычно 8-битная
черно-белая текстура и значения цвета текстуры не накладываются как
обычные текстуры, а используются для описания неровности поверхности.
Цвет каждого текселя определяет высоту соответствующей точки рельефа,
большие значения означают большую высоту над исходной поверхностью, а
меньшие, соответственно, меньшую. Или наоборот.

Степень освещенности точки зависит от угла падения лучей света. Чем
меньше угол между нормалью и лучом света, тем больше освещенность точки
поверхности. То есть, если взять ровную поверхность, то нормали в каждой
ее точке будут одинаковыми и освещенность также будет одинаковой. А если
поверхность неровная (собственно, практически все поверхности в
реальности), то нормали в каждой точке будут разными. И освещенность
разная, в одной точке она будет больше, в другой — меньше. Отсюда и
принцип бампмаппинга — для моделирования неровностей для разных точек
полигона задаются нормали к поверхности, которые учитываются при
вычислении попиксельного освещения. В результате получается более
натуральное изображение поверхности, бампмаппинг дает поверхности
большую детализацию, такую, как неровности на кирпиче, поры на коже и
т.п., без увеличения геометрической сложности модели, так как расчеты
ведутся на пиксельном уровне. Причем, при изменении положения источника
света освещение этих неровностей правильно изменяется.

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

Для продолжения необходимо напомнить о составляющих освещения. Цвет
точки поверхности рассчитывается как сумма ambient, diffuse и specular
составляющих от всех источников света в сцене (в идеале от всех,
зачастую многими пренебрегают). Вклад в это значение от каждого
источника света зависит от расстояния между источником света и точкой на
поверхности.

Равномерная (ambient) составляющая освещения — аппроксимация глобального
освещения, «начальное» освещение для каждой точки сцены, при котором все
точки освещаются одинаково и освещенность не зависит от других факторов.
Диффузная (diffuse) составляющая освещения зависит от положения
источника освещения и от нормали поверхности. Эта составляющая освещения
разная для каждой вершины объекта, что придает им объем. Свет уже не
заполняет поверхность одинаковым оттенком.
Бликовая (specular) составляющая освещения проявляется в бликах
отражения лучей света от поверхности. Для ее расчета, помимо вектора
положения источника света и нормали, используются еще два вектора:
вектор направления взгляда и вектор отражения. Specular модель освещения
впервые предложил Фонг (Phong Bui-Tong). Эти блики существенно
увеличивают реалистичность изображения, ведь редкие реальные поверхности
не отражают свет, поэтому specular составляющая очень важна. Особенно в
движении, потому что по бликам сразу видно изменение положения камеры
или самого объекта. В дальнейшем, исследователи придумывали иные способы
вычисления этой составляющей, более сложные (Blinn, Cook-Torrance,
Ward), учитывающие распределение энергии света, его поглощение
материалами и рассеивания в виде диффузной составляющей.

Что касается первого аппаратного применения, то некоторые виды
бампмаппинга (Emboss Bump Mapping) стали использовать еще во времена
видеокарт на базе чипов NVIDIA Riva TNT, однако техники того времени
были крайне примитивны и широкого применения не получили. Следующим
известным типом стал Environment Mapped Bump Mapping (EMBM), но
аппаратной его поддержкой в DirectX в то время обладали только
видеокарты Matrox, и опять применение было сильно ограничено. Затем
появился Dot3 Bump Mapping и видеочипы того времени (GeForce 256 и
GeForce 2) требовали три прохода для того, чтобы полностью выполнить
такой математический алгоритм, так как они ограничены двумя одновременно
используемыми текстурами. Начиная с NV20 (GeForce3), появилась
возможность делать то же самое за один проход при помощи пиксельных
шейдеров. Дальше — больше. Стали применять более эффективные техники,
такие как Normal Mapping.

Наложение карт смещения (Displacement Mapping) является методом
добавления деталей к трехмерным объектам. В отличие от бампмаппинга и
других попиксельных методов, когда картами высот правильно моделируется
только освещенность точки, но не изменяется ее положение в пространстве,
что дает лишь иллюзию увеличения сложности поверхности, карты смещения
позволяют получить настоящие сложные 3D объекты из вершин и полигонов,
без ограничений, присущих попиксельным методам. Этот метод изменяет
положение вершин треугольников, сдвигая их по нормали на величину,
исходя из значений в картах смещения. Карта смещения (displacement map)
— это обычно черно-белая текстура, и значения в ней используются для
определения высоты каждой точки поверхности объекта (значения могут
храниться как 8-битные или 16-битные числа), схоже с bumpmap. Часто
карты смещения используются (в этом случае они называются и картами
высот) для создания земной поверхности с холмами и впадинами. Так как
рельеф местности описывается двухмерной картой смещения, его
относительно легко деформировать при необходимости, так как это
потребует всего лишь модификации карты смещения и рендеринга на ее
основе поверхности в следующем кадре.

Большим преимуществом наложения карт смещения является не просто
возможность добавления деталей к поверхности, а практически полное
создание объекта. Берется низкополигональный объект, разбивается
(тесселируется) на большее количество вершин и полигонов. Вершины,
полученные в результате тесселяции, затем смещаются по нормали, исходя
из значения, прочитанного в карте смещения. Получаем в итоге сложный 3D
объект из простого, используя соответствующую displacement карту.

Количество треугольников, созданных при тесселяции, должно быть
достаточно большим для того, чтобы передать все детали, задаваемые
картой смещений. Иногда дополнительные треугольники создаются
автоматически, используя N-патчи или другие методы. Карты смещения лучше
использовать совместно с бампмаппингом для создания мелких деталей, где
достаточно правильного попиксельного освещения.
Наложение карт смещения впервые получило поддержку в DirectX 9.0. Это
была первая версия данного API, которая поддержала технику Displacement
Mapping. В DX9 поддерживается два типа наложения карт смещения, filtered
и presampled. Первый метод был поддержан забытым уже видеочипом MATROX
Parhelia, а второй — ATI RADEON 9700. Filtered метод отличается тем, что
позволяет использовать мип-уровни для карт смещения и применять для них
трилинейную фильтрацию. В таком методе мип-уровень карты смещения
выбирается для каждой вершины на основе расстояния от вершины до камеры,
то есть уровень детализации выбирается автоматически. Таким образом
достигается почти равномерное разбиение сцены, когда треугольники имеют
примерно одинаковый размер.

Илон Маск рекомендует:  Asp упрощение разработки с помощью изолирования процессов

Наложение карт смещения можно по существу считать методом сжатия
геометрии, использование карт смещения снижает объем памяти, требуемый
для определенной детализации 3D модели. Громоздкие геометрические данные
замещаются простыми двухмерными текстурами смещения, обычно 8-битными
или 16-битными. Это снижает требования к объему памяти и пропускной
способности, необходимой для доставки геометрических данных к видеочипу,
а эти ограничения являются одними из главных для сегодняшних систем. Или
же, при равных требованиях к пропускной способности и объему памяти,
наложение карт смещения позволяет использовать намного более сложные
геометрически 3D модели. Применение моделей значительно меньшей
сложности, когда вместо десятков или сотен тысяч треугольников
используют единицы тысяч, позволяет еще и ускорить их анимацию. Или же
улучшить, применив более сложные комплексные алгоритмы и техники, вроде
имитации тканей (cloth simulation).

Другое преимущество в том, что применение карт смещения превращает
сложные полигональные трехмерные сетки в несколько двухмерных текстур,
которые проще поддаются обработке. Например, для организации Level of
Detail можно использовать обычный мип-маппинг для наложения карт
смещения. Также, вместо сравнительно сложных алгоритмов сжатия
трехмерных сеток можно применять привычные методы сжатия текстур, даже
JPEG-подобные. А для процедурного создания 3D объектов можно
использовать обычные алгоритмы для двухмерных текстур.

Но у карт смещения есть и некоторые ограничения, они не могут быть
применены во всех ситуациях. Например, гладкие объекты, не содержащие
большого количества тонких деталей, будут лучше представлены в виде
стандартных полигональных сеток или иных поверхностей более высокого
уровня, вроде кривых Безье. С другой стороны, очень сложные модели,
такие как деревья или растения, также нелегко представить картами
смещения. Есть также проблемы удобства их применения, это почти всегда
требует специализированных утилит, ведь очень сложно напрямую создавать
карты смещения (если речь не идет о простых объектах, вроде ландшафта).
Многие проблемы и ограничения, присущие картам смещения, совпадают с
таковыми у наложения карт нормалей, поскольку эти два метода по сути —
два разных представления похожей идеи.

В качестве примера из реальных игр приведу игру, в которой используется
выборка текстур из вершинного шейдера, возможность, появившаяся в
видеочипах NVIDIA NV40 и шейдерной модели 3.0. Вершинное текстурирование
можно применить для полностью выполняемого видеочипом простого метода
наложения карт смещения, без тесселяции (разбиения на большее количество
треугольников). Применение такому алгоритму ограничено, они имеют смысл,
только если карты будут динамическими, то есть, будут изменяться в
процессе. Например, это рендеринг больших водных поверхностей, что и
сделано в игре Pacific Fighters.

The Cg Tutorial

The Cg Tutorial is now available, right here, online. You can purchase a beautifully printed version of this book, and others in the series, at a 30% discount courtesy of InformIT and Addison-Wesley.

Please visit our Recent Documents page to see all the latest whitepapers and conference presentations that can help you with your projects.

Chapter 8. Bump Mapping

Chapter 8. Bump Mapping

This chapter enhances the per-fragment lighting discussion in Chapter 5 with texture-encoded normals to create an effect known as bump mapping. This chapter has the following five sections:

  • «Bump Mapping a Brick Wall» introduces bump mapping of a single rectangle.
  • «Bump Mapping a Brick Floor» explains how to make bump mapping consistent for two planes.
  • «Bump Mapping a Torus» shows how to bump map curved surfaces that have mathematical representations, such as the torus.
  • «Bump Mapping Textured Polygonal Meshes» shows how to apply bump mapping to textured polygonal models.
  • «Combining Bump Mapping with Other Effects» combines bump mapping techniques with other textures, such as decals and gloss maps, for more sophisticated shading.

8.1 Bump Mapping a Brick Wall

The earlier presentation of lighting in Chapter 5 discussed per-vertex and per-fragment light computations. This chapter introduces an advanced lighting approach commonly called bump mapping. Bump mapping combines per-fragment lighting with surface normal perturbations supplied by a texture, in order to simulate lighting interactions on bumpy surfaces. This effect is achieved without requiring excessive geometric tessellation of the surface.

As an example, you can use bump mapping to make surfaces appear as if they have bricks protruding from them, and mortar between the bricks.

Most real-world surfaces such as brick walls or cobblestones have small-scale bumpy features that are too fine to represent with highly tessellated geometry. There are two reasons to avoid representing this kind of fine detail using geometry. The first is that representing the model with sufficient geometric detail to capture the bumpy nature of the surface would be too large and cumbersome for interactive rendering. The second is that the surface features may well be smaller than the size of a pixel, meaning that the rasterizer could not accurately render the full geometric detail.

With bump mapping, you can capture the detailed surface features that influence an object’s lit appearance in a texture instead of actually increasing the object’s geometric complexity. Done well, bump mapping convinces viewers that a bump-mapped scene has more geometry and surface richness than is actually present. Bump mapping is most compelling when lights move in relation to a surface, affecting the bump-mapped surface’s illuminated appearance.

Benefits of bump mapping include:

  • A higher level of visual complexity in a scene, without adding more geometry.
  • Simplified content authoring, because you can encode surface detail in textures as opposed to requiring artists to design highly detailed 3D models.
  • The ability to apply different bump maps to different instances of the same model to give each instance a distinct surface appearance. For example, a building model could be rendered once with a brick bump map and a second time with a stucco bump map.

8.1.1 The Brick Wall Normal Map

Consider a wall made of bricks of varying texture stacked on top of each other in a regular pattern. Between the bricks is mortar that holds the bricks together. The mortar is set into the wall. Though a brick wall may look flat from a distance, on closer inspection, the brickwork pattern is not flat at all. When the wall is illuminated, the gaps between bricks, cracks, and other features of the brick surface scatter light quite differently than a truly flat surface.

One approach to rendering a brick wall would be to model every brick, mortar gap, and even individual cracks in the wall with polygons, each with varying surface normals used for lighting. During lighting, the surface normals at each vertex would alter the illuminated surface appearance appropriately. However, this approach may require a tremendous number of polygons.

At a sufficiently coarse scale, a brick wall is more or less flat. Aside from all the surface variations that we’ve mentioned, a wall’s geometry is quite simple. A single rectangle can adequately represent a roughly flat rectangular section of brick wall.

8.1.2 Storing Bump Maps As Normal Map Textures

Before you encounter your first Cg program that lights surfaces with bump mapping, you should understand how textures for bump mapping are created and what they represent.

Conventional Color Textures

Conventional textures typically contain RGB or RGBA color values, though other formats are also available. As you know, each texel in an RGB texture consists of three components, one each for red, green, and blue. Each component is typically a single unsigned byte.

Because programmable GPUs allow arbitrary math and other operations to be performed on the results of texture lookups, you can use textures to store other types of data, encoded as colors.

Storing Normals in Conventional Color Textures

Bump maps can take a variety of forms. All the bump mapping examples in this book represent surface variations as surface normals. This type of bump map is commonly called a normal map, because normals, rather than colors, are stored in the texture. Each normal is a direction vector that points away from the surface and is usually stored as a three-component vector.

Conventional RGB texture formats are typically used to store normal maps. Unlike colors that are unsigned, direction vectors require signed values. In addition to being unsigned, color values in textures are typically constrained to the [0, 1] range. Because the normals are normalized vectors, each component has a [-1, 1] range.

To allow texture-filtering hardware for unsigned colors to operate correctly, signed texture values in the [-1, 1] range are range-compressed to the unsigned [0, 1] range with a simple scale and bias.

Signed normals are range-compressed this way:

After conventional unsigned texture filtering, range-compressed normals are expanded back to their signed range this way:

By using the red, green, and blue components of an RGB texture to store the x, y, and z components of a normal, and range-compressing the signed values to the [0, 1] unsigned range, normals may be stored in RGB textures.

Recent GPUs support signed floating-point color formats, but normal maps are still often stored in unsigned color textures because existing image file formats for unsigned colors can store normal maps. Recent GPUs have no performance penalty for expanding range-compressed normals from textures. So whether you store normals in a range-compressed form (using an unsigned texture format) or use a signed texture format is up to you.

Generating Normal Maps from Height Fields

Authoring normal maps raises another issue. Painting direction vectors in a computer paint program is very unintuitive. However, most normal maps are derived from what is known as a height field. Rather than encoding vectors, a height field texture encodes the height, or elevation, of each texel. A height field stores a single unsigned component per texel rather than using three components to store a vector. Figure 8-1 shows an example of a brick wall height field. (See Plate 12 in the book’s center insert for a color version of the normal map.) Darker regions of the height field are lower; lighter regions are higher. Solid white bricks are smooth. Bricks with uneven coloring are bumpy. The mortar is recessed, so it is the darkest region of the height field.

Figure 8-1 A Height Field Image for a Brick Bump Map

Converting a height field to a normal map is an automatic process, and it is typically done as a preprocessing step along with range compression. For each texel in the height field, you sample the height at the given texel, as well as the texels immediately to the right of and above the given texel. The normal vector is the normalized version of the cross product of two difference vectors. The first difference vector is (1, 0, Hr – Hg ), where Hg is the height of the given texel and Hr is the height of the texel directly to the right of the given texel. The second difference vector is (0, 1, Ha – Hg ), where Ha is the height of the texel directly above the given texel.

The cross product of these two vectors is a third vector pointing away from the height field surface. Normalizing this vector creates a normal suitable for bump mapping. The resulting normal is:

This normal is signed and must be range-compressed to be stored in an unsigned RGB texture. Other approaches exist for converting height fields to normal maps, but this approach is typically adequate.

The normal (0, 0, 1) is computed in regions of the height field that are flat. Think of the normal as a direction vector pointing up and away from the surface. In bumpy or uneven regions of the height field, an otherwise straight-up normal tilts appropriately.

As we’ve already mentioned, range-compressed normal maps are commonly stored in an unsigned RGB texture, where red, green, and blue correspond to x, y, and z. Due to the nature of the conversion process from height field to normal map, the z component is always positive and often either 1.0 or nearly 1.0. The z component is stored in the blue component conventionally, and range compression converts positive z values to the [0.5, 1] range. Thus, the predominant color of range-compressed normal maps stored in an RGB texture is blue. Figure 8-1 also shows the brick wall height field converted into a normal map. Because the coloration is important, you should refer to the color version of Figure 8-1 shown in Plate 12.

8.1.3 Simple Bump Mapping for a Brick Wall


Now that you know what a normal map is, you’re ready for your first bump mapping example. The example will show how to use the brick wall normal map in Figure 8-1 to render a single bump-mapped rectangle to look like a brick wall. When you interactively move a single light source, you will change the appearance of the wall due to the brick pattern in the normal map, as shown in Figure 8-2. The figure shows the effect of three different light positions. In the left image, the light is at the lower left of the wall. In the center image, the light is directly in front of the wall. And in the right image, the light is at the upper right of the wall.

Figure 8-2 A Bump-Mapped Brick Wall with Different Light Positions

To keep things simple, this first example is quite constrained. We position the rendered wall rectangle in the xy plane, with z equal to 0 everywhere on the wall. Without bump mapping, the surface normal for the wall would be (0, 0, 1) at every point on the wall.

The Vertex Program

The C8E1v_bumpWall vertex program in Example 8-1 computes the object-space vector from a vertex to a single light source. The program also transforms the vertex position into clip space using a conventional modelview-projection matrix, and it passes through a 2D texture coordinate set intended to sample the normal map texture.

Example 8-1. The C8E1v_bumpWall Vertex Program

The Fragment Program

The dot product of the light vector and the normal vector for diffuse lighting requires a unit-length light vector. Rather than implement the normalization directly with math operations, the C8E2f_bumpSurf fragment program in Example 8-2 normalizes the interpolated light direction vector using a normalization cube map, which will be explained shortly. For now, think of a normalization cube map as a way to take an unnormalized vector that is interpolated as a texture coordinate set and generate a normalized and range-compressed version of it. Because the program implements normalization with a cube map texture access, this style of per-fragment vector normalization is fast and well suited for the broadest range of GPUs.

In addition to normalizing the interpolated light vector, the C8E2f_bumpSurf program samples the normal map with conventional 2D texture coordinates. The result of the normal map access is another range-compressed normal.

Next, the program’s helper function, named expand , converts both the range-compressed normalized light direction and the range-compressed normal into signed vectors. Then the program computes the final fragment color with a dot product to simulate diffuse lighting.

Example 8-2 illustrates how the appearance of the brick wall changes with different light positions. The wall’s surface, rendered with the C8E1v_bumpWall and C8E2f_bumpSurf programs, really looks like it has the texture of a brick wall.

Example 8-2. The C8E2f_bumpSurf Fragment Program

Constructing a Normalization Cube Map

Chapter 7 explained how you could use cube maps for encoding environment maps as a way to give objects a reflective appearance. To simulate surface reflections, the 3D texture coordinate vector used for accessing the environment cube map represents a reflection vector. But cube map textures can encode other functions of direction vectors as well. Vector normalization is one such function.

The Cg Standard Library includes a routine called normalize for normalizing vectors. The routine has several overloaded variants, but the three-component version is most commonly used. The standard implementation of normalize is this:

The problem with this implementation of normalize is that basic fragment profiles provided by second-generation and third-generation GPUs cannot directly compile the normalize routine that we just presented. This is because these GPUs lack arbitrary floating-point math operations at the fragment level.

The normalization cube map—a fast way to normalize vectors supplied as texture coordinates—works on all GPUs, whether or not they support advanced fragment programmability.

Note

Even on GPUs supporting advanced fragment profiles, using normalization cube maps is often faster than implementing normalization with math operations, because GPU designers highly optimize texture accesses.

Figure 8-3 shows how a cube map normalizes a vector. The vector (3, 1.5, 0.9) pierces the cube map on the positive x face of the cube map, as shown. The faces of the normalization cube map are constructed such that the texel pierced by any given direction vector contains the normalized version of that vector. When signed texture components are unavailable, the normalized version of the vector may be stored range-compressed and then expanded prior to use as a normalized vector. This is what the examples in this chapter assume. So the range-compressed texel pierced by (3, 1.5, 0.9) is approximately (0.93, 0.72, 0.63). When this vector is expanded, it is (0.86, 0.43, 0.26), which is the approximate normalized version of (3, 1.5, 0.9).

Figure 8-3 Using Cube Maps to Normalize Vectors

A resolution of 32×32 texels is typically sufficient for a normalization cube map face with 8-bit color components. A resolution of 16×16, and even 8×8, can also generate acceptable results.

The electronic material accompanying this book includes a normalization cube map, as well as source code for constructing normalization cube maps. All of your Cg programs can share just one normalization cube map.

8.1.4 Specular Bump Mapping

You can further enhance the preceding programs by adding specular and ambient lighting terms and by adding control over the diffuse material, specular material, and light color. The next pair of programs illustrate this.

The Vertex Program

Example 8-3 extends the earlier C8E1v_bumpWall example to compute the half-angle vector, which the rasterizer interpolates as an additional texture coordinate set. The C8E3v_specWall program computes the half-angle vector by normalizing the sum of the vertex’s normalized light and eye vectors.

Example 8-3. The C8E3v_specWall Vertex Program

The Fragment Program

The corresponding fragment program, shown in Example 8-4, requires more modifications. In addition to normalizing the light vector with a normalization cube map as before, the updated fragment program also normalizes the half-angle vector with a second normalization cube map. Then, the program computes the dot product of the normalized half-angle vector with the perturbed normal obtained from the normal map.

Example 8-4. The C8E4f_specSurf Fragment Program

In the original C8E2f_bumpSurf program, the program outputs the diffuse dot product as the final color. The final color’s COLOR output semantic implicitly clamps any negative dot product results to zero. This clamping is required by the diffuse lighting equation, because negative illumination is physically impossible. The C8E4f_specSurf program combines the diffuse and specular dot products, so the program explicitly clamps negative values to zero with the saturate Standard Library function:

saturate(x)

Clamps a scalar or all the components of a vector to the range [0, 1].

Basic fragment profiles such as fp20 and ps_1_3 lack support for true exponentiation. To simulate specular exponentiation and support a broad range of fragment profiles, the program uses three successive multiplications to raise the specular dot product to the eighth power. Advanced profiles could use the pow or lit Standard Library functions for more generality.

Finally, the output color is computed by modulating the ambient and diffuse terms by a uniform parameter, LMd . The application supplies LMd , which represents the light color premultiplied by the material’s diffuse color. Similarly, the LMs uniform parameter modulates the specular illumination and represents the light color premultiplied by the material’s specular color.

Further Improvements

The C8E4f_specSurf program compiles under basic and advanced fragment profiles. Although this makes the bump mapping effect portable to a wider variety of GPUs, various improvements are possible if you target an advanced fragment profile. Following are a few examples.

C8E4f_specSurf binds the same normalization cube map texture into two texture units. As you saw in Chapter 7, this duplicate binding is required because basic fragment profiles can only sample a given texture unit with that texture unit’s corresponding texture coordinate set. Advanced fragment profiles do not have this limitation, so a single normalizeCube cube map sampler can normalize both the light vector and half-angle vector.

C8E4f_specSurf also computes the specular exponent by raising the specular dot product to the eighth power using successive multiplies, because basic fragment profiles do not support arbitrary exponentiation. An advanced profile version could use the following code:

where specularExponent is a uniform parameter, or even a value from a texture.

C8E3v_specWall computes a per-vertex half-angle vector. Ideally, you should compute the half-angle vector from the light and view vectors at every fragment. You could modify the vertex program to output the eyeDirection value rather than the half-angle vector. Then you could modify C8E4f_specSurf to compute the half-angle vector at each fragment, as shown:

As explained in Chapter 5, computing the half-angle vector at every fragment creates more realistic specular highlights than computing the half-angle vector per-vertex, but it is more expensive.

8.1.5 Bump Mapping Other Geometry

You have learned how to bump map a brick wall, and the results in Figure 8-2 are quite promising. However, bump mapping is not as simple as these first examples might have you believe.

The wall rectangle rendered in Figure 8-2 happens to be flat and has a surface normal that is uniformly (0, 0, 1). Additionally, the texture coordinates assigned to the rectangle are related to the vertex positions by a uniform linear mapping. At every point on the wall rectangle, the s texture coordinate is different from the x position by only a positive scale factor. The same is true of the t texture coordinate and the y position.

Under these considerably constrained circumstances, you can directly replace the surface normal with the normal sampled from the normal map. This is exactly what the prior examples do, and the bump mapping looks fine.

However, what happens when you bump map arbitrary geometry with the C8E1v_bumpWall and C8E2f_bumpSurf programs? What if the surface normal for the geometry is not uniformly (0, 0, 1)? What if the s and t texture coordinates used to access the normal map are not linearly related to the geometry’s x and y object-space positions?

In these situations, your rendering results may resemble correct bump mapping, but a closer inspection will show that the lighting in the scene is not consistent with the actual light and eye positions. What happens is that the object-space light vector and half-angle vectors used in the per-fragment bump mapping math no longer share a consistent coordinate system with the normals in the normal map. The lighting results are therefore noticeably wrong.

Object-Space Bump Mapping

One solution is to make sure that the normals stored in your normal map are oriented so that they directly replace the object-space surface normals for the geometry rendered. This means that the normal map effectively stores object-space normals, an approach known as object-space bump mapping. This approach does work, which is why our earlier bump-mapped wall example (Figure 8-2) is correct, though only for the particular wall rectangle shown.

Unfortunately, object-space bump mapping ties your normal map textures to the specific geometry that you are bump mapping. Creating a normal map requires knowing the exact geometry to which you will apply the normal map. This means that you cannot use a single brick-pattern normal map texture to bump map all the different possible brick walls in a scene. Instead, you end up needing a different normal map for every different brick wall you render. If the object animates its object-space vertex positions, every different pose potentially requires its own object-space normal map. For these reasons, object-space bump mapping is very limiting.

Texture-Space Bump Mapping

Correct bump mapping requires that the light vector and half-angle vector share a consistent coordinate system with the normal vectors in the normal map. It does not matter what coordinate system you choose, as long as you choose it consistently for all vectors in the lighting equations. Object space is one consistent coordinate system, but it is not the only choice.

You do not need to make all the normals in the normal map match up with the object-space coordinate system of the object to be bump mapped. Instead, you can rotate the object-space light vectors and half-angle vectors into the normal map’s coordinate system. It is a lot less work to rotate two direction vectors into an alternative coordinate system than to adjust every normal in a normal map. The coordinate system used by the normal map texture is called texture space, so this approach is known as texture-space bump mapping (sometimes called tangent-space bump mapping).

Vertex programs are efficient at transforming vectors from one coordinate system to another. The vector transformations required for texture-space bump mapping are akin to transforming positions from object space to clip space with the modelview-projection matrix.

You can program your GPU to transform each object-space light and half-angle vector into the coordinate system that matches your normal map texture.

However, a modelview-projection matrix is fixed for a given rendered object. In contrast, the transformation required to rotate object-space light and half-angle vectors into the coordinate system that matches your normal map typically varies over the surface you render. Every vertex you render may require a distinct rotation matrix!

Although it may require a distinct rotation matrix for each vertex, texture-space bump mapping has one chief advantage. It allows you to apply a single normal map texture to multiple models—or to a single model being animated—while keeping the per-fragment mathematics required for bump mapping simple and efficient enough for GPUs that support only basic fragment profiles.


8.2 Bump Mapping a Brick Floor

Before we consider bump mapping polygonal meshes, consider an only slightly more complicated case. Instead of rendering a bump-mapped brick wall that has an object-space surface normal (0, 0, 1), consider rendering the same brick-textured rectangle repositioned so it becomes a brick floor rather than a wall. The surface normal for the floor is (0, 1, 0), straight up in the y direction.

In this floor example, apply the same brick normal map to the floor that you applied to the wall in the last example. However, «straight up» in the normal map is the (0, 0, 1) vector, while «straight up» for the floor in object space is (0, 1, 0). These two coordinate systems are not consistent.

What does it take to make these two coordinate systems consistent? The floor has the same normal at every point, so the following rotation matrix transforms the floor’s object-space «straight up» vector to the normal map’s corresponding «straight up» vector:

Sections 8.3 and 8.4 will explain the details of how to construct this 3×3 matrix for arbitrary textured rectangles and triangles. For now, the important thing is that such a matrix exists and provides a way to transform vectors from object space to the normal map’s texture space.

We can use this matrix to rotate the object-space light and half-angle vectors for the floor rectangle so they match the coordinate system of the normal map. For example, if L is the light vector in object space (written as a row vector), then L‘ in the normal map coordinate system can be computed as follows:

To perform specular texture-space bump mapping, you must also rotate the half-angle vector in object space into texture space the same way. Although this example’s rotation matrix is trivial, the same principle applies to an arbitrary rotation matrix.

About Rotation Matrices

You can always represent a 3D rotation as a 3×3 matrix. Each column and each row of a rotation matrix must be a unit-length vector. Moreover, each column vector is mutually orthogonal to the other two column vectors, and the same applies to the row vectors. The length of a vector transformed by a rotation matrix does not change after transformation. A 3D rotation matrix can act as the bridge between direction vectors in two 3D coordinate systems.

Илон Маск рекомендует:  Запуск программы

The columns of a rotation matrix used to transform object-space direction vectors into a normal map’s texture space are named tangent (T), binormal (B), and normal (N), respectively. So rotation matrix entries can be labeled as in Equation 8-1.

Given two columns (or rows) of a rotation matrix, the third column (or row) is the cross product of the two known columns (or rows). For the columns, this means that the relationship in Equation 8-2 exists.

8.2.1 The Vertex Program for Rendering a Brick Floor

You can enhance the C8E1v_bumpWall example so that it can bump map using texture-space bump mapping. To do this, pass the tangent and normal vectors of the rotation matrix needed to transform object-space vectors into texture-space vectors.

Example 8-5’s C8E5v_bumpAny vertex program, in conjunction with the earlier C8E2f_bumpSurf fragment program, can bump map the brick wall and brick floor with the same normal map texture. But to do this, you must supply the proper normal and tangent vectors of the rotation matrix that maps between object space and texture space. You must specify these two vectors for each vertex. The program computes the binormal with a cross product. Rather than requiring the binormal to be passed as yet another per-vertex parameter, the program computes the binormal in order to reduce the amount of dynamic data that the GPU must read for each vertex processed. Computing the binormal also avoids having to precompute and devote memory to storing binormals.

Example 8-5. The C8E5v_bumpAny Vertex Program

Texture-space bump mapping is also known as tangent-space bump mapping because a tangent vector to the surface, in conjunction with the surface normal, establishes the required rotation matrix.

Figure 8-4 compares two images of a simple scene with the same bump-mapped wall and floor arrangement, the same normal map texture, the same light position, and the same C8E2f_bumpSurf fragment program. But each image uses a different vertex program. The lighting in the left image is consistent and correct because it uses the C8E5v_bumpAny vertex program with the correct object-space-to-texture-space rotation for correct texture-space bump mapping. However, the lighting in the right image is inconsistent. The lighting on the wall is correct, but the lighting on the floor is wrong. The inconsistent lighting arises because the image on the right uses the C8E1v_bumpWall vertex program for both the wall and the floor.

Figure 8-4 Consistent Texture-Space Bump Mapping vs. Inconsistent Object-Space Bump Mapping

Conventionally, we write position vectors as column vectors and direction vectors as row vectors. Using Equation 8-2, C8E5v_bumpAny computes the binormal as a cross product of the per-vertex tangent and normal vectors:

The cross routine for computing the cross product of two vectors is part of Cg’s Standard Library.

The program then constructs a rotation matrix with the float3x3 matrix constructor:

The rows of the constructed rotation matrix are the tangent, binormal, and normal, so the constructed matrix is the transpose of the matrix shown in Equation 8-1. Multiplying a row vector by a matrix is the same as multiplying the transpose of a matrix by a column vector. The C8E5v_bumpAny example’s multiply is a matrix-by-vector multiply because the rotation matrix is really the transpose of the intended matrix, as shown:

Enhancing the C8E3v_specWall program in the same way requires also rotating the half-angle vector, as shown:

The scene in Figure 8-4 has only flat surfaces. This means that the rotation matrix required for the wall, and the other rotation matrix required for the floor, are uniform across each flat surface. The C8E5v_bumpAny program permits distinct tangent and normal vectors for every vertex. A curved surface or a mesh of polygons would require this support for varying the tangent and normal vectors that define the rotation from object space to texture space at every vertex. Figure 8-5 shows how a curved triangular shape requires distinct normal, tangent, and binormal vectors at each vertex. These vectors define distinct rotation matrices at each vertex that rotate the light vector properly into texture space. In the figure, the light vectors are shown in gray.

Figure 8-5 Per-Vertex Texture Space Bases

The next two sections explain how to generalize texture-space bump mapping to support curved surfaces and polygonal meshes.

8.3 Bump Mapping a Torus

Note

This section and the next are for readers who want a more mathematically based understanding of texture space. In particular, these sections explain the mathematics of how to construct the rotation matrices for transforming object-space vectors to and from texture space. These topics are not essential if you are content to rely on 3D authoring tools or other software to generate the rotation matrices for texture-space bump mapping. If you are not interested in this level of detail, you are encouraged to skip ahead to Section 8.5.

In this section, we describe how to bump map a tessellated torus, as depicted in Figure 8-6. Bump mapping a torus is more involved than bump mapping a brick wall because the surface of a torus curves. That curvature means that there isn’t a single, fixed rotation from object space to texture space for the entire torus.

Figure 8-6 A Tessellated Torus

8.3.1 The Mathematics of the Torus

For bump mapping, the torus provides a well-behaved surface with which to develop your mathematical intuition before you apply these ideas to the more general case of an arbitrary polygonal model in Section 8.4.

Bump mapping a torus is more straightforward than bump mapping an arbitrary polygonal model, because a single set of parametric mathematical equations, shown in Equation 8-3, defines the surface of a torus.

The parametric variables (s, t) [0, 1] map to 3D positions (x, y, z) on the torus, where M is the radius from the center of the hole to the center of the torus tube, and N is the radius of the tube. The torus lies in the z=0 plane and is centered at the origin.

By defining the surface of a torus with a set of parametric equations, you can determine the exact curvature of a torus analytically, using partial differential equations.

The analytic definition of the torus in Equation 8-3 lets you determine an oriented surface-local coordinate system, the texture space we seek for bump mapping, in terms of the parametric variables (s, t) used to define the torus. These same parametric variables also serve as texture coordinates to access a normal map texture for bump mapping.

In practical terms, this provides a way to convert an object-space light vector and an object-space view vector into a surface-local coordinate system that is consistently oriented for normals stored in a normal map texture. Once you have a set of normal, light, and view vectors in this consistent coordinate system, the lighting equations explained in Chapter 5 work correctly. As discussed in Section 8.1.5, the trick to bump mapping is finding a consistent coordinate system and properly transforming all vectors required for lighting into that space.

If we assume that a surface is reasonably tessellated, we need to compute only the light and view vectors required for lighting at each vertex, and then interpolate these vectors for computing the lighting equations for every rasterized fragment. This assumption holds well for a uniformly tessellated torus.

As with the flat brick wall, we seek a rotation, in the form of a 3×3 matrix, that we can use to convert object-space vectors to a surface-local coordinate system oriented according to the (s, t) parameterization of the torus. Because of the curvature of the torus, the 3×3 matrix must be different for every vertex of the torus.

Constructing the rotation matrices requires the partial derivatives of the parametric equations that define the torus. These are shown in Equation 8-4.

We call the three-component vector formed from the partial derivatives in terms of either s or t an inverse gradient because it resembles the per-component reciprocal of a conventional gradient. An inverse gradient indicates the instantaneous direction and magnitude of change in surface position in terms of a single parametric variable.

You can use these inverse gradients to define a surface-local coordinate system. Forming a 3D coordinate system takes two orthogonal vectors. One vector that is essential for any surface-local coordinate system is the surface normal. By definition, the surface normal points away from the surface. You can construct the surface normal at a point on a surface by computing the cross product of two noncoincident inverse gradients to the surface at that point.

For our torus example, the surface normal N is given by Equation 8-5.

By picking the inverse gradient in terms of s as your tangent vector, in conjunction with the surface normal, you fashion a surface-local coordinate system.

The rotation matrix required to transform object-space vectors into the texture-space coordinate system for any particular torus vertex is

where the notation indicates a normalized vector, and given the equations shown in Equation 8-6.

You form the rotation matrix entirely of normalized vectors. This means that you can ignore the constant scale factors 2 p and 2N p in Equation 8-4 for the inverse gradients in terms of s and t, respectively.

Furthermore, the normalized surface normal N of the torus based on Equation 8-5 simplifies to

8.3.2 The Bump-Mapped Torus Vertex Program

Example 8-6 shows the vertex program C8E6v_torus for rendering a bump-mapped torus. This program procedurally generates a torus from a 2D grid specified over (s, t) [0, 1], as shown in Figure 8-7. Besides generating the torus, the program constructs the correct per-vertex rotation matrix, as described in Section 8.3.1. The program also rotates the uniform object-space light vector and half-angle vector parameters into texture space for consistent texture-space bump mapping.

Figure 8-7 Procedural Generation of a Torus from a 2D Grid

Figure 8-8 (on page 224) shows two bump-mapped tori rendered with the C8E6v_torus vertex program and the C8E4f_specSurf fragment program. The example applies the brick normal map from Figure 8-1. The bricks bump outward consistently, and a specular highlight is visible in each case.

Figure 8-8 Two Bump-Mapped Brick Tori Rendered with and

Example 8-6. The C8E6v_torus Vertex Program

8.4 Bump Mapping Textured Polygonal Meshes

Now consider the more general case of a textured polygonal model, such as the kind used for characters and other objects in 3D computer games. In general, objects are not strictly flat like a brick wall, nor easily described with a convenient mathematical expression, like a torus. Instead, an artist models such objects as a mesh of textured triangles.

Our approach is to explain how to bump map a single triangle from a textured polygonal mesh and then generalize this method to the entire mesh.

8.4.1 Examining a Single Triangle


Figure 8-9 shows a wireframe model of an alien head, along with a height-field texture for constructing the head’s normal map. The figure shows the same triangle three times. The version of the triangle on the left lies in 2D on the height-field texture. The version of the triangle on the right is shown in 3D object space in relation to other triangles forming the head. The middle version of the triangle appears on the head as rendered with bump mapping.

Figure 8-9 The Same Triangle Exists in Object Space and Texture Space

Each vertex of this textured triangle has a 3D object-space position and a 2D texture coordinate set. Think of the combination of these five coordinates as a 5D vertex. You can then describe the triangle’s vertices v , v 1, and v 2 this way:

Because all these coordinates lie in the plane of the same triangle, it is possible to derive plane equations for x, y, and z in terms of s and t:

For each of these three equations, you can compute the values of the coefficients A, B, C, and D using the triangle’s vertex coordinates. For example, A , B , C , and D would be computed as shown in Equation 8-7.

Rewriting the plane equations allows you to express x, y, and z in terms of s and t:

These three equations provide a means to translate texture-space 2D positions on the triangle to their corresponding 3D positions in object space. These are simple linear functions of s and t that provide x, y, and z. The equations are similar to Equation 8-3 for the torus. As in the case of the torus, we are interested in the partial derivatives of the vector in terms of s and t:

This equation provides inverse gradients analogous to those in Equation 8-4, but these inverse gradients are much simpler. Indeed, every term is a constant. That makes sense because a single triangle is uniformly flat, having none of the curvature of a torus.

Normalized versions of these inverse gradients form a rotation matrix in the same manner as the torus. Use the normalized inverse gradient in terms of s as the tangent vector, and use the normalized inverse gradient in terms of t as the binormal vector. You can use the cross product of these two inverse gradients as the normal vector, but if the model supplies a per-vertex normal for the vertices of the triangle, it is best to use the model’s per-vertex normals instead. That’s because the per-vertex normals ensure that your bump map lighting of the model is consistent with non-bump-mapped per-vertex lighting.

Normalizing the cross product of the two inverse gradients would give each triangle a uniform normal. This would create a faceted appearance.

8.4.2 Caveats

The Orthogonality of Texture Space with Object Space

There is no guarantee that the inverse gradient in terms of s will be orthogonal to the inverse gradient in terms of t. This happened to be true for every point on the torus (another reason the torus was used in Section 8.3), but it is not generally true. In practice, the two inverse gradients tend to be nearly orthogonal, because otherwise the artist who designed the model would have had to paint the associated texture accounting for a skew. Artists naturally choose to apply textures to models by using nearly orthogonal inverse gradients.

Beware of Zero-Area Triangles in Texture Space

It is possible that two vertices of the triangle will share the same (s, t) position in texture space (or very nearly the same position), or the three texture-space positions may all be collinear. This creates a degenerate triangle with zero area in texture space (or nearly zero area). This triangle may still have area in object space; it may only be degenerate in texture space. Artists should avoid authoring such triangles if the texture coordinates are intended for bump mapping.

If zero-area triangles in texture space are present on a bump-mapped model, they have a single perturbed normal for the entire triangle, leading to inappropriate lighting.

Negative-Area Triangles in Texture Space

Artists often mirror regions of a texture when applying texture coordinates to a polygonal model. For example, a texture may contain just the right half of a face. The artist can then apply the face’s texture region to both the right and the left half of the face. The same half-face image applies to both sides of the face because faces are typically symmetric. This optimization more efficiently uses the available texture resolution.

Because decals have no sense of what direction they face, this technique works fine when applying a decal texture. However, normal maps encode direction vectors that flip when polygons mirror regions of the normal map in this manner.

This issue can be avoided either by requiring artists to forgo mirroring while applying texture coordinates to models, or by automatically identifying when a triangle is mirrored in texture space and appropriately flipping the per-vertex normal direction. The latter approach is preferable, because you can use software tools to flip (negate) the normal whenever a triangle has a negative area in texture space, and adjust the mesh appropriately (for example, using NVIDIA’s NVMeshMender software, which is freely available from NVIDIA’s Developer Web site, developer.nvidia.com ).

Nonuniform Stretching of Bump Map Texture Coordinates

Artists can sometimes stretch a texture when assigning the texture coordinates for a model in a nonuniform manner. Again, this is usually fine for decals, but stretching creates issues for bump mapping. Nonuniform scaling of textures means that the inverse gradient in terms of s may have a much larger magnitude than the inverse gradient in terms of t. Typically, you automatically generate normal maps from height fields without reference to a particular model. Implicitly, you are assuming that any stretching of the normal map, when it applies to the model, is uniform.

You can avoid this issue either by requiring artists to avoid nonuniform stretching, or by accounting for the stretching when converting the height-field texture to a normal map.

8.4.3 Generalizing to a Polygonal Mesh

You can apply the approach described in the previous section on a polygon-by-polygon basis to every polygon in your polygonal mesh. You compute the tangent, binormal, and normal for every triangle in the mesh.

Blending Bases at Shared Vertices

However, in a mesh, more than one triangle may share a given vertex in the mesh. Typically, each triangle that shares a particular vertex will have its own distinct tangent, binormal, and normal vectors. Consequently, the basis—formed by the tangent, binormal, and normal vectors—for each triangle sharing the particular vertex is likewise distinct.

However, if the tangents, binormals, and normals for different triangles at a shared vertex are similar enough (and they often are), you can blend these vectors together without creating noticeable artifacts. The alternative is to create a copy of each vertex for each triangle shared by the original vertex. Generally, blending the bases at such vertices is the best approach if the vectors are not too divergent. This approach also helps to avoid a faceted appearance when a model is not optimally tessellated.

Mirroring, as discussed previously, is a situation in which the vectors are necessarily divergent. If mirroring occurs, you need to assign each triangle a distinct vertex with the appropriate tangent, binormal, and normal for each differently mirrored triangle.

8.5 Combining Bump Mapping with Other Effects

8.5.1 Decal Maps

The same texture coordinates used for your bump map are typically shared with decal map textures. Indeed, the discussion in Section 8.4 presumes that the texture coordinates assigned for applying a decal texture are used to derive the tangent and binormal vectors for bump mapping.

Often, when a game engine doesn’t support bump mapping, artists are forced to enhance decal textures by painting lighting variations into the textures. When you bump map a surface, the bump mapping should supply these lighting variations. Artists constructing bump maps and decals must be careful to encode diffuse material variations alone, without lighting variations, in the decal map. Artists should encode geometrical surface variations in the height field from which the normal map is generated.

For example, an artist should paint a green solid shirt as solid green in the decal map. In contrast, the artist should paint the folds in the fabric of the shirt that account for the lighting variations in the height field, rather than in the decal. If artists are not careful about this, they can inadvertently create double lighting effects that make bump-mapped surfaces too dark.

8.5.2 Gloss Maps

It’s common for some regions of an object to be shiny (such as belt buckles and armor) and other regions to be duller (such as fabric and flesh). A gloss map texture is a type of control map that encodes how specularity varies over a model. As with the decal map and normal map, the gloss map can share the same texture coordinate parameterization with these other texture maps. The gloss map can often be stored in the alpha component of the decal map (or even the bump map), because RGBA textures are often nearly as efficient as RGB textures.

This fragment of Cg code shows how a decal texture can provide both the decal material and a gloss map:

8.5.3 Geometric Self-Shadowing

Geometric self-shadowing accounts for the fact that a surface should not reflect a light if the light source is below the plane of the surface. For diffuse illumination, this occurs when the dot product of the light vector and the normal vector is negative. In this case, the dot product’s result is clamped to zero. You can implement this in Cg as follows:

Chapter 5 explained how the specular term should also account for geometric self-shadowing by clamping the specular term to zero either when the dot product of the half-angle vector and the normal vector is negative or when the diffuse contribution is clamped to zero. You can implement this in Cg as follows:

When you bump map, there are actually two surface normals: the conventional interpolated normal and the perturbed surface normal supplied by the normal map. One way to think about these two normals is that the interpolated normal is a large-scale approximation of the surface orientation, and the perturbed normal is a small-scale approximation of the surface orientation.

If either normal faces away from the incoming light direction, then there should be no illumination from the light. When you’re lighting in texture space for bump mapping, the light vector’s z component indicates whether the light is above or below the horizon of the geometric (or large-scale) normal. If the z component is negative, the geometric orientation of the surface faces away from the light and there should be no illumination from the light on the surface. You can implement this in Cg as follows:

The ?: test enforces geometric self-shadowing for the large-scale surface orientation; the max function enforces geometric self-shadowing for the small-scale surface orientation. You can implement the geometric self-shadowing for specular bump mapping in Cg this way:

Whether or not you account for geometric self-shadowing in your bump mapping is a matter of personal preference and a performance trade-off. Accounting for geometric self-shadowing caused by the large-scale surface orientation means that a light source will not illuminate some fragments that might otherwise be illuminated. However, if you do not account for geometric self-shadowing caused by the large-scale surface orientation, then lights on bump-mapped surfaces (particularly surfaces with considerable variation of surface normals) do not provide a clear cue for the direction of the incoming light.

An abrupt cut-off might cause illumination on a bump-mapped surface to flicker unnaturally because of large-scale geometric self-shadowing when the scene is animating. To avoid this problem, use a function such as lerp or smoothstep to provide a more gradual transition.

8.6 Exercises

Try this yourself: Use an image-editing program to replace the normal map used in Example 8-6 with a cobblestone pattern. Hint: You can edit the file that contains the normal map. You should not need to modify any code.

Try this yourself: When generating a normal map from a height field, you can scale the difference vectors by some scalar factor s as shown:

What happens visually when you regenerate a normal map from its height field with an increased value for the s scale factor? What happens when you decrease the scale factor? What happens if you use one scale factor to scale Hg — Ha and another scale factor to scale Hg — Hr ?

Try this yourself: Implement bump mapping for multiple light sources by using a rendering pass per light contribution. Use «depth equal» depth testing and additive blending to combine contributions from multiple lights for a bump-mapped surface.

8.7 Further Reading

Jim Blinn invented bump mapping in 1978. Blinn’s «Simulation of Wrinkled Surfaces» (ACM Press) is a seminal SIGGRAPH computer graphics paper.

Mark Peercy, John Airey, and Brian Cabral published a SIGGRAPH paper in 1997 titled «Efficient Bump Mapping Hardware» (ACM Press), which explains tangent space and its application to hardware bump mapping.

In 2000, Mark Kilgard published «A Practical and Robust Bump-Mapping Technique for Today’s GPUs.» The white paper explains the mathematics of bump mapping and presents a technique appropriate for third-generation GPUs. You can find this white paper on NVIDIA’s Developer Web site ( developer.nvidia.com ).

Sim Dietrich published an article titled «Hardware Bump Mapping,» in Game Programming Gems (Charles River Media, 2000), that introduced the idea of using the texture coordinates of polygonal models for texture-space bump mapping.

12.4 Использование bump-карты при работе с материалами

Использование Bump-карты при работе с материалами

Bump Mapping (Бамп-мэппинг)

Рельефное текстурирование — метод в компьютерной графике для придания более реалистичного и насыщенного вида поверхности объектов.


Bump mapping — техника заключается в том, что отклонение каждого пикселя от нормали к поверхности просчитываемого объекта смотрится в карте высот и применяется перед обсчётом освещения (см. для примера затенение по Фонгу).

Кроме описанной выше техники, называемой «Bump mapping» существует ряд альтернатив, также позволяющих придавать рельефность поверхностям. «Bump mapping» более корректно переводится как «эффект бугристой поверхности», так как в англоязычной литературе присутствует техника с названием «Relief mapping», отличающаяся от вышеизложенной.

Для примера работы данной технологии создадим новый материал и применим к нашему чайнику.
Шаг 1. Выберите пустую ячейку материала.

Шаг 2. Установите любой цвет, например, зеленоватый. После этого в свитке Maps поставьте галочку напротив строки Bump mapping, установите значение 75. Затем в качестве карты установите изображение, показанное на рис. 1.
Рисунок 1. Текстура для применения в качестве карты бамп-мэппинга.
Свиток Maps будет выглядеть следующим образом (рис. 2):
Рисунок 2. Установленная карта бапм-меппинга.
Шаг 3. Визуализируйте сцену использую стандартные настройки визуализации (рис. 3).
Рисунок 3. Визуализированное изображение.
Как видно из визуализированного изображения, на объекте появились трехмерные выделения в соответствии с картой бамп-меппинга. Основной особенностью данного метода является то, что дополнительных полигонов, как и вообще каких-либо изменений в геометрии модели не произошло. Трехмерный текст на объекте получается в результате просчета карты нормалей.

Simple Bumpmapping

Rotating, Diffuse Lit Torus

In the last few weeks I have seen many forum posts from people who want to use bump mapping in their applications. Despite this, there are no «simple» tutorials on how to acheive the effect. This is my attempt at explaining how to implement this technique.

This is the first time I have written a tutorial. Please feel free to tell me what you think.

We will use some OpenGL extensions to acheive this, but they are not vendor specific and will work on any NVidia Geforce series card, any ATI Radeon series card, and some others. If you have not used OpenGL extensions before, I will explain what is necessary to set them up. However, I am aiming this tutorial mainly at people who understand multitexturing and also preferably cube maps.

I will use glut for window management to keep the tutorial as simple as possible.

This is what we will acheive. The left picture shows the bump mapped torus, the right hand picture shows the same torus with standard OpenGL vertex lighting. You can download the demo at the bottom of this page.

The OpenGL Lighting Equation

The red book defines the standard OpenGL lighting equation as:

Vertex Color = emission + globalAmbient + sum(attenuation * spotlight *
[lightAmbient + (max * diffuse) + (max ^ shininess)*specular])

emission is the material’s emissive color
globalAmbient is the ambient color*global ambient brightness
attenuation is a term causing lights to become dimmer with distance
spotlight is a term causing spotlight effects
lightAmbient is the light’s ambient color*brightness
diffuse is the light’s diffuse color * the material’s diffuse color
shininess is the specular exponent, which tells us how shiny a surface is
specular is the light’s specular color * the material’s specular color
L is the normalised(unit length) vector from the vertex we are lighting to the light
N is the unit length normal to our vertex
H is the normalised «half angle» vector, which points half way between L and the viewer (V)

Here is a diagram describing these vectors:

Our aim is to evaluate the lighting equation per pixel. Then, by altering the normal, we can achieve a bumpy effect. There is no way we are going to be able to evaluate all of the above, especially without using any fragment programs, register combiners etc. So, we will simplify the equation.

Илон Маск рекомендует:  CSS-трансформации. Центр трансформации

Simplifications:

No ambient light
No emitted light
Only one light source
No attenuation or spotlight effects
No specular

Thus our per pixel lighting equation is simply:

Coordinate Spaces

The next thing you will have to figure out in order to understand bump mapping is coordinate spaces. There are many coordinate spaces used in this tutorial. Here is a diagram of them, and the matrices converting one to another:

Here are the five coordinate spaces we will use. I presume you are familiar with the standard OpenGL modelview and projection matrices. From the picture above, you can see that the modelview matrix takes coordinates in object space and converts them to view space, and the projection matrix takes coordinates in view space and converts them to clip space. After clip space, your coordinates undergo «perspective divide» and the viewport transformation and are then in «window coordinates» ready to be drawn on the screen.

When you specify vertices, you do so in object space. Thus the modelview and projection matrices together convert from object space to clip space. If you wanted to convert back from view space to object space, you could transform your vertices by the inverse of the modelview matrix.

So, what’s this «Tangent space»? And what’s the «TBN matrix»?

OK. This is probably the hardest part to understand. Tangent space is a space local to the surface of our model. Lets consider a single quad:

Now you all know what a normal is, right? It is a vector (in this case normalised) pointing out of the quad. In this image, the normal at each vertex is pointing out of the screen.

Now, we need to find two tangents at the vertex. The «S Tangent» points in the direction of increase of the s texture coordinate. The «T Tangent» points in the direction of increase of the T texture coordinate.

The 2 tangents and the normal form a «basis» at the vertex. They define a coordinate space- «tangent space». Think of the s tangent, t tangent and normal as the x, y and z axes of this space respectively. The TBN matrix is the matrix to convert from object space to tangent space, and it is composed like:

( Sx Sy Sz )
( Tx Ty Tz )
( Nx Ny Nz )

where Sx, Sy and Sz are the components of the S tangent, Tx, Ty, Tz are the components of the T tangent, and Nx, Ny, Nz are the components of the normal.

By multiplying a vector in object space by this matrix, you get the vector in tangent space.

By the way, the name TBN matrix comes from «Tangent, Binormal, Normal». The «Binormal» is another name for the t tangent. I believe the name «t tangent» makes more clear what this vector actually represtents.

Normal Maps

So, how do we change the normals on a per pixel basis? Much the same way as you would change the color of a model per pixel, by using a texture map. However, the normal map needs to store normal vectors, not colors.

Our normals look like:

Since they are unit length, (x*x)+(y*y)+(z*z)=1 . Thus x, y and z lie between -1 and 1. We can represent these in a texture map by letting the red, green and blue components of the texture equal x, y and z respectively. The color components which we will use to represent the vector must lie between 0 and 1. So, we put:

Take a minute to convince yourselves that every possible unit normal can be stored in a color this way.

The normals we enclose in the texture map will be in tangent space. In tangent space, the usual normal points in the z direction. Hence the RGB color for the straight up normal is (0.5, 0.5, 1.0). This is why normal maps are a blueish color.

Here is the normal map we will be using for our example.

Cube Maps

Recall that a 2d texture is a flat quad, a 2d array of colors. Imagine six square 2d textures, and put these together as the faces of a cube. This is a cube map. To access texture colors in a cube map, we use 3-component texture coordinates, ie glTexCoord3f rather than glTexCoord2f, or in our example, the corresponding vertex array structure.

Imagine centering the cube around the origin. Given a 3d texture coordinate, we can draw a line from the origin to our coordinate. The texture color at the point where this line(extended if necessary) meets the cube is the color used for texture mapping. Easy eh?

This means that the vector (2, 1, 2) and (8, 4, 8) and in fact (2k, k, 2k) for any k all return the same result.

Here is an example. If we pass the coordinates of the green X into glTexCoord3f, OpenGL will use the red texture color, which can be calculated by drawing the green line and seeing where it intersects the cube.

Normalisation Cube Map

In this demo we will use a normalisation cube map. This means that simply, instead of storing colors in our cube map we will store vectors, just as for the normal map. In the texel indexed by (ka, kb, kc), we will store the vector

Thus any vector passed into our cube map will cause the return of the normalised version of that vector. We will use this to ensure the light vector is normalized at every pixel.

As a reminder, here is our target:

We know our light’s position in world space. We will use the inverse model matrix (see diagram above) to convert this to object space. Then, we will calculate the vector from the vertex we are considering to the light. Then, we can use the TBN matrix to convert our light’s position into tangent space, and save this into our vertex structure. We will then draw our torus. The light vector will be normalised by the normalisation cube map. Since the normals in our normal map and the light vector are declared in tangent space, the dot product between them will make sense. Then, we simply multiply the result of this by the diffuse material color.

In order to achieve this, we will use the Architecture Review Board approved OpenGL extensions:

ARB_multitexture
ARB_texture_cube_map
ARB_texture_env_combine
ARB_texture_env_dot3

Understand all that? If not, don’t worry. Try reading it again, and try reading the notes in the code below. If all else fails and you still can’t get your head around it, contact me.

OK, on with the code.

Setting Up Extensions:

One of the greatest strengths of OpenGL is its ability to be extended. Extensions can be written by hardware vendors to take advantage of hardware features. The extensions we are using here are all «ARB» extensions, which means that they are approved by the OpenGL Architecture Review Board, the people who govern the OpenGL specification. These extensions are all «core» features of the latest version of OpenGL, version1.4. However Microsoft have decided not to ship libraries for any version of OpenGL beyond 1.1 for Windows. OpenGL 1.4 versions can be downloaded, but we will instead access the functions we require using extensions to OpenGL 1.1.

The tutorial comes with a .cpp file for each extension we are going to use. Here is the file for ARB_multitexture.

First, a boolean variable to hold whether or not the extension is supported:

This function tests whether ARB_multitexture is supported, and if so, initialises it. The names of all extensions supported by your particular implementation of OpenGL are stored in the extensions string. We parse this string trying to find «GL_ARB_multitexture».


Get the extensions string, and look through it for «GL_ARB_multitexture». If it is found, set «ARB_multitexture_supported» to true.

char * extensionString=(char *)glGetString(GL_EXTENSIONS);
char * extensionName=»GL_ARB_multitexture»;

char * endOfString; //store pointer to end of string
unsigned int distanceToSpace; //distance to next space

//loop through string
while(extensionString

If we did not find «GL_ARB_multitexture», output an error message and return false.

if(!ARB_multitexture_supported)
<
printf(«ARB_multitexture unsupported!\n»);
return false;
>

If we reached here, ARB_multitexture is supported. In order to use the ARB_multitexture functions, we need to create function pointers for each function. We then use wglGetProcAddress to initialise the function pointers:

PFNGLACTIVETEXTUREARBPROC glActiveTextureARB =NULL;

glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)
wglGetProcAddress(«glActiveTextureARB»);

The function pointers are declared globally, and are declared «extern» in header files. By including the correct header file, and calling «SetUpARB_multitexture» at the start of our program, we can now use the functions.

The other three extensions are set up in the same way, but they are simpler because no function pointers are needed.

Creating The Normalisation Cube Map

The function «GenerateNormalisationCubeMap» does exactly what it says on the tin.

First we create space to hold the data for a single face. Each face is 32×32, and we need to store the R, G and B components of the color at each point.

unsigned char * data=new unsigned char[32*32*3];
if(!data)
<
printf(«Unable to allocate memory for texture data for cube map\n»);
return false;
>

Declare some useful variables.

//some useful variables
int size=32;
float offset=0.5f;
float halfSize=16.0f;
VECTOR3D tempVector;
unsigned char * bytePtr;

We will do this next part once for each face. I will show how it is done for the positive x face. The other faces are very similar. Look in the source for details.

//positive x
bytePtr=data;

Loop through the pixels in the face.

Calculate the vector from the centre of the cube to this texel.

tempVector.SetX(halfSize);
tempVector.SetY(-(j+offset-halfSize));
tempVector.SetZ(-(i+offset-halfSize));

We normalize this vector, pack it to [0, 1] so it can be stored in a color, and save this into the texture data.

bytePtr[0]=(unsigned char)(tempVector.GetX()*255);
bytePtr[1]=(unsigned char)(tempVector.GetY()*255);
bytePtr[2]=(unsigned char)(tempVector.GetZ()*255);

Now we upload this face of the cube to OpenGL. We tell it that this is the positive x face of the current cube map, and where to find the data.

glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, 0, GL_RGBA8,
32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

After repeating this for each face of the cube, we are done. Don’t forget to delete the temporary data storage.

Creating the torus:

We will store our torus information in a class. First we have a simple class to store the data for one vertex. We store the position and the texture coordinates, followed by the tangents and normal. Finally, we store the tangent space light vector for each vertex. This is the vector from the vertex to the light, in tangent space.

class TORUS_VERTEX
<
public:
VECTOR3D position;
float s, t;
VECTOR3D sTangent, tTangent;
VECTOR3D normal;
VECTOR3D tangentSpaceLight;
>;

Now we have the main torus class. This simply stores a list of vertices and a list of indices, and the sizes of these lists. It also contains a function to fill these lists, InitTorus.

int numVertices;
int numIndices;

unsigned int * indices;
TORUS_VERTEX * vertices;
>;

We define our torus to have a precision of 48. This means that there are 48 vertices per ring when we construct it.

const int torusPrecision=48;

The constructor for the torus calls the Init function.

The torus destructor deletes the index and vertex lists to free up the memory.

TORUS()
<
if(indices)
delete [] indices;
indices=NULL;

if(vertices)
delete [] vertices;
vertices=NULL;
>

This is our torus initiation function.

We calculate the number of vertices and the nuber of indices. Then create space for the vertex and index lists.

vertices=new TORUS_VERTEX[numVertices];
if(!vertices)
<
printf(«Unable to allocate memory for torus vertices\n»);
return false;
>

indices=new unsigned int[numIndices];
if(!indices)
<
printf(«Unable to allocate memory for torus indices\n»);
return false;
>

Now we calculate the torus vertex positions, normals etc.

First we generate a ring of 48 vertices in the XY plane, off to the right of the origin:

The normals for these vertices are as shown. We also set the T texture coordinate to increse linearly with i, and the S texture coordinate to be 0. Thus, the T tangent points in the direction of increasing i. The S tangent points into the screen for a reason we will see in a minute.

//calculate the first ring — inner radius 4, outer radius 1.5
for(int i=0; i

Next, we rotate this entire ring around the Y axis in steps of 2*PI/48 rads. This will generate the other rings to form the torus out of. All normals, tangents etc get rotated, and the T texture coordinates remain the same as in the original ring. The S texture coordinates increase from one ring to the next. This is why the S tangent of the first ring points into the screen. This picture is an aerial view of the picture above.

//rotate the first ring to get the other rings
for(int ring=1; ring

Now we calculate the indices to construct triangles out of the vertices that we just placed.

//calculate the indices
for(ring=0; ring

OK, that’s the torus done!

Finally, on to the main file:

First we include the necessary header files.

«WIN32_LEAN_AND_MEAN» simply tells windows.h not to include lots of obscure stuff which we will not be using.
We then include «glut.h», which will itself include «gl.h» and «glu.h».
«glext.h» contains the tokens required for the extensions we will be using.
Then, we include the headers we made for the extensions. «IMAGE.h» is an image class for loading in our normal map and texture map. «Maths.h» contains the vector and matrix classes we will be using. «TORUS.h» contains the details of the torus we will draw, and «Normalisation cube map.h» contains. well, I’ll leave that to you to figure out.

#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include
#include «Extensions/ARB_multitexture_extension.h»
#include «Extensions/ARB_texture_cube_map_extension.h»
#include «Extensions/ARB_texture_env_combine_extension.h»
#include «Extensions/ARB_texture_env_dot3_extension.h»
#include «Image/IMAGE.h»
#include «Maths/Maths.h»
#include «TORUS.h»
#include «Normalisation Cube Map.h»

We will now declare some global objects, including booleans for whether or not to draw the bump map and color texture.

//Our torus
TORUS torus;

//Normal map
GLuint normalMap;


//Decal texture
GLuint decalTexture;

//Normalisation cube map
GLuint normalisationCubeMap;

//Light position in world space
VECTOR3D worldLightPosition=VECTOR3D(10.0f, 10.0f, 10.0f);

bool drawBumps=true;
bool drawColor=true;

Now, we create a function called «Init». This is called once at the beginning of our program, but AFTER our window has been created.

//Called for initiation
void Init(void)
<

First, we set up the extensions we will be using. If an extension setup returns false, the extension is not supported. So, we cannot run the demo. Output an error message and quit.

//Check for and set up extensions
if( !SetUpARB_multitexture() || !SetUpARB_texture_cube_map() ||
!SetUpARB_texture_env_combine() || !SetUpARB_texture_env_dot3())
<
printf(«Required Extension Unsupported\n»);
exit(0);
>

Now we set OpenGL states. This bit of code is probably familiar to anyone who has seen NeHe’s tutorials. We load the identity modelview matrix, set up our color and depth states, then enable backface culling.

//Load identity modelview
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

//Shading states
glShadeModel(GL_SMOOTH);
glClearColor(0.2f, 0.4f, 0.2f, 0.0f);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

//Depth states
glClearDepth(1.0f);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);

Now, we load in our textures. First we use the IMAGE class to load in the decal(color) map. Then, since our texture map is paletted (8 bits per pixel), we expand it to be 24bpp before sending to OpenGL. We use glTexImage2D to send the data, then set the texture’s parameters. We do exactly the same for the normal map.

//Load decal texture
IMAGE decalImage;
decalImage.Load(«decal.bmp»);
decalImage.ExpandPalette();

//Convert normal map to texture
glGenTextures(1, &decalTexture);
glBindTexture(GL_TEXTURE_2D, decalTexture);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, decalImage.width, decalImage.height,
0, decalImage.format, GL_UNSIGNED_BYTE, decalImage.data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

//Load normal map
IMAGE normalMapImage;
normalMapImage.Load(«Normal map.bmp»);
normalMapImage.ExpandPalette();

//Convert normal map to texture
glGenTextures(1, &normalMap);
glBindTexture(GL_TEXTURE_2D, normalMap);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, normalMapImage.width, normalMapImage.height,
0, normalMapImage.format, GL_UNSIGNED_BYTE, normalMapImage.data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

Now, we create our normalisation cube map. We create a texture just as before, but instead of using «GL_TEXTURE_2D», we use «GL_TEXTURE_CUBE_MAP_ARB». The «ARB» on the end signifies this token is part of an ARB extension. Instead of using the LoadTexture routine however, we generate the data for the cube map and send it to OpenGL in the «GenerateNormalisationCubeMap» function discussed above.

//Create normalisation cube map
glGenTextures(1, &normalisationCubeMap);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalisationCubeMap);
GenerateNormalisationCubeMap();
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
>

OK, that’s it for initiation. Now it’s time to draw something!

//Called to draw scene
void Display(void)
<

First, clear the color and depth buffers, and reset the modelview matrix to identity.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();

Now set up the modelview matrix.
First we apply the viewing transformation, the «view matrix» in the diagram above. This basically places our camera in the scene. We use gluLookAt to place the camera at (0.0, 10.0, 10.0), looking at the origin.
Next, we set up the model matrix. Remember the modelview matrix is simply the model matrix followed by the view matrix. However, OpenGL requires that we specify the transformations in the reverse order to that in which they occur. We want to rotate the torus around the Y axis. We have a static variable to keep our rotation, and we simply increase this value, and then send the rotation to OpenGL.

//use gluLookAt to look at torus
gluLookAt(0.0f,10.0f,10.0f,
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);

//rotate torus
static float angle=0.0f;
angle+=0.1f;
glRotatef(angle, 0.0f, 1.0f, 0.0f);

We now calculate the inverse model matrix. As you will know, this converts from world space to object space.
First we save the current modelview matrix. Then we reset it to identity. The inverse of a rotation by an angle a is the rotation around the same axis by -a. We send this rotation to OpenGL, and use GetFloatv to get the matrix. Then we restore the modelview matrix which we saved.

//Get the inverse model matrix
MATRIX4X4 inverseModelMatrix;
glPushMatrix();
glLoadIdentity();
glRotatef(-angle, 0.0f, 1.0f, 0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, inverseModelMatrix);
glPopMatrix();

Now, we use the matrix we just calculated to convert the light’s position into object space.

//Get the object space light vector
VECTOR3D objectLightPosition=inverseModelMatrix*worldLightPosition;

Now, we need to calculate the tangent space light vector for each vertex. We loop through all of the vertices, and fill in the vector.
Looking at the above diagram (again), we see that the TSB matrix takes points in object space and converts them to tangent space.
We know our light position in object space. We subtract from this the vertex position to get the vector from the vertex to the light. We can use the TSB matrix to convert this to tangent space. The TSB matrix has the form:

( Sx Sy Sz )
( Tx Ty Tz )
( Nx Ny Nz )

So, the first component of the tangent space light vector is given by (Sx, Sy, Sz) . (Lx, Ly, Lz) where L is the vector from the vertex to the light. Similarly, the other two components are given by simple dot products.

//Loop through vertices
for(int i=0; i

Now we can draw the torus. We draw the bump mapped torus in two passes. First we draw the bumps, then we multiply by the color texture. We can turn these passes on and off for different effects. First, draw the bump pass if necessary.

//Draw bump pass
if(drawBumps)
<

First, we bind the textures we will use. We bind the normal map to unit 0, and enable 2D texturing. We then bind the normalisation cube map to texture unit 1, and enable cube-map texturing. Then we reset the current texture unit to unit 0.

//Bind normal map to texture unit 0
glBindTexture(GL_TEXTURE_2D, normalMap);
glEnable(GL_TEXTURE_2D);

//Bind normalisation cube map to texture unit 1
glActiveTextureARB(GL_TEXTURE1_ARB);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalisationCubeMap);
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
glActiveTextureARB(GL_TEXTURE0_ARB);

Now we set up the vertex arrays we will use.
We set up the vertex pointer to point to the positions, and enable the vertex array.
The texture coordinate arrays are set up for each texture unit.
The texture coordinate set for unit 0 is simply the s and t texture coordinates. Thus, the normal map will be applied to the torus just like a standard texture map.
The texture coordinate set for unit 1 is simply the tangent space light vector. As we have set texture 1 to be the normalisation cube map, texture 1 will contain the normalised tangent space light vector for each pixel.

//Set vertex arrays for torus
glVertexPointer(3, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].position);
glEnableClientState(GL_VERTEX_ARRAY);

//Send texture coords for normal map to unit 0
glTexCoordPointer(2, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].s);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

//Send tangent space light vectors for normalisation to unit 1
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glTexCoordPointer(3, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].tangentSpaceLight);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0_ARB);

We have the normal in texture 0, and the normalised tangent space light vector in unit 1. Remember our aim is to evaluate:

We will multiply by diffuse in the second pass, so we need to evaluate tex0 dot tex1. So, we will use the ARB_texture_env_combine and ARB_texture_env_dot3 extensions to achieve this. The first texture unit will replace the fragment color with the texture color, i.e. the normal map.
The second texture unit will dot this with texture 1, i.e. the light vector. Since both are in tangent space, the dot product makes sense. Also, as this is output as a color, it will be clamped to [0,1]. So, we are set up to evaluate the first term of our equation. You can see what this term looks like on its own by pressing ‘2’ in the demo.

//Set up texture environment to do (tex0 dot tex1)*color
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGB_ARB);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB);

If you have been paying attention, you may be wondering about the fact that the vectors were packed into [0, 1] in the textures, and we did nothing about expanding them back to [-1, 1]. Don’t worry, the «DOT3_RGB_ARB» texture environment will do this for us automatically.

Now, we can draw the torus.

//Draw torus
glDrawElements(GL_TRIANGLES, torus.numIndices, GL_UNSIGNED_INT, torus.indices);

We have drawn what we need to with this setup, so we can disable the textures and the vertex arrays. We also reset the texture environment to the standard «modulate».

//Disable textures
glDisable(GL_TEXTURE_2D);

glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);
glActiveTextureARB(GL_TEXTURE0_ARB);

//disable vertex arrays
glDisableClientState(GL_VERTEX_ARRAY);

glClientActiveTextureARB(GL_TEXTURE1_ARB);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0_ARB);

//Return to standard modulate texenv
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
>

The next step is to draw the diffuse color, our textured torus. If we are drawing both the bump map pass and the textured pass, we combine them by multiplication. So, we enable multiplicative blending.

Now we can draw the textured pass.

//Perform a second pass to color the torus
if(drawColor)
<


If we are not drawing the bump mapped pass, we will enable a standard OpenGL light to enable us to compare the non-bumpmapped torus to the bumpmapped one. You can see the standard lit torus by pressing ‘3’ in the demo.

if(!drawBumps)
<
glLightfv(GL_LIGHT1, GL_POSITION, VECTOR4D(objectLightPosition));
glLightfv(GL_LIGHT1, GL_DIFFUSE, white);
glLightfv(GL_LIGHT1, GL_AMBIENT, black);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, black);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHTING);

glMaterialfv(GL_FRONT, GL_DIFFUSE, white);
>

We bind our decal (color) texture to texture unit 0, and enable 2d texturing.

//Bind decal texture
glBindTexture(GL_TEXTURE_2D, decalTexture);
glEnable(GL_TEXTURE_2D);

Now we enable the vertex array and texture coordinate array as above. This time, we also enable a normal array, for the standard OpenGL lighting.

//Set vertex arrays for torus
glVertexPointer(3, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].position);
glEnableClientState(GL_VERTEX_ARRAY);

glNormalPointer(GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].normal);
glEnableClientState(GL_NORMAL_ARRAY);

glTexCoordPointer(2, GL_FLOAT, sizeof(TORUS_VERTEX), &torus.vertices[0].s);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

Now we draw the torus and reset the OpenGL states (lighting, texturing etc).

//Draw torus
glDrawElements(GL_TRIANGLES, torus.numIndices, GL_UNSIGNED_INT, torus.indices);

//Disable texture
glDisable(GL_TEXTURE_2D);

//disable vertex arrays
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
>

glFinish tells OpenGL to complete any drawing.
glutSwapBuffers swaps the front and back color buffers.
glutPostRedisplay tells glut we want it to draw the next frame as soon as possible.

glFinish();
glutSwapBuffers();
glutPostRedisplay();
>

That’s all the drawing!

Now we need to create a few more functions for glut. These should be familiar to you if you have used glut before.

Reshape() is called when the window resizes. It resets the viewport to be the whole screen and also resets the projection matrix to the correct aspect ratio.

//Called on window resize
void Reshape(int w, int h)
<
//Set viewport
if(h==0)
h=1;

glViewport(0, 0, w, h);

//Set up projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective( 45.0f, (GLfloat)w/(GLfloat)h, 1.0f, 100.0f);

Keyboard() is called when a key is pressed.

//Called when a key is pressed
void Keyboard(unsigned char key, int x, int y)
<

If the user has pressed escape, we exit.

//If escape is pressed, exit
if(key==27)
exit(0);

If ‘1’ is pressed, we want to draw both the bump map pass and the color pass. So, set the two booleans to true.

//’1′ draws both passes
if(key==’1′)
<
drawBumps=true;
drawColor=true;
>

If ‘2’ is pressed, we only want to draw the bump pass, so set that to true, and the other pass to false.

//’2′ draws only bumps
if(key==’2′)
<
drawBumps=true;
drawColor=false;
>

If you press 3, we only want to draw the color pass. So, disable the bump pass.

//’3′ draws only color
if(key==’3′)
<
drawBumps=false;
drawColor=true;
>

If ‘W’ is pressed, we set the current drawing mode to wireframe.
If ‘F’ is pressed, we set the mode to filled polygons.

//’W’ draws in wireframe
if(key==’W’ || key==’w’)
glPolygonMode(GL_FRONT, GL_LINE);

//’F’ return to fill
if(key==’F’ || key==’f’)
glPolygonMode(GL_FRONT, GL_FILL);
>

Finally, our main function. This is a pretty standard glut affair. We Initiate glut, and create a window with a double buffer, RGB mode(as opposed to color index mode) anda depth buffer. Then, we call our Init function from above. We then tell glut where to find the display, reshape and keyboard functions. This is called «registering callbacks». Then, we tell glut to begin drawing. Glut will handle our program from here.

int main(int argc, char** argv)
<
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(640, 480);
glutCreateWindow(«Simple Bumpmapping»);

glutDisplayFunc(Display);
glutReshapeFunc(Reshape);
glutKeyboardFunc(Keyboard);
glutMainLoop();
return 0;
>

That’s it! I hope you understood most of that and look forward to seeing what you can do with it!

Основы программирования

Разделы сайта

Задачи

Документация

Сайты партнеры

Модуль запуска старлайн а93

Где купить активация старлайн а93 модуль запуска старлайн а93.

Архив

  • Январь, 2020 (1)
  • Июнь, 2015 (1)
  • Март, 2015 (1)
  • Май, 2014 (1)
  • Апрель, 2014 (1)
  • Январь, 2013 (1)
  • Декабрь, 2012 (1)
  • Май, 2012 (3)
  • Апрель, 2012 (3)
  • Март, 2012 (3)
  • Февраль, 2012 (1)
  • Декабрь, 2011 (1)

Рейтинг

Bump mapping

Опубликовано Мартиросян Давид в Ср, 06/17/2009 — 08:45

  • Задачи
  • Компьютерная графика
  • Pascal/Delphi

Реализовать эффекты глубины и наложения неровностей.

Для правильного 2d бампмэппинг надо посчитать нормаль для каждого пиксела в bumpmap (карте высот) и для вычисления цвета каждого пиксела использовать угол между источником света и данной нормалью.

Для данной задачи используется источник света, бесконечно близкий к освещаемой плоскости: координаты нормали nx и ny просто разность высот между соседними пикселaми в bumpmap по осям X и Y.

1. Заранее вычисляем интенсивность света (light map). (Световое пятно с максимальной интенсивностью в центре). Полагаем как обычно, что интенсивность зависит от n_z.
2. Подбирая из lightmap интенсивность по формуле: outvalue = lightmap[n_x+128][n_y+128] (если использовать нормали в диапазоне -128. +127) мы получим корректную картину освещения для бесконечно близкого источника.

n_x = bumpmap[x+1][y] — bumpmap[x-1][y]
n_y = bumpmap[x][y+1] — bumpmap[x][y-1]

Конечный цвет определяется так:
outvalue:=lightmap[(n_x-(lightx-currentx))][(n_y-(lighty-currenty))].

Automatic bump mapping

In the image, I know that setting Normal to a value (other than 0) in geometry will create a bump map from the texture onto the material. It works well and I’m doing this for the hair of a human model. What is the difference between positive and negative? Does one bump in one way and the other the exact opposite? (So if 1.0 bumps it outwards by a certain amount, -1.0 bumps it inwards by the same amount).

But my main question is the Bump Mapping Method and Space. What do these do exactly? I looked up on here and it doesn’t specify their purposes, only shows the available options. What is the difference between these options? I found that Original method (which will for some reason disable Space) looks the best to me but I want to make sure that option is the most appropriate for the situation I am in.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL