そろそろShaderをやるパート13 マウスのRayの座標をC#からShaderで受け取る

4 min読了の目安(約3700字TECH技術記事

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

デモ

球体の上でマウス入力を行うとポインター(赤い丸)が出現します。

やっていることとしては
C#のスクリプトで、マウスから出たRayとオブジェクトの衝突箇所の座標
Shaderに渡してShader側でポインターを描画してます。

C#スクリプト

using UnityEngine;

/// <summary>
/// マウスから出たRayとオブジェクトの衝突座標をShaderに渡す
/// 適当なオブジェクトにアタッチ
/// </summary>
public class MouseRayHitPointSendToShader : MonoBehaviour
{
    /// <summary>
    /// ポインターを出したいオブジェクトのレンダラー
    /// 前提:Shaderは座標受け取りに対応したものを適用
    /// </summary>
    [SerializeField] private Renderer _renderer;

    /// <summary>
    /// Shader側で定義済みの座標を受け取る変数
    /// </summary>
    private string propName = "_MousePosition";
    
    private Material mat;

    void Start ()
    {
        mat = _renderer.material;
    }
    
    void Update () 
    {
        //マウスの入力
        if (Input.GetMouseButton (0)) 
        {
            //Ray出す
            Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
            RaycastHit hit_info = new RaycastHit ();
            float max_distance = 100f;

            bool is_hit = Physics.Raycast (ray, out hit_info, max_distance); 

            //Rayとオブジェクトが衝突したときの処理を書く
            if (is_hit) 
            {
                //衝突
                Debug.Log(hit_info.point);
                //Shaderに座標を渡す
                mat.SetVector(propName, hit_info.point);
            }   
        }
    }
}

マテリアルで定義されているSetVector関数を利用して
Shader側で定義済みの座標を受け取る変数に対してデータを渡しています。

Shaderサンプル

Shader "Custom/MouseClickReceive"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldPos : WORLD_POS;
            };

            //C#側でいじる変数
            float4 _MousePosition;

            v2f vert (appdata_base v)
            {
                v2f o;
                //3D空間座標→スクリーン座標変換
                o.vertex = UnityObjectToClipPos(v.vertex);
                //描画したいピクセルのワールド座標を計算
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //ベースカラー 白
                float4 baseColor = (1,1,1,1);
                
                /*"マウスから出たRayとオブジェクトの衝突箇所(ワールド座標)"と
                  "描画しようとしているピクセルのワールド座標"の距離を求める*/
                float dist = distance( _MousePosition, i.worldPos);
                
                //求めた距離が任意の距離以下なら描画しようとしているピクセルの色を変える
                if( dist < 0.1)
                {
                    //赤色乗算代入
                    baseColor *= float4(1,0,0,0);
                }
                
                return baseColor;
            }
            ENDCG
        }
    }
}

distance

distanceメソッドは2点間の距離を求めるメソッドだそうです。

このメソッドを使って、
C#側で得た座標Shader内で計算したピクセルの座標の距離を計算しています。

そして、if文を使って任意の距離以下なら○○といった具合で処理を分岐させています。

※Shaderでif文は場合によってはあまりよくない?と聞いたことがあります、、、
 まだ調べきれてないのでわかり次第追記、更新します。

参考リンク

Raycastによるマウス座標との当たり判定
【Unityシェーダ入門】円やリングをかっこよく動かす方法
(Cg)C#のプログラムからマウスの位置をシェーダプログラムに送る方法