Unity #2 Advent Calender 2018 23日目を書かせていただきます。
前回は、choroskeさんのアニメーションしてる3Dモデルをラグドール化してふっ飛ばしてみる でした。
#前書き
この世には、あらゆる部位を揺らしたりするコンテンツは世の中あります。
しかしながら、ぷにぷにした感じでの表現ってそんなにないじゃないですか?
それは、ほっぺったをぷにぷにするような優しい関係
ないのだったら作ればよいじゃない。
#どんな用途として使うのを目指していたのか?
例えば、以下のような場合
・コンテンツの中で恋愛ゲーム的場面でほっぺたをつつきあうようなニッチの層への表現。
・VR上でのいちゃつくための見た目表現の拡張などに
#この記事を読む前に
このシェーダーは、まだ未完成な部分や、間違ったアプローチを含まれている可能性があります。
あくまでも、アプローチしてるんだなぁくらいの認識でご覧下さい。
アドバイスなどは大歓迎です。
#結論
結論からいうと、完成せずに終わりました。最終結果的には以下の通り
####アプローチ
まず最初に押さえたというものをどう表現するのか?といった感じを考えたところ
1.押さえた場所を中心に円形に沈む表現が必要
2.そのくぼみを表現するための、影表現が必要なのでノーマルを中心に向かって角度を変える必要がある。
やっていくうちに
3.抑える場所によっては、そのへこみ具合や、流動具合を変更する(一部試行錯誤)
##作成手順
結果だけを知りたい場合はシェーダーコードのところまでスキップ推奨です。
### 1.UVの位置を押さえたところっていう体で、頂点を動かす
シェーダー内のプロパティ内で、UV値を指定して、の場所付近の頂点を下げるっていう処理を行うところを外しました。(単純に工数を省けるとこははぶきたかったため)
以下がその実行例。赤にしているのは
影響範囲と、それ以外を明確に示すため、サーフェスシェーダー内で影響範囲をみるためでもあります。
この時点でやったことは2つ
1つ目は、単純に
押さえた場所(UV直接指定)の場所を下げる。(ただし2つ目と合わせてようやくちゃんと動いた風になる)
2つ目は、抑えた場所からどの範囲までをへこむ範囲として、それは中心点から遠のくほど、減衰する
これらはコード上の80行目から、103行目に当たります。
###2.へこんだところに影を作る
これに関しては、頂点部分の法線を、へこむ予定の部分の方角に向けて、法線を傾けるように処理を行い、それをピクセルシェーダー側でその法線と、光の向きとで、影を表現する
イメージ的に以下の通り
ノーマルの傾きのイメージの絵
また、その影響具合は、
中心がもっとも影響を受けず、
影響範囲内での最も外側が一番影響を受けるように処理をする必要があります。
理由としては、図1-1の通り、この段階での目指すべきは
押したところを中心におわん型を形成し、それに沿った影を作る必要があります。
そのためにその影響力に合わせて影を作る必要があるため、それだけ法線を曲げる必要があるため、その調整をしました。
それらの部分を作った感じが以下のようなもの
へこみに影をつけたもの
###3.押さえたところのへこみの影響具合での表現。
####その1 押さえたところを中心に、一定距離を超えたところで盛り上がりが存在する
例えば抑えた際に、必ずしも全てがへこむわけじゃないです。
その淵際は少しばかりに盛り上がる必要があります。
それを少し再現したものが以下
####その2押さえたところの下地の影響を受けるのを考える。
例えば、抑える場所に骨が存在する場合と、脂肪が存在する場合では、押さえた時に、へこみ具合が全然違うわけです。それをシェーダー上で値として渡さなければいけません。その場所が、固いのかやわらかいのか、それを判断しなければいけません。そのため、それを今回は、テクスチャという形で表現を行います。
##UnityChanの頬のモデルを使って、ぷにぷにを実現してみる。
完成度低い・・・・
#今後の問題点として
1.押さえた方向に頂点を移動させているだけなので、事実上押さえる位置等が極端な曲線をしていた場合は、その頂点が表面にでてしまう場合が存在する。
2.影表現を法線情報を計算によって変化させているため、このシェーダーとトゥーンシェーダみたいな感じのシェーダーで組み合わせるのには向いてないのかもしれない。(UnityChanシェーダーの本体がわからなかったので未検証)
3.当たり前だが、頂点の分割数によって左右されるので、必然的に抑えて変化が起きる部分は、頂点分割する手段が必要(この場合はテッセレーションが一つの解答かもしれない。)
#実際のコード部分
///このしぇーだーは、タッチしている領域に対して、タッチしている地点を中心に、
///くぼみみたいに表現を行うためのものです。
Shader "Unlit/ElasticSkinUnity"
{
Properties
{
//肌テクスチャ
_SkinTex("Texture", 2D) = "white" {}
//ノーマルマップ
_SkinNormalMap("Normal map",2D) = "white"{}
//圧力
[PowerSlider(0,1)]_PressPower("PressPower",Range(0.0,1)) = 0
//押している位置 (最後の方になったら正式に使用する。)
_PressMeshPos("TouchScreenPos",Vector) = (0.5,0.5,0,0)
//押した力の影響具合を受ける距離
_PressInFrenceDistance("PressInfrenceDistance",Range(0.0,0.5)) = 1
//光の方角
_LightDir("LightDir",Vector) = (0,0,1,0)
//反発力係数
_TexReflectionPow("ReflectPow",Range(0.0,1.0)) = 0.5
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION; //メッシュの頂点情報
float3 normal : NORMAL; //メッシュのノーマル情報
float2 uv : TEXCOORD0; //テクスチャのUVに当たる情報
};
struct v2f
{
float2 uv : TEXCOORD0; //テクスチャ座標
float4 vertex : SV_POSITION; //入力された、値の位置
float3 normal:NORMAL; //法線の方向 圧力を中心にベクトルを変形させる 一時的に、float2で表現
};
///肌に当たるテクスチャ
sampler2D _SkinTex;
// 肌のノーマルマップ
sampler2D _SkinNormalMap;
//圧力
float _PressPower;
//メッシュでの抑えた位置
float4 _PressMeshPos;
//なんかよくわからんけど必要
float4 _MainTex_ST;
//圧力の影響を与える距離
float _PressInFrenceDistance;
//テクスチャの力の反射係数
float _TexReflectionPow;
//光の方角
float4 _LightDir;
v2f vert(appdata v)
{
v2f o;
o.uv = v.uv;//TRANSFORM_TEX(v.uv, _MainTex);
float2 _pres_pos_uv = float2(_PressMeshPos.x, _PressMeshPos.y);
float _uv_distance = distance(_pres_pos_uv, v.uv);
//近いほど 受ける値を大きくする。ためのもの
float _inv_distance = max(0, min(1, _PressInFrenceDistance - _uv_distance));
//一定の距離内なら 1 に値を入れる 範囲内かの判定のためのもの
float _cheak_distance = step(_uv_distance, _PressInFrenceDistance);
//0-1の値で出力する
float _regulation_power = max(0, min(1, _PressPower));
//===================ここから頂点の移動を行う
//ただし現状だと、影響距離を伸ばした際に一定の範囲を超えた際にーになって変になる。
float _move_power = (_PressPower * _cheak_distance * max(0.00f, _inv_distance));
v.vertex.xyz = v.vertex.xyz - (v.normal * _move_power);
v.vertex.xyz = float3(v.vertex.x, v.vertex.y, v.vertex.z);
//テクスチャに対しての反射係数を取得する
float4 _ref_tex = tex2Dlod(_SkinNormalMap, float4(v.uv.x, v.uv.y, 0, 0));
_ref_tex = _ref_tex * _cheak_distance;
//テクスチャに保存されている反射係数を返す
// Y座標に対して 反射係数 * その係数の適応度 * 距離(離れているほど影響度大) * 押す力の強さ(0-1) *
float _ref_power = float(_ref_tex.w) * _TexReflectionPow * _uv_distance * _regulation_power * _regulation_power;
v.vertex.xyz = v.vertex.xyz + _ref_power * v.normal;
o.vertex = UnityObjectToClipPos(v.vertex);
//=====ここまでが頂点を動かすための機構
//ノーマル計算を行う UVの位置から方向を求める FIX:UVと位置情報とは違うため、注意が必要
float2 _press_dir_2 = float2(_pres_pos_uv.x - o.uv.x, _pres_pos_uv.y - o.uv.y);
float3 _press_dir = float3(_press_dir_2.x, 0, _press_dir_2.y);
//距離に応じて、処理するようにする必要がある。
float _raised_floor_dist = (_inv_distance + 0.01) * step(0.01f, _PressInFrenceDistance);
//
float3 _add_dir = v.normal + normalize(_press_dir) *(_raised_floor_dist * _PressPower);
//距離推移のベクトルを正規化したもの
o.normal = normalize(_add_dir);
return o;
}
//サーフェスシェーダー
fixed4 frag(v2f _i) : SV_Target
{
//
fixed4 _col = tex2D(_SkinTex, _i.uv);
//変更した法線に基づいた計算
float _dot = dot(_LightDir, _i.normal);
return fixed4(_dot, _dot, _dot, _dot);
}
ENDCG
}
}
}