Yaminabe

らくがきぶろぐ

てすと:Yaminabeちゃんは生まれたばかりのブログです みんなで仲よく使ってね

カテゴリ: シリーズ講座

 

 

こんばんは 予告通り 「三つ目時 」=テレビ東京標準時の木曜夜7時半の更新です。

だれもわからないだろうけどw

 

今回はノーマル法線とノーマルマップの合成に関してのはなしです。

 

sample_1sample_2

sample_3image022

●たっきゅんのガ☆チンコ開発日記 http://takkun.nyamuuuu.net/blog2/archives/1627

● ISO-IEC 18026 - Clause 8, Spatial reference frames

のほうから画像だけお借りしました 不都合があればお申し出ください

--------------------------------------------------------------------------------------------------------------------------------------------------------

 

ノーマルマップはオブジェクトの法線ベクトルをテクスチャ画像のカラー値に置き換えたものです。

接空間 (Tangent Space) ポリゴン面の法線に対して垂直に接する面の座標空間です。グーローシェーディングがかかっている場合はポリゴン面上の法線はポリゴンを構成する頂点の法線がグラデーションで補完された値になります。

図のようにノーマル法線方向をZ軸 ポリゴンのUV空間 U方向をX軸 V方向をY軸と見立てます。

ノーマルマップは X軸成分をRカラー値、Y軸成分をGカラー値に置き換えて ポリゴン上のポイントではテクスチャのRカラー値によって法線はTangent方向へ傾くように計算され Gカラー値によってBinormal方向に傾くように計算されます。 それによってポイントあたりの法線方向が歪められてライト計算に反映されると凹凸が表現できるという仕組みです。

テクスチャカラーのRGB成分は整数値の0~255または0~1で定義されていますので 0.5を中心としてそれより小さければマイナスの値 大きければプラスの値とみなして扱います。

 

Unityのシェーダで接空間 (Tangent Space)データを使用する場合はシェーダー入力の構造体にappdata_tanを指定します。 これで取得できるのはモデルのAssetデータの変換時に計算されたタンジェント値です。

struct appdata_tan {
    float4 vertex : POSITION;
    float4 tangent : TANGENT
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;
}; 

もちろんappdata_fullですべてのデータを取得しても構いませんがデータはなるべく使用するものだけにしたほうが軽量化出来ますので そこら辺はケースバイケースで判断してください。

binormalは以下の式で計算出来ます。

float3 binormal = cross( IN.normal, IN.tangent.xyz ) * IN.tangent.w;

あまりにざっくりなので解説しますと 2つのベクトルに対して外積(cross)をとることで直行するベクトルを算出することができます。(INはシェーダinput構造体を表します)

上の式ではnormalとtangentをcross(外積)とることでbinomal(副法線)を算出しています。ベクトルのw成分は

vector(ベクトルの方向(x,y,z)、ベクトルの大きさw)で定義されるベクトルの大きさ成分wをかけています。

cross(外積)を用いた2つのベクトルに直行するベクトルを求める式は たとえばポリゴンを自前で生成した時に

法線を求める用途などにも使われます。 あとポリゴン面の表裏判定などですかね。

 

これが法線計算の基本ですが これを理解していると例えば 波のようにポリゴンをうねらせたりといったモデルを

変形させた時にシェーダー内部でノーマルを計算させたり 自前でポリゴン生成をするときに法線の計算をしたりという応用ができます。

 

●TANGENTの計算式

float3 tangent;

float3 c1 = cross(Normal, float3(0.0, 0.0, 1.0));
float3 c2 = cross(Normal, float3(0.0, 1.0, 0.0));

if (length(c1)>length(c2))
{
    tangent = c1;
}
else
{
    tangent = c2;
}

tangent = normalize(tangent);

●BINORMALの計算式

float3 binormal;

binormal = cross(Normal, tangent);
binormal = normalize(binormal);

 

補足>ノーマルベクトルをタンジェント空間に投影するコードが以下のようになっています ノーマルを再計算させたい時に参考にしてみてください

ノーマルベクトルとポリゴンのタンジェント空間座標(UVZ)の内積をとって角度変換し0~1の範囲に丸めているだけです。


float3 normalTS;

normalTS.x = dot(normal, Input.binormal);
normalTS.y = dot(normal, Input.tangent);
normalTS.z = dot(normal, Input.normal);

normalTS = normalize(normalTS);

return float4(normalTS * 0.5 + 0.5, 1);

 

< ここまでが基本の解説でした>

 

NormalMap - Polycount Wiki     http://wiki.polycount.com/NormalMap#TSNM

これを読めば大丈夫じゃないかというのが PolyCount Wiki のノーマルマップの解説です 英語ですけど解説図が多いのでほぼ眺めるだけでも内容は理解できると思います。

このサイトを紹介すればよかっただけのような気もしますね・・・

 

 

 ■今回のお題 ノーマルマップの合成で質感のクオリティアップを図ります。      

 

●メリットとしては

モデル全体を覆うノーマルマップとデティール用のノーマルマップそれぞれが あまり大きなテクスチャを使用しなくてもモデルのデティールアップが図れることが挙げられます。たとえば壁などで細かい質感を出すために高解像度テクスチャを使用しなくてもわりとラフなテクスチャでも十分見栄えのする質感表現が可能になります。

ノーマルマップ自体は多少重た目なのですが、シェーダ内でLOD命令を使用してカメラからの距離に応じてシェーダの式を切り替えることで軽量化できるかと思います。 ビルドインシェーダーのウォーターシェーダ を参考にしてみてください。

 

 

■オーバーレイを使用する合成の例
オーバーレイの式を利用したノーマルマップの合成ですがオーバーレイはカラーの数値が
0.5を境に合成式を変化させます。 以前パーティクルのアイデア1で加算と乗算を重ねあわせましたが 
それとほぼ同じです。たとえば背景に対してブレンド式を適用すればカラー部分を強調したままの
加算が再現できます。
 

 BlendOverlayf(base, blend)     (base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)))

   
      float4 norm   = tex2D(_BumpMap, IN.uv_BumpMap);
      float4 norm2 = tex2D(_BumpMap2, IN.uv_BumpMap2);
      dest = norm2<0.5 ? 2*norm*norm2 : 1-2*(1-norm)*(1-norm2);
      dest = lerp(norm2, dest, _Opacity);
      o.Normal = UnpackNormal(dest);

 

例) フォトショップのオーバーレイモードを使用してノーマルマップを調整することができます

normalmapfilter

NVIDIA のフォトショップ用ノーマルマップフィルタでノーマルマップを生成した場合すこし薄めのノーマルマップが仕上がると思います。 この場合フォトショップでレイヤーを使用してノーマルマップの深さをコントロールすることが出来て

下図のようにオーバーレイモードで同じノーマルマップをレイヤーに複製して重ねます。レイヤーのオペーシティ(濃度)をコントロールしてノーマルの深さをコントロールします。

もちろん部分的にレイヤー切り貼りで重ねれば すこしコントラストを強めたい部分だけに作用します。

overlay

 

■ノーマライズを使用する合成の例   

図はUDK(アンリアルエディターのシェーダーツリー)のノーマルマップをブレンドするサンプルです

NormBlend1

