首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何计算像素着色器深度,以将绘制在点精灵上的圆渲染为将与其他对象相交的球体?

如何计算像素着色器深度,以将绘制在点精灵上的圆渲染为将与其他对象相交的球体?
EN

Stack Overflow用户
提问于 2012-09-10 19:21:03
回答 3查看 5.1K关注 0票数 17

我正在写一个着色器,通过绘制阴影圆来渲染点精灵上的球体,并且需要编写一个深度组件以及颜色,以便彼此附近的球体将正确相交。

我使用的代码与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的点子画面大小是以像素为单位计算的,我的精灵有几种大小--既有由于距离引起的衰减(我在我的顶点着色器中实现了same algorithm the old fixed-function pipeline used for point size computations ),也有由于精灵所代表的东西。

如何从像素着色器中的数据(精灵位置,精灵深度,原始世界空间半径,以像素为单位的屏幕半径,相关像素到精灵中心的归一化距离)转换为深度值?简单的精灵大小到深度坐标中球体厚度的部分解决方案就可以了-可以通过距离中心的归一化距离进行缩放,得到像素的球体厚度。

我使用着色器模型3作为SM上限的Direct3D 9和HLSL.

在图片中

为了演示这项技术,以及我遇到问题的地方:

从两个点精灵开始,在像素着色器中,在每个精灵上绘制一个圆,使用clip移除圆边界外的碎片:

其中一个将渲染在另一个之上,因为它们毕竟是平面。

现在,使着色器更高级,并使用照明绘制圆形,就像它是一个球体一样。请注意,尽管扁平精灵看起来是3D的,但它们仍然是一个完全位于另一个前面的精灵,因为这是一种错觉:它们仍然是扁平的。

(上面的步骤很简单;这是我遇到麻烦的最后一步,我正在询问如何实现。)

现在,像素着色器不再只写入颜色值,它还应该写入深度:

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

请注意,现在,当球体之间的距离小于它们的半径时,球体相交:

如何计算正确的深度值以实现这最后一步?

编辑/备注

一些人评论说,一个真实的球体会因为透视而扭曲,这可能在屏幕的边缘特别明显,所以我应该使用不同的技术。首先,感谢你指出这一点,它不一定是显而易见的,而且对未来的读者是有好处的!其次,我的目标不是渲染一个透视正确的球体,而是快速渲染数百万个数据点,在视觉上,我认为球体状的对象看起来比扁平的精灵看起来更好,也更好地显示了空间位置。轻微的失真或没有失真都无关紧要。如果你使用watch the demo video,你会发现它是一个非常有用的可视化工具。我不想渲染实际的球体网格,因为与简单的硬件生成的点精灵相比,有大量的三角形。我真的想使用点精灵的技术,我只是想扩展extant demo technique,以便计算正确的深度值,在演示中,它是作为一个变量传入的,没有来源说明它是如何派生的。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-09-18 21:47:31

我昨天提出了一个解决方案,它工作良好,并产生所需的结果,在精灵上绘制的球体,与场景中的其他对象和球体相交的正确深度值。它的效率可能比它需要的要低(例如,它计算并投影每个精灵的两个顶点),并且在数学上可能不是完全正确的(它需要快捷方式),但它可以产生良好的视觉效果。

技术

为了写出“球体”的深度,你需要在深度坐标中计算球体的半径-即球体的一半有多厚。然后,当您写出球体上的每个像素时,可以根据您离球体中心的距离来缩放该数量。

要以深度坐标计算半径,请执行以下操作:

  • Vertex着色器:未投影场景坐标中的通过球体中心(即,表示点精灵的顶点)从眼睛投射光线,并添加球体的半径。这将为您提供球体曲面上的一个点。投影精灵顶点和新球体曲面顶点,并计算每个顶点的深度(z/w)。不同之处在于深度值你need.
  • Pixel着色器:要绘制一个圆,你已经计算了一个距离精灵中心的标准化距离,使用clip不绘制圆之外的像素。由于它是归一化的(0-1),所以乘以球体深度(这是半径的深度值,即球体中心的像素),并加上平面子画面本身的深度。这会将球体中心处的深度最厚设置为0,并使边跟随球体的曲面。(根据需要的精确度,使用余弦可以获得弯曲的厚度。我发现linear给出了非常漂亮的结果。)

代码

这不是完整的代码,因为我的效果是针对我的公司的,但这里的代码是从我的实际效果文件中重写的,省略了不必要的/专有的东西,并且应该足够完整,以演示该技术。

顶点着色器

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 = ...')中的示例代码,它已经绘制了一个圆。

最终结果是..。

结果

瞧,点精灵绘制球体。

备注

  • 精灵的缩放算法可能也需要缩放深度。我不确定这条线是否正确。Fusion
  • It在数学上并不完全正确(可以走捷径),但正如你所看到的,当使用数百万个精灵时,结果是视觉上正确的
  • ,我仍然获得了很好的渲染速度(在VMWare Fusion模拟的Direct3D设备上,对于300万个精灵,每帧<10ms)
票数 10
EN

Stack Overflow用户

发布于 2012-09-14 13:51:33

第一个大错误是,在透视3d投影下,真实的3d球体不会投影到圆。

这是非常不直观的,但看看一些图片,特别是大视场和偏离中心的球体。

其次,我建议不要在一开始就使用point sprites,这可能会使事情变得更加困难,特别是考虑到第一点。只需在球体周围绘制一个宽大的边界四边形,然后从那里开始。

在着色器中,应将屏幕空间位置作为输入。通过视图变换和投影矩阵,您可以得到眼睛空间中的一条线。您需要将这条线与眼睛空间中的球体相交(光线跟踪),获得眼睛空间的交点,然后将其转换回屏幕空间。然后输出1/w作为深度。我不是在这里为你计算,因为我有点醉了,我有点懒,我不认为这是你真正想要做的。这是一个很好的线性代数练习,所以也许你应该试一试。:)

您可能正在尝试执行的效果称为深度精灵,通常仅用于正交投影和存储在纹理中的精灵的深度。只需将深度与您的颜色一起存储,例如,在alpha通道中,然后输出eye.z+(storeddepth-.5)*雪碧图的深度。

票数 2
EN

Stack Overflow用户

发布于 2012-09-17 03:17:10

在一般情况下,球体不会投影成圆。以下是解决方案。

这种技术称为spherical billboards。可以在本文中找到更深入的描述:Spherical Billboards and their Application to Rendering Explosions

您可以将点精灵绘制为四边形,然后对深度纹理进行采样,以便找到每像素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个相等的3D顶点来匹配粒子的中心。然后将它们移动到顶点着色器中相应的广告牌角落中,即:

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流水线,粗略的将与任何非退化透视投影一起工作。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12350624

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档