『シェーダ(Shader)』といえばもちろん、
- 頂点シェーダ(vertex shader)
- フラグメントシェーダ(fragment shader)
なのですが、
『Unity』では、
座標計算や、ライトの計算などややこしい箇所を『Unity』側でよしなにしてくれる
『サーフェスシェーダ(SurfaceShader)』なるものがあるそうで。
『サーフェスシェーダ(SurfaceShader)』についてぐぐってみると、
それはそれはわかりやすいサイトがありました。
『シェーダ』超入門3記事と、
『サーフェスシェーダ(SurfaceShader)』関連の記事が20ページありましたので、
個人的にまとめてみることにしました。
Unity シェーダー入門 サーフェスシェーダの立ち位置
Unity公式Youtubeに『シェーダ(Shader)』処理の流れがあったので拝借します。
本来『シェーダ(Shader)』で画面に描画するなら、
ステップ1からステップ8まで存在するそうです。
- 3Dモデルを準備
- Transformの値を4×4行列に変換
- 頂点ごとに描画位置を算出・・頂点シェーダ
- 表裏を調べ、裏なら描画しない・・カリング
- 描画点を確定
- 描画点をデプスバッファと比較
- 描画点に打つべき色を確定・・フラグメントシェーダ
- 点をうつ
『サーフェスシェーダ(Shader)』はちょうど、
『頂点シェーダ』と『フラグメントシェーダ』の間に位置することになるのかなと思います。
公式Youtubeによると、
- 頂点シェーダはたいてい同じことを書く・・省略しても生成してくれる
- フラグメントシェーダはたいてい複雑になる・・楽な記述で生成してくれる
というのが、『サーフェスシェーダ(Shader)』の立ち位置だそうです。
Unity シェーダー入門 サーフェスシェーダの手順と初期コード
『サーフェスシェーダ(SurfaceShader)』をつくるには下記の手順が必要になります。
- 3Dモデルを準備
- シェーダーファイルを新規作成
- シェーダファイルを選んだ状態でMaterialを作成
- シェーダーを書く
- マテリアルを3Dモデルにアタッチする
『サーフェスシェーダ(SurfaceShader)』の初期コードは以下。
Shader "Custom/sample" { SubShader { Tags { "RenderType"="Opaque" } // タグ 描画順など LOD 200 CGPROGRAM //シェーダ記述開始 // ライティングモデル #pragma surface surf Standard fullforwardshadows #pragma target 3.0 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" }
『Unity』では条件付きで『GLSL』も使えるそうですが、
正式に使える言語は『HLSL』という言語になります。
参考記事
surf関数の引数として、
- Input IN と
- input SurfaceOutputStandard o
の2つがあります。
頂点シェーダから渡ってきた頂点情報がINに入って、
inoutで色の設定などをフラグメントシェーダ側に渡しています。
void surf (Input IN, inout SurfaceOutputStandard o) { //ここがサーフェスシェーダ
タグ、ライティングモデル、構造体はいろいろ設定があるようで、
公式マニュアル見るとよさそうです。
Surface Shadersの記述 | unity 公式マニュアル
Unity シェーダー入門 インスペクタに表示
『Properties』を追加すると、
『Unity』画面右側のエリア『インスペクタ』に表示させることができます。
Shader "Custom/sample" { Properties{ // プロパティブロック追加 ここに書いた変数がインスペクタから操作できる _BaseColor ("Base Color", Color) = (1,1,1,1) // 変数名 インスペクタ上の表示 型名 = 初期値 } SubShader { 〜中略〜
型の種類として、
- Color
- Range(min, max)
- 2D
- Float
などがあります。
Unity シェーダー入門 透明なシェーダをつくる
透明な『サーフェスシェーダ(SurfaceShader)』をつくるには下記3箇所の設定が必要です。
- Tags {Queue} ・・描画の優先度
- #pragma alpha ・・透過など宣言
- o.Alpha = 0.6; ・・透過設定
Shader "Custom/sample" { SubShader { Tags { "Queue" = "Transparent" } //追加 LOD 200 CGPROGRAM #pragma surface surf Standard alpha:fade //追加 #pragma target 3.0 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; // Alpha設定 0-1 } ENDCG } FallBack "Diffuse" }
『Queue』には描画の優先度を指定します。
『Queue』 の描画順序は、
Background -> Geometry -> AlphaTest -> Transparent -> Overlay
だそうです。
Unity シェーダー入門 明るさ判定(ベクトル内積)
3Dプログラミングでしょっちゅう目にする『ベクトル内積』。
『ベクトル内積』は2つのベクトル(絶対値)*cosθ で計算できますが、
専用の『dot』変数で計算します。
参考記事
オブジェクトの法線ベクトルと、
視線ベクトルの『内積』をとって
0〜1で透明度や色を決めたり。
オブジェクトの法線ベクトルと、
雪が降る方向(上側)の『内積』をとって
白くしたりしなかったり。
『トゥーンエフェクト(マンガっぽくなる)』にも『内積』が使われます。
- worldNormal オブジェクトの法線ベクトル
- viewDir 視線ベクトル
『内積』をとって0〜1で透明度調整しています。
〜中略〜 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))); // 垂直に交わるなら透明度1、平行なら0にしたい。 // 絶対値をとって1から引いてる。 // 最終的に垂直なら1、平行なら0になる。 o.Alpha = alpha*1.5f; 〜中略〜
参考記事
【Unityシェーダ入門】リムライティングのシェーダを作る
Unity シェーダー入門 オブジェクトにテクスチャを表示する (uv座標, tex2D)
『テクスチャ(画像)』を用意してオブジェクトに貼ることができます。
テクスチャは『テクスチャuv座標』で指定必要です。
- uv_MainTex ・・テクスチャuv座標(左下0,0, 右上1,1)
- tex2Dメソッド ・・ 座標色取得(引数2つ)
- Sampler2D ・・ テクスチャ型
〜中略〜 Properties{ _MainTex("Texture", 2D) = "white"{} // インスペクタ表示用 } 〜中略〜 struct Input { float2 uv_MainTex; // テクスチャuv座標を サーフェスシェーダに教える用 }; sampler2D _MainTex; // テクスチャ格納用の変数 sampler2D型の _MainTex変数 void surf (Input IN, inout SurfaceOutputStandard o) { o.Albedo = tex2D(_MainTex, IN.uv_MainTex); // _MainTex から uv_MainTex で指定された座標色取得、 //それを出力する色としてAlbedoに指定 } 〜中略〜
Unity シェーダー入門 明るさ判定は『グレースケール』
単純に明るいか暗いかを判定させるには、
4次元情報のRGBAではなく、
1次元(明るさだけ)の『グレースケール』に変換すると便利です。
式は以下。
grayscale = 0.3 * R + 0.6 * G + 0.1 * B
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; }
Unity シェーダー入門 時間で動かす _Time
- _Time変数 ・・ 時間
- オフセットをかけて移動できる
『テクスチャuv座標』にスクロール速度×時間を足すことで、
動いているようにみせることができます。
スクロール速度×時間=移動距離
サンプルでは『Terrain(テライン)』で地形をつくって、『Plane』で池をつくっていました。
- Terrain(テライン)・・ 簡単に地形生成できるめっちゃすごいツール
Shader "Custom/water" { Properties { _MainTex ("Water Texture", 2D) = "white" {} //インスペクタに水面テクスチャ貼り付け用 } 〜中略〜 struct Input { float2 uv_MainTex; // テクスチャ座標受け取り用 }; void surf (Input IN, inout SurfaceOutputStandard o) { fixed2 uv = IN.uv_MainTex; uv.x += 0.1 * _Time; // _Time変数はUnityシェーダデフォルト // オフセットは スクロール速度 * 時間 (=移動距離) uv.y += 0.2 * _Time; o.Albedo = tex2D (_MainTex, uv); } ENDCG } FallBack "Diffuse" }
参考記事
【Unityシェーダ入門】uvスクロールで水面を動かす
Unity シェーダー入門 テクスチャのブレンド(混ぜる) lerp
『テクスチャ』をブレンドするには3つの要素が必要になります。
- テクスチャ1
- テクスチャ2
- マスク(どんな割合でブレンドするかの指標。通常は白黒の画像(ノイズみたいな))
マスク画像はPhotoShopなどでも作成できます。(白黒で雲模様)
- lerp関数 ・・ ブレンドする関数
参考記事
Shader "Custom/sample" { Properties { //インスペクタで表示用 _MainTex ("Main Texture", 2D) = "white" {} //ブレンド用 1 _SubTex ("Sub Texture", 2D) = "white" {} //ブレンド用 2 _MaskTex ("Mask Texture", 2D) = "white" {} // マスク } 〜中略〜 sampler2D _MainTex; //それぞれ定義 sampler2D _SubTex; sampler2D _MaskTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c1 = tex2D (_MainTex, IN.uv_MainTex); //テクスチャ1の色 fixed4 c2 = tex2D (_SubTex, IN.uv_MainTex); //テクスチャ2の色 fixed4 p = tex2D (_MaskTex, IN.uv_MainTex); o.Albedo = lerp(c1, c2, p); //lerpはブレンド用メソッド。 実際には rgb それぞれ ブレンド計算している } ENDCG } FallBack "Diffuse" }
Unity シェーダー入門 distanceとsin
円やリングを動かすために、
座標間の距離を測りつつ、三角関数(sin)に時間を入れて動かすサンプルがありました。
- distance関数・・2点間の距離を取得
- sin ・・三角関数
三角関数の関連記事
〜中略〜 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)); // sin で 0.98 以上のみ白 // _Time を引くことで外側に移動しているように見せかけている 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); // 紫色 } } 〜中略〜
Unity シェーダー入門 ノイズ5種盛り
3Dプログラミングで必須な『ノイズ』。
参考サイトにきれいに5種盛りで紹介されていました。
- ランダムノイズ・・砂嵐
- ブロックノイズ・・テレビの調子悪い時
- バリューノイズ・・ブロックノイズをなめらかに
- パーリンノイズ・・バリューノイズをさらになめらかに
- fBmノイズ・・様々な解像度のノイズをずらしながら重ねる
参考記事
【Unityシェーダ入門】シェーダで作るノイズ5種盛り
ランダムノイズ
〜中略〜 float random (fixed2 p) { // 引数にuv座標 座標からランダム 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); } 〜中略〜
ランダムの値はこの記事からきているようです。
ブロックノイズ
ランダムノイズからブロックごとに1点を選んで()ベタ塗りしています。
Shader "Custom/BlockNoise" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} // インスペクタ表示用 } 〜中略〜 float random (fixed2 p) { return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453); } float noise(fixed2 st) { fixed2 p = floor(st); // floor 整数部分のみ取り出す return random(p); } void surf (Input IN, inout SurfaceOutputStandard o) { float c = noise( IN.uv_MainTex*8 ); //テクスチャ座標を8倍してnoiseに渡すと 8x8になる o.Albedo = fixed4(c,c,c,1); } ENDCG } FallBack "Diffuse" }
バリューノイズ
ブロックノイズで使った四隅の色からブロック内の色を補間しています。
〜中略〜 float random (fixed2 p) { return frac(sin(dot(p, fixed2(12.9898,78.233))) * 43758.5453); } float noise(fixed2 st) { fixed2 p = floor(st); return random(p); } float valueNoise(fixed2 st) { fixed2 p = floor(st); fixed2 f = frac(st); // float v00 = random(p+fixed2(0,0)); //四隅の色 float v10 = random(p+fixed2(1,0)); float v01 = random(p+fixed2(0,1)); float v11 = random(p+fixed2(1,1)); fixed2 u = f * f * (3.0 - 2.0 * f); float v0010 = lerp(v00, v10, u.x); //4隅の補間 float v0111 = lerp(v01, v11, u.x); return lerp(v0010, v0111, u.y); //最終的な補間 } void surf (Input IN, inout SurfaceOutputStandard o) { float c = valueNoise( IN.uv_MainTex*8); o.Albedo = fixed4(c,c,c,1); } 〜中略〜
パーリンノイズ
炎や雲などにみられる自然な変化のノイズを表現するのに使われています。
ブロック内の点の座標(A)を指定します。
ブロック内の点(A)から四隅に向かうベクトルと、
四隅からランダムなベクトルを発生させて、
その『内積』をとって,色情報として扱います。
ブロック内の点が動けば四隅の色もなだらかに変わります。
〜中略〜 fixed2 random2(fixed2 st){ st = fixed2( dot(st,fixed2(127.1,311.7)), dot(st,fixed2(269.5,183.3)) ); return -1.0 + 2.0*frac(sin(st)*43758.5453123); } float perlinNoise(fixed2 st) // パーリンノイズ生成関数 { fixed2 p = floor(st); fixed2 f = frac(st); fixed2 u = f*f*(3.0-2.0*f); float v00 = random2(p+fixed2(0,0)); float v10 = random2(p+fixed2(1,0)); float v01 = random2(p+fixed2(0,1)); float v11 = random2(p+fixed2(1,1)); return lerp( lerp( dot( v00, f - fixed2(0,0) ), dot( v10, f - fixed2(1,0) ), u.x ), lerp( dot( v01, f - fixed2(0,1) ), dot( v11, f - fixed2(1,1) ), u.x ), u.y)+0.5f; } void surf (Input IN, inout SurfaceOutputStandard o) { float c = perlinNoise(IN.uv_MainTex*8); o.Albedo = fixed4(c,c,c,1); o.Metallic = 0; o.Smoothness = 0; o.Alpha = 1; } 〜中略〜
fBmノイズ
地形や雲など幅広い表現に使われています。
パーリンノイズの解像度を変えたテクスチャを足して生成します。
- オクターブ・・いくつの階層のノイズを合成するか
- パーシステンス・・それぞれのノイズをどのような割合で合成するか
FBMノイズはこちらのサイトが詳しいです。
〜中略〜 fixed2 random2(fixed2 st){ st = fixed2( dot(st,fixed2(127.1,311.7)), dot(st,fixed2(269.5,183.3)) ); return -1.0 + 2.0*frac(sin(st)*43758.5453123); } float perlinNoise(fixed2 st) { fixed2 p = floor(st); fixed2 f = frac(st); fixed2 u = f*f*(3.0-2.0*f); float v00 = random2(p+fixed2(0,0)); float v10 = random2(p+fixed2(1,0)); float v01 = random2(p+fixed2(0,1)); float v11 = random2(p+fixed2(1,1)); return lerp( lerp( dot( v00, f - fixed2(0,0) ), dot( v10, f - fixed2(1,0) ), u.x ), lerp( dot( v01, f - fixed2(0,1) ), dot( v11, f - fixed2(1,1) ), u.x ), u.y)+0.5f; } float fBm (fixed2 st) // { float f = 0; fixed2 q = st; f += 0.5000*perlinNoise( q ); q = q*2.01; f += 0.2500*perlinNoise( q ); q = q*2.02; f += 0.1250*perlinNoise( q ); q = q*2.03; f += 0.0625*perlinNoise( q ); q = q*2.01; return f; } void surf (Input IN, inout SurfaceOutputStandard o) { float c = fBm(IN.uv_MainTex*6); o.Albedo = fixed4(c,c,c,1); o.Metallic = 0; o.Smoothness = 0; o.Alpha = 1; } 〜中略〜
Unity シェーダー入門 ShaderToyから移植
『GLSL』で書かれた『ShaderToy』から移植する方法が書かれていました。
参考記事
【Unityシェーダ入門】粘性のある液体をシェーダで作る
オリジナルコード
ShaderToy
- Unlit ・・ unlightingの略。ライティングなし。
移植先は『フラグメントシェーダー内』。
修正前
void mainImage( out vec4 c, in vec2 w ) { vec2 p = w/iResolution.xy, a = p*5.; a.y -= iGlobalTime; vec2 f = fract(a); a -= f; f = f*f*(3.-2.*f); vec4 r = fract(sin(vec4(a.x + a.y*1e3) + vec4(0, 1, 1e3, 1001)) * 1e5)*30./p.y; c.rgb = p.y + vec3(1,.5,.2) * clamp(mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y)-30., -.2, 1.); }
修正後
fixed4 frag (v2f i) : SV_Target { half2 p = i.uv.xy; half2 a = p*4.; a.y -= _Time.w*0.5; half2 f = frac(a); a -= f; f = f*f*(3.-2.*f); half4 r = frac(sin((a.x + a.y*1e3) + half4(0, 1, 1e3, 1001)) * 1e5)*30./p.y; return half4(p.y+half3(1,.5,.2) * clamp(lerp(lerp(r.x, r.y, f.x), lerp(r.z, r.w, f.x), f.y)-30., -.2, 1.),1); }
変換箇所。
- fract -> frac
- vec -> half
- mix -> lerp
- iResolution.xy -> uv.xy
- iGlobalTime -> _Time.w
関連記事
Unity シェーダー入門 テクスチャの両面を描画する(カリングオフ)
裏面も表示させるには『カリング』をオフにする必要があります。
- カリング・・裏側は描画しない機能。処理軽くなる
こちらも『Unlit Shader』で作成必要です。
〜中略〜 SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } //透明部分対応 LOD 100 Cull off // カリングオフ Blend SrcAlpha OneMinusSrcAlpha //透明部分対応 〜中略〜
Unity シェーダー入門 頂点シェーダを動かす
『頂点シェーダ』にフックすることで、
『頂点シェーダ』を動かして、旗が揺れるような動きをつくれます。
〜中略〜 #pragma surface surf Lambert vertex:vert // 頂点シェーダ使う宣言 #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void vert(inout appdata_full v, out Input o ) // 頂点シェーダ { UNITY_INITIALIZE_OUTPUT(Input, o); // 初期化 //動かすタイミングをずらすために sin引数に頂点x座標を足している float amp = 0.5*sin(_Time*100 + v.vertex.x * 100); // yをsin(時間)で動かす v.vertex.xyz = float3(v.vertex.x, v.vertex.y+amp, v.vertex.z); } void surf (Input IN, inout SurfaceOutput o) { //引数が SurfaceOutput になる fixed4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
頂点の座標データを操作する場合は、
『頂点シェーダ』を自分で作ることもできます。
Unity シェーダー入門 オブジェクトが重なった部分をくり抜く
抜きたい形のオブジェクトをA。
抜かれる側をBとして。
オブジェクトAを『デプスバッファ』に書き込みつつ、
カラーバッファに書き込まないことで、
描画はされないけれど、深度が記録された状態になります。
その後オブジェクトを通常通り描画すると、
『デプスバッファ』にオブジェクトAが書き込まれてるのでこの部分だけ描画されなくなります。
Shader "Custom/Cutout" { Properties { _Color ("Color", Color) = (1,1,1,1) } SubShader { Tags {"Queue" = "Geometry-1"} // Queueを変えて描画順を変えられる Pass{ Zwrite On ColorMask 0 } } }
Unity シェーダー入門 ポリゴンをポイント(点)で表現する
3Dモデルの頂点情報は『MeshTopology(メッシュトポロジ)』で定義されています。
- トポロジ・・位相・位置関係など
『トポロジ』はいろいろあるようです。
- Triangles ・・三角形(デフォルト)
- Quads ・・四角形
- Lines ・・線
- LineStrip ・・線分
- Points ・・点
『頂点』をポイントで表示するなら『MeshTopology.Points』と指定します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PointController : MonoBehaviour { void Start () { MeshFilter meshFilter = GetComponent<MeshFilter>(); //メッシュコンポーネント取得 meshFilter.mesh.SetIndices(meshFilter.mesh.GetIndices(0),MeshTopology.Points,0); // Pointに書き換えて再度保存 } }
Unity シェーダー入門 をやってみた感想
『WebGL/GLSL』と比べると、
3Dモデルを置いたりインスペクタが使えたりで、
少し操作を覚えればとても使いやすいなと感じました。
『HLSL』文法も、仕組みさえわかれば、
今のところ『GLSL』とそんなに変わらないかなと思います。
となるとやっぱり必要なのは、
- 独創力 = 場数 ・・どれだけ作品を見たか・マネしたか
- 数学力 ・・複雑な動作をさせるなら必須
の2つになってくるよなと思います。
『Unity』関係ではこんな記事も読まれています。
1. 【Unity】本のおすすめはこれで決まり!【初心者向け】1冊をとことんやって次に行きましょ2. 【Unity】シェーダ入門(サーフェスシェーダ) をまとめてみた。※随時更新
3. 【OpenGL】と【DirectX】のバージョンをまとめてみた【シェーダーメイン】【初心者向け】
4. 【Unity】よく使う用語集【初心者向け】※随時更新
アオキのツイッターアカウント。
この記事へのコメントはありません。