Well, it pretty much started with Left 4 Dead and Plants vs. Zombies which resulted in a slew of lame zombie adaptations (i.e. COD: Black Ops but Zombified) and mobile apps. But they are making a full on comeback now. I am eagerly awaiting the release of “The Last of Us” by none other than the best story tellers in the business Naughty Dog. The game tentatively has no release date but here are the 2 released cinematics.
On another zombie note… I completely missed these 2 launch trailer cinematics for “Dead Island”. I agree with the masses. Arguably the best cinematic trailer ever created to date. But I heard the game was poopies. Reminds me of the movie Memento or the Modest Mouse video Little Motel. Amazing directing.
And now in chronological order.
The interesting thing about this trailer is it’s obviously rooted in mocap but the facial animation is done using technology from Dimensional Imaging who is also used by EA Canada located here in Vancouver. Really cool!
The 3 major scripts for Bystander that I am working on pertain to making just the morph target data interactive. They have little to do with the character customization aspect of the project which are part of a different milestone. Below is a Mindjet Manager diagram showing my initial plans for how the asset data and the operators will work together.
Here is a short description of what each script is meant to do and its status.
Morph Operator Script
Objective: Serve as a reference for the mesh data and their relationships to each other. Build the morph data.
Description: Unlike other morph scripts, my intention for this script was to be more artist friendly. The operator mimics somewhat the behavior of a stack operator in a 3d package. Here is where all the morph data is organized through user input. It requires the user to understand standard morph paradigms using anywhere between 1-4 targets.
Input: base mesh, morph targets, widget type, #widgets, number of morphs per widget
Status: alpha code
GUI Operator Script (Morphs)
Objective: Retrieve the widget information from the Morph Operator and draw all the GUI widgets.
Description: This script is simply retrieving all the widget data from the Morph Operator. Its is resposible for setting up all the related expression given the enum specified in the Morph Operator per widget. It also retrieves all the target mesh and widget string data to draw where required.
Input: choose draw widgets or sliders or none, compiled data from Morph Operator
Status: wip code
Active Materials Operator
Objective: Retrieve the validly named materials on base mesh and hook them up to a specified widget created by the GUI Operator. Trigger the morph data with 3d converted to 2d mouse pointer input.
Description: A little more complicated but my initial thoughts are, similar to hit materials for bullets, the user mouses over a body part. When over an “active material” the emmisive texture is enabled and click and drag gets converted to a blend weight. XYZ inputs based on camera orientation around the model activate corresponding widgets assigned in the Active Materials Operator. The mouse drag input drives the blend of the morph target data given the expressions per widget type.
Input: choose materials, assign widgets from GUI Operator
Status: wip code
Other scripts I am already using or will be creating include a Lookat Controller Script, FPS GUI Script, GUI Operator Script (Camera), and GUI Operator Script (Customizer).
It’s the long weekend! Which means I get to work more! This is the current working version of the morph target scripts I’m writing. The build shapes portion is sourced from the unity community forum and I’m refactoring it right now but this version still works. In the refactor, I am adding a progress bar and speeding up the compile as the forum code takes a whopping 15 minutes to process the data for only 2-3 targets! Would probably kill my machine with all the final split shapes assigned to the widgets. Also will rename some of the labels for clarity and add help buttons or tooltips in the Inspector for each entry.
I’ve actually moved on to the GUI Operator Script and what I call an Active Materials Operator Script so I am not blocked to get the project to a fully functional state. After the code side is fully functional I’ll just be waiting on my own updates to art content. That will be when the real fun will start.
This is what the Morph Operator looks like in the Inspector. The only thing not visible is the enum drop down for the widget types but you can see them on lines 19-27 of the script view.
This is the working version of the script without the performance refactor and progress bars.
[System.Serializable] publicclass MorphProp // properties per morph { publicstring morphName ="";// slider name public Mesh targetMesh;// additive target publicfloat blendWeight =0;// slider weight }
[System.Serializable] publicclass ParadigmList // list widgets containing morphs { publicstring widgetName ="";// group of sliders name publicenum Paradigms {
None =0, // enum starts from 0
Linear,
Bilinear,
Triangular,
Parametric_Axis,
Parametric_Corners }; public Paradigms widgetType = Paradigms.None;// default to none public MorphProp[] morphTargets; }
publicclass MorphOperator : MonoBehaviour { publicstring clusterName ="";// name affected points baked in art public Mesh baseMesh =null;// public ParadigmList[] morphList;
void Start() { }
void OnGUI() { }
void Update() { }
internalclass BlendShapeVertex { publicint originalIndex; public Vector3 position; public Vector3 normal; } internalclass BlendShape { public BlendShapeVertex[] vertices;// = new Array(); }
void Awake()// validate data when activated { if(baseMesh ==null){
Debug.LogWarning("No Base Mesh is assigned."); return; }
for(int i =0; i < morphList.Length; i++){//check if any targets in the morpher are missing and if so fire to debug log for(int j =0; j < morphList[i].morphTargets.Length; j++){ if(morphList[i].morphTargets[j].targetMesh==null){
Debug.LogWarning("Morph Target "+ j +" Target Mesh has not been assigned."); return; } } }
MeshFilter filter = gameObject.GetComponent(typeof(MeshFilter))as MeshFilter;//Populate the working mesh
filter.sharedMesh= baseMesh;
workingMesh = filter.mesh;
int validVertexCount = baseMesh.vertexCount;//store the vertex count for the base mesh
Debug.Log("Base Mesh vertex count: "+ validVertexCount);
for(int k =0; k < morphList.Length; k++){//check all targets for matching vertex count for(int l =0; l < morphList[k].morphTargets.Length; l++){
Debug.Log("Debug Info: "+ morphList[k].morphTargets[l].targetMesh.vertexCount); if(morphList[k].morphTargets[l].targetMesh.vertexCount!= validVertexCount){
Debug.LogError("Morph Element "+ k +" Target Mesh vertex count is not a match."); //make better message
Debug.LogWarning("Morph Element "+ k +" Target Mesh "+ l +" vertex count:"+ morphList[k].morphTargets[l].targetMesh.vertexCount); return; } } }
BuildMorphTargets(); }
for(int i =0; i < morphList.Length; i++)//For each attribute figure out which vertices are affected, then store their info in the blend shape object. { for(int m =0; m < morphList[i].morphTargets.Length; m++){ //Populate blendShapes array with new blend shapes
blendShapes[i]=new BlendShape();
/** TODO: Make this a little more stylish!
* UGLY hack to compensate the lack of dynamic arrays in C#. Feel free to improve!
*/ int blendShapeCounter =0; for(int j =0; j < workingMesh.vertexCount; j++) {
blendShapes[i].vertices=new BlendShapeVertex[blendShapeCounter];
blendShapeCounter =0; for(int j =0; j < workingMesh.vertexCount; j++) { //If the vertex is affected, populate a blend shape vertex with that info if(workingMesh.vertices[j]!= morphList[i].morphTargets[m].targetMesh.vertices[j]) { //Create a blend shape vertex and populate its data.
//Add new blend shape vertex to blendShape object.
blendShapes[i].vertices[blendShapeCounter]=blendShapeVertex;
blendShapeCounter++; } } //Convert blendShapes.vertices to builtin array //blendShapes[i].vertices = blendShapes[i].vertices.ToBuiltin(BlendShapeVertex); } } }
publicvoid SetMorph() { //Set up working data to store mesh offset information.
Vector3[] morphedVertices = baseMesh.vertices;
Vector3[] morphedNormals = baseMesh.normals;
for(int j =0; j < morphList.Length; j++)//For each attribute... { for(int m =0; m < morphList[j].morphTargets.Length; m++){ if(!Mathf.Approximately(morphList[j].morphTargets[m].blendWeight, 0))//If the weight of this attribute isn't 0 { //For each vertex in this attribute's blend shape... for(int i =0; i < blendShapes[j].vertices.Length; i++) { //...adjust the mesh according to the offset value and weight
morphedVertices[blendShapes[j].vertices[i].originalIndex]+= blendShapes[j].vertices[i].position* morphList[j].morphTargets[m].blendWeight; //Adjust normals as well
morphedNormals[blendShapes[j].vertices[i].originalIndex]+= blendShapes[j].vertices[i].normal* morphList[j].morphTargets[m].blendWeight; } } } } //Update the actual mesh with new vertex and normal information, then recalculate the mesh bounds.
workingMesh.vertices= morphedVertices;
workingMesh.normals= morphedNormals;
workingMesh.RecalculateBounds(); } }
In the next post I’ll explain more how each of these operators are intended to work together.
Long long long time ago when I worked at Mainframe, I was part of a small development team for a feature in Burnaby. Around that time Incredibles was the latest and greatest thing, Softimage was still a contender, and I still had hair.
A bunch of us learned, from an X Pixar good friend of mine, a ton about how they set up their rigs. Blend shapes with custom control panels and muscle sliding was the norm back then. Not many people knew of Micro and Macro setups and scripted poses supported by pose libraries. From these learnings, myself and 1 animator created this test. Took me 3 full days to model and rig plus 2 days for animation. The foundation of the brows and lips are Isner Spines and the rest were just well placed parent relationship nulls. Below are a couple images of the model.
The animation was done by my friend, Graham Silva, who now works and Blue Sky Studios.
We wanted to test how far we could push the rig before going off model and explore how well the exagerated animation style would fit with the IP we were developing. Overall, I think the test was quite successful given we only spent 5 days on it in total.
This is a closer look at the first pass head geometry I built specifically for Project:Bystander. I created this head very quickly using old school modeling methods in XSI. The intent is to take this mesh, when all my code for the project is working, and bring it into ZBrush to generate normals maps and subsequently into TopoGun to retopologize before I create my final morph targets. For now, I just wanted to make something that looked decent in my Unity environment and gave me the option to do some quick shape tests.
Occasionally, I use Face Robot to test my head geometry structure before starting the rigging process. Like I have said before, the Face Robot process will teach you the basics of what makes a good head mesh. It also reminds you to keep your assets clean and organized. It’s a great tool to validate your work, refine where you intend to place your influences, and see the potential areas where you may have flawed geometry that could cause visual imperfections.
Believe it or not, this face was setup for facial in Face Robot in 15 minutes! The images above are captured from the default range of motion animation clip supplied by Softimage. These tension map settings are all default but the results are still awesome feedback!
You can see there is alot of detail around the eyes and ears. I also fully modeled the inner mouth. You may ask why? Yes, this is not optimized for a run time environment. I don’t want it to be. I do that at work. This is not for work. The end result I want from this project is to be able to create high fideltiy feature film ready assets on my mobile device and deliver them to a user via e-mail with the click of a button. Thus the project name “Bystander”.
Its been a while since I have had time to work but here is an update. This is all WIP. I will be posting alot of WIP asset images in the next few days but all contents will be BACK POSTED according to when each portion was completed. I find it valuable for my own knowledge, despite only working nights on this project, what order each phase occurred and how long approximately each step took.
I have chosen Unity as my engine for Project Bystander. I cannot express enough how completely amazed I am how easy it is to use Unity. I think for rapid iteration game prototyping it is super user friendly for both artists and programmers not to mention a technical artist. I can’t wait to share knowledge at work from what I have learned about Unity.
As I am still at heart an Artist first and a Tech Artist second. I found the need on this project to force myself into a rapid iteration mentality. Too easily could I get consumed with iterating on the models, textures, normal maps, rigging, animation, physics, shaders, and not to mention ui design. In addition, since it is so fast to implement features in Unity 3.5, I found it very easy to get excited and go off on tangents not related to the scope of this character customizer.
As I have explained in previous posts, my goal is to get the core functionality and bare bones art in place before I do any polish.
As you can see below, I now have a full character animated tracking a light source. The target controller assigned to the light source will later be assigned to the game camera which will be used to focus on the full body or head similar to the posted Eve videos. Note: I’ll be trimming the compression off the beginning when I have time.
In the days to come, I will also be posting modified Unity tutorials I used to accomplish what I have here. From these posts you will learn how to import assets, apply animation, and setup the head look controller just like the above.
I’m a big fan of the Metal Gear franchise as the character designs have always been solid. Even Deus Ex seems like it has some of Metal Gear influence but Metal Gear Rising is just too much for me. Seems like a cross between Ninja Gaiden and Naruto with a sword game mechanic like Dead Space. The characters still look interesting but the gameplay looks super over the top. Sad…
My largest influence for Project Bystander has by far been Eve Online’s Incursion Character Creation by CCP Games. I’ve set this not only as my visual bench mark but as part of my technical bench mark as well. Most character customizers have very similar functionality and features but the execution and presentation of this one in particular makes it my favorite.
For any project, whether it be renovating your house to making dinner, its important to have an image in your head of the end product and a list of what you need to accomplish that vision. Making a game or feature film is no different. These are some of the references I started with to generate a list of features for Project Bystander to determine scope of work. Beware, most of these videos are pretty long but when reverse engineering visually how other people accomplished similar results requires more information rather than less.
Project Bystander is strictly being developed for my own knowledge and not for commercial use.
By now, I’m well into the first phase of Project Bystander. So what is it? What is the goal? How am I gonna get there?
Project Bystander is my own version of a character customizer based on all the things I’ve learned over the course of my career. The end goal is to have a fully functional, and hopefully polished, interactive character customizer working on a platform of my choice. In addition, I want to be able to dynamically update the customizers asset library. Along the way, I will be blogging all major concepts and considerations to complete the project. Where needed, I will be writing scripts/macros to assist me whether they be in python, mel, maxscript, or vbscript. Eventually, I will also discuss platform features I will need to code.
Below is a broad stroke diagram of the project. I will break down each stage and my process as completed.
Follow Me