内容は2つのマップのノーマル成分を加算してノーマライズするだけという単純なものですが オーバーレイ計算の式では条件判定に三項演算子を使用していました 以前解説したようにシェーダーはif条件式が苦手なのでできれば避けたいところです。ノーマライズ版のほうが多少軽めかもしれません。ただノーマライズもそんなに軽い関数ではないので状況によって実装して比べてみるしか無いですかね。

 
   
float4 norm   = tex2D(_BumpMap, IN.uv_BumpMap);
     float4 norm2 = tex2D(_BumpMap2, IN.uv_BumpMap2);
                o.Normal  = normalize(float3(norm.xy + norm2 .xy, norm.z));

 

補足> normalize, dot, inversesqrt などのオペレータはUnityが最適なコードに変換するので自作のものを使用しないこと。 pow, exp, log, cos, sin, tan などの計算関数は非常に重たいのでなるべくテクスチャ参照(例えばカラーカーブをテクスチャで用意したもの)などを利用することがあげられています。

●    Unity - Optimizing Graphics Performance

 

その他の方法については以下のリンクに詳しい情報が掲載されていますので参照してみてください

Blending in Detail - Self Shadow http://blog.selfshadow.com/publications/blending-in-detail/

 

シェーダを実装した画像

DetailMap 

 

■ 『 Diffuse Detail Bump.shader 』

Shader "custom/Diffuse Detail Bump"{

Properties {

_Color ("Main Color", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_DetailMap ("Detail (RGB)", 2D) = "gray" {}
_BumpMap ("Normalmap", 2D) = "bump" {}
_DetailBumpMap ("Normalmap(Detail)", 2D) = "bump" {}
_DetailScale("DetailScale", Range (0.01, 1)) = 0.4

}

SubShader {

Tags { "RenderType"="Opaque" }
LOD 250

CGPROGRAM

#pragma surface surf Lambert

#include “UnityCG.cginc”

sampler2D _MainTex;
sampler2D _DetailMap;
sampler2D _BumpMap;
sampler2D _DetailBumpMap;
fixed4 _Color;
half _DetailScale;



 struct Input { float2 uv_MainTex;
float2 uv_DetailMap;
float2 uv_DetailBumpMap;
                       };

void surf (Input IN, inout SurfaceOutput o)
{
// デティールマップ(カラー)をテクスチャカラーと合成する
fixed4  c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            c.rgb *= tex2D(_DetailMap,IN.uv_DetailMap).rgb*2;

// 2つのノーマルマップから法線ベクトルをフェッチして合成する
fixed4 Normal1 = tex2D(_BumpMap, IN.uv_MainTex);
fixed4 Normal2 = tex2D( _DetailBumpMap ,IN.uv_DetailBumpMap) * float4 (_DetailScale,_DetailScale,0,0);

//アウトプットノーマルに法線ベクトルをセットする
            o.Normal = UnpackNormal (Normal1+Normal2);

// アウトプットカラーをセットする
            o.Albedo = c.rgb; 
            o.Alpha   = c.a;
}
ENDCG
}
Fallback "Diffuse"
}


 fixed4   Normal2 = tex2D( _DetailBumpMap ,IN.uv_DetailBumpMap) * float4 (_DetailScale,_DetailScale,0,0);

画像の例ではこの式の部分を以下の例のように書き換えてあります

例)

#include “UnityCG.cginc”

float4 _DetailBumpMap_ST;

……………….

float2    UVCoord = IN.uv_MainTex * _DetailBumpMap_ST.xy

             Normal2 = tex2D( _DetailBumpMap , UVCoord) * float4 (_DetailScale,_DetailScale,0,0);

 

UVがメインテクスチャと同じものを使用する場合はテクスチャスロットのoffsetの数値を使用してこのように
記述します。 
”テクスチャ名+_ST”のUnityマクロの使用法は以前の”パーティクルのアイデア2”の記事を参照してください。 
 

Tips>

ノーマルマップは基本的にRとGのチャンネルしか使用しないためBやAlphaチャンネルは空いた状態ですので、テクスチャの枚数を圧縮したい場合はこの空きチャンネルを利用することが出来ます。 一般にはAO(アンビエントオクルージョンマップ)やマスクテクスチャなどをいれるようです。 スマホなどのテクスチャ枚数が処理速度に影響が出やすいものはなるべくあいているチャンネルを利用してテクスチャ枚数を減らすことでメモリ効率がよくなります。

 

 

記事が長くなりすぎましたので ノーマルマップ生成方法やツールの使用法などの解説はまた後日にします。 

