Posted at

Unity - Shaderを勉強する

More than 3 years have passed since last update.

Shaderを勉強していきます。Shader入門の記事を読んで思ったこと/気になることを書いていきます。

スクリーンショット 2016-08-02 21.16.31.png

Unity上で作成できるShaderの種類は

- Standard Surface Shader(ライティングの計算とかはUnity側がやってくれて簡単にshaderがかける)

- Unit Shader(ライティングの計算は自前でやらなきゃいけないけど自由にかける)

- Image Effect Shader(画面全体に対しての処理がかける)

- Compute Shader(CPUとかに直接アクセスできるらしい・・・?)

一番簡単そうなStandard Surface Shaderをやってみます。


下準備

Standard Surface ShaderとMaterialを作成します。

スクリーンショット 2016-08-02 21.19.22.png

Cubeを作成し、MeshRendererのMaterialに先ほど作成したMaterial(Shaderにはさっき作ったStandard Surface Shaderを指定ずみ)

スクリーンショット 2016-08-02 21.19.43.png

結果がわかりやすくなるようにCubeを回転させるスクリプトを作成


Rotation.cs

using UnityEngine;

public class Rotation : MonoBehaviour
{
public bool isRotation = true;
public float speed = 30;

void Update ()
{
if (!isRotation)
return;
var r = transform.rotation.eulerAngles;
r.y += speed * Time.deltaTime;
transform.rotation = Quaternion.Euler (r);
}

}


これをCubeにくっつける。

下準備終了 :)


何もコードをいじらないで再生してみる


  • 影が付いている

  • いろいろとパラメータを指定できる

スクリーンショット 2016-08-02 21.23.21.png


デフォルトのStandard Surface Shaderを見てみる


NewSurfaceShader

Shader "Custom/NewSurfaceShader" {

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
#pragma target 3.0

sampler2D _MainTex;

struct Input {
float2 uv_MainTex;
};

half _Glossiness;
half _Metallic;
fixed4 _Color;

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"
}


よくわからないなりに読んでみる。


NewSurfaceShader勝手にコメント

//CustomのNewSurfaceShaderという区分を指定している

