Camera-facing UVs

This guy is a genius … he has lots of interesting case studies.

1 Like

I’m not used to node-based editors so can’t help you there, but I think what you need is to convert the object position from world space to screen space, and then use the XY values as an offset. I also wonder if the Z value could actually replace your distance-based calculation here.

Yeah but I’m not sure about the math needed to convert world space to screen space. UE3 doesn’t seem to have a node specifically for that.

Aw man, I figured it totally WAS an expanding sphere. That makes the width calculation harder though…

I thought ue3 did have a node called “transform” with world to screen."
ue3 is getting old in the tooth already!

UE3 has a transform node but it doesn’t have world space to screen space unfortunately. Would world to view be the same thing?

1 Like

That’s what I was thinking of i think. :slight_smile:

Hey guys! I was just trying to do something similar in Unity 5.6. Really love that wallpaper-like effect! :slight_smile:
I used @Arthur_Gould’s shader as a base but for some reason the texture still scrolls quite a bit when I move the object or the camera. Do you know if something changed in Unity recently? I couldn’t figure out what’s wrong, yet :confused:

Does it work if you use the code verbatim? I don’t have 5.6 right now so I can’t test it but I can’t imagine anything that would have changed to cause it to not work.

One thing to be careful with though is that this only works on meshes that are not batched. If you have two batched objects using this shader, then the texture will only follow one of them (because they’ll share their UNITY_MATRIX_MVP)

1 Like

Oh that might be my issue! I’ll check and send a gif once I’m at home. Thanks for your fast reply :smile:

Ha, my bad! Looks like I just made some mistake with my adaptions. Your example works just fine :slight_smile:

@Arthur_Gould Just checked again with a clean project and your original code in Unity 5.6 and it looks like there actually is a bit of a scaling issue now. Maybe something changed with the clip space but I couldn’t figure it out, yet :confused:

But it looks like it doesn’t scale a lot, only when reaching the far clipping plane. Gonna try to figure out what happens there :smiley:

1 Like

5.5 added an inverted depth buffer, so the objPos.z is going to be inverted.


screenUV *= 1 - objPos.z;
screenUV *= objPos.z;

thanks for the link. awesome~!

Ok so the problem was that when I cleaned up the shader I also forgot that you can’t just use the POSITION semantic from the vertex shader and expect to be able to use the clip position in the fragment shader. It mostly works in the “Scene” tab (which is what I tested before posting, also why it might appear to work sometimes and not others), but not in the “Game”. so I just changed the code to store the clip space in TEXCOORD0 and that appears to work for both (but the texture is inverted along the Y axis in the “Scene” tab, because Unity is silly like that).

Here’s the new code. Sorry about the formatting, still haven’t found the best way to copy code into this forum:

Shader “Custom/screenSpaceUV” {
Properties {
_MainTex (“Color Texture”, 2D) = “white” {}
_SSUVScale(“UV Scale”, Range(0,10)) = 1

	sampler2D _MainTex;
	float _SSUVScale;

	struct appdata {
		float4 vertex : POSITION;

	struct v2f {
		float4 pos : POSITION;
		float4 pos2: TEXCOORD0;

	float2 GetScreenUV(float2 clipPos, float UVscaleFactor)
		float4 SSobjectPosition = mul (UNITY_MATRIX_MVP, float4(0,0,0,1.0)) ;
		float2 screenUV = float2(clipPos.x,clipPos.y);
		float screenRatio = _ScreenParams.y/_ScreenParams.x;

		screenUV.x -= SSobjectPosition.x/(SSobjectPosition.w);
		screenUV.y -= SSobjectPosition.y/(SSobjectPosition.w); 

		screenUV.y *= screenRatio;

		screenUV *= 1/UVscaleFactor;
		screenUV *= SSobjectPosition.z;

		return screenUV;


SubShader {
  	Tags { "RenderType" = "Opaque" }

		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		v2f vert(appdata v) {				
			v2f o;
			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
			o.pos2 = o.pos;

			return o;

		half4 frag(v2f i) :COLOR 
			float2 screenUV = GetScreenUV(i.pos2.xy/ i.pos2.w, _SSUVScale);
			half4 screenTexture = tex2D (_MainTex, screenUV);

			return screenTexture; 

Fallback "Diffuse"


Let me know if that works better for you.

1 Like

Wow, thanks @Arthur_Gould for the explanation and the update! :smiley:
Interestingly it didn’t work for me at first but I realized when changing
screenUV *= SSobjectPosition.z;
screenUV *= SSobjectPosition.w;
in the GetScreenUV function it works perfectly. No idea though why the .w component works like that now. .z would make much more sense.
Now the only thing left I’d like to figure out is if it’s possible to have the texture scale when the object scales.
I’ll try to fiddle a bit and see how far I can get :slight_smile:
I’ll keep you up to date and many thanks for the help!

1 Like

Awesome, thanks Arthur for the great breakdown. Here is a node network version for those using amplify or another shader graph.

Hey everyone,

I am currently trying to sample a texture in screen space like you did. This works well :

float4 positionCS = vertexInput.positionCS / vertexInput.positionCS.w;
screenPos = ComputeScreenPos(positionCS).xy;
float aspect = _ScreenParams.x / _ScreenParams.y;
screenPos.x = screenPos.x * aspect;

But I would like to be able to constrain uv position and scale based on object’s position and distance from camera. I tried your examples but I also faced some issues and for the moment I don’t see how to fix them. Here’s the code :

float4 positionCS = vertexInput.positionCS / vertexInput.positionCS.w;
screenPos = ComputeScreenPos(positionCS).xy;
float aspect = _ScreenParams.x / _ScreenParams.y;
screenPos.x = screenPos.x * aspect;
float4 originCS = TransformObjectToHClip(float3(0.0, 0.0, 0.0));
originCS = originCS / originCS.w;
float2 originSPos = ComputeScreenPos(originCS).xy;
originSPos.x = originSPos.x * aspect;
screenPos = screenPos - originSPos;
// You can match object's distance like this
float3 cameraPosWS = GetCameraPositionWS();
float3 originPosWS = TransformObjectToWorld(float4(0.0, 0.0, 0.0, 1.0));
float d = distance(float4(0.0, 0.0, 0.0, 0.0), cameraPosWS - originPosWS);
screenPos *= d;

And here’s the issue I am facing. You can notice that when the object is near screen edges the texture starts to move. Is there a way to avoid that ?

I am using URP but this doesn’t really matter.


This is related to FOV and its associated distortion but I don’t see a way to get rid of that for the moment.