rn.log

備忘録など

【ComputeShader メモ 1】平均化フィルタで画像をぼかす

はじめに

Compute Shader を理解したいと思い、簡単に実装できそうな 平均化フィルタ をCompute Shaderで実装してみました。

環境

Unity 2020.2.0f1
Universal RP 10.2.2

平均化フィルタの効果

平均化フィルタを適用することで、画像がぼやけるようなエフェクトをかけることができます。

f:id:r-ngtm:20210117162402p:plain
平均化フィルタの適用

平均化フィルタの仕組み

画像のあるピクセルに注目し、その値を周辺のピクセルの平均値に置き換えるような処理を考えます。

f:id:r-ngtm:20210117155047p:plain
平均化フィルタ(3x3)を利用した画素の置き換え

この平均化処理を画像のすべてのピクセルに対して適用することで、画像がぼやけたような効果が得られます。
先ほどの例ではフィルタサイズ3x3で説明していましたが、以下の例では64x64という大きいサイズのフィルタを適用しています。

f:id:r-ngtm:20210117162402p:plain
平均化フィルタの適用

平均化フィルタの実装 (Compute Shader)

ComputeShaderによる平均化フィルタの実装例を以下に示します。

ここでは[numthreads(1,1,1)] と書いてしまっていますが、これはあまり良いコードではありません(理由は後述します)

#pragma kernel CSMain

int FilterSize; // フィルタサイズ
Texture2D<float4> Texture; // 入力画像
RWTexture2D<float4> RenderTexture; // 平均化フィルタを書けた画像の書き込み先

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    // 周辺ピクセルを合計する
    float4 total = 0.0; 
    for (int y = -FilterSize/2; y <= FilterSize/2; y++) {
        for (int x = -FilterSize/2; x <= FilterSize/2; x++) {
            total += Texture[id.xy + int2(x, y)]; // sRGBテクスチャから取得する色には逆ガンマ補正がかかる(リニア色空間)
        }
    }
    // フィルタの大きさで割る (平均値をとる)
    total = total / FilterSize / FilterSize;
    
    // 書き込み先のRenderTextureはsRGBなので、ガンマ補正をかける (LinearカラーをsRGBカラーにする)
    total = pow(total, 1.0/2.2);
    
    // RenderTexture へ書き込み
    RenderTexture[id.xy] = total;
}

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

using UnityEngine;
using UnityEngine.UI;

public class AverageFilter : MonoBehaviour
{
    [SerializeField] private ComputeShader shader; // 実行するComputeShader
    [SerializeField] private Texture _texture; // 入力画像
    [SerializeField] private RawImage targetImage; // RenderTextureを割り当てる対象のRawImage
    [SerializeField] private int filterSize = 64; // フィルタサイズ
    private RenderTexture _renderTexture;

    private void Start()
    {
        // RenderTexture作成
        _renderTexture = new RenderTexture(_texture.width, _texture.height, 0);
        _renderTexture.enableRandomWrite = true;
        _renderTexture.Create();
        
        // 実行したいカーネル(関数のようなもの)
        int kernel = shader.FindKernel("CSMain"); 

        // ComputeShaderへデータを渡す
        shader.SetInt("FilterSize", filterSize); // フィルタサイズ
        shader.SetTexture(kernel, "Texture", _texture); // 元となるテクスチャ
        shader.SetTexture(kernel, "RenderTexture", _renderTexture); // 書き込み先のテクスチャ
        
        // テクスチャの画素数ぶんのスレッドを回す
        shader.Dispatch(kernel, _texture.width, _texture.height, 1);

        targetImage.texture = _renderTexture;
    }
}

結果

元のテクスチャ(左側)と、ComputeShaderでぼかした結果のレンダーテクスチャを割り当てたものを並べてみました。
両方ともRawImageにテクスチャを割り当てて表示しています。

f:id:r-ngtm:20210117162402p:plain
平均化フィルタの適用

スレッドグループ数は1を指定しないほうが良い

先ほどはスレッドグループ数 [numthreads(1,1,1)] を指定していました。
下記リンクによれば、スレッドグループ数には32や64といった数を指定した方が良いようです。
www.reddit.com


スレッドグループ数に1を指定した場合でも、nVidiaGPUでは32スレッドのグループが確保されてしまい、結果としてGPUの3%ぶんしか活用されないようです。

