MME
https://gyazo.com/489fd0cbe55d5dff8ed5b433071af8fe
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
環境
「MikuMikuEffectで学ぶHLSL入門」連載一覧
MMD、MMEをダウンロードしてディスクに展開する。
https://gyazo.com/6ff68c69b7dfa7c3926be061e096083c
VC++ランタイムが必要。いまどきのWin10ならvcredist_x64.exeをインストールすると良い。
アクセサリでエフェクト(シェーダー)を使う
https://gyazo.com/83df97756780d4ba1b084bf56202e8d2
「モデル操作」のドロップダウンで「カメラ・照明・アクセサリ」が選ばれている状態にすると、アクセサリ操作が利用できる。
https://gyazo.com/a79b43c0bc4034dde65383a787af9d44
アクセサリ読込でxファイル読込
メニュー背景->背景黒化で背景を白から黒に変更
Ctrl+Shift+EでMMEオン/オフ切替
メッシュにエフェクトをアサインする
MMDウィンドウの右上MMEメニューからエフェクトファイル割り当て->オブジェクト右クリックでサブセット展開。
https://gyazo.com/149a741c86783188d742b449d1984d5b
https://gyazo.com/849d02dc981a72dbaa3c85157974241d
シェーダーのコンパイル
シェーダーはエフェクト適用時、pmmを開いた時点などでコンパイルされる。
シェーダーファイルが更新されるとMMEは自動的に読み込み直す。
シェーダーファイルの拡張子は.fx。テキストでもコンパイル済バイナリでもMMEで利用できる。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
HLSL
MME記事のサンプルをベースにするとシンプルなHLSLファイルが使える。
PS2ではなくPS3を使うと諸々の制約が減って楽。2.0では実装するif文の数がちょっと増えると動かなかったりする。
code:PS3.HLSL
pass Main {
ALPHABLENDENABLE = TRUE;
ZENABLE = FALSE;
ZWRITEENABLE = FALSE;
VertexShader = compile vs_3_0 VS_Main();
PixelShader = compile ps_3_0 PS_Main();
}
グローバル変数は利用できない。グローバルで宣言した変数は定数として解釈される。
static float PI = acos(-1.);
ループはデフォで[unroll(n)]される。if文も内部的には分岐の両方が実行される。[flatten]。
[loop]が使える。条件分岐には[branch]をつける。
フロー制御について
スクリーンサイズ
code:ScreenSize.HLSL
float2 ViewportSize : VIEWPORTPIXELSIZE;
static float2 ViewportOffset = (float2(0.5,0.5)/ViewportSize);
UVの取得
VS_OUTPUT Xxx_VS(float4 Pos : POSITION,float2 Tex : TEXCOORD0)
時間
code:MMETime.HLSL
float time : TIME;
float ftime : TIME <bool SyncInEditMode=true;>;
float elapsed_time : ELAPSEDTIME;
ポストエフェクトを使う場合の宣言。MMEサンプルエフェクト(ver6)参照。
バッファを使うよう宣言pass Gaussian_X < string Script= "Draw=Buffer;"; > {すればスクリーンスペースのPOSITIONとUVが使える。
code:PostEffect.HLSL
float Script : STANDARDSGLOBAL <
string ScriptOutput = "color";
string ScriptClass = "scene";
string ScriptOrder = "postprocess";
= 0.8;
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
VSCode環境
https://gyazo.com/84db3b4820144cbab497a233c85cab42
シェーダーのコンパイル
MMEはエフェクトファイルの読み込み時にコンパイルを行う。重い。
fxcで事前にコンパイルしておくとストレスが減る。batファイル用意すればいい。
グローバル変数は定数じゃないとコンパイル通してくれない。
code:MMEShaderCompile.sh
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\fxc.exe" D:\MMEShader\shdersource.fx /T fx_2_0 /Fo D:\MMEShader\shaderbin.fx
.fxファイルはテキストでもコンパイル済バイナリでもMMEで使える。
https://gyazo.com/b985928aa17d4b059461fc3ebc6d8852
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
MMEレイマーチング
ポストエフェクトに乗っかってスクリーンスペースのレイマーチングを描く。
ポスプロとして書かれたシェーダーは「エフェクトファイル割り当て」からは適用できない。アクセサリとして読み込む。
https://gyazo.com/029eef06d4a8df54096ed8bc360ee534
任意の.xファイルを用意して、シェーダーファイルと同名にすれば動作する。
https://gyazo.com/f4a8c766193ad719db5ab39603b430e2
レイマーチング用HLSLひな形
https://gyazo.com/8eff55cb5af585023d7d59266c1a1c1f
code:MMERaymarchBase.HLSL
float Script : STANDARDSGLOBAL <
string ScriptOutput = "color";
string ScriptClass = "scene";
string ScriptOrder = "postprocess";
= 0.8;
float time : TIME;
float2 ViewportSize : VIEWPORTPIXELSIZE;
static float2 ViewportOffset = (float2(0.5,0.5)/ViewportSize);
float4 ClearColor = {0,0,0,0};
float ClearDepth = 1.0;
texture2D ScnMap : RENDERCOLORTARGET <
float2 ViewPortRatio = {1.0,1.0};
int MipLevels = 1;
string Format = "A8R8G8B8" ;
;
sampler2D ScnSamp = sampler_state {
texture = <ScnMap>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = NONE;
AddressU = CLAMP;
AddressV = CLAMP;
};
texture2D DepthBuffer : RENDERDEPTHSTENCILTARGET <
float2 ViewPortRatio = {1.0,1.0};
string Format = "D24S8";
;
static float PI=acos(-1);
float ease_in_out_circ(float x) {
float t=x; float b=0.; float c=1.; float d=1.;
if ((t/=d/2.) < 1.)
return -c/2. * (sqrt(1. - t*t) - 1.) + b;
t-=2.;
return c/2. * (sqrt(abs(1.-t*t)) + 1.) + b;
}
float2 rot(float2 p,float a){return float2(p.x*cos(a)-p.y*sin(a),p.x*sin(a)+p.y*cos(a));}
float mod(float x, float y){return x-floor(x/y)*y;}
float3 hsv(float h, float s, float v){return ((clamp(abs(6.*frac(h+float3(.0,.666,.333))-3.)-1.,.0,1.)-1.)*s+1.)*v;}
float box(float3 p,float3 r){p=abs(p)-r;return max(max(p.x,p.y),p.z);}
float map(float3 p, float time){
float3 q=p-float3(.5,.0,.0);
float t=ease_in_out_circ(abs(sin(time)));
q.x+=2.5*sin(t);
q.y+=sin(t);
q.xy=rot(q.xy,time);
q.yz=rot(q.yz,time);
return dot(q,normalize(sign(q)))-.5;
}
float3 norm(float3 p,float tt){float2 e=float2(.001,.0);return normalize(.000001+map(p,tt).x-float3(map(p-e.xyy,tt).x,map(p-e.yxy,tt).x,map(p-e.yyx,tt).x));}
float3 sky(float3 rd){return clamp(float3(.1,.2,.5)-rd.y*.4,0.,1.);}
/// Struct
struct VS_OUTPUT {
float4 Pos : POSITION;
float2 TexCoord: TexCoord0;
};
///
/// Pass 1
///
VS_OUTPUT VS1(float4 Pos : POSITION, float4 Tex : TexCoord0)
{
VS_OUTPUT Out;
Out.Pos = Pos;
Out.TexCoord = Tex.xy;
return Out;
}
float4 PS1(VS_OUTPUT IN) : COLOR0
{
float2 p=2*IN.TexCoord + ViewportOffset-1;
p*=ViewportSize/min(ViewportSize.x,ViewportSize.y);
// color
float3 col=(.0).xxx;
// camera
float3 ro=float3(0.,0.,-3.);
float3 ta=float3(0.0,0.0,0.0);
/*
// move camera
ro.z+=sin(time);
ro.xz=rot(ro.xz,time);
ta.xy=rot(ta.xy,time);
*/
float3 fwd=normalize(ta-ro);
float3 up=float3(.0,1.,.0);
float3 side=normalize(cross(fwd,up));
up=normalize(cross(side,fwd));
float3 rd=normalize(p.x*side+p.y*up+2.*fwd);
rd=normalize(rd);
// sky
float3 fo=sky(rd);
// marching
float3 ray=ro,N=(.0).xxx;
float3 L=normalize(float3(.3,.5,-.5));
int j=0;
for(int i=0;i<99;++i){
float d=map(ray,time);
if(d<.001)
{
N=norm(ray,time);
// lambert
float dif=.5+.5*max(dot(N,L),.0);
col+=dif;
break;
}
if(d>90.)
break;
ray+=d*rd;
j=i;
}
col=lerp(fo, col, (float)j/99);
return float4(pow(abs(col),.4545), 1);
}
///
/// technique
///
technique RayMarching <
string Script =
"RenderColorTarget0=ScnMap;"
"RenderDepthStencilTarget=DepthBuffer;"
"ClearSetColor=ClearColor;"
"ClearSetDepth=ClearDepth;"
"Clear=Color;"
"Clear=Depth;"
"ScriptExternal=Color;"
"RenderColorTarget0=;"
"RenderDepthStencilTarget=;"
"Pass=Raymarch;"
;
{
pass Raymarch < string Script= "Draw=Buffer;"; > {
AlphaBlendEnable = FALSE;
VertexShader = compile vs_3_0 VS1();
PixelShader = compile ps_3_0 PS1();
}
}
https://gyazo.com/201d1e62976c9a343e92bf9d05668b61
code:RaymarchSample.HLSL
float Script : STANDARDSGLOBAL <
string ScriptOutput = "color";
string ScriptClass = "scene";
string ScriptOrder = "postprocess";
= 0.8;
float time : TIME;
float2 ViewportSize : VIEWPORTPIXELSIZE;
static float2 ViewportOffset = (float2(0.5,0.5)/ViewportSize);
float4 ClearColor = {0,0,0,0};
float ClearDepth = 1.0;
texture2D ScnMap : RENDERCOLORTARGET <
float2 ViewPortRatio = {1.0,1.0};
int MipLevels = 1;
string Format = "A8R8G8B8" ;
;
sampler2D ScnSamp = sampler_state {
texture = <ScnMap>;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = NONE;
AddressU = CLAMP;
AddressV = CLAMP;
};
texture2D DepthBuffer : RENDERDEPTHSTENCILTARGET <
float2 ViewPortRatio = {1.0,1.0};
string Format = "D24S8";
;
/// const
static float PI=acos(-1);
static float EPS = .0001;
/// util
float2 rot(float2 p,float a){return float2(p.x*cos(a)-p.y*sin(a),p.x*sin(a)+p.y*cos(a));}
float rand(float2 p){return frac(43758.5453*sin(dot(frac(p),float2(12.9898,78.233))));}
float mod(float x, float y){return x-floor(x/y)*y;}
float2 mod(float2 x, float y){return x-floor(x/y)*y;}
float3 mod(float3 x, float y){return x-floor(x/y)*y;}
float rep(float p, float d) {return mod(p - d * .5, d) - d * .5;}
float3 rep(float3 p, float d) {return mod(p - d * .5, d) - d * .5;}
float3 hsv(float h, float s, float v){return ((clamp(abs(6.*frac(h+float3(.0,.666,.333))-3.)-1.,.0,1.)-1.)*s+1.)*v;}
float sphere(float3 p, float3 r){return length(p - r) - 1.;}
float box(float3 p,float3 r){p=abs(p)-r;return max(max(p.x,p.y),p.z);}
float scross(float3 p, float d) {
p = abs(p);
p = max(p, p.yzx);
return min(p.x, min(p.y, p.z)) - d;
}
float map(float3 p){
float t = time / 4.;
float3 q = p;
q.xy -= 2.;
q.xy = rot(q.xy, t);
q.yz = rot(q.yz, t);
q.zx = rot(q.zx, t);
float3 r = float3(2., 2., 2.);
float s = sphere(q, r);
q = rep(q, 4.);
float f = scross(q, .2);
s = min(s, f);
return s;
}
float3 normal(float3 p){float2 e=float2(.001,.0);return normalize(.000001+map(p).x-float3(map(p-e.xyy).x,map(p-e.yxy).x,map(p-e.yyx).x));}
/// Struct
struct VS_OUTPUT {
float4 Pos : POSITION;
float2 TexCoord: TexCoord0;
};
///
/// Pass 1
///
VS_OUTPUT VS1(float4 Pos : POSITION, float4 Tex : TexCoord0)
{
VS_OUTPUT Out;
Out.Pos = Pos;
Out.TexCoord = Tex.xy;
return Out;
}
float4 PS1(VS_OUTPUT IN) : COLOR0
{
float2 p=2*IN.TexCoord + ViewportOffset-1;
p.y*=-1;
p*=ViewportSize/min(ViewportSize.x,ViewportSize.y);
// time
float t = time;
// color
float3 col = float3(.1, .7, .2);
// camera and ray
float3 up = float3(0., 1., .0);
float3 fwd = float3(.0, 0., 1);
float3 side = normalize(cross(up, fwd));
float focus = PI;
float3 ray = float3(2.5, 3.5, -10);
float3 dir = normalize(p.x * side + p.y * up + fwd * focus);
// marching
float d, dTotal = .0;
float3 N;int j;
for(int i=0;i<128;++i){
d = map(ray);
dTotal += d;
if(d < EPS){
N = normal(ray);
col = float3(.2, .6, .3) * (dTotal / 8.) * pow(dot(N, normalize(float3(.2, .4, .8))) * .5 + .5, 2.);
break;
}
ray += d * dir;
j=i;
}
float cn=length(ray-float3(-2., -2., 0.));
col+=PI/pow(abs(cn),2.);
col+=float(j)/256.;
return float4(col, 1);
}
///
/// technique
///
technique RayMarching <
string Script =
"RenderColorTarget0=ScnMap;"
"RenderDepthStencilTarget=DepthBuffer;"
"ClearSetColor=ClearColor;"
"ClearSetDepth=ClearDepth;"
"Clear=Color;"
"Clear=Depth;"
"ScriptExternal=Color;"
"RenderColorTarget0=;"
"RenderDepthStencilTarget=;"
"Pass=Raymarch;"
;
{
pass Raymarch < string Script= "Draw=Buffer;"; > {
AlphaBlendEnable = FALSE;
VertexShader = compile vs_3_0 VS1();
PixelShader = compile ps_3_0 PS1();
}
}
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
バックバッファ
テクニックに1パス追加してピクセルシェーダーをもう1段かませば実現できそう。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
Ray-MMD BRDF
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
エフェクトリファレンス
MMEサンプルエフェクト(ver6)
MMDのモザイク処理エフェクト
curl - 今ちょっと話題のMMD用カールノイズパーティクルエフェクトデータ!
【MMD】流体水面エフェクト 配布【MME】
マンデルボックス
【MMD】PanelLight+ExcellentShadow【MME】
【MMD/MMM】リムライト風味なやつ配布【MME】
うるうる涙目─エフェクト付きアクセ型モデル配布