強火で進め

このブログではプログラム関連の記事を中心に書いてます。

(Cg)C#のプログラムからマウスの位置をシェーダプログラムに送る方法

現在、シェーダの勉強の一環として他の環境向けのCgやGLSLプログラムをUnityへ移植する事をしています。

そこで見つけたサンプルにマウスの位置情報をシェーダの中で利用したものが有りました。UnityのShaderLabではマウスの情報って使えるのかなぁ?と思い色々と調べてみたのですが見つからず「こりゃ、Unityだとマウスの位置情報はシェーダの中で使えないのかな?」とあきらめて居たのですが念のために「Unityユーザー助け合い所」に質問してみました。

すると流石、つわもの揃いのUnityユーザー助け合い所、即サンプル付きで回答が付きました

サンプルを読むとMaterialPropertyBlockというクラスを使うとC#/JS/BooなどCPU向けのプログラムからシェーダにデータを渡せるみたいです。

Unity Script Reference – MaterialPropertyBlock
http://unity3d.com/support/documentation/ScriptReference/MaterialPropertyBlock.html

サンプル

回答のサンプルを元にC#からシェーダへマウス情報を渡すプログラムを書いてみました。
X軸向き沿いにプラス方向とマイナス方向へ全頂点を反復的に行き来するシェーダを作成。その移動量のスケールをマウスのX座標を元に増減するプログラムを作成してみました。マウスカーソルをゲーム画面の右側に移動させるほど移動量が多くなります。

C#
MouseCShape.cs

using UnityEngine;
using System.Collections;

public class MouseCShape : MonoBehaviour {
	
	private MaterialPropertyBlock p;
	
	// Use this for initialization
	void Start () {
		p  = new MaterialPropertyBlock();
	}
	
	// Update is called once per frame
	void Update () {
		p.Clear();
		p.AddVector("_MousePosition", Input.mousePosition);
		renderer.SetPropertyBlock(p);
	}
}

【ShaderLab】
test.shader

Shader "test" {
  Properties {
    _MainTex ("Texture", 2D) = "white" {}
  }
  SubShader {
    Tags { "RenderType" = "Opaque" }
    CGPROGRAM
    #pragma surface surf Lambert vertex:vert
    struct Input {
        float2 uv_MainTex;
    };
    float4 _MousePosition;
    void vert (inout appdata_full v) {
        v.vertex.x += _SinTime.w * _MousePosition.x * 0.02;
    }
    sampler2D _MainTex;
    void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
    }
    ENDCG
  }
}

ShaderLabで最初、Vector _MousePosition; と書いていたのですがこれだとエラーが発生しました。
代わりに float4 _MousePosition; と記述するとコンパイルが通りました。うーむ、シェーダ難しい。

他にもMaterialのSetVectorを使う方法もあるとの事です。

Unity Script Reference – Material.SetVector
http://unity3d.com/support/documentation/ScriptReference/Material.SetVector.html

そちらを使って記述したC#のプログラムはこちら。

using UnityEngine;
using System.Collections;

public class MouseCShape : MonoBehaviour {
	
	private Material mat;
	
	// Use this for initialization
	void Start () {
		mat = renderer.material;
	}
	
	// Update is called once per frame
	void Update () {
		Vector3 mpos = Input.mousePosition;
		mat.SetVector("_MousePosition", mpos);
	}
}