среда, 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 перетаскиваем наш шейдер. Итог:



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




воскресенье, 1 февраля 2015 г.

HLSL: Шейдер воды

Какое то время назад я показывал, в своей публикации, видео с примером реализации воды.


По многочисленным просьбам решил открыть код своего шейдера Water.fx:

float4x4 mWorld;
float4x4 mWorldBack;
float4x4 mWVP;
float4x4 mWVPreflection;

float3 LightDir;
float3 ViewDir;

float waveHeight;
float2 windDelCoord;

float width = 1000;
float heught = 1000;

Texture texRefraction;
sampler RefractionMapSampler = sampler_state
{
texture =
magfilter = ANISOTROPIC;
minfilter = LINEAR;
mipfilter = LINEAR;
MaxAnisotropy = 8;
AddressU = mirror;
AddressV = mirror;
};

Texture texReflection;
sampler ReflectionMapSampler = sampler_state
{
texture =
magfilter = ANISOTROPIC;
minfilter = LINEAR;
mipfilter = LINEAR;
MaxAnisotropy = 8;
AddressU = mirror;
AddressV = mirror;
};

Texture texBumpMapA;
sampler BumpMapSamplerA = sampler_state
{
texture =
magfilter = ANISOTROPIC;
minfilter = LINEAR;
mipfilter = LINEAR;
MaxAnisotropy = 8;
AddressU = mirror;
AddressV = mirror;
};

Texture texBumpMapB;
sampler BumpMapSamplerB = sampler_state
{
texture =
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
float4 Position : POSITION0;
float4 TexCoordReflectionMap : TEXCOORD1;
float4 TexCoordRefractionMap : TEXCOORD2;
float2 TexCoordBumpMap : TEXCOORD3;
float4 Position3D : TEXCOORD4;
    float3 LightTS : TEXCOORD5;
float3 ViewTS : TEXCOORD6;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;

float4 worldPosition = mul(input.Position, mWorld);
output.Position = mul(input.Position, mWVP);
 
output.TexCoordReflectionMap = mul(input.Position, mWVPreflection);
output.TexCoordRefractionMap = output.Position;
output.TexCoordBumpMap = input.TexCoord;
output.Position3D = worldPosition;
 
    float3 tangent = float3(1,0,0);
float3 binormal = float3(0,0,1);
float3 normal = float3(0,1,0);
float3x3 matTS = float3x3( tangent, binormal, normal );

float3 Light = mul(LightDir, mWorldBack) - worldPosition;
float3 View = mul(ViewDir, mWorldBack) - worldPosition;

output.LightTS = normalize(mul(Light, matTS));
output.ViewTS = normalize(mul(View, matTS));

return output;
}

float4 cSpecularColor = float4(1, 1, 1, 1);
float fSpecularExponent = 300;

