Unityな日々(Unity Geek)

Unityで可視化アプリを開発するための試行錯誤の覚書

ShaderLabの基礎:Culling & Depth Testingを理解する

参考:ShaderLab syntax: Culling & Depth Testing

f:id:yasuda0404:20150214204213p:plain

英語の'cull'は「選びとる・間引く」という意味だが、CGの世界では「除外する」ととらえるとわかりすい。たとえば、「バックフェース・カリング」はポリゴンの裏面を表示しない処理になる。また、オブジェクトのもっとも近いサーフィス以外を表示しない「デプス・テスト」もカリングの一種。


Cull Back|Front|Off ポリゴンのどの面をカリングするかの指定。 デフォルトは'Back'で、裏面を表示しない。'Off'は両面を表示。

ZWrite On | Off このオブジェクトのからのピクセルをDepth Bufferに書き込むかどうか。デフォルトはOn。

ZTest Less | Greater | LEqual | GEqual | Equal | NotEqual | Always Depth Testの方法。デフォルトは'LEequal' = 存在するオブジェクトより近いものと同じ距離にあるものは表示、それより背後にあるものは非表示)

Offset Factor, Units Depthパラメータのオフセット。FactorはZの最大値を、X,Y値に対してスケーリングする。UnitsはDepth Buffer値の最小値をスケーリングする。 これによって、あるポリゴンを常に上側に表示できる。たとえば、'Offset 0, -1'は、ポリゴンの傾きを無視してポリゴンをカメラに近づける。一方、'Offset -1, -1'は、ポリゴンをさらに近くに表示する。


サンプル

1. オブジェクトの裏面のみをレンダリングする

Shader "Show Insides" {
    SubShader {
        Pass {
            Material {
                Diffuse (1,1,1,1)
            }
            Lighting On
            Cull Front
        }
    }
}


2. 深度を考慮したトランスペアレント・シェーダ

通常のトランスペアレント・シェーダではDepthを考慮せず、単純に描画順で前後が決まるらしい。これを回避するには、深度情報を考慮したトランスペアレント・シェーダを作る必要がある。下がその例。 参考:Unity で Transparent/Diffuse で描画順が崩れてしまう際の対処法

ZWrite OnでDepth Bufferに書き込み、ColorMask 0レンダリングはしない。そのかわりに

Shader "Transparent/Diffuse ZWrite" {
Properties {
    _Color ("Main Color", Color) = (1,1,1,1)
    _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
}
SubShader {
    Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    LOD 200

    // extra pass that renders to depth buffer only
    Pass {
        ZWrite On
        ColorMask 0
    }

    // paste in forward rendering passes from Transparent/Diffuse
    UsePass "Transparent/Diffuse/FORWARD"
}
Fallback "Transparent/VertexLit"
}


3. 法線ベクトルの修正

最初のパスでは通常のVertex Lightingでレンダリングし、2番めのパスでオブジェクトの背面をピンク色でレンダリングする。裏向きポリゴンを発見する際に便利なシェーダ。

Shader "Reveal Backfaces" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" { }
    }
    SubShader {
        // Render the front-facing parts of the object.
        // We use a simple white material, and apply the main texture.
        Pass {
            Material {
                Diffuse (1,1,1,1)
            }
            Lighting On
            SetTexture [_MainTex] {
                Combine Primary * Texture
            }
        }

        // Now we render the back-facing triangles in the most
        // irritating color in the world: BRIGHT PINK!
        Pass {
            Color (1,0,1,1)
            Cull Front
        }
    }
}


4. グラス・カリング

ガラスのような半透明なオブジェクトでは、オブジェクトの裏面が透けて見えるようにしたい。そのためのシェーダ。裏面と表面を2パスでレンダリングする。 spheres, cubes, car windscreensなどの凸形状のオブジェクト適用できる。

Shader "Simple Glass" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,0)
        _SpecColor ("Spec Color", Color) = (1,1,1,1)
        _Emission ("Emmisive Color", Color) = (0,0,0,0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.7
        _MainTex ("Base (RGB)", 2D) = "white" { }
    }

    SubShader {
        // We use the material in many passes by defining them in the subshader.
        // Anything defined here becomes default values for all contained passes.
        Material {
            Diffuse [_Color]
            Ambient [_Color]
            Shininess [_Shininess]
            Specular [_SpecColor]
            Emission [_Emission]
        }
        Lighting On
        SeparateSpecular On

        // Set up alpha blending
        Blend SrcAlpha OneMinusSrcAlpha

        // Render the back facing parts of the object.
        // If the object is convex, these will always be further away
        // than the front-faces.
        Pass {
            Cull Front
            SetTexture [_MainTex] {
                Combine Primary * Texture
            }
        }
        // Render the parts of the object facing us.
        // If the object is convex, these will be closer than the
        // back-faces.
        Pass {
            Cull Back
            SetTexture [_MainTex] {
                Combine Primary * Texture
            }
        }
    }
}

ShaderLabの基礎:Texture Combinersを理解する

f:id:yasuda0404:20150215211036p:plain

参考:ShaderLab syntax: Texture Combiners

基本的なVertex Lightingが計算された後、テクスチャが適用される。これはSetTexture()コマンドによっておこなわれる。

基本形は、SetTexture テクスチャ名 テクスチャブロックとなる。

※フラグメント・プログラムが有効な場合、SetTextureコマンドは無効になる。すなわち、ピクセル操作はすべてシェーダで行われる。

テクスチャブロックは、テクスチャがどのように適用されるかを指定するもので、combine,constantColor,matrixの3つからなる。

1. combine