Shader "Custom/NewSurfaceShader" {
//Inspectorに表示されるプロパティを指定
Properties {
//_Colorは型? , "Color"は変数名, Colorも型?, (1,1,1,1)は初期値
_Color ("Color", Color) = (1,1,1,1)
//"white"{}はTextureが指定されなかった時に自動的に設定されるやつかな
_MainTex ("Albedo (RGB)", 2D) = "white" {}
// Range(0,1)でスライダーが使えるようになっている
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader {
//RenderTypeか、、、Opaqueってよく見る
Tags { "RenderType"="Opaque" }
//Low なんちゃらの略なのだろうか・・・
LOD 200

//CGPROGRAM~ENDCGまで別のCG言語でかけるようになる・・・?
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
//Shaderのタイプを指定しているのだろうか・・・
//surfaceタイプstandardタイプ・・・?
#pragma surface surf Standard fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
//shaderのバージョンを指定しているのだろうか・・・?
//shader ver 3.0 ?
#pragma target 3.0
//sampler2D型? 引数名が_MainTex?
sampler2D _MainTex;

//Input内で指定した変数?はsurf関数内で使えるらしい
//uv_MainTexは_MainTexのこと?
struct Input {
float2 uv_MainTex;
};

//_Glossinessって型じゃなかったんだ・・・
//halfは確かfloatっぽいやつだった気がする
half _Glossiness;
half _Metallic;
//fixed4はVector4ってことかな
fixed4 _Color;

//ここで実際にごにょごにょしている
//ここはピクセルごとに処理をしているのだろうか、頂点ごとに処理しているのだろうか・・・
//Inputが入力された値?
//inoutのSurfaceOutputStandardに結果を入力するって感じかな
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
//Textureに色を加えたやつを
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
//Albedoに代入
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
//フォールバック?はこのShaderが使えなかった時の代理Shaderをしているって聞いたことがある
FallBack "Diffuse"
}


surf関数でごにょごにょしてて、そこが一番大事ということが分かった。

今までのプログラミング経験からいろいろと推測できる部分もある。


凹みさんの記事を読んでみる

素晴らしい記事があったので読んでみる

http://tips.hecomi.com/entry/2014/03/16/233943

サーフェイスシェーダは、CGPROGRAM 〜 ENDCG ブロック内に記述します。

なるほど、やっぱりそうなんだね。

最初の行にある pragma 文は、サーフェイスシェーダをどのようにコンパイルするかを指示しています。

#pragma surface surfaceFunction lightModel [optionalparams]

ふむふむ。pragma文をいじることでシェーダの結果が変わるのか

「surfaceFunction」はエントリポイントで、サンプルでは「surf」という名前の関数がそれですよ、と指定しています。

なるほど、サンプルでは#pragma surface surf Standard fullforwardshadowsと書いてあったから

surf関数がエントリポイントになっていたのか。

 #pragma surface surf Lambert

「lightModel」は組み込みライティングモデルの「Lambert」(ランバート反射)を指定しています。ランバート反射は理想的な拡散モデルで、どの方向から見ても輝度が一定になるモデルです。他には BlinnPhong(スペキュラ)があり、自作のカスタムライティングモデルも指定できます

なるほど、エントリポイントの次にライティング方式を指定するのね。

UnityでStandardシェーダーを作成した場合、デフォルトで記入されているのは#pragma surface surf Standard fullforwardshadowsだから、ライティング方式はStandardになるのか。

「optionalparams」には透過させたいときは alpha、頂点の変形をしたい場合は vertex などの指定が行えます。

optionalparamsで指定がしないと使えない機能があるのね。usingみたいなものか

エントリポイントのsurf関数について

void surf (Input IN, inout SurfaceOutput o) {

o.Albedo = _MainColor.rgb;
}

形式としては、Input を受け取り、それをゴニョゴニョと処理を行って SurfaceOutput につめる、という形になっています。ちなみに何もしないと真っ黒になります。
SurfaceOutput には Albedo を指定しています。アルベドは反射の割合を示していて 0 にすると真っ黒になります。

なるほど、Inputを加工してOutputに処理を詰め込むというわけですな。

struct SurfaceOutput {

half3 Albedo;
half3 Normal;
half3 Emission;
half Specular;
half Gloss;
half Alpha;
};

SurfaceOutputはこんな感じの構造体らしい。思ったよりパラメータないのね。これがSurfaceシェーダーでできることが少ない理由なのかな

struct Input {

float4 color : COLOR;
};
Input では UV 座標などが渡ってきます(Properties 等で設定した変数ではないです)。ここでは未だ例が微妙なので詳しくは後で見ていきます。

「Properties 等で設定した変数ではないです」ってどういう意味なんだろう。全くの無関係ではないとは思うけど・・・

Properties で与えられた変数を参照するためには、その変数を CGPROGRAM 〜 ENDCG ブロック内で宣言しておく必要があります。例では、surf 直前の行で宣言しています。

なるほど、

        //sampler2D型? 引数名が_MainTex?

sampler2D _MainTex;

こういうのをCGPROGRAM ~ ENDCG内に書くことでCg/HLSLとPropertiesをつなげているのか

pragma 文の後ろに alpha を追加しました。

これは、シェーダがアルファブレンディングを行いますよ!ということを指示しているものです。
この上で、surf の中で Alpha を 0.5 に設定しています。
ついでに Albedo で、青と緑は 0.5 しか反射しないようにしています
Shader "Example/Diffuse Simple" {
Properties {
_MainColor("Color", Color) = (1,1,1)
}
SubShader {
Tags { "RenderType" = "Transparent" }
CGPROGRAM
float4 _MainColor;
#pragma surface surf Lambert alpha
struct Input {
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = _MainColor.rgb * half3(1, 0.5, 0.5);
o.Alpha = 0.5;
}
ENDCG
}
Fallback "Diffuse"
}

このshaderを実行してみたけど画面に何も写らなくなった。

Alpha値をいじると完全に透過してしまうのか、Cameraの設定が悪いのか・・・

Tags ブロックでは「いつ」「どのように」レンダリングを行うかの指定を Key-Value の形式で与えます。最初の例では RenderType を Opaque (不透明)に指定しています。透明な方は Transparent を指定しています。

なるほど、良く見るOpaqueは不透明を意味していたのか、透過処理は重たいらしいから基本的にOpaqueを使用するのだろう

テクスチャの UV 座標を参照するためには Input 内で対象の変数の頭に uv プレフィックスを付加した変数を宣言します。

なるほど、uv_MainTexでuv座標を持った値を持ってこれるのね

        struct Input {

float2 uv_MainTex;
};

これを Cg の組み込み関数の tex2D を使って、該当するテクスチャ座標の色を参照しています。

void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}

tex2DはCgの関数なのか。tex2D(Textureを入れる,UV座標を入れる)って感じかな

Vertex(頂点)シェーダーについて

Shader "Example/Transform Vertex" {

SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert vertex:vert
struct Input {
float4 color : COLOR;
};
void vert (inout appdata_full v) {
v.vertex.x += 0.2 * v.normal.x * sin(v.vertex.y * 3.14 * 16);
v.vertex.z += 0.2 * v.normal.z * sin(v.vertex.y * 3.14 * 16);
}
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = half3(1, 0.5, 0.5);
}
ENDCG
}
Fallback "Diffuse"
}

