如何计算像素着色器的深度上呈现点精灵作为一个球体,将与其他对象相交画出的圆?(How do I ca

2019-06-28 03:26发布

我写一个着色器上呈现点精灵,球被画阴影的圆圈,并需要写一个深度分量以及颜色,以便彼此靠近球体将正确相交。

我使用类似于通过编写的代码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和的Shader Model 3为上SM限制。

在图片

为了证明该技术,并且我有麻烦,在这点:

开始具有两个点精灵,并且在像素着色器上绘制每个圆,使用夹子 ,以除去圆的边界之外的片段:

一个将呈现高于其他,因为毕竟他们是平坦的表面。

现在,让着色器更先进,并绘制圆好像它是一个球体,带照明。 请注意,即使平精灵看3D,他们仍然有一个充分借鉴在其他的前面,因为它是一种错觉:他们依然平淡。

(以上是简单的;这是我遇到的麻烦和我询问如何实现的最后一步。)

现在,而不是像素着色器只写入颜色值,它应该写的深度,以及:

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

请注意,现在的球相交时,它们之间的距离比他们的半径更小:

我如何才能达到这最后一步计算正确的深度值?

编辑/注意事项

有几个人评论说,一个真正的球会扭曲由于角度来看,这可能是在屏幕的边缘,特别是可见的,所以我应该使用不同的技术。 首先,感谢指出了这一点,它不一定明显,有利于未来的读者! 其次,我的目的不是渲染透视正确的球,而是使百万个数据点的快,而且在视觉上我觉得一个球状物体看起来比平坦精灵漂亮,太展示的空间位置更好。 轻微的失真或缺乏失真的无所谓。 如果你观看演示视频 ,你可以看到它是一个有用的可视化工具。 我不想使实际的球网格,因为有大量的三角形相比,一个简单的硬件产生的点精灵。 我真的想用点精灵的技术,我只是想延长现存的演示技术 ,以计算正确的深度值,这在演示中传递与没有它的源代码是如何衍生的变量。

Answer 1:

我想出了一个解决办法昨天,这其中运作良好,并与生产上的精灵绘制球体的期望的结果,与使用场景中的其他对象和领域相交的正确的深度值。 它可能比它需要(它计算和项目每个精灵两个顶点,例如),并可能是不完全正确数学(它需要的快捷方式)效率较低,但它会产生视觉上的好成绩。

该技术

为了写出来的“球”的深度,你需要计算深度球体的半径坐标 - 即球体的一半有多厚。 当你从你是球体的中心有多远写出来球体上的每个像素这个量就可以进行缩放。

要计算半径深度坐标:

  • 顶点着色器:在未投影场景坐标通过球体中心从眼睛投下射线(即,代表点精灵的顶点),并添加球体的半径。 这给你趴在球体的表面上的点。 项目无论是精灵的顶点和新的球体表面的顶点,并为每个计算深度(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 = ...这已经画了一个圈”)。

最终的结果是...

结果

瞧,点精灵绘制球体。

笔记

  • 对于精灵的缩放算法可能需要进行缩放的深度了。 我不知道该行是正确的。
  • 这是不完全正确的数学(需要快捷方式),但正如你所看到的结果是正确的视觉
  • 当使用数以百万计的精灵,我仍然获得了良好的渲染速度(<每帧10ms的300万个精灵,在VMware Fusion的模拟Direct3D设备)


Answer 2:

第一个大错误是,一个真正的3D球体不会投影到下视角3D投影的圆。

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

其次,我会建议不要使用点精灵在开始的时候,它可能使事情比必要的困难,特别是考虑到第一点。 只画在你的球一个慷慨的边界四,并从那里走。

在你的着色器,你应该有屏幕空间中的位置作为输入。 从这一点,视图变换,和你的投影矩阵,你可以在眼空间中的线。 您需要相交眼空间(光线追踪)球体这一行,让眼睛空间的交叉点,并变换回屏幕空间。 然后输出1 / w的深度。 我不是做数学你这里,是因为我有点喝醉了,懒,我不认为这是你真的想反正做什么。 这是线性代数中一个伟大的运动,虽然,所以也许你应该尝试一下。 :)

你可能想要做的效应被称为深度精灵和通常只用正投影与存储在纹理精灵的深度使用。 就在深度存储与您的alpha通道的颜色,例如,只是输出eye.z +(storeddepth-.5)* depthofsprite一起。



Answer 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个等于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管线和粗的将与任何非简透视投影工作。



文章来源: How do I calculate pixel shader depth to render a circle drawn on a point sprite as a sphere that will intersect with other objects?