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

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

Я использую код, аналогичный, что написано 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.

картинки

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

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

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

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

Редактировать / Примечания

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

3 ответов


в техника

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

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

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

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

код

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

вершинный шейдер

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's 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's 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

вуаля, точечные спрайты рисуют сферы.

Примечания

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

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

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

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

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

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


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

эта техника называется 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;
}

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

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

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-конвейер и грубо будет работать с любой невырожденной перспективной проекцией.