ここを見ると、

 #pragma surface surf Lambert vertex:vert

surfaceを使っていて、エントリポイントはsurf関数。ライティングはLambertでvertexを使用している(vert関数)って感じかな

        void vert (inout appdata_full v) {

v.vertex.x += 0.2 * v.normal.x * sin(v.vertex.y * 3.14 * 16);
v.vertex.z += 0.2 * v.normal.z * sin(v.vertex.y * 3.14 * 16);
}

引数のappdata_full v をいじることで頂点を操作しているのだろう。

このシェーダーを実行するとこんな感じになった

スクリーンショット 2016-08-03 11.08.22.png

当たり前だけど当たり判定はBoxColliderだからBoxのままなんだな。


頂点変形後のMeshColliderの当たり判定について

MeshColliderだと頂点変形後に合わせて当たり判定がつくのかなと思ったのでやってみた。

BoxColliderを外してMeshCollider+Rigidbodyをつける。

すると、

Non-convex MeshCollider with non-kinematic Rigidbody is no longer supported in Unity 5.

というエラーがでた。

スクリーンショット 2016-08-03 11.11.53.png

Unity5だとMeshColliderのConvex(いい感じに当たり判定を凸型にして処理を軽くしてくれるやつ)をtrueにしていないとだめってことか。

Convexにチェックを入れると、

Cooking::cookConvexMesh: user-provided hull must have less than 256 vertices!

というエラーが出た。頂点数が256未満じゃないとConvexできないよ、ってことかな。

頂点数どのくらいあるのだろうかGame ViewのStatusから見てみると、

DEMO.unity - UFPSTest - PC, Mac & Linux Standalone <OpenGL 4.1> 2016-08-03 11-20-18.jpg

8.1k = 81,000頂点??

そんな馬鹿な


頂点数を減らす


  • MainCamera

  • DirectionalLight

  • Sphere(Vertexシェーダーで加工済み)
    の3GameObjectしかないのにこんなにあるはずがないと思って調査してみました。

・Directional LightのShadow TypeがSoft ShadowsになっていたのでNo Shadowsに変更

8.1k => 5.6k

(Soft ShadowsとHard Shadowsを入れ替えても頂点数は変わらなかった。影があることの負荷ってすごいのね)

・MainCameraのClear FlagがSkyboxになっていたので、Solid Colorに変更

5.6k => 515

Skyboxの頂点数すごいな

