🐶
Planeにテキスチャを投影する
URP、HDRPにはDecalという機能が存在します。
テキスチャを好きなモデルの位置に張り付けることができるというものです。
今回はこれをシェーダーで実現したいと思います。
前提
テキスチャを投影するということは、テキスチャが投影されるワールド座標の位置を再計算しなくてはいけない&再計算の後、テキスチャを張り付けるためのUVをつくらなくてはいけません。
大まかな方針
1 深度を取得
2 深度情報をもとにワールド座標を作り直す。
3 ワールド座標系をオブジェクト座標系に変換。
4 オブジェクト座標系をUV座標として扱いたいので0~1にリマップする。
5 取得したUV座標をつかってテキスチャマッピングをする。
1 深度を取得する。
頂点シェーダー内でCompouteScreenPosを使用してスクリーン座標を0~wで取得してフラグメントシェーダー内でwで除算して0~1にしてUVとして扱えるようにします。それを深度テキスチャのサンプリングに使用します。
// vertex Shader
// スクリーンポジションを計算
o.screenPos = ComputeScreenPos (o.position);
// fragment shader
float2 screenUv = i.screenPos.xy / i.screenPos.w;
// 深度をサンプリング
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenPos);
// 深度をリニア化した後、0~Farの値で深度を表す。
depth = Linear01Depth(depth) * _ProjectionParams.z;
2 ワールド座標を作り直す
レイベクトルと深度を使用してワールド座標を計算しなおします。この際に、-UNITY_MATRIX_V[2].xyzをカメラの前方ベクトルとして扱います。
これが可能な理由は下記の記事をご参照ください。
内積の逆数を利用してレイベクトルの大きさを伸び縮みさせているのは、カメラの姿勢と平行に深度テキスチャがつくられるので微妙にギャップが生まれるためです。
// レイベクトルを正規化
worldRay = normalize(worldRay);
// -UNITY_MATRIX_V[2].xyz => カメラの前方ベクトル
// レイベクトルとカメラの前方ベクトルの内積の逆数をとる=>今見ている座標とカメラの前方ベクトルの差が大きいほどレイベクトルを伸ばして、近いほど伸ばさない。
float inverseDist = 1.0f / dot(worldRay, -UNITY_MATRIX_V[2].xyz);
worldRay = worldRay * inverseDist;
float3 worldPos = _WorldSpaceCameraPos + worldRay * depth;
3 ワールド座標系からオブジェクト座標系に変換
Unity側で用意されている変換用行列を使用してオブジェクト座標系に変換します。
float3 objectPos = mul(unity_WorldToObject, float4(worldPos, 1)).xyz;
4 オブジェクト座標系をUV座標として扱いたいので0~1にリマップする。
最終的にUVとして扱いたいので0~1に変換します
objectPos += 0.5;
5 取得したUV座標をつかってテキスチャマッピングをする。
作成した投影用のUV座標を使用してテキスチャマッピングをすることで完成です。
// fragment shader
fixed4 col = tex2D(_MainTex, projectionUV);
完成画像
シェーダー全体
Shader "Unlit/DecalShader"
{
Properties
{
[HDR] _Color ("Color", Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags{ "RenderType"="Transparent" "Queue"="Transparent" "DisableBatching"="True"}
Blend SrcAlpha OneMinusSrcAlpha
ZWrite off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 screenPos : TEXCOORD0;
float3 ray : TEXCOORD1;
};
sampler2D _MainTex;
fixed4 _Color;
sampler2D _CameraDepthTexture;
float3 getProjectionObjectPos(float2 screenPos, float3 worldRay)
{
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenPos);
depth = Linear01Depth(depth) * _ProjectionParams.z;
worldRay = normalize(worldRay);
float inverseDist = 1.0f / dot(worldRay, -UNITY_MATRIX_V[2].xyz);
worldRay = worldRay * inverseDist;
float3 worldPos = _WorldSpaceCameraPos + worldRay * depth;
float3 objectPos = mul(unity_WorldToObject, float4(worldPos, 1)).xyz;
clip(0.5 - abs(objectPos));
objectPos += 0.5;
return objectPos;
}
v2f vert (appdata v)
{
v2f o;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
o.vertex = UnityWorldToClipPos(worldPos);
o.ray = worldPos - _WorldSpaceCameraPos;
o.screenPos = ComputeScreenPos (o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 screenUv = i.screenPos.xy / i.screenPos.w;
float2 projectionUV = getProjectionObjectPos(screenUv, i.ray).xz;
fixed4 col = tex2D(_MainTex, projectionUV);
col *= _Color;
return col;
}
ENDCG
}
}
}
Discussion