четверг, 21 января 2016 г.

Unity3D – Chunky post process effect

Мы периодически встречаемся с друзьями из старой (древней) команды "Accept Corp".
SpeccyWiki: http://speccy.info/Accept_Corp
Вспоминаем былые времена, что творили на ZX Spectrum, немножко ностальгируем. Это всегда приятные встречи старых друзей.

Приветы:
Phantom Lord (Дмитрий Селин)
Cardinal (Виталий Казаков)
M.O.T (Виктор Васильев)
A.Soft (Владимир Коновалов)
DJ.Wolf (Сергей Малышев)
Cannibal (Григорий Садовой)
FF-Soft (Александр Макушин)
Shov (Олег Шашкин)
Layabout (Егор Волкодамов)
Y.Soft (Равиль Габитбаев)
Visual Graphics (Павел Мирчук)

К чему я это говорю? К тому что создание данного пост эффекта навеяно именно теми временами из 90х годов, когда появилась мода в Demo рендерить 3D сцены из блоков Chunky.

Как это выглядит:


Мы не будем останавливаться на моментах, описанных в предыдущей вводной статье:
Unity3D: Simple post process effect
Пропустим очевидные манипуляции. К тому же в данном подходе много общего.

Сама суть эффекта заключается в следующем:
- результат эффекта будет монохромный, черные пиксели на белом фоне;
- размер исходных блоков chunky сделаем 4x4 пикселя;
- спрайты chunky будем выбирать из заранее подготовленной текстуры. Выглядит она как полоска 64x4 пикселя, логически это наши 16 спрайтов 4x4 пикселя:


Маленькая правда? :) Для наглядности увеличим картинку в 4 раза:


- число спрайтов 16 соответствует количеству оттенков белого, которые мы будем использовать. Так же это обусловлено рисунком, комбинациями пикселей в каждой градации;
- мы выбираем из текстуры экрана цвет, приводим к чёрно-белому, условно соответствующему очередному блоку;
- приводим значение градации к индексу спрайта в листе chunky;
- и выводим сам спрайт.

А теперь по-настоящему.
Код шейдера:

Shader "Hidden/Chunky"
{
 Properties
 {
  _MainTex("Texture", 2D) = "white" {}
  _SprTex("Texture", 2D) = "white" {}
 }
 SubShader
 {
  Pass
  {
   CGPROGRAM
   #pragma vertex vert_img
   #pragma fragment frag

   #include "UnityCG.cginc"

   sampler2D _MainTex;
   sampler2D _SprTex;
   float4 _Color = float4(1, 1, 1, 1);
   float2 BlockCount;
   float2 BlockSize;

   fixed4 frag(v2f_img i) : SV_Target
   {
    // (1)
    float2 blockPos = floor(i.uv * BlockCount);
    float2 blockCenter = blockPos * BlockSize + BlockSize * 0.5;

    // (2)
    float4 del = float4(1, 1, 1, 1) - _Color;

    // (3)
    float4 tex = tex2D(_MainTex, blockCenter) - del;
    float grayscale = dot(tex.rgb, float3(0.3, 0.59, 0.11));
    grayscale = clamp(grayscale, 0.0, 1.0);

    // (4)
    float dx = floor(grayscale * 16.0);

    // (5)
    float2 sprPos = i.uv;
    sprPos -= blockPos*BlockSize;
    sprPos.x /= 16;
    sprPos *= BlockCount;
    sprPos.x += 1.0 / 16.0 * dx;

    // (6)
    float4 tex2 = tex2D(_SprTex, sprPos);
    return tex2;
   }
   ENDCG
  }
 }
}

Где:
1) - Уже знакомое нам, по прошлой статье, приведение экранных координат к текстурным
2) - Управление яркостью картинки, нужное больше для баловства и немножко для тестов;
3) - Перевод в grayscale;
4) - Индекс спрайта в листе;
5) - Расчет координат спрайта в листе chunky;
6) - Вывод.

using UnityEngine;
using UnityStandardAssets.ImageEffects;
 
namespace Assets.Scripts.PostEffects
{
    [ExecuteInEditMode]
    [AddComponentMenu("Image Effects/Color Adjustments/Chunky")]
    public class Chunky : ImageEffectBase
    {
        public Texture2D SprTex;
 
        public Color Color = Color.white;
 
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            float k = Camera.main.aspect;
            float w = Camera.main.pixelWidth;
            float h = Camera.main.pixelHeight;
            Vector2 count = new Vector2(w/SprTex.height, h/SprTex.height);
            Vector2 size = new Vector2(1.0f/count.x, 1.0f/count.y);
            //
            material.SetVector("BlockCount", count);
            material.SetVector("BlockSize", size);
            material.SetColor("_Color", Color);
            material.SetTexture("_SprTex", SprTex);
            Graphics.Blit(source, destination, material);
        }
    }
}

Ни чего сложного не добавилось.

Знакомый нам исходный экран:



Результат:


