Comments 27
Спасибо! Очень редко можно встретить статьи подобного качества. Обычно накопипастят из разных мест, соберут в кучу, порадуются что работает и бегом на хабр. Здесь же все по делу и в мелочах.
Добавлю только
Спорный момент. Переменные вычисляются в вершинном шейдере (а значит не так чтобы часто), и на GPU, который зачастую ждет CPU, так что не факт что перенос вычислений в скрипт даст прирост. Вычисления-то относительно несложные.
t0, t1, t2 можно считать на CPU раз за кадр для каждого щита в скрипте. Тем самым можно убрать 3 saturate и кучу вычислений.
Спорный момент. Переменные вычисляются в вершинном шейдере (а значит не так чтобы часто), и на GPU, который зачастую ждет CPU, так что не факт что перенос вычислений в скрипт даст прирост. Вычисления-то относительно несложные.
Согласен, такие оптимизации нужно производить только после профилирования, есть GPU, которые не очень хорошо переживают saturate и есть сценарии, когда CPU и так не сильно занят — поставить несколько uniform'ов в кадр может. А бывает и наоборот. С шейдерами, к сожалению, очень редко можно дать однозначный совет по оптимизации.
Лучше ипользовать стандартный Rim эффект а не Френеля, тот же визуальный эфект, но проще вычесления. Также есть встроенный метод для получени направления в камеру от вершины из пространства объекта ObjSpaceViewDir. С учетом doubleSided, я использую нечто подобное:

Shader "CS/Shield"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Tint ("Tint color", Color) = (1,1,1,1)
_Width ("Width", Range(0.0, 1.0)) = 0.7
_HdrPower ("HDR Power", Float) = 5.0
_DoubleSided ("Double Sided", Float) = 0.0
}
SubShader
{
Tags
{
"RenderType"="Transparent"
"Queue"="Transparent+100"
}
LOD 100
Blend One One
Cull [_DoubleSided]
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
fixed4 rim : TEXCOORD1;
};
sampler2D _MainTex;
fixed4 _MainTex_ST;
fixed4 _Tint;
fixed _Width;
fixed _HdrPower;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// rim
fixed3 viewDir =
normalize(ObjSpaceViewDir(v.vertex));
fixed dotRes =
1.0 - abs(dot(viewDir, v.normal));
o.rim = _Tint * _HdrPower *
smoothstep(1.0 - _Width, 1.0, dotRes);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col * i.rim;
}
ENDCG
}
}
}

