游戏中的捏脸系统一般有两种实现方式,一种是用骨骼动画来实现,另一种是使用变形动画(morph)来实现。
用骨骼动画实现捏脸要求在面部每个细节部位绑定若干骨骼,通过移动骨骼带动面部网格顶点移动,网格顶点的移动再反馈到另外一套用于表情动画的骨骼蒙皮中。
用变形动画实现捏脸要求先制作若干极端情况下的脸型模型,通过调节参数(权重)来混合这些极端模型,产生新的面部网格顶点。面部网格顶点生成后可以反馈到骨骼蒙皮中用于表情动画,也可以继续使用变形动画的方式和代表表情的极端模型相混合来产生表情动画。
关于使用变形动画方式实现时如何生成极端情况下的脸型模型,通过观察原始数据可知,每个极端模型长相如同妖魔鬼怪毫无规律可言,让美术人员直接制作估计是不现实的。
所以首先要确定好各调节参数之间的联动关系,比如将嘴部变宽时,下颌和鼻子底部也会相应的变宽。第二步美术人员制作单一参数下的极端模型。第三步联立方程组,将模型看做未知数,使用算法求解出图示的妖魔鬼怪极端模型。最后检验和微调。
捏脸的另一种表现形式是捏制面部纹理,比如眉毛胡子的浓淡、嘴唇颜色、眉毛高低等,这些并不需要网格模型做变动,仅通过变化纹理即可实现。纹理的捏制和变形动画类似,也是通过若干极端状态下的纹理根据参数混合而来。
关于使用语音控制口型动画,先将语音转换成对应的音节流,比如借助开源项目rhubarb-lip-sync导出口型数据,然后key出表情帧即可。
编辑器部分代码:
class CtrlFile;
class FimFile;
class TrigonFile;
class Express;
class FgFile;
class ExpressFrameHead;
class ExpressFrameLine;
class FaceEditorGui:public EditorDlgBase
{
public:
FaceEditorGui();
~FaceEditorGui();
virtual bool OnWake();
virtual bool OnSleep();
virtual const char* GetTypeName();
static const char* DlgName();
virtual void Update();
virtual void Render();
virtual bool ProcButtonUp (GuiButtonCtrl *ctrl);
virtual bool ProcButtonDown (GuiButtonCtrl *ctrl);
virtual bool ProcProgressUpdate (GuiProgressBarCtrl*ctrl);
virtual bool ProcDragIconUp (GuiDragIconCtrl *ctrl);
virtual bool ProcTabUp (GuiTabCtrl *ctrl);
virtual bool ProcKeyUp (int key);
//virtual bool OnDropFiles(int filenum,/*const*/ char** filenames);
virtual void OpenFile(const char* fileName,bool listFile=false);
virtual void SaveFile(const char* fileName);
private:
int OnTimeLine_LeftDown(const char* args);
int OnTimeLine_RightDown(const char* args);
int OnTimeLine_Event(const char* args);
void OnRightMenu(const char* args);
void PropertyFaceGeo(bool bSend=true);
void PropertyFaceTex(bool bSend=true);
void PropertyFaceExpress(bool bSend=true);
void RandomPropertyFaceGeo();
void RandomPropertyFaceTex();
void RandomPropertyFaceExpress();
void ZeroPropertyFaceGeo();
void ZeroPropertyFaceTex();
void ZeroPropertyFaceExpress();
void ReGenGeo();
void ReGenTex();
//private:
public:
GuiTabCtrl* m_tabFacegen;
GuiControl* m_ctrlCanvans;
RectF m_rectCanvans;
TexturePtr m_texCanvans;
CtrlFile* m_ctlFile;
FimFile* m_fimFile;
#define TestTrigonFileNum 8
TrigonFile* m_trigonFile[TestTrigonFileNum];
Express* m_express;
Express* m_animExpress;
FgFile* m_fgFile;
//动画
ExpressFrameLine* m_frameLine;
ExpressFrameHead* m_frameHead;
//遗传
FgFile* m_fgFileInherit[8];
//变形
FgFile* m_fgFileMorph[3];
//照片适配
};
//========================================================
// @Date: 2016.05
// @File: SourceLib/Gui/FaceEditorGui.cpp
// @Brief: FaceEditorGui
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================
#include "General/Pch.h"
#include "General/General.h"
#include "General/StringUtil.h"
#include "General/CallBack.h"
#include "General/Window.h"
#include "General/Option.h"
#include "Gui/GuiControlMisc.h"
#include "Gui/GuiDialogMisc.h"
#include "Gui/GuiMgr.h"
#include "Render/Camera.h"
#include "Render/MC_Misc.h"
#include "Render/RendDriver.h"
#include "Render/FaceGen.h"
#include "General/Pce.h"
using namespace RendSys;
FaceEditorGui::FaceEditorGui()
{
}
FaceEditorGui::~FaceEditorGui()
{
}
bool FaceEditorGui::OnWake()
{
EditorDlgBase::OnWake();
SubCtrlState("Menu.combox_view.contentGroup.menu_propertybox",GCS_CLICK);
MAPCTRL(m_tabFacegen,"splitFrameAll.EditPannel.tableFacegen");
MAPCTRL(m_propertyBox ,"splitFrameAll.EditPannel.scrollBar_property.contentGroup.list_ctrlBox");
MAPCTRL(m_timeline ,"timeLine");
MAPCTRL(m_ctrlCanvans,"splitFrameAll.EditCanvans");
G_TextureMgr->AddTexture(m_texCanvans,"data/gui/editorgui/paintBack.png");
if (m_timeline)
{
m_timeline->CallBack()->Add("MouseDown","",this,&FaceEditorGui::OnTimeLine_LeftDown);
m_timeline->CallBack()->Add("RightMouseDown","",this,&FaceEditorGui::OnTimeLine_RightDown);
m_timeline->CallBack()->Add("TimeLineEvent","",this,&FaceEditorGui::OnTimeLine_Event);
}
SubCtrlVisible("toolbaricon_play.play",true);
SubCtrlVisible("toolbaricon_play.pause",false);
//==================^_^
CameraCtrlerTarget* ctrler = new CameraCtrlerTarget;
ctrler->SetDistToTar(300);
ctrler->SetTarPos(vec3(0,0,0));
G_Camera->PushCtrler(ctrler);
G_Camera->SetEuler(0,-30,0);
//==================^_^
m_ctlFile = new CtrlFile;
m_fimFile = new FimFile;
for (int i=0;i { m_trigonFile[i] = new TrigonFile; } m_express = new Express; m_animExpress = new Express; m_fgFile = new FgFile; //动画 m_frameHead = new ExpressFrameHead(NULL); m_frameLine = new ExpressFrameLine; m_ctlFile->LoadFromFile("data/。。。"); m_fimFile->LoadFromFile("data/。。。"); m_fgFile->LoadFromFile("data/。。。"); //m_trigonFile[0]->LoadFromFile("data/。。。"); //m_trigonFile[0]->LoadFromFile("data/。。。"); m_trigonFile[0]->LoadFromFile("data/。。。"); m_trigonFile[1]->LoadFromFile("data/。。。"); m_trigonFile[2]->LoadFromFile("data/。。。"); m_trigonFile[3]->LoadFromFile("data/。。。"); m_trigonFile[4]->LoadFromFile("data/。。。"); m_trigonFile[5]->LoadFromFile("data/。。。"); m_trigonFile[6]->LoadFromFile("data/。。。"); m_trigonFile[7]->LoadFromFile("data/。。。"); for (int i=0;i { m_trigonFile[i]->GenTex(m_fgFile); m_trigonFile[i]->GenMesh(m_fgFile); } PropertyFaceGeo(); { //自动产生表情 int ExpressNum = 40; m_frameLine->m_keyFrameNum = ExpressNum*2; m_frameLine->m_maxTimeFrame = m_frameLine->m_keyFrameNum*30; m_frameLine->m_keyFrames = new ExpressFrame[ExpressNum*2]; for (int i=0;i { m_frameLine->m_keyFrames[i].m_timeFrame = i*30; } for (int i=0;i { ExpressFrame* frame = &m_frameLine->m_keyFrames[i*2+1]; frame->m_express.m_differenceMorphWeights[i] = 1500; } m_frameHead->SetFrameLine(m_frameLine); m_frameHead->SetPause(true); m_timeline->SetEditFrameLine(m_frameLine); m_timeline->SetEditPlayHead(m_frameHead); } return true; } bool FaceEditorGui::OnSleep() { G_Camera->PopCtrler(); SafeDelete(m_ctlFile); SafeDelete(m_fimFile); for (int i=0;i { SafeDelete(m_trigonFile[i]); } SafeDelete(m_express); SafeDelete(m_animExpress); SafeDelete(m_fgFile); SafeDelete(m_frameHead); SafeDelete(m_frameLine); return true; } void FaceEditorGui::OpenFile(const char* fileName,bool listFile) { if (fileName==NULL || strlen(fileName)>=MAX_PATH) { return; } m_fileName = fileName; } void FaceEditorGui::SaveFile(const char* fileName) { } //timeline自处理右键 回传:创建、清除、拷贝、剪切、粘贴关键帧(空白关键帧、补间、标签、动作)信息? int FaceEditorGui::OnTimeLine_LeftDown(const char* args) { int frameStart, frameEnd; m_timeline->GetSelectedFrame(frameStart, frameEnd); m_frameHead->GotoFrame(frameStart); m_frameHead->SetPause(true); SubCtrlVisible("toolbaricon_play.play",true); SubCtrlVisible("toolbaricon_play.pause",false); char buf[256]; sprintf(buf,"frame%d - %d",frameStart,frameEnd); m_statusText->SetText(buf); return 0; } int FaceEditorGui::OnTimeLine_RightDown(const char* args) { //RightMenu* rightMenu = G_GuiMgr->GetGui //G_GuiMgr->PushGui(rightMenu,GL_DIALOG); //if (rightMenu) //{rightMenu->ClearMenu(); // rightMenu->CallBack()->EraseParmCommand("onButtonDown"); // rightMenu->CallBack()->Add("onButtonDown","",this,&SceneEditorGui::OnRightMenu); // rightMenu->AddMenu("cut",M2U("剪切(T)").c_str()) // ->AddMenu("copy",M2U("拷贝(C)").c_str()) // ->AddMenu("paste",M2U("粘贴(P)").c_str()) // ->AddMenu("undo",M2U("撤销(U)").c_str()) // ->AddMenu("refresh",M2U("刷新(E)").c_str()) // ->AddMenu("property",M2U("属性(R)").c_str()); // rightMenu->AdjustRect(); //} return 0; } int FaceEditorGui::OnTimeLine_Event(const char* args) { GuiTimeLineCtrl::Event* event_ = (GuiTimeLineCtrl::Event*)args; // char buf[256]; // sprintf(buf,"key%d - %d",keyStart,keyEnd); m_statusText->SetText(event_->dsc.c_str()); return 0; } void FaceEditorGui::OnRightMenu(const char* args) { String name = args; if (name == "cut") { } else if (name == "copy") { } } bool FaceEditorGui::ProcKeyUp(int key) { EditorDlgBase::ProcKeyUp(key); return false; } bool FaceEditorGui::ProcButtonDown(GuiButtonCtrl* ctrl) { if (!ctrl) { return true; } EditorDlgBase::ProcButtonDown(ctrl); //String path = ctrl->GetPath(); String name = ctrl->GetIdName(); if (name == "menu_new") { } else if (name == "menu_undo") { } else if (name == "ApplyProperty") { if (m_tabFacegen->GetCheckPos()==0) { PropertyFaceGeo(false); } else if (m_tabFacegen->GetCheckPos()==1) { PropertyFaceTex(false); } else if (m_tabFacegen->GetCheckPos()==2) { PropertyFaceExpress(false); } } else if (name == "menu_propertybox") { GuiGroupCtrl* menu; MAPCTRL(menu,"Menu"); if (ctrl->IsChildOf(menu)) { bool visible = ctrl->GetState() == GCS_CLICK; SubCtrlVisible("splitFrameAll.EditPannel.scrollBar_property",visible); } } else if (name == "randomProperty") { if (m_tabFacegen->GetCheckPos()==0) { RandomPropertyFaceGeo(); } else if (m_tabFacegen->GetCheckPos()==1) { RandomPropertyFaceTex(); } else if (m_tabFacegen->GetCheckPos()==2) { RandomPropertyFaceExpress(); } } else if (name == "zeroProperty") { if (m_tabFacegen->GetCheckPos()==0) { ZeroPropertyFaceGeo(); } else if (m_tabFacegen->GetCheckPos()==1) { ZeroPropertyFaceTex(); } else if (m_tabFacegen->GetCheckPos()==2) { ZeroPropertyFaceExpress(); } } return true; } bool FaceEditorGui::ProcButtonUp(GuiButtonCtrl* ctrl) { return true; } bool FaceEditorGui::ProcTabUp(GuiTabCtrl *ctrl) { int curRadio = ctrl->GetCheckPos(); if (curRadio == 0) { m_propertyBox->SetVisible(true); PropertyFaceGeo(); } else if (curRadio == 1) { m_propertyBox->SetVisible(true); PropertyFaceTex(); } else if (curRadio == 2) { m_propertyBox->SetVisible(true); PropertyFaceExpress(); } else if (curRadio == 3) { m_propertyBox->SetVisible(false); PropertyFaceGeo(); } else if (curRadio == 4) { m_propertyBox->SetVisible(false); PropertyFaceTex(); } else if (curRadio == 5) { m_propertyBox->SetVisible(false); PropertyFaceExpress(); } return true; } bool FaceEditorGui::ProcProgressUpdate(GuiProgressBarCtrl* ctrl) { String name = ctrl->GetIdName(); //if (name == "button_pause") { // if (m_tabFacegen->GetCheckPos()==0) { //PropertyFaceGeo(false); float pos = (ctrl->GetPos()-0.5f)*20000; m_ctlFile->ChangeLenearCtrlPos(m_propertyBox->GetCurItem(),pos,m_fgFile,CtrlFile::LGS); PropertyFaceGeo(true); ReGenGeo(); } else if (m_tabFacegen->GetCheckPos()==1) { //PropertyFaceTex(false); float pos = (ctrl->GetPos()-0.5f)*20000; m_ctlFile->ChangeLenearCtrlPos(m_propertyBox->GetCurItem(),pos,m_fgFile,CtrlFile::LTS); PropertyFaceTex(true); ReGenTex(); } else if (m_tabFacegen->GetCheckPos()==2) { PropertyFaceExpress(false); //float pos = (ctrl->GetPos()-0.5f)*20000; //m_express->ChangeLenearCtrlPos(m_propertyBox->GetCurItem(),pos,&m_fgFile,CtrlFile::LGS); //PropertyFaceExpress(true); //ReGenGeo(); } } return true; } bool FaceEditorGui::ProcDragIconUp(GuiDragIconCtrl* ctrl) { if (!ctrl) { return true; } EditorDlgBase::ProcDragIconUp(ctrl); String path = ctrl->GetPath(); String name = ctrl->GetIdName(); if(strstr(path.c_str(),"toolbaricon0")) { } else if (strstr(path.c_str(),"toolbaricon_play")) { if (m_timeline) { m_timeline->ProcDragIconUp(ctrl); } } return true; } void FaceEditorGui::PropertyFaceGeo(bool bSend) { if (m_propertyBox) { m_ctlFile->CaculLinearCtrlPos(m_fgFile,CtrlFile::LGS); ListItemUpdater updater; updater.BeginUpdateItem(m_propertyBox,bSend,m_ctlFile->m_linearCtrlsGSNum); int i = 0; //for(int m=0;m //{ // //?不是对应的 // float temp = m_fgFile->m_symmetricGeoMorphWeights[m]; // updater.UpdateItem(i,m_ctlFile->m_linearCtrlsLGS[m%m_ctlFile->m_linearCtrlsGSNum].label.dsc,temp,-10000.0f,10000.0f); i++; // if (bSend==false) // { // m_fgFile->m_symmetricGeoMorphWeights[m] = temp; // } //} for(int l=0;l { float temp = m_ctlFile->m_curLinearCtrlsPos[l]; updater.UpdateItem(i,m_ctlFile->m_linearCtrlsLGS[l].label.dsc,temp,-10000.0f,10000.0f); i++; if (bSend==false) { m_ctlFile->m_curLinearCtrlsPos[l] = temp; } } if (bSend==false) { ReGenGeo(); } } } void FaceEditorGui::ReGenGeo() { for (int i=0;i { m_trigonFile[i]->GenMesh(m_fgFile); //重置表情 m_trigonFile[i]->GenMesh(m_express); } } void FaceEditorGui::ReGenTex() { for (int i=0;i { m_trigonFile[i]->GenTex(m_fgFile); } } void FaceEditorGui::ZeroPropertyFaceGeo() { if (m_propertyBox) { for (int d=0;d { m_fgFile->m_symmetricGeoMorphWeights[d] = 0; } PropertyFaceGeo(true); ReGenGeo(); } } void FaceEditorGui::RandomPropertyFaceGeo() { if (m_propertyBox) { for (int d=0;d { m_fgFile->m_symmetricGeoMorphWeights[d] = RandRange(-10000.0f,10000.0f); } PropertyFaceGeo(true); ReGenGeo(); } } void FaceEditorGui::PropertyFaceTex(bool bSend) { if (m_propertyBox) { m_ctlFile->CaculLinearCtrlPos(m_fgFile,CtrlFile::LTS); ListItemUpdater updater; updater.BeginUpdateItem(m_propertyBox,bSend,m_ctlFile->m_linearCtrlsTSNum); int i = 0; //for(int m=0;m //{ // //?不是对应的 // float temp = m_fgFile->m_symmetricTexMorphWeights[m]; // updater.UpdateItem(i,m_ctlFile->m_linearCtrlsLTS[m%m_ctlFile->m_linearCtrlsTSNum].label.dsc,temp,-1000.0f,1000.0f); i++; // if (bSend==false) // { // m_fgFile->m_symmetricTexMorphWeights[m] = temp; // } //} for(int l=0;l { float temp = m_ctlFile->m_curLinearCtrlsPos[l]; updater.UpdateItem(i,m_ctlFile->m_linearCtrlsLTS[l].label.dsc,temp,-10000.0f,10000.0f); i++; if (bSend==false) { m_ctlFile->m_curLinearCtrlsPos[l] = temp; } } if (bSend==false) { ReGenTex(); } } } void FaceEditorGui::ZeroPropertyFaceTex() { if (m_propertyBox) { for (int d=0;d { m_fgFile->m_symmetricTexMorphWeights[d] = 0; } PropertyFaceTex(true); ReGenTex(); } } void FaceEditorGui::RandomPropertyFaceTex() { //if (m_propertyBox) //{ // bool bSend = true; // ListItemUpdater updater; // updater.BeginUpdateItem(m_propertyBox,bSend); // int i = 0; // for(int m=0;m // { // float temp = RandRange(-1000.0f,1000.0f); // updater.UpdateItem(i,m_ctlFile->m_linearCtrlsLTS[m%m_ctlFile->m_linearCtrlsTSNum].label.dsc,temp,-1000.0f,1000.0f); i++; // } // m_ctlFile->CaculFg(&m_fgFile,CtrlFile::LTS); // ReGenTex(); //} if (m_propertyBox) { for (int d=0;d { m_fgFile->m_symmetricTexMorphWeights[d] = RandRange(-10000.0f,10000.0f); } PropertyFaceTex(true); ReGenTex(); } } void FaceEditorGui::PropertyFaceExpress(bool bSend) { if (m_propertyBox) { ListItemUpdater updater; updater.BeginUpdateItem(m_propertyBox,bSend,m_trigonFile[0]->m_expressMorphNum); int i = 0; for(int m=0;m { float temp = m_express->m_differenceMorphWeights[m]; updater.UpdateItem(i,m_trigonFile[0]->m_expressMorph[m].label.dsc,temp,-100.0f,1000.0f); i++; if (bSend==false) { m_express->m_differenceMorphWeights[m] = temp; } } if (bSend==false) { for (int i=0;i { m_trigonFile[i]->GenMesh(m_express); } } } } void FaceEditorGui::ZeroPropertyFaceExpress() { if (m_propertyBox) { bool bSend = true; ListItemUpdater updater; updater.BeginUpdateItem(m_propertyBox,bSend,m_trigonFile[0]->m_expressMorphNum); int i = 0; for(int m=0;m { float temp = 0; updater.UpdateItem(i,m_trigonFile[0]->m_expressMorph[m].label.dsc,temp,-100.0f,1000.0f); i++; } ReGenGeo(); } } void FaceEditorGui::RandomPropertyFaceExpress() { if (m_propertyBox) { bool bSend = true; ListItemUpdater updater; updater.BeginUpdateItem(m_propertyBox,bSend,m_trigonFile[0]->m_expressMorphNum); int i = 0; for(int m=0;m { float temp = RandRange(-100.0f,1000.0f); updater.UpdateItem(i,m_trigonFile[0]->m_expressMorph[m].label.dsc,temp,-100.0f,1000.0f); i++; } PropertyFaceExpress(false); } } void FaceEditorGui::Update() { PROFILEFUN("FaceEditorGui::Update;",0.0f,ALWAYSHIDE); m_rectCanvans = m_ctrlCanvans->GetRectReal(); vec2 off = m_ctrlCanvans->GetOffset(); m_rectCanvans.x += off.x; m_rectCanvans.y += off.y; // if (m_frameHead->IsPause()==false) { m_frameHead->Advance(-1); for (int i=0;i { m_trigonFile[i]->GenMesh(&m_frameHead->m_mixFrame.m_express); } } } void FaceEditorGui::Render() { PROFILEFUN("FaceEditorGui::Render;",0.0f,ALWAYSHIDE); if (G_Option->m_renderTargetEnable) { G_RendDriver->GetRenderTarget(GuiTarget)->EndTarget(); G_RendDriver->GetRenderTarget(SceneTarget)->BeginTarget(); G_RendDriver->GetDepthBuffer(GuiDepthBuffer)->BeginBuffer(); G_RendDriver->RendClear(RS_COLOR_BUFFER_BIT|RS_DEPTH_BUFFER_BIT|RS_STENCIL_BUFFER_BIT,Color(0.0f, 0.0f, 0.0f, 1.0f)); } G_RendDriver->Color3f(1,1,1); G_RendDriver->ClearClipRect(); m_texCanvans->Bind(); G_RendDriver->RendTextureRect(m_rectCanvans); //float width = (G_Window->m_iWidth - rect.width)/2; //旋转将不在以模型为中心 //G_Camera->SetTarPos(vec3(width*1,0,0)); G_RendDriver->EndUI(); G_RendDriver->SetViewPortf(m_rectCanvans.x/G_Window->m_iWidth, m_rectCanvans.y/G_Window->m_iHeight, m_rectCanvans.width /G_Window->m_iWidth, m_rectCanvans.height/G_Window->m_iHeight); G_RendDriver->Perspective(); G_RendDriver->DefaultLight(); G_RendDriver->SetRenderStateEnable(RS_LIGHT0,true); G_RendDriver->SetRenderStateEnable(RS_DEPTH_TEST,false); G_RendDriver->BlendFunc(RS_SRC_ALPHA, RS_ONE_MINUS_SRC_ALPHA); G_RendDriver->Color3f(1,1,1); for (int i=0;i { //if(i==3) continue;//m_trigonEyeGlasses.Rend(); if (i==1) { G_RendDriver->DepthMask(false); } else { G_RendDriver->DepthMask(true); } m_trigonFile[i]->Rend(); } G_RendDriver->SetViewPortf(0,0,1,1); if (G_Option->m_renderTargetEnable) { G_RendDriver->GetRenderTarget(SceneTarget)->EndTarget(); G_RendDriver->GetRenderTarget(GuiTarget)->BeginTarget(); } G_RendDriver->SetRenderStateEnable(RS_DEPTH_TEST,false); G_RendDriver->SetRenderStateEnable(RS_LIGHT0,false); G_RendDriver->SetRenderStateEnable(RS_TEXTURE_2D,true); G_RendDriver->BeginUI(); GuiGroupCtrl::Render(); } const char* FaceEditorGui::GetTypeName() { return "GuiGroupCtrl"; } const char* FaceEditorGui::DlgName() { return "FaceEditorGui"; } 完
友情链接:
Copyright © 2022 世界杯金靴_足球小子世界杯 - ffajyj.com All Rights Reserved.