Многие источники света с тенями в файле three.js вызывают ошибку шейдера фрагмента

Предположим, что у вас есть сцена с улицей, на которой много уличных фонарей (больше 20), вы приближаете объект к ним и ожидаете тень.

Огни, просто

var light = new THREE.PointLight(0xffffff, 0.5, 6.0);

Только на улице есть .receiveShadow = true и только у машины есть .castShadow = true (кроме фар)

пример

В three.js добавление .castShadow = true ко всем источникам света вызывает следующую ошибку

THREE.WebGLProgram: shader error:  0 gl.VALIDATE_STATUS false 
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).

К счастью, в часовой сцене нам нужно всего несколько (максимум 4) из них, чтобы отбрасывать тень, так как большинство источников света в любом случае находятся вне досягаемости.

Я пытался использовать 2 подхода

  1. Перебор всех источников света и динамическая настройка .castShadow = true или .castShadow = false.

  2. Добавление и удаление источников света полностью, но установка их без тени или тени.

С обоими из них я получил ту же ошибку.

Какой другой подход сработает?

Обновлять

@neeh создал для него Fiddle здесь (чтобы вызвать изменение ошибки var numLightRows = 8; на большее число). Однако следите за ошибкой, будет другая ошибка со слишком большим количеством огней, которая не вызвана той же проблемой.

Он также указал, что мы видим здесь что pointShadowMap создается, даже если он не используется. Это объясняет, почему нет никаких изменений при «умном» подходе. Теперь это находится в коде GLSL.

Таким образом, мы ограничены графическим процессором, который в моем случае имеет 16 IMAGE_UNITS, но это не относится ко всем графическим процессорам (мой процессор на самом деле отлично работает с большим количеством). Вы можете проверить свою систему с помощью renderer.capabilities.maxTextures. Но, как уже упоминалось, нам действительно нужно только 4.

Проблема остается.


person arc    schedule 18.03.2017    source источник
comment
Я бы не использовал scene.remove( light ) и scene.add( light ), а обошел сцену для источников света, удалил их тени и включил их для тех, которые находятся близко к машине.   -  person gaitat    schedule 19.03.2017
comment
@gaitat Я попробовал оба, и, к сожалению, оба варианта не удались   -  person arc    schedule 19.03.2017


Ответы (1)


Проблема

Да a новый карта теней будет создана для каждого источника света, имеющего castShadow = true (На самом деле это не так, проверьте эта проблема). Карта теней — это рисунок, на котором рассчитывается тень, чтобы затем наложить ее на поверхность.

gl.getProgramInfoLog Количество образцов фрагментного шейдера превышает MAX_TEXTURE_IMAGE_UNITS (16).

Это означает, что ваше устройство может отправлять не более 16 текстур за один вызов отрисовки. Как правило, машина (улица?), на которую вы хотите наложить тени, требует 1 вызова отрисовки.

Чтобы нарисовать объект, который получает тени, все карты теней должны быть смешаны вместе с диффузной картой. Таким образом, для одного вызова отрисовки требуется использовать N+1 текстурных блоков. (N — количество источников света, которые могут отбрасывать тень.)

Если вы покопаетесь в шейдерах Three.js, вы найдете это :

#ifdef USE_SHADOWMAP

    #if NUM_DIR_LIGHTS > 0

        // Reserving NUM_DIR_LIGHTS texture units
        uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];
        varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];

    #endif

    ...

#endif

Воспользуйтесь этим инструментом, чтобы узнать, сколько текстурных блоков может обрабатывать ваш браузер (Фрагментный шейдер > Макс. количество единиц изображения текстуры).


Решение ?

Динамическое создание и удаление источников света плохо, потому что требует много памяти (распределение карты теней...).

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

  1. Отключить все тени: light.castShadow = false;
  2. Искать ближайшие огни
  3. Включить тень для N ближайших источников света: light.castShadow = true;

Улучшение

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

Следовательно, идея состоит в том, чтобы повторно использовать одни и те же карты теней для ближайших источников света. Вы можете работать с теневыми картами следующим образом:

// create a new shadow map
var shadowMapCamera = new THREE.PerspectiveCamera(90, 1, 0.5, 500);
var shadow = new THREE.LightShadow(shadowMapCamera);

// use the shadow map on a light
light.shadow = shadow;
shadow.camera.position.copy(light.position);
light.castShadow = true;

Вы можете получить максимальное количество текстурных блоков с помощью renderer.capabilities.maxTextures. Таким образом, вы можете вычислить количество карт теней для создания на их основе, но не забудьте оставить часть для более обычных карт, таких как диффузная карта, нормальная карта...

введите здесь описание изображения

Проверьте эту скрипту для полной реализации (используются только 4 карты теней).

person neeh    schedule 19.03.2017
comment
Спасибо, сэр, за ваш ответ, он, безусловно, дал мне некоторое представление. Незабудно это не решает проблему. Увеличение numLightRows снова вызывает ошибку. Это кажется странным, вы можете без проблем добавить столько источников света, сколько хотите, но простое добавление одного теневого источника приведет к сбою. - person arc; 19.03.2017
comment
Странно, у меня этой ошибки нет, даже при 100 огнях и maxTextures = 32. На самом деле я получил еще одну ошибку, но это потому, что в моей сцене слишком много света. Вы уверены, что это точно такая же ошибка? - person neeh; 19.03.2017
comment
Да, та же ошибка с количеством лампочек до 29, после получаю ту же ошибку, что и на ноутбуке: Varyings over maximum register limit, но я знаю, что это аппаратное ограничение, так что тут делать нечего. У меня есть EVGA 970, это проблема. - person arc; 19.03.2017
comment
Значит ошибка Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16) все равно начинается с 16 лампочек? Это означало бы, что где-то есть еще одна карта для каждого света. - person neeh; 19.03.2017
comment
Я думаю, что у меня есть еще одна идея, которая должна работать для любого количества источников света, и если мне это удастся, я напишу новый ответ. - person neeh; 19.03.2017
comment
Может быть. По сути, я могу добавить столько источников света, сколько захочу. Но как только появляется тень, он падает. Конечно, рад, что мне здесь помогли - person arc; 19.03.2017
comment
Да, только что понял, что... Не понимаю, почему. - person neeh; 19.03.2017
comment
О, я понял! Просто посмотрите на код, который я вставил, renderer.shadowMap.enabled = true; активирует USE_SHADOWMAP, а затем мы выделяем NUM_POINTS_LIGHTS по-разному! Таким образом, движок даже не заботится о том, сколько карт теней мы отправляем ... В любом случае, у меня есть новая идея, и я удаляю этот ответ. - person neeh; 19.03.2017