rn.log

備忘録など

【技術書】シェーダーグラフの書籍をリリースしました【ShaderGraph CookBook vol.1】

はじめに

2021/03/21 に Unity ShaderGraphの本をリリースしました。
今回は、この本の魅力を紹介したいと思います。
zenn.dev

本書の特徴

  • 20種以上のノードの解説
  • 30種以上の作例
  • サンプルプロジェクト付き


特徴① : ノードの解説

ノードについての解説や、ノードで作れる表現の作例を紹介しています。

f:id:r-ngtm:20210408043105p:plain

サンプルページ

🍎 Sineノード|Unity ShaderGraph CookBook vol.1【ShaderGraph 入門】

特徴② : 豊富な作例

書籍では、30種以上の作例を紹介しています。
ShaderGraph単体で完結するような表現に焦点を絞っています。
f:id:r-ngtm:20210408044005g:plain

作例を知りたい方は以下のページをご覧ください。
🍍 レシピ一覧|Unity ShaderGraph CookBook vol.1【ShaderGraph 入門】



特徴③ : サンプルプロジェクト

本書の作例の一部は、GitHubにて公開しています。 github.com

環境

Unity2020.3.0f1
Universal RP 10.3.2

【Houdini + Unity】点光源の影でグリッドを作って遊ぶ

はじめに

Unityのポイントライトの影でグリッドを出してみました。
今回はこの3Dモデルの作り方を紹介します。

f:id:r-ngtm:20210208013405p:plain
ポイントライトの影でグリッドを作ってみました
www.youtube.com

作り方

グリッドを球面へ投影したようなモデルを作ります。 このモデルと点光源が地面に映し出す影はグリッドの形になります。

f:id:r-ngtm:20210208010331p:plain
平面上の点を球面へ投影

投影点の座標の求め方

線分PAの長さをD, 線分PBの長さをd、球の半径をRとおいたとき、以下のようになります。
dが分かれば、投影点Bの座標が分かります。

f:id:r-ngtm:20210208005259p:plain
平面上の点を球面に投影

長さdの導出

三角形 HPA と 三角形BPHは相似なので、以下が成り立ちます。

 
\begin{align}
PB:PH &= PH : PA \\\
\\\
d:2R &= 2R : D \\\
\\\
d&= \frac{4R^2}{D}  
\end{align}

投影点Bの座標は以下のようになります。


\vec{B} = \vec{P} + d \cdot  \dfrac {\vec{PA}}{D}

Houdiniで実装する

投影点Bの座標が求まったので、次にこれをHoudiniで実装します。

Grid

Gridノードを使ってグリッドを作成します。

f:id:r-ngtm:20210208012423p:plain
グリッド作成

PolyExtrude

PolyExtrudeノードを使ってグリッドに穴をあけます。

f:id:r-ngtm:20210208012500p:plain
穴をあける

Attribute Wrangle

投影点の座標を計算して、グリッド点を球へ投影します。

f:id:r-ngtm:20210208012809p:plain
投影点の計算

vector origin = { 0, 1, 0 }; // 点Pの座標
vector p = (@P - origin); // 点Pから平面へ向かうベクトル
vector dir = normalize(p); // 正規化
float R = 0.5; // 球の半径
float D = length(p); // 点Pから平面上の点までの距離
float d = 4 * R * R / D; // 点Pから投影点までの距離

@P = origin + d * dir; // 投影点の座標で置き換える

投影したモデルは、ROP FBX Outputノードなどで3Dモデル化します。

完成

モデルをUnityへ取り込み、点光源を置くと、影がグリッドになります。

f:id:r-ngtm:20210208012942p:plain
モデルと点光源を配置

点光源の位置

f:id:r-ngtm:20210208013119p:plain
点光源の位置 (0, 1, 0)

モデルの位置

f:id:r-ngtm:20210208013143p:plain
モデル位置 (0, 0, 0)

関連書籍

www.amazon.co.jp

【シェーダーグラフメモ その58】XOR演算を利用した市松模様

はじめに

XOR演算を利用して、市松模様を作る方法を紹介します。

f:id:r-ngtm:20210130051822p:plain:w320
市松模様

今回はワールド座標のX,Z成分から市松模様を作ります。

目次

 

環境

Unity2020.2.0f1
Universal RP 10.2.2


市松模様の作り方

タテ方向、ヨコ方向のシマシマを作ります。

f:id:r-ngtm:20210130045131p:plain
シマシマ

二つのシマシマに関してXORを適用すると、市松模様が作れます