float4 WaterPS(VertexShaderOutput input) : COLOR0
{
float3 Distance = input.Position3D - ViewDir;
float rDistance = abs(length(Distance));
float rDistanceY = abs(length(Distance.y));
float rDistanceXZ = abs(length(Distance.xz));
float lerpDistanceA = 1 - saturate(rDistanceXZ / 1000);
float lerpDistanceB = 0.0;
if(rDistance > 50.0f)
{
lerpDistanceB = 1 - saturate(rDistance) / (1 * width);
}
else
{
lerpDistanceB = saturate(rDistanceXZ / 50.0f);
}

// reflection
float2 TexCoorReflection;
TexCoorReflection.x = input.TexCoordReflectionMap.x / input.TexCoordReflectionMap.w / 2.0f + 0.5f;
TexCoorReflection.y = -input.TexCoordReflectionMap.y / input.TexCoordReflectionMap.w / 2.0f + 0.5f;
 
float3 bumpNormalA = tex2D(BumpMapSamplerA, input.TexCoordBumpMap * 16 + windDelCoord) * 2 - 1;
float3 bumpNormalB = tex2D(BumpMapSamplerB, input.TexCoordBumpMap * 256 - windDelCoord * 2 ) * 2 - 1;
 
float2 moveA = bumpNormalA.xy * waveHeight * lerpDistanceA;
float2 moveB = bumpNormalB.xy * 0.2 * lerpDistanceB;
float2 TextCoordMove = moveA + moveB;
 
float2 TexCoordsMoveReflection = TexCoorReflection * 0.75f + TextCoordMove;

float4 reflectiveColor = tex2D(ReflectionMapSampler, TexCoordsMoveReflection) * 0.7f;

float3 NormalTS = tex2D( BumpMapSamplerA, input.TexCoordBumpMap * 16 + windDelCoord) * 2 - 1;
NormalTS = normalize( NormalTS);

float3 vReflectionTS = normalize( 2 * dot( input.ViewTS, NormalTS * 2 ) * NormalTS - input.ViewTS );
float fRdotL = dot( vReflectionTS, input.LightTS );
float4 cSpecular = saturate( pow( fRdotL, fSpecularExponent )) * cSpecularColor;

float light = clamp(dot(NormalTS, input.LightTS) * (reflectiveColor + cSpecular), 0, 1);

reflectiveColor += light * 0.9;

// refraction
float2 TexCoordRefraction;
TexCoordRefraction.x = input.TexCoordRefractionMap.x / input.TexCoordRefractionMap.w / 2.0f + 0.5f;
TexCoordRefraction.y = -input.TexCoordRefractionMap.y / input.TexCoordRefractionMap.w / 2.0f + 0.5f;
float2 TexCoordsMoveRefraction = TexCoordRefraction + TextCoordMove;// * lerpDistanceRefracion;
float4 refractiveColor = tex2D(RefractionMapSampler, TexCoordsMoveRefraction);

float3 eyeVector = normalize(ViewDir - input.Position3D);
float3 normalVector = float3(0,1,0);
float fNdotV = dot(eyeVector, normalVector);
float fFresnel = 1.0 - fNdotV;

// result
float4 resultColor = lerp(refractiveColor, reflectiveColor, fFresnel);
//resultColor.rgb *= (1.0 + resultColor.a * 1.2);
return resultColor;
}

technique Water
{
    pass P0
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_3_0 WaterPS();
    }
}

Unity 3D: Управление персонажем мышкой, вид с верху

Решаем задачу:

- Вид с верху;
- Персонаж (спрайт) должен перемещаться к месту клика мышью;
- Персонаж должен поворачиваться по направлению движения.

Решение:

using UnityEngine;

public class Player : MonoBehaviour
{

    private Vector3 _positionMove;
    private float _speed = 1f;

    private void Start()
    {
        _positionMove = transform.position;
    }

    private void Update()
    {
        UpdateInput();
        UpdateMove();
    }

    private void UpdateInput()
    {
        if (Input.GetMouseButtonDown(0))
        {
            _positionMove =
                UnityEngine.Camera.main.ScreenToWorldPoint(Input.mousePosition);
            _positionMove.z = 0;
        }
    }

    private void UpdateMove()
    {
        float moveDelta = (_positionMove - transform.position).magnitude;
        if (moveDelta <= _speed * Time.deltaTime)
        {
            transform.position = _positionMove;
            return;
        }
        Vector3 moveDir = _positionMove - transform.position;
        //
        float angle = Mathf.Atan2(moveDir.y, moveDir.x) * Mathf.Rad2Deg - 90;
        transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle));
        //
        moveDir.Normalize();
        transform.position += moveDir * _speed * Time.deltaTime;
    }
}

Unity 3D: FPS script

Скриптов для подсчета fps в Unity много. Способы подсчета основываются на различных исходных данных и принципах. На данный момент выбрал такой вариант:

using UnityEngine;

public class Fps : MonoBehaviour
{
    private const float UpdateInterval = 1.0f;
    private float _timeleft;
    private float _lastTime;
    private float _timeSpan;
    private int _lastFrame;
    private int _frames;
    private float _fps;
   
