Страницы

Поиск по вопросам

Показаны сообщения с ярлыком shader. Показать все сообщения
Показаны сообщения с ярлыком shader. Показать все сообщения

пятница, 14 февраля 2020 г.

Проблема z-sorting`а в Transparent шейдерах

#unity3d #shader #unity3d_faq


Версия Unity 2018.1, шейдеры создавались в Shader Graph для Lightweight Render Pipeline.

При рендеринге с использованием полупрозрачного материала возникла проблема странной
отрисовки некоторых частей меши:

Opaque PBR


Transparent PBR, alpha = 1


Master node в Shader Graph




Объясните пожалуйста, почему так происходит, и как можно от этого избавиться? 
    


Ответы

Ответ 1



Почему так происходит? Непрозрачные (и не только, но это уже другая тема) шейдеры перед "основной" отрисовкой пишут глубину пикселя в буфер, как ни странно, глубины - depth buffer, также его называют z-buffer. Логика достаточно простая: объекты отрисовываются от самых близких к самым дальним - такой подход позволят отсекать невидимые пиксели при пересечении какой-либо геометрии в сцене. Перед обработкой очередного фрагмента (пикселя) в определенный буфер GPU сравнивает значение глубины этого самого фрагмента с z-buffer`ом, чтобы решить, нужно ли вообще обрабатывать этот пиксель. Вся эта предыстория нужна для того, чтобы понять, почему полупрозрачные объекты являются головной болью при рендеринге. Для примера возьмем обычный куб с полупрозрачным материалом и двусторонней отрисовкой и посмотрим, какая потенциальная проблема возникает в полупрозрачных объектах. Чтобы рисунок был попроще, допустим, что конкретно этот куб состоит из четырехугольников, а не треугольников. Для наглядности я продемонстрировал, в каком порядке GPU, например, отрисует полигоны (вообще, это зависит от того, как полигоны индексированы в index buffer), и сразу ниже я нарисовал, что мы ожидаем увидеть от GPU: Даже без отрисовки 2х последних граней видно, что что-то пошло не так: пиксели более "поздних" в плане отрисовки полигонов почему-то пишут поверх уже отрендеренных пикселей, даже не смотря на то, что они изначально должны были провалить ZTest - передний полигон, очевидно, ближе, чем все остальные полигоны нашего куба. Давайте вернемся к непрозрачным объектам. Почему буфер глубины работает? Все просто, из-за системы отрисовки front-to-back (от ближних объектов к дальним) значения в буфере глубины могут меняться только на более "близкие" (зависит от устройства буфера, иногда самый близкий пиксель отмечается 1, иногда 0). В Transparent объектах все наоборот: Они отрисовываются по системе back-to-front, тут стоит сделать оговорку: так происходит не везде, такой подход на сегодняшний день используется в Unity на данный момент, с добавлением Scriptable Render Pipeline появилась возможность переписать рендер как душе угодно, в том числе можно применить front-to-back transparent rendering, подробнее про это здесь. Для чего это нужно? Если вы расположите 2 полупрозрачных объекта с, к примеру, alpha = 0.5, то вы ожидаете, что сначала произойдет блендинг первого объекта с непрозрачным фоном, после произойдет блендинг обновленного фона и второго объекта. Такой подход обязывает не обновлять информацию в буфере глубины, опять же, почему? На то есть 2 причины: первая - мы все еще проверяем наш пиксель на глубину, чтобы не рисовать лишний раз перекрытый пиксель, вторая - если мы начнем обновлять буфер глубины, возникнут артефакты при частичном перекрытии друг другом двух полупрозрачных объектов: В данном случае я поместил один полупрозрачный объект (каска) в другой (стена) так, чтобы каска была по обе стороны стены. Без обновления буфера глубины видно, что оба объекта нормально смешиваются между собой, но стоит включить обновление буфера глубины, как мы сразу видим, что половина каски "за стеной" не отрисовывается. P.S. Каска при ZWrite Off выглядит, как будто непрозрачная - это из-за того, что у стены и у каски цвет материала белый, в обычных условиях все будет выглядеть хорошо. Решение данной проблемы Для начала нужно понять, что панацеи от этого не существует. Вся проблема заключается в том, что пикселям полупрозрачных объектов не очень нравится бороться за право быть отрисованным - поэтому они вообще этого не делают. Ладно бы это бы разные объекты - там все просто разрешается сортировкой полупрозрачных объектов по глубине, но когда эти пиксели принадлежать одному объекту - возникает довольно сложная в решении проблема, описанная выше. И вот, после горы теории, мы подобрались к способам решения такой проблемы: Обновлять буфер глубины или что такое ZWrite On Радикально писать в буфер я не советую, в идеале нужно просто написать shader variant (нужно писать свой шейдер с нуля), либо же сделать 2 разных шейдера в Shader Graph, один с ZWrite On при alpha = [1..0.5) и ZWrite Off при alpha = [0.5..0]. Уж совсем в идеале можно слегка переписать Lightweight Render Pipeline, проверяя, пересекаются ли полупрозрачные объекты при их сортировке. В общем, вариантов при данном подходе много. Самый дешевый способ - один шейдер с ZWrite On, но и рисует он только "внешнюю оболочку" объекта, если внутри что-то есть - нужно его комбинировать с ZWrite Off для разных значений alpha: Как такое сделать в Shader Graph? К сожалению, Shader Graph, в отличии от его аналога - Amplify Shader Editor, не дает вот так просто менять ZWrite и прочие прелести сабшейдеров, ну... на то она и бета, когда-нибудь добавят :) А мы не брезгуя создаем новый файл шейдера, подойдет любой, лишь бы не Graph - нам этот дурацкий редактор не нужен, например - Standard Surface Shader. Называем, как хотим, открываем, удаляем все содержимое, сохраняем и пока что оставляем в покое. Создаем любой нужный нам шейдер в Shader Graph, ставим Surface - Transparent. Создали, протестировали, все работает - кликаем правой кнопкой мыши на Master Node и выбираем Show Generated Code. Ждем, пока прогрузится выбранный редактор кода, видим наш шейдер, выделяем его и копируем. Далее открываем наш первый файл шейдера, из которого мы все удалили, и вставляем скопированный код. Далее через Ctrl + F находим ZWrite Off и меняем его на ZWrite On. Будьте внимательны, это нужно заменить в Forward проходе сабшейдера, это важно: А важно, потому что проходов у сабшейдера может быть несколько, и каждый из них будет содержать параметр ZWrite: P.S. Вообще есть еще несколько вариантов, например, - Cutout шейдер и постоянная смена индексирования вершин для разного положения и поворота камеры. Первый вариант не подходит, т.к. нужен именно полупрозрачный объект, а второй не подходит сразу по двум причинам: во-первых слишком сложно реализовать без хоть каких-то знаний в области GPU и конвейера рендеринга, во-вторых такая методика явно не подходит для runtime вычислений.

