Как вычислить глубину шейдера пикселя, чтобы сделать круг, нарисованный на точном спрайте, в виде сферы, которая будет пересекаться с другими объектами? - программирование
Подтвердить что ты не робот

Как вычислить глубину шейдера пикселя, чтобы сделать круг, нарисованный на точном спрайте, в виде сферы, которая будет пересекаться с другими объектами?

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

Я использую код, похожий на код, написанный Johna Holwerda:

void PS_ShowDepth(VS_OUTPUT input, out float4 color: COLOR0,out float depth : DEPTH)
{
   float dist = length (input.uv - float2 (0.5f, 0.5f)); //get the distance form the center of the point-sprite
   float alpha = saturate(sign (0.5f - dist));
   sphereDepth = cos (dist * 3.14159) * sphereThickness * particleSize; //calculate how thick the sphere should be; sphereThickness is a variable.

   depth = saturate (sphereDepth + input.color.w); //input.color.w represents the depth value of the pixel on the point-sprite
   color = float4 (depth.xxx ,alpha ); //or anything else you might need in future passes
}

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

Я могу точно рассчитать глубину точечного спрайта. Однако я не уверен, что рассчитает толщину сферы на пиксель, чтобы добавить ее к глубине спрайта, чтобы дать окончательное значение глубины. (Приведенный выше код использует переменную, а не вычисляет ее.)

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

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

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

Я использую Direct3D 9 и HLSL с шейдерной моделью 3 в качестве верхнего предела SM.

На снимках

Чтобы продемонстрировать технику и точку, в которой у меня возникают проблемы:

Начните с двух точечных спрайтов, а в пиксельном шейдере нарисуйте круг на каждом, используя clip, чтобы удалить фрагменты вне границы круга:

enter image description here

Один будет изображать над другим, так как в конце концов они являются плоскими поверхностями.

Теперь сделайте шейдер более продвинутым и нарисуйте круг, как если бы он был сферой, с освещением. Обратите внимание, что даже если плоские спрайты выглядят 3D, они все еще рисуют один полностью перед другим, поскольку это иллюзия: они все еще плоские.

enter image description here

(Это легко, это последний шаг, с которым у меня возникают проблемы, и я спрашиваю, как это сделать.)

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

