1
\$\begingroup\$

I have quite some struggle with a post processing shader that I'm working on.

I use Unity, and I have a shader in which I want to, in screen space as part of post processing, render a sphere that looks like it was drawn in world space.

Now, I've tried some different approaches.

For all attempts, I've given the shader a world position of the sphere,a screen position of the sphere and a camera position named:

uniform float4 _SpherePositionCamera;
uniform float4 _SpherePositionCamera;
uniform float4 _SpherePositionWorld;

That is, the same point in respectively world-space camera-space and screen-space.

I've tried to project the fragment in screen space into camera space using the inverse projection matrix, and tested in the xy coords minus the screen space center of the sphere are of length < 1. This gives a circle on the screen that does not really follow a world space shere very well.

the vertex program of the gc code is

v2f vert (appdata_img v){
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                o.uv = MultiplyUV (UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
                o.camPos = mul (inverseProjection, float4((o.uv * 2) - 1, _SpherePositionCamera.zw));
                return o; 
            }

And the fragment program is

        fixed4 frag (v2f i) : COLOR
        {
            // Get the original screen color
            fixed4 screenColor = tex2D(_MainTex, i.uv);

            if ( length(_SpherePositionCamera.xy - i.camPos.xy) < 1 )
            {
                screenColor = ( screenColor + fixed4(1,0,0,1) ) / 2;
            }

            return screenColor;
        }

I have been working on this for quite some time, so I'm sorry if I'm somewhat failing at explaining the problem, but I hope that someone has some pointer to what I'm missing.

Thanks.

EDIT

I figured a better formulation. I think it might be done by casting a ray from each fragment and testing for intersection with the sphere in world space.

So all I need now is to construct a ray that originates from the near clipping plane and is directed in the "projection" orientation.

could this easily be done?

\$\endgroup\$
  • \$\begingroup\$ I'm not at my computer right now, so I can't test it out, but your suggestion sounds very much on point! I would very much like those links, and I'll give your answer a closer look once I get to my computer! \$\endgroup\$ – user1291510 Feb 6 '16 at 23:09
  • \$\begingroup\$ I added the links at the bottom. My answer should help you get lit/not-lit working, but won't generate realistic lighting; you'll end up with more of a flashlight/highlight effect (which may be your desire). Catalin's tutorial series covers "G-Buffers", which I did not, and which are needed to generate realistic post-process lighting. If you are already using "G-Buffers", you are ready to implement NDL once you get the geometry volumes working. \$\endgroup\$ – Jon Feb 7 '16 at 0:19
0
\$\begingroup\$

The only point of trying to approximate this sphere is to filter which pixels to modify.

Try this from the reverse direction:

1) Render everything (no sphere) normally using FragmentShader1, transforming with ViewProjection:

return tex2D(_MainTex, i.uv);

2) Activate an Opaque BlendState so we overwrite whatever was just drawn.
3) Activate FragmentShader2:

return (tex2D(_MainTex, i.uv) + fixed4(1,0,0,1)) / 2;

2) Render a sphere (or bill-boarded circle) in its' actual 3D location using the same ViewProjection.
3) Fragments are generated to fill its' interior.
4) With culling enabled, fragments are generated for the front OR the back side, but not both.
5) So, for every pixel the sphere ends up occupying, FragmentShader2 will be invoked exactly once, and will overwrite the color previously stored there with the 'enhanced' color.

In this way, you are using actual geometry as a means to apply a specific fragment shader to a specific selection of pixels. Spheres work well for point lights, while cones are used for spot lights.

enter image description here

Note:
As proposed, every pixel the sphere occupies gets shaded twice; the first color produced gets overwritten. Bigger spheres means more wasted time. You can solve that problem by using the stencil buffer and drawing the sphere first. Once the sphere's pixels are drawn, the stencil buffer prevents the regular draw call(s) from overwriting it. As such, each shaded pixel requires only a single fragment shader invocation.

Edit:
Some of the tutorials are written with XNA, but should still make sense. The HLSL code should be virtually copy-and-pastable.

Zima has a limited selection of tutorials but specifically covers deferred lighting using geometric volumes:
http://www.catalinzima.com/xna/tutorials/deferred-rendering-in-xna/

Riemers.net is where I started. It is XNA-based and has many, many, tutorials. Series 3 covers lights and shadows.
http://www.riemers.net/

Rastertek.com has many tutorials for C++ DX11. The site is somewhat cluttered with unfinished series' (i.e. DX10, DX12) so I've linked directly to a few of the most relevant (DX11 C++):
http://www.rastertek.com/tutdx11.html #30 is point lights using shader math instead of volumes
http://www.rastertek.com/tutterr.html
http://www.rastertek.com/tutdx11s2ter.html

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.