пятница, 27 декабря 2019 г.

Проблема с освещением в шейдере opengl es 2.0

#android #shader #opengles


Имеется ландшафт который я хочу осветить. Собственно везде освещение нормальное,
а ближе к центру огромное белое пятно! Я пока не пойму из за чего это пятно, и как
это исправить. Нужна помощь. Мне нужно понять из за чего это белое пятно выходит, и
понять как его исправить?

Вот скриншет со смартфона samsung galaxy S4



Теперь покажу как на эмуляторе andy



Теперь покажу как распологаются нормали



Вот код фрагментного шейдера:

precision mediump float;
varying vec3 vPosition;
uniform sampler2D uTexture;
uniform sampler2D uTexture2;
uniform vec3 u_camera;
varying vec2 vTexcoord;
varying vec3 v_normal;

float getLight(vec3 lightPos,vec3 camPos,vec3 vertexPos, vec3 normal,
               float ambient, float k_diffuse, float k_specular)
{
    vec3 n_normal=normalize(normal);
    vec3 lightvector = normalize(lightPos - vertexPos);
    vec3 lookvector = normalize(camPos - vertexPos);
    float diffuse = k_diffuse * max(dot(n_normal, lightvector), 0.0);
    vec3 reflectvector = reflect(-lightvector, n_normal);
    float specular = k_specular * pow( max(dot(lookvector,reflectvector),0.0), 60.0
);      
    return ambient+diffuse+specular;
}


void main() {
    vec3 lightPos = vec3(60.0,100.0,-415.0);
    float light = getLight(lightPos,u_camera,vPosition,v_normal,
                           0.5,  0.9,   0.3);

    vec4 pixsel;
    vec3 pos = normalize(vPosition);
    if (pos.y > 0.01) 
        pixsel = texture2D(uTexture2,vTexcoord); 
    else 
        pixsel = texture2D(uTexture,vTexcoord);

    gl_FragColor = light * pixsel;
}

    


Ответы

Ответ 1



Проблема решена, на самом деле не верно передавались координаты камеры.