Конечно на живую в fullscreen и в динамике выглядит по интереснее.
Но, в любом случае, на картинки лучше кликать для просмотра.

среда, 20 января 2016 г.

Unity3D: Simple post process effect

В этой статье мы рассмотрим:
- порядок создания простого пост эффекта;
- некоторые нюансы приведения размеров.

              Для наших экспериментов нам потребуется сцена, с объектами или картинками, не важно, главное по пестрее. Я набросал такую:



                На этой сцене отлаживался не один пост эффект, поэтому все что на ней присутствует – закономерно и не лишнее.
                Подготовим файлы. Мы будем работать с файлом шейдера и скриптом.
               
                Начнем с шейдера.
В папке Assets кликаем правой кнопкой мыши и выбираем:
Create->Shader-> NewImageEffectShader.
               
                Нам будет предложено на выбор несколько типов шейдеров, но это не так важно, внутренности мы перепишем. Я переименовал созданный файл в «Pixelation». Открываем и зачищаем от всего лишнего до такого вида:

Shader "Hidden/Pixelation Effect"
{
 Properties
 {
  _MainTex ("Texture", 2D) = "white" {}
 }
 SubShader
 {
  Pass
  {
   CGPROGRAM
   #pragma vertex vert_img
   #pragma fragment frag
   
   #include "UnityCG.cginc"

   fixed4 frag (v2f_img i) : SV_Target
   {
   }
   ENDCG
  }
 }
}


Обратите внимание на то, что входные данные пиксельного (фрагментного, привык я к терминологии HLSL) шейдера описывает структура v2f_img. Ее вполне достаточно для нас.
Далее добавляем входные параметры шейдера и необходимый код как в примере:

Shader "Hidden/Pixelation Effect"
{
 Properties
 {
  _MainTex ("Texture", 2D) = "white" {}
 }
 SubShader
 {
  Pass
  {
   CGPROGRAM
   #pragma vertex vert_img
   #pragma fragment frag
   
   #include "UnityCG.cginc"

   sampler2D _MainTex;
   float2 BlockCount;
   float2 BlockSize;

   fixed4 frag (v2f_img i) : SV_Target
   {
    float2 blockPos = floor(i.uv * BlockCount);
    float2 blockCenter = blockPos * BlockSize + BlockSize * 0.5;

    float4 tex = tex2D(_MainTex, blockCenter);
    return tex;
   }
   ENDCG
  }
 }
}

sampler2D _MainTex – буфер экрана как текстура;
float2 BlockCount – количество кирпичиков на экране (псевдопикселей) по X и Y;
float2 BlockSize – размеры кирпичиков тоже по X и Y;

Шейдер простой, вся суть в функции floor. Из справки:
floor(x) - возвращает самое большое целое число, которое является меньше чем или равным x.
Назначение и подготовку BlockCount и BlockSize рассмотрим в скрипте эффекта.
Как создать новый скрипт я писать не буду, но стоит уточнить вот что.
Нам потребуется импортировать некоторые стандартные ассеты, а именно:
Assets->Import package->Effects
С точки зрения изучения пост эффектов нам интересен весь пакет, в дальнейшем можно самостоятельно почитать типовые эффекты, входящие в него. Но пока на нужен только ImageEffectBase.cs что бы наследовать от него класс нашего скрипта.
В результате у нас должен получиться вот такой код:

using UnityEngine;
using UnityStandardAssets.ImageEffects;
 
namespace Assets.Scripts.PostEffects
{
    [ExecuteInEditMode]
    [AddComponentMenu("Image Effects/Color Adjustments/Pixelation")]
    public class Pixelation : ImageEffectBase
    {
        [Range(64.0f, 512.0f)]
        public float BlockCount = 256;
 
        private void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            float k = Camera.main.aspect;
            Vector2 count = new Vector2(BlockCount, BlockCount/k);
            Vector2 size = new Vector2(1.0f/count.x, 1.0f/count.y);
            //
            material.SetVector("BlockCount", count);
            material.SetVector("BlockSize", size);
            Graphics.Blit(source, destination, material);
        }
    }
}

Где:
BlockCount – исходное количество блоков по горизонтали;
k – понятно из определения, коэффициент соотношения сторон экрана;
count – вектор в который складываем расчетные значения количества кирпичиков по горизонтали и вертикали;
size – расчетное значение размеров кирпичиков, основанное на их количестве и коэффициенте соотношения сторон экрана.
               
                Может быть лишнее, но я хотел бы пояснить то, что в шейдере мы имеем дело с текстурами размером (1f, 1f) не зависимо от их реальных размеров в пикселях. И т.к. текстурные координаты носят относительный характер, то эту самую относительность мы и выражаем в size. Для пущей очевидности замечу, что size делает условные пикселы квадратными на всех разрешениях мониторов с разным соотношением сторон.
               
                Далее файл скрипта перетаскиваем на объект нашей сцены - камеру, в поле скрипта Shader перетаскиваем наш шейдер. Итог:



Готово.
Запускаем.