PDA

Просмотр полной версии : Как написать Swirl на AGAL?


Bletraut
26.12.2015, 01:11
Собственно понять не могу как это сделать, как написать эффект закручивания на AGAL, в других-то языках всё понятно, имеем позицию пикселя и её двигаем. Но в агал нет позиции пикселя, как же быть? Прошу поделиться примерами и формулами если таковые имеются.

Tails
26.12.2015, 15:43
Можно получить координаты пикселя в формате от 0 до 1. (float) И уже из этого посчитать конкретный пиксель, из заранее переданных констант, ширина экрана или ширина области эффекта.

Bletraut
26.12.2015, 18:20
Можно получить координаты пикселя в формате от 0 до 1.

Это как?

Tails
26.12.2015, 19:19
Рекомендую эту статью: http://demiart.ru/forum/lofi/index.php/t190499.html (На вес золота)
Ещё эту: http://habrahabr.ru/post/130454/
И справочник: http://help.adobe.com/ru_RU/as3/dev/WSd6a006f2eb1dc31e-310b95831324724ec56-8000.html

Теперь, когда обложились материалом, начать читать и изучать:
1. Сперва выполняется вершинный шейдер для каждого полигона, составляющего меш. В него передаются координаты, матрица.
2. Затем запускается пиксельная программа (шейдер) для каждого пиксела на экране, которые занимает меш.

В пиксельный шейдер, через промежуточные регистры v0..v7 из вершинного шейдера передаются интерполированные координаты. По этим координатам, вы можете, например, сделать выборку цвета пиксела из текстуры. Координаты выглядят таким образом, что крайний левый пиксел полигона равен нулю, а крайний правый пиксел будет равен единице по оси x, для y аналогично (В системе координат полигона, а не экрана). То-есть, вы получаете координаты пиксела на полигоне в формате 0-1.

Если ваш меш представляет собою всего 2 треугольника (плоскость, Quad) растянутый по ширине и высоте экрана, то вы де факто получите в пиксельном шейдере координаты от 0 до 1, соответствующие крайней левой и крайней правой стороне экрана по оси x. Соответственно, если вы передадите в свой шейдер константами текущее разрешение экрана, то внутри пиксельного шейдера сможете получить текущий, конкретный пиксель: screenWidth * v0.x

Как-то так оно работало. Где-то я мог чуть приврать, сам уже больше двух лет не работал с agal.
Очень рекомендую почитать статью по первой ссылке, всё просто и понятно объясняется.

Bletraut
26.12.2015, 19:24
Я знаю и понимаю содержание всех этих статей. Я не понимаю как я могу получить координаты любого пикселя, имея только четере вершины и соответственно 4 UV координаты. В пиксельные шейдер передаются UV из буффера, но там только 4 точки! Откуда он берет координаты остальных?

Добавлено через 3 минуты
Когда я писал шейдер тумана, то я брал координаты вершин и по ним вычислял цвет тумана. Но как мне закрутить пиксели если у меня только 4 вершины? А мне нужно сделать из них спираль...

Добавлено через 5 минут
то внутри пиксельного шейдера сможете получить текущий, конкретный пиксель: screenWidth * v0.x

А разве я не получу координату одной из 4 вершин, умноженную на ширину экрана?

Bletraut
26.12.2015, 19:39
Вот код моего шейдера, преобразовались только вершины.
// Sin sistortion
vertexCode = "mov vt0, va0\n";
vertexCode += "mul vt1.x, vt0.x, vc4.x\n"; // v.x * stageWidth
vertexCode += "sin vt1.x, vt1.x\n"; // sin(v.x * stageWidth)
vertexCode += "add vt0.y, vt0.y, vt1.x\n"; // v.y + sin(v.x * stageWidth)
vertexCode += "mov op, vt0\n";

Tails
26.12.2015, 19:53
Забудем про экран, в шейдере есть понятие полигон.
Если надо, чтобы эффект был на весь экран, просто растягиваем полигон на весь экран.
В пиксельном шейдере мы рисуем на полигоне, а не на экране, соответственно и координаты у нас в системе полигона, относительные (0-1), а не конкретные (0px-1280px).

У нас есть текущие x,y и текстура, которую нужно "закрутить".
Задача в том, чтоб вывести пикселы из текстуры, с определённым смещением.