суббота, 14 декабря 2019 г.

Шейдеры в unity3d

#unity3d #shader


Рискуя нахватать по шапке, я все таки задам этот вопрос. 

Встала необходимость написать шейдер для Unity3d, казалось бы простейший, радиально
проявляющееся изображение (как на gif ниже), но с эффектами блюра и градиентной прозрачности.
То есть кусок сегмента (например градусов 30), который  заполняет изображение - не
резко видимый, а с плавным переходом прозрачности (пример на рисунке ниже). 

При этом изображение при нулевой прозрачности имеет максимальную заблюренность (хоть
на прозрачном и не видно, да и на этом примере не очень понятно, но т.к. при использовании
шейдер будет применяться на переходах между фото, то там будет все четко видно) и по
мере того, как прозрачность отдельно взятых пикселей уменьшается, уменьшается и степень
блюра на них. Таким образом там, где прозрачность 100 (непрозрачный), там блюр = 0.





Прочитав несколько статей все же никак не могу разложить по полочкам это все в голове.
Прочитал как именно реализуется размытие по гауссу (усреднение значение rgb ближайших
пикселей на спрайте по каждому столбцу/линии), понимаю что к этому необходимо прицепить
прозрачность и менять одно в зависимости от другого, но, например, как это все сделать,
непосредственно в коде, прицепив еще и анимацию сюда, абсолютно не понимаю (хотя и
понимаю что есть св-во _TransVal есть параметр #pragma alpha, который за прозрачность
отвечает).

Т.к. писать шейдеры не приходилось ранее и, если честно, те статьи, что нашел в инете,
не сильно внесли ясности - именно в вопрос реализации. Cамо понимание что такое шейдер,
какие они бывают и зачем - у меня присутствует. 

Буду благодарен, если кто-нибудь возьмется поэтапно расписать на примере моей задачи
процесс реализации с пояснениями: мол нам нужно это для этого, а это для этого, вот
так мы будем изменять блюр, вот так прозрачность, а вот так анимацию присобачим.

UPDATE
Чего я смог добиться: Регулируя значения sigma и cutoff собранного мною в бреду непонимания
(в силу отсутствия опыта в написании шейдеров) происходит все то что мне необходимо
кроме одного НО, спрайт позади всего этого откуда он и как от него избавиться я так
и не смог понять. 



http://g.recordit.co/rUaGYdynQ3.gif (картинка в большем разрешении). 

Данный материал (с шейдером) применен на image. image и canvas в котором они находятся
вынесены на слой transparentfx который рендерит отдельная камера (на основной отключен
этот слой). Осталось понять как отключить (или убрать из рендера) этот спрайт позади
и в минимальном виде задача будет решена (прозрачность на краю заполнения уже не суть
важно хотя бы с блюром разобраться).

UPDATE 2 задача решается если выставить на самом Image type filled и крутить ручку
fill amount. Но остается непонятным, как по-человечески отключить рендер image'a, оставив
рендер только того, что есть. 

Результат работы шейдера:



UPDATE от 29 декабря
Поняв что с написанием по человечески у меня все не очень, я попробовал использовать
Shader Forge и в нем вроде бы добился необходимого эффекта (одного из необходимых).
Но получилось очень странно, в том смысле что в окне инспектора в юнити эффект отображался
как надо а в сцене гейм он вел себя "упрощенно" так сказать. Смотри гиф ниже для лучшего
понимания.
Гиф1, окно инспектора.
Гиф2, окно игры
    


Ответы

Ответ 1



А стоит все это через единый шейдер городить? Делал бы так: Чтобы использовать разблуреное изображение вам понадобиться еще одна камера которая через постэффект BLUR будет писать в RenderTexture экран без накладываемого изображения(поместите его в отдельный Layer и в этой камере этот слой выключите). Далее делаем анимацию проявления любой формы которая вас интересует. Cделайте форму с анимированием маски по альфе. А дальше уже все просто, для анимированной формы используем простейший материал который в зависимости от прозрачности текстуры на материале рисует через смешение либо с основной текстуры либо с размытой в RenderTexture.

Ответ 2



