Unity Compute Shader メモ
compute shaderとは?
- GPUで動作するプログラム
- DirectX 11 スタイルのHLSL言語で記述される
概要 .computeのファイルにプログラムを書き込む。 compute shaderではC#スクリプト(.cs)と組み合わせてUnityで実行する。
気付き .csで使用するcompute bufferにはcompute shaderからの計算で受け取りたい値を格納する。 受け渡ししたい値は以下のように.computeで宣言する
RWStructuredBuffer<float> Result; - Compute Shaderでは、前回の計算結果を保持するためには通常、RWStructuredBufferが必要になる。例えばライフゲームは、セルの状態を更新するために前の状態を参照する必要があるため、RWStructuredBufferを使用して前回の計算結果を格納し、それを次の計算に使用する。
競合状態が発生しているのか?
RWStructuredBuffer
構造体である T 型を取ることができる読み取り/書き込みバッファー。
ParallaxBackground
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class ParallaxBackground : MonoBehaviour
{
[HideInInspector]
[SerializeField]
bool isInitialized = false;
//あらかじめ各画像を紐付けしておく。
[Header("背景画像 (0が最奥、順に手前)")]
[SerializeField]
Sprite[] backgroundSprites = new Sprite[3];
//左右スクロール対応の場合は、各X値を-1920(1画像幅分)に設定してください。
[Header("背景画像のオフセット (ズラす値)(左右スクロール対応の場合は1画像分、左にズラす)")]
[SerializeField]
Vector2 backgroundOffset = new Vector2(-1920, -1920);
//サンプルで使用した、[Ansimuz] GothicVania Cemeteryの場合の画像サイズなので、自分の使用アセットに応じて調整してください。
[Header("背景画像のサイズ")]
[SerializeField]
Vector2 backgroundSpriteSize = new Vector2(1920, 1920);
[Header("背景画像のスクロール率 (奥(0)の物程小さめに指定)")]
[SerializeField]
float[] scrollRates = new float[] {
1.0f,
3.0f,
5.0f,
};
//3Dオブジェクト(キャラクター等)より奥になるように調整。カメラの位置や3Dオブジェクトのサイズによるが30~設定すれば良い。
[Header("カメラからUIへの距離 (カメラの位置や3Dオブジェクトのサイズによるが30~指定)")]
[Range(10.0f, 100.0f)]
[SerializeField]
float planeDistance = 10.0f;
int imageMax = 3;
[Header("スクロール時間")]
[Range(0.1f, 3.0f)]
[SerializeField]
float scrollDuration = 1.0f;
float smoothTime;
[Header("スクロール速度の上限 (多分deltaTimeが掛かるので大きめに指定)")]
[Range(500.0f, 10000.0f)]
[SerializeField]
float scrollSpeedMax = 1000.0f;
//各背景画像のRectTransform。
[HideInInspector]
[SerializeField]
RectTransform[] backgroundsRt;
//背景画像数。
[HideInInspector]
[SerializeField]
int backgroundMax;
//各背景画像がスクロールした量。
[HideInInspector]
[SerializeField]
Vector2[] backgroundScrollValues;
//RectMask2Dを有効にした状態で実行すると、スクロールしても画面外に設置した画像が非表示になる仕様っぽいので、実行時に有効化している。
[HideInInspector]
[SerializeField]
RectMask2D parallaxBackgroundRectMask2D;
//スクロール経過時間。
float scrollElapsedTime;
//スクロール加速度。SmoothDampに必要。
[HideInInspector]
[SerializeField]
Vector2[] scrollVelocities;
//コルーチンの管理に使用。
Coroutine scroll;
//前にスクロールが呼ばれた時のプレイヤーの位置。
Vector2 previousPlayerPosition = Vector2.zero;
//一時的に使用。
Canvas parallaxBackgroundCanvas;
GameObject parallaxBackgroundGo;
RectTransform parallaxBackgroundRt;
GameObject tempBackgroundGo;
RectTransform tempBackgroundRt;
Image tempBackgroundImg;
Vector2 tempBackgroundPosition;
Vector2 tempBackgroundsPosition;
void Awake()
{
if (!isInitialized)
CreateParallaxBackground();
parallaxBackgroundRectMask2D.enabled = true;
//SmoothDampのsmoothTimeと、スクロールの長さが厳密には違うので、一回り小さく計算しておく。
smoothTime = scrollDuration * 0.33f;
}
//背景画像をスクロールしたい場合にコレを呼ぶ。引数にはプレイヤーの位置を渡す(位置差でなく)。
public void StartScroll(Vector3 _playerPosition)
{
Vector2 playerPosition = new Vector2(_playerPosition.x, _playerPosition.y);
//1画像分進んだ時、スクロールが繋がるように良い感じに戻している。
for (int i = 0; i < backgroundMax; i++)
{
backgroundScrollValues[i] -= (playerPosition - previousPlayerPosition) * scrollRates[i];
if (backgroundSpriteSize.x < backgroundsRt[i].anchoredPosition.x)
{
backgroundScrollValues[i].x -= backgroundSpriteSize.x;
tempBackgroundsPosition.Set(backgroundSpriteSize.x, 0);
backgroundsRt[i].anchoredPosition -= tempBackgroundsPosition;
}
else if (backgroundsRt[i].anchoredPosition.x < -backgroundSpriteSize.x)
{
backgroundScrollValues[i].x += backgroundSpriteSize.x;
tempBackgroundsPosition.Set(backgroundSpriteSize.x, 0);
backgroundsRt[i].anchoredPosition += tempBackgroundsPosition;
}
if (backgroundSpriteSize.y < backgroundsRt[i].anchoredPosition.y)
{
backgroundScrollValues[i].y -= backgroundSpriteSize.y;
tempBackgroundsPosition.Set(0, backgroundSpriteSize.y);
backgroundsRt[i].anchoredPosition -= tempBackgroundsPosition;
}
else if (backgroundsRt[i].anchoredPosition.y < -backgroundSpriteSize.y)
{
backgroundScrollValues[i].y += backgroundSpriteSize.y;
tempBackgroundsPosition.Set(0, backgroundSpriteSize.y);
backgroundsRt[i].anchoredPosition += tempBackgroundsPosition;
}
}
//多重実行防止。
if (scroll != null)
{
StopCoroutine(scroll);
}
scroll = StartCoroutine(Scroll());
previousPlayerPosition = playerPosition;
}
IEnumerator Scroll()
{
scrollElapsedTime = 0;
while (true)
{
scrollElapsedTime += Time.deltaTime;
for (int i = 0; i < backgroundMax; i++)
{
tempBackgroundsPosition.Set(
backgroundScrollValues[i].x + backgroundOffset.x,
backgroundScrollValues[i].y + backgroundOffset.y
);
backgroundsRt[i].anchoredPosition = Vector2.SmoothDamp(backgroundsRt[i].anchoredPosition, tempBackgroundsPosition, ref scrollVelocities[i], smoothTime, scrollSpeedMax);
}
if (scrollDuration <= scrollElapsedTime)
{
//SmoothDampはVelocityの値を参考にして現在の速度を出す為、初期化しておかないと次回実行時に動きが残る。
for (int i = 0; i < backgroundMax; i++)
{
scrollVelocities[i] = Vector2.zero;
}
scroll = null;
yield break;
}
yield return null;
}
}
//各種コンポーネントをアタッチし、背景画像等を生成。
public void CreateParallaxBackground()
{
if (backgroundSprites == null || backgroundSprites.Length == 0)
return;
backgroundMax = backgroundSprites.Length;
parallaxBackgroundCanvas = GetComponent<Canvas>();
if (parallaxBackgroundCanvas == null)
return;
backgroundsRt = new RectTransform[backgroundMax];
scrollVelocities = new Vector2[backgroundMax];
backgroundScrollValues = new Vector2[backgroundMax];
parallaxBackgroundCanvas.renderMode = RenderMode.ScreenSpaceCamera;
parallaxBackgroundCanvas.worldCamera = Camera.main;
parallaxBackgroundCanvas.planeDistance = planeDistance;
//ボタンを設置しないので、このCanvasへのタッチ判定を無効化しておく(インスペクターから削除しても良い)。
GetComponent<GraphicRaycaster>().enabled = false;
parallaxBackgroundGo = new GameObject("ParallaxBackground");
parallaxBackgroundRt = parallaxBackgroundGo.AddComponent<RectTransform>();
parallaxBackgroundRectMask2D = parallaxBackgroundGo.AddComponent<RectMask2D>();
parallaxBackgroundRectMask2D.enabled = false;
parallaxBackgroundRt.SetParent(transform);
parallaxBackgroundRt.localScale = Vector3.one;
parallaxBackgroundRt.localPosition = Vector3.zero;
parallaxBackgroundRt.sizeDelta = gameObject.GetComponent<RectTransform>().sizeDelta;
for (int i = 0; i < backgroundMax; i++)
{
backgroundsRt[i] = new GameObject(System.String.Format("Backgrounds{0}", i + 1)).AddComponent<RectTransform>();
backgroundsRt[i].SetParent(parallaxBackgroundRt);
backgroundsRt[i].localScale = Vector3.one;
backgroundsRt[i].localPosition = Vector3.zero;
tempBackgroundPosition.Set(backgroundOffset.x, backgroundOffset.y);
backgroundsRt[i].anchoredPosition = tempBackgroundPosition;
for (int j = 0; j < imageMax; j++)
{
for (int k = 0; k < imageMax; k++)
{
tempBackgroundGo = new GameObject(System.String.Format("Background{0}", k + 1));
tempBackgroundRt = tempBackgroundGo.AddComponent<RectTransform>();
tempBackgroundImg = tempBackgroundGo.AddComponent<Image>();
tempBackgroundImg.sprite = backgroundSprites[i];
tempBackgroundImg.raycastTarget = false;
tempBackgroundRt.SetParent(backgroundsRt[i]);
tempBackgroundRt.localScale = Vector3.one;
tempBackgroundRt.localPosition = Vector3.zero;
tempBackgroundRt.sizeDelta = backgroundSpriteSize;
tempBackgroundPosition.Set(backgroundOffset.x + backgroundSpriteSize.x * j, backgroundOffset.y + backgroundSpriteSize.y * k);
tempBackgroundRt.anchoredPosition = tempBackgroundPosition;
}
}
}
//カメラと平行に設置したい場合には、localRotationをリセットしておく。
parallaxBackgroundRt.localRotation = Quaternion.identity;
isInitialized = true;
}
}