f:id:r-ngtm:20210130045207p:plain
XORを取ると市松模様になる

XORについて

xor(a,b)は以下のような値を取ります。

a b xor(a,b)
0 0 0
0 1 1
1 0 1
1 1 0

シェーダーグラフ実装

シマシマを作る

以下のようなシェーダーグラフを組みます。

f:id:r-ngtm:20210130050053p:plain
ワールド座標からシマシマを作る

結果

これを板ポリゴンに貼り付けると、以下のようになります。 f:id:r-ngtm:20210130050809p:plain:w480

色のX成分、Z成分はそれぞれ以下のようになっています。

f:id:r-ngtm:20210130051643p:plain
X,Z 成分はそれぞれ0, 1 の繰り返しになっている

次にXORを利用して市松模様を作ります。

f:id:r-ngtm:20210130051822p:plain:w320
市松模様

ShaderGraphにXOR演算は無い

ShaderGraphにはXOR演算は用意されておらず、自分で作る必要があります。 今回はXORの実装方法を3つ紹介します。

方法1 : Abs を利用したXOR

1つ目の方法はAbsを利用したXORの実装です。

abs(a-b)

X成分(横シマ)からZ成分(縦シマ)を減算すると、以下のようになります。

f:id:r-ngtm:20210130054431p:plain
二つのシマシマを引き算する

減算結果にAbs(絶対値)を取ると、0,1が交互に並びます。

f:id:r-ngtm:20210130054944p:plain
abs(絶対値)を適用

シェーダーグラフで実装すると以下のようになります。

f:id:r-ngtm:20210130055257p:plain
absを利用したXORの実装

方法2 : X・(1 - Z) + (1 - X)・Z

以下の計算式でXORを作ることができます。

x * (1 - z) + (1 - x) * z

f:id:r-ngtm:20210130060901p:plain
X(1-Z) + (1-X)Z

f:id:r-ngtm:20210130061329p:plain
X(1-Z) + (1-X)ZによるXOR実装

方法3 : Moduloを利用したXOR

3つ目の方法は、Moduloを使ったXORの実装です

fmod(a+b, 2.0)

X成分とZ成分を足し合わせると、以下のようになります。

f:id:r-ngtm:20210130053513p:plain
横のシマシマと縦のシマシマの加算

2で割った余りを計算すると0と1が交互に並びます。

f:id:r-ngtm:20210130053955p:plain
Addした結果にMod 2 を取る

シェーダーグラフで実装すると、以下のようになります。

f:id:r-ngtm:20210130052243p:plain
Moduloを使ったXOR実装

結果

f:id:r-ngtm:20210130051822p:plain:w320
市松模様

【Unity】シャドウマッピングについて

環境

Unity2020.2.0f1
Universal RP 10.2.2

シーンに光源を置いてみる

シーンに緑色のDirectional Lightを置くと、以下のようになります。

f:id:r-ngtm:20210126050256p:plain:w480
緑のDirectional Lightを配置

光が当たった領域は緑色になりますが、影の領域には色を与えません ( = 真っ黒になります)

シャドウマップ

Unityでは、「ある領域が影になっているかどうか」の判定にシャドウマップを利用します。

f:id:r-ngtm:20210126072742p:plain:w640
シャドウマップの深度と描画点の深度を比較

シャドウマップのベイク

点光源を例に見てみます。
点光源から、シーンのオブジェクトに最も近い点までの距離を求め、それをテクスチャ(シャドウマップ)に保存します。
テクスチャは0 ~ 1 までの値しか保存できないので、範囲[0, maxDistance] を [0, 1] へ変換したものをテクスチャに保存します。

f:id:r-ngtm:20210126072044p:plain:w640
光源から見て最も近いオブジェクトまでの距離をシャドウマップへ保存

影の最大距離(maxDistance) はURP の Pipelineアセットの Shadows の部分から設定できます。

f:id:r-ngtm:20210126072552p:plain:w320
影のmaxDistance

シャドウマップを利用した影の判定

カメラで描画しようとしている点とライト間の距離D、シャドウマップに保存されている距離dを比較して、影かどうかを判定します。

f:id:r-ngtm:20210126072742p:plain:w640
シャドウマップの深度と描画点の深度を比較


ちなみに、MainLightのシャドウマッピングは Universal RP パッケージ内部の MainLightShadowCasterPass.cs にて実装されており、実装の内容を見ることができます。

FrameDebugger で シャドウマップを見てみる

以下のようなシーンを作成して、シャドウマップを見てみます。