Ответ, часть 2. Так как возможно это будет нагрузка на шейдер, то можно некоторые части также довыносить в управляющий скрипт. Оставить тут только применение значений. В итоге шейдер и скрипт могут быть такими: 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" } Скрипт будет таким: 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().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); } } в инспекторе управление такое: И по поводу размытия...Так как мой ответ уже большой (из-за кода)...и уже вторая часть, то я приведу код шейдера Blur, который вы можете перенести в шейдеры выше. А также дописать управление размытием из скрипта по примеру выше. 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 } } } P.S. К сожалению шейдер RadialFill работает только для Sprite Mode → Single. Как сделать для мульти Multiple, я пока не знаю. P.P.S. Можно сделать еще улучшения: Вынести функцию getMatrix (матрица вращения) также в скрипт. Сделать маску не генерируемой внутри шейдера, а взять текстуру в виде картинки atan2 и применять уже её. Глядишь еще чуть снять загрузку с шейдера можно. P.P.P.S. Для попытки понимая шейдеров хоть на маленком уровне можно использовать ассет Shader Forge - Визуальный редактор для программирования шейдеров. Визуальность в данном случае очень в плюс. Из бесплатных пока поиск показывает только uShader FREE - но его я не пробовал, не знаю на сколько хорош. Ссылки к прочтению, которые использовались в шейдерах выше: Стандартные шейдерные предпроцессорные макросы Создание программ с несколькими вариантами шейдеров Директивы компиляции #pragma Culling & Depth Блендинг (Blending) Шейдеры и эффекты в Unity. Книга рецептов Unity 5.x Shaders and Effects Cookbook

Ответ 3



Ух ты, интересный вопрос и даже награаадааа! Вообще, сколько я не читал, я не смог представить зачем эффект размытия применять на прозрачности. Ведь если некая часть спрайта будет прозрачна, то размытия там итак не будет видно и заметно, а там где не прозрачно, то и размытия нет. Либо я чего не понял и имелось виду применение размытия вообще на весь спрайт, не зависимо от того, какой процент радиального заполнения сейчас имеется. Если именно так, то Можно поступить так, как написано в другом ответе: взять из стандартных ассетов Юнити (благо их не мало предоставляется) эффект размытия, применяемый на камере. Добавить еще одну камеру, добавить туда скрипт размытия и шейдер и в нужный момент включать ту камеру и изменять смещение в Blur-эффекте. Реализовать, как и хотели в шейдере)) Об этом в самом конце. (!!!) Да простят меня админы сайта, но в один ответ у меня не вместится (из-за количество кода, а не из-за "воды"). Поэтому ответ будет в двух частях. Ответ, часть 1. Суть, что обрезки, что заливания прозрачностью будет сводиться к тому, что будет браться маска, на основе которой всё будет происходить. Маска генерируется программно через тригонометрическую функцию atan2. На осях она выглядит так: В двумерной системе координат выглядит так: Так как маска — это некая компонента, состоящая из оттенков черного и белого, в которой черный цвет — полностью отсутствие текстуры, а белый — полностью видимая текстура, то для прозрачности маска atan2 будет представлять из себя переход от черного к белому (см. рисунок выше), а для обрезки будет применена дополнительная функция, чтоб было только черное/белое, без плавных переходов. Я попробую просто опубликовать шейдер, в котором будут комментарии того, что сделано. Не уверен, что все будет понятно, но я хоть как-то худо бедно постараюсь. 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().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; } } где: cutoffStartAngle — начальный угол обрезки, opacityStartAngle — начальный угол прозрачности. Эти параметры для того, чтобы немного отрегулировать по вкусу площадь сектора, занимаемого прозрачностью. Замечу, что прозрачность изменяется от -360 до 360, потому что первый круг она проворачивается сама по себе, а второй круг — плавно "заходит" за текстуру. deltaAngle - дельта, на которую проворачиваются маски. Что скрипт делает? При нажатии нажатии на клавишу мыши он берет шейдер у спрайта (точнее с его материала), устанавливает изначальные углы, в цикле изменяет угол поворота и передает это значение в шейдер, чтобы он там уже у себя применил значения в frag. Выглядит в инспекторе так: Opacity Rotator - вращение маски прозрачности Texture Rotator - вращение маски обрезки Fill Clockwise - по часовой стрелке или против Fill Origin - с какой стороны начинать (справа/слева/сверху/снизу) Итог будет выглядеть примерно таким: Увы на данной гифке не получается передать то, как это выглядит в правильности Улучшение Так как у секции 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" }

четверг, 11 июля 2019 г.

Изменение цвета спрайта на более светлый в Unity 3D

Как можно высветлить спрайт, если его цвет в Sprite Rendrer и так самый белый? Пробовал менять шейдер на какой-нибудь другой, но из стандартных шейдеров получится сделать только полностью белым, а мне так не надо.


