Dav*_*vid 16 math directx 3d shader hlsl
我正在编写一个着色器,通过绘制阴影圆圈在点精灵上渲染球体,并且需要编写深度分量和颜色,以便彼此相邻的球体正确相交.
我使用的代码类似于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
}
Run Code Online (Sandbox Code Playgroud)
该链接上的视频很好地了解了我所追求的效果:在点精灵上绘制的那些球体正确相交.我在下面添加了图片来说明.
我可以很好地计算点精灵本身的深度.但是,我不确定要计算一个像素的球体厚度,以便将其添加到精灵的深度,以给出最终的深度值.(上面的代码使用变量而不是计算它.)
我已经开了几个星期一直在研究这个问题,但还没想出来 - 我确信这很简单,但这是我的大脑没有扯过的东西.
Direct3D 9的点精灵大小以像素计算,我的精灵有几种尺寸 - 由于距离导致的衰减(我实现了相同的算法,用于我的顶点着色器中的点大小计算的旧固定功能管道)以及由于什么精灵代表.
如何从像素着色器中的数据(精灵位置,精灵深度,原始世界空间半径,屏幕上的像素半径,相关像素的标准化距离从精灵中心)到深度值? 简单地将精灵大小与球体厚度在深度坐标中的部分解决方案很好 - 可以通过距离中心的归一化距离来缩放以获得球体在像素处的厚度.
我使用Direct3D 9和HLSL与着色器模型3作为SM上限.
为了演示技术,以及我遇到麻烦的地方:
从两个点精灵开始,并在像素着色器中绘制一个圆圈,使用剪辑移除圆圈边界外的碎片:
一个将在另一个之上呈现,因为它们毕竟是平坦的表面.
现在,使着色器更高级,并绘制圆形,就好像它是一个球体,带有光照.请注意,即使平面精灵看起来像3D,它们仍然完全在另一个之前绘制,因为它是一种幻觉:它们仍然是平的.
(以上情况很简单;这是我遇到麻烦的最后一步,我正在问如何实现.)
现在,代替仅使用颜色值的像素着色器,它也应该写入深度:
void SpherePS (...any parameters...
out float4 oBackBuffer : COLOR0,
out float oDepth : DEPTH0 <- now also writing depth
)
{
Run Code Online (Sandbox Code Playgroud)
请注意,现在球体在它们之间的距离小于其半径时相交:
如何计算正确的深度值才能实现最后一步?
有几个人评论说,真实的球体会因为透视而扭曲,这可能在屏幕的边缘特别明显,所以我应该使用不同的技术.首先,感谢您指出这一点,它不一定显而易见,对未来的读者有益!其次,我的目标不是渲染透视正确的球体,而是快速渲染数百万个数据点,在视觉上我认为类似球体的物体看起来比扁平精灵更好,并且更好地显示空间位置.轻微失真或失真并不重要.如果您观看演示视频,您可以看到它是如何有用的可视化工具.我不想渲染实际的球体网格,因为与简单的硬件生成的点精灵相比,有大量的三角形.我确实想要使用点精灵的技术,我只是想扩展现有的演示技术,以便计算正确的深度值,在演示中将其作为变量传递而没有源的推导.
我昨天提出了一个解决方案,它可以很好地工作并产生在精灵上绘制的球体的所需结果,其正确的深度值与场景中的其他对象和球体相交.它的效率可能低于它需要的效果(例如,它计算并投影每个精灵的两个顶点)并且可能在数学上不完全正确(它需要快捷方式),但它会产生视觉上良好的结果.
为了写出"球体"的深度,您需要以深度坐标计算球体的半径 - 即,球体的一半厚度.然后,当您将球体上的每个像素写出距离球体中心的距离时,可以缩放此数量.
要计算深度坐标的半径:
顶点着色器:在未投影的场景坐标中,通过球体中心(即表示点精灵的顶点)投射来自眼睛的光线,并添加球体的半径.这会给你一个位于球体表面的点.投影精灵顶点和新球体曲面顶点,并计算每个顶点和深度(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);
...
}
Run Code Online (Sandbox Code Playgroud)
所有标准的东西,但我包括它,以显示它是如何使用的.
关键方法和问题的答案是:
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;
}
Run Code Online (Sandbox Code Playgroud)
像素着色器
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
}
Run Code Online (Sandbox Code Playgroud)
此代码省略了光照等.要计算像素离精灵中心的距离(要获取fCircleDist
),请参阅问题中的示例代码(计算' float dist = ...
')已经绘制了一个圆.
最终的结果是......
瞧,精灵画出球体.