Risking on the cap, I still ask this question.

There is a need to write a shader for Unity3d , seemingly the simplest, radially appearing image (as in gif below), but with blur effects and gradient transparency. That is, a piece of a segment (for example, 30 degrees) that fills the image is not sharply visible, but with a smooth transition of transparency (see the example in the figure below).

At the same time, the image with zero transparency has maximum obstruction (although it is not visible on the transparent, and this example is not very clear, but since using the shader will be used on the transitions between the photos, everything will be clearly visible) and as the transparency of individual pixels decreases, the degree of blur on them decreases. Thus, where the transparency is 100 (opaque), there is a blur = 0.

enter image description here

enter image description here

After reading a few articles, I still can’t sort it all into my head. I read how the Gaussian blur is implemented (averaging the rgb value of the closest pixels on the sprite over each column / line), I understand that you need to attach transparency to this and change one depending on the other, but, for example, how to do it all, directly in the code, I also don’t understand the animation here (although I understand that there is a _TransVal parameter, #pragma alpha , which is responsible for transparency).

Because I did not have to write shaders earlier and, honestly, those articles that I found in the internet didn’t bring much clarity to the question of implementation. I understand myself what a shader is, what they are and why.

I would be grateful if someone will take it step by step to paint on the example of my task the implementation process with explanations: they say we need it for this, and this is for this, this is how we will change the blur, this is how transparency, and this is how we add animation.

UPDATE What I was able to achieve: By adjusting the sigma and cutoff values ​​of the misunderstanding I collected (due to lack of experience in writing shaders), everything that I need except one BUT occurs, the sprite behind all of this is where it comes from and how to get rid of it could understand.

enter image description here

http://g.recordit.co/rUaGYdynQ3.gif (picture in higher resolution).

This material (with shader) is applied on image . image and canvas in which they are placed on a transparentfx layer which renders a separate camera (this layer is disabled on the main one). It remains to understand how to disable (or remove from the render) this sprite behind and in the minimum form the task will be solved (transparency on the edge of the fill is no longer important, at least with a blur to figure out).

UPDATE 2 problem is solved if you set on the Image type filled and turn the fill amount . But it remains unclear how to humanly disable the render image'a, leaving the render only what is.

The result of the shader:

enter image description here

UPDATE from December 29 Realizing that with writing on a human basis, everything is not very good for me, I tried to use Shader Forge and it seemed to have achieved the necessary effect (one of the necessary). But it turned out very strange, in the sense that in the inspector's window in the unit, the effect was displayed as it should, while in the game scene he behaved “simplistically” so to speak. See the hyphae below for a better understanding. GIF1 window inspector. enter image description here GIF2 window game enter image description here

  • It is not clear, do you have a shader already written or not?) It’s easy to access the field responsible for transparency or something else through a script - it’s easy ... but if there isn’t a shader, then there’s something else to describe there not kamilfo) but still it is not clear you want to apply it on a 3d object but shot in 2d or on a sprite? or on lineRendere any? It will be applicable in many places or in one and only one. If in a single, it is easier not to make a sprite through a shader (if in 2d) - Alexey Shimansky
  • I have no shaders. Although the crutches and sticks on the Internet have already gathered almost everything that is needed. Shaders will be applied on textures, that is, on individual 2d images. - justyx
  • Unfortunately there is no time to write something to answer, but I would recommend to duplicate the question on enSO or gamedev.stackexchange.com and also on Russian forms on the unity3d3d.ru/distribution/index.php and answer if you get, then publish here too. The question is interesting. Especially, that the shader to the sprite, and not to the 3v model ..... By the way, I can recommend for the shaders to find (find for free 😊) and play with Shader Forge - assetstore.unity3d.com/en/#!/content/14147 . .... acegikmo.com/shaderforge/wiki/… - Alexey Shimansky
  • @ Alexey Shimansky Thank you for sure. I picked these shaders all these days and almost managed to achieve the result I needed. Keyword ALMOST :) By the way, I duplicated the question (in a simplified form) to answers.unity3d.com, but I haven’t received an answer yet, simplifying the fact that at least get an effect like image filled only with additional transparency on the verge that sprite fills I have the first picture (I hope I explained it clearly) :) - justyx
  • Strangely, you say that you wanted to get the effect as a image like filled only with additional transparency, but the question is about the updater and apply it to the canvas image effect. So what is the final result: apply to simple sprites or still apply to canvas images?) - Alexey Shimansky

3 answers 3

Is it worth all this through a single shader? Would do this:

  1. To use the blurred image, you will need another camera which, through the BLUR post effect, will write to the RenderTexture screen without an overlay image (place it in a separate Layer and turn off this layer in this camera).
  2. Next, do the animation of the manifestation of any form that interests you. Make a shape with an alpha animated mask.
  3. And then everything is simple, for the animated form we use the simplest material which, depending on the transparency of the texture on the material, draws through mixing either from the main texture or blurred in the RenderTexture.
  • In general, what I did in the end. I wrote a shader that in 2 passes the first one bluites the second to make the catoff along the radial alpha. All this in a separate layer and on a separate camera to separate from the main image. The problem was that the image I needed was blurred, filled up with the help of a catof, but another absolutely clean sprite hung behind it. How it was taken I could not understand. - justyx
  • You have the ClearFlag option on your camera. Depending on what is selected in it and what is the queue of rendering, something gets into this camera on the background. - KingPeas
  • @justyx is interesting, but did this answer solve the problem? Otherwise, I didn’t quite understand how it all happens without the use of Canvas UI ....... Can anyone somehow demonstrate this? - Alexey Shimansky
  • @ Alexey Shimansky this method works for any camera, and not just when working with UI. This approach is used in one of our projects, where you have to mix several scenes with different effects and a pseudo-generated image - KingPeas
  • @KingPeas I already talked to the author, and in the update of the question I looked and understood what it was about and other nuances)) - Alexey Shimansky

