ID:1333092
 
Hey guys, me again, with another crazy tutorial. So, the other day I posted a screenshot that started a small argument. It was a picture of a first person world I made in BYOND.

I wanted to talk about how I made it, and why it's actually not a hard thing to do.

The first thing you need to do, is to get some graphics together. I'll teach you how to generate some graphics using a 3D modeling, animating, and rendering tool called Blender. It's free, so get on over to Blender3D.org and download a copy of Blender 2.67. (If you already have blender, make sure you have 2.65+, or else a shader I wrote for the cycles renderer won't work!)


Setting up your environment

Let's open up blender. The startup script will give you a light, a camera, and a cube to start with.

Just select everything by pressing "a", then delete it all by pressing "del" on your keyboard.


Now, you need to create a plane. Go to the Add menu at the top of your screen, and Add a plane (Add->mesh->Plane. Look at the bottom of the 3D view panel, and switch the Mode from Object Mode to Edit Mode.



Now, you will see your plane drop into the world. We need to scale up the cube to whatever size you are going to use for your images.

I use 80x60 for walls, and 80x80 for floors. Of course, you can use whatever size you want, but your resolutions need to fit certain factors.

For instance, my resolution is 320x240 for my entire screen, so I choose 80px for my floor textures because 320/4 = 80. I choose 60 for my wall height, because 240/4 = 60. I also choose a 45 degree field of view. The reasons for that are simply aesthetic.

Since I'm using 80x80 for floors, I need to scale this floor tile (which is 2 units by 2 units) by a factor of 40. While in Edit mode, select all vertices by pressing a again.

Now, press "s" to start the scale. Type "40", and then press enter. Your floor tile will have gotten pretty big.



You can zoom in and out by scrolling the mousewheel, rotate your view by clicking middle mouse and dragging, and pan the view by clicking middle mouse holding shift, and dragging it around. If you left-click, you will move the 3D cursor, which you can position to move where you insert objects, and you can use it as the origin of a transformation such as rotation.

Now, we need to reduce the size of the object by scaling it back down. Remember, the geometry of the object is what we are editing right now in Edit mode, so swap back to "Object mode"



Now that we have our plane Object selected, let's take a look at the properties panel. The properties panel should be docked to the right side of the 3D viewport. If you don't see it, press "n".

Let's reduce the X Y and Z scale of the object by 1/W, which in this case will be 1/80, or 0.0125.



Okay, we've got our floor object in place now. Let's start talking about texturing.



Shaders! Glorious Shaders!

Blender has two rendering engines. One is called cycles. We are interested in Cycles rendering, because we want to acheive a pixelized look on our textures. Normally, 3D objects are interpolated and anti-aliased when they are rendered.

In this next section, we're going to create a cycles shader for our object that will allow us to have an object that is always fully lit, and doesn't do any kind of interpolating.

Let's swap blender to cycles rendering by changing from Blender Render to "Cycles render" at the top of the screen.



Now, we need to talk about Blender's interface a bit. It's not friendly, but people who complain about it largely never bothered to learn it, and to understand why it's like it is. Blender is a complex tool that can do some AMAZING things. You probably will never use huge portions of the software, but it's all there for you to use.

You see these things all over the place?



These are viewports. The image in the little box is the current viewport this portion of the window is showing. You can drag on the edges of viewports to resize them, you can move them around by dragging on the title bar, and you can pull out new ones by clicking on the corners and dragging. You can find out what each viewport is called by clicking on the icon to expand the list.

Right now, the imporant viewports are the Information viewport (plays host to the menu), the 3D view, the Texture Viewer, and the Buttons View, which allows us to edit some extended properties about objects. Transform properties are edited in the tools panel, which is docked to the 3D view.

Let's click on the bottom viewport, which might be set to the timeline viewport. Let's swap that to the "Node editor" while our object is selected. Don't swap 3D view. We'll need to keep the 3D view active. Just the viewport below the 3D view, which is most likely set to the timeline view.



Now, expand the view a bit so you have room to work: You can do this by clicking between the 3D view and the top of the Node editor. You'll see your mouse icon change to a resize cursor.



Let's click on the material icon so that we swap the edit mode to Material node editing.



Now, check the "use nodes" checkbox on the bottom while your plane object is selected (in object mode!). Blender will populate some nodes for you. You can navigate this space just like the 3d view, using the middle mouse to move pan around and mousewheel to zoom. Go ahead and select all the nodes just like you did for the 3d view by pressing "a", and delete them all.

Let's start adding our nodes that we are going to need now.

First, we need to add two value fields. You can find this under the Add menu, under Input.



Now, look over to the right side of the screen for a small plus icon. You can press n to bring out the active node menu. This will allow you to edit properties about the node.

We need to change our nodes' labels to "X" and "Y", and then change the values of these input nodes to 80.



Now add a Texture Coordinate node. It's under Input as well.



Now we need to ensure that the engine doesn't do any blending of texture values by multiplying the texture coordinate value by the maximum width/height of the image, then adding 0.5, rounding it down, then dividing by the width again. This will result the pixel being rendered never being between two pixels, causing no colors to ever blend. This will preserve your palette perfectly.

But first, we need to split the X and Y values out of the UV coordinate from the texture coordinate node. First, add an RGB Splitter.



The RGB Separator will convert a Vector object to three numbers that you can easily work with. Why this works isn't really important. It has to do with the way that data is stored in 3D engines.

Now, we need to link the UV output (the purple circles) from the Texture Coordinate Node to the input (yellow circles on the left) Separate node. Just click and drag until a curved line links them up. Your X value will now be converted and stored in the R output (grey circles on the right of the Separate nodes)



Next, we need to do our math, so let's add a Math node from the Add->Converter->Math menu.

We need to copy it four times. Do this by pressing "shift+D", then moving it with your left mouse button.

For the first Math node, change the operation to Multiply using the dropdown menu.



The second node should stay as Add. The value of the node should be 0.5
The third node should be Round. The value of the round node should be 1.0
The fourth node should be Subtract. The value of the subtract node will be 0.5
The final node should be Divide.

We need to connect the R output of the separate node to Multiply's first input, and the X input node's value to to the Multiply's second output, and Divide's second output.

Multiply's output value will connect to Add's first input, Add's output into Round's first input, Round's output into the first input of subtract, and subtract should output to divide.

So far, your inputs should look like this:



Now, we need to do the same thing for the Y coordinate. You can select all the math nodes by either shift+clicking to select multiple, or by using the box select with the "b" button and duplicate them using "shift+d" again.



Now we need to add a Combine node. Add->Converter->Combine RGB



Now link up your divide nodes. Red to red, Green to Green.



Now that we have our coordinate system worked out, we can start on the second part of this shader.

The last part of this shader takes an image, and applies the lighting to it.

Lighting is usually based on a number of factors. We don't need lights in our scene, so I'm just setting all lighting to fully bright no matter the camera angle or normals.

I won't go into detail about how this part works, because I'm not fully sure myself.


We need:

an Image Texture node (Add->Texture->Image Texture)
a Light Path node (Add->Input->Light Path)
an Emission node(Add->Shader->Emission)
a Mix Shader node (Add->Shader->Mix Shader)
and a Material output node (Add->Output->Material Output)

Now, link the Image output of the Combine to the Image Texture's Vector input. The Image Texture node's Color output needs to be linked to the Emission's Color input. The Emission output needs to be linked to a shader input on the Mix Shader. The LightPath's Is Camera Ray output needs to be linked to the Fac input on the Mix Shader. The Shader output needs to be linked to the Surface input on the Material output.

It should look like this:



Your whole shader will look like this:



Now, you can open any of the textures you made using the Image Texture node, and it will apply the texture to the image, but there's still a little bit more work we need to do before it will look right.

I'm suspending this tutorial for a few minutes. The next post in this thread will contain instruction for getting your textures set up and mapped to the 3D object so you can actually see your turfs in glorious 3D.
UV Mapping

So what's that UV coordinate thing we were pulling data from earlier?

Well, in 3D modeling, UV coordinates are two-dimensional coordinates that vertices of 3D models use to map a flat image onto the face of a 3D polygon. You can assign bits of an image to the 3D model by specifying the coordinates of each vertice.

It's actually a really simple process, but it can be tedious if you aren't familiar with it.

So let's get started!


Let's swap the Node viewport you had open earlier to the UV/Image editor.



Now, head back up to our 3D viewport, and swap your plane back to edit mode.

We need to unwrap the mesh. Change your selection mode to Face select, and right click on the face of your plane.



Next, go to Mesh->UV Unwrap->Unwrap.



You will notice that the quad appears in your UV/Image editor. You can move the vertices around to make different parts of your image show up. You will want your whole image to show, so just ensure it goes from 0.0 to 1.0 on both axes:



That's all you need to do for now. Swap your bottom panel back to your node editor, and load the texture you want on this plane.



I've taken the liberty of making some textures here that we're going to play around with.



Remember that these are 3D objects, so you need to model the parts that normally wouldn't show because of your perspective. The door frame at the bottom left had a lot of nooks and crannies for me to draw that otherwise would have been invisible. The door and handle, on the other hand were pretty simple, so I was able to pretty quickly generate the five sides I need. You don't always need the top of models either, so you can shortcut your way out of those if they are always going to be flush with the ceiling.

Again, my dimensions are 80x80 for floors and 80x60 for walls.

Let's Just swap back to the UV editor. Now, change to the texture you loaded in the Node editor:



Alright, let's watch the cycles renderer in action!

Swap the 3d view's shading mode to rendered.



And this is our result:



You might want to swap to material shading in the viewport to continue working on your objects while you are modeling them.

Alright, so we've got the floor done. Let's make the ceiling.

Go to object mode on your floor object. In order to start editing your object, you will need to set the scale back to 1. When you are done, you want to set it back to 0.0125:



In edit mode with our floor selected, we need to duplicate with Shift+D the plane, and move it up 60 units. Press G to enable the movement transform, then Z to specify the Z axis to move the floor. Type in 60. Now, go back into object mode and scale the floor back down to 0.0125.

Now, let's pop back into edit mode. We need to create a new material and assign the ceiling face to it.

Head over to the right side to the properties viewport. Click on the material tab:



Now, click on the dropdown menu and add a new material:



This creates a material named Material.001 with the same properties as our original Material.
Let's Add back the original Material using the + symbol next to the material list:



You can rename the materials by clicking on their name in the original material datablock dropdown.

Now, with the ceiling plane selected ONLY, you want to click the assign button to assign the face to the new material, and the floor back to the floor material.

Let's head back to the node editor, and select the new floor material, and load our ceiling texture.


Now, you might need to adjust your UV coordinates by rotating them, or inverting them (scale X/Y by -1) in order to get your textures lined up right, but you should be pretty much done with your first 3D tile.


For the wall tile, you will want to build a cube, and delete the top two faces. Then make sure you scale it to 1x1x.75 (80x80x60) You will need to duplicate a new material in it, and assign the proper texture. Also, you need to set the maxY value on the shader we wrote to 60, or else the texture will look terrible.



You'll see here that I did something different with the wall. I split the left and right walls into three quads, then moved the textures around to make the brickwork look correct on a 3D object.


I also spent about an hour making a pretty nice little door object.





The geometry is all based on cubes where the pixels should be. I tend to make all the geometry first, then deal with the texture last. I use the UV project from view, and select all faces that are on one side. Scale them down to the right size, and then match them up with the right part of the texture.

It's a pretty simple process. You just have to get a feel for it.

That's about it for Part II. I'll be posting part III tomorrow night if I have time. I need to get some sleep before work as is.

Tomorrow, we'll be covering setting up your view cone and getting your images to render down to a file that you can easily import into BYOND.
Part III: The view cone

We have our models together, so now we need to figure out how to render them in the proper positions on the screen so that we can cut up the images and get them into BYOND.

Let's introduce our view cone:

=====      //=: back walls
..... //.: tiles
..... //O: Player's location
.....
...
.O.


This results in 26 tiles visible to the player at most.

Our viewport is going to be 320x240 for this demonstration, which is a classic DOS-era screen resolution.


The Camera

Cameras allow us to define a perspective for our renders. We just need to position it in such a way that our tiles render in the right place and the right angles.

Click Add->Camera



Move the camera to (0x,-1.50700y,0.375z).



Now, move into the properties viewport for the camera.
Switch the perspective to Field of View.
Change Field of View value to 45.
Change Clipping Start to 0.001
Change Clipping End to 6.00701




Switch to the animation properties.

You will now need to change the Resolution to 320x240, and turn Render and Preview Sampling down to 1. This will result in no anti-aliasing being done on the final render.




That's all there is to it. You can now render and save your renders with your image moved around the viewport in order to get your tiles.

I'm going to teach you guys a few shortcuts, though, because this is going to take a few hours per tile unless you find some techniques for automating the process.


Building an Armature:

Armatures allow you to move objects around during a render. We are going to use one to generate all 26 images for each tile with one click of a button.

Go to add->armature->single bone



Now, let's set it up. Go to the object properties for the armature. Name it "TileArmature" in the object properties menu. Plop it down into the center of your tile, and reorient the bone to be facing your camera.



Select the bone menu in the properties view, and rename the bone to "Tile".



Now, select your tile object, and get into edit mode. We need to get to the Mesh settings. Add a vertex group and name it "Tile". Now select the tile group, select all vertices in your tile, and click "assign" to make it so that all vertices in your mesh move along with the Tile vertex group, which we will be binding by name to the Tile bone in our armature.



Now, move to the modifiers menu in the properties view. Click on add modifier, and select "armature".



Set the object reference to the "TileArmature" armature object in your world, then make sure to check "Vertex Groups".



If you select your armature, then change over to pose mode:



You should be able to drag the bone around, rotate it, and scale it and watch your tile move along with it.


Introducing the Dopesheet: Keyframe animating

Make sure you have your armature selected, and in pose mode.

Go ahead and swap your bottom viewport to the dopesheet view:



Now, we want to be in the Action Editor.



Next, we need to create a new action. Name it "TileRender".



Now, head back up to your 3D view. Select the bone, and in pose mode, press "i" on your keyboard. This will bring up the keyframe insert menu. Press "location". This will add a keyframe a frame 1. You can move the keyframe by clicking and dragging in the dopesheet on the green line with the number at the bottom.



You will want to advance the frame count by 1, then move the bone into the next tile location, then move set a keyframe. Keep doing this until all 26 tile positions are covered.

This is the order you are going to move your tile in:

+--+--+--+--+--+  //XX = skip
|22|23|24|25|26|
+--+--+--+--+--+
|17|18|19|20|21|
+--+--+--+--+--+
|12|13|14|15|16|
+--+--+--+--+--+
| 7| 8| 9|10|11|
+--+--+--+--+--+
|XX| 4| 5| 6|XX|
+--+--+--+--+--+
|XX| 1| 2| 3|XX|
+--+--+--+--+--+


The very last few things we need to do, is adjust our scene so that it knows that the last frame we should render should be frame 26.



Now, scroll down to the output panel in the render settings. Change the output mode to .PNG, and the color settings to 8 depth RBGA. Increase Compression to 100%. PNG is a lossless format, so you don't need to worry about the compression messing your image up. (0% compression is 7MB per tileset. 100% is ~67KB per tileset.)

You should also note that you can change the directory from /tmp\ to whatever directory you want. I'd recommend putting each tile in its own folder, that way you have 26 images per folder.



Lastly, we need to make it so that our world background is transparent during rendering. Scroll down to the Film section and check "Transparent".



Now you can render your entire animation by pressing Ctrl+F12, or by clicking on the "Animation" button in the Render settings menu.

That's it for now, folks, the next section will be up tonight. I'm going to discuss the way we need to cut up these images in order to get them to show up, and I'm going to write a handy little program for you guys that will further compress the size of the icons that this generates by cropping the excess area from each tile.
Part IV: Rendering and cropping

Let's take a look at what our screen real-estate looks like after we've done some rendering and figured out how things should look:



I've taken the liberty of making the above image for you. This will help you determine the size of tiles, and the area you are going to have to crop for each tile.

We can start cutting this up into viewport regions now.

One thing that I would like to point out in my cropping process, is that I would like to keep half of the area of the tiles surrounding each section. This means that tile #1 will have a total width of 160 pixels, even though it only takes up a screen area of ~64 pixels.

The reason for this, is if we decide to include geometry that overlaps the adjacent tile, we can include that quite easily.

Below, you will see the data I mined from the render results:

Croppable area:
Tile 1&3: 160x240
Tile 2: 320x240
Tile 4&6: 160x192
Tile 5: 316x192
Tile 7&11: 63x120
Tile 8&10: 160x120
Tile 9: 254x120
Tile 12&16: 66x84
Tile 13&15: 160x84
Tile 14: 190x84
Tile 17&21: 94x66
Tile 18&20: 144x66
Tile 19: 152x66
Tile 22&26: 96x54
Tile 23-25: 128x54


Now let's talk about cropping. We need to remove the excess blank space in as many of these as we can. That means we are going to have to go into each tile, measure, and cut.

That's going to take a lot of time. We can cut that time down by using a program to do the work for us. I went ahead and wrote a little program that will handle that work completely for you.

Unfortunately, you may already have noticed, but there is a problem with the way that our renders are coming through that we need to fix. The back walls aren't showing, except one tiny little pixel in the center of the screen. The reason for this is somewhat obvious.

It's because the distance from each pixel on the screen is based on the total distance it must travel to the eye of the camera. The distance is the cubic root of(x^2+y^2+z^2). Hence, any pixel more than 6.00701 is not being rendered. This includes the x, y, and z distance. We need to find a way to ignore the x and z distance from the camera, and only calculate the y distance.

First, we need to increase the view distance of the camera. Go ahead and increase the view distance to 100. Now, let's open up the shader we wrote earlier and make some changes.

What we are going to do, is read the raw camera coordinates of each pixel we render, and set any pixels that render outside of our desired y-coordinate to render as completely transparent.

You'll notice that your texture coordinate node has a Camera coordinate on it. Let's hook that into an RGB separator.



Now, let's get the global Y coordinate from the camera. It's important to note here, that the camera is always facing "down". This means that the global Y coordinate will be the "Z" coordinate of the camera for our purposes. So, create a Greater than Node (It's a math node), and hook up the Separator's "B" output to the first value. Set the second value to 6.00701.



Now, we need to modify the output a bit here. Let's move over to our output section.

Create a new Mix Shader, and put the output into the first shader input of your old Mix Shader. Now, Create a "Transparent BSDF" Shader, and put the BSDF output into the second slot. Now set the color of the Transparent BSDF to {1,1,1,0}, which is all white, with zero alpha. For the "Fac" input for the new mix shader, link the Value of our Greater than node to it. This will make it pick between the fully lit texture color, and the transparent color based on whether the distance from the camera on the z coordinate is greater than 6.00701.

It should look like this:



And your whole shader will now look like this:



Overwrite all of your old shaders with this node setup, and you should be able to see all of the tiles up until the front wall of the backmost tile, and nothing beyond it.

Re-dump your renders, and now we're ready to move on to proper cropping.


Using RayCropper:

Now, I've made a nice little tool for you guys that will help you in cropping your images automatically, and inserting them into DMI files.

You can download it here.

Boot up RayCropper:



This is the main menu. You can select a folder to load from.

Note: You should output your renders in this format for 1-directional icons:



And this format for 4-directional icons:



Currently, RayCropper doesn't support 8-directional, animated, or multi-tile graphics. However, it does allow you to import and export graphics of any size, as well as save/load cropping templates.


To import a set of renders, just type the path of the containing folder in the Location box, or select the folder after pressing the "..." and using the file browser:

Now, type a state name for the icon state that this graphic will use:



Click on the process button, and let it do its thing.

Now that you have processed an image file, try clicking on the tileset menu, and opening up the Tileset Editor.



The tileset preview has tools that will help you in getting your game's tilesets ready for the actual game, and quickly.

In the top left, you will notice that there is a black box. That is the preview window. It will show you what your tiles look like when they are all pieced together and put on the screen. You can zoom in and out using the radio buttons.

On the right, you will see a list of all currently imported icon states. Click on them to switch between them.

On the bottom left, you will see some buttons for handling preview visibility and direction. Click on the boxes to make tiles visible or invisible, and click on the arrow icons to make tiles face different directions.

Last, you will see the Import/Export buttons. Import a set of files to append the icon states to your currently imported tiles, and export will generate 26 .dmi files to a folder of your choice.



This is the layout editor.



Select the tiles on the left to edit the cropping offsets we determined earlier, and this is how each individual frame of your render will wind up being sized down.

No changes are saved until you press Update, and then Apply. You can also load and save layouts for later use. Just use the Layout menu at the top of the window.

That's pretty much it, folks. Just process your renders, then export them using the preview tool. Once you export your files, you should be able to copy them right into your project folder!

Also, you guys can download my 3D environment Here.
Reserved for Part V: DM
Reserved for Part VI: Improvements
Reserved for Part VII: If needed
Reserved for Part VIII: If needed
Reserved for Part IX: if needed
Reserved for Part X: if needed
That door looks like it was a pain in the ass to make. It's a pretty bizarre way of putting together a mesh. It might be easier to extrude the door mesh from plane with a heightmap and then do some detailing work on top of that.
In response to D4RK3 54B3R
D4RK3 54B3R wrote:
That door looks like it was a pain in the ass to make. It's a pretty bizarre way of putting together a mesh. It might be easier to extrude the door mesh from plane with a heightmap and then do some detailing work on top of that.

You'll see why I do what I do as I get to the visual effect of the final result.
Now tell us how to actually do first person without raycasting
Raycasting in BYOND is pretty damn sexy, Bumblemore. I, for one, am impressed.

If you're still feeling down on the concept, just write a raytracer. They're probably easier to implement. Shoot a vector from an imaginary eye through each pixel on the screen, see what it hits, that's what colour it is.
In response to Jp
Jp wrote:
Raycasting in BYOND is pretty damn sexy, Bumblemore. I, for one, am impressed.

If you're still feeling down on the concept, just write a raytracer. They're probably easier to implement. Shoot a vector from an imaginary eye through each pixel on the screen, see what it hits, that's what colour it is.

Flash is significantly faster than DM. Even so, with the some serious optimization, I've been unable to get raytracing working in Flash without a pixelbender pipeline running at more than 120FPS. And that's with only a few billboards being cast into the world in addition to the texturing.

Textured raytracing just isn't a winning algorithm for BYOND's particular toolset at this time. Perhaps when client-side image and screen manipulation are worked in, it will be doable, but for now, it just isn't workable for anything more than a small tech demo.

As for you, Bumblemore, I'm getting to it. There's a lot of prep work that goes into making this easy. Once you get it set up, though, getting this into DM is fast and easy. It's just going to take some time to write the tutorial up and teach you guys how to use blender to render your needed graphics in a time-efficient manner.

I'm sitting down to work on Parts III and IV tonight, so stick with me.
Sorry guys, I got bogged down on Part IV last night. It's almost done. I just need to finish writing the cropping tool. The cropping tool I'm writing is about 50% done. I have the graphic batch-importing and batch-cropping working, I just need to get the exporting, preview, and dmi merging working.

Will work on it tonight, and then we ought to be moving into the actual BYOND portion of the tutorial by this weekend.
Raycasting +1. I look forward to reading the rest of this.
Just finished writing the batch cropping tool, and updated the shaders a little bit to circumvent a problem I didn't account for.
Fantastic stuff! Looking forward to the rest
You said "advanced" not "insane".

Not sure my insurance is going to cover the mental health breakdown I just had from attempting to absorb that much information.
Page: 1 2