再帰反射風のUnityのMaterial
反射材つくりたいじゃん。
解説:MultiLightControllerスクリプトとMultiLightRetroreflectiveWithFalloffシェーダー
1. 概要
「MultiLightController」スクリプトと「MultiLightRetroreflectiveWithFalloff」シェーダーは、
近くにあるライトを最大4つまで検出し、それらのライトの位置と強度に基づいてオブジェクトの反射効果を調整する仕組みです。再帰反射素材(Retroreflective Material)は、光源の方向に光が帰ってくるので、特に暗い場所での道路標識や、反射ベストの反射材などに使えると思います。
2. MultiLightControllerスクリプトの解説
このスクリプトの目的は、周囲のライトを検出してシェーダーに情報を渡すことです。以下、各部分の動作について解説します。
detectionRadius:この半径内にあるライトのみを検出します。デフォルトは100です。適宜変更してください。
Update()メソッド:
ライトの検出:シーン内の全てのライトを調べ、detectionRadius内にある有効なライトを検出します。
ライトの並び替え:検出したライトを距離でソートし、近いものから最大4つを選びます。
シェーダーへの情報送信:選択したライトの位置と強度(intensity)をシェーダーに渡します。変数_LightPos0〜_LightPos3に情報をセットし、それ以外は無効にします。
3. MultiLightRetroreflectiveWithFalloffシェーダーの解説
このシェーダーは、近くのライトの位置と強度に基づいて反射の強度を計算し、オブジェクトの反射効果に反映させます。
Propertiesセクション:
_Color:オブジェクトの基本色。
_Reflectivity:反射の強さ。
_Falloff:ライトの距離に基づく減衰の調整。
構造体と変数の定義:
appdata と v2fは頂点およびフラグメントシェーダー間でデータをやり取りする構造体です。
_LightPos0〜_LightPos3は最大4つのライトの位置と強度のデータを格納します。
頂点シェーダー:
ワールド座標における頂点の位置と法線を計算し、フラグメントシェーダーに渡します。
フラグメントシェーダー:
それぞれのライトについて、以下を行います:
ライトの位置と距離を計算し、方向ベクトルを取得。
法線とライト方向の内積NdotLを計算し、ライトの方向に向いた部分のみ反射強度を計算。
距離に基づく減衰を適用し、反射強度に加算。
最後に、各ライトからの反射強度を合計し、最終的なピクセル色として_Colorに掛け合わせて返します。
4. 使用方法
スクリプトの設定:MultiLightControllerをオブジェクトにアタッチし、適切なretroreflectiveMaterialを設定します。
シェーダーの適用:MultiLightRetroreflectiveWithFalloffシェーダーを持つマテリアルを作成し、オブジェクトに適用します。
調整:detectionRadius、_Reflectivity、および_Falloffを調整して、反射効果を理想的なものに調整します。
この仕組みにより、ライトが近づくと反射効果が強くなり、遠ざかると減衰していく自然な反射表現が可能になります。
MultiLightController.cs
using UnityEngine;
using System.Collections.Generic;
[ExecuteInEditMode]
public class MultiLightController : MonoBehaviour
{
public Material retroreflectiveMaterial;
public float detectionRadius = 100f; // ライト検出の半径
public LayerMask obstacleLayerMask; // 遮蔽物として検知するレイヤーマスク
void Update()
{
if (retroreflectiveMaterial != null)
{
// 周辺の有効なライトを取得
Light[] allLights = FindObjectsOfType<Light>();
List<Light> validLights = new List<Light>();
foreach (Light light in allLights)
{
if (!light.enabled) continue; // 無効なライトはスキップ
float distance = Vector3.Distance(transform.position, light.transform.position);
// ライトの種類と距離でフィルタリング
if (distance > detectionRadius) continue; // 検出半径外ならスキップ
// エリアライトの場合は範囲内かどうか確認
if (light.type == LightType.Area && distance > light.range) continue;
// 遮蔽物がないかチェック
if (!Physics.Raycast(transform.position, (light.transform.position - transform.position).normalized,
distance, obstacleLayerMask))
{
validLights.Add(light); // 遮蔽物がなければ有効ライトに追加
}
}
// 距離順にソートし、最も近いライトを4つまでシェーダーに送信
validLights.Sort((a, b) => Vector3.Distance(transform.position, a.transform.position)
.CompareTo(Vector3.Distance(transform.position, b.transform.position)));
int activeLightCount = 0;
for (int i = 0; i < validLights.Count && activeLightCount < 4; i++)
{
Light light = validLights[i];
Vector4 lightData = new Vector4(
light.transform.position.x,
light.transform.position.y,
light.transform.position.z,
light.intensity
);
retroreflectiveMaterial.SetVector("_LightPos" + activeLightCount, lightData);
activeLightCount++;
}
// 残りのライトスロットを無効化
for (int j = activeLightCount; j < 4; j++)
{
retroreflectiveMaterial.SetVector("_LightPos" + j, Vector4.zero);
}
}
}
}
MultiLightRetroreflectiveWithFalloff
Shader "Custom/MultiLightRetroreflectiveWithFalloff"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Reflectivity ("Reflectivity", Range(0,20)) = 2
_Falloff ("Falloff", Range(0,2)) = 0.001 // 減衰の強さを調整
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
float4 _Color;
float _Reflectivity;
float _Falloff;
float4 _LightPos0, _LightPos1, _LightPos2, _LightPos3;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // ワールド空間の頂点位置を取得
return o;
}
half4 frag (v2f i) : SV_Target
{
float totalReflectFactor = 0.0;
// 各ライトに対して反射の強度を計算
for (int j = 0; j < 4; j++)
{
float4 lightData = j == 0 ? _LightPos0 : j == 1 ? _LightPos1 : j == 2 ? _LightPos2 : _LightPos3;
if (lightData.w > 0.0) // 有効なライトかどうかを確認
{
float3 lightDir = lightData.xyz - i.worldPos;
float distance = length(lightDir); // 距離を計算
lightDir = normalize(lightDir);
float NdotL = max(dot(i.worldNormal, lightDir), 0.0);
// 距離による減衰を適用して反射強度を計算
float attenuation = 1.0 / (1.0 + _Falloff * distance * distance);
totalReflectFactor += pow(NdotL, 8.0) * _Reflectivity * lightData.w * attenuation;
}
}
// 複数のライトに対する反射の合計
return _Color * saturate(totalReflectFactor + 0.05); // 基本の明るさを少し追加
}
ENDCG
}
}
FallBack "Diffuse"
}