The answer, part 2.

  • Since it may be a load on the shader, some parts can also be delivered to the control script. Leave here only the application of values. As a result, the shader and script can be:

     Shader "Custom/RadialFill_MoreScriptControl" { Properties { [PerRendererData]_MainTex ("MainTex", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) _OpacityRotator ("Opacity Rotator", Range(-360, 360)) = -360 // два полных оборота _TextureRotator ("Texture Rotator", Range(0, 360)) = 360 [MaterialToggle] _FillClockwise ("Fill Clockwise", int ) = 1 [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5 [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 [HideInInspector] _CutoffRightBottomLeftTop ("cRBLT", Float) = 1.0 [HideInInspector] _OpRightBottomLeftTop ("oRBLT", Float) = 1.0 [HideInInspector] _OpVector ("OpVector", Vector) = (1, -1, 0, 0) [HideInInspector] _ReverseMaskCoords ("_ReverseMaskCoords", int) = 0 } SubShader { Tags { "IgnoreProjector"="True" "Queue"="Transparent" "RenderType"="Transparent" "CanUseSpriteAtlas"="True" "PreviewType"="Plane" } Pass { Name "FORWARD" Tags { "LightMode"="ForwardBase" } Blend One OneMinusSrcAlpha Cull Off ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #define UNITY_PASS_FORWARDBASE #pragma multi_compile _ PIXELSNAP_ON #include "UnityCG.cginc" #pragma multi_compile_fwdbase #pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2 #pragma target 3.0 static const float TAU = float(6.283185); // это 2 * PI, кто не знает uniform sampler2D _MainTex; uniform float4 _MainTex_ST; uniform float4 _Color; uniform float _OpacityRotator; uniform float _TextureRotator; uniform fixed _FillClockwise; uniform fixed _CutoffRightBottomLeftTop; uniform fixed _OpRightBottomLeftTop; uniform float2 _OpVector; uniform int _ReverseMaskCoords; struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 texcoord0 : TEXCOORD0; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float4 posWorld : TEXCOORD1; float3 normalDir : TEXCOORD2; float3 tangentDir : TEXCOORD3; float3 bitangentDir : TEXCOORD4; }; // матрица вращения float2x2 getMatrix(float angle) { float r_cos = cos(angle); float r_sin = sin(angle); return float2x2(r_cos, -r_sin, r_sin, r_cos); } // формирование маски float2x2 getMask(float oAtan2MaskNormalized, float rotator, int isRotatorSubtract) { float oAtan2MaskRotatable = isRotatorSubtract ? oAtan2MaskNormalized - rotator : rotator - oAtan2MaskNormalized; return ceil(oAtan2MaskRotatable); } float getNormalizedAtanMask(float2 maskChannels, int reverseMaskCoords) { float atan2var = reverseMaskCoords ? atan2(maskChannels.r, maskChannels.g) : atan2(maskChannels.g, maskChannels.r); return (atan2var / TAU) + 0.5; } VertexOutput vert (VertexInput v) { VertexOutput o = (VertexOutput)0; o.uv0 = v.texcoord0; o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize(mul(_Object2World, float4(v.tangent.xyz, 0.0)).xyz); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); o.posWorld = mul(_Object2World, v.vertex); o.pos = mul(UNITY_MATRIX_MVP, v.vertex ); #ifdef PIXELSNAP_ON o.pos = UnityPixelSnap(o.pos); #endif return o; } float4 frag(VertexOutput i) : COLOR { i.normalDir = normalize(i.normalDir); float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex)); /*** Общее начало для opacity и cutoff, помогающее переключать вращение по/против часовой стрелки BEGIN ***/ // float2(1, -1) - по часовой, float2(1, 1) - против часовой float2 clockCounterDirection = _FillClockwise ? float2(1, -1) : float2(1, 1); // по умолчанию "обрезание" начинается слева. // умножение на -1 для того, чтоб началось справа.....просто потому, что я так хочу =) float2 CommonStartAndSwitcher = (-1 * (i.uv0 - 0.5)) * clockCounterDirection; /*** Общее начало для opacity и cutoff с переключателем вращения по/против часовой стрелки END ***/ /*** Секция для cutoff ***/ float tRotatorNormalized = _TextureRotator / 360.0; float cutoffRotator_ang = _CutoffRightBottomLeftTop * -TAU; float2x2 cutoffRotationMatrix = getMatrix(cutoffRotator_ang); float2 cutoffRotator = mul(CommonStartAndSwitcher, cutoffRotationMatrix); float whiteToBlackMask = getMask(getNormalizedAtanMask(cutoffRotator, 0), tRotatorNormalized, 1); // Финальная маска float finalMask = 1.0 - whiteToBlackMask; clip(finalMask - 0.5); /*** Секция для opacity ***/ float oRotatorNormalized = _OpacityRotator / 360.0; float2 oVector = float2(_OpVector); float oRotator_ang = _OpRightBottomLeftTop * (oRotatorNormalized * -TAU); float2x2 oRotationMatrix = getMatrix(oRotator_ang); float2 oRotator = mul(oVector * CommonStartAndSwitcher, oRotationMatrix); float oWhiteToBlackMask = getMask(getNormalizedAtanMask(oRotator, _ReverseMaskCoords), oRotatorNormalized, 0); // Финальная прозрачность float oFinalMultiply = _MainTex_var.a * max(getNormalizedAtanMask(oRotator, _ReverseMaskCoords), ceil(oWhiteToBlackMask)); /*** Излучение (Emissive) ***/ // oFinalMultiply чтоб обрезать прозрачную область, где она обрезана в самой текстуре float3 finalColor = _MainTex_var.rgb * _Color.rgb * oFinalMultiply; // Конечный результат (цвет, обработанный маской и повернутый под углом альфа канал) return fixed4(finalColor, oFinalMultiply); } ENDCG } } FallBack "Diffuse" } 

    The script will be like this:

     using UnityEngine; using System.Collections; public enum FillOrigin { Right, Bottom, Left, Top } public class RadialFill_MoreScriptControl : MonoBehaviour { public float cutoffStartAngle = 5.0f; // градусы public float opacityStartAngle = -350.0f; // градусы, -2 * PI + 10 (небольшой начальный угол) public float deltaAngle = 5f; public bool fillClockwise = true; public FillOrigin fillOrigin = FillOrigin.Right; private const float MAX_ANGLE = 360.0f; private Material material; private float _TextureRotator; // ссылка на переменную _TextureRotator в шейдере private float _OpacityRotator; // ссылка на переменную _TextureRotator в шейдере void Start () { material = GetComponent<SpriteRenderer>().material; } void Update () { if (Input.GetMouseButtonDown(0)) //if (Input.GetKeyDown("f")) StartCoroutine(FillSprite()); } IEnumerator FillSprite() { var cOffStart = cutoffStartAngle; var oStart = opacityStartAngle; material.SetFloat("_FillClockwise", fillClockwise ? 1 : 0); material.SetFloat("_TextureRotator", cOffStart); material.SetFloat("_OpacityRotator", oStart); SetCutoffData(); SetOpacityData(); _TextureRotator = cOffStart; _OpacityRotator = oStart; while(_OpacityRotator <= MAX_ANGLE) { if (_TextureRotator >= MAX_ANGLE) _TextureRotator = MAX_ANGLE; if (_OpacityRotator >= MAX_ANGLE) _OpacityRotator = MAX_ANGLE; material.SetFloat("_TextureRotator", _TextureRotator); material.SetFloat("_OpacityRotator", _OpacityRotator); _OpacityRotator += deltaAngle; _TextureRotator += deltaAngle; yield return null; } yield break; } private void SetCutoffData() { var cutoffRightBottomLeftTop = 1.0f; if (fillOrigin == FillOrigin.Bottom) cutoffRightBottomLeftTop = fillClockwise ? 1.75f : 1.25f; else if (fillOrigin == FillOrigin.Left) cutoffRightBottomLeftTop = 1.5f; else if (fillOrigin == FillOrigin.Top) cutoffRightBottomLeftTop = fillClockwise ? 1.25f : 1.75f; cutoffRightBottomLeftTop += 0.001f; material.SetFloat("_CutoffRightBottomLeftTop", cutoffRightBottomLeftTop); } private void SetOpacityData() { Vector2 oVector = new Vector2(1, -1); var oRightBottomLeftTop = 1.0f; int reverseMaskCoords = (fillOrigin == FillOrigin.Top || fillOrigin == FillOrigin.Bottom) ? 1 : 0; if (fillOrigin == FillOrigin.Left) oVector = new Vector2(-1, 1); else if (fillOrigin == FillOrigin.Top) { oVector = fillClockwise ? new Vector2(-1, -1) : new Vector2(1, 1); oRightBottomLeftTop = -1.0f; } else if (fillOrigin == FillOrigin.Bottom) { oVector = fillClockwise ? new Vector2(1, 1) : new Vector2(-1, -1); oRightBottomLeftTop = -1.0f; } material.SetInt("_ReverseMaskCoords", reverseMaskCoords); material.SetVector("_OpVector", oVector); material.SetFloat("_OpRightBottomLeftTop", oRightBottomLeftTop); } } 

    in the inspector, the control is:

    enter image description here


And about the blur ... Since my answer is already large (because of the code) ... and already the second part, I will give the code for the Blur shader, which you can transfer to the shaders above. And also add control blur from the script for example above.

 Shader "Custom/Blur" { Properties { _MainTex ("Texture", 2D) = "white" {} radius ("radius", Range(0, 80)) =0 resolution ("resolution", float) = 800 } 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; }; sampler2D _MainTex; float4 _MainTex_ST; uniform float resolution = 800; uniform float radius = 400; uniform float2 dir = float2(0,1); v2f vert (appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { float4 sum = float4(0.0, 0.0, 0.0, 0.0); float2 tc = i.uv; // радиус размытия в пикселях float blur = radius/resolution/4; float hstep = 1; // размытие по горизонтали float vstep = 0; // размытие по вертикали sum += tex2D(_MainTex, float2(tc.x - 5.0 * blur * hstep, tc.y - 5.0 * blur * vstep)) * 0.0052111262; sum += tex2D(_MainTex, float2(tc.x - 4.0 * blur * hstep, tc.y - 4.0 * blur * vstep)) * 0.0162162162; sum += tex2D(_MainTex, float2(tc.x - 3.0 * blur * hstep, tc.y - 3.0 * blur * vstep)) * 0.0540540541; sum += tex2D(_MainTex, float2(tc.x - 2.0 * blur * hstep, tc.y - 2.0 * blur * vstep)) * 0.1216216216; sum += tex2D(_MainTex, float2(tc.x - 1.0 * blur * hstep, tc.y - 1.0 * blur * vstep)) * 0.1945945946; sum += tex2D(_MainTex, float2(tc.x, tc.y)) * 0.2270270270; sum += tex2D(_MainTex, float2(tc.x + 1.0 * blur * hstep, tc.y + 1.0 * blur * vstep)) * 0.1945945946; sum += tex2D(_MainTex, float2(tc.x + 2.0 * blur * hstep, tc.y + 2.0 * blur * vstep)) * 0.1216216216; sum += tex2D(_MainTex, float2(tc.x + 3.0 * blur * hstep, tc.y + 3.0 * blur * vstep)) * 0.0540540541; sum += tex2D(_MainTex, float2(tc.x + 4.0 * blur * hstep, tc.y + 4.0 * blur * vstep)) * 0.0162162162; sum += tex2D(_MainTex, float2(tc.x + 5.0 * blur * hstep, tc.y + 5.0 * blur * vstep)) * 0.0052111262; return float4(sum.rgb, 1); } ENDCG } } } 

PS Unfortunately, the RadialFill shader RadialFill works for Sprite ModeSingle . How to make for Multi Multiple , I do not know yet.

PPS You can make more improvements:

  • The getMatrix function getMatrix (rotation matrix) is also in the script.
  • Make the mask not generated inside the shader, but take the texture as an atan2 image and apply it already. You can still slightly remove the download from the shader.

PPPS For an attempt to understand shaders, at least on a small level, you can use the asset Shader Forge - A visual editor for programming shaders. The visuality in this case is very plus.

Of the free so far, the search shows only uShader FREE - but I have not tried it, I don’t know how good it is.


Links to read, which were used in the shaders above:

  • Do you know by chance programs like ShaderForge, but free? Maybe there are third-party, but with export under unity? - user220409
  • Unfortunately, I don’t know @OlmerDale ... I just recently found out about SF myself))) If you only search for assetStore by the Free Only parameter, well, on the Internet, request a unity3d visual shader editor ...... But I think such significant ones things are not free. In addition, 5 tr. rubles for building shaders without steaming with text editors (and without autocomplete) for them and constantly viewing the results of testing and experimenting with various possible combinations - in my opinion the price is not so great. - Alexey Shimansky
  • @OlmerDale on the search for free while only shows uShader FREE - Alexey Shimansky

Wow, an interesting question and even a reward!

enter image description here

Generally, how much I did not read, I could not imagine why the effect of blur to apply on transparency. After all, if a certain part of the sprite is transparent, then there will be no blur and so visible and there, and where there is no transparency, then there is no blur. Either I didn’t understand what it was and it meant that the application of the blur in general to the whole sprite, regardless of what percentage of the radial fill is now available. If so, then

  1. You can do what is written in another answer: take Unity from the standard assemblies (the benefit is not a little provided) the blur effect applied on the camera. Add another camera, add a blur script and a shader there and turn on that camera at the right moment and change the offset in the Blur effect.
  2. Implement, as they wanted in the shader)) About this at the very end.