テクスチャ同士をどのように合成するかの指定。

  • combine src1 * src2 2つのソースを乗算で合成。いずれよりも暗くなる。

  • combine src1 + src2 加算で合成。どちらよりも明るくなる。

  • combine src1 - src2 ソース1からソース2を減じる。

  • combine src1 +- src2 ソース1とソース2を加算し0.5を引く=符号付でソース2を加える。

  • combine src1 lerp (src2) src3 ソース2のアルファ値を使って、ソース1とソース3を内挿する。アルファ1の時ソース1となり、アルファ0のときソース3となる。

  • combine src1 * src2 + src3 ソース1にソース2のアルファ値を掛け、ソース3を加える。

  • combine src1 * src2 +- src3 ソース1にソース2のアルファ値を掛け、符号付でソース3を加える。

  • combine src1 * src2 - src3 ソース1にソース2のアルファ値を掛け、ソース3を引く。

ソース すべてのソースは次のどれかになる。

  • previous 直前のSetTextureの結果
  • primary ライティング計算結果の色、または、頂点カラー(バインドされている場合)
  • texture TextureNameで指定したテクスチャの色
  • constant ConstantColorで指定した色

モディファイヤ

  • 上の指定のあとにオプションとして、Double(2倍)、Quad(4倍)のキーワードを付加し、明度をあげることができる。
  • lerp以外のソースは、前に1をつけることでカラーは無効になる
  • ソースの後にalphaをつければ、アルファチャンネルのみを取り出す。

2. constantColor

combineコマンドで使われるコンスタントカラーを指定

3. matrix

matrix[マトリクス・プロパティ名] 与えたマトリクスでテクスチャ座標を変換する。

フラグメント・プログラミングができる前の古いグラフィック・カードは、テクスチャをひとつずつ重ね合わせながら色を計算していた。

「純粋な固定関数デバイス」 (OpenGL, OpenGL ES 1.1, Wii) は、各SetTextureの値が0~1に制限されていたが、それ以降 (Direct3D, OpenGL ES 2.0)はその制限がなくなった。これが、SetTextureの値に影響する恐れがある。

カラーとアルファの分離 通常、combineRGBカラーとアルファを同じ方法で合成するが、カラーとアルファを異なった方法で合成することも可能。 次のケースでは、RGBカラーは乗算で、アルファは加算で合成している。

SetTexture [_MainTex] { combine previous * texture, previous + texture }

スペキュラ・ハイライト 通常、プライマリ・カラーは、ディフューズ、アンビエント、スペキュラの合計になる。しかし、SeparateSpecularをOnにした場合は、スペキュラカラーはCombine合成計算の後で別に加えられる。 ビルトイン・頂点シェーダーではこちらの方法がデフォルト。


サンプル

1.アルファブレンディング

Baseテクスチャに、Alpha Blendedテクスチャの色を、Alppha Blendedテクスチャのアルファチャンネル値で合成。 最初の'previous'は、頂点カラーとライティングが適用された状態をさす。これがベースカラーとなる。

Shader "Examples/2 Alpha Blended Textures" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BlendTex ("Alpha Blended (RGBA) ", 2D) = "white" {}
    }
    SubShader {
        Pass {
            // Apply base texture
            SetTexture [_MainTex] {
                combine texture
            }
            // Blend in the alpha texture using the lerp operator
            SetTexture [_BlendTex] {
                combine texture lerp (texture) previous
            }
        }
    }
}


2.アルファ制御セルフ・イルミネーション

_MainTexのアルファ値でどこにライトを適用するかを決定する。 第1段階で、頂点カラーと白色を_MainTexのアルファ値で合成。 第2段階で、第1段階の結果に_MainTexのカラーを乗算。 ※アルファがすべて1であれば、_MainTexのカラーそのものになる。アルファが1より小さい部部分は色が弱くなる。

Shader "Examples/Self-Illumination" {
    Properties {
        _MainTex ("Base (RGB) Self-Illumination (A)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            // Set up basic white vertex lighting
            Material {
                Diffuse (1,1,1,1)
                Ambient (1,1,1,1)
            }
            Lighting On

            // Use texture alpha to blend up to white (= full illumination)
            SetTexture [_MainTex] {
                constantColor (1,1,1,1)
                combine constant lerp(texture) previous
            }
            // Multiply in texture
            SetTexture [_MainTex] {
                combine previous * texture
            }
        }
    }
}


_MainTexのアルファ値を使って、_IlluminCol(=constant)previousを合成、それに_MainTexの色を乗算。イルミネーションを白色ではなく、任意のカラーに設定するもの。

Shader "Examples/Self-Illumination 2" {
    Properties {
        _IlluminCol ("Self-Illumination color (RGB)", Color) = (1,1,1,1)
        _MainTex ("Base (RGB) Self-Illumination (A)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            // Set up basic white vertex lighting
            Material {
                Diffuse (1,1,1,1)
                Ambient (1,1,1,1)
            }
            Lighting On

            // Use texture alpha to blend up to white (= full illumination)
            SetTexture [_MainTex] {
                // Pull the color property into this blender
                constantColor [_IlluminCol]
                // And use the texture's alpha to blend between it and
                // vertex color
                combine constant lerp(texture) previous
            }
            // Multiply in texture
            SetTexture [_MainTex] {
                combine previous * texture
            }
        }
    }
}


すべてのLightを適用したもの。 最初のpreviousは、Diffuse,Ambient, Shiness, Specular, Emissionが適用された状態になる。そこに、_IlluminCol_MainTexのアルファ値で合成し、さらに_MainTexのカラーを乗算する。

