I am using the Unity game engine with shader code (I'm a beginner).
The Aim
I am trying to create a ring of circles, similar to the picture below (I'm not concerned with the colours right now).
The Problem
Each circle seems to be squished towards the center, and I can't work out how to fix this (see below).
My Setup
This shader's material is applied to a panel within a canvas, and I'm using uv's (see below).
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
Problem Code
I think the issue lies in the way I'm computing the aspect ratio, angle or disance within the frag function (see below).
fixed4 frag(v2f i) : SV_Target
{
float aspectRatio = _ScreenParams.x / _ScreenParams.y;
float2 center = (0.5 * aspectRatio, 0.5);
float2 uv = i.uv - center;
uv.x *= aspectRatio;
float angle = atan2(uv.y, uv.x);
angle = (angle < 0) ? (angle + UNITY_TWO_PI) : angle;
float circleX = _DistanceFromCenter * cos(angle * _CircleNumber);
float circleY = _DistanceFromCenter * sin(angle * _CircleNumber);
float distance = length(uv - float2(circleX, circleY) * aspectRatio);
float circlePattern = smoothstep(_CircleRadius, _CircleRadius + 0.01, distance);
fixed4 smallCircleColor = _CircleColor;
fixed4 result = lerp(_BackgroundColor, smallCircleColor, circlePattern);
return result;
}
I've attempted to change the way I calculate the aspect ratio, however anything I do moves it away from the center, so I've just stuck with the closest thing I could get.
Full Code
Please see full script below.
Shader "Magnality/Loading/LoadingCircle"
{
Properties
{
_BackgroundColor("Background Color", Color) = (0, 0, 0, 0)
_CircleColor("Circle Color", Color) = (1, 1, 1, 1)
_CircleRadius("Circle Radius", Float) = 0.1
_CircleNumber("Circle Number", Int) = 10
_DistanceFromCenter("Distance from Center", Float) = 0.2
[HideInInspector] _MainTex("Dummy Texture", 2D) = "white" { }
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
fixed4 _BackgroundColor;
fixed4 _CircleColor;
float _CircleRadius;
int _CircleNumber;
float _DistanceFromCenter;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float aspectRatio = _ScreenParams.x / _ScreenParams.y;
float2 center = (0.5 * aspectRatio, 0.5);
float2 uv = i.uv - center;
uv.x *= aspectRatio;
float angle = atan2(uv.y, uv.x);
angle = (angle < 0) ? (angle + UNITY_TWO_PI) : angle;
float circleX = _DistanceFromCenter * cos(angle * _CircleNumber);
float circleY = _DistanceFromCenter * sin(angle * _CircleNumber);
float distance = length(uv - float2(circleX, circleY) * aspectRatio);
float circlePattern = smoothstep(_CircleRadius, _CircleRadius + 0.01, distance);
fixed4 smallCircleColor = _CircleColor;
fixed4 result = lerp(_BackgroundColor, smallCircleColor, circlePattern);
return result;
}
ENDCG
}
}
}
Any advice is much appreciated.
It looks like your issue is coming from your use of the variable angle
- it's calculated as the angle between the center of the screen and the pixel being rendered - but it's being used as if it's the angle between the center of the screen and the center of the circle. The centers of the circles should only be at set angles (eg. 0, 45, 90, etc degrees for 8 circles), not at the angle specified at the angle
variable in your code.
An easier way to do it is to reduce the problem into just one quadrant and rotating the angle variable so it's always in that one quadrant, and then having a set position for the circle within that quadrant. You can take your relative-to-the-center UV variable, and then rotate it as if it's part of the first "quadrant", and just solve it for that one case.
For example, if you wanted to render 8 circles, the pixels that have an angle of 3 degrees, 48 degrees, 93 degrees, etc should all render out the same - because of the rotational symmetry you are trying to create, every 45 degrees should be identical, so those cases are the same. So you can just convert these angles to the one simplest case, and then act as if there's just one circle - you get rotational symmetry and it renders all 8 circles.
Here's a rough working example of that method:
fixed4 frag(v2f i) : SV_Target
{
float aspectRatio = _ScreenParams.x / _ScreenParams.y;
float2 center = (0.5 * aspectRatio, 0.5);
float2 uv = i.uv - center;
uv.x *= aspectRatio;
float angle = atan2(uv.y, uv.x);
angle = (angle < 0) ? (angle + UNITY_TWO_PI) : angle;
float quadrantSize = UNITY_TWO_PI / _CircleNumber;
float quadrantAngle = (angle) % (quadrantSize) - quadrantSize/2;
float uvLength = length(uv);
float qX = uvLength * cos(quadrantAngle);
float qY = uvLength * sin(quadrantAngle);
float circleX = _DistanceFromCenter;
float circleY = 0;
float dist = distance(float2(qX, qY), float2(circleX, circleY) * aspectRatio);
float circlePattern = smoothstep(_CircleRadius, _CircleRadius + 0.01, dist);
fixed4 smallCircleColor = _CircleColor;
fixed4 result = lerp(_BackgroundColor, smallCircleColor, circlePattern);
return result;
}
It's basically the same as your code, but it hardcodes the circle position to (_DistanceFromCenter, 0)
, and converts angle
to quadrantAngle
by just doing a modulo on it so that the angle is always within -0.5*(2PI/_CircleNumber)
and 0.5*(2PI/_CircleNumber)
, to create rotational symmetry, and then recreating a quadrant-relative UV position (qX
and qY
) to work out the distance to the circle.