(!!!)

May the site admins forgive me, but I don’t have one answer (because of the amount of code, and not because of "water"). Therefore, the answer will be in two parts.


The answer, part 1.

The point is that the trimming, which is flooded with transparency will be reduced to what will be taken mask, on the basis of which everything will happen. The mask is generated programmatically via the atan2 trigonometric function. On axes, it looks like this:

atan2

In a two-dimensional coordinate system it looks like this:

atan2 2D

Since the mask is a kind of component consisting of shades of black and white, in which black is the complete absence of texture and white is completely visible texture, for transparency the atan2 mask will be a transition from black to white (see the picture above ), and for trimming an additional function will be applied, so that there is only black / white, without smooth transitions.

atan2 ceil

I will try to just post a shader, which will be comments on what has been done. I’m not sure that everything will be clear, but I’ll try to do something really bad.

 Shader "Custom/RadialFill" { Properties { [PerRendererData]_MainTex ("MainTex", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) _OpacityRotator ("Opacity Rotator", Range(-360, 360)) = -360 // два полных оборота _TextureRotator ("Texture Rotator", Range(0, 360)) = 360 [MaterialToggle] _FillClockwise ("Fill Clockwise", Float ) = 1 [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5 [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 [KeywordEnum(Right, Bottom, Left, Top)] _Fill_Origin("Fill Origin", Int) = 0 } SubShader { // https://docs.unity3d.com/ru/current/Manual/SL-SubShaderTags.html Tags { "IgnoreProjector"="True" "Queue"="Transparent" "RenderType"="Transparent" "CanUseSpriteAtlas"="True" "PreviewType"="Plane" } Pass { Name "FORWARD" Tags { "LightMode"="ForwardBase" // https://docs.unity3d.com/Manual/SL-PassTags.html } Blend One OneMinusSrcAlpha // https://docs.unity3d.com/ru/current/Manual/SL-Blend.html ZWrite Off // https://docs.unity3d.com/ru/current/Manual/SL-CullAndDepth.html CGPROGRAM #pragma vertex vert // vert - имя функции обработки вершин #pragma fragment frag // frag - имя функции обработки пикселей #pragma multi_compile _ PIXELSNAP_ON // как работает shader_feature: https://docs.unity3d.com/ru/530/Manual/SL-MultipleProgramVariants.html // он относится к свойству _Fill_Origin .... по сути - автоматически конвертируем его имя и значения в константы #pragma shader_feature _FILL_ORIGIN_RIGHT _FILL_ORIGIN_BOTTOM _FILL_ORIGIN_LEFT _FILL_ORIGIN_TOP #include "UnityCG.cginc" #pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2 #pragma target 3.0 uniform sampler2D _MainTex; uniform float4 _MainTex_ST; uniform float4 _Color; uniform float _OpacityRotator; uniform float _TextureRotator; uniform fixed _FillClockwise; static const float TAU = float(6.283185); // это 2 * PI, кто не знает struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 texcoord0 : TEXCOORD0; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float4 posWorld : TEXCOORD1; float3 normalDir : TEXCOORD2; float3 tangentDir : TEXCOORD3; float3 bitangentDir : TEXCOORD4; }; VertexOutput vert (VertexInput v) { VertexOutput o = (VertexOutput)0; o.uv0 = v.texcoord0; o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize(mul(_Object2World, float4(v.tangent.xyz, 0.0)).xyz); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); o.posWorld = mul(_Object2World, v.vertex); o.pos = mul(UNITY_MATRIX_MVP, v.vertex ); #ifdef PIXELSNAP_ON o.pos = UnityPixelSnap(o.pos); #endif return o; } float4 frag(VertexOutput i) : COLOR { i.normalDir = normalize(i.normalDir); float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex)); /*** Общее начало для opacity и cutoff, помогающее переключать вращение по/против часовой стрелки BEGIN ***/ // float2(1, -1) - по часовой, float2(1, 1) - против часовой float2 clockCounterDirection = _FillClockwise ? float2(1, -1) : float2(1, 1); // по умолчанию "обрезание" начинается слева. // умножение на -1 для того, чтоб началось справа.....просто потому, что я так хочу =) float2 CommonStartAndSwitcher = (-1 * (i.uv0 - 0.5)) * clockCounterDirection; /*** Общее начало для opacity и cutoff с переключателем вращения по/против часовой стрелки END ***/ /*** Секция для cutoff ***/ float cutoffRightBottomLeftTop = 1.0; // изменение направления // В зависимости от того, что выбрано в качестве старта вращения право/лево/верх/низ // нужно будет провернуть и текстурку. // +0.25 - 90 градусов, +0.5 - 180, +0.75 - 270 #if _FILL_ORIGIN_BOTTOM cutoffRightBottomLeftTop = _FillClockwise ? 1.75 : 1.25; #elif _FILL_ORIGIN_LEFT cutoffRightBottomLeftTop = 1.5; #elif _FILL_ORIGIN_TOP cutoffRightBottomLeftTop = _FillClockwise ? 1.25 : 1.75; #endif cutoffRightBottomLeftTop += 0.001; // Матрица вращения для cutoff float cutoffRotator_ang = cutoffRightBottomLeftTop * -TAU; float cutoffRotator_cos = cos(cutoffRotator_ang); float cutoffRotator_sin = sin(cutoffRotator_ang); float2x2 cutoffRotationMatrix = float2x2(cutoffRotator_cos, -cutoffRotator_sin, cutoffRotator_sin, cutoffRotator_cos); float2 cutoffRotator = mul(CommonStartAndSwitcher, cutoffRotationMatrix); // перевод из системы от 0 до 360 градусов в отсчет от 0 до 1 float tRotatorNormalized = _TextureRotator / 360.0; // Генерирование маски для отсечения пикселей и отсечение пикселей по предоставленной маске // 1. Для генерации нужны исхоные две координаты.... // rg, утрированно, представляют из себя x и y float2 cutoffMaskSource = cutoffRotator.rg; // 2. Формируем начальную маску // в инете рисуночки глянуть как это выглядит =) // Угол задается в радианах и принимает значения от -PI до PI, исключая -PI float atan2Mask = atan2(cutoffMaskSource.g, cutoffMaskSource.r); // 3. Добавляем пол оборота (до целого) и конвертируем в значение от 0 до 1, // для дальнейшей удобной работы в единичном отрезке, т.к tRotatorNormalized меняется от 0 до 1 float atan2MaskNormalized = (atan2Mask / TAU) + 0.5; // 4. Привязка маски к повороту. хз как объяснить float atan2MaskRotatable = atan2MaskNormalized - tRotatorNormalized; // 5. Получаем карту заливки от белого к черному // Белый - полностью видимый участок, Черный - обрезающиеся (не отображающиеся) пиксели float whiteToBlackMask = ceil(atan2MaskRotatable); // 6. Собираем финальную маску от чёрного к белому (т.к. нужно постепенное заполнение) float finalMask = 1.0 - whiteToBlackMask; clip(finalMask - 0.5); /*** Секция для opacity ***/ // oVector меняется в зависимости от начала направления - лево/право/верх/низ float2 oVector = float2(1, -1); // изменение направления в зависимости от лево-право (1.0) или верх-низ (-1.0) float oRightBottomLeftTop = 1.0; // В зависимости от того, что выбрано в качестве старта вращения право/лево/верх/низ // нужно будет провернуть и маску. #if _FILL_ORIGIN_LEFT oVector = float2(-1, 1); #elif _FILL_ORIGIN_TOP oVector = _FillClockwise ? float2(-1, -1) : float2(1, 1); oRightBottomLeftTop = -1.0; #elif _FILL_ORIGIN_BOTTOM oVector = _FillClockwise ? float2(1, 1) : float2(-1, -1); oRightBottomLeftTop = -1.0; #endif float oRotatorNormalized = _OpacityRotator / 360.0; // Матрица вращения для opacity float oRotator_ang = oRightBottomLeftTop * (oRotatorNormalized * -TAU); float oRotator_cos = cos(oRotator_ang); float oRotator_sin = sin(oRotator_ang); float2x2 oRotationMatrix = float2x2(oRotator_cos, -oRotator_sin, oRotator_sin, oRotator_cos); float2 oRotator = mul(oVector * CommonStartAndSwitcher, oRotationMatrix); // Как и у cutoff формируем маску float2 oMask = oRotator.rg; float2 oMaskHorizOrVert = atan2(oMask.g, oMask.r); // при формировании маски по вертикали, нужно поменять x, y местами в функции #if (_FILL_ORIGIN_TOP || _FILL_ORIGIN_BOTTOM) oMaskHorizOrVert = atan2(oMask.r, oMask.g); #endif float oAtan2MaskNormalized = (oMaskHorizOrVert / TAU) + 0.5; // oRotatorNormalized - oAtan2MaskNormalized для того, чтобы первый круг просто провернуться, а на втором // начать обрезку как у cutoff, только начиная схвоста, но при этом продолжая вращаться. // Если было бы oAtan2MaskNormalized - oRotatorNormalized (как в примере с cutoff выше), то, т.к. значение oRotatorNormalized // меняется с -1 до 1 (два полных круга), получается что маска наложена на изображение 2 раза: 1 раз - прозрачность, 2 раз - она же // поэтому увеличивается наложенность, белый цвет. В итоге при изменении с -1 до 1 ушла бы в начале белизна, а потом провернулась бы маска, // и не обрезалась бы float oAtan2MaskRotatable = oRotatorNormalized - oAtan2MaskNormalized; float oWhiteToBlackMask = ceil(oAtan2MaskRotatable); // Финальная прозрачность float oFinalMultiply = _MainTex_var.a * max(oAtan2MaskNormalized, ceil(oWhiteToBlackMask)); /*** Излучение (Emissive) ***/ // oFinalMultiply чтоб обрезать прозрачную область, где она обрезана в самой текстуре float3 finalColor = _MainTex_var.rgb * _Color.rgb * oFinalMultiply; // Конечный результат (цвет, обработанный маской и повернутый под углом альфа канал) return fixed4(finalColor, oFinalMultiply); } ENDCG } } FallBack "Diffuse" } 

Чтобы в нужный момент запустить заливку, конечно же нужно дать команду. А откуда её можно дать? Правильно — из скрипта. Он будет расположен ниже:

 using UnityEngine; using System.Collections; public class RadialFill : MonoBehaviour { public float cutoffStartAngle = 5.0f; // градусы public float opacityStartAngle = -350.0f; // градусы, -2 * PI + 10 (небольшой начальный угол) public float deltaAngle = 5f; private const float MAX_ANGLE = 360.0f; private Material material; private float _TextureRotator; // ссылка на переменную _TextureRotator в шейдере private float _OpacityRotator; // ссылка на переменную _TextureRotator в шейдере void Start () { material = GetComponent<SpriteRenderer>().material; } void Update () { if (Input.GetMouseButtonDown(0)) //if (Input.GetKeyDown("f")) StartCoroutine(FillSprite()); } IEnumerator FillSprite() { var cOffStart = cutoffStartAngle; var oStart = opacityStartAngle; material.SetFloat("_TextureRotator", cOffStart); material.SetFloat("_OpacityRotator", oStart); _TextureRotator = cOffStart; _OpacityRotator = oStart; while(_OpacityRotator <= MAX_ANGLE) { if (_TextureRotator >= MAX_ANGLE) _TextureRotator = MAX_ANGLE; if (_OpacityRotator >= MAX_ANGLE) _OpacityRotator = MAX_ANGLE; material.SetFloat("_TextureRotator", _TextureRotator); material.SetFloat("_OpacityRotator", _OpacityRotator); _OpacityRotator += deltaAngle; _TextureRotator += deltaAngle; yield return null; } yield break; } } 

Where:

cutoffStartAngle — начальный угол обрезки, opacityStartAngle — начальный угол прозрачности. Эти параметры для того, чтобы немного отрегулировать по вкусу площадь сектора, занимаемого прозрачностью. Замечу, что прозрачность изменяется от -360 до 360, потому что первый круг она проворачивается сама по себе, а второй круг — плавно "заходит" за текстуру.

deltaAngle - дельта, на которую проворачиваются маски.

Что скрипт делает? При нажатии нажатии на клавишу мыши он берет шейдер у спрайта (точнее с его материала), устанавливает изначальные углы, в цикле изменяет угол поворота и передает это значение в шейдер, чтобы он там уже у себя применил значения в frag .

Выглядит в инспекторе так:

RadialFillShader&Script inspector

  • Opacity Rotator - вращение маски прозрачности
  • Texture Rotator - вращение маски обрезки
  • Fill Clockwise - по часовой стрелке или против
  • Fill Origin - с какой стороны начинать (справа/слева/сверху/снизу)

Итог будет выглядеть примерно таким:

enter image description here

Увы на данной гифке не получается передать то, как это выглядит в правильности


Улучшение

  • Так как у секции cutoff и opacity есть общие части, то их можно вынести в общие функции, как и во всех нормальных языках программирования.

     Shader "Custom/RadialFillCommonFunctions" { Properties { [PerRendererData]_MainTex ("MainTex", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) _OpacityRotator ("Opacity Rotator", Range(-360, 360)) = -360 // два полных оборота _TextureRotator ("Texture Rotator", Range(0, 360)) = 360 [MaterialToggle] _FillClockwise ("Fill Clockwise", Float ) = 1 [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5 [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 [KeywordEnum(Right, Bottom, Left, Top)] _Fill_Origin("Fill Origin", Int) = 0 } SubShader { Tags { "IgnoreProjector"="True" "Queue"="Transparent" "RenderType"="Transparent" "CanUseSpriteAtlas"="True" "PreviewType"="Plane" } Pass { Name "FORWARD" Tags { "LightMode"="ForwardBase" } Blend One OneMinusSrcAlpha Cull Off ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #define UNITY_PASS_FORWARDBASE #pragma multi_compile _ PIXELSNAP_ON #pragma shader_feature _FILL_ORIGIN_RIGHT _FILL_ORIGIN_BOTTOM _FILL_ORIGIN_LEFT _FILL_ORIGIN_TOP #include "UnityCG.cginc" #pragma multi_compile_fwdbase #pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2 #pragma target 3.0 static const float TAU = float(6.283185); // это 2 * PI, кто не знает uniform sampler2D _MainTex; uniform float4 _MainTex_ST; uniform float4 _Color; uniform float _OpacityRotator; uniform float _TextureRotator; uniform fixed _FillClockwise; struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float2 texcoord0 : TEXCOORD0; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float4 posWorld : TEXCOORD1; float3 normalDir : TEXCOORD2; float3 tangentDir : TEXCOORD3; float3 bitangentDir : TEXCOORD4; }; // матрица вращения float2x2 getMatrix(float angle) { float r_cos = cos(angle); float r_sin = sin(angle); return float2x2(r_cos, -r_sin, r_sin, r_cos); } // формирование маски float2x2 getMask(float oAtan2MaskNormalized, float rotator, int isRotatorSubtract) { //float atan2var = reverseMaskCoords ? atan2(maskChannels.r, maskChannels.g) : atan2(maskChannels.g, maskChannels.r); //float oAtan2MaskNormalized = (atan2var / TAU) + 0.5; float oAtan2MaskRotatable = isRotatorSubtract ? oAtan2MaskNormalized - rotator : rotator - oAtan2MaskNormalized; return ceil(oAtan2MaskRotatable); } float getNormalizedAtanMask(float2 maskChannels, int reverseMaskCoords) { float atan2var = reverseMaskCoords ? atan2(maskChannels.r, maskChannels.g) : atan2(maskChannels.g, maskChannels.r); return (atan2var / TAU) + 0.5; } VertexOutput vert (VertexInput v) { VertexOutput o = (VertexOutput)0; o.uv0 = v.texcoord0; o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize(mul(_Object2World, float4(v.tangent.xyz, 0.0)).xyz); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); o.posWorld = mul(_Object2World, v.vertex); o.pos = mul(UNITY_MATRIX_MVP, v.vertex ); #ifdef PIXELSNAP_ON o.pos = UnityPixelSnap(o.pos); #endif return o; } float4 frag(VertexOutput i) : COLOR { i.normalDir = normalize(i.normalDir); float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex)); /*** Общее начало для opacity и cutoff, помогающее переключать вращение по/против часовой стрелки BEGIN ***/ // float2(1, -1) - по часовой, float2(1, 1) - против часовой float2 clockCounterDirection = _FillClockwise ? float2(1, -1) : float2(1, 1); // по умолчанию "обрезание" начинается слева. // умножение на -1 для того, чтоб началось справа.....просто потому, что я так хочу =) float2 CommonStartAndSwitcher = (-1 * (i.uv0 - 0.5)) * clockCounterDirection; /*** Общее начало для opacity и cutoff с переключателем вращения по/против часовой стрелки END ***/ /*** Секция для cutoff ***/ float tRotatorNormalized = _TextureRotator / 360.0; float cutoffRightBottomLeftTop = 1.0; // изменение направления #if _FILL_ORIGIN_BOTTOM cutoffRightBottomLeftTop = _FillClockwise ? 1.75 : 1.25; #elif _FILL_ORIGIN_LEFT cutoffRightBottomLeftTop = 1.5; #elif _FILL_ORIGIN_TOP cutoffRightBottomLeftTop = _FillClockwise ? 1.25 : 1.75; #endif cutoffRightBottomLeftTop += 0.001; float cutoffRotator_ang = cutoffRightBottomLeftTop * -TAU; float2x2 cutoffRotationMatrix = getMatrix(cutoffRotator_ang); float2 cutoffRotator = mul(CommonStartAndSwitcher, cutoffRotationMatrix); float whiteToBlackMask = getMask(getNormalizedAtanMask(cutoffRotator, 0), tRotatorNormalized, 1); // Финальная маска float finalMask = 1.0 - whiteToBlackMask; clip(finalMask - 0.5); /*** Секция для opacity ***/ float oRotatorNormalized = _OpacityRotator / 360.0; float2 oVector = float2(1, -1); float oRightBottomLeftTop = 1.0; int reverseMaskCoords = 0; #if (_FILL_ORIGIN_TOP || _FILL_ORIGIN_BOTTOM) reverseMaskCoords = 1; #endif #if _FILL_ORIGIN_LEFT oVector = float2(-1, 1); #elif _FILL_ORIGIN_TOP oVector = _FillClockwise ? float2(-1, -1) : float2(1, 1); oRightBottomLeftTop = -1.0; #elif _FILL_ORIGIN_BOTTOM oVector = _FillClockwise ? float2(1, 1) : float2(-1, -1); oRightBottomLeftTop = -1.0; #endif float oRotator_ang = oRightBottomLeftTop * (oRotatorNormalized * -TAU); float2x2 oRotationMatrix = getMatrix(oRotator_ang); float2 oRotator = mul(oVector * CommonStartAndSwitcher, oRotationMatrix); float oWhiteToBlackMask = getMask(getNormalizedAtanMask(oRotator, reverseMaskCoords), oRotatorNormalized, 0); // Финальная прозрачность float oFinalMultiply = _MainTex_var.a * max(getNormalizedAtanMask(oRotator, reverseMaskCoords), ceil(oWhiteToBlackMask)); /*** Излучение (Emissive) ***/ // oFinalMultiply чтоб обрезать прозрачную область, где она обрезана в самой текстуре float3 finalColor = _MainTex_var.rgb * _Color.rgb * oFinalMultiply; // Конечный результат (цвет, обработанный маской и повернутый под углом альфа канал) return fixed4(finalColor, oFinalMultiply); } ENDCG } } FallBack "Diffuse" } 
  • Вот это ответ))) спасибо большое))сегодня попробую обязательно, у меня была еще такая проблема странная. Попробовал в shader forge собрать. Добился нужного эффекта, в итоге в Юнити в окне инспектора все норм, а в непосредственно game, просто эффект фэйда на весь объект. Звучит непонятно понимаю, прикреплю гиф сейчас покажу.justyx
  • @justyx к сожалению проблемой метода в ответе - является равномерная маска. В теории, как я описал, надо бы маску сделать в виде текстуры и работать с ней. Ведь вручную маску можно уже сделать более плотной по белому или черному цвету, что позволило бы еще больше проконтролировать площадь прозрачности и её переход. Вот правда с текстурой чёт пока не заладилось.............. а ну и невозможность работы с мультиспрайтами, как это делает Canvas UI. Там алгоритм дофига большой нужен.Алексей Шиманский
  • Сейчас буду читать вникать. я добавил там апдейт в описание.justyx
  • @justyx а можешь код из ShaderForge куда-либо выложить? Посмотреть что там. Покопаться. Правда Не знаю на сколько быстрый ответ от меня ожидать ))) ............. а я что-то не вижу тот эффект, который ты в вопросе описывал)) ни блюра ни радиальной обрезки)Алексей Шиманский
  • да я потому и указал что это один ИЗ требуемых эффектов. их было 5. радиальный, вертикальны, через вспышку и тд. Но у всех особенность плавного вот такого "поведения". При этом данный эффект на гиф я уже плюнул на блюр и попытался просто реализовать эффект как стандартный image в unity (заполнение сверху вниз), только с плавным фэйдом. Код сейчас поищу. Не уверен что смогу вот так сходу найти. Но сегодня найду.justyx