Shader "Examples/Self-Illumination 3" {
    Properties {
        _IlluminCol ("Self-Illumination color (RGB)", Color) = (1,1,1,1)
        _Color ("Main Color", Color) = (1,1,1,0)
        _SpecColor ("Spec Color", Color) = (1,1,1,1)
        _Emission ("Emmisive Color", Color) = (0,0,0,0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.7
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader {
        Pass {
            // Set up basic vertex lighting
            Material {
                Diffuse [_Color]
                Ambient [_Color]
                Shininess [_Shininess]
                Specular [_SpecColor]
                Emission [_Emission]
            }
            Lighting On

            // Use texture alpha to blend up to white (= full illumination)
            SetTexture [_MainTex] {
                constantColor [_IlluminCol]
                combine constant lerp(texture) previous
            }
            // Multiply in texture
            SetTexture [_MainTex] {
                combine previous * texture
            }
        }
    }
}

ShaderLabの基礎:SubshaderとColor, Material, Lightingを理解する

1つのShaderには複数のSubshaderを記述できる。Unityは上からSubshaderを見ていき、現在のマシン(GraphicCard)に適用できるシェーダがあれば、それを適用する。

1つのSubshaderの中には、1つ以上のレンダリングパス(Pass)を指定できる。また、オプションとして特定のタグ(Tag)を指定できる。

レンダリングパスには - Regular Pass (Pass) - Use Pass (UsePass) - Grab Pass (GrapPass) の3種類がある。

次の例は、シングルパスで、ライトを消し、_MainTexというテクスチャを設定してメッシュを表示するもの。

// ...
SubShader {
    Pass {
        Lighting Off
        SetTexture [_MainTex] {}
    }
}
// ...

Passは Pass {[Name and Tags\] [RenderSetup\] [TextureSetup] } という構造になる。

Name and Tagsは、Passの名前と、複数の任意のタグを指定するもの。

  1. レンダリング設定

パスの中にはレンダリングに関するさまざまな設定を記述する。レンダリング設定には次のブロックがある。

  • Material 下のMaterialブロックを参照
  • Lighting: Materialブロックの設定を有効にするには、LightingをOnにする必要あり。
  • Cull
  • ZTest
  • ZWrite
  • Fog
  • AlphaTest
  • Blend
  • Color
  • ColorMask
  • Offset
  • SeparateSpecular: テクスチャリングの後にスペキュラを設定するか?
  • ColorMaterial: Materialの設定カラーではなく、Per-vertexカラーを使う。AmbientAndDiffuseとEmissionの2つがある。

f:id:yasuda0404:20150214190647p:plain

レンダリングパスの最初は、Vertex ColoringとLighting。ここではColor、Material、Lightingが関係する。

Materialブロックには次の設定がある。

  • Diffuse color:オブジェクトの基本色

  • Ambient color: アンビエントライトの色

  • Specular color:スペキュラの色

  • Shininess number:スペキュラの集中度。0(全体に拡散)~1(一点に集中)

  • Emission color: オブジェクトの発光色。ライトの影響を受けない。

サンプル

Shader "VertexLit" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,0)
        _SpecColor ("Spec Color", Color) = (1,1,1,1)
        _Emission ("Emmisive Color", Color) = (0,0,0,0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.7
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            Material {
                Diffuse [_Color]
                Ambient [_Color]
                Shininess [_Shininess]
                Specular [_SpecColor]
                Emission [_Emission]
            }
            Lighting On
            SeparateSpecular On
            SetTexture [_MainTex] {
                Combine texture * primary DOUBLE, texture * primary
            }
        }
    }
}

※SetTextureについては、ShaderLab:Texture Cobiners、または、ShaderLabの基礎:Texture Combinersを理解するを参照のこと。

ShaderLab:Propertiesの種類

Properties{}ブロックの中に記述するPropertyは次の種類がある。

  1. Float Range (Range):スライダで範囲指定
  2. Color(Color):RGBAの4次元配列。各値は0~1
  3. Texuture2D(2D):テクスチャ
  4. Rectangle(Rect):
  5. Cubemap(Cube):Cubemap
  6. Float(Float):不動小数点数
  7. Vector(Vector):ベクトル配列

サンプル

Properties {
    // properties for water shader
    _WaveScale ("Wave scale", Range (0.02,0.15)) = 0.07 // sliders
    _ReflDistort ("Reflection distort", Range (0,1.5)) = 0.5
    _RefrDistort ("Refraction distort", Range (0,1.5)) = 0.4
    _RefrColor ("Refraction color", Color) = (.34, .85, .92, 1) // color
    _ReflectionTex ("Environment Reflection", 2D) = "" {} // textures
    _RefractionTex ("Environment Refraction", 2D) = "" {}
    _Fresnel ("Fresnel (A) ", 2D) = "" {}
    _BumpMap ("Bumpmap (RGB) ", 2D) = "" {}
}

プロパティ名("表示名",種類) = デフォルト値

という構造はすべて同じ。