(Clear FlagをSolid Color,Depth Only,Don't Clearにしても頂点数は515になった)

この515がSphereの頂点数なのだろうか。

・Sphere GameObjectを削除してみると、

515 => 0

無事頂点数が0になりました。

スクリーンショット 2016-08-03 11.31.07.png

頂点をVertexシェーダーによって変更したから頂点数が515もあったのだろうか。

そう思ってVertexシェーダーの処理をコメントアウトして普通のShereに戻してみたが、

頂点数は515のままだった。今回のVertexシェーダーでは頂点を移動させていただけで頂点数を増減していたわけじゃないから当たり前か。。。

変だなと思ったのは、Vertexシェーダーで頂点をいじった時のほうがFPS値が高い+Render Threadは低いことだ。

頂点変形あり(272.6FPS、render threae 1.2ms)

DEMO.unity - UFPSTest - PC, Mac & Linux Standalone <OpenGL 4.1> 2016-08-03 11-36-14.jpg

頂点変形なし(230.7FPS、render thread 2.0ms)

DEMO.unity - UFPSTest - PC, Mac & Linux Standalone <OpenGL 4.1> 2016-08-03 11-37-08.jpg

render threadが何を表しているのかはわからないが、FPS値は高いほうが良いのは知ってる。

Sphereのレンダリングは思ったよりGPUに負荷がかかるのだろうか・・・?

render threadはテラシュールブログさんの記事によると、

http://tsubakit1.hateblo.jp/entry/2016/05/09/073000

Main Thread以外の、例えばRender ThreadやUnity Job Systemは、メインスレッドを止めない別スレッドの処理です(※ユーザーの作ったスレッドは表示されません)。

ここではCanvasの再構築とかParticleの計算とかレンダリングとか、そういった処理の負荷を確認出来ます。

とのこと。Render Threadが高いと負荷が高いということかな。


話を戻す・・・

Vertexシェーダーによって頂点数が増減されない以上、515がSphereの頂点数と考えて良いだろう。

もともと515の頂点数があるならこのエラーは取れなさそうだ。

Cooking::cookConvexMesh: user-provided hull must have less than 256 vertices!

そもそもSphereにMeshColliderをつけるほうがおかしいのだろう。

ShpereColliderがあるし、、、。

このままだと頂点変形をした後の頂点位置にMeshColliderの当たり判定がつくのかどうかがわからないので、

頂点数が少ないであろうCubeにVertexシェーダーをつけて試して見る。

vertexシェーダーはこんな感じで。

        void vert (inout appdata_full v) {

v.vertex.x += 1.2 * v.vertex.y;
}

これが、

スクリーンショット 2016-08-03 12.21.33.png

こうなる!

スクリーンショット 2016-08-03 12.21.56.png

そしてMeshRendererのConvexにチェックを入れるとこうなる。

スクリーンショット 2016-08-03 12.24.49.png

頂点変形後であろうとなかろうとMeshColliderは元のMeshに沿って当たり判定をつけるらしい。

ShaderはGPU側で処理されて、物理処理はCPU側で処理されるから当たり前か・・・。

ちなみにMeshRendererのConvexにチェックを入れないと当たり判定の枠が見えないので分かりづらいが、

元のMeshに沿って当たり判定がつけられる。

適当なCubeにRigidbodyをつけて上から落としてみると、Cubeメッシュにあたって止まる。

スクリーンショット 2016-08-03 12.24.35.png


頂点シェーダーとフラグメントシェーダーについて

引き続き凹みさんの記事を読んでいく。

もっとも柔軟性の高いのが、この頂点シェーダ / フラグメントシェーダです。ライティングも行いたいならサーフェイスシェーダ、プログラマブルな形状やテクスチャを作成したいならこちら、みたいなイメージだと思います。

なるほど、、、。さっきのサーフェイスシェーダーで頂点操作しているからサーフェイスシェーダーも頂点シェーダー(Vertexシェーダー)と読んでいたけど、違うのかな。サーフェイスシェーダーはサーフェイスシェーダーであって、たとえ頂点をいじっていたとしてもVertexシェーダーとは言わないのかもしれない。

ちなみにUnityでUnitShaderを作成するとデフォルトでこんなコードが書いてある。

Shader "Unlit/NewUnlitShader"

{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

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 = mul(UNITY_MATRIX_MVP, 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
}
}
}

ぱっと見違うのはPassという記述だろうか。それ以外はサーフェイスシェーダーとほとんど似ている。

CubeにこのUnitShaderをつけてみたところ、たしかにライティング処理がされていなかった。

真っ白やわ

スクリーンショット 2016-08-03 12.38.50.png

テクスチャを指定していないのに色がつくのはPropertiesでこう定義しているからか。

    Properties

{
_MainTex ("Texture", 2D) = "white" {}
}

凹みさん記事のコードをみると、次のように書いてある。

Shader "Custom/SolidColor" {

SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION) : SV_POSITION {
return mul (UNITY_MATRIX_MVP, v);
}
fixed4 frag() : COLOR {
return fixed4(1.0,0.0,0.0,1.0);
}
ENDCG
}
}
}

