Description
Key Learnings
- Learn how to create procedural content with 3ds Max software’s Max Creation Graph
- Learn how to author real-time VR in Stingray
- Learn how to set up fast updates and alternatives for rides review
- Learn how to enhance VR experiences with environments and animations
Speaker
- LMLouis MarcouxLouis Marcoux has been a technical expert for 3D animation, visual effects and real-time rendering at Autodesk, Inc., since 2003. Prior to Autodesk, he was a real-time broadcast graphics specialist for 5 years, working with Discreet Logic and VertigoXMedia. Marcoux received a bachelor’s degree in electrical engineering from Polytechnique in Montréal, and he also holds a Bachelor of Communications degree from Université du Québec à Montréal and Bachelor of Fine Arts degree in film production from Concordia University. Marcoux has been awarded Best Speaker at Autodesk University 3 times. For more information, you can go to: http://area.autodesk.com/louis
LOUIS MARCOUX: All right, so thank you everybody for skipping the keynote this morning. I was told it was going to be the best keynote ever at AU. So you're all going to miss it. So thank you for coming. My name is Lou Marcoux. That's pretty much enough you need to know about me because we need to focus on the class. I got tons of content, and going to focus on it.
So this is a very long name for the class. What I submitted to the company was VRIs and MCG. So I got it extended to a paragraph. So that's-- now we're going to talk about virtual reality procedural experiences with 3Ds Max, Max Creation Graph and Stingray.
So basically what I want to do during the class is kind of defining my objectives at the bottom there. We're going to look at MCG to create procedural content. And in the end, we're going to bring it in Stingray to experience it in VR. And I'm going to use the roller coaster project that I've been working on for a while.
Some of you may have seen some people, like Matthew Doyle, doing a video on YouTube on how he used it. What I'm going to cover today is completely different. We're going to dig into the Max Creation Graph, the thinking process on how to build procedural content, and how we can-- what we need to bring the Stingray in order to create the VR experience out of it.
How did it start for me to work with the Max Creation Graph? Basically, in August 2015, one of my colleagues was presenting to the CTO Council for game developers at SIGGRAPH. And he said to me, I'd like to be able to present these guys-- the Max Creation Graph was just out. It was 2015 last year. And he said, I'd like to be able to present to them a procedural tool that would allow them to very quickly create a racetrack, be able to redefine the path of the racetrack, add a few settings to make it automatic, like the width, the curb size, and all of that.
Also, he wanted it to be flexible enough to be able to add the correct decoration on the sides, like lights, barriers, things like that. But he wanted everything to be interactive. So that was the project that I built to learn the Max Creation Graph. It was a very fun project. It was a learning project for me, but it's also the kind of tool that you can build for-- with the Max Creation Graph.
This was my first project. And after that, we used it for multiple contexts. We used it at AU last year. We built an experience for the [INAUDIBLE]. Someone built [INAUDIBLE]. So we used the vehicle template in Stingray. We've drawn a path with it, and we use this tool to decorate the environment.
So that was my first project. And believe me, after this project, I was like, oh my god, I need to do a post-mortem, and rethink how I build things in the graph. Because I approached it the hard way. And after that, I was like, I need to rethink all of this. I had a big spaghetti of nodes. It was very hard for me to go back to these nodes and trying to figure out what I did. So what I did was I stepped back, and I said, what are the building blocks of this, and how can I create blocks or components that would be reusable and allow me to reuse in a different context and be more flexible?
So that's kind of-- after that project, I was like, I need-- this is kind of my thinking process. And I came up with a few recipes. And what I'm going to talk about today mainly is, we're going to use a roller coaster project as the base for the class. But it's-- going to show you my recipes. I'm going to give you my recipes. And I'm going to give you enough material for you to go back home and redo what we're doing here. I'm going to cover the high-level concepts.
And if you stay until the last slide, like any other classes I've given at AU before, on the last slide, there's a big surprise. I've recorded a lot of content for you to go back home and try the things that we are going to do today. You will have access to everything that I'm showing and also detailed explanation to go a bit further.
Because in one hour, to build a roller coaster, it's impossible. So we're going to go over the concepts, the tools, and then you're going to be able to go back home and try them yourself. But nothing that you're going to see on screen today will-- sorry, let me rephrase. Everything you see on screen today will be available to you after the class. So those will be my recipes.
Before we get into the actual technicality of it, I want to define procedural content because everybody has a different definition. My understanding of it, of procedural content, is we are going to use procedural tools to build or to model something inside of 3ds Max. So for me, procedural content is a meaningful system. It's something that I can describe with very few settings-- second line-- very, very few parameters, but it allows me to define something that's meaningful.
The drawing of a forest there kind of represents that. If I am in 3ds Max and I start, I open a new scene, and I want to create a forest, I will go into the Edit Poly. And will start to model a leaf and then a trunk, and then I'll probably copy that a few times around the tree. And that's what I call manually creating the content. But in a procedural kind of thinking process, I want to define a forest to be-- this is about this length and this width, this wide. I want it to be about this high, and I want to define a few parameters and generate a forest.
So procedural content is with very few settings, be able to create relationships and cloning and transformation, mathematical functions, that will define all of the internals of building a forest so that at the end, you can only define a few things and you get a forest inside of your scene. So our goal with procedural content is to create simple tools that within the production will have a large influence on your scene. Like I said, if you have to model the forest leaf by leaf, you're going to be working on that for a week or two. But if you have to just give it a length and a width and a height, you'll be able to create it much faster.
So you can also create conditions, that if you are in the winter, the forest doesn't have trees, it doesn't have leaves. So in my country at least, we don't have leaves during the winter. So that's the kind of stuff that you want to define procedurally. There's different tools that you can use to work procedurally. You can use MAXScript. You can use C++. You can write C++ plugin plugin.
At Autodesk, there's Dynamo, which also a good procedural content creation tool. If you look at SideFX Houdini, there's a node graph in there where you can also build procedural tools. But in my case, I'm using the Max Creation Graph because I'm a 3ds Max artist. I've been using Max for a long time now and this is what I use. And this, I started to use it a couple of years ago when it got released with 3ds Max.
So what is the Max Creation Graph? The Max Creation Graph is a system to build procedural tools. And it allows you to build the packages that will be available in 3ds Max. In Max, if you're a Max user, you're probably familiar with the concept of wire parameters. So you have a node and you connect the exposition with the scale of this thing. So it has nothing to do with that. So visually, you could think that oh, yeah, we're wiring things, and it's a wire parameter tool.
No. What we're doing in here, we're taking inputs from the users. We're going to pick nodes inside of 3ds Max. We are going to connect nodes to create or to modify these things. And we're going to create an output. And this output could be a primitive, modifiers. You can create your own modifiers. You can create your own controllers. There's a lot of things you can create more than this. But mainly, the graph will allow you to create tools that you can package for use in your production.
So that's kind of the idea. And when you're going to look at the graph, you'll see that we have a lot of nodes, a lot of compounds, a lot of functions that you can use to process the data, to change the data, to create custom primitives, and output that through the graph. So let me just show you very quickly how the graph works. So the graph is in scripting. If you go to Max Creation Graph, open the Max Creation Graph window. When you open it for the first time, it's empty.
And when you create a new project, it's always good to give it-- define who's the author for it and give it a category. The category will define where this tool will reside in the 3ds Max interface. So the category here, we're creating a primitive. It's going to have its own category in primitive, It's going to be called AU and this is where your tool is going to land. So I'm giving you the category. And when you save it, you give it a name. And that's also the name that I'm calling it-- AU Box.
It's funny, I was in the Eric Craft's class yesterday. And he was giving a class on MCG, and he's doing a box as well. So you'll be able to create boxes when you go back home if you've seen the two classes. So-- OK, now we have an empty graph. To create a box, we need to define how is it going to create the box. So on the left here, you have all the nodes. They're about 1,000 nodes that you can use for creating content in the Max Creation Graph. And yes, I'm going to cover every single one of them before the end of the class. No, I'm not.
I'm only going to use the ones that we're going to-- but what I recommend, and I still haven't learned them all, that there's-- you search and then you find something that makes sense. You bring it and you try it. And it helps you. But there's no way to cover-- and, like I said, I didn't even study all of them yet. So we have about 1,000 nodes. When we want to define what type of tool we want to build, we want to look for the output.
So we have a few outputs there. We have transformation controllers, compounds. What do we want to do is we want to create a box. So we want to create a custom geometry. When you press the X key anywhere in the user interface, it brings the search window as well. So you can search directly from there. So I'm going to search for create box. I'm going to find it. And now we have a node that allows us to create a box.
The create box has an output called TriMesh. TriMesh means it's a triangular mesh. This is the raw triangle of data that creates a primitive. So this we can connect it to the output and we now have a tool to create a box. It needs input so we need to define a width, a height and a depth. So we can drag out the width. And when you drag out like this, it shows you only the parameters or the nodes that are compatible with this input.
So we know that it's a singles. We're going to look for a parameter single. And a parameter is something that's going to be visible in the user interface of this tool, which means that now I'm going to define the name for it. It's going to be called a width. I give it a default value. And this is now what I'm going to see in the user interface to be able to provide the width.
The lazy way of doing this, you can right click, and at the bottom there it says generate parameters. When you say generate parameters-- you get all your parameters created automatically. You can set up the default values so that they are meaningful. And now we have a tool that creates a box. You evaluate it. You look for it in the category called AU. And now we have AU Box and we can create box.
We support everything which means that AutoGrid is also accessible. So we can use all the Max tool with this new primitive that you've created with the Max Creation Graph. What's interesting is now we can create a box. And you're going to say, wow, I could create a box in the Max user interface? Yes, good point. I'm glad you bring it up. The idea is that now that we have this box within the graph, we can do other things with it, and then we can start to build something that's a little bit more complex.
So in this case, I'm going to create a new node called the Transform Mesh. The Transform Mesh is your best friend because it allows you to take a mesh, transform it-- translation, rotation scale-- and output the transform mesh. So what you can do is you can bring it into the graph. If you hold the Control key as you drag it into a line, it automatically connects. So it's a very nice workflow to add nodes.
As I said, when you drag out of a node or an input, it shows you only compatible things. So it's going to look for the translation matrix. I want to add a translation matrix here. Then the translation matrix needs a position. The position is defined by three values and generating parameters. And now I'm using Control-Alt to move this. But the idea is it's very quick to start to build tools.
The thing about the graph as well is every time that you have a description of a mesh, you can take that mesh and you can combine it so that you can have multiple versions of that mesh. What I mean by that is that right now we're outputting the transformed mesh as the output. I'm going to connect that. And I'm going to use a tool called Combined Mesh. And this Combined Mesh tool allows me to bring in two meshes together for the output.
So what I'll do is I'll just grab the transformed mesh, and I'll grab the original mesh and connect the two together. So now I have two cubes that will be translated from one to the other. So if I look at the parameters, now you have a bit more parameters. So I translated an x, but I can change the width, the height, and the depth of the box. And we've created a system of two boxes.
When you start to-- you see we only created two boxes, but already we have a big spaghetti in front of us and it's hard to find. Where did I do this box again? So what I recommend is when you're done with something, create a group. You can color code it. Give it a name. And you can even put comment in the group so you know that now this is where I create the box. I can-- if I go back to this graph, I'll be able to find where it is, where I did create the box.
But now let's go to our real project, which is not a box but a roller coaster. So I want to show you the thinking of how we're going to build a roller coaster. This is an image that I took from the internet. So I have absolutely no rights for this. So-- but the thing is when I approach this-- when I finish the racetrack toolkit, I said, I want to do a roller coaster. That's going to be fun. And I can reuse some of the stuff that I've done. But I need to have reference. So I went on the internet. I look at a couple of images that are typical of roller coaster. And I chose this one, and that was my reference to do pretty much everything that I did for the roller coaster.
So what do we have here? What do we need to build to generate a procedural roller coaster? We have the rails. Yeah, the rails will have a size the distance-- I want to be able to change that. I don't want to be stuck with the rail that the system is going to give me. Same thing with the central beam. It has a size, it has a distance from the main rail so I want to be able to adjust that. I want that to be flexible.
Then we have the ribs. Some roller coasters have a lot of ribs. Some have less depending on the structure. But this is also part of the system. We have the section joints. They had little-- every-- you know, it's kind of a-- its a detail that stands out when you look at roller coasters is those joints. And I wanted to be able to see them as I built my roller coaster.
Then we have the pillars. One of the main thing is they need to not be in the way. So I can not put pillars only under my roller coaster because when they start to twist, I want my roller coaster to be able to go through and not have pillars in the way of the roller coaster. So also the pillars will have a size. Some roller coasters will have more. Some will have less. They are all based on the ground. So we have to take that into consideration.
Then we have the train. The train is made of wagons. It follows the track, hopefully. If it's safe, it's going to follow the track. It has a traction at the beginning of the ride and then after that it's all moving based on gravity. So I need to take that into consideration into the system as well.
To define where the roller coaster goes, that's something I've learned as I was building. We define what we call a heart line. The heart line is where your heart goes through the ride. And then you define the up direction for everywhere on the roller coaster. So if you want to make it twist upside down, you will need to define the other direction.
So my goal is to create a system where I can adjust any of these parameters. But I just want to use the heart line to define where the roller coaster goes. And I want to use an up line to define the up direction of my roller coaster. And I want it to be connected to the ground so that all the pillars, all the support, will be done automatically. So as a user of this tool, I'll create a couple of lines. One for the heart line or for the up. And then we're going to use a ground with that and it's going to be our system. And then the rest will be all adjustable with parameters.
So now that we know the project, we're going start studying how we are able to create a custom mesh. We're not going to do a box anymore. We are going to create our own mesh with an MCG. But before that, we need to understand how we create a mesh procedurally because we're used to 3ds Max, where we're going to create a primitive, add flows and edges and rings and all of that. That's interactive modeling. But we're not doing interactive modeling. We are creating the procedural tool.
So when we look at the rectangle procedurally, if I use Max script, it's going to be the same kind of thinking, or C++ plugin, as I write my own thing. It's the same thinking. So you have a rectangle. It's made of four vertices. And it has two faces. So I need to describe that to the system. It's good to start with numbering the vertices because you need to make some connections or correspondence. But the concept is always the same. So we start by describing the vertices. We define the position in space. So if I want to create a 5 by 5 rectangle, that would be the position in space of the four vertices that I'm using to create the space, this rectangle.
Then we need to define the face and where they point forward. So if we want a face to point towards us, we need to define the vertices that will create that face. And we need to define them in the order counter-clockwise so that it's pointing forward to me. If I wanted it to point away from me, I would say clockwise, define the vertex order.
This is what we call face indices. So face number one is defined by 3, 2, 1. The 3, 2, 1 vertices. And indices for the face number 2 is 3, 4, and 2. And it's counter-clockwise. That's how we defined this. But that's not all because a lot of that stuff, a lot of what I'm going to talk about now happens automatically in Max. If you create a rectangle in Max, it's going to generate UV Map, smoothing group, material IDs. If you do it procedurally, you need to define it manually as well.
So we're going to define smoothing grooves for all the faces. We need to define material IDs. And we need to define UV Map for every single vertex. Where is that vertex in the UV Map space, the one-by-one space that defined how a texture will be applied to the rectangle.
So if we want to do this in MCG, a single rectangle, simple rectangle of two triangles, this is how it would be done. You would create the output to geometry like we've seen. We would use a node called Create Mesh. Create Mesh accepts vertices, as we saw, and indices. So what we need to do is that we need to define an array. We have four vertices so we're going to use an array of four. And we have two faces, so we're going to use an array of two. And we then need to feed that with the data about the vertices and the faces so we can use Vector3 input. Vector3 in the first case, is the position of every vertex. And in array number two, the Vector3 is the three points or the three vertices that will generate the face.
But all we have now is the description of the rectangle. Now we need to define the set smoothing group. Again, we had a new node. We send a smoothing group with an array, and we define the array. Like we would say, smoothing group one or two. And you would define that as constant.
Then we define material IDs. And then we define the UV maps. Then we get what? A rectangle. That's a lot of work. But if you look at the simplest component of my roller coaster, it's a bolt. A bolt has 36 vertices and 50 faces. That's a bolt that I've modeled in 3ds Max very quickly. That's the bolt that I want to use in place around my roller coaster so that it all holds together.
So I have to replicate all these nodes to create the bolts. And believe me, I've done it with the racetrack tool kit. This is how I learned it. And I was kind of junior using the graph and I've build faces one by one. And it was very time consuming. And I said, never again. So I'm going to share with you a recipe that you can bring back home to make it in a very much simple fashion.
What I discover in the graph, there's a tool here called Split. You provide it with a string, and a delimitation character. Does that say delimitation? That exists? And what it does, it creates an array of values based on your string. So I can use that to create vertice-- the vertices description. So I can provide a string, and it's going to put an array. And my array could be the description of my vertices. Oh, I'm saving a bit of time.
I apply the same context for indices, Mat IDs, UV Maps, and Smoothing Groups. And I was able to build a single compound that allowed me to input a string, and it outputs a mesh after. So this node here is now your best friend. It's called LM String to Mesh. LM is for Louis Marcoux, that's me. It's mine. But I'm giving it to you as part of the distribution file. And also I have recorded a small tutorial on how to install it on your system. But if you are serious about using the Max Creation Graph and you want to hard code some geometry as part of your tools, you can use this simple tool and you can output the geometry.
So the way it works is very simple. It's very simple now. But at the beginning, it was not simple. The idea is you provide it with a string that describes all your mesh. And it processes it, and it outputs the geometry that you want. In the graph, if you want to write that script-- well, not that script, but that description-- you use the constant node and you go in and you type all of this manually.
And I got bored by this very quickly. So what I did is I built a script that I called Get Mesh Info. So you select the mesh in 3ds Max that you've modeled. You just run this little macro script. And it puts the string into the clipboard. You can paste it and then you have your description for your mesh.
So for the roller coaster, this-- I'm going to use it a lot today. That was my best friend. And then like I said, I'm giving you both the macro script and this so that you don't have to ever think about anything else than this. So this is now the-- my recipe for you to create custom mesh and combine them into the graph.
So let me show you very quickly how we can use this. So this is the bolt here. I select it. Then I run the Get Mesh Info. Once it's run, the data is in the clipboard. I use the constant node. And all I have to do is just to use Control-V to paste the string. And I'm not going to read the string. I know I trust it. I've tested it a few times. It's going to work. And now I connect it and I'm looking for LM String To TriMesh. And I know that it's going to generate the TriMesh. I'm going to output that as a geometry. We compile it. It's in the AU. And now I've got an AU bolt. I've taken the model. And we have-- and it supports the auto grids so I can put bolts anywhere in my scene.
But now that we have this in to the Max Creation Graph, what's going to be interesting is we can now start to build systems of bolts. So creating one bolt is cool, but we want to create systems of bolts so that we can make a bit more complex system. So I'm going to use a tool here called Combine All Meshes. Combine All Meshes accepts an array of meshes. So I'm going to create an array of meshes by using another node, called Clone Mesh at Position. So Clone Mesh at Position takes the input of the mesh, and then it takes an input-- an array of position in space. And it's going to clone that mesh everywhere on that.
So instead of creating the array manually, I'm using another node called Circular Arc of Points, which creates a circle of points. And it has a count, a degree and a radius. I'm just generating the parameters, automatically compiling this. And now what I've got is a tool that allows me to put bolts on a circle very quickly, define how many I want. And you can do some interesting things with it already.
If you think about it in the context of the roller coaster and how the roller coaster holds together, we can put this at every section. It's very few nodes, and we get that into the system.
So system of components. This is what I just did. I created one component, and I create a system of them. And we have a ring of bolts. So let's take a look at the problem of a pillar. So I want to create a pillar. And if you look at the pillar, it could be represented by a set of a few primitives. I want to be able to change the height. So if I change to height, the ladder will change. I'll have more steps on the ladder. I'll have maybe more sections. I want to be able to adjust the size and all of that. But if I want this system to be procedural, I want to narrow it down to the simplest description as possible so that someone that creates a pillar just have to enter a few parameters, and we have a pillar.
So I came up with this conclusion. The size, how big it is. It's going to influence a lot of things like the base. It's going to influence the size of this and the size of the ladder. The total height, where is it that it needs to be to support the roller coaster. The base height. This is the concrete part here. And the section length is how far these sections are apart from each other.
Once I've got that, I think I can build the pillar. So if we look at this, we can create a small primitive for the base. And if we scale this up and down and sideways in the width and the length, I can create pretty much any base I want, and I can find out the right proportions when using those parameters. Then I can put the anchor, which is that part over here, I can just put it on top over there. And I'm able to put it in the right place. And again, scale it so that it fits to the top. Then we can put bolts to put it all together. I can create the main pole by just using a simple cylinder. I can create a section using the same type of idea. Then I can have a very simple primitive that represents one step in the ladder. Clone it multiple times to the top, and scale it as needed.
And then I have this little primitive for the base of the support there. Like I said, I face it on the reference that I had at the beginning. So I'm trying to replicate it as much as I can. So you start by using all of these simple primitives. And by transforming them based on relationships here, I can create the pillar. So that's kind of the thinking process when you are building something procedurally.
So as you can see, transformations are very important. And when you want to use small primitives to create complex system. But in the 3ds-- in the Max Creation Graph, the transformation are very picky. If you are transforming things inside of the user interface of 3ds Max, you pick an object, you decide if you rotate it on its local pivot. If you want to scale, is it going to be scaled on the local pivot?
But in the Max Creation Graph, when you click with the mouse to create your primitive, this is becoming the local space of the object, and this is 0, 0, 0. Even if in world space it's at 10,000, it doesn't matter for the Max Creation Graph when it's creating a primitive. The local space is always always centered at 0, 0, 0, which is where you clicked to create that object. So all of those transformations are based on that location.
That has significant impact on your transformation. Because let's say that I have an object here-- well, let me use this. That's my pivot point. I create it here, then I translate it, then I rotate it, and then I scale it. And it goes away from me. So if we do the transformation in the wrong order, we get really bad result in its unpredictable results. So we always have to remember that when we modify things or when we transform a mesh inside of Max Creation Graph, there's no idea of parenting. There's no concept of arbitrary pivot point. You rotate everything at 0, 0, 0. You scale everything at 0, 0, 0. Everything happens at 0, 0, 0.
Therefore if you want to be successful at transforming meshes inside of the graph, you need to do the transformation in the following order. First, you scale so that you can scale without distorting your geometry. Then you rotate it at the origin, and then you translate it to bring it at the final location. And because you have to do all these things, you have to think about how to do them and all of that, there's a lot of mathematics involved.
So you'll have-- if you want to do these types of transformation, yes, you'll have to open your college books, and you'll have to learn mathematics again. And you'll probably say, ah, I don't want to do this. And-- but the reality is yes, you have to learn a bit of math. But in the handout, I've given you three links for refreshing, I think, the most three important concepts that I use for pretty much everything when I use the graph. The rule of three, which is very useful when you scale objects and you want to maintain proportions, or that you want to scale to a certain factor, the rule of three is very useful.
Algebra. So vector algebra, the normal of a surface where a vector cross a plane. Things like that. And I've given you a link to the formulas that you need to use. So you can just follow that link and use that as a reference. And also trigonometry is always useful, cos tangent, and all of that. When you do translation, place object in-- and you want to give them a position based on an angle that's always good to refresh.
So let's take a look at quickly how we can use these things together. So this is my basic primitives for the pillar. I'm not going to build everything in front of you. I'm just going to show you the high-level concepts. So I've just grabbed the mesh for this, the mesh definitions. So I'm using a constant, pasting that in here. I'm using the LM String to TriMesh. And then I'm going to create a geometry out of that.
And I want to define a transformation so that I can transform this object. So I'm going to do it in the right-- in the wrong order first so you can get an idea. I'm going to use a transform mesh node. This is your friend when you want to transform a mesh. This needs a matrix. I'm going to use a translation matrix to start. It needs a Vector3. So I'm going to grab the parameters and quickly name them so that they have a unique name in the user interface.
So all of this node system is for the translation. I'm going to do the same thing for the rotations or transform mesh with a rotation matrix with the proper parameters. And we have now the parameters to rotate the mesh. And we can do the same thing for the scaling using a translation-- sorry, the scaling matrix and all of that. We name it correctly.
So now we have three groups. The groups for translation, for rotation and scale. Now we can start to connect those transformations. So I am using the base mesh on the left there that we grabbed from the Max modeling. I translate it first, then I rotate it. And then we scale it, and we output the result as the geometry. So we save that. And if we use the tool, I called it Pillar Parts. Go click there. We have the 0, 0, 0 so we can translate it from that 0, 0, 0. We can rotate it. But immediately when you start to scale it, you see that it becomes tapered. It's distorted. It's not exactly the transformation that we would like.
This is because we've done the transformation in the wrong order. So I'm just to cut all of that and reorder my transformation to first do the scaling. So it scaled at the center. Then we rotate it at the center. And then we translate it and we bring it to the final position. So we're going to get much better results when we do something like this. So if I do now scale it up in any direction, and if I do rotate it, you'll see that it's going to rotate in place. This is the right way to do these things.
So now I'm going to grab the second mesh, which is the anchor. I'm going to grab the mesh info. I'm going to paste it in a constant. Same idea. So I'm going to paste it here. And I'm lazy, so I'm just going to copy all of that. And I'm going to connect it to this. So we have the string. We have all the transformation. We only can have one output per nodes. I'm going to delete one of them. And then I'm going to use the Combine Mesh tool that we've used before, combine these two together. And now we have a primitive that is based on these two.
I need to name everything uniquely, so what I'm doing for the second part here, I'm just adding the anchor prefix. And now we have a new set of parameters. And you see that now I can put those two in relationship to each other. I can scale it up, rotate it, adjust the base and all of that.
But now, what we have is a whole bunch of parameters. And if I want to create my pillar, I would have to do this manually. Like all-- and adjust all these parameters for my primitives. This is not what I want. I want to start making connection. I want to simplify this. And this is where the mathematics comes in. Mathematics will allow you to take the scale of the sky and connect it with the position of the sky, and depending on the height and all of that. So that's where the mathematics comes in.
Simple example, what we're going to do is the first node system is the base, and the second is the anchor. So we're going to take the scale of the base, and we're going to define the zed position, or z position, of the anchor. So that's what I'm doing here. I'm removing one parameter from that whole list. So I'm going to go into the scale there. We're going to open up the scale in zed. I'm taking this output. And I'm going to connect it to the zed position of the zed translation of the anchor.
You can compile. And now you can see that when we do scale up, it's adjusting, but it's not to the right factor. The base has a height of 3. So we need to scale it correctly. So this is where the real-- the rule of three comes in. I need to multiply this by 3. And I'm saying 3 because I know it's 3. But you would have to do it for your own calculation here.
So if I multiply the translation by a value of 3, it's going to be correct. So just adding a constant, putting a value of 3. And here we go. We have our final tool. So now if I do scale the base, you see that anchor will be on top. So if you continue this for a long time, and you make all these connections, you can create a pillar primitive. And the pillar primitive as only as you can see on the right there, four parameters.
So by using those four parameters, as you'll see there, I can change the size, I want it bigger or smaller. So just the scaling, the transformation, the total height, the base height. You see that the cloning of the ladder happens how it needed. The section length defines how many sections do we have. And those-- everything that's done here is just transforming these objects in relation to each other. So define this on paper first, how you can create this. Define your components, and that's how you can build something like a pillar.
Now that we have a pillar, we also need to build the rails and the ribbons and everything that's related to the ride itself. So we need to work with splines. And working with splines in MCG is cool when you want to create a spline. But when you want to interpret the spline to-do tools, it's a little bit different. But we're going to use the spline to create to loft along for the beam and the rails. And we're going to use the spline to also clone the ribbons with the proper transformation along the splice.
But the thing is, when you bring-- when we pick a spline, and we analyze it in the Max Creation Graph, we don't have access to a lot of information about that spline. We can extract. We could have access to the construction of the spline, how many vertices it has. But for us, it's not important. That's not something we care about. What do we care about is the position and the percentage.
So when we query a spline, yes, we can get a position at a certain percent on the spline, and that's it. You don't get anything more. There's no more data out of that. So if you want to know in which direction it's going because you want to create a rotation at that point, you have nothing. So what you need to do is you calculate the position at a percentage forward. And from that, you can deduct the direction vector for this.
Then the problem is, we have no up. We have no idea-- there's no notion of up or orientation when you're using a spline. So what we need is a second spline. And that second spline is kind of the copy-- you can use a reference. You can use anything you want. But you want to look up to find where-- at the same percentage, to find where is the up direction. So if you use that second spline, you can go and say, at this point, it's going to be upside down, then it's going to twist back up, and it's going to twist upside down. And you can bank it, and you can have a second spline to define where the up direction is.
Once you have that, because we have vector algebra in our back pocket, I can deduct the right vector. And now I have a coordinate system that allows me to rotate and translate for any point along the spline. So again, I'm lazy and I don't like to do this mathematics every single day and every single time. So if you want to clone multiple person values, what you would do is you will lay out your mesh and the origin. We have the ribs so we can just model it at the origin. And then we can scale it, depending on the size of our roller coaster. Rotate it, translate it, and it's going to be in the right spot.
I don't want to do this manually. So I did it once, and I created a note called transformation matrix on curve. That's a node. That's part of the distribution files. I'm giving it to you. You can use it. It's super simple. And what it does, it takes as an input a position curve, an up curve, and a percentage value. And it creates the matrix that you can use in a regular transform mesh.
Those 3, x, y, and z values, they're just offsets in the local transformation system. And it doesn't apply to the roller coaster, but for the racetrack toolkit, I wanted to be able to place in the local space or at a different percentage objects on the side of the road. And that's what I'm using here is I'm just moving these objects at the local transformation system at this specific point. But in our case, it doesn't apply, so we'll leave it at 0, 0, 0.
And we just feed it to a transformation Max. And then we'll get the proper transformation to bring our objects in the right position and orientation. If we want the clone it multiple times, there's a tool called map. Map allows you to feed in multiple percentage values. And say at every of these percentage values, you are going to put a mesh or a clone of that mesh. And we're going to get a lot of meshes and all of the clones along the splice.
So this is the tool that we're going to use. And then for lofting along the spline. The same idea applies. You put vertices along that spline based on this transformation, and then you create faces around those vertices. Again, I don't want to do this manually every time. So I created a node called Loft Mesh on Curve. And it's part of the distribution files. So what you need to give it is, the mesh-- the mesh you need to model it in Max starting from ground zero and z up. And once you've modeled it, it's going to use that as your lofting mesh for this.
So you just provide a mesh like this, the number of segments that you want on the loft. And the same-- I am not very consistent when I name stuff, but position curve is the same thing as flow spline, and up curve is the same thing as up spline. It's just I didn't do them in the same time frame, and I didn't remember how I called it. But the same idea applies. So we use these nodes to create the splines.
Very quickly, again, we are going into the graph. So I'm going to grab the ribbon-- not the ribbon, the ribs, the ribs. And I'm getting the mesh information and pasting it into a constant note feeding into the TriMesh. And this is outputting just that single geometry. If I want to transform it, I'm going to use a transform mesh node as usual. It needs a matrix as an input. My LM Matrix is compatible with that. So LM Transform Matrix on Curve. This is what I need. I can create parameters manually, or again, I could just say generate parameters, compile, save. And now in the AU, we're going to get that primitive.
So I add it to the scene. I pick the position curve. I pick the up curve. And then you see that this object can be moved along the spline. And you can even change the spline. It's all going to be dynamic. It's going to be placed accordingly. So that's-- all I would build also a controller, and I'm using it a bit later. But this is how I would create a control or an animation controller that would follow the spline. All it needs is a matrix. We can just apply that transformation matrix, and it's going to bring it for the right percentage.
So if you want the clone it multiple times, like I said, we need to use the map tool. The map tool provides an array of meshes. So we need to combine all of them together to produce the final mesh. So that's why we use the Combine All Meshes here. When we want to apply a function because we want to create multiple mesh, you see there's a function connection there, so I'm going to connect it here. So a function connection means that somewhere in my incoming flow, there will be an empty input. And that empty input will be the parameters that I'm going to use to create these clones.
So in this case, the map, I'm going to use Range node. The Range node, what it does is it creates multiple values between 0 and 1 that are all equally spaced. So it's all float values. So if I want to add five values, it's going to go 0.2, 0.4, 0-- da, da, da. So all you have to do is to give it an amount of values that you want to get, and it will give you multiple values between 0 and 1.
So that can be used as percentage values. This is why I'm going to delete the percentage parameter, because it's no longer a fixed value. So now what I've got is the amount. So if I increase the amount, I can have multiple clones along the spline, and I can offset them in local space if I want to. And all of that is fully dynamic. The splines can be changed, and you see that the ribbons-- the ribbons-- why do I say this word all the time? Ribs.
So now I'm getting-- so I'm just going to get ahead of myself. The rails here, I just extracted two cylinders on the z up axis. I grabbed using the Get Mesh Info. I'm going to paste that again using the constant nodes or creating the constant node. Paste it here using the LM to TriMesh. I now have this mesh that is at 0, 0, 0. I'm going to use my node called Loft Mesh on Curve. I'm providing it with this mesh, combining this mesh with the other mesh that we had. And we feed it as the final output. All of the rest of the input, I could generate them again, but I could use some data that's already in my graph.
So the segments, I could say, well, the amount of ribs that I have could be defining the number of segments that I've got into my loft. And the flow spline, we already have the position curves. I'm going to feed that in here. And then we can feed the other one here at the up spline. And we can run this. And now what we have is a system where we have two splines, and it kind of creates the rail. It creates the ribs. And we-- it's fully interactive and all of that.
So we now have the pillar and the rails. We need to finish the tool. And when I finish a tool like this, what I try to do is to organize it in a user interface that makes sense. So I will group, like, the rail values, the central beam values, the number of pillars, and the parameters that you've seen before, how many segments. I try to make it make sense for the user, so then when I'm going to use it, it's going to be parameters that they understand. Just polishing it up.
And then I also created an animation controller. And the animation controller, the only thing I did is, you saw the transformation matrix. I just feed it as a transformation matrix output for the animation controller. And now I have an animation controller that allows me to put a train on those tracks without any efforts.
So this is kind of the idea. This is the tool that I've built. It's called the-- I'm not consistent when I create nodes, but I'm consistent in my naming convention. Because the role of the racetrack toolkit, I call that RTTK. And the roller coaster toolkit, I call that RCTK. So you have access to both. You can install them on your system. I've given both of them to you.
So this is the roller coaster system. I'm going to try to move it a bit faster here. You'll find it in roller coaster toolkit. You add it to your scene. It creates this little helper as a starting point. And then you pick your track. You pick your up line. You pick your ground. And then you have your, what I call the proxy version of the roller coaster. This will be very interactive if you have the full detailed version, If you have a smaller roller coaster. But when we started to build the final one I'm going to show you at the end, we wanted the ride to last for one minute. So one minute creates a big roller coaster and lots of details. So if you use the full detailed track, it was very hard to manipulate. So that's why I added this option at the top called proxy track, so that it would only create rectangle shapes. This is what allows you to go in and say, you know, I want you to modify and change it and make it upside down. And you can define the whole system. This way, it's very interactive and it is very responsive.
Once you've got that all done and you've got your flow, your up line is all defined, you can turn off proxy tracks. And then you can refine your mesh. I can see that and I don't have enough ribs, so I can add more ribs. It's very jaggy, so I want to increase the number of segments so that it looks good. I can change the rail distance, the rail size. The bean, I can put it further away or closer from the rail. Also you can see that sections there, you can add more. So it adds nice, little details on the track.
And you see also that the number of pillars will define how many pillars you have on the track. And little subtlety there, you see that here it's banking, so it puts the pillar on the side rather than underneath so that it's not in the way of the pillar. And same thing, we have a section of the roller coaster there that goes upside down. So when it goes upside down, it puts two pillars side by side, and we can always make sure that the pillars are outside of the way of the train. You have that graph. If you want to reverse engineering and look at the mathematics and how it was done, you can all open it and go into it and go and look at that if you want to.
The last thing we want is we want to animate this wagon on the track. Right now there's no animation on it. So we're going to go into an animation controller. I've created a controller, like I said, with the Max Creation Graph. It's called RCTK Transform. You have that controller. It's one node. You'll see it's just feeding in the percentage, and it puts it on the correct track.
So that's kind of the idea. And as you can see here, if you move the percentage, it will follow. And if you go a bit closer and you change any of these splines or anything in your system, everything will be updated automatically. So it's a very nice system to build, iterate, change things, and build a track.
So because we've already done an animation controller, what I'm doing here is I didn't want to create the key frames manually, so I added a little simulation tool at the bottom. So when you press Simulate, it just creates simulation based on gravity. You can provide changes to the gravity if you want to. You can also give it a minimum speed. So that's kind of the traction speed as it goes up. You can make it smaller or bigger. But when you simulate, it creates the whole animation for you. So when you press play, you have your-- this is now the minimum speed. But you'll see that as it gets to the top there, it's going to start to accelerate with gravity. And you have your ride within seconds. And again, you can change the ride if you want to if you don't like it. You can copy this, and just by changing a little upset in the percentage, you can have very quickly a train of animated objects that will follow along the ride.
So the two last things that we need, and that's where we need to-- that's what we need to go to Stingray and to build our VR experience, is we need a point of view into this. Where is it that we're going to be when we're going to look at this ride? So if we're using the Oculus, we need to have a camera or a point of view. And we need a camera there. So what I'm doing is I'm using the same controller on a camera. So you'll see that the camera will follow as well. I just placed it with offsets in the right position. And if we want to build it for the HD C5, the HD C5, we are interested in where the ground is in our system. We want the tracking space to follow that grounds. So I just added a rectangle here. And that rectangle I'm applying the same transform controller on it. And it's going to follow that the track. And I'm calling it Ground Car because I want to be able to reuse that later as my tracking spot for the roller coaster.
So we have a point of view, and we have the ground. So now we're going to talk about real time VR. And we're going to use Stingray to bring all of this into a real time environment to be able to experience it in VR. Why do I say real time VR? We could-- in 3ds Max, I could decide to take that camera and render a 360 video. And that could be a VR experience. But if I do this, I'm not able to look above the wagon. I'm not able to bend forward. I don't have any parallax effects because it's just a single point of view. If you have a Samsung Gear, you can render this out of 3ds Max, and you'll get your 360 video.
But what we want is we want to be able to kind of move our head around. And that's why we want to use a game engine so that we can fit in the tracking of the Oculus [INAUDIBLE]. I'll show you the two techniques. So entry in Stingray, we have different templates. In Stingray, I'm using 1.5. And I'm specifying 1.5 because there's multiple versions of Stingray and the templates are different. So if you're using 1.2, it's a different set of templates. So I'm using a template in 1.5.
You've got two templates when you start a project in Stingray that are related to VR. One for the Oculus, one for the Steam. Eventually, it could be just a single one. But because the two of them are treated differently and they need different type of inputs, we created two templates. But I feel like I'm in the VHS beta kind of world where we don't know which one is going to pick up. And we better adapt to both.
And we're going to have a VHS copy of our film and a beta copy of our film. But one day, I'm pretty sure that it's going to be one thing, and it's going to be VR, and everybody is going to kind of share the same thing. Hopefully, I hope. Because it's-- now that we have to prepare for everything, it's on the same drivers. Sometimes they are not compatible so you have to-- anyway.
So for the Oculus, we do support both the Rift and the DK2. I, personally, have a DK2. I know I should upgrade. But I didn't get a chance to do it yet. But we support both. So if you're doing-- if you're using the Oculus template, it will work for both. For this team, it's the full system. It also has the positional tracking so we support the whole thing.
If you are in the Oculus world, this is what we call in place or seating. You have a central point of view, and you cannot move around a lot with the Oculus. That's changing right now. They're changing this, and they're-- now you can connect multiple devices for tracking, and they're going to have the controllers and all of that. So those two are a competitor, so we should expect them to be at par kind of as moving along.
But right now, it's mainly in place. And we're going to approach the problem differently because what we want to do is we would attach it to a point of view, and we already created a camera in 3ds Max. That's the point of view that we want for our VR experience.
For the Sting, like I said, it's room scale, which means that you can walk around. And what we need to define is where the ground is, and that's why we did this little plane in our system. We wanted to find where the ground is, and we wanted to follow. So how do you bring our content that we've just done into Stingray? It's pretty simple. But what we have to remember is every time we export from 3ds Max or we send to Stingray from 3ds Max, no matter how many objects you have selected, you do it at once. Stingray will interpret it as a single unit. We call it unit, but it's kind of a simple, single element inside of Stingray.
So if I grab all of my environment and I export it in one FBX file, when I'm going to import it, it's going to be one thing instead of Stingray. Which is great because it allows me to not worry about having to import all of these things separately and place them. It's just going to be one thing and then it's going to be static. It's going to stay there. I don't have to worry about it. So that's the thing. Every time you export, it's a single entity inside of Stingray.
So we can export it in two ways. We can send the assets. There's a Send To in 3ds Max that sends it to Stingray. And the import process happens automatically. And you can update and all of that. That's working for static assets. So environments, the props, the roller coaster. Everything that's static, you can use the Send To-- no problem.
But if it's animated, then you need to export as FBX. Why? Because when you animate, if it's a single animation, you can store the animation in FBX. You can decide to bring the animation if you want to. There's a few decisions that you have to make when you export. So that's done through FBX.
And we also have a game exporter that allows you to export multiple animation from a single time line. So if you want to a character, and we're not going to do it here, you know, walk forward, turn right, turn left, jump, run. So all of these can be done into a single timeline. And we can use the game exporter for that. But in our case, we have a single animation. So we're just going to export using FBX.
Then once you have everything into Stingray, you want to make the connection with the head-mounted display. And it's pretty simple. We're using Flow. Flow is a node-based system, another node-based system inside of Stingray. And that's what we're using to make the connection. So if we're using the Oculus, we have a node called Set Active Camera. What it does, it takes the Oculus and brings it to the point of view of the camera that you selected. If the camera is animated, the camera is going to follow, no problem.
So that's the only node we need pick. We just pick the camera, feed it our camera that we created in 3ds Max. We feed it as the active camera. We test. If the Oculus is connected, we're going to be in VR in Oculus. For the Vive, we are going to have to set the tracking space continuously. It's a bit different, because now we need to define where the position of that rectangle is because the tracking space is the whole space.
So we need to define where the position of that space is, where the rotation of that space is. And the scale, we're just going to leave it as is because we're not going to scale our scene here. So we just need to define this, connect the two, and update it all constantly. And our ground or tracking space will follow the train. Then when we want to play the animation that we're bringing in, we have the Play Animation Clip. And we just fit in with the unit on which there's an animation, the name of the animation which is the name of our file, and then we can play it.
So let's see this in action. And so that's going to be perfect. We're going to finish right on time. So this is the Stingray template. We have the Oculus here, and I'm going to show you the Steam after. So if I'm grabbing the Oculus template, we create a project. We define where it is going to be on our system. We give it a name, very important. And then we create the project.
When we create a project in Stingray, it gives us a default level with nodes in there. So you don't want to use that, you just select all these nodes, and you delete, and now you have an empty level. The thing is that here-- the if you look at the shading environment, what I'm doing here is I'm turning off the fog because my roller coaster is way bigger than the room. So when it appears, it's all in the fog, so I always turn it off.
And then I bring in the models. So I created in the Asset Browser of Stingray, you can create folders. So I'm just creating a folder there, and that's where I'm going to put my content. I'm just-- I'm going to grab the environment all at once, connect to Stingray, and then say Send Selection. Already, Max knows where your current project in Stingray is, so it knows where to save this. I'm going to save it in the folder that I just created. Stingray receives a warning. You just say Import. And now your environment will be in there. I'm dragging it here and I'm putting it at 0, 0, 0 so that it matches where it is in 3ds Max. And if I want to line up multiple things, I want everything to be put at 0, 0, 0. So if I bring things separately, they are going to be aligned.
Then I want to bring my roller coaster. And the reason why I'm bringing it separately is if I want to create multiple versions of the roller coaster, if I want to update the roller coaster, I can update just this part. I don't have to update the environment every single time. So now I'm just going to say Send Selection. It's going to send the roller coaster. I'm going to call it Track 01, import it. And then I'm just dragging it here, and put it at 0, 0, 0. ,
But let's take a look at what we can do as well. Now that it's in place, that we've done some logic on it, whatever. But it's there in our scene. We can go back to 3ds Max, grab it, and change it. Say, well, I want that to bank a little bit more here than, say, update. And you'll see that in Stingray, we get the immediate update of this, even if it's in context. So that if you want to iterate your ride and try different things, you're going to be able to do that as well. So now, I just did undo. And back to the ride, and we're good.
I'm selecting all of the animated objects there, so the train, the camera, and the ground plane. Export, export selected, as an FBX file. When you do export, I do turn on bake animation. Bring the camera, embedded media and all of that. So make sure that everything is turned on. And then in Stingray, you need to manually import it. This time, it's going to bring the camera. It's going to also bring the animation. And we say import.
And now you'll see that our train is in there. We do put it at 0, 0, 0 as well. We have an animation there. So we're going to just drag it, put it at 0, 0, 0 so it aligns. And you see that my rectangle there is visible. I don't want that to be visible, so what I'm doing here is I'm just selecting the Unit Editor. I'm selecting the Ground Car Plane. And I'm just turning off visibility and cast shadows, and now we don't see it anymore. We're good.
Now we need to create our connections in Flow. We have our unit, the train that we brought in. It has a camera. So I'm going to say Get Unit Camera. Get the camera from that unit. The camera is Camera 01. That's the only one we have. And then we are going to pick up the Set Active Camera node. That's the main one that we need. Connect the camera, connect the unit. When is it that we want that to happen? It's when the level is loaded. That's it. We're good. We have the Oculus set up.
Now we want to play the animation. So we have-- we're going to use the Play Animation Clip. Connect the unit that has the animation. Define when it's going to be played. So I'm going to say just whenever the space bar is being pressed. So I'm going to say spacebar, press, connect it to Play, and it's going to play that animation. If you go back here, test the level. If you have an Oculus connected, you are able to look around in the Oculus. And if you press spacebar, it's going to start the ride.
For the Vive, it's pretty much the same process. You create a steam project. You define where you want to put it on your system. You give it a name. You bring all of-- and when it creates the project for you, it shows you the default level. You can delete all of this. If you want to have the original sky dome and all of that, you can just create a new level. It's an empty level. But again, the fog is on so I'm going to turn it off. We have the same contents. I'm going to bring that in, put it at 0, 0, 0 everywhere.
So it's the same process. Nothing different there. But where it's different, it's in the level flow. What you have to do is you have-- when the level is loaded, you need to go to the Steam VR and grab the tracking space. We are going to set it at the beginning. But also we're going to set it at every single frame so they always follow. So when this is there, we can now grab the train unit. This is where the rectangle is. We are going to get the world position of that rectangle.
So we're just going to connect it here, connect the position. And we pick that ground car. And now we have the position defined. Same thing for the rotation. Connect it here with the ground car. And the scale, we're going to leave it at 1, 1, 1 because we're not changing it and we're not changing the scale. Same idea. We play the animation. We define where-- when we want to play that animation. In this case, I'm going to grab the Steam VR trigger. And we're going to connect it here. Trigger number one, but it can be trigger number one and two. And this is now ready to go. If we press test, and if we have the steam VR connected, we are going to be in VR with this.
So this is my last slide here. I'm done one minute late. But in the handout, you'll see there's a Dropbox site with a password. Only you have that password. What I've put there-- it's not on the AU site because there was a lot of content and it didn't fit on the AU site. So I've put all of the MCG-- all of the MCGs are there. I've put the two builds, one for the Vive, one for the Oculus. So if you want to just try it at home for fun and you don't want to learn any of what I've told you today, you can do that.
I have recorded video tutorials with very deep explanation, step-by-step, of everything I've built today. So if you didn't catch everything or every subtlety, don't worry. You can look at it at home. And there's about two hours and a half of videos that you can watch to re-go through that process.
I knew that I would not have time to cover alternatives and animated props, so I added videos to show you how to switch between multiple rides that you bring in. So it's a very simple flow set up, so that I'm showing you that. Same thing if you want to animate with textures and all of that. I've created a few animation tutorials for you guys.
So I'm going to show it-- as you walk out, I'm going to show you the final project. I want to say, thank you, to Pixel Nauts. The reason why I'm saying thank you to these guys is I'm very good at doing this procedural stuff and mathematical stuff. But then I go to on the next project. And when you are in VR, the first thing you notice when you're in the VR is that yeah, the train is cool but show me something around the train. So I had nothing around in my environment.
So we are this company called Pixel Nauts, and they build a nice environment around the roller coaster. So now as you ride a roller coaster, it's fun to look at stuff. So they've done a really amazing job. We showed it at GDC in our VIP event. People loved it. And my colleague, Matthew Doyle, incorporated sound and added some triggers so that you have birds. And you go through birds and stuff like that.
So I'm going to show you this as you walk out. But thank you for your participation in the class. And I'm just going to run the EXE. And if you want to see it in VR, right now, I'm going to walk to the Intel booth. As you walk into the exhibit all, Intel is right there on the right. And we have the roller coaster ride set up with the Oculus, so you'll be able to ride it if you want to.
So I'm just going to show you the final result here. And I want to thank you very much for attending the class. And for missing the keynote.
[MECHANICAL NOISES]
So that's it. That was a procedural roller coaster in Max.
Downloads
Tags
Product | |
Industries | |
Topics |