f:id:r-ngtm:20210126080022p:plain
仮のシーン

DrawOpaqueObjects パスからシャドウマップを Ctrl + 左クリックすることで、シャドウマップを確認することができます。

f:id:r-ngtm:20210126075939p:plain
DrawOpaqueObjects

シャドウマップ

今回は以下のようなシャドウマップが確認できました。

f:id:r-ngtm:20210126080150p:plain
シャドウマップ

このシャドウマップは、カスケードシャドウマップになっています。

f:id:r-ngtm:20210126081532p:plain
シャドウマップの詳細
f:id:r-ngtm:20210126081731p:plain
カスケードの最大距離

おまけ : シャドウマップの実装場所

Universal RP の 中を見ると、シャドウマップを利用している処理を見ることができます。

Shadows.hlsl

Shadows.hlsl の中身を見ると、シャドウマップをサンプリングしている関数があります。

half MainLightRealtimeShadow(float4 shadowCoord)
{
#if !defined(MAIN_LIGHT_CALCULATE_SHADOWS)
    return 1.0h;
#endif

    ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
    half4 shadowParams = GetMainLightShadowParams();
    return SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), shadowCoord, shadowSamplingData, shadowParams, false);
}

Lighting.hlsl

上記のMainLightRealtimeShadow関数は、 Lighting.hlsl にて利用されています。

Light GetMainLight(float4 shadowCoord)
{
    Light light = GetMainLight();
    light.shadowAttenuation = MainLightRealtimeShadow(shadowCoord);
    return light;
}

上記のGetMainLight関数は URP の PBRシェーディングを行う関数 UniversalFragmentPBRの中で利用されています。(場所はLighting.hlslです)

    Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, shadowMask);

    #if defined(_SCREEN_SPACE_OCCLUSION)
        AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(inputData.normalizedScreenSpaceUV);
        mainLight.color *= aoFactor.directAmbientOcclusion;
        surfaceData.occlusion = min(surfaceData.occlusion, aoFactor.indirectAmbientOcclusion);
    #endif

    MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI);
    half3 color = GlobalIllumination(brdfData, brdfDataClearCoat, surfaceData.clearCoatMask,
                                     inputData.bakedGI, surfaceData.occlusion,
                                     inputData.normalWS, inputData.viewDirectionWS);
    color += LightingPhysicallyBased(brdfData, brdfDataClearCoat,
                                     mainLight,
                                     inputData.normalWS, inputData.viewDirectionWS,
                                     surfaceData.clearCoatMask, specularHighlightsOff);

【Unity】Linearワークフロー と Gammaワークフローについてまとめてみる

はじめに

UnityのLinearワークフローGammaワークフローについて、軽くまとめてみようと思います。

環境

Unity2020.2.0f1
Universal RP 10.2.2

問題

突然ですが、ここで問題です。
フラグメントシェーダーで 0.5 ( RGB = (128, 128, 128 ) ) という色を返した場合、
画面にはどんな色が表示されるでしょうか? (Linearワークフロー)

fixed4 frag (v2f i) : SV_Target
{
    return 0.5;
}

答え

RGB = (188, 188, 188) が表示されます。(明るく表示されます)


表示色が異なる理由はLinearワークフローにあります。

Linearワークフロー

通常のUnityでは、Linearワークフローが採用されており、以下のような計算を行います。

  • シェーダー内では色の計算をLinear空間で行う
  • 計算結果はガンマ補正してから画面に表示する

f:id:r-ngtm:20210120235736p:plain
色はガンマ補正してから画面に表示する

sRGBテクスチャが画面に表示されるまで (Linearワークフロー)

sRGBテクスチャが画面に表示される流れは以下のようになります。
1. sRGBテクスチャはサンプリング時にLinear色空間へ変換される (逆ガンマ補正)
2. シェーダーではLinear色空間で計算を行う
3. 色の計算結果はsRGB色空間へ変換してから画面に表示する(ガンマ補正)

f:id:r-ngtm:20210121000255p:plain
sRGBテクスチャがスクリーンに表示されるまで

sRGBテクスチャとLinearテクスチャの切り替え

テクスチャは、デフォルトではsRGBにチェックが入った状態になっています。
sRGBのチェックが入っていると、そのテクスチャはsRGB色空間にあるものとして扱われます。

f:id:r-ngtm:20210121003421p:plain
sRGBのチェックが入っていると、sRGB色空間として扱われる

テクスチャのsRGBのチェックを外すと、Linear色空間のテクスチャとして扱われるようになります。