デフォルト値の指定方法はPropertyの種類によって異なる。 - Range/Float 数値 - Color/Vector 4次元配列 - 2D/Rect/Cube 空文字""、または、ビルトイン・テクスチャ名(“white”, “black”, “gray” or “bump")

テクスチャ系(2D/Rect/Cube)の {}内にはオプションを指定できる。

  • TexGen: texgenmode=テクスチャ座標を自動生成モード。ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormalのいずれかを指定。

  • LightmapMode:指定した場合はlightmapパラメータの影響を受ける。Renderer.lightmapIndex

Unity Shaderの基本構造

シェーダーとはレンダリングを行うプログラム。Unityには80以上のビルトイン・シェーダーが組み込まれている。

自分でシェーダーを書く場合の基礎知識をまとめる。Unity Manual/Shaders Overview

Shader全体はShderLabでラップされる。基本構造は次のとおり。

Shader "MyShader" {
    Properties {
        _MyTexture ("My Texture", 2D) = "white" { }
        // other properties like colors or vectors go here as well
    }
    SubShader {
        // here goes the 'meat' of your
        // - surface shader or
        // - vertex and fragment shader or
        // - fixed function shader
    }
    SubShader {
        // here goes a simpler version of the SubShader above that can run on older graphics cards
    }
}

UnityのReferenceには、まずShaderLabを理解してから各論に進め、と言っているので、ShaderLabについて。

そもそもシェーダとは、そのシェーダを適用したマテリアルがどのようにレンダリングされるかを定義するプログラム。

Shader "MyShader" { ... } でシェーダを定義する。1つのファイルに1つのシェーダしか定義できない。 "MyShader"には任意の名前をつける。"MyDirectory/MyShader"のようにすれば、MyDirectoryグループの下のMyShader、というように階層(グループ)構造にできる。

Properties {…}は、プロパティ、つまり、ユーザが入力・変更できる変数を定義する場所。オブジェクト指向言語でいえば、Public変数に対応する。

Subshaderは、 Subshader {...} Subshader {...} ... fallback というように、Shaderの中に複数のSubshaderを記述できる。Subshaderは少なくとも1つないといけない。 Shaderが読み込まれると、UnityはSubshaderのリストを上から見ていって、使用しているマシン(=Graphic Card)がサポート可能な最初のSubshaderを適用する。 もしどのSubshaderも使えない場合は、fallbackに指定したシェーダを使う。 これによって、ハイエンドユーザ向けのSubshader、ミドルエンド用、ローエンド用、と書き分けることができる。

    各Subshaderの書き方には次の3種類の方法がある。

  1. サーフィスシェーダ  「光と影」に関するシェーダ。つまりライトと組み合わせて使用される。逆にライトと関係ないならサーフィスシェーダは使うべきではない。Cg/HLSLを使える。

  2. 頂点シェーダ・フラグメントシェーダ  ライトと関係ない効果、あるいは、サーフィスシェーダでは扱えないような特殊な効果を得たいときに使う。Cg/HLSLを使える。  

  3. 固定関数シェーダ  プログラマブル・シェーダを使えない旧式、あるいはモバイル・ハードウェア用。ShaderLabのみで書かれる。

Inputの種類

Unityにはさまざまな入力APIが準備されている。
Unity - Scripting API: Input


Static関数としては次がある。Down/Up=Yesは、たとえばGetKeyに対して、GetKeyDown/GetKeyUpというメソッドもあるということ。

入力機器 メソッド 引数 戻り値 Down/Up
キー GetKey(string) キー名 bool Yes
マウスボタン GetMouseButton(int) 0:左,1:右,2:中 bool Yes
ボタン GetButton(string) 仮想ボタン名(*1) bool Yes
アクシス(スムージングあり) GetAxis(string)(*2) 仮想アクシス名(*3) float -1~+1 No
アクシス(スムージングなし) GetAxisRaw(string)(*2) 仮想アクシス名(*3) float -1~+1 No
タッチ GetTouch(int).phase 0-Input.touchCount:指番号 タッチフェーズ No(*3)


(*1)バーチャルボタンは、Project Settings-Inputで設定する。デフォルトでは、'Fire1','Fire2','Fire3','Jump','Submit','Cancel'が設定されている。

(*2)スティックは、ジョイスティック以外に、マウスの動き、キーにもアサインできる。
GetAxisRaw()の場合のキーボード入力は、-1,0,+1のいずれかになる。

(*3)GetTouch()は、

Input.GetTouch(i).phase == TouchPhase.Began

のように、.phaseプロパティと、TouchPhaseクラスとのセットで使う。

SourceTreeでGitのコミットができない時の対応

UnityでGitを使う方法は【連載Git入門 第6回】SourceTreeでGitを始めよう!UnityのプロジェクトをGitで管理しよう | naichilab - Android iOSアプリ開発メモが参考になる。

しかし、コミットしようとすると、

git: fatal unable to auto-detect email address

なるエラーが発生。環境はWindows7


この解決策は、http://tizio1976.blogspot.jp/2013/12/sourcetree-git-commit-fatal-unable-to.htmlが参考になった。

ただし、この記事には誤記あり! 正しい手順は次のとおり。


SourceTreeのターミナルを開き、

$ git config --global user.email "otoiawase@xooms.co.jp"
$ git config --global user.name "Atsuhiko Yasuda"

として、それぞれを設定する。user.email/name の後は`=`は不要で、テキストを""で囲むこと。


一度設定した情報を上書きするには、--replace-all をつけて

$ git config --global --replace-all user.email "admin@xooms.co.jp"

とする。

$ git config --global -l

で、設定した情報を確認できる。

f:id:yasuda0404:20160510160349p:plain




SourceTreeのリポジトリ設定の「ユーザー情報」で、「グローバルユーザー設定を使う」にチェックを入れる。(デフォルトはそうなっているはず)

f:id:yasuda0404:20150210114351p:plain

これで、無事コミットができた。

Marmoset Skyshopを使う

f:id:yasuda0404:20150126155741p:plain
Mamoset/SkyshopはUnity上でHDRIを利用できるライブラリ。クオリティの高い表現は定評がある。Tutorial: Introduction to Skyshop | Marmosetを参考に、Marmoset/Skyshopを触ってみる。

まず当然のことだがSkyshopを購入し、unitypackageを読み込む必要がある。
パッケージを読み込んだ状態。
f:id:yasuda0404:20150126161733p:plain


HDRIデータを準備する。Skyshopが扱うのは、2DまたはCube(立方体)の360°パノラマ・テクスチャ。ファイル形式は.HDR形式または.PFM をインポートできる。
今回はDOSCHのHDRI素材を使うことにした。

トップメニューのWindowにSkyshopという項目が追加される。これはシーンに環境マッピングを設定するためのツール。これを使って、HDRI画像をDiffuseとSpecularのマップに変換し、シーンに配置するまでをやってくれる。
f:id:yasuda0404:20150126161850p:plain

これを選択すると、Skyshopのインスペクタが開く。一番上の'INPUT PANORAMA'にHDRI画像を適用すると自動的にCubemap(SKYBOXとSPECULAR OUTPUT)が作られる。が、まず最初にSKYBOXとSPECULAR OUTPT、それぞれで'New'をクリックしてダミーデータを作っておかなければいけないようだ。
すると、Assetの直下にシーン名と同じフォルダが作られ、その中にSkyboxとSpecular Outpuのファイルができる。こんな状態。
f:id:yasuda0404:20150126164102p:plain

さらに、Assetの中にHDRI画像データを読み込む。Texture Typeは'Advanced'または'Texture'、MaxSizeは4096とした。(これがベストの設定かどうかはわからない)
f:id:yasuda0404:20150126164626p:plain

画像の解像度は任意だが、Skyshopのサンプルシーンでは2048X1024pixelの矩形画像をつかっているので、これを踏襲。DOSCHの場合は'SN'シリーズに相当。(DOSCHのHDRIデータは末尾の2つのアルファベットで画像の種類と解像度を表す)

一番上の'INPUT PANORAMA'スロットのSelectボタンをおし、該当するHDRI画像データを選択する。

Mipmapは不要、というエラーが出たら'Fix Now'をクリック。
f:id:yasuda0404:20150126175441p:plain

INPUT PANORAMAにHDRI画像データが読み込まれる
f:id:yasuda0404:20150126175510p:plain

次にSkybox、Specular Outputを自動作成する。これをおこなうにはSkyShopインスペクタの'Compute'ボタンをクリックする。(GPU Computeでもいい。こちらのほうが速い)
このとき、Basic OptionsのQualityで品質を変えられる。とりあえずは'Medium'でやってみる。
f:id:yasuda0404:20150126175853p:plain


しばらくするとSkybox, Specular Output画像ができる。'Add to Scene'をクリック。
f:id:yasuda0404:20150126180839p:plain

新規作成の場合、'Sky Manager'がない、というメッセージが出るので'Create'。
f:id:yasuda0404:20150126181100p:plain

プロジェクトの他のシーンでSkyMangerを使っている場合はエラーメッセージがでないので、マニュアルでSky Managerを加える。
この方法は、ヒエラルキービューで作成したSkyObjectを選択し、Component-Scripts-mset-SkyMangerを選択。これでMainCameraにSkyObjectが表示される。
f:id:yasuda0404:20150128190010p:plain


これでシーンにHDRIが適用される。シーンのメインカメラにComponent-Scripts...からFree Cameraを適用すれば360°ビューを見ることができる。
f:id:yasuda0404:20150126181201p:plain


ためしに鏡面反射するSphereオブジェクトをおいてみる。
シーンにSphereをつくり、プロジェクトにあらたなMaterialを追加、名前をChromeTestとでもしておく。
このMaterialのShaderを、Marmoset/Specular IBLに設定。Diffuse Colorを黒、Specular Colorを白、Specular Sharpnessを1近くに設定する。
f:id:yasuda0404:20150126182548p:plain

このMaterialをSphereに適用すると鏡面反射するオブジェクトが表現できる。
f:id:yasuda0404:20150126182743p:plain



※補足
Marmoset/SkyshopパッケージのAsset/Marmosetフォルダ内のShaderForgeExtension.zipを解凍すると、次のエラーが出る。
f:id:yasuda0404:20150126173846p:plain
継承しているSF_Nodeクラスがない、というエラーのようで確かにSF_Nodeクラスは見当たらない。。。?
なお、ShaderForgeExtensionはShaderを改良・作成するためのツールのようでなくてもSkyshopは機能する。とりあえず解凍せずに使っている。

軸周りの回転の方向

回転は一見単純な操作だが、すぐ忘れるので備忘録代わりに書いておく。


プリミティブをの円柱(Cylinder)を新規作成した場合、

transform.right :グローバルX方向&ローカルX方向=半径方向1
transform.up :グローバルY方向&ローカルY方向=円柱の軸方向
transform.forwad: グローバルZ方向&ローカルZ方向=半径方向2

となる。
f:id:yasuda0404:20141017191018p:plain


この円柱を

transform.Rotate(new Vector3(90.0f, 0.0f, 0.0f))

と、X軸周りに90度回転させる。(円柱の軸は水平方向に向く)
このとき、

transform.right :グローバルX方向&ローカル半径方向1
transform.up :グローバルZ方向&ローカル軸方向
transform.forwad: グローバル-Y方向&ローカル半径方向2

となる。
f:id:yasuda0404:20141017191251p:plain

つまり、ある軸まわりの回転は、その軸を手前から向こうに向かう方向から見たとき、反時計回りの回転となる。ねじ回しでいうと、ねじを外す方向。

当然のことだが、ローカル座標は物体にくっついて方向を変える。円柱の場合、軸方向は常にローカルY軸となる。


この円柱を軸方向に動かしたい時、どうするか。

obj.transform.Translate(10*obj.transform.up);

と書いてしまいそうだが、これではだめ。Translate()はデフォルト(=Translateの第二引数が`Space.Self`の状態)ではグローバル座標で方向を指定するからだ。
下の画像は、青い球の中心に生成した円柱を、青い球の半径分、円柱の軸方向に動かそうとしたもの。しかし、軸方向ではなく円柱の半径方向に動いてしまっている。これは、グローバル座標で(0.0, 0.0, 1.0)であるtransform.upを、円柱のローカル座標で動かしているため、矛盾が生じてしまったのだ。つまり、円柱の軸は水平を向いているが、円柱ローカルのZは下を向いているので、下方に移動してしまう。
f:id:yasuda0404:20141017194946p:plain


円柱の軸方向に動かすには、Translateの第二引数に'Space.World'を指定し、

obj.transform.Translate(10*obj.transform.up, Space.World);

とすればよい。これで、グローバルで見ているtransform.upを、正しくグローバル座標で移動できる。
f:id:yasuda0404:20141017194956p:plain



ランダムに与えた緯度・経度の示す地球表面上の地点に、垂直に円柱を立てるには次のようにすればよい。

void Update () {
  if(count < 20) {
    cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    cylinder.transform.position = Vector3.zero;
    Debug.Log (cylinder.transform.up);	//(0.0, 1.0, 0.0)
    cylinder.transform.localScale = new Vector3(cylinderRadius, cylinderHeight, cylinderRadius);
    lat =  Random.Range(-80.0f,80.0f);	//latitude
    lng = Random.Range(-180.0f,180.0f);	//longitude

    cylinder.transform.Rotate(new Vector3(-90.0f, 0.0f, 0.0f), Space.Self);	//rotate 90 around X to equator
    Debug.Log (cylinder.transform.up);	//(0.0, 0.0, 1.0)
    cylinder.transform.Rotate(new Vector3(0.0f, 0.0f, -lng), Space.Self);	//rotate in longitude direction
    cylinder.transform.Rotate(new Vector3(lat, 0.0f, 0.0f), Space.Self);

    cylinder.transform.Translate((sphereRadius+cylinderHeight)*cylinder.transform.up, Space.World);
  };
  count++;
}

f:id:yasuda0404:20141017201230p:plain

Unityで使える広義の配列

参考:http://wiki.unity3d.com/index.php?title=Which_Kind_Of_Array_Or_Collection_Should_I_Use%3F

Unityの広義の配列―コレクション―はいくつか種類がある。それぞれ宣言や代入、値の取出しの記述が異なる。また、自由度が高い反面、使わないほうがよいものもある。



1)ビルトイン配列(Built-in Array)