ずいぶんスッキリしている。これがVertexシェーダーやフラグメントシェーダーの最小コードなのだろうか。Passでくくってあるのが特徴的だ。(Passってなんだ?)


Passとは?

http://docs.unity3d.com/ja/current/Manual/SL-Pass.html

ShaderLab: Pass

Pass ブロックはオブジェクト形状を一回のみレンダリングするようにします。

凹みさんの記事

Pass ブロックの中には色々な命令を記述でき、ここでは Color の指定を行っています。そして、固定関数シェーダは Pass のみからなるシェーダになります。

なるほど、Passで囲むことでグルーピングしてるってことかな。

Passの中に書き込むもの=>フラグメントシェーダーやVertexシェーダー、固定関数シェーダー

Passに書かなくても良いもの=>サーフェイスシェーダー

という感じなのだろうか(適当な予想)


話を戻す

先ほどのシェーダーをCubeにつけて実行してみると、

スクリーンショット 2016-08-03 12.53.32.png

the赤って感じだ。

赤になる理由は

            fixed4 frag() : COLOR {

return fixed4(1.0,0.0,0.0,1.0);
}

ここの記述でRGBAを指定しているからだろう。おそらくshaderのRGBAは0~1の間を取るのがわかる。(Unityは0~255まで取るのに・・・どっちかに統一して欲しい)

fixed4はfloatを4つ(RGBA)取る型なのはわかるが、COLORは何を表しているのだろう。COLORも型なのかな。fixed4でCOLOR型ということか?

ピクセル一つずつに対してfrag関数が呼ばれる=>全てのピクセルに「赤色で塗れ」という命令が出されているのだろう。

vert は頂点シェーダで、すべての頂点に対して座標変換を行います。

ここでは、引数で与えられた頂点座標に UNITY_MATRIX_MVP という組み込み値である MVP 行列(モデル・ビュー・プロジェクション行列)をかけてモデルが2次元のディスプレイでどの位置に見えるかの計算を行っています

3Dモデルを2D変換している・・・というよりは、TransformのRotationによって実際カメラにどのように映るかを計算しているということかな。


モデル・ビュー・プロジェクション行列とは?

UNITY_MATRIX_MVPで検索してもよくわからなかったのでモデル・ビュー・プロジェクション行列を調べて見る。

こちらによると、

https://wgld.org/d/webgl/w013.html

一つ目はモデル変換行列で、DirectX などではワールド変換行列とも呼ばれるものです。モデル変換行列はその名のとおり、描画されるモデルに対して影響を与えます。モデルの位置、モデルの回転、モデルの拡大縮小(スケーリング)に関する情報を持たせるのが普通です。

二つ目はビュー変換行列です。これはわかりやすく言うと三次元空間を撮影するカメラを定義するためのものですね。カメラの位置、カメラの注視点、カメラの上方向を定義することで、カメラがどのように振舞うのかを決めます。

三つ目はプロジェクション変換行列です。この座標変換ではスクリーンの縦横比や、クリッピング領域などを定義します。また遠近法のような効果を得るためにもこの変換行列が必要になります。

モデル・ビュー・プロジェクション行列は、モデルビュープロジェクション行列という行列があるのではなくて、モデル行列/ビュー行列/プロジェクション行列の3つの行列を指しているのだろうか。

UNITY_MATRIX_MVPとVをかけることで頂点をモデル変換行列、ビュー変換行列、プロジェクション変換行列をしているのかな。

mul (UNITY_MATRIX_MVP, v);のmul関数は第一引数と第二引数を掛け算する関数。

UNITY_MATRIX_MVP * vでも同じような計算結果がでるのかと思ってやってみたところ、

            float4 vert(float4 v:POSITION) : SV_POSITION {

//return mul(UNITY_MATRIX_MVP,v);
return UNITY_MATRIX_MVP * v;
}

思いっきりコンパイルエラーがでた。

mul関数を使うことで初めてUNITY_MATRIX_MVPという特別なキーワードを使って掛け算することができるのかもしれない。

mul関数の第一引数と第二引数を入れ替えるとどうなるのかな

            float4 vert(float4 v:POSITION) : SV_POSITION {

return mul(v,UNITY_MATRIX_MVP);
//return UNITY_MATRIX_MVP * v;
}

スクリーンショット 2016-08-03 13.24.39.png