その気になれば10年後も可能とい(ry

もうすこし内容をつめようと思ってたんですけどねー すこし中途半端な感じになってしまいました次回への課題にします。

 

ではまた

 

今回はCurlノイズを使用したパーティクルの制御についてのおはなしです。

 

CurlノイズはPerlinノイズをベースにしたランダムノイズ関数で 適用することでパーティクルをあたかも流体計算をしているように振る舞わせることができます

Perlinノイズは空間座標を与えるとだいたい同じような値を返してくる特徴があって これが空間の物理特性を表現するのに都合がよく 考え方は空間にポテンシャル場 (流体のムラ)が存在しそれによって、移動するパーティクルの速度変化が影響を受けるというシュミレーションです。

そういえば先日 光子の速度に影響するヒッグス粒子が発見されましたね イメージはあんなふう

 

流体といえばナビエ・ストークス方程式(Navier-Stokes)をもとにしたシュミレーションを耳にしたことがあるかと思います。3DCGでセルグリッドの流体シュミレーションなどはだいたいナビエ・ストークス式をもとにした近似方程式で計算されています。 ナビエ・ストークス方程式は非常に複雑で まだ解を求めることが出来ないため。3DCG計算の分野では用途に合わせて簡易方程式を利用して計算を行うことで計算量を減らしています。

現在の流体計算はシュミレーション上はそれっぽく振る舞うので実用的にはこんなもんでいいかな というところで

おさまっているようです。

sqex75

スクウェア・エニックス オープンカンファレンスレポート(前編) - GAME Watch

2年ほどまえのスライドからですが、ゲームで使用する場合グリッドベースでまじめに計算を行う場合は計算量に見合ったクオリティは得られないため それっぽく振る舞うノイズ関数を使用した流体計算のほうがコストに見合うということが語られています。 FFアグニなどもこの方法みたいですね DeepDownもそれっぽいですが

 

 

■今回の実行サンプル

CurlTest1

● WebPlayer CurlTest.html

● UnityPackage CurlTest(131002).unitypackage

今回の場合はノイズを毎回計算してパーティクルの挙動に反映させるやりかたを考えます。参考にしたのがこのサイト

■curl noise for particles

curl noise for particles - syphobia -- the procedural way

 

パーティクルシステムで乱流をシュミレートする場合フレームごとに弱い力を速度に与える方法がとられます。(fig1

Unityのパーティクルシステムのアセット 例えばXeffectなどはソースも公開されていますので解析してみるとRandomで数値を加算しているだけです。

fig1:

particle[i].force.x+=random(-0.1,0.1);

particle[i].force.y+=random(-0.1,0.1);

 

ベクトルは−1から1で全ての角度方向をカバーできますのでこれでパーティクルの方向はコントロールできます。

そこにPerlinノイズによるゆらぎを加味することでそれらしくパーティクルの速度をコントロールします。

fig2:

particle[i].force.x+=perlin(particle[i].x*scale, particle[i].y*scale,1.352+time);

particle[i].force.y+=perlin(particle[i].x*scale, particle[i].y*scale,12.814+time);

 

パーティクルの座標をスケーリングしてPerlinノイズで(-1.0,1.0)の範囲で値を導きますz座標 にはある定数値と時間を与えてノイズフィールドを変更します。

という感じです これは自分の知ってるカールノイズなのか?という疑問もあるのですが それっぽければ良いので 今回はこの記事を信用しましょう。

 

 

■コードの解説

●"ParticleBufferAccess_Curl.cs"

using UnityEngine;
using System.Collections;


public class ParticleBufferAccess_Curl : MonoBehaviour {
       
public Vector3 Amount = new Vector3(1.0f,1.0f,1.0f);
const int bufferSize =  1000;
   
private ParticleSystem.Particle[] particles = new ParticleSystem.Particle[bufferSize];
private float[] dx =new float[bufferSize];
private float[] dy =new float[bufferSize];
private float[] dz =new float[bufferSize];
   
private Perlin noise;
   
public float  scale = 0.1f;
public float  speed = 0.05f;
      
void  Start ()
{
   
        noise = new Perlin ();
             
}    
       
void LateUpdate() {

int length = particleSystem.GetParticles(particles);
int i = 0;
       

while (i < length) {


    float  scalex = Time.time  * speed + 0.1365143f;
    float  scaley = Time.time  * speed +   1.21688f;   
    float  scalez = Time.time  * speed +    2.5564f;   
       
    dx[i] = noise.Noise(particles[i].position.x*scale, particles[i].position.y*scale, particles[i].position.z*scalex);
    dy[i] = noise.Noise(particles[i].position.x*scale, particles[i].position.y*scale, particles[i].position.z*scaley);
    dz[i] = noise.Noise(particles[i].position.x*scale, particles[i].position.y*scale, particles[i].position.z*scalez);

    particles[i].position += new Vector3(dx[i]*Amount.x, dy[i]*Amount.y, dz[i]*Amount.z) ;           
    i++;   
       
}
       
particleSystem.SetParticles(particles, length);

}       
}

particleSystemからGetParticlesでパーティクルの構造体を取得して 計算したものをparticleSystemにSetParticlesで書き込みます

データ更新にはLateUpdate()を使用します パーティクシステムはフレーム単位で更新されますが データが確定してから処理をしないと更新のタイミングが合わずにうまくいかないためです。

 

Unityのランタイム関数 Mathf.PerlinNoise は2Dのノイズをかえす関数なので、今回の場合は3Dに拡張したPerlin関数が必要になります。unity公式で配布されているProcedual Examplesデモの"Crumple mesh modifier"シーンがPerlinノイズを使用してランダムにサンプルをモーフさせるサンプルになっているので、そこからPerlinノイズのクラスファイルを引用しています。

f1f41dd7-d329-47da-92e8-6e8345fb72c7

■Procedural Examples  By Unity Technologies, Free

http://u3d.as/content/unity-technologies/procedural-examples/3zu

 

 

 

"Crumple mesh modifier"のコードを参考にしてPerlinノイズからパーティクルの速度を計算する本体を記述したのが以下の部分です。

    float  scalex = Time.time  * speed + 0.1365143f
    float  scaley = Time.time  * speed +     1.21688f;   
    float  scalez = Time.time  * speed +      
2.5564f;   
       
    dx[i] = noise.Noise(particles[i].position.x*scale, particles[i].position.y*scale, particles[i].position.z*scalex);
    dy[i] = noise.Noise(particles[i].position.x*scale, particles[i].position.y*scale,particles[i].position.z* scaley); 
    dz[i] = noise.Noise(particles[i].position.x*scale, particles[i].position.y*scale, particles[i].position.z*scalez);

    particles[i].position += new Vector3(dx[i]*Amount.x, dy[i]*Amount.y, dz[i]*Amount.z) ;   

時間あたりのスケールコントロールがspeed でscalexyzにそれぞれ加算される数値 x: 0.1365143f, y: 1.21688f, z:   2.5564f    はマジックナンバーのようなのでそのまま使用します。

サンプルのコード(fig2:)ではposition.z は式に定義していませんが 今回のコードでは計算に加味しています。

このPerlin関数は(0,1)の値を返す関数なのですが(Unityの2DPerlinも同じ範囲の値を返す) サンプルのコード

ではPerin関数が(−1,1)の値を返してくるという前提だと思いますので 同じような式にしたい場合はperlinノイズに perlin(***)*2-1 の補正をかけて拡張すればよいかと思います。

式によって若干パーティクルの挙動に変化が出ると思いますが、もともとが正確な計算でもないので用途に応じてそれっぽい飛び方をするように最適化すればいいような気がします。

さらに大量にパーティクルを処理させたい場合は Perlinノイズ生成部分を3Dテクスチャ化またはハッシュテーブルを使用したものに置き換えたり、GPUシェーダやコンピュートシェーダの使用 などが有効そうです。

データの調整ですがScale 値がPerlinノイズのサイズに対する数値の取得範囲だと考えてください。

ノイズテクスチャに対して値が小さければパーティクルは大きく動き 大きければ小刻みに振動します。

だいたい良い感じに調整したつもりなんですけどね。

スクリプトはデータの流れがわかりにくくなるのでベタな書き方をしていますが 変数を構造体でまとめたり わかりやすく最適化をしてみてください。

 

その他 使用法についてはPackageファイルをダウンロードして確認してみてください。

 

■参考

■Introduction To Noise Functions

http://freespace.virgin.net/hugo.elias/models/m_perlin.htm

パーリンノイズの生成方法とコードが掲載してあるサイトがありますので興味のある方は参照して理解を深めてみてください

■Noise-Based Particles, Part I at The Little Grasshopper

●Noise-Based Particles, Part I http://prideout.net/blog/?p=63

●Noise-Based Particles, Part II http://prideout.net/blog/?p=67

流体シュミレーションでは有名?なThe Little Grasshopperブログですがノイズベースパーティクルの構造が

把握できると思います。

GPU Gems - Chapter 5. Implementing Improved Perlin Noise

GPU Gems - Chapter 38. Fast Fluid Dynamics Simulation on the GPU

GPU Gems - Chapter 26. Implementing Improved Perlin Noise

 

テラシュールウェアさんのところで、ヒットチェックに関する記事が書かれていましたので補足的なことを解説してみようと思います。

内容はリンク先を参照してみてください。

 

[Unity]地面の接地判定を取得する3つの方法

http://terasur.blog.fc2.com/blog-entry-555.html

・CharacterControllerのisGroundを使う
・CheckSphereを使う
・SphereCastを足元に撃つ

能動的にスクリプトでコリジョン判定を確認する場合はこの例のような感じになるとおもいます。

以前も最適化の記事でPhysicsタイプの関数は全体的に重た目なのでなるべく限定して使用したいということを記述してきましたが、ゲームエンジンのスクリプトがなぜ速度をあまり必要としないかという解説が公式のアンリアルスクリプトの項目に寄せられていまして、 ゲームのスクリプトが他のアプリケーションのスクリプトと異なる点は基本的に待機関数がメイン つまり何かの行動を起こした時に実行される関数が処理の大半を占めるためそれほど高速に動作しなくてもゲームは成立するという説明がされています。

Physics関数は基本的にUpdate内部でコールしますが レイキャストなどのコリジョンチェックはフレームごとに呼び出されるためにゲームの実行速度に影響が出やすいです。 これを回避するための方法としてはWaitForSeconds()を使用してレイキャストの頻度をコントロールする さらにレイキャストの距離を短めに設定して必要ないヒットチェックをしないことなどヒットチェックを最小限に抑える方法があります。

ただしあまり レイキャストの頻度を下げてしまうと オブジェクトが高速に移動する場合にヒットチェックをすり抜ける可能性も考えられますので極端にレイキャストの頻度を減らすことは出来ません。

そこでPhysics関数の弱点を補ってPhysics関数をなるだけつかわない例もありますというのを解説します。

 

■トリガーを使用したコリジョンのヒット判定

Unity3Dでの待機関数というと名前が ”On~ ”で始まる関数全般ですが、 これを積極的に使用して重た目な関数は補助的に使用することで、最適化が期待できると考えられます。

待機型関数の場合はヒットチェックが実行中常に監視されていて 能動的に使用するPhysics関数と比べてコライダーを検出するだけのシンプルな動作で比較的動作が軽いです。

そこでトリガーを使用したヒットチェックの例を解説します。

EXAMPLE1:

TriggerSample1

図のようにキャラクターコントローラやキャラクターモーターを中心にトリガーを効率よく配置して、それぞれのトリガーにスクリプトを設定します。

スクリプト例:

static var triggered=0;

function OnTriggerEnter  (other : Collider) {
triggered=1;
}

function OnTriggerExit (other : Collider) {
triggered=0;
}

 

とても単純なスクリプトですが周囲のコリジョンコライダーにヒットした時、離れた時にフラグのOn、Offを行います。

フラグはキャラクターコントローラーに設定したスクリプトからトリガーのフラグ変数を読み取ってもよいですが、キャラクターコントローラにフラグをまとめてしまって各トリガーに状態を書き込んでもらったほうが見やすくなりそうな気もします。またはStaticタイプを指定すれば外部からも参照しやすくなります 使いやすく工夫してみてください。

static型は使用しない時 nullを放り込んでおけば実質メモリ消費がないはず

トリガーの設置位置はAIのステアリング操作ならば左右斜め前方に配置するとか 壁との距離を一定に保ちたい場合は左右にそれぞれ配置するなど うまくいく配置を試す必要があると思います。

もちろん配置した後ゲームの進行に合わせてトリガーを移動させたりスケールをかけたりゲームにおけるコリジョンの使い方と同じようにするのは自由です。格闘ゲームでは技によって判定が大きくなったり小さくなったりしますよね。

 

EXAMPLE2:

 

hittrig

そのほかに 敵との距離に応じてAIで状態遷移をする場合などは下図のようにキャラクター全体を覆うトリガーでヒットチェックを行うことでレイキャストの使用を避ける事が出来ます

 

TrigHitArray

図のように複数の敵が存在してそれぞれがプレイヤーの攻撃エリア(トリガー)に接触する場合。

攻撃をするための配列を用意して接触時OnTriggerEnterが有効になった時に配列に敵の情報をプッシュし

トリガーから敵が離脱した時に、その敵の情報(IDやTransform)をリムーブすることで効率よく索敵ができます。

その後の攻撃は一旦配列に取り込んだ敵だけを対象にすればよいため無駄にレイキャストをすることもなくスクリプトの高速化につながります。

●Physics.OverlapSphere

配列に取り込む処理を簡略化するのにPhysics.OverlapSphereを使用することも出来ます。

これは球型の判定を発生させて球内部のゲームオブジェクトを配列に格納したい場合に使用します。

例えば 球型の爆発によりエネミーにダメージを与えたい場合は配列を読み取ってそれぞれのゲームオブジェクトに対してダメージを加算します。 またタワーデフィフェンス系ゲームならば攻撃エリアにはいった敵を一気に配列に格納することで レイキャストで距離を判定しなくても配列を参照してゲームオブジェクトのTransform を参照するだけで攻撃有効な距離(例えば一番近くにいる敵)の計算が出来ます。OnTriggerEnter()に仕込むと良いと思いますが処理速度の実測をしていないので自分で配列管理するのとどちらが良いのかははっきりとはわかりません。

ダメージを与えるだけなら敵にスクリプトを仕込んでSendMessageとばしてもいいんですけど 敵が多いと重くてね。。

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour {
    void ExplosionDamage(Vector3 center, float radius) {
        Collider[] hitColliders = Physics.OverlapSphere(center, radius);
        int i = 0;
        while (i < hitColliders.Length) {
            hitColliders[i].SendMessage("AddDamage");
            i++;
        }
    }
}

EXAMPLE3:実装例

dotaSample

以前製作していた DOTA系のミニオンキャラクターのトリガーの配置ですがゲームのルール上 道で敵と出会った時にキャラクター同士すれ違いができないように 道全体をカバーするようにトリガーを配置してトリガー同士が重なる時にヒットチェックがでるようになっています。敵と自分の間に遮蔽物がある場合があるので、敵がトリガーにヒットしたときに一回だけ相手に向かってレイキャストします。 帰ってきた情報がトリガーにヒットした相手のコライダーと同じならば、敵が見える位置にいるので攻撃に移ります。

あとは状態に応じてトリガーのスケールなどを変化させることで細かい調整をしています。ここらへんはトライアンドエラーですね。 レイキャストだけの使用だとすり抜けが起きてしまうのでこのような構造になりました。

ちなみにこのゲームは諸事情あって棚上げになってます。

製作過程を見たい方は 過去ログを参照してみてください。

 

前回の記事で使用したPhotoshop のカラー合成の式をHLSL化したコードを使用していましたが

記事が長すぎてその部分の解説ができませんでした。

現在コードが掲載されていたリンク先が落ちてしまっているようなのでコードだけ転載します。

もとがライブラリ.hファイルなので 下の方の関数は上のほうを参照していますので Shader内に記述するときは

上から下への並びを入れ替えずに書き加えて下さい。

デザイナーさんからPhotoshopと同じカラー合成モードの見た目をお願いされることも多々あると思いますので

そんな場面にも使用してみるといいかと思います。

 

●ソースリンク先

http://www.pegtop.net/delphi/articles/blendmodes/
http://blog.mouaif.org/2009/01/05/photoshop-math-with-glsl-shaders/

 

 

 

■ Desaturate (彩度を下げる)

float4 Desaturate(float3 color, float Desaturation)
{
    float3 grayXfer = float3(0.3, 0.59, 0.11);
    float grayf = dot(grayXfer, color);
    float3 gray = float3(grayf, grayf, grayf);
    return float4(lerp(color, gray, Desaturation), 1.0);
}

 

■ RGBToHSL (RGB値 からHSL値(色相・彩度・明度)に変換)

float3 RGBToHSL(float3 color)
{
    float3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part)
   
    float fmin = min(min(color.r, color.g), color.b);    //Min. value of RGB
    float fmax = max(max(color.r, color.g), color.b);    //Max. value of RGB
    float delta = fmax - fmin;             //Delta RGB value

    hsl.z = (fmax + fmin) / 2.0; // Luminance

    if (delta == 0.0)        //This is a gray, no chroma...
    {
        hsl.x = 0.0;    // Hue
        hsl.y = 0.0;    // Saturation
    }
    else                                    //Chromatic data...
    {
        if (hsl.z < 0.5)
            hsl.y = delta / (fmax + fmin); // Saturation
        else
            hsl.y = delta / (2.0 - fmax - fmin); // Saturation
       
        float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
        float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
        float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;

        if (color.r == fmax )
            hsl.x = deltaB - deltaG; // Hue
        else if (color.g == fmax)
            hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue
        else if (color.b == fmax)
            hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue

        if (hsl.x < 0.0)
            hsl.x += 1.0; // Hue
        else if (hsl.x > 1.0)
            hsl.x -= 1.0; // Hue
    }

    return hsl;
}

 

■ HueToRGB(色相からRGB値を計算)

float HueToRGB(float f1, float f2, float hue)
{
    if (hue < 0.0)
        hue += 1.0;
    else if (hue > 1.0)
        hue -= 1.0;
    float res;
    if ((6.0 * hue) < 1.0)
        res = f1 + (f2 - f1) * 6.0 * hue;
    else if ((2.0 * hue) < 1.0)
        res = f2;
    else if ((3.0 * hue) < 2.0)
        res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
    else
        res = f1;
    return res;
}

 

■ HSLToRGB(HSL値 からRGBに変換)

float3 HSLToRGB(float3 hsl)
{
    float3 rgb;
   
    if (hsl.y == 0.0)
        rgb = float3(hsl.z, hsl.z, hsl.z); // Luminance
    else
    {
        float f2;
       
        if (hsl.z < 0.5)
            f2 = hsl.z * (1.0 + hsl.y);
        else
            f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
           
        float f1 = 2.0 * hsl.z - f2;
       
        rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0));
        rgb.g = HueToRGB(f1, f2, hsl.x);
        rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0));
    }
   
    return rgb;
}

 

