MikuMikuMoving在很多方面都要比MMD好用一些,但是目前最大的问题就是有些很重要的MME在MMM里面没法使用,比如HalfLambertShader Ver0.301,以下略称HLShader。
但是根据官方给出的文档和范例,可以对其进行改造。
参考文档:https://sites.google.com/site/mikumikumoving/manual/effect-1/mmekaranokonbajon
PS1:发布完整修改的代码是不可能的,第一我不太清楚改造或者二配之类的问题,第二因为我完全不懂着色器语言,所以里面还存留着很多无意义的代码和小BUG,虽然好像不影响使用,但是我实在拿不出手……我只是分享一下方法给有需要的人,网上哪里都找不到这方面的教程……
PS2:推荐使用Notepad++修改,因为fx内注释多为日文,中文系统会乱码,用Notepad++可以很方便地修改编码,并且可以左右对照。当然其他类似的文本编辑器都行。
那开始吧,请各位做好代码冲击准备!
首先我们把Cook-Torrance.fx加载进MMM,应用在模型上,摆好姿势,设置好灯光,会发现两个主要的问题:
1. 在地面阴影模式下HLShader不起作用
在地面阴影模式下HLShader不起作用,而在本影模式下虽然起作用,但是动作却不起作用了。
这是因为MMM改变了读取顶点位置的方式,先来看一下HLShader的这一段代码:
// 頂点シェーダ
BufferShadow_OUTPUT BufferShadow_VS(float4 Pos : POSITION, float3 Normal : NORMAL, float2 Tex : TEXCOORD0, uniform bool useTexture)
{
BufferShadow_OUTPUT Out = (BufferShadow_OUTPUT)0;
// カメラ視点のワールドビュー射影変換
Out.Pos = mul( Pos, WorldViewProjMatrix );
// ワールド位置
Out.WPos= mul( Pos, WorldMatrix );
// カメラとの相対位置
Out.Eye = CameraPosition - Out.WPos;
// 頂点法線
Out.Normal = mul( Normal, (float3x3)WorldMatrix );
// ライト視点によるワールドビュー射影変換
Out.ZCalcTex = mul( Pos, LightWorldViewProjMatrix );
// テクスチャ座標
Out.Tex = Tex;
return Out;
}
很明显,这个函数可以接收几个关于顶点位置的参数,我们需要这样改:
首先是接收参数的方式需要用到MMM内建的结构体:MMM_SKINNING_INPUT IN
然后使用MMM_SkinnedPositionNormal
函数将IN的数据传入临时创建的MMM_SKINNING_OUTPUT SkinOut
结构体。
接下来把所有以前的Pos和Normal都改成SkinOut.Position及SkinOut.Normal。
但是Tex要改成:Out.Tex = IN.Tex;
。
完整代码如下:
// 頂点シェーダ
BufferShadow_OUTPUT BufferShadow_VS(MMM_SKINNING_INPUT IN, uniform bool useTexture)
{
BufferShadow_OUTPUT Out = (BufferShadow_OUTPUT)0;
MMM_SKINNING_OUTPUT SkinOut = MMM_SkinnedPositionNormal(IN.Pos, IN.Normal, IN.BlendWeight, IN.BlendIndices, IN.SdefC, IN.SdefR0, IN.SdefR1);
// カメラ視点のワールドビュー射影変換
Out.Pos = mul( SkinOUt.Position, WorldViewProjMatrix );
// ワールド位置
Out.WPos= mul( SkinOUt.Position, WorldMatrix );
// カメラとの相対位置
Out.Eye = CameraPosition - Out.WPos;
// 頂点法線
Out.Normal = mul( SkinOUt.Normal, (float3x3)WorldMatrix );
// ライト視点によるワールドビュー射影変換
Out.ZCalcTex = mul( SkinOUt.Position, LightWorldViewProjMatrix );
// テクスチャ座標
Out.Tex = IN.Tex;
return Out;
}
全部改完之后进MMM测试一下,问题应该已经解决了。
2. 某些模型的某些部位HLShader不起作用
代码的最后面,有两段绘制用的方法:
// オブジェクト描画用テクニック
technique MainTecBS0 < string MMDPass = "object_ss"; bool UseTexture = false;> {
pass DrawObject {
VertexShader = compile vs_3_0 BufferShadow_VS(false);
PixelShader = compile ps_3_0 BufferShadow_PS(false);
}
}
technique MainTecBS1 < string MMDPass = "object_ss"; bool UseTexture = true;> {
pass DrawObject {
VertexShader = compile vs_3_0 BufferShadow_VS(true);
PixelShader = compile ps_3_0 BufferShadow_PS(true);
}
把这两段复制一遍,将object_ss改成object,把前面的MainTecBS序号也改一下,像这样:
// オブジェクト描画用テクニック
technique MainTecBS0 < string MMDPass = "object"; bool UseTexture = false;> {
pass DrawObject {
VertexShader = compile vs_3_0 BufferShadow_VS(false);
PixelShader = compile ps_3_0 BufferShadow_PS(false);
}
}
technique MainTecBS1 < string MMDPass = "object"; bool UseTexture = true;> {
pass DrawObject {
VertexShader = compile vs_3_0 BufferShadow_VS(true);
PixelShader = compile ps_3_0 BufferShadow_PS(true);
}
}
technique MainTecBS2 < string MMDPass = "object_ss"; bool UseTexture = false;> {
pass DrawObject {
VertexShader = compile vs_3_0 BufferShadow_VS(false);
PixelShader = compile ps_3_0 BufferShadow_PS(false);
}
}
technique MainTecBS3 < string MMDPass = "object_ss"; bool UseTexture = true;> {
pass DrawObject {
VertexShader = compile vs_3_0 BufferShadow_VS(true);
PixelShader = compile ps_3_0 BufferShadow_PS(true);
}
}
关于object和object_ss的区别,我也不是很清楚(日语无能…),引用一段MME的文档:
string MMDPass
そのテクニックを適用する描画対象を指定する。以下のうちいずれかを指定する。この区分は、MMDの描画手順に由来する。“object” オブジェクト本体(セルフシャドウOFF)
“zplot” セルフシャドウ用Z値プロット
“object_ss” オブジェクト本体(セルフシャドウON)
“shadow” 影(セルフシャドウではない単純な影)
“edge” 輪郭(PMDモデルのみ)
3. 全面修改
到目前为止我们已经解决了两个比较致命的问题,现在已经可以在地面阴影模式下使用HLShader,但是效果和在MMD里面还是有一些差别。同样的灯光配置,上图是MMD的效果,下图是MMM的效果:
选用这只模型的原因是因为每个材质都有toon材质,其他地方可能区别很细微,但是最明显的地方就是头发,原因似乎是因为MMM修改了渲染toon材质的方式,并且改变和增加了很多全局变量。
在MMM安装目录的Shader文件夹下有一份SampleBase.fxm,这是一份官方给出的shader模板,我们要做的就是参照那份文档逐行检查和修改HLShader的代码,并且添加在本影和柔影模式下渲染的方法。这就是为什么我推荐使用Notepad++的原因。
另外MMM官方也有一些修改好的特效文件可以下载。
我建议可以先对比一下以前MME用的版本和官方修改的版本,在我们不知道该怎么修改的时候会有很大的启发,我对比的是AdultShader。
其实这个过程不难,但是需要的是耐心和仔细,只要有点c/c++基础的,哪怕不懂HLSL编程(比如我就是),也可以修改。我这里也给大家一些例子:
首先是全局变量的修改:
float3 LightDirection : DIRECTION < string Object = "Light"; >;
修改成
float3 LightDirection[MMM_LightCount] : LIGHTDIRECTIONS;
MMM中自带3个灯光,所以需要一个数组来存放,而默认情况下只有一个灯光是开启的,所以某些代码修改的时候我们只需要用到第一个灯光就行,比如:
comp = min(max(0,dot(Nn,-LightDirection[0]))*Toon,comp);
关于floatN:
这些是HLSL使用的特殊数据类型,表示float型N维向量,N最大为4,其他诸如intN同理。
比如float3可以存放3个float类型的数据,一般用于存放xyz或者rgb;而float4则还可以再加一个alpha,很好理解。修改的时候务必注意这些数据类型。
再比如原来的结构体:
struct BufferShadow_OUTPUT {
float4 Pos : POSITION; // 射影変換座標
float4 ZCalcTex : TEXCOORD0; // Z値
float2 Tex : TEXCOORD1; // テクスチャ
float3 Normal : TEXCOORD2; // 法線
float3 Eye : TEXCOORD3; // カメラとの相対位置
float4 WPos : TEXCOORD4; // ワールド位置
};
最终会需要修改成:
struct BufferShadow_OUTPUT {
float4 Pos : POSITION; // 射影変換座標
float4 ZCalcTex : TEXCOORD0; // Z値
float2 Tex : TEXCOORD1; // テクスチャ
float3 Normal : TEXCOORD2; // 法線
float3 Eye : TEXCOORD3; // カメラとの相対位置
float4 SS_UV1 : TEXCOORD4; // セルフシャドウテクスチャ座標
float4 SS_UV2 : TEXCOORD5; // セルフシャドウテクスチャ座標
float4 SS_UV3 : TEXCOORD6; // セルフシャドウテクスチャ座標
float4 SubTex : TEXCOORD7; // サブテクスチャ/スフィアマップテクスチャ座標
float4 WPos : TEXCOORD8; // ワールド位置
float4 Color : COLOR0; // ライト0による色
};
注意保留原有的WPos!我之前把它替换成了SS_UV1,把所有后面用到的也都替换了,结果渲染变得很奇怪……
之前修改过的那一段顶点位置的计算:
Out.Pos = mul( SkinOut.Position, WorldViewProjMatrix );
按照官方的文档修改(虽然没什么太大作用…):
// カメラ視点のワールドビュー射影変換( 頂点座標)
if (MMM_IsDinamicProjection)
{
float4x4 wvpmat = mul(mul(WorldMatrix, ViewMatrix), MMM_DynamicFov(ProjMatrix, length(Out.Eye)));
Out.Pos = mul( SkinOut.Position, wvpmat );
}
else
{
Out.Pos = mul( SkinOut.Position, WorldViewProjMatrix );
}
然后像这一段修改好的代码就是关于渲染toon材质的一段(直接从sample里复制的,而且这段很关键):
// セルフシャドウなしのトゥーン適用
float3 color;
if (!useSelfShadow && useToon && usetoontexturemap ) {
color = MMM_GetToonColor(MaterialToon, IN.Normal, LightDirection[0], LightDirection[1], LightDirection[2]);
Color.rgb *= color;
}
if (useSelfShadow) {
if (useToon && usetoontexturemap) {
float3 shadow = MMM_GetToonColor(MaterialToon, IN.Normal, LightDirection[0], LightDirection[1], LightDirection[2]);
color = MMM_GetSelfShadowToonColor(MaterialToon, IN.Normal, IN.SS_UV1, IN.SS_UV2, IN.SS_UV3, false, useToon);
Color.rgb *= min(shadow, color);
}
else {
Color.rgb *= MMM_GetSelfShadowToonColor(MaterialToon, IN.Normal, IN.SS_UV1, IN.SS_UV2, IN.SS_UV3, false, useToon);
}
}
我是懒得修改最后的渲染方法,所以保留了官方推荐的这些形式,但是在某些函数的开始自己的定义了一些布尔值,以便以后再修改,比如:
// ピクセルシェーダ
float4 BufferShadow_PS(BufferShadow_OUTPUT IN, uniform bool useTexture) : COLOR0
{
bool useSphereMap = false;
bool useToon = true;
bool useSelfShadow = true;
但是切记一定要保留HLShader原本的计算方法,比如这是sample当中的一段:
static float3 AmbientColor[3] = {
saturate(MaterialAmbient * LightAmbients[0]) + MaterialEmmisive,
saturate(MaterialAmbient * LightAmbients[1]) + MaterialEmmisive,
saturate(MaterialAmbient * LightAmbients[2]) + MaterialEmmisive};
而这是原来HLShader的计算方法:
static float4 AmbientColor = float4( MaterialEmmisive*MaterialToon*LightAmbient/0.60, MaterialDiffuse.a+0.01);
所以我们要在保留原计算方法的同时把它修改成数组的形式:
static float4 AmbientColor[3] = {
float4( MaterialEmmisive*MaterialToon*LightAmbients[0]/0.60, MaterialDiffuse.a+0.01),
float4( MaterialEmmisive*MaterialToon*LightAmbients[1]/0.60, MaterialDiffuse.a+0.01),
float4( MaterialEmmisive*MaterialToon*LightAmbients[2]/0.60, MaterialDiffuse.a+0.01)};
成果
有没有头晕掉……我可以晕了整整两天才摸索出来然后全部修改完啊!
全部修改完之后可以就拖进MMM中进行调试,它报哪里错就修改哪里,只要足够仔细,应该没问题的。另外ShadowDraw.fx也要修改,但是加载的时候是加载ShadowMap.fx,因为是调用关系。同样的,其他涉及到shader方面的MME应该也都是这样改造。
最后这是我改造完的成果,祝各位成功~