7日間でマスターするUnityシェーダー入門
https://gyazo.com/fd5bba28c0dcef9ec835f0f06a1671ce
これをやってく
◆目次
1. シェーダ超入門
2. surfaceシェーダ入門
3. 頂点シェーダ入門
4. ポストエフェクト入門
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
1. シェーダ超入門
◆ Unityのシェーダで遊んでみよう
▼ 説明
https://gyazo.com/5845eed5ca35b91068414f711243ba37
いろいろ書いてあって難しそうに見えるかもしれませんが、大きく分けると3つのパートにわかれます。
- Parameters
- Shader Settings
- Surface Shader
Parametersのパートにはインスペクタに公開する変数を書きます。C#スクリプトのpublic変数のようなものと考えれば理解しやすいと思います。
Shader Settingsのパートにはライティングや透明度などのシェーダの設定項目を記述します。
Surface Shaderのパートにはシェーダ本体のプログラムを書きます。
▼ シェーダを書き換えてみる
こちらを使用
https://gyazo.com/113b013565e3bd97ba44fef7b0b5c484
もとのシェーダ
code:Sample.shader.cs
Shader "Custom/Sample"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
以下のように書き換える
o.Albedoを書き換えただけ
Albedoに代入する値をテクスチャの値ではなく黒色に
code:cs
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = fixed4(0.1f, 0.1f, 0.1f, 1);
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
https://gyazo.com/50cd1ae15498fb731ff4daa776df4f87
◆ 20行から始めるUnityミニマルシェーダ
▼ サーフェイスシェーダへの位置づけ
Unityのサーフェイスシェーダにおいて、surfシェーダの位置づけは次のようになっている
Vertexシェーダから出力された値(Input構造体)を入力に取り、オブジェクトの表面色(SurfaceOutputStandard)を出力
このSurfaceOutputStandardに対してライティングを行い最終的な絵になる
https://gyazo.com/b21847b15c1fb7752e2b32fe65452845
https://gyazo.com/facde2abefd5752470dcb4a9dcaff123
Output構造体が持つ値
https://gyazo.com/b573bd748614189cafd5e60bca7b2321
▼ 最低限のシェーダ
Tags〜#pragmaまでの行は、前回Settingsパートとして紹介した部分
ここはおいおい出てきた時に理解する
重要なのは struct Input と surf関数の2つ
ここでは、特定の色で塗りつぶすだけなので入力としてテクスチャのuv情報(uv_MainTex)は必要はないが、Input structの中身が空ではコンパイルが通らないための苦肉の策
surf関数がサーフェイスシェーダの肝
出力用の構造体(SurfaceOutputStandard)がもつAlbedo変数に色情報を指定しています。ここでは全ての入力について白色を指定している
code:cs
Shader "Custom/sample" {
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
o.Albedo = fixed4(1.0f, 1.0f, 1.0f, 1);
}
ENDCG
}
FallBack "Diffuse"
}
https://gyazo.com/be79a54c94cf0c73e29939e81b8ae6ac
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
◆ シェーダのパラメータをインスペクタから設定する
▼ まずはC#スクリプトから
シェーダ
fixed4 _BaseColorをスクリプトから設定できる
code:shader.cs
Shader "Custom/sample" {
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows struct Input {
float2 uv_MainTex;
};
fixed4 _BaseColor;
void surf (Input IN, inout SurfaceOutputStandard o) {
o.Albedo = _BaseColor.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
スクリプト
code:ShaderColor.cs
using UnityEngine;
using System.Collections;
public class ShaderColor : MonoBehaviour {
private void Start () {
GetComponent<Renderer> ().material.SetColor ("_BaseColor", Color.white);
}
}
▼ インスペクター
シェーダを書き換え
シェーダプログラムの先頭にPropertiesブロックを追加
Propertiesブロックに書いた変数がインスペクタから操作できるようになる
C#のpublic変数と同じような扱い
code:shader.cs
Shader "Custom/sample" {
Properties{
_BaseColor ("Base Color", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows struct Input {
float2 uv_MainTex;
};
fixed4 _BaseColor;
void surf (Input IN, inout SurfaceOutputStandard o) {
o.Albedo = _BaseColor.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Propertiesブロック
使用するフォーマット
https://gyazo.com/6143bb47b931866a4169ebe65b414385
使用できる主な型名
https://gyazo.com/921ec6e4a000743281ed1263aad3b8c4
▼ シェーダのパラメータを変更してみる
こんな風に変更できるように
https://gyazo.com/418f5a19a6b399f7f53f673a26f474a6
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
2. surfaceシェーダ入門
◆ 目次
◆ 透明なシェーダ
◆ 氷のような半透明シェーダ
◆ ステンドグラスのシェーダ
◆ uvスクロールで水面を動かす
◆ 円やリングをかっこよく動かす方法
◆ シェーダで作るノイズ
◆ テクスチャの両面を描画する
◆ トゥーンシェーダを自作
◆ シェーダで旗や水面をなびかせる
◆ Dissolveシェーダ
◆ シェーダを使って世界に雪を降らせよう
◆ オブジェクトが重なった部分をくり抜く
◆ スパイクノイズを作る
◆ 透明なシェーダを作る
▼ 透明シェーダ
最低限からの変更点
Tagブロックの内容が追加されている
#pragmaの宣言にalpha:fadeが追加されている
SurfaceOutputStandardにAlphaを設定している
code:shader.cs
Shader "Custom/sample" {
SubShader {
Tags { "Queue" = "Transparent" }
LOD 200
CGPROGRAM
#pragma surface surf Standard alpha:fade struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
o.Albedo = fixed4(0.6f, 0.7f, 0.4f, 1);
o.Alpha = 0.6;
}
ENDCG
}
FallBack "Diffuse"
}
▼ TagブロックでTransparentを指定する
Tagブロックの中のQueueには描画の優先度を指定
基本的にはカメラからみて遠くにある(奥にある)ものから順番に描画されるため、わざわざQueueを指定する必要はない
ただ、半透明のものを描画する場合は、不透明オブジェクトの後に描画しないと描画結果が破綻する
https://gyazo.com/3b25faf1a21210b56850bd5b46cb321e
Queueでは、「Background」→「Geometry」→「AlphaTest」→「Transparent」→「Overlay」の順で描画
ここではQueueにTransparentを指定しているので、BackgroundやGeometryなどの不透明オブジェクトを全て描画してから今回の半透明オブエジェクトが描画される
▼pragmaの宣言にalpha:fadeを追加する
#pragma surface surf Standard に続けて「alpha:fade」を指定している
これを指定することで、オブジェクトを半透明で描くことが出来るようになる
そのほかのパラメータなど詳細は以下
▼ SurfaceOutputStandardにAlphaを設定する
前回まではSurfaceOutputStandardのAlbedoのみに値を設定してきた
透明度を指定する場合にはSurfaceOutputStandardのAlphaに値を設定する
アルファ値は0〜1の範囲で指定
https://gyazo.com/1927eb5e51f80cca87fe09c3214d3808
◆ 氷のような半透明シェーダ
透明シェーダとの変更点
サーフェイスシェーダに、法線ベクトルと視線ベクトルを入力している
透明度の求めるために、ベクトル計算をしている
code:shader.cs
Shader "Custom/sample" {
SubShader {
Tags { "Queue"="Transparent" }
LOD 200
CGPROGRAM
#pragma surface surf Standard alpha:fade struct Input {
float3 worldNormal;
float3 viewDir;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
o.Albedo = fixed4(1, 1, 1, 1);
float alpha = 1 - (abs(dot(IN.viewDir, IN.worldNormal)));
o.Alpha = alpha*1.5f;
}
ENDCG
}
FallBack "Diffuse"
}
▼ 法線ベクトルと視線ベクトルについて
今回はサーフェイスシェーダにworldNormal(オブジェクトの法線ベクトル)とviewDir(視線ベクトル)を入力している
法線ベクトルはオブジェクトの表面に対して垂直方向のベクトル
視線ベクトルはカメラが向いている方向のベクトル
https://gyazo.com/a8a7c399501b4ce1f6b163200eca2a75
▼ 氷シェーダの数式
完成図を見るとドラゴンの輪郭部分の透明度が低くなっているのに対して、中央部分の透明度は高くなっている
worldNormalとviewDIrを使って輪郭部分では1、中央部分では0になるような計算式を考える
輪郭部分と中央部分のベクトルに注目してみる
輪郭部分では視線ベクトルと法線ベクトルが垂直に近い角度で交わるのに対して、中央部分ではほぼ平行に近い角度で交わっている
https://gyazo.com/774e17b7596c968dc639541c10c96f4a
この2つのベクトルが交わる角度を透明度に変換すればよさそう
まずは2つのベクトルのなす角度を調べるために、内積(dot product)を使っている
worldNormalとviewDirの内積をとると下記の式ように、2つのベクトルが交わる角度に変換できる
worldNormalとviewDirはどちらも正規化された長さ1のベクトルなので、|worldNormal=1、|viewDir|=1
$ worldNormal * viewDir = |worldNormal| * |viewDir| * cosθ = cosθ
視線ベクトルと法線ベクトルの内積をとると垂直に交わる場合は0、平行に交わる場合には1または-1になる
今回は垂直に交わる場合には透明度を1、並行の場合は透明度を0にするために絶対値をとって1から引いている
これにより最終的な透明度の計算結果は、垂直に交わる場合には1、平行に交わる場合には0になる
$ alpha = 1 - abs( cosθ );
最後に1.5を掛けているのは見た目を調整するためのパラメータ
ここの値を変化させることで見栄えが変わる
https://gyazo.com/56f26bc183cae9dfa3f332e901ef6807
◆ ステンドグラスのシェーダ
▼ ステンドグラス用シェーダのアルゴリズム
今回作るシェーダは、ステンドグラスのような見た目になるように、テクスチャの色が黒色に近い場合は透明度を1にして、それ以外の場合は透明度を0.7ぐらいにして半透明する
https://gyazo.com/d2f33f126a21e95c37777938be41e4a1
テクスチャの色は前回紹介したように、tex2Dメソッドで取得
ただし、tex2Dメソッドで得られる色はRGBAのカラー情報で、黒色に近いかどうかを判定するのは少々面倒
そこで、よく使われる方法が画像のグレースケール化です。画像をグレースケールに変換することで、RGBAという4次元の値ではなく、明度という1次元の値で扱えるようになるので「明るい色か暗い色か」を判定するには非常に便利です。
https://gyazo.com/a2b71f03b22c357bb458f0cb02e2b9ec
画像処理ライブラリなどでは、グレースケール化のメソッドが用意されているのですが、Unityのシェーダにはグレースケール化のメソッドはありません。
そこで自分でRGBの情報からグレースケールに変換する必要がある
この変換式は簡略化すると次のように書ける
$ grayscale = 0.3 * R + 0.6 * G + 0.1 * B
▼ ステンドグラスシェーダのプログラム
今回は半透明のシェーダを利用するので、Tagの設定・alpha:fadeの指定・Alpha値の指定を行う
surfメソッドの中ではtex2Dメソッドを使ってテクスチャの色情報を取得し、グレースケールに変換している
グレースケールの値が0.2以下の場合には透明度を1に設定、0.2より大きい場合には透明度を0.7に設定
インスペクターで設定した画像の黒い部分は不透明、それ以外の部分は半透明になり、ステンドグラスのような見た目になる
code:shader.cs
Shader "Custom/sample" {
Properties{
_MainTex("Texture", 2D)="white"{}
}
SubShader {
Tags { "Queue"="Transparent" }
LOD 200
CGPROGRAM
#pragma surface surf Standard alpha:fade struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = (c.r*0.3 + c.g*0.6 + c.b*0.1 < 0.2) ? 1 : 0.7;
}
ENDCG
}
FallBack "Diffuse"
}
◆ uvスクロールで水面を動かす
▼ UVスクロールとは
テクスチャの左下が(u, v)=(0, 0)、右上が(u, v)=(1, 1)になる
テクスチャをスクロールするためには、フレームごとにuv座標にオフセットを足していく
https://gyazo.com/bd14008af09e192240c29a961fb467d2
ただし、サーフェイスシェーダは前フレームの状態を覚えておくことはできず、分かるのは「テクスチャのどこを使うか」だけ
したがって「前フレームのuv座標にオフセットを足していく」ことはできない
そこで「フレームごとにuv座標にオフセットを足す」かわりに、「uv座標にスクロール速度✕時間を足す」方法を使う
スクロール速度✕時間=移動距離なので、結局は同じことをしていますが、この方法を使えば非常に簡単なプログラムでuvスクロールが実現できる
▼ 水面を動かすシェーダを作る
インスペクタから水面のテクスチャをセットできるようにPropertiesにテクスチャ変数を追加
また、サーフェイスシェーダが処理するテクスチャ座標を受け取るため、Input構造体にuv_MainTexを宣言
surfメソッドの中でテクスチャをスクロールさせている
まずは「テクスチャのどの部分を使うか」という情報をuv変数に代入
次の2行で、uv変数に対してどれだけのオフセットをたすのかを指定
オフセットは「スクロール速度✕時間(=移動距離)」で計算
ここではu方向とv方向でスクロール速度を変えている
_Time変数はUnityのシェーダにデフォルトで用意されている変数で時間とともに値が増加する
code:shader.cs
Shader "Custom/Water"
{
Properties {
_MainTex ("Water Texture", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
fixed2 uv = IN.uv_MainTex;
uv.x += 0.1 * _Time;
uv.y += 0.2 * _Time;
o.Albedo = tex2D (_MainTex, uv);
}
ENDCG
}
FallBack "Diffuse"
}
◆ 円やリングをかっこよく動かす方法
▼ 円を書いてみる
このシェーダではワールド座標を利用して円を描く
そのため、サーフェイスシェーダの入力としてワールド座標を受け取れるように、Input構造体には worldPosを宣言している
サーフェイスシェーダの中では、現在処理しているワールド座標と原点の距離がradius(=2.0)以内なら白色、radiusより大きければ紫色を指定
ここでは、Unityが用意してくれているdistanceメソッド(2点間の距離を求めるメソッドです)を使って原点までの距離を求めている
code:shader.cs
Shader "Custom/sample" {
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
struct Input {
float3 worldPos;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float dist = distance( fixed3(0,0,0), IN.worldPos );
float radius = 2;
if( radius < dist ){
o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1);
} else {
o.Albedo = fixed4(1,1,1,1);
}
}
ENDCG
}
FallBack "Diffuse"
}
https://gyazo.com/44c3c0ba89db7f9581fa33dc9afd7131
リング
code:shader.cs
Shader "Custom/CircleDraw"
{
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
struct Input {
float3 worldPos;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float dist = distance( fixed3(0,0,0), IN.worldPos );
float radius = 2;
if( radius < dist && radius > dist - 0.2f) {
o.Albedo = fixed4(1,1,1,1);
} else {
o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1);
}
}
ENDCG
}
FallBack "Diffuse"
}
https://gyazo.com/246bf86459f2ee020fe536b4f4f9d3a0
▼ リングをいっぱい描く
code:shader.cs
Shader "Custom/CircleDraw"
{
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
struct Input {
float3 worldPos;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float dist = distance( fixed3(0,0,0), IN.worldPos);
float val = abs(sin(dist*3.0));
if( val > 0.98 ){
o.Albedo = fixed4(1, 1, 1, 1);
} else {
o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1);
}
}
ENDCG
}
FallBack "Diffuse"
}
https://gyazo.com/572cb749ad4b78912dccbca134cab2ff
https://gyazo.com/fa66c65623781b42f9c9268a794791f8
https://gyazo.com/486813b481994e9d5ff5e1a6464fd807
▼ リングを動かす
code:shader.cs
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
struct Input {
float3 worldPos;
};
void surf (Input IN, inout SurfaceOutputStandard o) {
float dist = distance( fixed3(0,0,0), IN.worldPos);
float val = abs(sin(dist*3.0-_Time*100));
if( val > 0.98 ){
o.Albedo = fixed4(1, 1, 1, 1);
} else {
o.Albedo = fixed4(110/255.0, 87/255.0, 139/255.0, 1);
}
}
ENDCG
}
FallBack "Diffuse"
https://gyazo.com/6dd04ce4d46021a4ca51d134c917360a
◆ シェーダで作るノイズ
code:shader.cs
Shader "Custom/RandomNoise" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
float random (fixed2 p) {
return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453);
}
void surf (Input IN, inout SurfaceOutputStandard o) {
float c = random(IN.uv_MainTex);
o.Albedo = fixed4(c,c,c,1);
}
ENDCG
}
FallBack "Diffuse"
}
https://gyazo.com/c310f5f11db1c29bd4f287ccc4c396fb
ちなみに_Timeを使ってテレビのノイズみたいに動かすこともできた
code:shader.cs
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Cull off
CGPROGRAM
#pragma surface surf Standard fullforwardshadows sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
float random (fixed2 p) {
return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453 * _Time * 100);
}
void surf (Input IN, inout SurfaceOutputStandard o) {
float c = random(IN.uv_MainTex);
o.Albedo = fixed4(c,c,c,1);
}
ENDCG
}
FallBack "Diffuse"
https://gyazo.com/ef2469d9e4a5f8474dcbc4fd904ad130
◆ テクスチャの両面を描画する
▼ 片面のみの描画
Planeオブジェクトにテクスチャを貼り付けると、両面が描画されるのではなく裏面は透明になってしまう
片面だけが描画されてしまうのは、カリングと呼ばれる「見えないところは描画しない設定」が原因
カリングをオフにして両面を表示するためには、シェーダを書く必要がある
▼ 両面描画
ここでは、プロジェクトビューで「Create」→「Shader」→「Unlit Shader」を選択
作成したデフォルトのUnlitシェーダに、カリングをオフにして両面表示する設定を書き加える
ここでは、デフォルトで生成されるUnlitLシェーダに3行追加しています。「LOD100」と書かれた行の下に「Cull off」の一行を追加
カリングを切るだけであれば追加するのはこの一行でOK
これによりテクスチャの両面表示ができるように
ここではテクスチャの透明部分にも対応できるようにするため、合わせてTransparentの設定も
code:cs
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
code:shader.cs
Shader "Unlit/Culloff"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Transparent" "RenderType"="Opaque" }
LOD 100
Cull off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
// make fog work
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
https://gyazo.com/f98d6caa8cd19742ec76acdbdeedc516
◆ トゥーンシェーダを自作
▼ トゥーンシェーダとは
Unityではフォトリアルな映像を作るのは簡単ですが、風ノ旅ビトやGravity dazeなどの独自にデフォルメされた映像を作るためにはシェーダを使う必要がある
その中でアニメ風の映像を作るためには、トゥーンシェーダ(Toon Shader)を使う
UnityにはEffectパッケージにToonシェーダが用意されているが、ここでは勉強のためにトゥーンシェーダを自作してみる
Unityに標準で付属するToonシェーダについては、↓の記事で非常にわかりやすく説明されている
▼ Toonシェーダのアルゴリズム
今回作るトゥーンシェーダではRampテクスチャと呼ばれる、色をサンプリングするための専用テクスチャを使う
オブジェクト表面の暗い所はこの色、明るい所はこの色、といった感じでRampテクスチャから色を取得する
https://gyazo.com/815dc14e433af7c1837733077b5828ed
計算方法
オブジェクト表面の明るさを計算するためには、ライトの方向とオブジェクトの法線の内積を取る
これにより、ライトと法線方向が一致する場合は値が大きくなり、ライトと法線が垂直に近づくにつれて値が小さくなる
https://gyazo.com/03bd5cef1b0a10775192033a0b66aaf5
ライトの方向とオブジェクトの法線の内積で明るさが計算できたら、それを0〜1にスケーリングしてRampテクスチャのu座標とする
Rampテクスチャはu座標に沿って段階的に明るくなるため、アニメのような明るさの変化がしっかり出る表現になる
▼ Toonシェーダファイルを作る
まずはトゥーンシェーダ用のシェーダファイルと、シェーダをアタッチしたマテリアルを作る
プロジェクトビューで「Create」→「Shader」→「Standard Surface Shader」を選択
プログラム
今回作成するToonシェーダの主要な部分はLightingToonRampメソッド
このメソッドでは上のアルゴリズムどおり、ライトの方向とオブジェクトの法線の内積を取り、その値に応じてRampテクスチャから値を取得している
このLightingToonRampメソッドはカスタムライティングを行うためのメソッド
データ型がようわからんくなってきたのでここで調べた
code:shader.cs
Shader "Custom/Toon" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_RampTex ("Ramp", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
sampler2D _MainTex;
sampler2D _RampTex;
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
fixed4 LightingToonRamp (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
// オブジェクトの法線の内積で明るさが計算し、それを0〜1にスケーリング
half d = dot(s.Normal, lightDir)*0.5 + 0.5;
// Rampテクスチャのu座標に設定
// Tex2D関数は、UV座標(uv_MainTex)からテクスチャ(_MainTex)上のピクセルの色を計算して返す
// 今回のRampテクスチャはy座標で変化はないので0.5で適当に固定してるのであろう
fixed3 ramp = tex2D(_RampTex, fixed2(d, 0.5)).rgb;
fixed4 c;
c.rgb = s.Albedo * _LightColor0.rgb * ramp;
c.a = 0;
return c;
}
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
UV座標とは
LightingToonRamp内の計算で??となったので調べた
下図のようにテクスチャの左下を(0, 0)、右上を(1, 1)として座標を定義します。
例えば、画像の中央を表すuv座標は(0.5, 0.5)となります。
https://gyazo.com/66f2bff7b84833eb608f422cd0a96a1d
▼ サーフェイスシェーダでカスタムライティングを使う
トゥーンシェーダのプログラムは、これまで出てきたsurfシェーダだけでは作れない
サーフェイスシェーダに追加するかたちで、ライティングの工程をフックする必要がある
Unityのサーフェイスシェーダは頂点シェーダ(Vertex)とマテリアルの表面の色を定義するサーフェイスシェーダ(Surf)、ライティングシェーダ(Lighting)の3つに分かれている
これまでは、VertexとLightingシェーダはUnityに任せていたが、今回は自分でライティングを行いたいのでライティングの工程をフックする
https://cdn-ak.f.st-hatena.com/images/fotolife/n/nn_hokuson/20170401/20170401082156.png
ライティングの工程をフックするためには次の3つの手順が必要になる
ライティング用のメソッド(Lighting◯◯◯)を作る
メソッド名をUnityに伝える
StandardSurfaceOutputを使わないようにする
まずはライティング用のメソッドを作ります。ライティング用のメソッド名はLightingから始める必要があり、ここではLightingToonRampという名前に
code:shader.cs
fixed4 LightingToonRamp (SurfaceOutput s, fixed3 lightDir, fixed atten)
また、ライティングのメソッドをフックしたことをUnityに伝えるため、#pragmaの行にライティングのメソッド名の◯◯◯の部分を追加
code:shader.cs
surfシェーダでライティングの工程をフックした場合には、surfの出力にはSurfaceOutputStandard型を使うことができないので、SurfaceOutput型に書き換えている
SurfaceOutput型にはEmmisionやSmoothnessは定義されていないので、surfメソッドからこれらの行も削除
▼ Rampテクスチャを設定
マテリアルに以下の画像を設定してみる
https://gyazo.com/c3b85c8ae872c14c63820dfd0a92505b
Sphereがこんな感じに
https://gyazo.com/cb9cf3c4167a0ec426a044ff16690f5d
◆ シェーダで旗や水面をなびかせる
▼ 旗や水面をなびかせるアルゴリズム
旗をなびかせるためには、平面オブジェクトの頂点を移動させることで実現可能
3Dで考えると難しいので、2Dで考えてみる
平面オブジェクトを横から見ると次のように一直線に頂点が並んでいる
https://gyazo.com/c4bad571d7aadd5f3d23c5507bd36e21
この頂点を波の形に移動させることで、平面オブジェクトがなびいているように見せかけることができる
https://gyazo.com/3b7a5d0c17cd4752d547b3d724d12711
全体的には波打っているように見えるが、それぞれの頂点は単に上下運動を繰り返しているだけ
ただし、隣の頂点とは少しタイミングをずらして上下運動をさせる
https://gyazo.com/204be31d920a57c53dcb9b2e2b9a6792
※UnityのPlaneは10x10のメッシュなので、旗のようになびかせると少々ぎこちない動きになるため、もう少し細かいメッシュのものを使いたい方は、Blenderなどで作成する必要がある
▼ 頂点シェーダ
頂点情報を扱うためには頂点シェーダ(vertex shader)をフックし、頂点データを取り出す必要がある
取り出した頂点データは頂点シェーダからsurfシェーダに渡して表示
https://gyazo.com/94e4d161c8a38cb689705e11065aa575
頂点シェーダをフックするための手順は次の2つ
頂点シェーダをフックすることをUnityに伝える
頂点シェーダのメソッドを作成する
pragmaの行に「vertex:頂点シェーダのメソッド名」と書くことで、Unityに頂点シェーダをカスタムで作成することを伝えることができる
ここではvertという名前の頂点シェーダを作成する
続いて、頂点シェーダのメソッドを作成する
引数には頂点シェーダへの入力としてappdata_full型、頂点シェーダからの出力にInput型をとる
頂点シェーダの中ではUNITY_INITIALIZE_OUTPUTを使って出力の値を初期化したあと、appdata_full型のcolor変数から頂点カラーの情報を取り出し、Input型のvertColor変数に値を詰めている
surfシェーダではこのInput型の値を受け取り、Albedoとして表示している
https://gyazo.com/5941462526feaab2b5ea7b5c4c9851dc
例
code:shader.cs
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert struct Input {
float4 vertColor;
};
void vert(inout appdata_full v, out Input o){
UNITY_INITIALIZE_OUTPUT(Input, o);
o.vertColor = v.color;
}
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = IN.vertColor.rgb;
}
ENDCG
}
FallBack "Diffuse"
▼ シェーダプログラム
頂点の座標データを操作するので、独自のvertexシェーダをカスタムで作る必要がある
pragmaの中で「vertex:vert」とすることで、カスタムしたバーテックスシェーダ(vert)を使うことを宣言している
頂点シェーダ(vert)のなかでは、各頂点を上下運動させるシェーダプログラムを書いている
sin関数を使って時間とともに頂点のy座標を変化させる
隣り合う頂点とは少し周期をずらすために、sin関数の引数に頂点のx座標を足している
code:shader.cs
Shader "Custom/Flag" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert vertex:vert sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void vert(inout appdata_full v, out Input o )
{
UNITY_INITIALIZE_OUTPUT(Input, o);
float amp = 0.5*sin(_Time*100 + v.vertex.x * 100);
v.vertex.xyz = float3(v.vertex.x, v.vertex.y+amp, v.vertex.z);
//v.normal = normalize(float3(v.normal.x+offset_, v.normal.y, v.normal.z));
}
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
https://gyazo.com/f2a771acc8c2c41e5270bb706e6077f5
◆ Dissolve(溶けるような)シェーダをつくる
▼ Dissolveシェーダの考え方
幾何学図形やノイズのテクスチャ(幾何学じゃなくても大丈夫)を用意し、そのピクセル明度によって「表示する・しない」を切り替える
https://gyazo.com/3690f48f9ffa3588d661e1672aeb1ec3
幾何学図形のテクスチャはどのようなものでも使用できるので(ノイズ画像がきれいな消え方にはなりやすい)いろいろ試してみると良い
https://gyazo.com/cbe35e83841de4cf9f9093ebfabc6791
ピクセルの明度に対するしきい値をシーケンシャルに変化させることで、フワァっと侵食されて消えていくような効果を表現することができるのです。
https://gyazo.com/bfe8a4f46ed7379313ab1ac4bd1ebd8d
▼ Dissolveシェーダの作り方
デフォルトのSurface Shaderからの変更点は次の二点
プロパティにDissolve用のテクスチャとしきい値を追加
surfメソッドにディゾルブのためのプログラムを追加
インスペクタからDissolve用のテクスチャを設定できるようにプロパティに_DisolveTexを追加している
また、「消す・消さない」のしきい値を決めるための_Threshold変数を追加
surfメソッド内ではDisolveテクスチャから値を取り出し、グレースケールに変換
グレースケールの変換式は以下
$ Gray = R * 0.3 + G * 0.6 + B * 0.1
グレースケールに変換後、明度がしきい値(_Threshold)以下のものについては破棄(discard)
code:shader.cs
Shader "Custom/Dissolve"
{
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_DisolveTex ("DisolveTex (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Threshold("Threshold", Range(0,1))= 0.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows sampler2D _MainTex;
sampler2D _DisolveTex;
struct Input {
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
half _Threshold;
fixed4 _Color;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 m = tex2D (_DisolveTex, IN.uv_MainTex);
half g = m.r * 0.2 + m.g * 0.7 + m.b * 0.1;
if( g < _Threshold ){
discard;
}
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
▼ 実行結果
Thresholdを変化させることで徐々に消滅させられる
Dissolveテクスチャを変えると消滅の見え方も変わる
https://gyazo.com/ffe6106f60d93e7a0ea5309e5d3e8749
https://gyazo.com/056af8d1ac33a11879d19ce8dc1226ee
◆ シェーダを使って世界に雪を降らせよう
▼ 雪を積もらせるアルゴリズム
深さのある雪を積もらせる場合は色々と計算が大変ですが、表面に薄っすらと積もらせるだけであれば、テクスチャの色を変えるだけでそれっぽく見せることができる
https://gyazo.com/909d3c7435b3684741f052a2eb404bfd
https://gyazo.com/7cc24c5e5c621e3271c07503bf880b02
ただ、テクスチャの色を変えると言っても、テクスチャを真っ白にするだけでは画面一面真っ白になってしまうだけ
屋根の裏側や葉っぱの裏側には雪を積もらせないようにするには、オブジェクトの法線と雪が降ってくるベクトル(大体上向き)の内積を取る
この内積の値が1に近ければば面が上を向いていると判断してテクスチャの色を白色に、そうでない部分は面が横や下方向を向いているので、オリジナルのテクスチャ色を使用
https://gyazo.com/d5e2b901015aece34d5ce3bfe2234910
▼ 雪のシェーダプログラム
雪の積もらせ方をインスペクタから設定できるようにPropertiesにSnowパラメータ(0.0〜2.0)を追加
surfメソッドの中ではまず、上方向のベクトルと面の法線ベクトルの内積をとって、面の上向き具合を調べる
この値とインスペクタから入力したSnowパラメータをかけた値が「雪積もり度」になる
この「雪積もり度」により、上向きの面ほど雪が積もって白くなる
code:shader.cs
Shader "Custom/Snow" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Snow("Snow", Range(0,2))= 0.0
}
SubShader {
Tags { }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float3 worldNormal;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
half _Snow;
void surf (Input IN, inout SurfaceOutputStandard o) {
float d = dot(IN.worldNormal, fixed3(0, 1, 0));
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
fixed4 white = fixed4(1,1,1,1);
c = lerp(c, white, d*_Snow);
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = 1;
}
ENDCG
}
FallBack "Diffuse"
}
▼ その他雪の表現参考記事
◆ オブジェクトが重なった部分をくり抜く
https://gyazo.com/84ed5cbccd54f5065c4a54e0f027f83f
▼ オブジェクトの形にくり抜くためには
抜きたい形のオブジェクトをA、抜かれる側のオブジェクトをBとする
https://gyazo.com/a410c32819924f69b6c938ac7a59b740
まずは、オブジェクトAをデプスバッファにだけ書き込み、カラーバッファには書き込まないようにする
これにより、画面には表示されないけれどもデプスバッファには書き込まれた状態になる
https://gyazo.com/8b68c4ed87f9a8fe5a5d8a5c90403a22
続いて、オブジェクトBを通常通り描画する
デプスバッファにはすでにオブジェクトAの情報が書き込まれているため、この部分だけは描画されないことに
https://gyazo.com/66e93d9fb11112f129561efe33e73414
▼ シェーダの作成
まずはシーンビューに抜くオブジェクト(球)と、抜かれるオブジェクト(立方体)を配置しておく
https://gyazo.com/a06bcb95fd058599fac7089f4d9518e9
シェーダプログラムは以下
code:shader.cs
Shader "Custom/Cutout" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
}
SubShader {
Tags {"Queue" = "Geometry-1"}
Pass{
Zwrite On
ColorMask 0
}
}
}
▼ 床は透明にせずに残したい
この方法では球と重なったオブジェクトが全てくり抜かれることになる
例えば、くり抜いた立方体の下側に床を表示したい場合はどうすればよいでしょうか。普通に床を描画すると床までくり抜かれてしまいます。
https://gyazo.com/db7f6aa732ef933e9890806f963300d6
球の情報がデプスバッファに書かれることで、それ以降に描画するオブジェクトは描画されなくなる
ということは、球の情報をデプスバッファに書き込む前に床を描画すれば良さそう
描画する順番はシェーダのRenderQueueで指定できる
Tags { "Queue"="Geometry-2" }
これにより、Render Queueの値は次のようになり、床→球→立方体の順序で描画される様になる
背景画像 1998
球 1999
立方体 2000
作成した床用のマテリアルを床オブジェクトにアタッチして実行すると、実行結果は次のように
https://gyazo.com/84ed5cbccd54f5065c4a54e0f027f83f
◆ スパイクノイズを作る
▼ スパイクノイズのエフェクトを作る
画像を横方向に歪めるエフェクトは非常に簡単で、参照するテクスチャのX座標値をY軸の値をもとにずらすだけです。ずらす量は基本的にはsin関数で良いと思います。
https://gyazo.com/fd01a22ffa4e4d1b5b20773bd299608e
今回は場所によってもう少しランダムなノイズにしたかったので、sin(10*x)*(-(x-1)^2+1)という関数の0〜3の範囲を使う
この関数はsin関数と二次曲線の値を掛けただけのものですが、これにより山形のノイズが生成できる
https://gyazo.com/67a13d2586aaa816d778961934b554c6
MacではGrapherというアプリ(デフォルトで入っている)を使うと、簡単にグラフが書けるので、「こういう波形が作りたい」というときに重宝する(知らなかった....)
▼ パルスノイズのシェーダを作る
プロジェクトビューで右クリックし、「Create/Shader/Unlit Shader」を選択
フラグメントシェーダで、テクスチャを横方向にずらす処理をしている
テクスチャのy座標の値をもとにx方向にずらす量を計算(計算式は上のもの)
また、ノイズの強さ自体をインスペクタから操作するため、propertiesブロックに_Amount変数を用意しています。この値を上で計算した値に掛けることで、ノイズ全体の大きさを制御します。
シェーダが書けたら、ノイズをかけたいuGUIのImageのインスペクタのMaterial欄に作成したPulseNoiseマテリアルをドラッグ&ドロップ
PulseNoiseマテリアルのインスペクタからAmount値を操作するとノイズがかかることが確認できる
code:shader.cs
Shader "UI/PulseNoise"
{
Properties
{
_MainTex ("Sprite Texture", 2D) = "white" {}
_Amount ("Distort", Float) = 0.0
}
SubShader
{
Tags { "RenderType"="Transparent" }
LOD 100
Pass
{
CGPROGRAM
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Amount;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = i.uv;
float x = 2*uv.y;
uv.x += _Amount*sin(10*x)*(-(x-1)*(x-1)+1);
fixed4 col = tex2D(_MainTex, uv);
return col;
}
ENDCG
}
}
}
https://gyazo.com/ce07d7c05741b6d10ca9cb6d0f1f0936
▼ パルスノイズを発生させるスクリプト
ノイズを一瞬だけ発生させるため、C#スクリプトから_Amount変数の値を操作する
NoiseControllerを作って次のプログラムを入力する
code:NoiseController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class NoiseController : MonoBehaviour {
IEnumerator GeneratePulseNoise()
{
for (int i = 0; i <= 180; i += 30)
{
GetComponent<Image> ().material.SetFloat ("_Amount", 0.2f * Mathf.Sin (i * Mathf.Deg2Rad));
yield return null;
}
}
void Update () {
if (Input.GetMouseButtonDown (0))
{
StartCoroutine (GeneratePulseNoise ());
}
}
}
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
3. 頂点シェーダ入門
◆ アウトラインシェーダを作る
https://gyazo.com/7741f2c33ca8a07832035fb9cfb0ed05
▼ アウトラインの概要
モデルにアウトラインをつけるには次のように幾つかの方法がある
ステンシルバッファを使う
法線情報から境界を計算する
少し膨らませたモデルを使う
ポストエフェクトで輪郭抽出する
ここでは直感的に分かりやすい3つめの「少し膨らませたモデルを使う」方法を紹介
この方法は2パス(モデルを2回描画する)でアウトラインを描画
まず1パス目では、法線方向にモデルを少し膨らませてから、モデルの裏面を塗りつぶしで描画(これが輪郭になる)
https://gyazo.com/388139f54bace9ff6be971e8698508df
続いて2パス目では通常通りモデルを描画します
このときモデルの表面のみを描画するようにします
1パス目では少し大きめのモデルを描画しているため、この部分がはみ出てアウトラインに
https://gyazo.com/97ffcb53c4046871d74f4f3daa7ca3f7
このように、1パス目で法線方向に拡大したモデルを塗りつぶして描画し、2パス目で通常通り描画することで、アウトラインのついたモデルを作ることが出来る
▼ シェーダファイルの準備
Create→Shader→Unlit Shader
1パス目の頂点シェーダではモデルを頂点方向にすこし膨らませています。ここでは裏面のみを描画するため「Cull Front」を指定していることに注意
頂点シェーダに入力される頂点座標と法線方向はローカル座標系
そこでローカル座標系でモデルを膨張させてから、UnityObjectToClipPos関数を使って頂点座標をワールド座標系に変換
1パス目のフラグメントシェーダは、特にシェーディングの計算などはせず、黒色でベタ塗りしているだけ
このフラグメントシェーダで指定した色がアウトラインの色になる
続いて2パス目ではモデルを通常通り描画
ここではトゥーン調の表示にするためフラグメントシェーダでランバートの計算をした後、3段階に階調化して表示しています
code:shader.cs
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Cull Front
CGPROGRAM
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
v.vertex += float4(v.normal * 0.04f, 0);
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = fixed4(0.1,0.1,0.1,1);
return col;
}
ENDCG
}
Pass
{
Cull Back
CGPROGRAM
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half nl = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));
if( nl <= 0.01f ) nl = 0.1f;
else if( nl <= 0.3f ) nl = 0.3f;
else nl = 1.0f;
fixed4 col = fixed4(nl, nl, nl, 1);
return col;
}
ENDCG
}
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
4. ポストエフェクト入門
◆ 目次
◆ モノクロ画面を作る
◆ シェーダでワイプエフェクトを作る
◆ 画面をセピア色にするポストエフェクト
◆ モノクロ画面を作る
ポストエフェクトとは、カメラに映ったゲームシーンを一枚の画像として加工する処理のこと
▼ カメラのメソッドをフックする
カメラに映った画像を取得してポストエフェクトをかけるために、レンダリングする途中で画像をフックします。
https://gyazo.com/2e3b43db43a7900ae4afe6ee4092be8d
スクリプト
OnRenderImageメソッドはすべてのオブジェクトのレンダリングが完了した後に呼び出されるメソッド
このOnRenderImageメソッドの中でBlitメソッドを使ってポストエフェクトをかけている
Blitメソッドはsrc画像に第三引数で指定したポストエフェクトをかけてdest画像に書き込む
スクリプトが保存できたら、PostEffect.csをMainCameraにドラッグ&ドロップしてアタッチしておく
code:PostEffect.cs
using UnityEngine;
using System.Collections;
public class PostEffect : MonoBehaviour {
public Material monoTone;
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit (src, dest, monoTone);
}
}
▼ Unityでモノクロシェーダとマテリアルを作る
今回はサーフェスシェーダではなく、バーテックスシェーダとフラグメントシェーダを使用
具体的なモノクロ処理はフラグメントシェーダで行っている
フラグメントシェーダでは、画面に描画する予定の色を取得し、それをグレースケール化してから出力
code:shader.cs
Shader "Custom/MonoTone" {
Properties {
_MainTex("MainTex", 2D) = ""{}
}
SubShader {
Pass {
CGPROGRAM
sampler2D _MainTex;
fixed4 frag(v2f_img i) : COLOR {
fixed4 c = tex2D(_MainTex, i.uv);
float gray = c.r * 0.3 + c.g * 0.6 + c.b * 0.1;
c.rgb = fixed3(gray, gray, gray);
return c;
}
ENDCG
}
}
}
◆ シェーダでワイプエフェクトを作る
https://gyazo.com/a37dca10245decc51ede426cb94fa9d7
▼ カメラのメソッドをフックする
今回はポストエフェクトとしてワイプエフェクトをかけるシェーダで実装
code:PostEffect.cs
using UnityEngine;
using System.Collections;
public class PostEffect : MonoBehaviour {
public Material wipeCircle;
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit (src, dest, wipeCircle);
}
}
▼ Unityシェーダとマテリアルを作る
今回作るワイプエフェクトはポストエフェクトなので、フラグメントシェーダで処理
やっていることは、原点からの値を見て黒く塗りつぶすかを決めているだけ
ただ、次の2点を補正するための処理が必要になる
原点が左下にある
画面のアスペクトが影響する
画面の左下が原点になるため、画面の中央からの距離を見たい場合には次のようにu方向、v方向ともに0.5ずつ座標をずらす必要がある
$ i.uv -= fixed2(0.5, 0.5);
https://gyazo.com/d1162946de527ac7376d5d275ed48350
また、入力される座標系は、画面の左下が(0, 0)、右上が(1, 1)になり、アスペクト比は考慮されていない
そこで正円をかくために、次のようにu方向にアスペクト比の補正をかけている
$ i.uv.x *= 16.0/9.0;
https://gyazo.com/39d4bf0e3a8359d11c0a9c1b0be453d6
最後に、原点からの距離が_Radius以下の場合にはワイプエフェクトをかけずに元の色を出力する
それ以外の場合には黒色で塗りつぶす
_Radiusはインスペクタから変更できるように、Propertiesブロックで宣言
シェーダプログラム
code:shader.cs
Shader "Custom/WipeCircle" {
Properties{
_Radius("Radius", Range(0,2))=2
}
SubShader {
Pass {
CGPROGRAM
float _Radius;
fixed4 frag(v2f_img i) : COLOR {
i.uv -= fixed2(0.5, 0.5);
i.uv.x *= 16.0/9.0;
if( distance(i.uv, fixed2(0,0)) < _Radius ){
discard;
}
return fixed4(0.0,0.0,0.0,1.0);
}
ENDCG
}
}
}
と思ったらこのシェーダだと画面が真っ黒になったので以下に修正したらいけた
code:shader.cs
Shader "Custom/WipeCircle"
{
Properties{
_Radius("Radius", Range(0,2))=2
_MainTex("MainTex", 2D) = "white"{}
}
SubShader {
Pass {
CGPROGRAM
sampler2D _MainTex;
float _Radius;
fixed4 frag(v2f_img i) : COLOR
{
fixed2 wipezone = i.uv - fixed2(0.5, 0.5);
wipezone.x *= 16.0/9.0;
if( distance(wipezone, fixed2(0,0)) < _Radius ){
fixed4 c = tex2D(_MainTex, i.uv);
return c;
}
return fixed4(0.0,0.0,0.0,1.0);
}
ENDCG
}
}
}
あと、以下のようにUpdateで_Radiusの値を動かせばシーン開始とともにワイプが動く演出もできた
code:PostEffect.cs
using UnityEngine;
using System.Collections;
public class PostEffect : MonoBehaviour {
public Material wipeCircle;
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit (src, dest, wipeCircle);
}
private void Update() {
wipeCircle.SetFloat("_Radius", Time.time/2);
}
}
こんな感じでスクリプトからワイプを動かせるように
イージングかけたらもっといい感じになりそう
https://gyazo.com/cc1d00d2afdf18d8de94d57cb67c7f71
◆ 画面をセピア色にするポストエフェクト
https://gyazo.com/fc947668149b97a477988655906e8493
▼ 描画する画像をフックする
カメラに映った画像を取得してポストエフェクトをかけるために、画面をレンダリングする途中で画像をフックして画像処理します。
https://gyazo.com/99a314514b52fd2325c3cdb4f4d0fc46
Blitメソッドはsrc画像に第三引数で指定したポストエフェクトをかけてdest画像に書き込む
code:PostEffect.cs
using UnityEngine;
using System.Collections;
public class PostEffect : MonoBehaviour {
public Material sepia;
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Graphics.Blit (src, dest, sepia);
}
}
▼ Unityでセピアシェーダとマテリアルを作る
上記のプログラムでははセピア変換処理はフラグメントシェーダ(fragメソッドの中)で行う
このフラグメントシェーダでは、次の3ステップで画像をセピア色に変換している
画像のグレースケール変換
明度を修正
グレースケール値から赤成分を足し、青成分を減らす
ステップ2で使用する画像を暗くする量(Darkness)と、Step3で使用するセピア色の強さ(Strength)はUnityのインスペクタからも変更できるように、プロパティの部分で宣言している
code:shader.cs
Shader "Custom/Sepia"
{
Properties {
_Darkness("Dark", Range(0, 0.1)) = 0.04
_Strength("Strength", Range(0.05, 0.15)) = 0.05
_MainTex("MainTex", 2D) = ""{}
}
SubShader {
Pass {
CGPROGRAM
sampler2D _MainTex;
half _Darkness;
half _Strength;
fixed4 frag(v2f_img i) : COLOR {
fixed4 c = tex2D(_MainTex, i.uv);
half gray = c.r * 0.3 + c.g * 0.6 + c.b * 0.1 - _Darkness;
gray = ( gray < 0 ) ? 0 : gray;
half R = gray + _Strength;
half B = gray - _Strength;
R = ( R > 1.0 ) ? 1.0 : R;
B = ( B < 0 ) ? 0 : B;
c.rgb = fixed3(R, gray, B);
return c;
}
ENDCG
}
}
}