В обычном выводе мы просто пишем:
(Синтаксис утрирован)
буфер = текстура(v0.xy);

А надо:
буфер = текстура(спираль(v0.xy, strength));

То-есть, задача сводится к тому, что-бы просто написать формулу этой самой спирали, которая будет принимать x, y, strength и возвращать новые x, y. Повторю, диапазон значений xy от нуля до единицы.

Допустим, для волны мы бы могли использовать синус:
function(x:Number, y:Number, strength:Number):Point {
return new Point(Math.sin(x * strength, y)); // Вертикальная волна с амплитудой длиною в текстуру.
}

Я специально написал на as3, чтоб понятнее было. Сперва создаёте формулу, отлаживаете, а потом переносите на agal.
При этом, знать о том, что там с экраном, какое у него разрешение и т.п. пиксельной программе не нужно.

Bletraut
26.12.2015, 19:55
Тоже самое и при шейдере
vertexCode += "mov op, va0\n";

// Sin sistortion
vertexCode += "mov vt0, va0\n";
vertexCode += "mul vt1.x, vt0.x, vc4.x\n"; // v.x * stageWidth
vertexCode += "sin vt1.x, vt1.x\n"; // sin(v.x * stageWidth)
vertexCode += "mov vt2, va1\n"; // vt0 = uv
vertexCode += "add vt2.y, vt2.y, vt1.x\n"; // u = u + sin(v.x * stageWidth)

vertexCode += "mov v0, vt2\n"; // uv

fragmentCode = "tex ft0, v0, fs0 <2d,nearest,clamp>\n";
fragmentCode += "mov oc, ft0\n";

Bletraut
26.12.2015, 19:57
Если надо, чтобы эффект был на весь экран, просто растягиваем полигон на весь экран.

Это и есть полигон на весь экран, это пост-процессинг отрендеренной 3д сцены.

Tails
26.12.2015, 19:58
Оставь в покое вершинный шейдер.
Рисуем в пиксельном, читай выше что написано.

Bletraut
26.12.2015, 19:58
У нас есть текущие x,y и текстура, которую нужно "закрутить".

Откуда у нас текущие? У нас же есть только координаты вершин и всё, разве нет?

Добавлено через 54 секунды
Оставь в покое вершинный шейдер.
Рисуем в пиксельном, читай выше что написано.

Я и рисовал в нем, я отправил в него измененные UV, комментарии в шейдере есть. Эффект тот же самый, изменились только угловые UV.

Tails
26.12.2015, 20:04
Ты пытаешься крутить меш, изменяя его вершины, надо "крутить" x,y, передаваемые в:
fragmentCode = "tex ft0, v0, fs0 <2d,nearest,clamp>\n";
Как ещё объяснить? :)

Добавлено через 4 минуты
Оставь вершинный код базовым, какой есть.

Добавлено через 4 минуты
И крути в пиксельном.

Добавлено через 6 минут
Знать о вершинах и разрешений экрана не нужно, нужны только обычные uv в v0 и сила закручивания, передаваемая константой.

Bletraut
26.12.2015, 20:11
function(x:Number, y:Number, strength:Number):Point {
return new Point(Math.sin(x * strength, y)); // Вертикальная волна с амплитудой длиною в текстуру.
}

По твоей формуле написал шейдер
name = "screen";

vertexCode += "mov op, va0\n";
vertexCode += "mov v0, va1\n"; // uv

// distortion
vertexCode += "mov v1, vc0\n"; // stageWidth, stageHeight

fragmentCode = "mov ft1, v0\n"; // uv
fragmentCode += "mul ft2.x, ft1.x, v1.x\n"; // u * stageWidth
fragmentCode += "sin ft2.x, ft2.x\n"; // sin(u * stageWidth)
fragmentCode += "add ft1.x, ft1.x, ft2.x\n"; // u = sin(u * stageWidth)

fragmentCode += "tex ft0, ft1, fs0 <2d,nearest,clamp>\n";
fragmentCode += "mov oc, ft0\n";

Результат

Tails
26.12.2015, 20:12
Ладно, так и быть, счас я схожу за чашкой кофе...