■コントラスト・彩度・明度 の調整

float3 ContrastSaturationBrightness(float3 color, float brt, float sat, float con)
{
    // Increase or decrease theese values to adjust r, g and b color channels seperately
    const float AvgLumR = 0.5;
    const float AvgLumG = 0.5;
    const float AvgLumB = 0.5;
   
    const float3 LumCoeff = float3(0.2125, 0.7154, 0.0721);
   
    float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
    float3 brtColor = color * brt;
    float intensityf = dot(brtColor, LumCoeff);
    float3 intensity = float3(intensityf, intensityf, intensityf);
    float3 satColor = lerp(intensity, brtColor, sat);
    float3 conColor = lerp(AvgLumin, satColor, con);
    return conColor;
}

 

■ カラー合成モードいろいろ

1. Photoshopのカラー合成モードを再現した式のファンクション定義

#define BlendLinearDodgef             BlendAddf
#define BlendLinearBurnf             BlendSubstractf
#define BlendAddf(base, blend)         min(base + blend, 1.0)
#define BlendSubstractf(base, blend)     max(base + blend - 1.0, 0.0)
#define BlendLightenf(base, blend)         max(blend, base)
#define BlendDarkenf(base, blend)         min(blend, base)
#define BlendLinearLightf(base, blend)     (blend < 0.5 ? BlendLinearBurnf(base, (2.0 * blend)) : BlendLinearDodgef(base, (2.0 * (blend - 0.5))))
#define BlendScreenf(base, blend)         (1.0 - ((1.0 - base) * (1.0 - blend)))
#define BlendOverlayf(base, blend)     (base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)))
#define BlendSoftLightf(base, blend)     ((blend < 0.5) ? (2.0 * base * blend + base * base * (1.0 - 2.0 * blend)) : (sqrt(base) * (2.0 * blend - 1.0) + 2.0 * base * (1.0 - blend)))
#define BlendColorDodgef(base, blend)     ((blend == 1.0) ? blend : min(base / (1.0 - blend), 1.0))
#define BlendColorBurnf(base, blend)     ((blend == 0.0) ? blend : max((1.0 - ((1.0 - base) / blend)), 0.0))
#define BlendVividLightf(base, blend)     ((blend < 0.5) ? BlendColorBurnf(base, (2.0 * blend)) : BlendColorDodgef(base, (2.0 * (blend - 0.5))))
#define BlendPinLightf(base, blend)     ((blend < 0.5) ? BlendDarkenf(base, (2.0 * blend)) : BlendLightenf(base, (2.0 *(blend - 0.5))))
#define BlendHardMixf(base, blend)     ((BlendVividLightf(base, blend) < 0.5) ? 0.0 : 1.0)
#define BlendReflectf(base, blend)         ((blend == 1.0) ? blend : min(base * base / (1.0 - blend), 1.0))

 