    void Update()
    {
        _timeleft += Time.deltaTime;
        if (_timeleft > UpdateInterval)
        {
            _timeleft -= UpdateInterval;
            _frames = Time.frameCount - _lastFrame;
            _lastFrame = Time.frameCount;
            _timeSpan = Time.realtimeSinceStartup - _lastTime;
            _lastTime = Time.realtimeSinceStartup;
            _fps = Mathf.RoundToInt(_frames/_timeSpan);
        }
    }

    void OnGUI()
    {
        GUI.Box(new Rect(10, 10, 70, 25), string.Format("fps {0}", _fps));
    }
}

пятница, 23 января 2015 г.

воскресенье, 11 ноября 2012 г.

Распаковка DxtCompressed текстур


Статья актуальна для разработки в XNA Game Studio и MonoGame.

MSDN Library: Opaque and 1-Bit Alpha Textures (Direct3D 9)

В данной статье приведу пример распаковки DxtCompressed текстуры. DxtCompressed текстуры имеют несколько форматов сжатия. Ниже приведенный пример распаковки предназначен для работы с форматом DXT1. В моих проектах это наиболее распространенный применяемый формат. В DXT1 прозрачность кодируется только двумя значениями 0 и 1 т.е. ни какой полу прозрачности, что дает преимущество сжатия в 4 раза. Для сравнения в DXT5 поддерживает полу прозрачность, которая кодируется отдельным блоком, за счет чего текстура жмется всего в 2 раза.

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

Код примера можно разбить на части:
- загрузка DxtCompressed текстуры;
- распаковка и преобразование в массив цветов Color[];
- преобразование массива цветов в обычную текстуру;
- вывод текстуры на экран для визуальной проверки.

Для работы примера необходимо:
- создать новый проект Windows Game (4.0);
- в Content добавить нашу DXT1 текстуру;
- в свойствах этой текстуры переключить параметр "Texture Format" с "Color" в "DxtCompressed"


- добавляем код из примера.

