VRChatで写真展！こだわりのシェーダー表現 雨っぽい編

この記事は[ふたり？VRChat Advent Calendar 2024](https://qiita.com/advent-calendar/2024/vrchat-osakanabiyori2024)の2日の記事です。
>何気にQiitaで記事書くの初めてです。
Osakanabiyoriさんにお誘いいただきました！
このような機会をくださって感謝します。
## はじめに
はじめまして！
ゲームエンジニアの[雨空(amasora)](https://x.com/rainysky_vrc)です！
本業はゲームエンジニアをしていますが、趣味で写真撮影を楽しんでいます。
写真を通して、日常の中にある美しい瞬間を切り取ることが大好きです！

VRCは2024年9月21日から本格的に始めた新米VRChatterです。
仮想空間に足を踏み入れ、その可能性に心を奪われる日々が続いています

そんな私が掲げた目標は、「VRChatで写真展を開くこと」です！
VRの中だからこそ実現できる独自の表現や空間作りに挑戦し、見る人が心に残る体験をできる展示を作りたいと考えています。この記事では、その第一歩として、写真空間がより引き立つシェーダー表現についてお話していきたいと思います！

これからVRChatでの活動を通じて、写真が好きな人や、技術的な興味を持つ方と交流し、成長していきたいです。どうぞよろしくお願いします！

なお、本記事は革新的なShaderをお求めな方や
Shader上級者向けに解説したような記事ではございません。

## まずは結果映像から
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZyhzjtDDXTc?si=KZ4Ujjp4-3dSHldO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950340/202ad070-a14f-0302-b478-6f4cb26f2459.png)

どうでしょうか？より写真の中がちゃんと雨なんだ。と感じませんか？
これから載せる映像はYouTubeまで飛んで全画面表示にしないとわかりづらいかも；；

## 制作フロー

今回はこの写真をちょっとよく見せるためのシェーダーを書いていきたいと思います。

![VRChat_2024-09-21_23-00-01.109_1920x1080.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950340/a5e15b80-3503-de9b-6c4e-3363e852b038.jpeg)


試作1:
<iframe width="560" height="315" src="https://www.youtube.com/embed/FF2MBsKDF9c?si=ktL1jaY6ZJx6u5ai" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950340/670abece-fd5d-8138-d09c-b64f5f5c8925.png)



まずは、雨コンセプトまっすぐにとりあえず写真に水滴を当ててみました。

どうでしょうか。

うーん。。。
私が目指す表現とはちょっと違う結果になりました。

被写体に水滴が当たってるとなんか汗っぽい感じだし、
写真の上に水滴がのってるだけでは。。。？

### そもそもこのShaderなんで作ろうと思ったんだっけ？

- VRの写真展だからただ単に写真をおくだけじゃつまらないと思ったから。
- 雨コンセプトをより強く感じてほしかった。
- 写真空間のアップデートをしたい。

etc..

・・・　

**写真空間のアップデートをしたい**　これやん！
これができていない！

そう。試作1だと写真空間を超えたエフェクトになっているのです。

私が作りたいものは写真空間を超えずに写真の中がより引き立つようにしたい！

と、いうことで

試作2:

<iframe width="560" height="315" src="https://www.youtube.com/embed/cvyzaZp5_So?si=92DZeLLZlK3_htuk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

お～悪くはない？
だた、もう少し味がほしい。。。雨っぽい・・・雨っぽい・・・



雨で拡散した光と物に雨が当たってる感があるともっとよくなりそう？

ん～どうやって作ろう。。。

試作3:

<iframe width="560" height="315" src="https://www.youtube.com/embed/B768leu2g1s?si=icfbEMgG9JFLCPYo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950340/7ad3d05e-1465-e3b3-51ab-0af0cd404af6.png)


お？悪くないのでは？

雨っぽいし、写真空間がUpdateされた感じがする。