E.G. nVidia's hardware is organized in groups of 32 threads, any threadgroups of less than 32 threads is still using 32 hardware threads. The extra hardware threads are just prevented from writing to memory so they have no effect. A 1080ti has about 28 compute clusters, each with 128 threads. If you do (1,1,1) you can run at most 112 threads (28CUs * 4 simd's per CU) out of the 3584 (28*128) potential threads that the gpu can run. Achieving about 3% gpu utilization.

修正ComputeShader

今回は1024x1024の画像に対して平均化フィルタを適用しているため、[numthreads(32,32,1)] を指定してみます。 (合計 32 * 32 * 1 = 1024スレッドが実行されます)

#pragma kernel CSMain

int FilterSize; // フィルタサイズ
Texture2D<float4> Texture; // 入力画像
RWTexture2D<float4> RenderTexture; // 平均化フィルタを書けた画像の書き込み先

[numthreads(32,32,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    // 周辺ピクセルを合計する
    float4 total = 0.0; 
    for (int y = -FilterSize/2; y <= FilterSize/2; y++) {
        for (int x = -FilterSize/2; x <= FilterSize/2; x++) {
            total += Texture[id.xy + int2(x, y)];
        }
    }
    // フィルタの大きさで割る (平均値をとる)
    total = total / FilterSize / FilterSize;
    
    // RenderTextureはsRGBなので、リニアカラーにガンマ補正をかける
    total = pow(total, 1.0/2.2);
    
    // RenderTexture へ書き込み
    RenderTexture[id.xy] = total;
}

修正ComputeShaderを実行するC#スクリプト

Threadグループ数(numthread)を考慮して、Threadグループサイズを計算します。

 グループ数 \times グループサイズ = 合計スレッド数 


今回はテクスチャの画素数ぶんのスレッドを回すため、Threadグループサイズは以下の計算で求まります。

 グループサイズ  = \dfrac{テクスチャ画素数}{グループ数}


using UnityEngine;
using UnityEngine.UI;

public class AverageFilter : MonoBehaviour
{
    [SerializeField] private ComputeShader shader; // 実行するComputeShader
    [SerializeField] private Texture _texture; // 入力画像
    [SerializeField] private RawImage targetImage; // RenderTextureを割り当てる対象のRawImage
    [SerializeField] private int filterSize = 64; // フィルタサイズ
    private RenderTexture _renderTexture;

    private void Start()
    {
        // RenderTexture作成
        _renderTexture = new RenderTexture(_texture.width, _texture.height, 0);
        _renderTexture.enableRandomWrite = true;
        _renderTexture.Create();
        
        // 実行したいカーネル(関数のようなもの)
        int kernel = shader.FindKernel("CSMain"); 
        
        // ComputeShaderへデータを渡す
        shader.SetInt("FilterSize", filterSize); // フィルタサイズ
        shader.SetTexture(kernel, "Texture", _texture); // 元となるテクスチャ
        shader.SetTexture(kernel, "RenderTexture", _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:20210117171253p:plain
平均化フィルタサイズ 64 x 64 での実行結果
f:id:r-ngtm:20210117171337p:plain
平均化フィルタサイズ 256 x 256 での実行結果

【ガンマ補正】sRGBテクスチャをRenderTextureに書き込むと表示が暗くなる話【Unity】

環境

Unity2020.2.0f1
Universal RP 10.2.2

はじめに

sRGBのテクスチャをCompute Shaderでサンプリングし、
計算結果をRenderTexture(sRGB)へコピーすると、表示がなぜか暗くなるという不可解な現象に遭遇しました。

f:id:r-ngtm:20210117111100p:plain
sRGBテクスチャをRenderTexture(sRGB)にコピーすると暗くなる

シェーダーにてsRGBテクスチャをサンプリングした色がリニア色空間であり、これをsRGBのテクスチャに書き込んだことによって発生する現象だと考えられます。

sRGBテクスチャから取得する色

sRGBテクスチャの色空間はガンマ色空間です。
sRGBテクスチャ(ガンマ色空間)をシェーダーでサンプリングする時、逆ガンマ補正された値(リニア色空間)がシェーダーへ流れてきます。

f:id:r-ngtm:20210117103352p:plain
sRGBテクスチャをsRGBレンダーテクスチャにコピーすると暗くなる

sRGBレンダーテクスチャにリニアカラーを書き込んでしまうと、Unity側でガンマ補正が行われなくなるため、表示結果が暗くなります。

sRGBカラーが逆ガンマ補正される理由について

sRGBテクスチャをサンプリングする時、逆ガンマ補正がなぜ入るのかをまとめてみたいと思います。

sRGBカラーはそのままモニターに表示される

ここで、sRGBテクスチャに 0.2, 0.3 という色が保存されていたとします。

これらをそのままモニターに表示した場合、0.2, 0.3 という色が表示されます。
モニターに送信される色は、ディスプレイ側で逆ガンマ補正がかかります。

f:id:r-ngtm:20210117122257p:plain
sRGBテクスチャカラーが画面に表示されるまで

sRGBカラーをそのまま使うと表示がおかしくなる

sRGBカラー 0.2, 0.3 という値をシェーダー内でそのまま足し算してしまうと、モニターには 1.134... という値が表示されてしまい、 0.2 + 0.3 = 0.5 という計算結果に一致しません。

f:id:r-ngtm:20210117122956p:plain
sRGBテクスチャカラーをそのまま足すと結果がおかしくなる

sRGBカラーを逆ガンマ補正すると表示が正しくなる

sRGBカラー を最初に逆ガンマ補正してからシェーダーにて足し算を行い、
モニターに送信する前にガンマ補正をかけてやると、モニター上には 0.5 という色が表示されます。

sRGBカラーの  0.2 + 0.3 = 0.5 という計算結果と、モニター上の表示0.5 が一致するのでこれは都合が良い結果となります。

f:id:r-ngtm:20210117123053p:plain
sRGBカラーを逆ガンマ補正してから計算に使った場合

sRGBカラーの逆ガンマ補正はUnityが自動でやってくれるようです。

f:id:r-ngtm:20210117124325p:plain
sRGBのチェックを入れると、シェーダー利用時に逆ガンマ補正がかかるようになります

【シェーダー】バイリニア補間で4色グラデーションを作る

はじめに

シェーダーを使い、以下のような4色のグラデーションを作る方法を紹介します。

f:id:r-ngtm:20210116142756p:plain:w480
4色グラデーション

2色グラデーション

x座標を使って二つの色を線形補間した場合、以下のような二色のグラデーションを作ることができます。

f:id:r-ngtm:20210116153308p:plain:w480
2つの色を線形補間
vec3 c = mix(GREEN, BLUE, p.x);  // x座標で 緑と青を線形補間

4色グラデーション

x, y座標を使って4色を補完すると、以下のような4色のグラデーションを作ることができます。

f:id:r-ngtm:20210116152839p:plain:w480
x, y で4つの色をブレンド
vec3 tc = mix(YELLOW, RED, p.x); // top : 黄と赤の線形補間 (上側の色)
vec3 bc = mix(GREEN, BLUE, p.x); // bottom : 緑と青の線形補間 (下側の色)
vec3 c = mix(bc, tc, p.y); // y座標で top と bottom の線形補間

この補間はバイリニア補間(双一次補間)という名前がついています。


GLSLによる4色グラデーション実装

uniform vec2 resolution;

#define YELLOW vec3(1,1,0)
#define RED vec3(1,0,0)
#define GREEN vec3(0,1,0) 
#define BLUE vec3(0,0,1)

void main()
{
    vec2 p = gl_FragCoord.xy / resolution.xy;    
    vec3 tc = mix(YELLOW, RED, p.x); // top color
    vec3 bc = mix(GREEN, BLUE, p.x); // bottom color
    vec3 c = mix(bc, tc, p.y);
    gl_FragColor = vec4(c, 1);
}

シェーダーグラフによる実装

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

【Unity C#】エフェクト用のらせんメッシュを作る

はじめに

お遊びで、エフェクト用のらせんメッシュをUnity C# で作ってみました。
今回の記事では、Unity C# でらせんメッシュを作る方法を解説したいと思います。

f:id:r-ngtm:20210115205521p:plain:w480
Unity C# で動的に作成したメッシュ

ソースコード

らせんメッシュのC#スクリプトは、GitHubリポジトリにて公開しています。SpiralMeshGenerator.csというスクリプトが当該のスクリプトになります。

github.com

Unity 上でメッシュを作るメリット

エフェクト用のメッシュを用意する時、通常はHoudiniやBlenderといった外部のDCCツールを使ってエフェクト用の3Dモデルを作成することになります。
しかし、DCCツールとUnityを行ったり来たりするのはコストがかかります。

Unity上でメッシュを作れる場合、Unity上で結果を見ながら形状を調整できるため、低コストでエフェクト用のメッシュを作ることが可能となります。



作り方解説

STEP1 : らせんのカーブを作る

らせんを作る考え方

らせん上の点の方位角を  \theta と置いたとき、らせん上の点の座標Pは (cos k \theta, \theta, sin k \theta) のような形で表されます。

f:id:r-ngtm:20210115185818p:plain:w480
らせん
/// <summary>
/// カーブに沿ったポイントを作成
/// </summary>
private static void ComputeCurvePoints(SpiralMeshGenerator generator, out Vector3[] points)
{
    float curveTimeMin = generator.radiusCurve[0].time;
    float curveTimeMax = generator.radiusCurve[generator.radiusCurve.length - 1].time;

    points = new Vector3[generator.curveDivsV]; // らせん上のポイントの座標を保存するための配列
    for (int i = 0; i < generator.curveDivsV; i++)
    {
        float t = (float) i / (generator.curveDivsV - 1);
        float radian = t * 2f * Mathf.PI * generator.loops;
        float radius = generator.radiusCurve.Evaluate(Remap(t, 0f, 1f, curveTimeMin, curveTimeMax));  // らせんの半径はAnimationCurveで管理している
        float x = Mathf.Cos(radian) * radius;
        float y = generator.height * t;
        float z = Mathf.Sin(radian) * radius;
        points[i] = new Vector3(x, y, z);
    }
}

半径rのコントロール

高さ \thetaでの らせんの半径 r(\theta) は AnimationCurve でコントロールできるようにしています。

f:id:r-ngtm:20210115192431p:plain:w240
らせんの半径r
float radius = generator.radiusCurve.Evaluate(Remap(t, 0f, 1f, curveTimeMin, curveTimeMax));  // らせんの半径はAnimationCurveで管理している

これにより、AnimationCurveを変えることでらせん形状のバリエーションを出せるようになります。

f:id:r-ngtm:20210115204435p:plain:w320
様々ならせん形状



STEP2 : らせんに沿ってメッシュを作成する

らせんメッシュの作り方

STEP1 でらせんを計算しました。

f:id:r-ngtm:20210115194751p:plain:w280
らせんのライン

らせんの各ポイントから外側へ向かうベクトル  r を計算します。

f:id:r-ngtm:20210115212315p:plain:w280
外側へ向かうベクトル r

ベクトル  r にそってメッシュを広げることで、らせんメッシュを作ることができます。

f:id:r-ngtm:20210115212710p:plain:w280
らせんメッシュ


このベクトル  r外積を利用して計算することができます。

外積を利用する

らせんに沿うような接線ベクトルを  \vec t 、y軸上向きのベクトルを  \vec uと置きます。

らせんの外側を向くベクトル  \vec r外積を利用して計算できます。

 \vec r = \vec t \times \vec u

f:id:r-ngtm:20210115010540p:plain:w320
外側方向のベクトル r

STEP3 : らせんメッシュをねじる

 \vec r, \vec  u を軸とみなす

STEP2で、二つのベクトル  \vec r  \vec  u を計算しました。
ここで、これらのベクトルを座標軸と考えて、以下のような点Pを取ることを考えます。

 \vec P = cos \theta \cdot \vec r + sin  \theta \cdot \vec u
f:id:r-ngtm:20210115011833p:plain:w320

 \vec r, \vec  u を利用して、メッシュ頂点を計算する

この点Pをメッシュの頂点として利用すると、らせんメッシュをらせんに沿ってひねったような軌跡を描きます。

f:id:r-ngtm:20210115201110p:plain:w240
点Pを取る
f:id:r-ngtm:20210115201713p:plain:w240
点Pをメッシュ頂点として利用
f:id:r-ngtm:20210115213052p:plain:w240
結果

この \vec r, \vec  uは、線形代数学では直交基底ベクトルと呼ばれたりもします。

【シェーダーグラフメモ その56】内積を使って任意方向のグラデーションを作る

UV座標と2次元ベクトルの内積 = グラデーション

以下のシェーダーグラフを見てください。

UV座標とベクトル(1, 0.5) の内積をとると、(1, 0.5)の方向へ増加するグラデーションができます。

f:id:r-ngtm:20210102165822p:plain
UVとベクトルの内積

数式による解説

UV座標  p = (x, y) と ベクトル  (1, 0.5)内積を計算すると、  x + 0.5 y になります。

この式がある色の値 g をとると考えた場合、以下のような直線の方程式を得ます。

x + 0.5 y = g

グラデーションの色の値

gの値を変えながら、グラデーションの上に直線x + 0.5y = g を引いてみると以下のようになります。

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


このグラデーションは直線  x + 0.5y = g (0 \leq  g \leq 1.5) が表す領域であるとも言えます。

ちなみに、直線 x + 0.5y = g はベクトル  (1, 0.5) と垂直になっています。

内積に使用するベクトルは正規化したほうが良い

内積に使用するベクトルの長さが変化すると、内積結果は変わってしまいます。

f:id:r-ngtm:20210102164043p:plain
ベクトルの長さによって内積結果が変わる

内積に使用するベクトルは Normalizeで長さを1にした方が良いでしょう。

f:id:r-ngtm:20210102164314p:plain
Normalizeを使う

3次元の内積

3次元座標と3次元ベクトルの内積をとると、これもベクトル方向へ増加するグラデーションになります。

f:id:r-ngtm:20210102162320p:plain
3次元座標と3次元ベクトルの内積

ベクトル(1, 2, 3)の正規化 と3次元座標の内積をとると、(1, 2, 3) の方向へ増加するグラデーションになります。

f:id:r-ngtm:20210102162707p:plain
ベクトル(1, 2, 3) の方向へ増加するグラデーション

3次元座標とベクトルの内積は平面の方程式を作る

ベクトル(1, 2, 3)と3次元座標(x, y, z)内積x + 2y +3zになります。

この式がある色の値gをとると考えた場合、以下の方程式を得ます。
x + 2y +3z = g

これはベクトル(1, 2, 3)に垂直な平面の方程式になっています。

g = 0 の部分は、Unity上のオブジェクトでは以下のように対応します。
f:id:r-ngtm:20210102165228p:plain

2020年を振り返る

 はじめに


こんにちは。普段はUnityエンジニアとして勤めているかもそば(@rn49rn49)と申します。

2020年も残すところあと僅か。

来年は丑(うし)年なので、乳でもたくさん飲みたいと思います。

 

今回の記事では、2020年をふりかえりたいと思います。

 

   

活動01. 技術アウトプット

2020年はブログ記事をいくつか書きました。

ゲームグラフィックスに関係する記事が多いです。

 

技術記事 

【Universal RP】SRP Batcher対応の積雪シェーダーを書いてみた - Qiita

 

【Unity】C# Job System + Burstで波動方程式を実装し、 ShaderGraphで水を描画する - Qiita

 

【シェーダーグラフメモ その55】ボロノイを利用した、水中コースティクス表現 - rn.log

 

Unityエフェクトレシピ02 - 六角形シールド表現 (2/2) - rn.log

 

独学用途にGASを覚えたりもしました。 

【GAS】Googleスプレッドシートに並べたクイズをランダム出題する (zenn.dev)

 

 

活動02. Unity1週間ゲームジャム

2020年はUnity1週間ゲームジャムに2回参加しました。

 

テーマ : あける

空中でボールを蹴りながらゴールに入れるゲームを作りました。

ShaderGraphを使って画面を盛ったり、Cinemachineでカメラをグリグリ動かしています。

エフェクト用にもShaderGraphを実は使っています。

www.youtube.com

 

Unity1週間ゲームジャム「空中キックボール」開発記録|かもそば|note

 

 

技術的な話

ゲームの裏ではテストコードを書いたり、Zenjectを使ってDIしたり、NCMB使ってランキング実装したり、RenderererFeatureを使って描画をコントロールしたり、と技術面でいろいろと勉強になりました。

 

 

 

テーマ : 密

レーザーを撃つゲームを作りました

ShaderGraphやエフェクトで画面を盛っています。

ブログ

【ゲーム制作】Unity1Week(お題:密)に参加しました - rn.log

 

活動03. エフェクトセミナー登壇

ご縁があって、Born Digital さんでエフェクトセミナーを発表させていただきました。

HoudiniとUnity ShaderGraphで作る Sci-Fi シールド表現(ボーンデジタルユーザー限定) – Born Digital サポート

 

発表したエフェクトはブログにて紹介しています。

Unityエフェクトレシピ02 - 六角形シールド表現 (2/2) - rn.log (hatenablog.com)


活動04. ゲームエフェクトを学び、そして辞める

ゲームエフェクトに興味があったため、

去年の冬から今年の9月ごろにかけて、

FLYPOTさんのエフェクト講座でゲームエフェクト制作を学んでいました。

エフェクト講座 | FLYPOT.LLC

 

しかし、9月頃にはゲームエフェクトからはいったん距離を置くことにしました。

 

 

ゲームエフェクトを辞めた理由

・作りたいものがなくなった(作ってみたかったものは趣味で作りきった)

・趣味としてエフェクトを作るのは楽しいが、職業アーティストになりたいわけではなかった

プログラマーとしての業務経験をエフェクトに活かす道が見えなかった

・今後のキャリアを考える上で、エフェクト制作スキルはあまり重要ではないように思えてきた

いろいろな業界で役に立つスキルが欲しい(エフェクト制作スキルは非ゲーム業界では役に立たなさそうに思います)

・ゲームエフェクト以外にも興味が沸いた

作ったエフェクト一部紹介

 

 

 

活動05. おめシス(うんちゃん)のゲームを作った

今年の9月 ~ 11月にかけて、NEWVIEW CYPHERというイベントに参加してゲームを作りました。

newview.design

 

 

かもそば = おめシスのファン

実は私かもそば、VTuberとして活動しているおめシス(おめがシスターズ)のファンでございます。

YouTubeに上がっているおめシスの動画はほぼ全て観ました。

www.youtube.com

 

 

イベント参加特典 = おめシスがゲームを遊んでくれる

NEWVIEW CYPHERというイベントに参加してゲームを提出すると、

「おめシスがゲームを遊んでコメントをくれる」という特典がありました。

 

おめが団としてはこれはもう参加するしかない! ということで光の速さで参加を決めました。 (おめが団 = おめシスのファンを指す言葉)

 

 

 

 

作ったゲーム : うんちゃんタワー

おめシスのうんちゃんを使ったVRゲーム「うんちゃんタワー」を制作いたしました。

制作ツールはSTYLYUnity になります。

www.youtube.com

 

 

賞をいただきました

制作したうんちゃんタワーですが、ありがたいことにベストプレイヤー賞を頂きました。

 

うんちゃんタワーは下記リンクから遊べます。 (遊ぶには、OculusQuestなどのVRバイスが必要です)

gallery.styly.cc

 

活動06. GLSLレイマーチング

GLSLレイマーチングを利用して、映像的な表現を作る趣味に1か月ほどハマっていました。 

 

 

制作した作品はNEORTにまとめています。

neort.io

 

自分で書いたGLSLレイマーチングより、既存のレンダラーを使ったほうが短い工数で品質の高い絵が出せてしまうため、続ける必要性を感じなくなってきています。

 

活動07. ポートフォリオを整理した

ポートフォリオが長年放置されていましたが、重い腰を上げて整理しました。

職務経歴もしれっと載せていたりします。

rngtm.github.io

 

ポートフォリオの作成にはGitBookを使っており、作り方は別の記事にてまとめています。

r-ngtm.hatenablog.com

 

 

2020年のまとめ

・GLSLレイマーチングを通してレンダリングへの理解が深まった

・技術インプット・アウトプットがあまりできていなかった

VRゲームを作るのはけっこう楽しい 

・ゲームエフェクトは趣味としてやる分には楽しい

 

2021年の目標

目標1 : 自分の武器を見つける

今の自分の興味のあるキーワードに+αして、自分だけの武器を身に着けられたらいいと思っています。

・シェーダー

・ゲームエフェクト(VFX)

レンダリング

・数学

あたりのキーワードに興味があるので、この領域を広めて良ければ良いなと思います。

 

目標2 : スキルアップ

自分の持っている手札の領域を広め、より良い物作りができるようなスキルを身に着けることを2021年の目標にしたいと思います。

・DIなどの設計の知識を身に着けてより良いプログラムを書けるようにする

・数学・物理・アルゴリズムなどへの知識を深め、アプリ実装力を向上させる

・シェーダーやレンダリングパイプラインへの理解を深め、表現の実装力を向上させる

・CPU/GPUなどの低レイヤーへの理解を深め、パフォーマンスの良い実装ができるよ

うにする

・コミュニケーション力の向上

などなど

 

今までは技術ばかりを見ていましたが、2021年はより広い視点を持てるようになりたいとも思います。

 

2020年はコミュニティ活動があまりできていなかったので、2021年は何かしらの形でコミュニティに還元していきたいなぁとも思います。

勉強会登壇したい

 

目標3 : アウトプットのチャンネルを増やす

今まではTwitterかブログの二つでしか情報を発信していませんでした。

それまでとは別のチャンネルで情報を発信出来たら良いなぁと思います。

技術書を出してみたい

 

目標4 : 転職

2021年の4つ目の目標は転職です。

転職のモチベーションとしては、

・新しいことにチャレンジしてみたい (新しい刺激が欲しい)

・自分の専門性を高めたい (自分だけの武器が欲しい)

収入を上げたい (同じ会社にいても給料は上がりにくい)

 

現在考えている業界は xR系、ハイカジュ系あたりでしょうか

(シェーダーを組むスキルや、ゲーム開発経験が役に立ちそう)

 

趣味でシェーダーを組んで得た知識が現職の会社ではあまり活用できていないのが、

ちょっとだけフラストレーションが溜まっていたりします。

 

 

2020年に買った本

2020年は数学/グラフィックス系の本をたくさん買ったと思います。

(8割くらい積み本になっているので、ちゃんと読まなきゃ...)

 

UniRx/UniTask完全理解 より高度なUnity C#プログラミング | 打田 恭平 |本 | 通販 | Amazon

 

リアルタイムレンダリング 第4版 (Real Time Rendering Fourth Edition 日本語版) | Tomas Akenine-Moller, Eric Haines, Naty Hoffman, 髙橋 誠史, 今給黎 隆, 加藤 諒, 中本 浩 |本 | 通販 | Amazon

 

Amazon.co.jp: コンピュータグラフィックス [改訂新版] eBook: コンピュータグラフィックス編集委員会: Kindleストア

 

 生成 Deep Learning ―絵を描き、物語や音楽を作り、ゲームをプレイする | David Foster, 松田 晃一, 小沼 千絵 |本 | 通販 | Amazon

 

その数式、プログラムできますか? | アレクサンダー・A・ステパノフ, ダニエル・E・ローズ, 株式会社クイープ | コンピュータ・IT | Kindleストア | Amazon

 

ゲームを動かす技術と発想 R | 堂前 嘉樹 | Kindle本 | Kindleストア | Amazon

 

実例で学ぶゲーム3D数学 | Fletcher Dunn, Ian Parberry, 松田 晃一 |本 | 通販 | Amazon

 

Unityでわかる!ゲーム数学 | 加藤 潔 | コンピュータ・IT | Kindleストア | Amazon

 

イラストでわかる物理現象 CGエフェクトLab. CGWORLD (シージーワールド) | 近藤啓太 | コンピュータ・テクノロジー | Kindleストア | Amazon

 

VRが変える これからの仕事図鑑 | 赤津 慧, 鳴海 拓志 | 社会・政治 | Kindleストア | Amazon

 

ミライをつくろう! VRで紡ぐバーチャル創世記 | GOROman, 西田 宗千佳 |本 | 通販 | Amazon

 

 

Unity デザイナーズ・バイブル | 森 哲哉, 秋山 高廣, 室星 亮太, 石塚 淳一, 轟 昂, 牙竜, コポコポ, すいみん, ツバネ, ryosios, トライタム, やまたくさん, クロイニャン, ズゴゴ, karukaru, Maruton, monmoko, 大下 岳志, 時任 友興, 佐藤 英一 |本 | 通販 | Amazon

 

エフェクトグラフィックス 動き・流れ・質感の表現カタログ | 松岡 伸治 |本 | 通販 | Amazon

 

SubstanceDesigner入門 | ぽこぽん丸。, ktk.kumamoto |本 | 通販 | Amazon

 

その他 

r-ngtm.hatenablog.com

【Unity1Week/あける】シェーダー解説 (ShaderGraph)

 

 はじめに

1週間ゲームジャムUnity1Weekに参加したので、記事を書きたいと思います。

Unity 1週間ゲームジャム | フリーゲーム投稿サイト unityroom

 

作ったゲーム

空中でボールを蹴りながらゴールに入れるゲームを作りました。

空中キックボール | フリーゲーム投稿サイト unityroom

 

www.youtube.com

www.youtube.com

 

 

今回の記事

実装したシェーダー表現を4つほど紹介したいと思います。

  1. スクリーン座標を利用したスキャンライン表現
  2. 物陰に隠れない点線
  3. 輪郭線が太い3D矢印
  4. 六角形Sci-Fiシールド

 

環境

Unity2019.4.15f1

Universal RP 7.3.1

 

表現1 : スクリーン座標を利用したスキャンライン

www.youtube.com

 

 

スキャンライン(走査線)とは

モニターの走査線のような模様のことスキャンラインと呼びます。

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

スキャンラインのON/OFF

スキャンラインの作り方 (ShaderGraph)

スクリーン座標(Screen Positionノード)のY成分を利用すると、横縞を作ることができます。

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

ScreenPositionを利用したスキャンライン

 

ゲームで使用する際は、スキャンラインをSubGraphとして定義し、汎用的に使えるようにしています。

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

SubGraphとしてスキャンラインを定義

 

シールド表現のスキャンライン

f:id:r-ngtm:20201228185544g:plain

スキャンライン

 

フレネル効果にスキャンラインを加算しています。

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

 

ゴール表現にもフレネル+スキャンライン表現を利用しています。

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

 

矢印模様のスキャンライン

f:id:r-ngtm:20201228190151g:plain

矢印表現のスキャンライン

モデルUVから矢印模様を作成し、そこにスキャンラインを乗算しています。

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

スキャンラインを乗算

 

表現2 : 物陰に隠れない点線表現

今回のゲームでは、物陰に隠れない点線を作成しました。

f:id:r-ngtm:20201228192000g:plain

3Dモデルの裏に隠れない点線

 

Forward Renderer Dataを利用する

Universal Render Pipelineの Forward Rendererにて

「Overlayレイヤーの不透明オブジェクトはDepthテストを常に成功させる」

という設定を行いました。

 

これによってOverlayレイヤーのオブジェクトは手前のオブジェクトの裏に隠れなくなります。

 

以下はForwardRendererの設定例です。

 

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

ForwardRendererの設定

 

点線オブジェクトの設定

点線オブジェクトは Overlay というレイヤーに設定します。

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

点線オブジェクトのレイヤー

 

結果

点線は常に表示されるようになります。

f:id:r-ngtm:20201228192000g:plain

3Dモデルの裏に隠れない点線

 

表現3 : 輪郭が太い矢印の表現

今回のゲームでは以下のような矢印を作成しました。

矢印の3Dモデル2つを配置しています。

f:id:r-ngtm:20201228193424g:plain

矢印表現

矢印の3Dモデル(Houdini)

矢印の3Dモデルは、Houdiniを利用して作成しています。

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

Houdiniで作成した矢印モデル 2つ

矢印モデルのマテリアルにはUnityの Universal Render Pipeline / Unlit シェーダーを適用します。

 

矢印はOverlayレイヤーにする

大きいモデルの中に小さいモデルを埋め込むと、通常だと内側のモデルは隠れてしまいます。

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

 

矢印モデルのDepthテストを無視することにより、内側のモデルが見える状態にできます。

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

 

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

 

 

描画順のコントロール

内側のオレンジ色モデルのマテリアルはPriority = -1、

外側の白色の矢印モデルはPriority = 0 に設定します。

 

これにより、白色のモデルが描画された後にオレンジ色のモデルが描画されます。

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

白色の矢印はPriority = -1 に設定

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

オレンジ色の矢印はPriority = 0 に設定

 

表現4 : 六角形シールド表現

今回のゲームでは、以下のような六角形のシールド表現を作成しました。

サッカーボール状の3Dモデルにシェーダーで色を付けています。

f:id:r-ngtm:20201228200941g:plain

六角形シールド表現

 

 

3Dモデルの作成(Houdini)

シールド表現用の3DモデルはHoudiniで作成しました。

作り方は過去記事に載せているので、興味があればご覧ください。

Unityエフェクトレシピ 02 - 六角形シールド表現 (1/2) - rn.log (hatenablog.com)

 

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

六角形シールド用3Dモデル

シェーダーで色を付ける

六角形シールドに使用したシェーダーは以下になります。

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

六角形表現で使用しているシェーダー