Код:  

 
 using System;
 
 using Microsoft.Xna.Framework;
 using Microsoft.Xna.Framework.Content;
 using Microsoft.Xna.Framework.Graphics;
 using Microsoft.Xna.Framework.Input;
 
 namespace DxtCompressed
 {
     public class Game1 : Microsoft.Xna.Framework.Game
     {
         GraphicsDeviceManager _graphics;
         SpriteBatch _spriteBatch;
 
         Texture2D _dxtCompressedTexture;
         Texture2D _colorTexture;
 
         public Game1()
         {
             _graphics = new GraphicsDeviceManager(this);
             Content.RootDirectory = "Content";
         }
 
         protected override void LoadContent()
         {
             _spriteBatch = new SpriteBatch(GraphicsDevice);
             _dxtCompressedTexture = Content.Load<Texture2D>("512x256");
             _colorTexture = Unpacking(_dxtCompressedTexture);
         }
 
         protected override void Update(GameTime gameTime)
         {
             if(Keyboard.GetState().IsKeyDown(Keys.Escape))
                 Exit();
         }
 
         protected override void Draw(GameTime gameTime)
         {
             GraphicsDevice.Clear(Color.CornflowerBlue);
             _spriteBatch.Begin();
             _spriteBatch.Draw(_colorTexture, Vector2.Zero, Color.White);
             _spriteBatch.End();
         }
 
         public struct DXT1pixel
         {
             public ushort color0;
             public ushort color1;
 
             public byte[,] pixel;
 
             public DXT1pixel(byte[] b)
             {
                 color0 = 0;
                 color0 |= (ushort)(b[1] << 8);
                 color0 |= (ushort)b[0];
                 color1 = 0;
                 color1 |= (ushort)(b[3] << 8);
                 color1 |= (ushort)b[2];
                 pixel = new byte[4, 4];
                 byte[] bbbb;
                 for(int i = 0; i < 4; i++)
                 {
                     bbbb = DecompressByte(b[i + 4]);
                     pixel[0, i] = bbbb[0];
                     pixel[1, i] = bbbb[1];
                     pixel[2, i] = bbbb[2];
                     pixel[3, i] = bbbb[3];
                 }
             }
 
             private byte[] DecompressByte(byte b)
             {
                 byte[] bb = new byte[4];
                 for(int i = 0; i < 4; i++)
                 {
                     bb[i] = (byte)(b & 0x03);
                     b = (byte)(b >> 2);
                 }
                 return bb;
             }
         }
 
         private Color WordToColor(int word)
         {
             int r,g,b;
             b = word & 0x1f;
             word = word >> 5;
             g = word & 0x3f;
             word = word >> 6;
             r = word;
             return new Color(r * 8, g * 4, b * 8);
         }
 
         public Texture2D Unpacking(Texture2D dxtTexture)
         {
             if(dxtTexture.Format != SurfaceFormat.Dxt1)
                 return null;
             int _width = dxtTexture.Width;
             int _height = dxtTexture.Height;
             int w = _width >> 2;
             int h = _height >> 2;
             byte[] color = new byte[w * h * 8];
             dxtTexture.GetData<byte>(color);
             //
             DXT1pixel[,] dxt = new DXT1pixel[w, h];
             int i;
             byte[] b = new byte[8];
             for(int y = 0; y < h; y++)
             {
                 for(int x = 0; x < w; x++)
                 {
                     i = (y * w + x) * 8;
                     Array.Copy(color, i, b, 0, 8);
                     dxt[x, y] = new DXT1pixel(b);
                 }
             }
             i = 0;
             Color[] newColor = new Color[_width * _height];
             for(int y = 0; y < h; y++)
             {
                 for(int x = 0; x < w; x++)
                 {
                     for(int yy = 0; yy < 4; yy++)
                     {
                         for(int xx = 0; xx < 4; xx++)
                         {
                             int ic = (y * 4 + yy) * _width + x * 4 + xx;
                             Color color0 = WordToColor(dxt[x, y].color0);
                             Color color1 = WordToColor(dxt[x, y].color1);
                             if(dxt[x, y].color0 > dxt[x, y].color1) 
                             {
                                 Color color2 = new Color(
                                     (int)(color0.R * 0.66f + color1.R * 0.34f),
                                     (int)(color0.G * 0.66f + color1.G * 0.34f),
                                     (int)(color0.B * 0.66f + color1.B * 0.34f));
                                 Color color3 = new Color(
                                     (int)(color0.R * 0.34f + color1.R * 0.66f),
                                     (int)(color0.G * 0.34f + color1.G * 0.66f),
                                     (int)(color0.B * 0.34f + color1.B * 0.66f));
                                 switch(dxt[x, y].pixel[xx, yy])
                                 {
                                     case 0x00:
                                         newColor[ic] = color0;
                                         break;
                                     case 0x01:
                                         newColor[ic] = color1;
                                         break;
                                     case 0x02:
                                         newColor[ic] = color2;
                                         break;
                                     case 0x03:
                                         newColor[ic] = color3;
                                         break;
                                 }
                             }
                             else 
                             {
                                 
                                 Color color2 = new Color(
                                     (int)(color0.R / 2 + color1.R / 2),
                                     (int)(color0.G / 2 + color1.G / 2),
                                     (int)(color0.B / 2 + color1.B / 2));
                                 Color color3 = Color.Transparent;
                                 
                                 switch(dxt[x, y].pixel[xx, yy])
                                 {
                                     case 0x00:
                                         newColor[ic] = color0;
                                         break;
                                     case 0x01:
                                         newColor[ic] = color1;
                                         break;
                                     case 0x02:
                                         newColor[ic] = color2;
                                         break;
                                     case 0x03:
                                         newColor[ic] = color3;
                                         break;
                                 }
                             }
                             
                         }
                     }
                 }
             }
             Texture2D t = new Texture2D(_graphics.GraphicsDevice, _width, _height);
             t.SetData<Color>(newColor);
             return t;
         }
     }
 }
 
 
 
 
 
 

Как работать с XNA Game Studio в Visual Studio 2012?

Александр Сороколетов подсказал волшебную статью:
How to install XNA game studio on Visual Studio 2012?