f:id:r-ngtm:20210121002928p:plain
sRGBのチェックを外すとLinear色空間として扱われる

Linearテクスチャが画面に表示されるまで (Linearワークフロー)

Linear色空間のテクスチャは、テクスチャサンプリング時に逆ガンマ補正がかからなくなります。
画面に表示される際にはガンマ補正がかかります。
結果として、テクスチャは明るく表示されるようになります。

f:id:r-ngtm:20210121002814p:plain
非sRGBテクスチャが画面に表示されるまで(Linearワークフロー)

Linearワークフロー と Gammaワークフローの切り替え

UnityのPlayerSettingsには Color Space という設定項目があります。

ここがLinearの場合、UnityはLinearワークフローレンダリングを行うようになります。

f:id:r-ngtm:20210121001007p:plain
Linearワークフロー

Gammaだと、UnityはGammaワークフローレンダリングを行うようになります。。

f:id:r-ngtm:20210121000837p:plain
Gammaワークフロー

Gammaワークフロー

Gammaワークフローでは、色が画面にそのまま表示されるようになります。(Linearワークフローだとガンマ補正がかかっていました)

f:id:r-ngtm:20210121004008p:plain
Gammaワークフローでは色がそのまま表示される

テクスチャが画面に表示されるまで (Gammaワークフロー)

Gamma ワークフローでは、テクスチャの色はそのままシェーダーに取り込まれ、シェーダーの計算結果もそのまま画面に表示されます。

f:id:r-ngtm:20210121001336p:plain
Gammaワークフロー

まとめ

  1. Linearワークフローでは、ガンマ補正や逆ガンマ補正が入る
  2. Gammaワークフローでは、ガンマ補正や逆ガンマ補正は入らない

Linearワークフロー

f:id:r-ngtm:20210121000255p:plain:w500
Linearワークフロー

Gammaワークフロー

f:id:r-ngtm:20210121001336p:plain:w500
Gammaワークフロー

【Compute Shader メモ 2】中心差分法を利用してノーマルマップを作る

はじめに

Compute Shader で何かを作るシリーズ 第二弾!

今回は、Compute Shader で ノーマルマップ(法線マップ)を作ってみたいと思います。

f:id:r-ngtm:20210120000231p:plain:h240
実行結果 (1024x1024のハイトマップからノーマルマップを計算)

ノーマルマップ(法線マップ)とは

ノーマルマップとは、3Dモデルの法線情報を画像に保存したものです。

f:id:r-ngtm:20210119214419p:plain
ノーマルマップ

平面メッシュにノーマルマップを適用することで、あたかも凹凸が付いているように陰影をつけることができます。

f:id:r-ngtm:20210119214336p:plain
ノーマルマップを適用することで、凹凸が付いて見える

ノーマルマップによって陰影がつく原理

Unityは物体をレンダリングするときに、物体表面の法線を見て凹んでいるかどうかを判断しています。
(法線は黄色の線で表示しています)

f:id:r-ngtm:20210119221045p:plain:w480
凹凸のある面の法線はバラバラになる

ノーマルマップを使うと、物体の法線を再現することができ、陰影をつけることができます。

f:id:r-ngtm:20210119221358p:plain:w480
法線によって陰影がつく

Unityは物体をレンダリングするときに、物体の法線を見て凹んでいるかどうかを判断しています。
そのため、法線がバラつくとあたかもその面には凹凸が付いているかのように陰影が付くわけです。

Heightマップからノーマルマップを作る

今回、以下のような高低マップ(Heightマップ)から法線マップを作りたいと思います。

f:id:r-ngtm:20210119232127p:plain:w240
パーリンノイズ

以下のような法線マップを作ります。 (下記テクスチャはSubstance Designer で作ったノーマルマップになります)

f:id:r-ngtm:20210119232316p:plain:w240
ノーマルマップ

Heightマップからノーマルマップを求める方法

今回は中心差分法を利用して、Heightマップから勾配を求め、そこから法線を計算します。

f:id:r-ngtm:20210119211927p:plain:w320
法線ベクトルnは勾配の外積で計算

求めた法線情報はテクスチャ(ノーマルマップ)に保存します。



中心差分法で勾配を求める

ここで、連続な曲線 f(x) 上の接線(勾配)を求めることを考えます。

f:id:r-ngtm:20210119215214p:plain:w400
ある点での接線の傾きは、両隣の点の座標の差分で近似できる