2. 1を使用したカラーブレンド式の定義 Shader式に組み込むにはこちらをつかいます

#define Blend(base, blend, funcf)         float3(funcf(base.r, blend.r), funcf(base.g, blend.g), funcf(base.b, blend.b))

#define BlendNormal(base, blend)         (base)
#define BlendLighten                BlendLightenf
#define BlendDarken                BlendDarkenf
#define BlendMultiply(base, blend)         (base * blend)
#define BlendAverage(base, blend)         ((base + blend) / 2.0)
#define BlendAdd(base, blend)         min(base + blend, float3(1.0, 1.0, 1.0))
#define BlendSubstract(base, blend)     max(base + blend - float3(1.0, 1.0, 1.0), float3(0.0, 0.0, 0.0))
#define BlendDifference(base, blend)     abs(base - blend)
#define BlendNegation(base, blend)     (float3(1.0, 1.0, 1.0) - abs(float3(1.0, 1.0, 1.0) - base - blend))
#define BlendExclusion(base, blend)     (base + blend - 2.0 * base * blend)
#define BlendScreen(base, blend)         Blend(base, blend, BlendScreenf)
#define BlendOverlay(base, blend)         Blend(base, blend, BlendOverlayf)
#define BlendSoftLight(base, blend)     Blend(base, blend, BlendSoftLightf)
#define BlendHardLight(base, blend)     BlendOverlay(blend, base)
#define BlendColorDodge(base, blend)     Blend(base, blend, BlendColorDodgef)
#define BlendColorBurn(base, blend)     Blend(base, blend, BlendColorBurnf)
#define BlendLinearDodge            BlendAdd
#define BlendLinearBurn            BlendSubstract

#define BlendLinearLight(base, blend)     Blend(base, blend, BlendLinearLightf)

#define BlendVividLight(base, blend)     Blend(base, blend, BlendVividLightf)
#define BlendPinLight(base, blend)         Blend(base, blend, BlendPinLightf)
#define BlendHardMix(base, blend)         Blend(base, blend, BlendHardMixf)
#define BlendReflect(base, blend)         Blend(base, blend, BlendReflectf)
#define BlendGlow(base, blend)         BlendReflect(blend, base)
#define BlendPhoenix(base, blend)         (min(base, blend) - max(base, blend) + float3(1.0, 1.0, 1.0))
#define BlendOpacity(base, blend, F, O)     (F(base, blend) * O + blend * (1.0 - O))

 

■色相のブレンド

float3 BlendHue(float3 base, float3 blend)
{
    float3 baseHSL = RGBToHSL(base);
    return HSLToRGB(float3(RGBToHSL(blend).r, baseHSL.g, baseHSL.b));
}

 

■彩度のブレンド