Ответ

Стандартные шейдеры на то и стандартные, что не всегда помогают.
Поэтому для разных задач можно пробовать писать свой на основе этих самых стандартных (переделывая их)
Не знаю, какие есть варианты решений данной задачи помимо шейдеров, но если решать её именно с помощью шейдеров, то вот данный код (более менее гибкий) может несколько помочь:
Shader "Custom/MyShader" { Properties { [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} _Color("Tint", Color) = (1, 1, 1, 1) _ShineLocation("ShineLocation", Range(0, 1)) = 0 _ShineWidth("ShineWidth", Range(0, 1)) = 0 [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 }
SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" }
Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ PIXELSNAP_ON #include "UnityCG.cginc"
struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; };
struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; };
fixed4 _Color;
v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; #ifdef PIXELSNAP_ON OUT.vertex = UnityPixelSnap(OUT.vertex); #endif
return OUT; }
sampler2D _MainTex; sampler2D _AlphaTex; float _AlphaSplitEnabled; float _ShineLocation; float _ShineWidth;
fixed4 SampleSpriteTexture(float2 uv) { fixed4 color = tex2D(_MainTex, uv);
#if UNITY_TEXTURE_ALPHASPLIT_ALLOWED if (_AlphaSplitEnabled) color.a = tex2D(_AlphaTex, uv).r; #endif //UNITY_TEXTURE_ALPHASPLIT_ALLOWED

float lowLevel = _ShineLocation - _ShineWidth; float highLevel = _ShineLocation + _ShineWidth; float currentDistanceProjection = (uv.x + uv.y) / 2; if (currentDistanceProjection > lowLevel && currentDistanceProjection < highLevel) { float whitePower = 1 - (abs(currentDistanceProjection - _ShineLocation) / _ShineWidth); color.rgb += color.a * whitePower; }
return color; }
fixed4 frag(v2f IN) : SV_Target { fixed4 c = SampleSpriteTexture(IN.texcoord) * IN.color; c.rgb *= c.a;
return c; } ENDCG } } }
Пробовал в Unity 4.6
Или еще:
Shader "Custom/MyShader" { Properties { [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} _Color("Tint", Color) = (1, 1, 1, 1) [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 }
SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" }
Cull Off Lighting Off ZWrite Off Fog{ Mode Off } Blend SrcAlpha OneMinusSrcAlpha
Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile DUMMY PIXELSNAP_ON #include "UnityCG.cginc"
struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; };
struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; half2 texcoord : TEXCOORD0; };
fixed4 _Color;
v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color; #ifdef PIXELSNAP_ON OUT.vertex = UnityPixelSnap(OUT.vertex); #endif
return OUT; }
sampler2D _MainTex;
fixed4 frag(v2f IN) : COLOR { fixed4 c = tex2D(_MainTex, IN.texcoord); c.a *= IN.color.a; c.rgb = lerp(c.rgb, step(0.5, IN.color.rgb), abs(IN.color.rgb - 0.5) * 2); return c * _Color; } ENDCG } } }
При данном шейдере дефолтный цвет спрайта (в SpriteRenderer -> Color) должен быть не белый, а, допустим с параметрами rgba(128,128,128,255)

среда, 5 декабря 2018 г.

Проблема с освещением в шейдере opengl es 2.0

Имеется ландшафт который я хочу осветить. Собственно везде освещение нормальное, а ближе к центру огромное белое пятно! Я пока не пойму из за чего это пятно, и как это исправить. Нужна помощь. Мне нужно понять из за чего это белое пятно выходит, и понять как его исправить?
Вот скриншет со смартфона samsung galaxy S4

Теперь покажу как на эмуляторе andy

Теперь покажу как распологаются нормали

Вот код фрагментного шейдера:
precision mediump float; varying vec3 vPosition; uniform sampler2D uTexture; uniform sampler2D uTexture2; uniform vec3 u_camera; varying vec2 vTexcoord; varying vec3 v_normal;
float getLight(vec3 lightPos,vec3 camPos,vec3 vertexPos, vec3 normal, float ambient, float k_diffuse, float k_specular) { vec3 n_normal=normalize(normal); vec3 lightvector = normalize(lightPos - vertexPos); vec3 lookvector = normalize(camPos - vertexPos); float diffuse = k_diffuse * max(dot(n_normal, lightvector), 0.0); vec3 reflectvector = reflect(-lightvector, n_normal); float specular = k_specular * pow( max(dot(lookvector,reflectvector),0.0), 60.0 ); return ambient+diffuse+specular; }
void main() { vec3 lightPos = vec3(60.0,100.0,-415.0); float light = getLight(lightPos,u_camera,vPosition,v_normal, 0.5, 0.9, 0.3);
vec4 pixsel; vec3 pos = normalize(vPosition); if (pos.y > 0.01) pixsel = texture2D(uTexture2,vTexcoord); else pixsel = texture2D(uTexture,vTexcoord);
gl_FragColor = light * pixsel; }