なんだが気持ち悪い感じになってしまった。順番は入れ替えてはいけないらしい。

かけないでそのままvをreturnした場合はどうなろうのだろう

            float4 vert(float4 v:POSITION) : SV_POSITION {

//return mul(UNITY_MATRIX_MVP,v);
return v;
}

スクリーンショット 2016-08-03 13.25.57.png

カメラにCubeがちょっとでも映ってる=>カメラ平面に四角形が固定されて表示

カメラのCubeが映っていない=>カメラに四角形は表示されない

という感じだ。これカメラに映っていないものはそもそも描画しない処理が行われているからかな。(そもそもvert関数が呼ばれない)

return vをするとなぜ四角形が表示されるのだろう。

MeshをcubeからSphereに変えてみると、

スクリーンショット 2016-08-03 13.29.43.png

楕円形が表示されるのがわかる。

return vをすると四角形が表示されるというわけではなく、Meshに依存しているのがわかる。

うーん、でもなんで楕円形や長方形という横長の形で描画されるんだろう・・・。まだ理解できない分野なので飛ばす。

mul(UNITY_MATRIX_MVP,v)で各頂点を行列変換していい感じにカメラに写していることがわかった。

これも大切

https://wgld.org/d/webgl/w013.html

回転やスケーリングなどを行うことが可能です。

ただし、ここで注意点があります。それは、移動・回転・拡大縮小を行なう順序です。
移動してから回転するのと、回転してから移動するのでは、結果がまるで変わってきます。これは、回転が原点(0.0, 0.0, 0.0)を中心に行なわれるためです。
モデル変換を行なう順序には十分に気を配りましょう。

具体的には、拡大縮小 > 回転 > 移動、という順序で変換を行なうようにすることで、回転・拡大縮小した状態のモデルを任意の位置に移動させることができるはずですね。

拡大縮小 => 回転 => 移動 という順番でモデル行列変換しているのね。


フラグメントシェーダーについて

frag はフラグメントシェーダで、すべてのピクセルに対して計算を行います。

なるほど、負荷高そうだな。そういえば負荷対策としてフラグメントシェーダーの処理をバーテックスシェーダーに移したという話をちらっと聞いたことがあるな(そんなことできるのか?)

ここまでは GLSL などに触れていた方は馴染みのある内容だと思いますが、「: POSITION」「: SV_POSITION」「: COLOR」となっている場所が気になりますね。

これはセマンティクスと呼ばれるもので、グラフィックパイプラインの各ステージ間で伝達する変数が何なのかを伝えるタグの役割をします。GLSL の varying 変数みたいな感じですね。

:COLORとかはセマンティクスというのか。結構重要なタグらしい。

:COLORを:COLORAにしたらコンパイルエラーが発生した。

vert は float4 型の引数を受け取るよう指定されていますが、float4 の何を貰えばいいのか?というのを指定している形になり、ここでは POSITION、つまり頂点のモデルに対するローカル位置座標を取得しています。セマンティクスは、頂点シェーダの入出力とフラグメントシェーダの入出力で計4つ与える箇所があります。

float4のどんな情報なのか、というのを定義できるのか。メソッドのオーバーロードとかは使えないから同じ型で意味合いだけ別途指定しているのかもしれない(適当)

例ではフラグメントシェーダの入力は省略されているので3つになっています。

フラグメントシェーダーも入力を受け取れるのか

vert の出力が POSITION でなく、SV_POSITION となっているのは DX11 への互換性確保のためのようです。

ふむふむ。UV_なんちゃらの親戚かとおもったら、別にそういうわけじゃなくて互換性のためだったのね。

Pre-defined なセマンティクスを引数と返り値に与える以外にも、

必要なセマンティクスをパックしたユーザ定義型を引数として与えることも可能です。

SubShader {
Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma target 3.0

struct appdata_base {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};

float4 vert(appdata_base v) : POSITION {
return mul (UNITY_MATRIX_MVP, v.vertex);
}

fixed4 frag(float4 sp:WPOS) : COLOR {
fixed2 red_green = sp.xy / _ScreenParams.xy;
fixed blue = 0.0;
fixed alpha = 1.0f;
return fixed4(red_green, blue, alpha);
}

ENDCG
}
}

スクリーンショット 2016-08-03 14.43.13.png

appdata_base は UnityCG.cginc というファイル内で宣言されており、