もっとも基本的な配列で、もっとも高速。ただし宣言時に要素数を指定する必要がある。が、宣言後にリサイズは可能。(System.Array.Resize(ref myArray, size))で行う。)

C#での宣言、代入、リサイズは次のように行う。ただし、多次元のビルトイン配列はリサイズできない。

TheType myArray = new TheType[lengthOfArray];  // declaration
int[] myNumbers = new int[10];                 // declaration example using ints
GameObject[] enemies = new GameObject[16];       // declaration example using GameObjects
int howBig = myArray.Length;               // get the length of the array
myArray[i] = newValue;                     // set a value at position i
TheType thisValue = myArray[i];            // get a value from position i
System.Array.Resize(ref myArray, size);    //resize array
 
string[] weapons = new string[] { "Sword", "Knife", "Gun" };
float[] alphas = new float[] { 1.0f }; 


2)Javascript配列(Javascript Array)

標準的な.netクラスにはない、Unity独自の配列。ただし名前通り、javascriptスクリプトでのみつかえ、C#型では使えない。

このため、サイズ変更可能、要素の型を宣言する必要がないなど柔軟性が高いが、反面、性能的にはもっとも遅い。

Javascript Arrayは基本的にはArrayListにラッパーをかけたものとみなせ、同様のことはObjectリストで実現できる。しかも速い。Javascript Arrayは「過去の遺産」と考えた方がよさそう。