Ответ

Проблема решена, на самом деле не верно передавались координаты камеры.

четверг, 15 ноября 2018 г.

Шейдеры в unity3d

Рискуя нахватать по шапке, я все таки задам этот вопрос.
Встала необходимость написать шейдер для Unity3d, казалось бы простейший, радиально проявляющееся изображение (как на gif ниже), но с эффектами блюра и градиентной прозрачности. То есть кусок сегмента (например градусов 30), который заполняет изображение - не резко видимый, а с плавным переходом прозрачности (пример на рисунке ниже).
При этом изображение при нулевой прозрачности имеет максимальную заблюренность (хоть на прозрачном и не видно, да и на этом примере не очень понятно, но т.к. при использовании шейдер будет применяться на переходах между фото, то там будет все четко видно) и по мере того, как прозрачность отдельно взятых пикселей уменьшается, уменьшается и степень блюра на них. Таким образом там, где прозрачность 100 (непрозрачный), там блюр = 0.


Прочитав несколько статей все же никак не могу разложить по полочкам это все в голове. Прочитал как именно реализуется размытие по гауссу (усреднение значение rgb ближайших пикселей на спрайте по каждому столбцу/линии), понимаю что к этому необходимо прицепить прозрачность и менять одно в зависимости от другого, но, например, как это все сделать, непосредственно в коде, прицепив еще и анимацию сюда, абсолютно не понимаю (хотя и понимаю что есть св-во _TransVal есть параметр #pragma alpha, который за прозрачность отвечает).
Т.к. писать шейдеры не приходилось ранее и, если честно, те статьи, что нашел в инете, не сильно внесли ясности - именно в вопрос реализации. Cамо понимание что такое шейдер, какие они бывают и зачем - у меня присутствует.
Буду благодарен, если кто-нибудь возьмется поэтапно расписать на примере моей задачи процесс реализации с пояснениями: мол нам нужно это для этого, а это для этого, вот так мы будем изменять блюр, вот так прозрачность, а вот так анимацию присобачим.
UPDATE Чего я смог добиться: Регулируя значения sigma и cutoff собранного мною в бреду непонимания (в силу отсутствия опыта в написании шейдеров) происходит все то что мне необходимо кроме одного НО, спрайт позади всего этого откуда он и как от него избавиться я так и не смог понять.

http://g.recordit.co/rUaGYdynQ3.gif (картинка в большем разрешении).
Данный материал (с шейдером) применен на image. image и canvas в котором они находятся вынесены на слой transparentfx который рендерит отдельная камера (на основной отключен этот слой). Осталось понять как отключить (или убрать из рендера) этот спрайт позади и в минимальном виде задача будет решена (прозрачность на краю заполнения уже не суть важно хотя бы с блюром разобраться).
UPDATE 2 задача решается если выставить на самом Image type filled и крутить ручку fill amount. Но остается непонятным, как по-человечески отключить рендер image'a, оставив рендер только того, что есть.
Результат работы шейдера:

UPDATE от 29 декабря Поняв что с написанием по человечески у меня все не очень, я попробовал использовать Shader Forge и в нем вроде бы добился необходимого эффекта (одного из необходимых). Но получилось очень странно, в том смысле что в окне инспектора в юнити эффект отображался как надо а в сцене гейм он вел себя "упрощенно" так сказать. Смотри гиф ниже для лучшего понимания. Гиф1, окно инспектора. Гиф2, окно игры


Ответ

А стоит все это через единый шейдер городить? Делал бы так:
Чтобы использовать разблуреное изображение вам понадобиться еще одна камера которая через постэффект BLUR будет писать в RenderTexture экран без накладываемого изображения(поместите его в отдельный Layer и в этой камере этот слой выключите). Далее делаем анимацию проявления любой формы которая вас интересует. Cделайте форму с анимированием маски по альфе. А дальше уже все просто, для анимированной формы используем простейший материал который в зависимости от прозрачности текстуры на материале рисует через смешение либо с основной текстуры либо с размытой в RenderTexture.