ただ、あくまで写真展なので、写真そのものも見てほしいので近寄ってくれた人に対しては
エフェクトがフェードアウトするようにしました。
(シェーダーの粗が目立つのもありますが。。。）

個人的にとりあえず満足

と思いましたが、この記事を書いてる途中でキャラクタにエフェクトがかかってるのはおかしくない？って思ったので、修正しました！

マスクテクスチャを作ってエフェクトがかかる場所とかからない場所を分けました！

<iframe width="560" height="315" src="https://www.youtube.com/embed/ZyhzjtDDXTc?si=1pawVahPu8NXXa2I" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3950340/c635303b-5276-31a2-e13b-de017b7c9fe5.png)
    
被写体（キャラクタ）部分にしかマスクをかけていませんが、ほんとはもっと暗い部分とか
雨に当たってる感が欲しくないところにかけるとより良くなりそうです。


さてここからはどうやって表現したか。です。
## 制作したシェーダーの説明

結構単純です。

### 概要
・雨模様のテクスチャをUVスクロール
・ランダムに発生するグリッジエフェクト + マスク領域に適応させるシステム


### UVスクロール
```
float2 rainUV = i.uv;
rainUV.y += time * _RainSpeed; // 雨のスクロール
rainUV = RotateUV(rainUV, _RainRotation); // 雨模様の回転
fixed4 rainEffect = tex2D(_RainTex, rainUV) * _RainIntensity; // 雨の強度を適用
```
回転と強度（速度）を設定することによって横殴りの雨とか、
とんでもない豪雨とか再現できるようにしてます

### グリッジエフェクト
```
float randomSeed = frac(sin(dot(i.uv.xy, float2(12.9898, 78.233))) * 43758.5453);
float glitchActive = step(0.8, frac(time * _GlitchFrequency + randomSeed));
float2 blockUV = floor(i.uv / _BlockSize) * _BlockSize;
float2 noiseUV = blockUV + float2(time, -time) * 0.1;
fixed4 noise = tex2D(_NoiseTex, noiseUV);
float2 blockOffset = float2((noise.r - 0.5) * 2.0, 0) * _GlitchStrength * glitchActive;
fixed4 glitchEffect = tex2D(_MainTex, i.uv + blockOffset);
```
ランダムにグリッジさせるのと、ブロック単位で水平方向に動かすことによって
雨による揺らぎのある光の拡散（っぽいやつ）を表現しています

### マスク機能
```
fixed4 maskValue = tex2D(_MaskTex, i.uv);
float isGlitchAllowed = maskValue.r;
if (isGlitchAllowed > 0.5 && glitchActive > 0.5)
{
    finalColor = glitchEffect;
}
```
マスク画像には白黒画像を使っています（黒がグリッジ無効範囲）
シェーダーにはifを使うな！ってよく学生の時に注意されていましたがifつかっちゃいました。

このシェーダはComputeShaderでもないですし、そんな処理速度ガタ落ちすることもないだろう。と思っているので特に気にせず使っています。

気になる人はこの記事とか読んでみるといいかも？
[「Shaderでif文を使ったら遅い」は正しくない🧐](https://qiita.com/up-hash/items/e932ccae110d687e225f)

あとは雨エフェクトをベースカラーに加算合成すれば終わりですね！
簡単！単純！テクニカルなことしてない！
でも、劇的な印象を与えられるのがShaderの魅力的なところですよね！

Shaderよわよわなので、やっぱ勉強しないとな。。。

# 最後に
ここまで読んでいただき、本当にありがとうございました！
今回、Qiita記事デビューを果たすことができ、とても嬉しく思っています。

間違った説明をしてたり、
もっといい表現方法を知ってるよ！思いついたよ！という人はご連絡ください！

記事を書く過程で、自分が何をしたいのか整理できたり、システムの粗に気づいたりと、多くの学びがありました。「何を表現したかったのか」を再認識する良い機会でした！
このような振り返りの場を持てたのも、記事を書くおかげですね。

また技術的な記事を書きたいな。。。そのためには、まずは自分で新しいものを作り出していかないといけないですね。。。

もしVRChatでお会いすることがあれば、ぜひ仲良くしてください！
そして、まだいつになるかは分かりませんが、写真展を開催する際には、足を運んでいただけると嬉しいです。

最後までお読みいただき、ありがとうございました！

おわーり！