通过拓扑和 2D 阴影的 360 度 FOV 深度缓冲区

360 FOV depth buffer by topology and 2D shadows

在上一个问题 中,我询问了 2D 阴影的矩阵解决方案,其中最近的施法者的深度由四周的光线发现。好吧,似乎不可能像我预期的那样制作这样的矩阵。所以我找到了另一种方法(仍然没有按预期工作但非常接近),这就是问题所在。首先让我解释一下拓扑结构和行为:

  1. 我定义了一个背景矩形,其顶点位于 XY 平面 (0,0),(1,0),(1,1),(0,1)
  2. 灯光位置为(0.5,0.5),可通过鼠标移动
  3. 可以通过在同一 XY 平面中单击鼠标添加新矩形,作为阴影投射器,同时作为阴影接收器。 这是视频 http://www.youtube.com/watch?v=xn1jHdTpAHU

所以,为了计算从光线位置围绕一圈的深度缓冲区,我这样做:

  1. 对于多边形线的每个顶点,VS通过atan2函数计算与光线位置的角度,输出顶点位置为-1 <= X <= 1, Y = 0.5 and 0 <= Z <= 1, 所以我只是根据角度的弧度生成水平线目标纹理的中间高度(暂时)

    struct VertexShaderInput
    {
        float4 Position : SV_POSITION;
        float4 Color : COLOR0;
    };
    struct VertexShaderOutputMakeShadow
    {
        float4 Position : SV_POSITION;
        float2 PosW : TEXCOORD0;
    };
    VertexShaderOutputMakeShadow MakeShadowVS(VertexShaderInput input)
    {
        VertexShaderOutputMakeShadow output;
        float2 v = input.Position.xy - LightPos;
        float angle = atan2(-v.y, v.x); // minus to flip y, because y+ goes down
        //output.Position = float4(angle, 0, length(v), 3.1415926535);// same as line bellow, but (-1) HLSL instruction because x is devided by w always in hardware
        output.Position = float4(angle / 3.1415926535, 0, length(v), 1.0);
        output.PosW = input.Position.xy;
        return output;
    }
    

    然后通过PS,我计算深度缓冲区,depth = ((interpolated PosW) - light pos)

    float MakeShadowPS(VertexShaderOutputMakeShadow input) : COLOR0
    {
        float2 v = input.PosW - LightPos;
        return length(v);
    }
    

最后,我通过在相同角度比较光与具有深度缓冲距离的像素之间的距离来渲染阴影,因此如果距离大于存储的距离,则它处于阴影中:

struct VertexShaderOutputUseShadow
{
    float4 Position : SV_POSITION;
    float2 PosW : TEXCOORD0;
    float4 Color : COLOR0;
};
VertexShaderOutputUseShadow UseShadowVS(VertexShaderInput input)
{
    VertexShaderOutputUseShadow output;
    float4 p = float4(input.Position.xy, 0, 1);
    output.Position = mul(p, World);
    output.Color = input.Color;
    output.PosW = input.Position.xy;
    return output;
}
float4 UseShadowPS(VertexShaderOutputUseShadow input) : COLOR0
{
    float2 v = input.PosW - LightPos;
    float angle = atan2(-v.y, v.x);
    float2 UV = float2((angle / 3.1415926535 + 1) / 2, 0.5);
    float shadowD = tex2D(shadowSampler, UV);
    float d = length(v);
    return input.Color * (1 - (d > shadowD ? 1 : d));
}

