На предыдущем уроке мы обсуждали, что каждому объекту можно назначить свой собственный уникальный материал, который будет по-разному реагировать на свет. Это отличный способ придать любому объекту уникальный (в сравнении с другими объектами) внешний вид, но все же он не дает нам необходимой гибкости при визуальном отображении объекта.
Также на прошлом уроке мы определили единый материал для всего объекта. Однако в реальном мире объекты обычно состоят не из одного, а из нескольких материалов. Например, представим себе автомобиль: его внешний вид состоит из блестящей поверхности, у машины есть окна, которые частично отражают окружающую среду, его шины почти не блестят, поэтому у них нет зеркальных отблесков, и у него, в том числе, есть диски, которые блестят очень сильно (если вы, конечно, хорошо вымыли свою машину). Автомобиль также имеет рассеянные и фоновые цвета, которые для всего объекта не являются одинаковыми; автомобиль отображает множество различных рассеянных/фоновых цветов. В общем, такой объект обладает различными свойствами материалов для каждой из его отдельно взятых частей.
Таким образом, мы приходим к пониманию того, что использование системы материалов из предыдущего урока нам не подходит, т.к. данная система годится только для простейших моделей. А поэтому необходимо расширить её, введя карты рассеянного и отраженного цветов. Они позволят нам с гораздо большей точностью воздействовать на рассеянный (и, косвенно, на фоновый компонент, поскольку они все равно должны быть одинаковыми) и отраженный компоненты объекта.
Диффузные карты
Наша задача заключается в том, чтобы найти способ, который позволит задать рассеянный цвет для каждого отдельного фрагмента объекта. Быть может, мы уже сталкивались с какой-то системой, в которой можно получить значение цвета на основе положения фрагмента на объекте?
Вероятно, данная подсказка уже навела вас на определенные мысли. Ведь всё это очень напоминает текстуры и это действительно именно так: текстура — вот тот способ, который нам нужен. Ведь если присмотреться, то это просто другое название для того же самого основополагающего принципа: использовать изображение, наложенное на объект, с помощью которого мы можем задавать разные значения цвета для каждого фрагмента. В сценах с освещением такая текстура обычно носит название диффузной карты (или «карта рассеянного света», от англ. «diffuse map»), поскольку текстурное изображение описывает все цвета рассеянной составляющей цвета объекта.
Для демонстрации диффузной карты нам потребуется следующее изображение деревянного контейнера со стальной окантовкой по его периметру:
Использование диффузной карты в шейдерах в точности похоже на то, что мы уже проходили на уроке по работе с текстурами. Однако на этот раз мы сохраняем текстуру в виде переменной типа sampler2D
внутри структуры Material
, заменяя при этом ранее определенный вектор диффузного цвета vec3
на диффузную карту.
Примечание: Имейте в виду, что sampler2D
— это так называемый непрозрачный тип данных (англ. «opaque type»), экземпляры которого мы создать не можем, а можем только определить их как uniform-переменные. Если бы экземпляр структуры определялся не так, как uniform-переменная (например, в качестве параметра функции), то GLSL выбрасывал бы странные ошибки; то же самое относится и к любой другой структуре, содержащей такие непрозрачные типы данных.
Также мы уберем вектор фонового цвета материала, потому что теперь, когда мы управляем фоновым освещением, данный цвет в любом случае совпадает с диффузным цветом. Так что нет никакой необходимости хранить его отдельно:
1 2 3 4 5 6 7 |
struct Material { sampler2D diffuse; vec3 specular; float shininess; }; ... in vec2 TexCoords; |
Примечание: Если вы все же хотите задавать различные значения фоновых цветов (отличающиеся от значения диффузного цвета), вы можете оставить вектор фонового цвета vec3
, но тогда для всего объекта фоновые цвета все равно останутся одинаковыми. Чтобы получить различные фоновые значения цвета для каждого фрагмента, вам придется использовать для них отдельную текстуру.
Обратите внимание, что во фрагментном шейдере нам снова понадобятся текстурные координаты, поэтому мы объявили дополнительную входную переменную. Затем мы просто используем сэмплер, чтобы получить из текстуры значение диффузного цвета фрагмента:
1 |
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); |
Кроме того, не забудьте установить фоновый цвет материала равным значению его диффузной составляющей:
1 |
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); |
И это всё, что нужно, чтобы использовать диффузную карту. Как вы можете видеть, ничего принципиально нового здесь нет, но, благодаря этому, действительно обеспечивается резкое повышение визуального качества всей сцены. Чтобы теперь заставить всё это работать, нам нужно к вершинным данным добавить текстурные координаты, перенести их в качестве атрибутов вершин во фрагментный шейдер, загрузить текстуру и привязать её к соответствующему текстурному юниту.
Обновленные вершинные данные выглядят следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
float vertices[] = { // координаты // нормали // текстурные координаты -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; |
Теперь вершинные данные каждой вершины куба включают в себя координаты вершин, нормальные векторы и координаты текстуры. Давайте обновим вершинный шейдер, чтобы иметь возможность принять координаты текстуры в качестве вершинного атрибута и затем переслать их во фрагментный шейдер:
1 2 3 4 5 6 7 8 9 10 11 12 |
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; ... out vec2 TexCoords; void main() { ... TexCoords = aTexCoords; } |
Обязательно обновите указатели вершинных атрибутов обоих VAO, чтобы они соответствовали новым вершинным данным, и загрузите изображение текстуры ящика. Перед его рендерингом мы присваиваем материалу правильный текстурный юнит для uniform-сэмплера material.diffuse
и привязываем текстуру ящика к данному текстурному юниту:
1 2 3 4 |
lightingShader.setInt("material.diffuse", 0); ... glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap); |
Теперь, используя диффузную карту, мы получили большой прирост в детализации объекта — на этот раз ящик действительно начинает блестеть (в буквальном смысле этого слова). Результат:
GitHub / Урок №13. Карты освещения в OpenGL — Исходный код №1
Карты отраженного цвета
Вы, вероятно, заметили, что отраженное освещение ящика выглядит немного странно, так как объект представляет собой контейнер, состоящий в основном из дерева, а дерево не имеет таких ярко выраженных зеркальных бликов. Мы можем исправить эту ситуацию, установив значение отраженной составляющей цвета материала равным vec3(0.0)
, но это будет означать, что стальные края контейнера перестанут отбрасывать зеркальные блики, а сталь должна их отбрасывать. Было бы неплохо, если бы у нас была возможность контролировать то, какие части объекта могут отбрасывать отраженный цвет, а какие — нет, и также варьировать его интенсивность. Звучит знакомо, не так ли?
Для задания зеркальных бликов мы снова можем прибегнуть к использованию текстурной карты. Для этого необходимо создать черно-белую (или цветную, если вам так хочется) текстуру, которая определяет интенсивность отраженной составляющей цвета для каждой части объекта. Примером подобной карты отраженного цвета может являться следующее изображение:
Интенсивность отраженного цвета зависит от яркости каждого пикселя изображения. Каждый пиксель карты отраженного цвета может быть представлен в виде цветового вектора, где, например, черный цвет задается цветовым вектором vec3(0.0)
, а серый цвет — цветовым вектором vec3(0.5)
. Затем во фрагментном шейдере мы выбираем соответствующее значение цвета и умножаем это значение на интенсивность отраженной составляющей света. Чем сильнее цвет пикселя уходит в сторону белого цвета, тем больше значение результата умножения и тем ярче становится составляющая отраженного цвета объекта.
Поскольку контейнер в основном состоит из дерева, а дерево как материал не должно иметь зеркальных бликов, то вся деревянная секция диффузной текстуры была преобразована в черную область: черные области не имеют никакого зеркального блика. Стальная граница контейнера имеет интенсивность отражения, которая варьируется, причем сама сталь относительно восприимчива к зеркальным бликам, а трещины — нет.
Примечание: Технически древесина также имеет зеркальные блики, хотя и с гораздо более низким значением блеска (большая часть света рассеивается) и меньшим воздействием, но в иллюстративных целях мы опустим этот момент, предполагая, что древесина никак не реагирует на отраженную составляющую света.
С помощью таких инструментов, как Photoshop или GIMP, относительно легко можно преобразовать диффузную текстуру в текстуру карты отраженной составляющей цвета, вырезав некоторые части, преобразовав их в черно-белые и увеличив яркость/контрастность.
Сэмплирование карт отраженного цвета
Карта отраженного цвета ничем не отличается от любой другой текстуры, поэтому код её использования похож на код использования диффузной карты. Сначала убедимся, что мы правильно загрузили изображение и создали текстурный объект. Поскольку мы используем другой текстурный сэмплер в том же фрагментном шейдере, то мы должны использовать другой текстурный юнит для карты отраженной составляющей, поэтому давайте свяжем его с соответствующим текстурным юнитом перед рендерингом:
1 2 3 4 |
lightingShader.setInt("material.specular", 1); ... glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, specularMap); |
Затем обновим свойства материала во фрагментном шейдере, чтобы принять sampler2D
в качестве отраженного компонента вместо vec3
:
1 2 3 4 5 |
struct Material { sampler2D diffuse; sampler2D specular; float shininess; }; |
И, наконец, нам необходимо сэмплировать карту отраженной составляющей, чтобы получить соответствующую интенсивность отраженной составляющей цвета фрагмента:
1 2 3 4 |
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); FragColor = vec4(ambient + diffuse + specular, 1.0); |
Используя карту отраженной составляющей, мы можем с огромной детализацией определить, какие части объекта обладают блеском, и даже можем контролировать соответствующую интенсивность. Карты отраженной составляющей дают нам дополнительный слой контроля над освещением поверх диффузной карты.
Примечание: Если вы не хотите быть и делать «как все», то можете использовать фактические цвета в карте отраженного цвета, чтобы не только устанавливать зеркальную интенсивность каждого фрагмента, но и задавать цвет отраженного блика. Однако в реальности цвет отраженного блика в основном определяется самим источником света, поэтому он не создает реалистичных визуальных эффектов (вот почему изображения обычно черно-белые: нам важна только интенсивность).
Если бы мы сейчас запустили приложение, то увидели бы, что материал контейнера теперь очень похож на настоящий деревянный контейнер со стальными частями:
GitHub / Урок №13. Карты освещения в OpenGL — Исходный код №2
Используя диффузные карты и карты отраженного цвета, мы действительно можем добавить огромное количество деталей в относительно простые объекты. Мы даже можем добавить еще больше деталей в объекты с помощью других текстурных карт, таких как: карты нормалей/рельефа и/или отражения, но рассмотрение данных вещей мы оставим для последующих уроков.
Упражнения
Задание №1
Поэкспериментируйте с фоновым, диффузным и отраженным векторами источника света и посмотрите, как они влияют на визуальное представление контейнера.
Задание №2
Попробуйте во фрагментном шейдере инвертировать значения карты отраженной составляющей цвета так, чтобы дерево отбрасывало зеркальные блики, а стальная окантовка — нет (обратите внимание, что из-за трещин в стальной окантовке она все еще отображает некоторые зеркальные блики, хотя и с меньшей интенсивностью).
Ответ №2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#version 330 core out vec4 FragColor; struct Material { sampler2D diffuse; sampler2D specular; float shininess; }; struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; in vec3 FragPos; in vec3 Normal; in vec2 TexCoords; uniform vec3 viewPos; uniform Material material; uniform Light light; void main() { // Фоновое освещение vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); // Рассеянное освещение vec3 norm = normalize(Normal); vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); // Отраженное освещение vec3 viewDir = normalize(viewPos - FragPos); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); vec3 specular = light.specular * spec * (vec3(1.0) - vec3(texture(material.specular, TexCoords))); // здесь мы инвертируем сэмплированный отраженный цвет. Черный становится белым, а белый становится черным FragColor = vec4(ambient + diffuse + specular, 1.0); } |
Задание №3
Попробуйте из диффузной текстуры создать карту отраженного цвета, которая использует реальные цвета вместо черно-белого, и убедитесь, что результат выглядит не слишком реалистичным. Вы можете использовать следующую цветную карту отраженного цвета (если не хотите генерировать сами):
Результат должен быть примерно таким:
Задание №4
Также попробуйте добавить так называемую карту излучения (или «карту эмиссии», от англ. «emission map»), которая представляет собой текстуру, хранящую значения эмиссии для каждого фрагмента. Значение эмиссии — это цвета, которые объект может излучать, как если бы он сам был источником света; таким образом, объект может светиться независимо от условий освещения. Карты излучения — это то, что вы видите, когда объекты в игре светятся (например, глаза робота или световые полосы на контейнере). Добавьте на контейнер следующую текстуру в качестве карты эмиссии:
Результат должен быть примерно следующим:
Ответ №4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <stb_image.h> #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> #include <learnopengl/shader_m.h> #include <learnopengl/camera.h> #include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window); unsigned int loadTexture(const char *path); // Константы const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600; // Камера Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); float lastX = SCR_WIDTH / 2.0f; float lastY = SCR_HEIGHT / 2.0f; bool firstMouse = true; // Тайминг float deltaTime = 0.0f; float lastFrame = 0.0f; // Освещение glm::vec3 lightPos(1.2f, 1.0f, 2.0f); int main() { // glfw: инициализация и конфигурирование glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Раскомментируйте данную часть кода, если используете macOS /* #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); #endif */ // glfw: создание окна GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // Сообщаем GLFW захватывать движения нашей мышки glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // glad: загрузка всех указателей на OpenGL-функции if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // Конфигурация глобального состояния OpenGL glEnable(GL_DEPTH_TEST); // Компилирование нашей шейдерной программы Shader lightingShader("4.4.lighting_maps.vs", "4.4.lighting_maps.fs"); Shader lightCubeShader("4.4.light_cube.vs", "4.4.light_cube.fs"); // Указывание вершин (и буферов) и настройка вершинных атрибутов float vertices[] = { // координаты // нормали // текстурные координаты -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; // Сначала конфигурируем VAO (и VBO) куба unsigned int VBO, cubeVAO; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindVertexArray(cubeVAO); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); // Затем конфигурируем VAO света (VBO остается прежним; вершины такие же для объекта света, который также является 3D-кубом) unsigned int lightCubeVAO; glGenVertexArrays(1, &lightCubeVAO); glBindVertexArray(lightCubeVAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // Обратите внимание, что мы обновляем шаг позиционирования атрибута лампы, чтобы отразить обновленные данные буфера glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // Загружаем текстуры (теперь мы используем служебную функцию, чтобы сохранить код более организованным) unsigned int diffuseMap = loadTexture(FileSystem::getPath("resources/textures/container2.png").c_str()); unsigned int specularMap = loadTexture(FileSystem::getPath("resources/textures/container2_specular.png").c_str()); unsigned int emissionMap = loadTexture(FileSystem::getPath("resources/textures/matrix.jpg").c_str()); // Конфигурирование шейдера lightingShader.use(); lightingShader.setInt("material.diffuse", 0); lightingShader.setInt("material.specular", 1); lightingShader.setInt("material.emission", 2); // Цикл рендеринга while (!glfwWindowShouldClose(window)) { // Покадровая временная логика float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // Обработка ввода processInput(window); // Рендеринг glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Не забудьте активировать шейдер при настройке uniform-переменных/рисовании объектов lightingShader.use(); lightingShader.setVec3("light.position", lightPos); lightingShader.setVec3("viewPos", camera.Position); // Свойства света/освещения lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f); lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); // Свойства материала lightingShader.setFloat("material.shininess", 64.0f); // Трансформации вида/проекции glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); lightingShader.setMat4("projection", projection); lightingShader.setMat4("view", view); // Мировая трансформация glm::mat4 model = glm::mat4(1.0f); lightingShader.setMat4("model", model); // Привязываем диффузную карту glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, diffuseMap); // Привязываем карту отраженного света glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, specularMap); // Привязываем карту излучения/эмиссии glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, emissionMap); // Рендеринг куба glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // Также рисуем объект лампы lightCubeShader.use(); lightCubeShader.setMat4("projection", projection); lightCubeShader.setMat4("view", view); model = glm::mat4(1.0f); model = glm::translate(model, lightPos); model = glm::scale(model, glm::vec3(0.2f)); // меньший куб lightCubeShader.setMat4("model", model); glBindVertexArray(lightCubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); // glfw: обмен содержимым front- и back-буферов. Отслеживание событий ввода/вывода (была ли нажата/отпущена кнопка, перемещен курсор мыши и т.п.) glfwSwapBuffers(window); glfwPollEvents(); } // Опционально: освобождаем все ресурсы, как только они выполнили свое предназначение glDeleteVertexArrays(1, &cubeVAO); glDeleteVertexArrays(1, &lightCubeVAO); glDeleteBuffers(1, &VBO); // glfw: завершение, освобождение всех ранее задействованных GLFW-ресурсов glfwTerminate(); return 0; } // Обработка всех событий ввода: запрос GLFW о нажатии/отпускании кнопки мыши в данном кадре и соответствующая обработка данных событий void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); } // glfw: всякий раз, когда изменяются размеры окна (пользователем или операционной системой), вызывается данная callback-функция void framebuffer_size_callback(GLFWwindow* window, int width, int height) { // Убеждаемся, что окно просмотра соответствует новым размерам окна. // Обратите внимание, высота окна на Retina-дисплеях будет значительно больше, чем указано в программе glViewport(0, 0, width, height); } // glfw: всякий раз при перемещении курсора вызывается данная callback-функция void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; // выполняем реверс, поскольку y-координаты идут снизу вверх lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } // glfw: всякий раз при прокрутке колёсика мышки вызывается дання callback-функция void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); } // Служебная функция для загрузки 2D-текстуры из файла unsigned int loadTexture(char const * path) { unsigned int textureID; glGenTextures(1, &textureID); int width, height, nrComponents; unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); if (data) { GLenum format; if (nrComponents == 1) format = GL_RED; else if (nrComponents == 3) format = GL_RGB; else if (nrComponents == 4) format = GL_RGBA; glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); } else { std::cout << "Texture failed to load at path: " << path << std::endl; stbi_image_free(data); } return textureID; } |
Мне показалось простейшее решение — добавить по краям текстуры эмиссии черную рамку в 40 pix (на ширину металла).
для эмиссии можно просто другие текстурные координаты задать
Если у кого в 4-м задании получилось также как у меня: https://ibb.co/HY6bnvM
то вот небольшое дополнение, вроде работает
Хорошее решение, но вообще это плохая практика использовать долго вычислимые блоки кода в шейдерных программах, коими являются if else ветвления. Ведь из-за того что пикселей на экране — не меньше 2млн (1920х1080) , то делать столько раз обрыв операции и переход на другую — не эффективно. Советую лучше попробовать ограничить использование ветвей , и использовать встроенные (build in) функции в GLSL, которые справляются с этой работой не хуже, или пытаться продумать другое решение.
как это например.
спасибо! попробую.
Еще как вариант:
Чем больше будет кат, тем больше будет проявляться эмиссия на блестящей части.
Либо так)