Конференция "Игры" » Вопрос по теням (из Example DirectX8/StencilBuffer/ShadowVolume) [Delphi, DirectX 9]
 
  • @!!ex_ (09.06.07 08:07) [20]
    Я так и не въехал что за тень на камере??
    На скрин щоте только тенб на кубе, но исправляется соответственно просто... Либо отбрасыванием тени кубом, либо отрисовку куба после отрисовки SV.
  • Sapersky (09.06.07 13:22) [21]
    Скриншот, ИМХО, здесь вообще не в тему. Телепатор подсказывает, что имеется в виду эта проблема (из 2-й ссылки в [14]):

    I first implemented stencil shadow volumes over two years ago in the
    post-Q2 research period. They looked great until you flew the viewpoint
    into one of the volumes, and depending on the exact test you used, either
    most of the screen went into negative shadow, or most of the shadows
    disappeared.

    Разглагольствования Кармака - это, конечно, интересно, но по 1-й ссылке решение проблемы описано более коротко и внятно:

    First off, the algorithm only works if the viewpoint is outside of shadow, otherwise the stencil counting is inverted. This can be remedied by testing this special case, inverting the shadow test, and also initializing the stencil buffer to 2N−1 (where N is the stencil buffer precision), as the stencil buffer holds only unsigned values and decrementing from a zero value would again result in incorrect shadows. A more elegant solution is proposed in [Everitt and Kilgard 2002] , which was partially inspired by an insight of John Carmack, lead programmer of id software [Carmack 2000]. First off, instead of computing the stencil values by incrementing front facing shadow quads and decrementing back facing shadow quads on Z-buffer pass, the entire process is modified to count from infinity instead of from the viewpoint, the so called Z-fail version:

    procedure COMPUTEFRAGMENTSINSHADOW(Z-fail)
    for all shadow casting objects do
       compute potential silhouette edges (PSE) of the polygonal model
       compute the shadow volume polygons (shadow quads) from the light source(s) and the PSE
    end for

    for all front facing shadow quads from viewpoint do
       if Z-buffer test fails then
           decrement stencil buffer value
       end if
    end for

    for all back facing shadow quads from viewpoint do
       if Z-buffer test fails then
           increment stencil buffer value
       end if
    end for

    The two representations (Z-pass and Z-fail) are completely equivalent and compute the same value, yet they do not suffer from the problems mentioned above: the viewer being in shadow is no longer a special case.
  • ElectriC © (09.06.07 16:46) [22]
    Так я не понимаю, как реализовать это на дельфи?
  • ElectriC © (09.06.07 16:47) [23]

    > Я так и не въехал что за тень на камере??

    Поставь камеру именно (внутрь) в тень!!!
  • Sapersky (09.06.07 17:52) [24]
    В примере из SDK8 (CMyD3DApplication.RenderShadow) реализован метод Z-pass:

    for all front facing shadow quads from viewpoint do
       if Z-buffer test passes then
           increment stencil buffer value
       end if
    end for

    for all back facing shadow quads from viewpoint do
       if Z-buffer test passes then
           decrement stencil buffer value
       end if
    end for

    Нужно переделать его на Z-fail (см. ранее). Буквально несколько констант поменять. Может, сам сообразишь, какие? А то я всё равно проверить не смогу, нельзя в этом примере влезть внутрь тени.
  • ElectriC © (10.06.07 16:35) [25]

    > Sapersky

    Лана, попробую!
  • ElectriC © (10.06.07 17:25) [26]
    function CMyD3DApplication.RenderShadow: HResult;
    begin
     // Disable z-buffer writes (note: z-testing still occurs), and enable the
     // stencil-buffer
     m_pd3dDevice.SetRenderState(D3DRS_ZWRITEENABLE,  iFALSE);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILENABLE, iTRUE);

     // Dont bother with interpolating color
     m_pd3dDevice.SetRenderState(D3DRS_SHADEMODE,     D3DSHADE_FLAT);

     // Set up stencil compare fuction, reference value, and masks.
     // Stencil test passes if ((ref  and mask) cmpfn (stencil  and mask)) is true.
     // Note: since we set up the stencil-test to always pass, the STENCILFAIL
     // renderstate is really not needed.
     m_pd3dDevice.SetRenderState(D3DRS_STENCILFUNC,  D3DCMP_ALWAYS);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILFAIL,  D3DSTENCILOP_KEEP);

     // If ztest passes, inc/decrement stencil buffer value
     m_pd3dDevice.SetRenderState(D3DRS_STENCILREF,       $1);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILMASK,      $ffffffff);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILWRITEMASK, $ffffffff);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_INCR);

     // Make sure that no pixels get drawn to the frame buffer
     m_pd3dDevice.SetRenderState(D3DRS_ALPHABLENDENABLE, iTRUE);
     m_pd3dDevice.SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_ZERO);
     m_pd3dDevice.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

     // Draw front-side of shadow volume in stencil/z only
     m_pd3dDevice.SetTransform(D3DTS_WORLD, m_matObjectMatrix);
     m_pShadowVolume.Render(m_pd3dDevice);

     // Now reverse cull order so back sides of shadow volume are written.
     m_pd3dDevice.SetRenderState(D3DRS_CULLMODE,   D3DCULL_CW);

     // Decrement stencil buffer value
     m_pd3dDevice.SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_DECR);

     // Draw back-side of shadow volume in stencil/z only
     m_pd3dDevice.SetTransform(D3DTS_WORLD, m_matObjectMatrix);
     m_pShadowVolume.Render(m_pd3dDevice);

     // Restore render states
     m_pd3dDevice.SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
     m_pd3dDevice.SetRenderState(D3DRS_CULLMODE,  D3DCULL_CCW);
     m_pd3dDevice.SetRenderState(D3DRS_ZWRITEENABLE,     iTRUE);
     m_pd3dDevice.SetRenderState(D3DRS_STENCILENABLE,    iFALSE);
     m_pd3dDevice.SetRenderState(D3DRS_ALPHABLENDENABLE, iFALSE);

     Result := S_OK;
    end;

    Можешь подсказать, что, примерно, нужно изменять в этой функции?
  • Sapersky (11.06.07 10:34) [27]
    Похоже, что не всё так просто и методы эти не вполне эквивалентны.
    Если рисовать по z-fail, рисуются части теневых объёмов ЗА отбрасывающим тень объектом (что логично, z-fail фактически означает "рисовать невидимое"). При отрисовке тени они вылезают наверх, и появляются лишние тени на объекте. То ли метод Кармака рассчитан на то, чтобы рисовать объект после теневых объёмов (но тогда никакого самозатенения), то ли я чего-то недопонял.
    В общем, надо копать глубже, смотреть [Everitt and Kilgard 2002], или some good code and comprehensible presentations from http://developer.nvidia.com
  • ElectriC © (11.06.07 18:42) [28]
    Да, видимо, всё сложнее, чем думалось))
  • ElectriC © (11.06.07 19:14) [29]
    Кста:

    >Исправляем ситуацию, когда камера находится внутри объёма.
    >1. Отключим запись в буфер глубины и цвета
    >2. Включим отсечение передних граней. Установим операция в >инкрементирование на единицу при провале теста глубины. Нарисуем >объём.
    >3. Включим отсечение задних граней. Установим операцию трафарета в >уменьшение на единицу при провале теста глубины. Нарисуем объём.
    >Все освещённые точки будут иметь значение 0 в буфере трафарета.
    >Для нормальной работы метода может потребоваться отрисовка всех >задних граней модели, вытянутых в бесконечность, но в большинстве >случаев этого не требуется, так как эти грани в любом случае были бы >закрыты геометрией.

    Как реализовать этот метод - может поможет???
  • Sapersky (12.06.07 13:14) [30]
    Это тот же z-fail.
    Как выяснилось отсюда:
    http://developer.nvidia.com/attach/6831
    отличие его в том, что нужно "заткнуть" теневой объём с двух сторон полигонами модели. Спереди (или сверху, в общем, с той стороны, которая ближе к источнику света) - лицевыми по отношению к источнику света, сзади - соответственно, задними, перемещёнными к заднему срезу теневого объёма и отмасштабированными под этот срез. Ну вот эти самые "задние грани модели, вытянутые в бесконечность".

    Догадываюсь, какой будет следующий пост...
    "Так я не понимаю, как реализовать это на дельфи?"
  • ElectriC © (12.06.07 15:25) [31]
    Конечно ;))))))))))
  • ElectriC © (12.06.07 15:27) [32]
    Так как же??? ;)))
  • Sapersky (12.06.07 17:40) [33]
    А я вот, в свою очередь, не понимаю, что мешает попытаться понять самостоятельно.
    Ну, кроме лени, конечно. А мне, что ли, не лень писать за тебя?

    Векторную алгебру, насколько помню, в школе изучают. Даже если ещё не - тем лучше, сейчас выучишь, потом не надо будет.
    Фактически здесь нужно использовать:
    1) для определения, какой стороной полигон повёрнут к ист. света - скалярное произведение (dot product) даёт косинус угла между векторами. Синусы/косинусы в школе изучают однозначно...
    2) угол нужно искать между нормалью полигона и вектором от ист. света. Нормаль (перпендикуляр) получается векторным произведением (cross product) двух граней полигона.
    3) для получения заднего среза теневого объёма - масштабирование (scale), сложение/вычитание (add/subtract) векторов.

    Причём с нуля писать ничего не надо, всё это уже есть в TShadowVolume.BuildFromMesh (построение стандартного теневого объёма), но используется несколько по-другому. Т.е. нужно понять, что именно там происходит, и дописать на основе это кода "затычки" к теневому объёму.

    Что касается переделки CMyD3DApplication.RenderShadow на z-fail - там чуть ли не каждая строчка откомментирована, даже не зная английского можно уловить знакомые словосочетания "z-pass" и "z-fail" и сделать выводы. Щёлкнув с Ctrl на любую константу вроде D3DRS_STENCILPASS можно найти в Direct3D.pas список других подходящих для этой функции констант, часто с поясняющими комментариями.

    Могу даже сделать перевод (пока это всё ещё z-pass, я только выкинул кое-что лишнее):

    function CMyD3DApplication.RenderShadow: HResult;
    begin
    m_pd3dDevice.SetTransform(D3DTS_WORLD, m_matObjectMatrix);

    // Выключаем запись в z-буфер (но z-тест работает)
    m_pd3dDevice.SetRenderState(D3DRS_ZWRITEENABLE,  iFALSE);
    // включаем stencil-буфер
    m_pd3dDevice.SetRenderState(D3DRS_STENCILENABLE, iTRUE);

    // Выключить цветовую интерполяцию (для оптимизации, не обязательно)
    m_pd3dDevice.SetRenderState(D3DRS_SHADEMODE,     D3DSHADE_FLAT);

    // Выключаем вывод на экран теневых объёмов.
    // Чтобы они рисовались - закомментировать
    m_pd3dDevice.SetRenderState(D3DRS_ALPHABLENDENABLE, iTRUE);
    m_pd3dDevice.SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_ZERO);
    m_pd3dDevice.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);

    // для большей наглядности при рисовании теневых объёмов можно ещё включить wireframe-режим
    //  m_pd3dDevice.SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);

    // Устанавливаем stencil-функцию сравнения, в данном случае stencil-тест
    // всегда проходит (D3DCMP_ALWAYS). Другие константы в Direct3D8.pas.
    m_pd3dDevice.SetRenderState(D3DRS_STENCILFUNC,  D3DCMP_ALWAYS);

    // Если stencil и Z-тесты проходят, увеличиваем значения в буфере.
    // поскольку stencil-тест проходит всегда, фактически получается "если Z-тест проходит"
    m_pd3dDevice.SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);
    // Рисуем только передние грани
    m_pd3dDevice.SetRenderState(D3DRS_CULLMODE,   D3DCULL_CСW);
    // выводим объём
    m_pShadowVolume.Render(m_pd3dDevice);

    // Если stencil и Z-тесты проходят, уменьшаем значения в буфере
    m_pd3dDevice.SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_DECR);
    // Рисуем только задние грани
    m_pd3dDevice.SetRenderState(D3DRS_CULLMODE,   D3DCULL_CW);
    // выводим объём
    m_pShadowVolume.Render(m_pd3dDevice);

    // Восстанавливаем стейты
    m_pd3dDevice.SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
    m_pd3dDevice.SetRenderState(D3DRS_CULLMODE,  D3DCULL_CCW);
    m_pd3dDevice.SetRenderState(D3DRS_ZWRITEENABLE,     iTRUE);
    m_pd3dDevice.SetRenderState(D3DRS_STENCILENABLE,    iFALSE);
    m_pd3dDevice.SetRenderState(D3DRS_ALPHABLENDENABLE, iFALSE);
    m_pd3dDevice.SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);

    Result := S_OK;
    end;

    Будут КОНКРЕТНЫЕ вопросы по деталям - задавай.
  • ElectriC © (12.06.07 18:19) [34]
    За перевод спасибо (хотя и не надо было - перевёл давно уже сам).
    Мне бы разобраться, как не рисовать тень на камере.
  • ElectriC © (12.06.07 18:56) [35]
    Кстати, если не лень, можешь посмотреть, почему на тенях пояляются
    артефакты ввиде белых точек?
    P.S. Если не лень скачай моё первое творение ;))) -> http://slil.ru/24404040/874845188/School_54.rar
  • ElectriC © (13.06.07 15:04) [36]
    .
  • Sapersky (13.06.07 17:10) [37]
    почему на тенях пояляются  артефакты ввиде белых точек?

    Похоже, из-за кривых мешей, в которых имеются избыточные вершины.
    Удалить такие вершины - D3DXWeldVertices.

    Творение посмотрел... ну что сказать... неплохо для начала.
    С тенями на встроенной видео страшно тормозит - 2-3 fps, без - около 30.
    Подозреваю, что теневые объёмы пересчитываются на каждом кадре для всех объектов. Так вот, для статичных объектов (школы) можно сделать это один раз в начале, ещё хорошо бы загнать их в VB вместо рисования через DrawPrimitiveUP.

    "Отражения" в окнах можно сделать динамическими, см. пример SphereMap из SDK. В SDK8.1 его зачем-то переделали на шейдер, но только Delphi-вариант, С++ остался (видимо, Cooltie забыл обновить) на автоматической генерации текстурных координат, что ИМХО проще:

           // Generate spheremap texture coords, and shift them over
           D3DXMATRIX mat;
           mat._11 = 0.5f; mat._12 = 0.0f; mat._13 = 0.0f; mat._14 = 0.0f;
           mat._21 = 0.0f; mat._22 =-0.5f; mat._23 = 0.0f; mat._24 = 0.0f;
           mat._31 = 0.0f; mat._32 = 0.0f; mat._33 = 1.0f; mat._34 = 0.0f;
           mat._41 = 0.5f; mat._42 = 0.5f; mat._43 = 0.0f; mat._44 = 1.0f;
           m_pd3dDevice->SetTransform( D3DTS_TEXTURE0, &mat );
           m_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 );
           m_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACENORMAL );

    Само собой, окна тогда нужно рендерить отдельно от всего.
    Некоторые пояснения есть в readme к примеру.
  • ElectriC © (13.06.07 17:14) [38]

    > Подозреваю, что теневые объёмы пересчитываются на каждом
    > кадре для всех объектов.

    Кстати пересчитываются только один раз - при первом запуске программы!!!
  • ElectriC © (13.06.07 18:06) [39]
    Может объяснить, как работать с D3DXWeldVertices?
    P.S. Если можно, то на примере.
 
Конференция "Игры" » Вопрос по теням (из Example DirectX8/StencilBuffer/ShadowVolume) [Delphi, DirectX 9]
Есть новые Нет новых   [120293   +70][b:0][p:0.001]