Bletraut
26.12.2015, 20:16
Готовый пример синусоидального дисторшена решил бы все мои проблемы. Хоть убейте, но понять не могу. Результаты соответствующие.

Tails
26.12.2015, 20:30
Опробуй, синусоидальный дисторшен:
package game.view.filters
{
import starling.textures.Texture;
import flash.display3D.Context3D;
import flash.display3D.Program3D;
import starling.animation.IAnimatable;
import starling.filters.FragmentFilter;
import flash.display3D.Context3DProgramType;


/**
* Шейдер волнового деформирования изображения.
* @author Tails
*/
public class Wave extends FragmentFilter{

// Приват
private var _programWave:Program3D; // Шейдер отрисовки
private var _programNormal:Program3D; // Обычный шейдер
private var _parameters:Vector.<Number>; // Вектор для передачи параметров фильтра


/**
* Создать фильтр волнового деформирования.
*/
public function Wave() {

/**
* [0] - Количество волн.
* [1] - Ширина волны по горизонтали.
* [2] - Y смещение
* [3] - 0
*/
_parameters = new <Number>[50, .1, 3.14, 0];
_parameters.fixed = true;
}


// ГЕТТЕР-СЕТТЕРЫ
/// Количество волн.
public function get waveNum():Number {
return _parameters[0];
}
public function set waveNum(value:Number):void {
_parameters[0] = value;
}
/// Ширина волны по горизонтали.
public function get waveSize():Number {
return _parameters[1];
}
public function set waveSize(value:Number):void {
_parameters[1] = value;
}
/// Отступ волны сверху, где полный цикл = 2PI
public function get waveStep():Number {
return _parameters[2];
}
public function set waveStep(value:Number):void {
_parameters[2] = value;
}


// ОПРЕДЕЛЕНИЕ
override protected function activate(pass:int, context:Context3D, texture:Texture):void {
if(_parameters[1] > 0){
context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _parameters);
context.setProgram(_programWave);
}else {
context.setProgram(_programNormal);
}
}
protected override function createPrograms():void {

var fragmentProgramCode:String =
// Перенос координат в переменную + смещение для синуса
"mov ft0, v0 \n" +

// Количество волн по вертикали:
"mul ft0.x, ft0.y, fc.x \n" +
"add ft0.x, ft0.x, fc.z \n" +
"sin ft0.x, ft0.x \n" +

// Уменьшаем велечину волны по горизонтали:
"mul ft0.x, ft0.x, fc0.y \n" +
"add ft0.x, v0.x, ft0.x \n" +
"tex ft0, ft0, fs0 <2d, clamp, linear, mipnone> \n" +
"mov oc, ft0 \n";

_programWave = assembleAgal(fragmentProgramCode, STD_VERTEX_SHADER);
_programNormal = assembleAgal(STD_FRAGMENT_SHADER, STD_VERTEX_SHADER);
}


// УДАЛЕНИЕ
public override function dispose():void{
_programNormal.dispose();
_programWave.dispose();
super.dispose();
}
}
}

Bletraut
26.12.2015, 20:40
Спасибо! Работает! Сейчас буду изучать.

Tails
26.12.2015, 20:41
Класс! А я думал не запустится...

Bletraut
26.12.2015, 20:50
А можешь формулу на ас3 написать?

Tails
26.12.2015, 20:51
Какую?

Bletraut
26.12.2015, 20:54
Дисторшена, но я уже сам по шейдеру составил. Изучаю сижу.

Tails
26.12.2015, 21:16
http://www.geeks3d.com/20110428/shader-library-swirl-post-processing-filter-in-glsl/

Bletraut
27.12.2015, 14:20
Собственно вот, написал Swirl-shader. Результат можно посмотреть на флешке.

Эффект перемещается за курсором.
Управление персонажа на стрелки.

Aurorasmemories.swf

Код шейдера:
1) Во фрагментный шейдер посылаются 2 константы, первая содержит в себе позицию эффекта, радиус и угол закручивания. Вторая константа содержит в себе ширину и высоту экрана и необходимые константы.
vertexCode += "mov op, va0\n";
vertexCode += "mov v0, va1\n"; // uv

fragmentCode = "mov ft0, v0\n"; // uv