float3 BlendSaturation(float3 base, float3 blend)
{
    float3 baseHSL = RGBToHSL(base);
    return HSLToRGB(float3(baseHSL.r, RGBToHSL(blend).g, baseHSL.b));
}

 

■カラーのブレンド

float3 BlendColor(float3 base, float3 blend)
{
    float3 blendHSL = RGBToHSL(blend);
    return HSLToRGB(float3(blendHSL.r, blendHSL.g, RGBToHSL(base).b));
}

 

■明度のブレンド

float3 BlendLuminosity(float3 base, float3 blend)
{
    float3 baseHSL = RGBToHSL(base);
    return HSLToRGB(float3(baseHSL.r, baseHSL.g, RGBToHSL(blend).b));
}

 

前回からまた更新の時間があいてしまいましたが 見に来てくださってる方々ありがとうございます。

拍手コメント頂いてるのですが お返事も出来なくて申し訳ないです。

先週 一日ほど空き時間があったのでエフェクト用シェーダサンプルを作成してみました

ベースはPaticleAdditive.shader ですが様々なシェーダにも応用が効きそうなものを詰め込んでみた感じです

エフェクト作成時に 画面内で画質調整ができると非常に作業が簡略化できるのでそうした機能がメインですね

今回のシェーダーは多機能な反面 若干重ためなコードだと思いますが あくまでサンプルなので

大量描画するパーティクルエフェクトやスクリーンに対して大きな面積をとる用途の場合を想定してきません

実装するときには 必要に応じて未使用な機能をコメントアウトするなどして最適化をしてください。

 

 

デバッグもあまりできていないので型付けのおかしい部分もあるかなとは思いますが なにぶん時間がたりないので気づいた方はご指摘ください。

(要約: なるべくじりきでがんばってみてほしいなぁ)

 

■"Particles/~MultiPurpose"

Shader "Particles/~MultiPurpose" {
Properties {

_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
_MainTex ("Particle Texture", 2D) = "white" {}
_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0

_RotateSpeed ("RotateSpeed", Float) = 0.0


_ColorIndices ("ColorIndices Texture", 2D) = "white" {}
_SelectColorIndices("SelectColorIndices", Range(0.0,1.0)) = 1.0

_SliceColor ("Slice Color", Color) = (0.5,0.5,0.5,0.5)
_SliceGuide ("Slice Guide (RGB)", 2D) = "white" {}
_SliceEdge ("Slice Edge (RGB)", 2D) = "Gray" {}
_SliceAmount ("Slice Amount", Range(-1.2, 1.2)) = 0.5


_Flowmap("Flowmap", 2D) = "black" {}
_FlowAmount("Flow Amount", Range(-2.0, 2.0)) = 0.0

_MaskTex ("Mask Texture", 2D) = "white" {}

_VertexFlowTex ("VertexFlow Texture ", 2D) = "white" {}
_VertexFlowAmount ("VertexFlow Amount", Range(0, 10.0)) = 0.0

_CC_Bright("CC_Bright", Range(0.0,2.0)) = 0.5
_CC_Saturate("CC_Saturate", Range(0.0,1.0)) = 0.5
_CC_Contrast("CC_Contrast", Range(0.0,3.0)) = 1.2

_CC_Hue("CC_Hue", Range(-10.0,10.0)) = 0.5
_CC_Saturation("CC_Saturation", Range(0.0,1.0)) = 0.8
_CC_Luminosity("CC_Luminosity", Range(0.0,2.0)) = 0.6
}

Category {

Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend One OneMinusSrcAlpha
ColorMask RGB
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,1) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}

// ---- Fragment program cards
SubShader {
Pass {

CGPROGRAM

#pragma glsl
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#pragma target 4.0
#include "UnityCG.cginc"

sampler2D _MainTex;

float _RotateSpeed;
float4x4 pulsateMatrix ;

sampler2D _ColorIndices;
float _SelectColorIndices;

fixed4 _SliceColor ;

sampler2D _SliceGuide;
sampler2D _SliceEdge;
float _SliceAmount;

sampler2D _Flowmap;
float _FlowAmount;

sampler2D _MaskTex;
sampler2D _VertexFlowTex;
float4 _VertexFlowTex_ST;
float _VertexFlowAmount;


float _CC_Bright;
float _CC_Saturate;
float _CC_Contrast;

float _CC_Hue;
float _CC_Saturation;
float _CC_Luminosity;


fixed4 _TintColor;



struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};

struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD1;
#endif
float2 texcoord_orig : TEXCOORD2;
};

float4 _MainTex_ST;


float2 Rotator(float2 UV)
{
float sine = sin(_Time*_RotateSpeed);
float cosine = cos(_Time*_RotateSpeed);

pulsateMatrix._m00 = pulsateMatrix._m11 = cosine;
pulsateMatrix._m10 = -sine;
pulsateMatrix._m01 = sine;
float4 tempUV = float4(UV.x-0.5,UV.y-0.5,0,0);
float4 temp = mul(pulsateMatrix,tempUV);
UV.x = temp.x+0.5;
UV.y = temp.y+0.5;
return UV;
}

float3 ContrastSaturationBrightness(float3 color, float brt, float sat, float con)
{

float AvgLumR = 0.5;
float AvgLumG = 0.5;
float AvgLumB = 0.5;

float3 LumCoeff = float3(0.2125, 0.7154, 0.0721);

float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
float3 brtColor = color * brt;
float intensityf = dot(brtColor, LumCoeff);
float3 intensity = float3(intensityf, intensityf, intensityf);
float3 satColor = lerp(intensity, brtColor, sat);
float3 conColor = lerp(AvgLumin, satColor, con);
return conColor;
}

float3 RGBToHSL(float3 color)
{
float3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part)

float fmin = min(min(color.r, color.g), color.b); //Min. value of RGB
float fmax = max(max(color.r, color.g), color.b); //Max. value of RGB
float delta = fmax - fmin; //Delta RGB value

hsl.z = (fmax + fmin) / 2.0; // Luminance

if (delta == 0.0) //This is a gray, no chroma...
{
hsl.x = 0.0; // Hue
hsl.y = 0.0; // Saturation
}
else //Chromatic data...
{
if (hsl.z < 0.5)
hsl.y = delta / (fmax + fmin); // Saturation
else
hsl.y = delta / (2.0 - fmax - fmin); // Saturation

float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;

if (color.r == fmax )
hsl.x = deltaB - deltaG; // Hue
else if (color.g == fmax)
hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue
else if (color.b == fmax)
hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue

if (hsl.x < 0.0)
hsl.x += 1.0; // Hue
else if (hsl.x > 1.0)
hsl.x -= 1.0; // Hue
}

return hsl;
}

float HueToRGB(float f1, float f2, float hue)
{
if (hue < 0.0)
hue += 1.0;
else if (hue > 1.0)
hue -= 1.0;
float res;
if ((6.0 * hue) < 1.0)
res = f1 + (f2 - f1) * 6.0 * hue;
else if ((2.0 * hue) < 1.0)
res = f2;
else if ((3.0 * hue) < 2.0)
res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
else
res = f1;
return res;
}

float3 HSLToRGB(float3 hsl)
{
float3 rgb;

if (hsl.y == 0.0)
rgb = float3(hsl.z, hsl.z, hsl.z); // Luminance
else
{
float f2;

if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);

float f1 = 2.0 * hsl.z - f2;

rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0));
rgb.g = HueToRGB(f1, f2, hsl.x);
rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0));
}