但是有一点很奇怪——你可以在视频中的 0:19 看到它(左上角靠近太阳的黄色区域),有点像鱼眼效果。 其次(还不确定如何修复它)- 线开始说 135 度到 -135 它应该渲染到 -0.75PI 到 0.75PI(矩形的左线)所以它直接重写几乎整个缓冲区(0:31) ,但是我想要它分为两部分 - -1..-0.75 和 0.75-1。好吧,我找到了解决方案,但它很奇怪。找不到好的 :( 对于这个视频,我根本不渲染左侧,所以在 0:30 处有像没有阴影的蓝色三角形这样的人工制品。 有什么想法吗?

好吧,实施了奇怪的解决方案 - 我只是用不同的着色器渲染顶点缓冲区两次,所以 VS 发现如果线点之间的角度 > PI 然后调整 X(zw 是线的第二个点的 xy):

VertexShaderOutputMakeShadow MakeShadowVS1(VertexShaderInput input)
{
    VertexShaderOutputMakeShadow output;
    float2 v1 = input.Position.xy - LightPos, v2 = input.Position.zw - LightPos;
    float angle1 = atan2(-v1.y, v1.x), angle2 = atan2(-v2.y, v2.x);
    if (abs(angle1 - angle2) > 3.1415926535)
    {
        if (angle1 < 0)
        {
            angle1 = 2 * 3.1415926535 + angle1;
        }
    }
    output.Position = float4(angle1 / 3.1415926535, 0, length(v1), 1.0);
    output.PosW = input.Position.xy;
    return output;
}

和第二个 VS 只不同:

if (abs(angle1 - angle2) > 3.1415926535)
{
    if (angle1 > 0)
    {
        angle1 = angle1 - 2 * 3.1415926535;
    }
}
else
{
    angle1 = -100;
}

P.S。 "angle1 = -100;" 在这里的意思是禁用由第一个着色器提供的光栅化线,所以 youtu 发生了什么。be/BWmBnF1eTho

但是第一个问题依然存在。 通过 VS Graphics Debugger 进行调试显示了第一个问题 - 通过传递给 TEXTCOORD 将 xy 从 x1y1 插值到 x2y2 并没有像直线那样进行,我不确定为什么 :( 尝试在点之间插入角度并找到深度作为点与 light/sin(内插角度)之间的距离,并且它适用于水平线 -

float MakeShadowPS(VertexShaderOutputMakeShadow input) : COLOR0
{
    return (LightPos.y - input.PosW.y) / sin(input.PosW.z);
}

优图.be/HgAiYRmSRSk 垂直线相同,但余弦 - abs(LightPos.x - input.PosW.x) / cos(input.PosW.z); 但是我怎样才能合并这两种方法呢? 位于 https://yadi.sk/d/pgri0j_IjBamD 的项目使用 VS2013 和最后一个 MonoGame。如果您要尝试,请注意 QuadsBoard.cs 第 111 和 185 行 - 已定义渲染线

我终于找到了解决办法 :) 这是视频 https://youtu.be/TbMJs1zGY6g

下面是制作阴影的VS(附注释)

VertexShaderOutputMakeShadow MakeShadowVS1(VertexShaderInput input)
{
    // lenght of the line (x1y1-x2y2)
    float d1 = distance(input.Position.xy, input.Position.zw);
    // lenght of perpendicular from LightPos to the line
    float d2 = abs(((LightPos.y - input.Position.y) * (input.Position.z - input.Position.x) - (LightPos.x - input.Position.x) * (input.Position.w - input.Position.y)) / d1);
    float2 v = input.Position.xy - input.Position.zw; // vector of the line
    float2 v1 = input.Position.xy - LightPos; // vector from light to x1y1
    float2 v2 = input.Position.zw - LightPos; // vector from light to x2y2

    float sa = v.x * v1.y - v1.x * v.y; // classify position of light and vector to x1y1 which we calculate to current vertex (left < 0 > right)

    float2 perpendicular = normalize(float2(-v.y, v.x)) * sign(sa); // sign is to flip perpendicular vector if light is not at right
    float perpendicularAngle = atan2(-perpendicular.y, perpendicular.x); // angle of perpendecular

    float angle1 = atan2(-v1.y, v1.x), angle2 = atan2(-v2.y, v2.x); // angles for both line points
    /*
        Here is the tricky part
        Since rasterizer will render left-half circle points as shortest line, like 3/4PI to (-3/4PI) it will overwrite whole depth buffer
        i render only part of it which and to oposite direction, so during the second pass the another part of it will be rendered with different condition,
        but all other lines will be clipped:
        if (abs(angle1 - angle2) > 3.1415926535)
        {
            if (angle1 > 0)
            {
            angle1 = angle1 - 2 * 3.1415926535;
            }
        }
        else
        {
            angle1 = -100;
        }
    */
    if (abs(angle1 - angle2) > 3.1415926535)
    {
        if (angle1 < 0)
        {
            angle1 = 2 * 3.1415926535 + angle1;
        }
    }
    float angleBetweenPerpendicularAndPoint = angle1 - perpendicularAngle; // angle to be interpolated
    VertexShaderOutputMakeShadow output;
    output.PosW = float4(angleBetweenPerpendicularAndPoint, d2, 0, 0); // feed to interpolator
    output.Position = float4(angle1 / 3.1415926535, 0, length(v1), 1.0); // generate X and Z of the transformed vertex, so its between -1 <= X <= 1, and 0 <= Z <= 1
    return output;
}

而且PS很简单

float MakeShadowPS(VertexShaderOutputMakeShadow input) : COLOR0
{
    // input.PosW.y is always the distance from light to the rendered line
    // but input.PosW.x interpolated from angle1 to angle2
    return input.PosW.y / cos(input.PosW.x);
}

VS/PS使用阴影还是一样