3)アレイリスト(ArrayList

Javascript Array以上に柔軟性がある(たとえば要素数を変更できるなど)が、性能上は遅い(*)。またArrayList自体には型はなく、任意の型を要素として持つことができる。型の混在も可能。

ただし、Generic Listの登場によりArrayListの存在意義はなくなったといわれる。旧式のコードとの互換性上のものと考えた方がよさそう。

(*)性能面のハンデの多くは宣言時に要素数を宣言しないため、という報告もある。つまり、ArrayListでも要素数を固定すれば、速度は改善するらしい。

ArrayList myArrayList = new ArrayList();    // declaration
myArrayList.Add(anItem);                    // add an item to the end of the array
myArrayList[i] = newValue;                  // change the value stored at position i
TheType thisItem = (TheType) myArray[i];    // retrieve an item from position i
myArray.RemoveAt(i);                        // remove an item from position i
var howBig = myArray.Count;                 // get the number of items in the ArrayList


4)ハッシュテーブル(Hashtable)

'key-value'方式で要素を参照する配列。
key, valueともにどんな型でもよい(untype)。型の混在も可能。(keyは通常はString型であることがおおいが。)このため、Hashtableから要素を取り出す際は、ユーザがCastしてやる必要がある。
ただし、ArrayListよりもGeneric Listが良いのと同じように、近年は、HashtableよりDictionaryを使う方が良い。

Hashtable myHashtable = new Hashtable();                 // declaration
myHashtable[anyKey] = newValue;                          // insert or change the value for the given key
ValueType thisValue = (ValueType)myHashtable[theKey];    // retrieve a value for the given key
int howBig = myHashtable.Count;                          // get the number of items in the Hashtable
myHashtable.Remove(theKey);                              // remove the key & value pair from the Hashtable, for the given key.

5) ジェネリック・リスト(Generic List)

Generic Listは、Listとも呼ばれる。要素数可変、要素の追加・削除ができる点ではJavascript, ArrayListと同等の柔軟性がある。違いは、定義時に型を指定する点。宣言した型の要素しか保持できないため

  • 要素の値を取出す際、型キャストが不要。
  • ArrayListより速い

という利点がある。
つまり、特定の型のみ扱うことがわかっている場合は、ArrayListよりGeneric Listを使う方が良い。

using System.Collections.Generic;

List<Type> myList = new List<Type>();                 // declaration
List<int> someNumbers = new List<int>();              // a real-world example of declaring a List of 'ints'
List<GameObject> enemies = new List<GameObject>();    // a real-world example of declaring a List of 'GameObjects'
myList.Add(theItem);                                  // add an item to the end of the List
myList[i] = newItem;                                  // change the value in the List at position i
Type thisItem = List[i];                              // retrieve the item at position i
myList.RemoveAt(i);                                   // remove the item from position i