return rgb;
}





v2f vert (appdata_t v)
{
v2f o;


o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);


#ifdef SOFTPARTICLES_ON
o.projPos = ComputeScreenPos (o.vertex);
COMPUTE_EYEDEPTH(o.projPos.z);
#endif

o.color = v.color;

o.texcoord_orig = Rotator( v.texcoord.xy);
o.texcoord = Rotator( v.texcoord);
o.texcoord = TRANSFORM_TEX( o.texcoord,_MainTex);

half2 VertexFlowUV = TRANSFORM_TEX( v.texcoord, _VertexFlowTex);
half3 VertexFlowVec = tex2Dlod(_VertexFlowTex, float4(VertexFlowUV.xy,0,0)).rgb * 2.0-1.0;

o.vertex.xyz += VertexFlowVec.xyz * _VertexFlowAmount;


return o;

}



sampler2D _CameraDepthTexture;
float _InvFade;





fixed4 frag (v2f i) : COLOR
{
#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(
tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color *= fade;
#endif




////FlowMap////////////////////////////////////////////////////////////

half2 FlowtexUV = tex2D( _Flowmap,i.texcoord_orig).rg * 2.0-1.0;

half2 texUV;

texUV.x =clamp( ( i.texcoord_orig.x *2.0 -1.0)+ (FlowtexUV.x *_FlowAmount ),-1,1);
texUV.y =clamp( ( i.texcoord_orig.y *2.0 -1.0)+ (FlowtexUV.y *_FlowAmount ),-1,1);

texUV.x =( texUV.x +1.0)/2.0*_MainTex_ST.x +_MainTex_ST.z;
texUV.y =( texUV.y +1.0)/2.0*_MainTex_ST.y +_MainTex_ST.w;


// Mask///////////////////////////////////////////////////////////////////////
fixed4 tex = tex2D(_MainTex, texUV);

float MaskVal;
half4 MaskColor=half4(1,1,1,1);


MaskColor = tex2D ( _MaskTex,i.texcoord_orig);

tex.a =MaskColor.a ;


fixed4 col;



// ColorIndices/////////////////////////////////////////////////////


half2 ColorIndicesUV ;

ColorIndicesUV.x = tex.r;
ColorIndicesUV.y =1.0-_SelectColorIndices ;

fixed4 ColorIndices = tex2D(_ColorIndices, ColorIndicesUV );


col.rgb =_TintColor.rgb * ColorIndices.rgb * tex.rgb * i.color.rgb * 2.0f;


// Dissolve with Alpha channel/////////////////////////////////////////


float AbsSliceAmount = abs(_SliceAmount );

clip(tex2D (_SliceGuide, i.texcoord_orig).rgb - AbsSliceAmount );

half2 SliceEdgeUV;

SliceEdgeUV.x = (tex2D (_SliceGuide, i.texcoord_orig).r -AbsSliceAmount );
SliceEdgeUV.y = 0.5;

fixed3 EdgeColor = _SliceColor* tex2D (_SliceEdge, SliceEdgeUV).rgb* 1.8;

if (_SliceAmount >0.1 )
col.rgb += EdgeColor.rgb;
else if (_SliceAmount < -0.1 )
col.rgb *= 1- EdgeColor.rgb*0.5;




// ColorCollect /////////////////////////////////////////////////////////////////////

col.rgb= ContrastSaturationBrightness(col.rgb, _CC_Bright, _CC_Saturate, _CC_Contrast);

float3 HSL = RGBToHSL(col.rgb);

HSL.r *= _CC_Hue;
HSL.g *= _CC_Saturation;
HSL.b *= _CC_Luminosity;

col.rgb = HSLToRGB(HSL);

//////////////////////////////////////////////////////////////////////////////////////


col.a = (1 - tex.a) * (_TintColor.a * i.color.a * 2.0f) ;


return col;



}
ENDCG


}
}

// ---- Dual texture cards
SubShader {
Pass {
SetTexture [_MainTex] {
constantColor [_TintColor]
combine constant * texture, constant * primary DOUBLE
}
SetTexture [_MainTex] {
combine previous * primary DOUBLE, one - texture * previous
}
}
}

// ---- Single texture cards (does not do color tint)
SubShader {
Pass {
SetTexture [_MainTex] {
combine texture * primary DOUBLE, one - texture * primary
}
}
}
}
}


shader_slot

 

 

●Color_Indices

image1

カラーのR成分の数値 0~1に対して テクスチャのカラーをUV成分のU方向のカラーを割り当てます

テクスチャサイズどのようなものを使用しても良いのですが Rチャンネルのカラーは256階調なので インデックスカラーテクスチャのU方向は最大256までです

連続でカラーアニメーションさせたい場合はV方向にカラーバーを並べてUVのアニメーションをするなどのように使用します

テクスチャのインポート設定でwrapMode をClamp指定します Repeatのままですとテクスチャの端でカラーが隣のピクセル(たとえばテクスチャのU最大のときU最小のカラー)から侵食されてしまいますので注意してください。

 

image5

 

 

●Dissolve_Filter

image2


Dissolveアルファ値の境界にグラデーションがかかるようにしてあります スライダーを中心から右にずらすと加算 左にずらすと乗算でグラデーションはテクスチャでコントロールしています。 左側が外(アルファ値の低い方)の1D表現ですのでV方向のサイズはいくつでも構いません

clip(tex2D (_SliceGuide, i.texcoord_orig).rgb - AbsSliceAmount );

カットアウトアルファの しきい値指定はClip関数を用いていますが グラフィックカード(モバイルなど)によっては使用できないことがあるようです。 その場合はAlphaTest を使用してください。

 Unity - ShaderLab syntax- Alpha testing

 

 

補足>

シェーダ内でif命令を使用しています。 シェーダーで if ~else構文は使用できますが if命令は実行速度があまり早くありません。 (理由は「 SIMD 」で検索してみてください)

ピクセルシェーダ(フラグメントシェーダ)で分岐処理をおこなうとスクリーン表示ピクセル分だけ分岐命令の処理をする必要がありシェーダ自体がとても重くなります。その場合ShadowGunのサンプルに見られるように 頂点シェーダに計算部分を移動してしまう方法がまずひとつの方法です。 (頂点シェーダは頂点数分だけ計算が行われますが ピクセルシェーダはピクセル数分だけの計算量になるため)

 

そのほか if命令を 標準のシェーダー関数で置き換えてしまうことで最適化をはかる方法もあります

例1)

これ↓を

if  (distance > _Value) { cfinal = c2; } 
else cfinal = c;

 

この↓ように

v = max(0,sign(distance  -  _Value))  
cfinal = lerp(c, c2, v);

比較したい2つの値の差分をsignで(負 or 0 or 正) を(−1 or 0 or 1)の値に変換して

maxで0か1の値にまとめて leap(a, b, val) 「val=0のときa val=bのときb をとる関数」 を使用して

値を割り付けます

vは0または1をとるブーリアン型相当の値をとればよいので int、uint型などで代用します

 

例2)

fixed mixFactor = saturate(1 - ((distance - _ColorDecay) / _FadeDistance + 1) * 0.5);

cfinal = lerp(c, c2, mixFactor);

 