// ---- SWIRL ----
// fc0 = [x = 300, y = 300, radius = 400, angle = 0.8]
// fc1 = [stageWidth, stageHeight, 8.0, 0]
fragmentCode += "mul ft0.x, ft0.x, fc1.x\n"; // tc.x = tc.x * stageWidth
fragmentCode += "mul ft0.y, ft0.y, fc1.y\n"; // tc.y = tc.y * stageHeight
fragmentCode += "mov ft0.z, fc1.w\n"; // tc.z = 0
fragmentCode += "sub ft0.x, ft0.x, fc0.x\n"; // dx = tc.x - center.x
fragmentCode += "sub ft0.y, ft0.y, fc0.y\n"; // dy = tc.y - center.y
fragmentCode += "mul ft1.x, ft0.x, ft0.x\n"; // dx * dx
fragmentCode += "mul ft1.y, ft0.y, ft0.y\n"; // dy * dy
fragmentCode += "add ft1.x, ft1.x, ft1.y\n"; // dx * dx + dy * dy
fragmentCode += "sqt ft1.x, ft1.x\n"; // dist = sqrt(dx * dx + dy * dy)
fragmentCode += "slt ft1.w, ft1.x, fc0.z\n"; // if (dist < radius)
fragmentCode += "sub ft1.y, fc0.z, ft1.x\n"; // (radius - dist)
fragmentCode += "div ft1.y, ft1.y, fc0.z\n"; // percent = (raius - dist) / raius
fragmentCode += "mul ft1.y, ft1.y, ft1.y\n"; // percent * percent
fragmentCode += "mul ft1.y, ft1.y, fc0.w\n"; // percent * percent * angle
fragmentCode += "mul ft1.y, ft1.y, fc1.z\n"; // theta = percent * percent * angle * 8.0
fragmentCode += "cos ft1.z, ft1.y\n"; // c = cos(theta)
fragmentCode += "sin ft1.y, ft1.y\n"; // s = sin(theta)
fragmentCode += "mov ft2.x, ft1.y\n"; // vec(s, ?, ?)
fragmentCode += "mov ft2.y, ft1.z\n"; // vec(s, c, ?)
fragmentCode += "mov ft2.z, ft0.z\n"; // vec(s, c, 0)
fragmentCode += "dp3 ft1.x, ft0.xyz, ft2.xyz\n"; // dot(tc, vec(s, c, 0)) : y
fragmentCode += "neg ft1.y, ft1.y\n"; // s = -s
fragmentCode += "mov ft2.x, ft1.z\n"; // vec(c, ?, 0)
fragmentCode += "mov ft2.y, ft1.y\n"; // vec(c, -s, 0)
fragmentCode += "dp3 ft1.y, ft0.xyz, ft2.xyz\n"; // dot(tc, vec(c, -s, 0)) : x
fragmentCode += "mul ft1.y, ft1.y, ft1.w\n"; // if (dist < radius) ? 1 : 0
fragmentCode += "mul ft1.x, ft1.x, ft1.w\n"; // if (dist < radius) ? 1 : 0
fragmentCode += "seq ft1.w, ft1.w, fc1.w\n"; // if (uv == 0) ? uv = 1 : 0
fragmentCode += "mul ft0.xy, ft0.xy, ft1.w\n"; // tc.xy = if (uv == 1) ? tc.xy - center.xy : 0
fragmentCode += "add ft0.xy, ft0.xy, ft1.yx\n"; // tc.xy = if (uv == 0) ? vec(dot(tc, vec(c, -s, 0)), dot(tc, vec(s, c, 0))) : 0
fragmentCode += "add ft0.x, ft0.x, fc0.x\n"; // tc.x = tc.x + center.x
fragmentCode += "add ft0.y, ft0.y, fc0.y\n"; // tc.y = tc.y + center.y
fragmentCode += "div ft0.x, ft0.x, fc1.x\n"; // tc.x = tc.x / stageWidth
fragmentCode += "div ft0.y, ft0.y, fc1.y\n"; // tc.y = tc.y / stageHeight
// ---- END SWIRL ----

fragmentCode += "tex ft0, ft0, fs0 <2d,nearest,clamp>\n";
fragmentCode += "mov oc, ft0\n";

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

Bletraut
27.12.2015, 14:23
На картинке.

Tails
27.12.2015, 14:38
Класс!