ある点での接線の傾き(勾配)は、以下の式で近似できます。(中心差分法)
 \dfrac{ \partial f }{ \partial x } = \dfrac{ y_{i + 1} - y_{i - 1} }{ 2 \Delta x } = \dfrac{ f(x_{i + 1}) - f(x_{i - 1}) }{ 2 \Delta x }

参考 : http://www.icehap.chiba-u.jp/activity/SS2016/textbook/SS2016_miyoshi_FD.pdf



勾配ベクトルを求める

Hiehgtマップの座標(x,y)に保存されている高さ情報をf(x,y)とすると、以下のような図になります。

f:id:r-ngtm:20210119210754p:plain:w640
あるピクセルとその隣接ピクセルを可視化

中心差分法を利用すると二つの勾配ベクトル(\Delta x, 0, \Delta f_x ) , (0, \Delta y, \Delta f_y ) を求めることができます。

f:id:r-ngtm:20210119231430p:plain:w640
差分から勾配を求めることができる

外積を利用して法線を求める

法線ベクトル \vec{n} はこれら2つの勾配の外積を利用すると計算することができます。
 \vec{n} (- \Delta y \Delta f_x,  - \Delta x \Delta f_y,  \Delta x \Delta y ) に平行になります。

f:id:r-ngtm:20210119211927p:plain:w640
法線ベクトルnは外積で計算

 \Delta x = 2, \Delta y = 2と置いたとき、
 \vec{n} (- \Delta f_x,  - \Delta f_y,  2 ) に平行になります。

Compute Shader で実装

#pragma kernel CSMain

RWTexture2D<float4> Result; // ノーマルマップの書き込み先
Texture2D<float4> HeightMap; // Heightマップ

