Pull to refresh

Comments 27

Спасибо! Очень редко можно встретить статьи подобного качества. Обычно накопипастят из разных мест, соберут в кучу, порадуются что работает и бегом на хабр. Здесь же все по делу и в мелочах.
Добавлю только
t0, t1, t2 можно считать на CPU раз за кадр для каждого щита в скрипте. Тем самым можно убрать 3 saturate и кучу вычислений.

Спорный момент. Переменные вычисляются в вершинном шейдере (а значит не так чтобы часто), и на GPU, который зачастую ждет CPU, так что не факт что перенос вычислений в скрипт даст прирост. Вычисления-то относительно несложные.
Согласен, такие оптимизации нужно производить только после профилирования, есть GPU, которые не очень хорошо переживают saturate и есть сценарии, когда CPU и так не сильно занят — поставить несколько uniform'ов в кадр может. А бывает и наоборот. С шейдерами, к сожалению, очень редко можно дать однозначный совет по оптимизации.
Единственное что могу добавить — _HitDuration и _MaxDistance лучше слать в виде 1/_HitDuration и 1/_MaxDistance, посчитав один раз на CPU — тогда можно будет умножать вместо деления, что быстрее на всех GPU.
UFO just landed and posted this here
Лучше ипользовать стандартный 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 — визуально, конечно, особой роли нет, хоть и заметно, но математически квадрат значения, меньшего единицы — уменьшает результат, что собой являет искажение, вроде не видно — но комочек в горле есть.
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 есть отдельные инструкции, так что можно расслабиться. А то мне кажется, так с ума можно сойти, оптимизируя то, что совсем не тормозит.
Либо профилирую сам, либо гуглю результаты других. Полагаться на то, что какая-то функция реализовано железно не всегда правильно, особенно, если рассматривать мобильные устройства. В общем случае если я могу обойтись без функций, которые наивно реализуются через бранчинг, я стараюсь без них обходится.
В функциях есть смысл из-за разработки под разные платформы, + не все шарят математику достаточно хорошо, что бы писать более оптимальные решения. К сожалению не все понимают, что тот же abs, например, реализован на бинарных операциях, а не с помощью if )))
Здесь v2f — возвращаемое значение вершинных шейдеров интерполированное для данного пикселя на экране

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};


uv — текстурная координата пикселя
vertext — координата пикселя в экранных координатах

«Пиксель пиксельный». Такие формулировки приводят в заблуждение начинающих шейдер-мейкеров, они потом разобраться не могут, что же все таки пиксель, и кто с ним работает. uv это координаты текселя, по которым берется семпл. А то что у вас названо vertex'ом (вершина) — имя не подходящее, так как это не вершина, а координаты «фрагмента» в пространстве клипера. Фрагмент — не пиксель, а претиндент на место пикселя (собственно поетому шейдер и «фрагментный»), из которого ему дорога в NDC, и воевать за место на экране в операциях блендинга с его соседями.

Названо не у меня, а у Unity, переименовывать я не стал.


uv это координаты текселя

Для данного пикселя/фрагмента, вычисляемая из uv-координат присвоенных вершинам, по которым может взяться, а может и не взяться, семпл из текстуры.


Уж, простите, но различие между фрагментом и пикселем я в этом контексте считаю буквоедством.

Не, не, не. Не хотел придираться или обидеть, не поймите не правильно, просто вы говорите в том месте о разных понятиях, а обозвали по одинаковому, еще и тем, чем они не являются. Сам, когда изучал когда-то эти дебри, голову сломал на терминалогии. Сейчас преподаю детям в школе, и у них от этого тоже крыша едет. Так ребенок послушает, и покивает, даже если не поймет, а взролый, подростки, докапываються «как так-то, и то пиксель, и это пиксель?!», оно и понятно, если в документации даже не придерживаются этого, хотя опять же, кто назвал документацию эталоном. Прекрасно понимаю, что это не столь фажно в кругу специалистов, где все шарят, но статья ведь образовательная, для тех кто не силен в этих вопросах. Еще рас прошу прощение, за занудство. Даже наоборот, вы вдохновили меня все таки попробовать написать статью самому, а не копать под чужими)))
Добавьте ссылку на Cg Tutorial (7.4) с формулой.
I — направление на камеру в мировых координатах — можно посчитать, как разницу мировых координат камеры и мировых координат вершины.
float3 I = normalize(worldVertex - _WorldSpaceCameraPos.xyz);
Такая формулировка противоречит формуле, ведь I это вектор от камеры к вершине.

И вопрос. В следующем контексте normalize() выполняется над float4 или float3?
float4 a;
float3 b;
float3 c = normalize(a - b);
По первому пункту — спасибо, исправил, вектор считался верно, но словесное описание неверное.

По второму вопросу, я не знаю, но я думаю что для float4, а потом приведется к float3

Вы молодец, но сам спецэффект выглядит не очень. Возможно в другом масштабе хорошо смотрится. Игроки нынче избалованны эффектами в ааа играх 8)

На вкус и цвет все фломастеры разные )
unity3d.com/ru/public-relations/brand

> Ссылаясь на нашу компанию, используйте “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.

Articles