问题
in prev question https://stackoverflow.com/questions/32085542/how-to-define-a-matrix-to-transform-2d-plane-into-perspective-360%C2%B0-fov i've asked for a matrix solution for 2D shadows, where depth to the nearest caster is found by all around light. Well, seems its impossible to make such a matrix as i expected. So i've found another method (still does'nt work as expected but very near), and this is the question. First let me explain topology and behaviour:
- I've defined a background rect with vertices in XY plane (0,0),(1,0),(1,1),(0,1)
- Light position is (0.5,0.5) and can be moved by mouse
- New rects can be added by mouse click in the same XY plane, as shadow casters and in the same time as shadow receivers. Here is the video http://www.youtube.com/watch?v=xn1jHdTpAHU
So, to calc depth buffer all around a circle from light position i do:
For each vertex of the poligon line, VS calculates angle from light position by atan2 function, and output position of the vertex to be -1 <= X <= 1, Y = 0.5 and 0 <= Z <= 1, so i just generate horizontal lines according to the arc of angle in the middle of height of a target texture (just for now)
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; }
then by PS, i calculate the depth buffer, depth = ((interpolated PosW) - light pos)
float MakeShadowPS(VertexShaderOutputMakeShadow input) : COLOR0 { float2 v = input.PosW - LightPos; return length(v); }
and finally, i render shadows by comparing distance between light and a pixel with depth buffer distance at the same angle, so if the distance is more than stored then it is in shadow:
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));
}
But there is strange thing - you can see it at the 0:19 in the video (yellow areas nearby sun in the left top coner), something like a fish-eye effect. And second (not sure how to fix it yet) - where line begins say 135 degrees to -135 it should rendered to -0.75PI to 0.75PI (left line of a rect) so it rewrites almost whole buffer directly (0:31), however i want it in 2 parts - -1..-0.75 and 0.75-1. Well, i've found a solution, but its weird. Can't find fine one :( For this video, i just don't render left side at all, so there such artifacts like not shadowed blue triangles as at the 0:30. Any ideas ?
Well, implemented weird solution - i just render vertex buffer twice with a different shader, so VS findouts if an angle between line points > PI then adjust X (zw are xy of the second point of the line):
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;
}
and second VS only differs:
if (abs(angle1 - angle2) > 3.1415926535)
{
if (angle1 > 0)
{
angle1 = angle1 - 2 * 3.1415926535;
}
}
else
{
angle1 = -100;
}
P.S. "angle1 = -100;" means here to disable rasterizing lines which were feeded by the first shader, so what is happenning youtu.be/BWmBnF1eTho
But first problem is still there. Debugging by VS Graphics Debugger shown the first problem - interpolation of xy from x1y1 to x2y2 by passing to the TEXTCOORD is not going as strigth line, and i'm not sure why :( Tried to interpolate angle between points and find depth as distance between point and light/sin(interpolated angle), and it worked for horizontal lines -
float MakeShadowPS(VertexShaderOutputMakeShadow input) : COLOR0
{
return (LightPos.y - input.PosW.y) / sin(input.PosW.z);
}
youtu.be/HgAiYRmSRSk same for vertical lines but cosine - abs(LightPos.x - input.PosW.x) / cos(input.PosW.z); But how can i merge these two methods ? Project located at https://yadi.sk/d/pgri0j_IjBamD it uses VS2013 and last MonoGame. If you are going to try it, please note QuadsBoard.cs lines 111 and 185 - there are rendered lines defined
回答1:
Finaly i've found a solution :) Here is the video https://youtu.be/TbMJs1zGY6g
So here is the VS to make shadow (with comments)
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;
}
And PS quite simple
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 to use shadows still the same
来源:https://stackoverflow.com/questions/32410189/360-fov-depth-buffer-by-topology-and-2d-shadows