[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{    
    // Heightマップの差分
    float dfx = (HeightMap[id.xy + uint2(1, 0)].r - HeightMap[id.xy + uint2(-1, 0)].r);
    float dfy = (HeightMap[id.xy + uint2(0, 1)].r - HeightMap[id.xy + uint2(0, -1)].r);
    
    // 法線
    float3 n = float3(-dfx, -dfy, 2.0);
    n = normalize(n);
    
    // 範囲[-a,a]を[0,1]に変換してからテクスチャに書き込む    
    float a = 0.02;
    n = (n / a + 1.0)  * 0.5;

    Result[id.xy] = float4(n, 1.0);
}

Compute Shader を実行する C#スクリプト

using UnityEngine;
using UnityEngine.UI;

public class ComputeNormal : MonoBehaviour
{
    [SerializeField] private ComputeShader shader; // 実行するComputeShader
    [SerializeField] private Texture _texture; // 入力画像
    [SerializeField] private RawImage targetImage; // RenderTextureを割り当てる対象のRawImage
    private RenderTexture _renderTexture;
    private int kernel;
    
    private void Start()
    {
        // RenderTexture作成
        _renderTexture = new RenderTexture(_texture.width, _texture.height, 0);
        _renderTexture.enableRandomWrite = true;
        _renderTexture.Create();
        
        // 実行したいカーネル(関数のようなもの)
        kernel = shader.FindKernel("CSMain"); 
        
        // ComputeShaderへデータを渡す
        shader.SetTexture(kernel, "HeightMap", _texture); // 元となるテクスチャ
        shader.SetTexture(kernel, "Result", _renderTexture); // 書き込み先のテクスチャ
        
        // スレッドグループサイズの取得
        uint sizeX, sizeY, sizeZ;
        shader.GetKernelThreadGroupSizes(kernel, out sizeX, out sizeY, out sizeZ);
        
        shader.Dispatch(kernel, _texture.width / (int)sizeX, _texture.height / (int)sizeY, 1 / (int)sizeZ);

        targetImage.texture = _renderTexture;
    }
}

実行結果

f:id:r-ngtm:20210120000231p:plain
実行結果 (1024x1024のハイトマップからノーマルマップを計算)

【Unity】メッシュの頂点カラーにColorを設定するとParticleSystemで色が壊れる話

はじめに

Unity C# で作成したメッシュをParticleSystemで使用したとき、頂点カラーの表示が壊れました。
この現象の原因の考察・および回避方法をまとめようと思います。

f:id:r-ngtm:20210119040423p:plain:h320
ParticleSystemでメッシュを表示

以下のような頂点カラーを設定すると、ParticleSystemで表示したときに頂点カラーが壊れます。

mesh.colors = colors; // Color[]



環境

Unity 2020.2.0f1
Universal RP 10.2.2


mesh.colorに頂点カラーを設定

mesh.colorsに頂点カラーを設定してメッシュを作成したとします。

mesh.colors = colors; // Color[]

頂点カラー情報は メッシュに16byte カラーとして保存されます。

f:id:r-ngtm:20210119052726p:plain
colorsは16byteカラーとしてメッシュに保存されます。

MeshRenderer で表示した場合

このメッシュをMeshRendererで描画した際は、色が正常に表示されます。

f:id:r-ngtm:20210119040441p:plain:h320
MeshRendererで表示

メッシュのマテリアルにはシェーダーグラフを使用しており、頂点カラーをMasterノードとして出力しています。

f:id:r-ngtm:20210119040750p:plain:h320
頂点カラー(Vertex Color)を出力するShaderGraph

ParticleSystem で表示した場合

このメッシュをParticleSystemで表示すると色が壊れます。

f:id:r-ngtm:20210119040423p:plain:h320
ParticleSystemでメッシュを表示

mesh.colors は Color であるのに対して、Particle Systemでは頂点カラーは Color32 として扱われていることが理由だと考えられます。


以下のように32bitカラーを設定すれば、色が壊れる現象は回避できます。

mesh.colors32 = colors32; // Color32[]

Colors32 は 4byteカラー(32bitカラー)としてメッシュに保存されます。

f:id:r-ngtm:20210119055540p:plain
Colors32 は 4byteカラーとしてメッシュに保存される

色が壊れる理由について

ParticleSystemのカラーはColors32

Unityの ParticleSystemは頂点カラーをColor32として持っているようです。

たとえばParticleSystem.csの中を見ると、ParticleSystem.startColorはColor32型で定義されています。

/// <summary>
///   <para>The initial color of the particle. The current color of the particle is calculated procedurally based on this value and the active color modules.</para>
/// </summary>
public Color32 startColor

ドキュメントが見つからなかったので断言はできませんが、ParticleSystemはメモリ領域からColor32[ ]を読んでいると考えられます。
そして、メッシュの頂点カラー領域にColor[ ] が設定されていた場合、色が壊れます

16byte カラーを設定したときのメモリ領域

RGBA = (0.5, 0.5, 0.5 ,1.0) という灰色を メッシュのColorに設定したとします。

f:id:r-ngtm:20210119054337p:plain
RGB = (0.5, 0.5, 0.5)

メモリ上では、以下のような32bit Float値が4つ並びます。

f:id:r-ngtm:20210119045127p:plain
Color型は16byteカラー (RGBAがそれぞれ32bit float)

ParticleSystemは32bitカラーとして解釈

ParticleSystemは頂点カラーのことを Color32 だと思っているので、カラー情報を以下のように読み取ります。

f:id:r-ngtm:20210119045904p:plain
32bit color として解釈

1つの16byte カラーは 4つの32bit カラーとして解釈されます。

0.5を 32bit カラーとして解釈された場合

0.5という32bit float のビット列は、以下のような32ケタの2進数になります。

f:id:r-ngtm:20210119050221p:plain
0.5のビット列

ParticleSystemはこれをbyte が4つ並んだRGBAカラーとして解釈し、RGBA = (63, 0, 0, 0) になります。

f:id:r-ngtm:20210119051003p:plain
0.5という値は RGBA = (63, 0, 0, 0) として認識される

RGBA = (63, 0, 0, 0) は画面上では以下のような色として表示されます。

f:id:r-ngtm:20210119052512p:plain
RGB = (63, 0, 0)

0.6 を32bitカラーとして認識した場合

Rチャンネルが0.6だった場合も考えてみます。

0.6という32bit floatのビット列は以下のようになります。

f:id:r-ngtm:20210119051924p:plain
0.6のビット列

このビット列を32bit RGBAカラーとして解釈すると、RGBA = (63, 25, 153, 154) になります。

f:id:r-ngtm:20210119052302p:plain
0.6を32bit RGBAカラーとして解釈した場合

RGBA = (63, 25, 153, 154)は画面上では以下のような色として表示されます。

f:id:r-ngtm:20210119052434p:plain
RGB = (63, 25, 153)


まとめ

Color[] を 頂点カラーに設定すると、想定とは異なる色が表示されてしまうことがわかりました。

メッシュを作成するときは以下のように32bitカラーを設定すれば、色が壊れる現象を回避できます。

mesh.colors32 = colors32; // Color32[]
f:id:r-ngtm:20210119055540p:plain
Colors32 は 4byteカラー(32bitカラー)としてメッシュに保存される