VolumetricLinesUnity icon indicating copy to clipboard operation
VolumetricLinesUnity copied to clipboard

Weird code for aspectRatio correction

Open lgarczyn opened this issue 2 years ago • 1 comments

The code contains multiple instances of this line


		float aspectRatio = unity_CameraProjection._m11 / unity_CameraProjection._m00;

                float2 lineDirProj_prev = normalize(
			csPos.xy * aspectRatio / csPos.w - // screen-space pos of current end
			csPos_prev.xy * aspectRatio / csPos_prev.w // screen-space position of the other "previous" end
		) * sign(csPos.w) * sign(csPos_prev.w) * scaledLineWidth;

However that aspect ratio multiplication does... nothing.

It is equivalent to:

float2 lineDirProj_prev = normalize((csPos.xy  / csPos.w - csPos_prev.xy / csPos_prev.w) * aspectRatio)
sign(csPos.w) * sign(csPos_prev.w) * scaledLineWidth;

which is equivalent to:

float2 lineDirProj_prev = normalize(csPos.xy  / csPos.w - csPos_prev.xy / csPos_prev.w)
sign(csPos.w) * sign(csPos_prev.w) * scaledLineWidth;

unless aspect ratio is 0, which it never is.

It can be simply corrected by defining aspectRatio as:

float2 aspectRatio = float2(
unity_CameraProjection._m11 / unity_CameraProjection._m00,
1);

Found this mistake while making an opaque version of the shader that doesn't suffer from high foreshortening, source in details

custom opaque line shader
#ifndef VOL_LINE_SINGLE_LINE_SHADER_URP_INC
#define VOL_LINE_SINGLE_LINE_SHADER_URP_INC
	
	// Property-variables declarations
	float _LineRadius;
	float4 _Color;

	// Vertex shader input attributes
	struct a2v
	{
		float4 vertex : POSITION;
		float3 otherPos : NORMAL; // object-space position of the other end
		half2 texcoord : TEXCOORD0;
		float2 offset : TEXCOORD1;
		// float4 color : COLOR;

		UNITY_VERTEX_INPUT_INSTANCE_ID
	};
	
	// Vertex out/fragment in data:
	struct v2f
	{
		float4 pos : SV_POSITION;
		half2 uv : TEXCOORD0;
		// float4 color : COLOR;

		UNITY_VERTEX_INPUT_INSTANCE_ID
			UNITY_VERTEX_OUTPUT_STEREO
	};
	
	// Vertex shader
	v2f vert (a2v v)
	{
		v2f o;

		// Setup
		UNITY_SETUP_INSTANCE_ID(v);
		ZERO_INITIALIZE(v2f, o);
		UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

		// Pass on texture coordinates to fragment shader as they are:
		o.uv = v.texcoord;
		// o.color = v.color;

		// Extract average scale from matrix
		float lineScale = dot(float3(
			length(unity_ObjectToWorld._m00_m10_m20),
			length(unity_ObjectToWorld._m01_m11_m21),
			length(unity_ObjectToWorld._m02_m12_m22)
		), float3(1.0/3, 1.0/3, 1.0/3));

		// Scale to properly match Unity's world space units:
		// The `projScale` factor also handles different field of view values, which 
		// used to be handled via FOV_SCALING_OFF in previous versions of this asset.
		// Furthermore, `projScale` handles orthographic projection matrices gracefully.
		float projScale = unity_CameraProjection._m11;
		float scaledLineRadius = _LineRadius * lineScale * projScale;

		// Get aspect ratio
		float2 aspectRatio = float2(
			unity_CameraProjection._m11 / unity_CameraProjection._m00,
			1);

		// Transform current end of line		to homogeneous clip space:
		float4 csPos = TransformObjectToHClip(v.vertex.xyz);

		// Offset for our current vertex:
		float2 offset;

		// If transforming vertex of the middle part
		if (v.offset.x == 0) {

			// Transform opposite end of line to homogeneous clip space:
			float4 csPos_other = TransformObjectToHClip(v.otherPos);
			
			// The positions of the circles in clip space, corrected for aspect ratio
			float2 circleCenter1 = csPos.xy * aspectRatio / csPos.w;
			float2 circleCenter2 = csPos_other.xy * aspectRatio / csPos_other.w;

			// The projection from one end to another
			float2 lineDirProj = normalize(
				circleCenter1 - // screen-space pos of current end
				circleCenter2 // screen-space position of the other end
			) * scaledLineRadius; // account for scale

			// Some tangent math
			// see https://www.math-only-math.com/important-properties-of-direct-common-tangents.html

			// Radiuses, not accounting for line width
			float circleRadius1 = 1/csPos.w;
			float circleRadius2 = 1/csPos_other.w;
			// Difference between radiuses
			float radiusDiff = (circleRadius1-circleRadius2) * scaledLineRadius;

			// Distance between points
			float circleDist = length(circleCenter1-circleCenter2);

			// Angle between the centerline and the perpendicular to the tangent
			// float angleOfTangent = acos(radiusDiff / circleDist);
			// float xOffset = -cos(angleOfTangent)
			// float yOffset = sin(angleOfTangent)
			
			// Optimized version
			float xOffset = -radiusDiff / circleDist;
			float yOffset = v.offset.y * sqrt(1 - xOffset * xOffset);

			// Project the offset to face the camera
			offset = xOffset * lineDirProj +
				yOffset * float2(lineDirProj.y, -lineDirProj.x);
		} else {
			offset = v.offset * scaledLineRadius;
		}
		
		// Apply (aspect-ratio corrected) offset
		csPos.xy += offset / aspectRatio;
		o.pos = csPos;

		return o;
	}	
	
	// Fragment shader
	float4 frag(v2f i) : SV_Target
	{
		UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

		float2 uvCentered = i.uv - 0.5;
		float uvSquareLength = dot(uvCentered, uvCentered);
		float alpha = uvSquareLength <= 0.25;

		return float4(1, 1, 1, alpha) * _Color;
	}
	
#endif

lgarczyn avatar Jan 20 '23 13:01 lgarczyn

Thank you so much for pointing this out. I'll take this into account in the next release.

johannesugb avatar Jul 03 '23 08:07 johannesugb