Это та же самая формула, которую использую я, только в локальных координатах объекта, значения в мировых координатах мне всё равно нужны в расчетах попадания, поэтому лишних вычислений не производится, за исключением вычисления нормали поверхности в мировых координатах.
Я так же заменил abs на возведение скалярного произведения в квадрат.
Я так же заменил abs на возведение скалярного произведения в квадрат.
Согласен с вами, но для меня дело в употреблении метода возведения в степень, тяжело шейдерам со степенями.
Квадрат или abs — визуально, конечно, особой роли нет, хоть и заметно, но математически квадрат значения, меньшего единицы — уменьшает результат, что собой являет искажение, вроде не видно — но комочек в горле есть.
1. abs(-0.5) => 0.5
2. -0.5 * (-0.5) => 0.25
3. -0.1 * (-0.1) => 0.01
В любом случае статья хорошая, Спасибо!
Кстати, примени вы блендинг один к одному, получите боле сочный энергетичный еффект)))
Квадрат или abs — визуально, конечно, особой роли нет, хоть и заметно, но математически квадрат значения, меньшего единицы — уменьшает результат, что собой являет искажение, вроде не видно — но комочек в горле есть.
1. abs(-0.5) => 0.5
2. -0.5 * (-0.5) => 0.25
3. -0.1 * (-0.1) => 0.01
В любом случае статья хорошая, Спасибо!
Кстати, примени вы блендинг один к одному, получите боле сочный энергетичный еффект)))
один-к-одному это что? ONE, ONE Имеете ввиду сложение цветов?
Действительно, в изначальном варианте я убрал pow, так как всё равно всегда использовал _Power=1.0f, в варианте для статьи я решил не отходить от формулы из cg tutorial, а в возможные оптимизации вписать забыл.
А где вы берете информацию о том, какая функция быстрее/медленнее? Стоит ли вообще обращать внимание на такие вещи в шейдере? Бранчинг в шейдере стоит дорого, но насколько я знаю, для abs есть отдельные инструкции, так что можно расслабиться. А то мне кажется, так с ума можно сойти, оптимизируя то, что совсем не тормозит.
Либо профилирую сам, либо гуглю результаты других. Полагаться на то, что какая-то функция реализовано железно не всегда правильно, особенно, если рассматривать мобильные устройства. В общем случае если я могу обойтись без функций, которые наивно реализуются через бранчинг, я стараюсь без них обходится.
Здесь v2f — возвращаемое значение вершинных шейдеров интерполированное для данного пикселя на экране
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
uv — текстурная координата пикселя
vertext — координата пикселя в экранных координатах
«Пиксель пиксельный». Такие формулировки приводят в заблуждение начинающих шейдер-мейкеров, они потом разобраться не могут, что же все таки пиксель, и кто с ним работает. uv это координаты текселя, по которым берется семпл. А то что у вас названо vertex'ом (вершина) — имя не подходящее, так как это не вершина, а координаты «фрагмента» в пространстве клипера. Фрагмент — не пиксель, а претиндент на место пикселя (собственно поетому шейдер и «фрагментный»), из которого ему дорога в NDC, и воевать за место на экране в операциях блендинга с его соседями.
Названо не у меня, а у Unity, переименовывать я не стал.
uv это координаты текселя
Для данного пикселя/фрагмента, вычисляемая из uv-координат присвоенных вершинам, по которым может взяться, а может и не взяться, семпл из текстуры.
Уж, простите, но различие между фрагментом и пикселем я в этом контексте считаю буквоедством.
Не, не, не. Не хотел придираться или обидеть, не поймите не правильно, просто вы говорите в том месте о разных понятиях, а обозвали по одинаковому, еще и тем, чем они не являются. Сам, когда изучал когда-то эти дебри, голову сломал на терминалогии. Сейчас преподаю детям в школе, и у них от этого тоже крыша едет. Так ребенок послушает, и покивает, даже если не поймет, а взролый, подростки, докапываються «как так-то, и то пиксель, и это пиксель?!», оно и понятно, если в документации даже не придерживаются этого, хотя опять же, кто назвал документацию эталоном. Прекрасно понимаю, что это не столь фажно в кругу специалистов, где все шарят, но статья ведь образовательная, для тех кто не силен в этих вопросах. Еще рас прошу прощение, за занудство. Даже наоборот, вы вдохновили меня все таки попробовать написать статью самому, а не копать под чужими)))
Добавьте ссылку на Cg Tutorial (7.4) с формулой.
И вопрос. В следующем контексте normalize() выполняется над float4 или float3?
I — направление на камеру в мировых координатах — можно посчитать, как разницу мировых координат камеры и мировых координат вершины.Такая формулировка противоречит формуле, ведь I это вектор от камеры к вершине.
float3 I = normalize(worldVertex - _WorldSpaceCameraPos.xyz);
И вопрос. В следующем контексте normalize() выполняется над float4 или float3?
float4 a;
float3 b;
float3 c = normalize(a - b);
По первому пункту — спасибо, исправил, вектор считался верно, но словесное описание неверное.
По второму вопросу, я не знаю, но я думаю что для float4, а потом приведется к float3
По второму вопросу, я не знаю, но я думаю что для float4, а потом приведется к float3
и вот еще, моя самая любимая ссылочка )
Вы молодец, но сам спецэффект выглядит не очень. Возможно в другом масштабе хорошо смотрится. Игроки нынче избалованны эффектами в ааа играх 8)
unity3d.com/ru/public-relations/brand
> Ссылаясь на нашу компанию, используйте “Unity Technologies.” Ссылаясь на движок Unity, пишите “Unity®” или “Unity®Pro” (не Unity3d)
> Ссылаясь на нашу компанию, используйте “Unity Technologies.” Ссылаясь на движок Unity, пишите “Unity®” или “Unity®Pro” (не Unity3d)
Спасибо! Очень полезная и занимательная статья.
Кстати, что в шейдерах с возможным делением на ноль?
Например, здесь:
Кстати, что в шейдерах с возможным делением на ноль?
Например, здесь:
float hitIntensity = (1 - t0) * ((1 / (d0)) - 1) +
(1 - t1) * ((1 / (d1)) - 1) +
(1 - t2) * ((1 / (d2)) - 1);
Результат деления на ноль при операциях с плавующей точкой зависит от конкретного целевого API(directx, opengl, еtc.). В большинстве случаев поведение не определено, но есть гарантия отсутствия креша. Учитывая, что получить дистанцию ровно 0.0f не очень просто и эффект этого будет мало заметен я не стал заморачиваться.
Sign up to leave a comment.
Анимированный эффект щита космического корабля в Unity3D