Unityでブラウン管風シェーダーを作りました。
画面の歪み、走査線のシミュレーションのほか、三種のノイズ、画面のズレなどを実装しました。
ポストエフェクトとして実装しているので、カメラにアタッチするだけで使えます。
RGBをピクセルごとにズラしているので少し暗くなりますが、Bloomフィルタをかけると色が馴染んでいい感じになります。
コード
シェーダー
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
Shader "PostEffects/CRT" { Properties { _MainTex ("Texture", 2D) = "white" {} _NoiseX("NoiseX", Range(0, 1)) = 0 _Offset("Offset", Vector) = (0, 0, 0, 0) _RGBNoise("RGBNoise", Range(0, 1)) = 0 _SinNoiseWidth("SineNoiseWidth", Float) = 1 _SinNoiseScale("SinNoiseScale", Float) = 1 _SinNoiseOffset("SinNoiseOffset", Float) = 1 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; return o; } float rand(float2 co) { return frac(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453); } sampler2D _MainTex; float _NoiseX; float2 _Offset; float _RGBNoise; float _SinNoiseWidth; float _SinNoiseScale; float _SinNoiseOffset; fixed4 frag (v2f i) : SV_Target { float2 inUV = i.uv; float2 uv = i.uv - 0.5; // UV座標を再計算し、画面を歪ませる float vignet = length(uv); uv /= 1.15 - vignet * 0.4; float2 texUV = uv + 0.5; // 画面外なら描画しない if (max(abs(uv.y) - 0.5, abs(uv.x) - 0.5) > 0) { return float4(0, 0, 0, 1); } // 色を計算 float3 col; // ノイズ、オフセットを適用 texUV += _Offset; texUV.x += sin(texUV.y * _SinNoiseWidth + _SinNoiseOffset) * _SinNoiseScale; texUV.x += (rand(floor(texUV.y * 500) + _Time.y) - 0.5) * _NoiseX; texUV = frac(texUV); // 色を取得、RGBを少しずつずらす col.r = tex2D(_MainTex, texUV).r; col.g = tex2D(_MainTex, texUV - float2(0.002, 0)).g; col.b = tex2D(_MainTex, texUV - float2(0.004, 0)).b; // RGBノイズ if (rand((rand(floor(texUV.y * 500) + _Time.y) - 0.5) + _Time.y) < _RGBNoise) { col.r = rand(uv + float2(123 + _Time.y, 0)); col.g = rand(uv + float2(123 + _Time.y, 1)); col.b = rand(uv + float2(123 + _Time.y, 2)); } // ピクセルごとに描画するRGBを決める float floorX = fmod(inUV.x * _ScreenParams.x / 3, 1); col.r *= floorX > 0.3333; col.g *= floorX < 0.3333 || floorX > 0.6666; col.b *= floorX < 0.6666; // スキャンラインを描画 float scanLine = sin(_Time.y * 10 + uv.y * 500) / 2 + 0.5; col *= 0.5 + clamp(scanLine + 0.5, 0, 1) * 0.5; // 画面端を暗くする col *= 1 - vignet * 1.3; return float4(col, 1); } ENDCG } } } |
C#スクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
using UnityEngine; using System.Collections; [ExecuteInEditMode()] public class CRT : MonoBehaviour { [SerializeField] Material material; [SerializeField] [Range(0, 1)] float noiseX; public float NoiseX { get { return noiseX; } set { noiseX = value; } } [SerializeField] [Range(0, 1)] float rgbNoise; public float RGBNoise { get { return rgbNoise; } set { rgbNoise = value; } } [SerializeField] [Range(0, 1)] float sinNoiseScale; public float SinNoiseScale { get { return sinNoiseScale; } set { sinNoiseScale = value; } } [SerializeField] [Range(0, 10)] float sinNoiseWidth; public float SinNoiseWidth { get { return sinNoiseWidth; } set { sinNoiseWidth = value; } } [SerializeField] float sinNoiseOffset; public float SinNoiseOffset { get { return sinNoiseOffset; } set { sinNoiseOffset = value; } } [SerializeField] Vector2 offset; public Vector2 Offset { get { return offset; } set { offset = value; } } void OnRenderImage(RenderTexture src, RenderTexture dest) { material.SetFloat("_NoiseX", noiseX); material.SetFloat("_RGBNoise", rgbNoise); material.SetFloat("_SinNoiseScale", sinNoiseScale); material.SetFloat("_SinNoiseWidth", sinNoiseWidth); material.SetFloat("_SinNoiseOffset", sinNoiseOffset); material.SetVector("_Offset", offset); Graphics.Blit(src, dest, material); } } |
おまけ
動画撮影に利用したランダマイザーです。
UniRX下で動き、ランダムで画面にノイズを掛けてくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
using UnityEngine; using System.Collections; using UniRx; using UniRx.Triggers; public class CRTRandomizer : MonoBehaviour { [SerializeField] CRT crt; void Start() { var resetTimer = Observable.FromCoroutine<float>(RandomInterval); var totalTime = 0.0f; var time = 0.0f; var noisePower = 0.0f; var baseNoisePower = 0.0f; var noisyTime = 0.0f; var offset = Vector2.zero; var baseOffset = Vector2.zero; resetTimer.Subscribe(t => { totalTime = t; time = 0; noisePower = Random.Range(0.0f, 1.0f); baseNoisePower = Mathf.Clamp01(Random.Range(-0.01f, 0.01f)); noisyTime = Random.Range(0.0f, 0.5f); crt.SinNoiseWidth = Random.Range(0.0f, 30.0f); offset = Vector2.right * Random.Range(-5.0f, 5) + Vector2.up * Random.Range(-5.0f, 5); baseOffset = (Vector2.right * Random.Range(-1.0f, 1) + Vector2.up * Random.Range(-1.0f, 1)) * 0.05f; }); this.UpdateAsObservable().Subscribe(_ => { float t = time / totalTime; float nt = Mathf.Clamp01(t / noisyTime); float np = baseNoisePower + noisePower * (1 - nt); crt.NoiseX = np * 0.5f; crt.RGBNoise = np * 0.7f; crt.SinNoiseScale = np * 1.0f; crt.SinNoiseOffset += Time.deltaTime * 2; crt.Offset = baseOffset + offset * (np + baseNoisePower * t * 4); time += Time.deltaTime; }); } IEnumerator RandomInterval(IObserver<float> observer) { while (true) { float wait = Random.Range(0.2f, 2); observer.OnNext(wait); yield return new WaitForSeconds(wait); } } } |
コメントを残す