6) ジェネリック・ディクショナリー(Generic Dictionary)

HashtableのGneric List版。宣言時にkey, valueの型を指定する。このため、Gneric Listと同じ利点が得られる。

using System.Collections.Generic;

// declaration:
Dictionary<KeyType,ValueType> myDictionary = new Dictionary<KeyType,ValueType>();
 
// and a real-world declaration example (where 'Person' is a custom class):
Dictionary<string,Person> myContacts = new Dictionary<string,Person>();
 
myDictionary[anyKey] = newValue;                 // insert or change the value for the given key
ValueType thisValue = myDictionary[theKey];      // retrieve a value for the given key
int howBig = myDictionary.Count;                 // get the number of items in the Hashtable
myDictionary.Remove(theKey);                     // remove the key & value pair from the Hashtable, for the given key.


7) 2次元配列(2D Array)

以上はすべて1次元の配列について述べてきた。Unityでは2次元以上の配列を使うこともできる。以下は2次元配列についてのべるが、3次元、4次元…といった多次元配列を扱うこともできる。

「一般の」2次元配列の宣言では、外側・内側の2要素の要素数を指定する。

// declaration:
string[,] myArray = new string[16,4];            // a 16 x 4 array of strings
 
// and a real-world declaration example (where 'Tile' is a user-created custom class):
Tile[,] map = new Tile[32,32];                   // create an array to hold a map of 32x32 tiles
 
myArray[x,y] = newValue;                         // set the value at a given location in the array
ValueType thisValue = myArray[x,y];              // retrieve a value from a given location in the array
int width = myArray.GetLength(0);                // get the length of 1st dimension of the array 
int length = myArray.GetLength(1);               // get the length of 2nd dimension of the array


一方「ジャグ(jagged)」2次元配列では、(内側の)要素の数がそろっていなくても良い。

var myJaggedArray = [ [1, 2, 3], [4, 5], [6, 7, 8, 9, 10] ]; // a jagged int array

myArray[x][y] = newValue;
ValueType thisValue = myArray[x][y]; 

ちなみに、「ビルトイン配列(たとえば、GameObject[])を要素にもつジェネリック・リスト」を作るには次のように書く。

GameObject[] go1;
GameObject[] go2;
GameObject[] go3;
List<GameObject[]> gos = new List<GameObject[]>{go1,go2,go3};

以上、Unityで扱われる広義の配列について述べた。利用指針は次のようになるだろう。

・大きさがわかっているケースは、ビルトイン配列を使う。メモリ効率も速度ももっともよい。
ArrayListのかわりにGneric Listを、Hashtableの代わりにGeneric Dictionaryを使う。
Javascript Arrayは使わない。

要するに、型のキャストを避けるべき。
開発時はArrayListやHashtableを使ってもよいが、リリース時はGnericに変換するとよいだろう。

実行の順番:OnEnable, Awake, OnLevelWasLoaded, Start

Unityのスクリプト内で初期化を行う関数はいくつかある。

ひとつのスクリプトの中で実行される順番は次の通りらしい。(出典はこれ

Awake
OnEnable
OnLevelWasLoaded
Start

しかし、スクリプトを無効にした場合(Script.enabled=false)は、次の通り

Awake
OnLevelWasLoaded

すなわち、OnEnable, Startは実行されない

Awake, Startはひとつのオブジェクトについて、一度しか呼ばれない。
しかし、OnEnableは、Enableされるたびに呼ばれる、というのも違い。


また、異なるスクリプトどのAwake(あるいはOnEnable、Start)が先に呼ばれるかはデフォルトでは不定。


明示的にどのスクリプトを先に実行するかを指定するには、Edit>Project Settings...>Script Execution Order Settings で行う。
'Default Time'より先に実行したいスクリプトを上に、後に実行したいものを下に置く。上ほど先に実行される。
指定したスクリプト以外は'DefaultTime’に実行されるがその順番は不定である。

インスペクタで、カスタムクラスの中にある変数にアクセスする

わかりにくいタイトルだが、具体的な例で説明する。

下のように、メインであるクラス(下の例では、Physics)のPublic変数にカスタム・クラス(SubData)
を指定しているとする。、

using UnityEngine;
using System.Collections;

public class SubData {
	public int data1;
	public int data2;
	private int data3;
}

public class Physics : MonoBehaviour {
	public SubData data;
	// 途中省略
}

この場合、インスペクターから、SubDataクラスは見えない。つまり、data1, data2も見えない。
f:id:yasuda0404:20130913160031p:plain



インスペクタからSubDataの中にある変数を見えるようにするには、Runtime Attributeの、[System.Serializable]を指定する。

[System.Serializable]
public class SubData {
	public int data1;
	public int data2;
	private int data3;
}

こうすれば、インスペクタから、SubDataのPublic変数が見えるようになる。SubDataクラスのPrivate変数には当然アクセスはできない。

f:id:yasuda0404:20130913160413p:plain

コンポーネントが存在するかどうかを見る

GameObjectに対し、あるコンポーネントを持っているかを調べ、なければ足す(あるいは、処理を変える)場合の手順

例えば、あるGameObjectに対し、rigidbodyコンポーネントがなければ新たに加え、rigidbodyコンポーネントを返す例。

if(rigidbody == null){
	return transform.gameObject.AddComponent("Rigidbody") as Rigidbody;
}else{
	return rigidbody;
}

'rigidbody == null'は、rigidbodyコンポーネントがない場合に真となる。

Componentを足すだけなら、

GameObject.AddComponent("コンポーネント名")

でよいが、Componentを加えると同時に、そのコンポーネントを変数として取得する場合、
変数 = GameObject.AddComponent(”コンポーネント名”) as コンポーネント
のように一行で行える。
AddCompnent()の引数はComponent型のため、コンポーネントの型に応じたキャスト(上の例ではRigidbody)が必要。


カスタムクラス・コンポーネントの場合は、次のように書ける。

if(ball.GetComponent<Ball>()){
	//if Ball does not exist
	ballRB = ball.GetComponent<Ball>().getRB();
}else{
	ballRB = ball.rigidbody;
}

一行目は、

if(ball.GetComponent<Ball>() != null){

としてもよい。

                  • -

追記:2013/9/13

特定のコンポーネントなければ加える別な方法として、Runtime Attributeの[Require Component(typeOf(コンポーネント名))]
を使う手もある。エディター上で見えるので、必ず組にして使う必要があるコンポーネントならこの方法がわかりやすい。

using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Rigidbody))]
public class Test : MonoBehaviour {
.........

とすれば、Scriptをアタッチした時点で、アタッチしたゲームオブジェクトがRigidbodyコンポーネントを持っていなければ自動的に加えられる。

timeScaleでアニメーション速度を変更する

Time.timeScaleでFixedUpdate()の時間間隔を調整できる。

Time.timeScale=1.0 で、デフォルトの速度(時間間隔)
Time.timeScale=0.5で、速度を半分(時間間隔を2倍)にする
Time.timeScale=0.0は、速度ゼロ、すなわち、FixedUpdate()を停止する


具体的な事例で見てみる。

子オブジェクトのスクリプト。一定速度で回転する。

using UnityEngine;
using System.Collections;

public class revolution : MonoBehaviour {
	