例のように 計算の値をsaturate()を使用して値を0~1の範囲にたたんでしまうようにすればよいかと思います

shader if else performance - Unity Answers

SIMD - Wikipedia, the free encyclopedia

 

●FlowMap_Filter

image3

FlowMapというテクスチャを用いて水面などをアニメーションさせる方法があります。

考え方はNormalMapと少し似ていますが NormalMapがカラーデータを使用して法線の傾きを

与えるのに対してFlowMapはテクスチャUV座標に変位を与えます。


詳しくはリンク先を参照してください
https://www.dropbox.com/s/ii2x077vj64lyhl/Water%20Flow%20For%20UDK.pdf

image8 flowsheet1

 


float2 flowmap = tex2D( _FlowMap, uv ).rg * 2.0 - 1.0;

テクスチャのアンパック化はこのような式で行われます
flowmapテクスチャからrとgの成分を読みだして カラーの値0~1の範囲を -1~1に拡張しています。
テクスチャ中心からベクトルUV方向に-1 ~ +1 となるように加工したほうが使い勝手が良いので

image9


このような形の式(突然数値を増減し始める)が出てきたら 値を調整している部分だと考えていいかと思います。 多分


その他 水面を表現する場合FlowMapを使用してNormalMapの合成など加工をおこなう場合には以下のような記述

ALGOholic ? A Coder's Blog - Another Flow Field Editor Update

 

    phase0 = cycleOffset * 0.5 + FlowOffset0;

    phase1 = cycleOffset * 0.5 + FlowOffset1;

    float3 norm0 = tex2D(NormTex0, (Uv * タイリング数) + flowmap * phase0);

    float3 norm1 = tex2D(NormTex1, (Uv * タイリング数) + flowmap * phase1);

VFX_Flowmap

左側FlowMapを使用して Leap0時(テクスチャまま):Leap1(オフセット変位100%)




 

 

●VertexModify

FlowMap2

今回はテクスチャの値を使用してメッシュ頂点をアニメーションさせています

頂点のアニメーションを制御しているのはこの行です

half2  VertexFlowUV= TRANSFORM_TEX( v.texcoord,  _VertexFlowTex);

half3  VertexFlowVec = tex2Dlod (_VertexFlowTex, float4(VertexFlowUV.xy,0,0)).rgb * 2.0-1.0;

 

o.vertex.xyz += VertexFlowVec.xyz * _VertexFlowAmount;

TRANSFORM_TEX はテクスチャUVの値を   _VertexFlowTex_ST  で設定される値にUVタイリング値と

オフセットを再計算させるUnityのマクロ関数です。

シェーダに float4 ”テクスチャ名”_ST  と記述することでシェーダ実行時に値が帰ってきます

テクスチャスロットと 帰ってくる値の関係は下図のとおりです

image4

”テクスチャ名”_ST .xy           =   Tiling値 UV方向のタイリング数

”テクスチャ名”_ST .wz          = Offset値 UV方向のオフセット値

UV.xy = TRANSFORM_TEX ( v.texcoord,  _MainTex );

これをマクロを使用しないで記述すると

UV.x  =  v.texcoord.x  * _MainTex_ST.x +_MainTex_ST.z;
UV.y  =  v.texcoord.y  * _MainTex_ST.y +_MainTex_ST.w;

となります。

 

vertexシェーダ内でテクスチャを参照する場合は tex2D の代わりに tex2Dlod を使用します。

tex2Dlod( テクスチャ, float4(U値, V値, 0, w =( lod 値の指定0~7 )

これは テクスチャlodの値がdepth値(カメラからの距離)によって決定されるため depth値がvertexシェーダでメッシュのソートが解決されてPixelシェーダにデータが渡るときに計算が行われるので vertexシェーダ内ではlodの自動割り当てが出来なくなり 直接lod値を指定してあげる必要があるからです。

※ Tex2Dlod関数はとても役立つので今後もとりあげていく予定。

image7

最後に 頂点データxyzにカラーデータを-1~+1範囲に加工したものを加算します 変化率をコントロールできるように

_VertexFlowAmount として掛けてあげます。

 

o.vertex.xyz += VertexFlowVec.xyz * _VertexFlowAmount;

 

頂点アニメーションというと三角関数を使用したものが多いですが テクスチャデータを使用したアニメーションは

テクスチャデータを呼び出して簡単な計算を行うだけなので重たい三角関数ほど負荷もなく複雑なアニメーションも見た目でコントロールできるので応用がありそうです。 ほかにはスクリプトに不慣れなデザイナーにとってもテクスチャを入れ替えるだけで扱いやすいというメリットもあると思います。

 

 

●COLOR_Collect_Filter

おまけ機能  色調整機能です ネットで公開されていたフォトショップの機能をシェーダで記述した関数を

そのまま使用してあります。

その他カラーコントロールのシェーダーサンプルはnVidiaの公式サイトを参照してみてください。

NVIDIA Shader Library

「Bright と Luminosity」「Saturate と Saturation」の変数がダブっているのですが どちらか片方しか実装しないと コントロールが難しかったため 両方記述してあります。(調整にあまり時間も掛けたくなかったもので..)

Bright と Contrast は上げ過ぎると色飛びしますので Saturateを下げてバランスを整えてると良いような気がします。 Contrastはアルファ部分のカラーが持ち上がりすぎを防ぐのに有効です。

カラーセーフ的な安全装置をつけても良いのですが エディタにくせがあると使いにくくなるので自由にカスタマイズしてみてください。

 

CC_Bright :明度 CC_Saturate  :彩度 CC_Contrast   :コントラスト


CC_Hue :色相 CC_Saturation  :彩度2 CC_Luminosity  :明度2

image10

 

■テクスチャのアニメーション

過去記事を読んでいただくかリンク先を参照してください

Animating Tiled texture - Unify Community Wiki

 

 

■シェーダーに関するそのほかのお話

 

■Unity Shaders and Effects Cookbook [Kindle版]

Kenneth Lammers (著)

Unity Shaders and Effects Cookbook

 

各所で非常に評価の高い本書 英語版ですけどコード部分だけながめても わりと分かりやすいのではないかなと思いました リンク先↓ のWikiも合わせて読むと 理解が深まるかもしれません

Cg Programming-Unity - Wikibooks, open books for an open world

よくリンクされているGLバージョンではなくCGバージョンのほうですね。3Dソフトで開発する場合最近だとCGFXに対応していることが多いので CGFXに似たCGでシェーダーを書いておくといろいろ捗ります。

 

 

■Shader Forge - A visual, node-based shader editor plugin for Unity

Shader Forge - A visual, node-based shader editor

現在開発中らしき フリー?のノードベースシェーダーエディター。 ロードマップを見るとまだこれからのようですがUI表示がUDKのマテリアルエディターに似た感じでアイコン表示になっているのはわかりやすくてGoodです。

ただ見た目の簡便さと裏腹に ノードベース型って複雑なシェーダが作りにくいんですよね 吐いてくるコードを実装できるまで最適化するのが一苦労だったりして 今のところテキストエディタしかつかってなかったりw いいものになるといいな

 

■参照リンクなど

View Profile- TheJamsh - Epic Games Forums

How Do You Make a Flow Map- [Archive] - Polycount Forum

Flow Map Painting Script - Early Alpha - Polycount Forum

↑このページのトップヘ