I am struggling quite a lot to understand what is the correct way of converting from ScreenSpace of the type (Screen.width, Screen.heingth) to Object/World spaces and the other way around. I have tried many different approaches and none of them results correct.
To illustrate them, I have set up a geometry shader that builds a quad on the fly and is supposed to place these quads at the screen position of the vertices of the mesh to which the shader is assigned to. So, just get my example shader, assign it to a material, assign any texture to it and them that material to a Cube. What should happen is that the Cube should disappear and in its place, each vertex of the Cube should become a quad facing the camera at the position of each vertex:
Shader code
Shader "Custom/ScreenSpaceTest"
{
Properties
{
_SpriteTex("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Pass
{
Tags{ "RenderType" = "Geometry" }
LOD 100
Lighting Off
Cull off
ZWrite on
CGPROGRAM
#pragma enable_d3d11_debug_symbols
#pragma target 5.0
#pragma vertex VS_Main
#pragma fragment FS_Main
#pragma geometry GS_Main
#include "UnityCG.cginc"
// **************************************************************
// Data structures *
// **************************************************************
struct entershader {
float4 vertex : POSITION;
float3 tex0 : TEXCOORD0;
float4 color : COLOR;
};
struct GS_INPUT
{
float4 pos : POSITION;
float3 normal : NORMAL;
float2 tex0 : TEXCOORD0;
};
struct FS_INPUT
{
float4 pos : POSITION;
float2 tex0 : TEXCOORD0;
};
// **************************************************************
// Vars *
// **************************************************************
Texture2D _SpriteTex;
SamplerState sampler_SpriteTex;
// **************************************************************
// Shader Programs *
// **************************************************************
// Vertex Shader ------------------------------------------------
GS_INPUT VS_Main(entershader v)
{
GS_INPUT output = (GS_INPUT)0;
output.pos = v.vertex;
output.tex0 = float2(0, 0);
return output;
}
// Geometry Shader -----------------------------------------------------
[maxvertexcount(4)]
void GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
{
float halfSize = 20.0f; //half the size of the quad to be created below
float4 screenpos;
//####The lines below test different possibilities of getting the ScreenSpace position. All will fail:
screenpos = ComputeScreenPos(mul(UNITY_MATRIX_MVP, p[0].pos));
//screenpos = ComputeScreenPos(p[0].pos);
//screenpos = ComputeScreenPos(mul(_Object2World, p[0].pos));
//Now uncomment the line below to see how everything is working great from this point on
//screenpos = float4(_ScreenParams.x- halfSize, _ScreenParams.y- halfSize, 0, 1);
//Let's create a quad of size halfSize * 2:
float4 v[4];
v[0] = float4(screenpos.x - halfSize, screenpos.y - halfSize, 0, 1.0f);
v[1] = float4(screenpos.x - halfSize, screenpos.y + halfSize, 0, 1.0f);
v[2] = float4(screenpos.x + halfSize, screenpos.y - halfSize, 0, 1.0f);
v[3] = float4(screenpos.x + halfSize, screenpos.y + halfSize, 0, 1.0f);
//The code below transforms the quad vertices from screen space to final clip space. It seems to be working properly
float4 final[4];
final[0] = float4(
2.0 * v[0].x / _ScreenParams.x - 1.0,
_ProjectionParams.x * (2.0 * v[0].y / _ScreenParams.y - 1.0),
_ProjectionParams.y, // near plane is at -1.0 or at 0.0
1.0);
final[1] = float4(
2.0 * v[1].x / _ScreenParams.x - 1.0,
_ProjectionParams.x * (2.0 * v[1].y / _ScreenParams.y - 1.0),
_ProjectionParams.y, // near plane is at -1.0 or at 0.0
1.0);
final[2] = float4(
2.0 * v[2].x / _ScreenParams.x - 1.0,
_ProjectionParams.x * (2.0 * v[2].y / _ScreenParams.y - 1.0),
_ProjectionParams.y, // near plane is at -1.0 or at 0.0
1.0);
final[3] = float4(
2.0 * v[3].x / _ScreenParams.x - 1.0,
_ProjectionParams.x * (2.0 * v[3].y / _ScreenParams.y - 1.0),
_ProjectionParams.y, // near plane is at -1.0 or at 0.0
1.0);
//Now we just have to pass the newly generated quad to the triangle stream that goes out geometry shaders:
FS_INPUT pIn;
pIn.pos = final[0];
pIn.tex0 = float2(1.0f, 0.0f);
triStream.Append(pIn);
pIn.pos = final[1];
pIn.tex0 = float2(1.0f, 1.0f);
triStream.Append(pIn);
pIn.pos = final[2];
pIn.tex0 = float2(0.0f, 0.0f);
triStream.Append(pIn);
pIn.pos = final[3];
pIn.tex0 = float2(0.0f, 1.0f);
triStream.Append(pIn);
}
// Fragment Shader -----------------------------------------------
float4 FS_Main(FS_INPUT input) : COLOR
{
return _SpriteTex.Sample(sampler_SpriteTex, input.tex0);
}
ENDCG
}
}
}
It does not work, however. Lines 84, 85 and 86 bring three different attempts of calculating the ScreenPosition of the vertices. You can comment out each one, save and re-try to see what happens. Lastly, to prove that the error is in that part of the code (i.e on the screen space conversion), you can also un-comment line 90, which manually passes the up-right corner of the screen as the place to display the quads. This works properly.
Could anyone shed some light on how can I achieve the correct ScreenSpace position conversion in lines 84-86? Also, if a overly generous soul can also show me how to convert the other way around, I would be impossibly too happy.
Thanks!