    // Update is called once per frame
        void FixedUpdate () {
        transform.RotateAround(Vector3.up, 0.02f);
    }
}


親オブジェクト:一定速度で上昇するスクリプト。スペースキーでTime.timeScale=0.0f/1.0fにトグルする。これによって、アニメーションの停止・作動を切り替える。

using UnityEngine;
using System.Collections;

public class move : MonoBehaviour {
	
	// Use this for initialization
	    void Start () {
	}
	
	// Update is called once per frame
	void Update () {
	    if (Input.GetKeyDown ("space")){
            print ("space key was pressed Time.timeScale="+Time.timeScale);
	    Time.timeScale = Mathf.Abs (1.0f-Time.timeScale);
	}

	}
	void FixedUpdate(){
		transform.Translate(0.01f*Vector3.up);
	}
	
}


ここで注意するのは次の2点。

1)timeScale=0.0fで停止するのは、FixedUpdate()のみ。Update()はTime.timeScaleの値に関係なく、動き続ける。
 つまり、上の事例では、親オブジェクトのUpdate()内のInput.GetKeyDownによるキー入力は有効のままである。

2)親オブジェクトでTime.timeScale=0.0fとすると、子オブジェクトを含む全てのFixedUpdate()が停止する。上の例では、親オブジェクトの上昇が止まるだけでなく、子オブジェクトの回転も止まる。
 親子関係にあるオブジェクトだけではなく、シーン内のすべてのオブジェクトでFixedUpdate()は停止する。

オブジェクトの一部をマスクする

Unityで、GameObjectの一部にマスクをかけるには、DepthMaskを使う。
参考:Unity - Manual: ShaderLab syntax: SubShader Tags

1. Maskとなるオブジェクトの設定

1-1. 新規シェーダを作成。Maskシェーダのスクリプト(下記)を記述する。

Maskオブジェクトの順番を、レギュラー・ジオメトリとマスクをかけるジオメトリの間に設定している。

Shader "Masked/Mask" {
	
	SubShader {
		// Render the mask after regular geometry, but before masked geometry and
		// transparent things.
		
		Tags {"Queue" = "Geometry+10" }
		
		// Don't draw in the RGBA channels; just the depth buffer
		
		ColorMask 0
		ZWrite On
		
		// Do nothing specific in the pass:
		
		Pass {}
	}
}

肝は、SubShaderの中の、

Tags {"Queue" = "Geometry+10" }

の部分。Queueはレンダリングの順番を指定するタグ。デフォルトの設定値は次の通り。数字が小さいものから先にレンダリングされる。

Queue Value
Background 1000
Geometry 2000
AlphaTest 2450
Transparent 3000
Overlay 4000


Geometryは一般のオブジェクトを指す。上のスクリプトでは、Geometryのデフォルト値に10を加えた値、すなわち2010をQueueタグに設定していることになる。

1-2. 新しいMaterialを作り、Maskシェーダをアタッチする。

f:id:yasuda0404:20130401142827p:plain

1-3. このMaterialを、Maskとして使用したいオブジェクトにアタッチ。

2.Maskをかけるオブジェクトの設定

2-1. Maskをかけたいオブジェクトに、任意のMaterialを設定する。
2-2. 同オブジェクトに、次のスクリプトをアタッチ。

  

/*
	SetRenderQueue.cs
	
	Sets the RenderQueue of an object's materials on Awake. This will instance
	the materials, so the script won't interfere with other renderers that
	reference the same materials.
*/

using UnityEngine;

[AddComponentMenu("Rendering/SetRenderQueue")]

public class SetRenderQueue : MonoBehaviour {
	
	[SerializeField]
	protected int[] m_queues = new int[]{3000};
	
	protected void Awake() {
		Material[] materials = renderer.materials;
		for (int i = 0; i < materials.Length && i < m_queues.Length; ++i) {
			materials[i].renderQueue = m_queues[i];
		}
	}
}

[SerializeField]は、public変数ではないが、Inspectorウィンドウから値を設定できるようにする修飾子。
このスクリプトは、MaskをかけたいオブジェクトのRenderQueueを3000にしている。これによって、Regular Geometry(2000)→Mask(2010)→Maskをかけたいオブジェクト(3000)の順番にレンダリングされ、MaskをかけたいオブジェクトのみにMaskがかかることになる。


要は、RenderQueueの値を相対的に変えることで、レンダリングの順番を変更し、Maskをかけることになる。