これをインクルードすると使えるようにもなります。

なるほど#include "UnityCG.cginc"を書くことでappdata_baseは宣言しなくても使えるようになるのね。これ以外にも色々とヘルパ関数があるらしい。

frag の引数を見てみると、WPOS というセマンティクスが与えられています。これはフラグメントシェーダでスクリーン座標を参照することが出来る変数になります。

なるほど、WPOSは今処理しているピクセルのスクリーン座標0 ~ ???の値がとれるのね。

WPOS = World Positionなのかな。Worldというから3D世界のワールド座標かと思ったらスクリーン座標なのね。

組み込み値の _ScreenParams.xy で割ってあげると、0 〜 1 でスクリーンの左下が (0, 0)、右上が (1, 1) になります。

WPOSそのままだと値を操作しずらいと聞いたことがある。そのため正規化?をして0~1にするらしい。

                fixed2 red_green = sp.xy / _ScreenParams.xy;

fixed blue = 0.0;
fixed alpha = 1.0f;
return fixed4(red_green, blue, alpha);

sp.xy / _ScreenParams.xyの計算結果はfixed4(red_green,blue,alpha)で使用されている。

fixed4(red_green,blue,alpha)は展開されてfixed4(red_green.x,red_green.y,blue,alpha)になるらしい。

なんか変な感じだけど。。。

オブジェクトが左に移るほどred_green.xの値が0に近づくので黒くなる。

オブジェクトが右に移るほどred_green.xの値が1に近づくので白くなる。

(blue値が固定されているので実際には真っ黒/真っ白になるわけじゃないけど)

ちなみに

        fixed4 frag(float4 sp:WPOS) :COLOR {

fixed2 red_green = sp.xy / _ScreenParams.xy;
fixed blue = 0.0;
fixed alpha = 1.0;
//return fixed4(red_green,blue,alpha);
return fixed4(red_green.x,red_green.x,red_green.x,1.0);
}

と書き換えると、こんな感じになる。

スクリーンショット 2016-08-03 14.54.06.png

CameraPreviewに写っているCubeが真っ白なのは、Scene Windowの右端に写っているからかな?(もし別途カメラが存在している場合は、そのカメラのWPOSに沿って黒白になると思う)


UVから色をつける

    SubShader {

Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"

float4 frag(v2f_img i) : COLOR {
return float4(i.uv,0.0,1.0);
}

ENDCG
}
}

スクリーンショット 2016-08-03 15.13.49.png

UVが何なのかいまいち理解していないが、面ごとに規則性をもってUV値が変化していることがわかる。

こんな感じにコードを変えてみると、

            float4 frag(v2f_img i) : COLOR {

return float4(i.u,i.v.0,1.0);
}

エラーがでる;;

v2f_img i のi.uvは、i.uやi.vというわけではないらしい。float2型(fixed2型?)でi.uv.xやi.uv.yでUV値にアクセスすることができる。

これで元のコードと同じものができる。

            float4 frag(v2f_img i) : COLOR {

return float4(i.uv.x,i.uv.y,0,1.0);
}

UVのX値だけ見てみる。

            float4 frag(v2f_img i) : COLOR {

return float4(i.uv.x,0.0,0,1.0);
}

スクリーンショット 2016-08-03 15.18.06.png

UV.xは左側が0、右に行くにつれて1になるみたいだ。

UV.yは下側が0、上に行くにつれて1になるみたいだ。

ポリゴンというのは三角形だと思ったが、結果を見る限り、四角形(1面)毎にUVが完結しているようにみえる。

Sphereでやってみるとこんな感じ

スクリーンショット 2016-08-03 15.21.18.png

vertexに基づいて何かをやるときはUV値がどのように作られているか知っておく必要がありそうだ。


頂点シェーダー,フラグメントシェーダーでPropertyを使う

    Properties {

_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag

#include "UnityCG.cginc"

uniform sampler2D _MainTex;

float4 frag(v2f_img i) : COLOR {
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}

Properties で記載した変数を、uniform 修飾子をつけて CGPROGRAM ブロック内で宣言すると、頂点シェーダ / フラグメントシェーダで使えるようになります。

なるほど、uniform修飾詞をつけると使えるようになるのか。サーフィスシェーダーのときはなくてもできたのにね。