void SpherePS (...any parameters...
    out float4 oBackBuffer : COLOR0,
    out float oDepth : DEPTH0 <- now also writing depth
   )
{

Заметим, что теперь сферы пересекаются, когда расстояние между ними меньше их радиусов:

enter image description here

Как вычислить правильное значение глубины для достижения этого последнего шага?

Изменить/Примечания

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

4b9b3361

Ответ 1

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

Техника

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

Чтобы вычислить радиус в координатах глубины:

  • Vertex shader: в непрозрачных координатах сцены отбрасывает луч от глаза через центр сферы (то есть вершину, представляющую точечный спрайт) и добавляет радиус сферы. Это дает вам точку, лежащую на поверхности сферы. Проектируйте как вершину спрайта, так и вашу новую вершину поверхности сферы, и вычислите глубину (z/w) для каждого. Другое значение глубины вам нужно.

  • Pixel Shader:, чтобы нарисовать круг, вы уже рассчитали нормализованное расстояние от центра спрайта, используя clip, чтобы не рисовать пиксели за пределами круга. Так как он нормирован (0-1), умножьте его на глубину сферы (это значение глубины радиуса, то есть пиксель в центре сферы) и добавьте к глубине самого плоского спрайта. Это дает глубину, наиболее большую в центре сферы, равную 0, и край, следующий за поверхностью сферы. (В зависимости от того, насколько точна она вам нужна, используйте косинус, чтобы получить изогнутую толщину. Я нашел, что линейные дали совершенно прекрасные результаты.)

код

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

Vertex shader

void SphereVS(float4 vPos // Input vertex,
   float fPointRadius, // Radius of circle / sphere in world coords
   out float fDXScale, // Result of DirectX algorithm to scale the sprite size
   out float fDepth, // Flat sprite depth
   out float4 oPos : POSITION0, // Projected sprite position
   out float fDiameter : PSIZE, // Sprite size in pixels (DX point sprites are sized in px)
   out float fSphereRadiusDepth : TEXCOORDn // Radius of the sphere in depth coords
{
    ...
   // Normal projection
   oPos = mul(vPos, g_mWorldViewProj);

   // DX depth (of the flat billboarded point sprite)
   fDepth = oPos.z / oPos.w;

   // Also scale the sprite size - DX specifies a point sprite size in pixels.
   // One (old) algorithm is in http://msdn.microsoft.com/en-us/library/windows/desktop/bb147281(v=vs.85).aspx
   fDXScale = ...;
   fDiameter = fDXScale * fPointRadius;

   // Finally, the key: what the depth coord to use for the thickness of the sphere?
   fSphereRadiusDepth = CalculateSphereDepth(vPos, fPointRadius, fDepth, fDXScale);

   ...
}

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

Ключевой метод и ответ на вопрос:

float CalculateSphereDepth(float4 vPos, float fPointRadius, float fSphereCenterDepth, float fDXScale) {
   // Calculate sphere depth.  Do this by calculating a point on the
   // far side of the sphere, ie cast a ray from the eye, through the
   // point sprite vertex (the sphere center) and extend it by the radius
   // of the sphere
   // The difference in depths between the sphere center and the sphere
   // edge is then used to write out sphere 'depth' on the sprite.
   float4 vRayDir = vPos - g_vecEyePos;
   float fLength = length(vRayDir);
   vRayDir = normalize(vRayDir);
   fLength = fLength + vPointRadius; // Distance from eye through sphere center to edge of sphere

   float4 oSphereEdgePos = g_vecEyePos + (fLength * vRayDir); // Point on the edge of the sphere
   oSphereEdgePos.w = 1.0;
   oSphereEdgePos = mul(oSphereEdgePos, g_mWorldViewProj); // Project it

   // DX depth calculation of the projected sphere-edge point
   const float fSphereEdgeDepth = oSphereEdgePos.z / oSphereEdgePos.w;
   float fSphereRadiusDepth = fSphereCenterDepth - fSphereEdgeDepth; // Difference between center and edge of sphere
   fSphereRadiusDepth *= fDXScale; // Account for sphere scaling

   return fSphereRadiusDepth;
}

Пиксельный шейдер

void SpherePS(
   ...
    float fSpriteDepth : TEXCOORD0,
    float fSphereRadiusDepth : TEXCOORD1,
    out float4 oFragment : COLOR0,
    out float fSphereDepth : DEPTH0
   )
{
   float fCircleDist = ...; // See example code in the question
   // 0-1 value from the center of the sprite, use clip to form the sprite into a circle
   clip(fCircleDist);    

   fSphereDepth = fSpriteDepth + (fCircleDist * fSphereRadiusDepth);

   // And calculate a pixel color
   oFragment = ...; // Add lighting etc here
}

Этот код пропускает освещение и т.д. Чтобы рассчитать, как далеко пиксель находится от центра спрайта (чтобы получить fCircleDist), посмотрите пример кода в вопросе (вычисляет "float dist = ..." ), который уже нарисовал круг.

Конечный результат:

Результат

Intersecting spheres using point sprites

Воила, точки рисования сфер.

Примечания

  • Алгоритм масштабирования для спрайтов может потребовать, чтобы глубина была также масштабируется. Я не уверен, что строка верна.
  • Это не полностью математически корректно (требуется сокращение) но, как вы видите, результат визуально правильный.
  • При использовании миллионов спрайтов я по-прежнему получаю хорошую скорость рендеринга (< 10ms на кадр для 3 миллионов спрайтов на эмуляторе DirectWD с эмуляцией VMWare Fusion).

Ответ 2

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

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

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

В вашем шейдере вы должны иметь место на экране как вход. Из этого, преобразования представления и вашей матрицы проекции вы можете добраться до линии в пространстве глаз. Вам нужно пересечь эту линию со сферой в пространстве глаза (raytracing), получить точку пересечения глазного пространства и преобразовать ее обратно в пространство экрана. Затем выведите 1/w в качестве глубины. Я не делаю математику для вас здесь, потому что я немного пьян и ленив, и я не думаю, что вы действительно хотите все равно. Это отличное упражнение в линейной алгебре, хотя, возможно, вам стоит попробовать.:)

Эффект, который вы, вероятно, пытаетесь сделать, называется Depth Sprites и обычно используется только с орфографической проекцией и глубиной спрайта, хранящегося в текстуре. Просто храните глубину вместе с цветом, например, в альфа-канале, и просто выведите eye.z + (storeddepth-0,5) * depthofsprite.

Ответ 3

Сфера не будет проектироваться в круг в общем случае. Вот решение.

Этот метод называется spherical billboards. Подробное описание можно найти в этой статье: Сферические рекламные щиты и их приложение для визуализации взрывов

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

float Opacity(float3 P, float3 Q, float r, float2 scr)
{
   float alpha = 0;
   float d = length(P.xy - Q.xy);
   if(d < r) {
      float w = sqrt(r*r - d*d);
      float F = P.z - w;
      float B = P.z + w;
      float Zs = tex2D(Depth, scr);
      float ds = min(Zs, B) - max(f, F);
      alpha = 1 - exp(-tau * (1-d/r) * ds);
   }
   return alpha;
}

Это предотвратит резкие перекрестки ваших рекламных щитов с геометрией сцены.

В случае, если конвейер с острием-спрайтами трудно контролировать (я могу сказать только об OpenGL, а не DirectX), лучше использовать биллинг с ускорением GPU: вы поставляете 4 равных трехмерных вершины, которые соответствуют центру частицы. Затем вы перемещаете их в соответствующие углы рекламного щита в вершинном шейдере, то есть:

if ( idx == 0 ) ParticlePos += (-X - Y);
if ( idx == 1 ) ParticlePos += (+X - Y);
if ( idx == 2 ) ParticlePos += (+X + Y);
if ( idx == 3 ) ParticlePos += (-X + Y);

Это больше ориентировано на современный конвейер GPU, а грубая работа будет работать с любой невырожденной перспективной проекцией.