Reverse Engineering "Ultima VII" Isometrics: WIP
The weeks' Friday Challenge is "homage", and the first thing that came to my mind is the unique style of Ultima 7. A few days ago, I chatted with Bart on IRC about generating 2D-sprites from 3D-model, and thought I'll put 1+1 together...
...adding up to a PITA. The graphical style of Ultima 7 comes from both the perspective as well as the rendering. The perspective is a -135 oblique projection (thanks to the folks at Exult... I fiddled fruitlessly to get isometric to work); turns out that no major 3D modelling software supports this (non-)perspective. Oblique projections are used in the ol' days for design/architecture, and organic forms are pretty hard to draw accurately. (In U7's case, the severe distortion make it very hard to see if things are drawn correctly...) The workaround is to have a camera with proper perspective pointing at a far, far away object that is appropriately offset from the scene you wish to render and xy-shift the line-of-sight. After major fiddling, you end up with more-or-less the right perspective (I got sick of it when I was ~2-3 degrees off).
The rendering itself is mostly lit from the NW (top-left) direction, except for external architecture, where the shading is even throughout. All images are outlined in a 1px black stroke (there are exceptions). It's also very pixelated, but I think we don't need to emulate that aspect :)
An example follows. Apparently there is a group that is trying to recreate/extend the original U7, so if nothing else, this stuff could be of interest to them!
Couldn't you achieve this with an orthographic camera?
No 3D camera can produce what's in that screenshot. Orthographic basically means items don't get smaller as they get further from the camera. In this Ultima VII style, planes parallel to the ground are rendered without distortion but planes perpendicular to the ground have that 135 degree angle.
What might be possible is making 3D models as usual then using Shear transforms on the models themselves to approximate the desired shape.
This seems to work. I took my Broken Tower and sheared the model the same amount on the X and Y planes. Here's the Diablo isometric view compare to the faked Ultima VII perspective using Shearing.
Good thinking pfunk! I tried shearing the isometric output (which looks strange), but I haven't thought about shearing the model. That would make accurate adjustments much easier to make.
And now that you've put up an isometric and an oblique side-by-side, the latter looks bizarrely distorted. Once upon a time, I spent many hours looking at this world with a strange perspective... be it any wonder that I look at the world with a strange perspective today? ;) I wonder how the artists at Origin deal with 10,000 of these without much to work with back in the days...
I'm not sure that this is truly impossible with any 3d camera, as long as you allow for orthographic cameras and some intereststing transformations. That view there is actually just an oddly scaled and rotated isometric view, which is possible with an orthographic camera:
Bart, they are not fully convertible with only 2D transforms -- your solution is similar to pfunk's, although it requires a Z-compression on the model as opposed to a 3D-shear (actually, I like yours better - it's immediately obvious how to set this up with little/no experimentation). The scaling can't (?) be done on the 2D level and maintain the isometric angles (is this true?). In any case, this is quite cool -- it means that there's direct, automatable workflow from 3D to this flavor of 2D, via a combination of blender scripting and batch image operations.
I just noticed that I didn't mention it in the first post. The outline generation can be done at render time in Blender, or stroking the render in PS. I did the above with blender, since I wanted to stroke the internal contours as well; but if it is to be scaled non-uniformly as part of the post-processing, stroking in PS at the end is the way to go.
Start with a scene in 45 degree isometric. Video game style, where the camera angle is Blender (60,0,45).
In Blender if you look at Buttons Window -> Scene -> Render Buttons -> Format, you can set the render aspect ratio. Set AspY to half of AspX. This is the same as taking regular rendered output and scaling X by 50%. If you rendered a cube, the top of the cube will be a perfect square (though at a 45 degree angle).
We can then use Blender nodes to rotate the result 45 degrees. The output:
Note this started as a cube, so there's a lot of "vertical" distortion. So you might have to scale meshes to 50% Z before using this method. Also notice the Edge seems to be applied after the Aspect, so the edge isn't distorted.
Blend file: http://clintbellanger.net/images/temp/UltimaVII.blend (I'm a Nodes noob so there might be a smarter setup).
For kicks, here is that tower again. I pulled it into the above workflow scene and scaled Z by 50%. Click "Re-render this layer" on the first node to create the composite.
When it comes to orthographic perspectives, there's loads of variables and variations, especially in the technical drawing field. Games use only a few of the options available. Anyway, one of the big differences between these two views is that Ultima has 2 of its world axes parallel to the view plane, while the standard 2:1 isometric perspective has only one of its axes parallel to the view plane (its vertical axis)
pfunk: You're the man :) There's so much I need to learn about blender!
pfunked I am working on an Ultima 7 like RPG Construction Set as speak...but I do need couple of sample tileset to work around them. Is it possible pfunked you can make one single wall, door, one table and one tiny book sprite to work with? All I will simply constantly use it to work my engine around it for future graphics. If you want my email address you can find it at amiga500@gmail.com, thanks in advance.
amiga500, hopefully this will give you a starting point for development. http://opengameart.org/content/perspective-walls-template
I've spent a long time working with games in this selfsame perspective (more in the tradition of Tibia, though, which blatantly borrowed and stole from U7), and as such I've seen a lot of discussion on this topic. Probably the most thorough (if wordy) explanations I've seen was here: http://forum.phobosonline.com/viewtopic.php?f=8&t=4815 if anyone is interested in looking at it more. Though to be honest it's not too hard to get the hang of the perspective just through practise.
And I must apologize for this sorta necro post, I've been away, but this is the one topic I know anything about... xD
Pfunked wanna help me add your nifty camera to Briswak? Diablo isometric i meant :) if you dunno how just tell me the specifics and what not. I will probly try editing my file and making it at that angle. :D Oldschool diablo here we come! And if i cant figure it out ima cry.
Just curious but why would systems not be able to make a camera at any angle you want it? This stuff does what its told if told correctly. Just wondering I don't know much about GUI/camera's.
www.tkodo.com
TheKingofDemons: I suppose I can offer some advice.
Is it a 3D game (e.g. OpenGL) or 2D?
If 2D, are you using 3D models to pre-render 2D sprites?
This perspective is definitely easy to use if you're doing pixel art, but 3D rendering is obviously tricky. As you can see by all the above gymnastics.
It is a 3d direct x c++ game. trying to make it as fimiliar to diablo fans as I can and this camera is a bangin step.
www.tkodo.com
If you want a Diablo camera the settings are easier. Put the camera at angle (x,y,z) = (60,0,45).
If you want a pure engineering-schematic style Isometric view, Put the camera angle at (x,y,z) = (54.736,0,45) and set the camera to Orthogonal.
Heres the code what do I change lol. :D
void IsometricCamera::setPosition(float x, float z, float zoom) {
static const float SCALING = 0.5f;
D3DXMATRIX a, b;
D3DXMatrixTranslation(&a, -x, 0.0f, -z);
D3DXMatrixScaling(&b, 95.0*SCALING, 63.0*SCALING, 9.0*SCALING);
D3DXMatrixMultiply(&a, &a, &b);
const FLOAT SWIZZLE_MATRIX[] = {
1.0, 0.0, 0.0, 0.0,
0.0, myTiltWorldFlag?(-0.67-1.0599993) : 0.0, 1.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0,
};
D3DXMATRIX swizzle(SWIZZLE_MATRIX);
D3DXMatrixMultiply(&myLocationMatrix, &a, &swizzle);
const FLOAT VIEW_MATRIX[] = {
1.0, 0.0, 0.0, 0.0,
0.0, -1.0, 0.0, 0.0,
0.0, 0.0, -1.0, 0.0,
0.0, 0.0, 791.0*SCALING, 1.0,
};
const FLOAT PROJECTION_MATRIX[] = {
4.414213, 0.000000, 0.00000, 0.000000,
0.000000, 4.414213, 0.00000, 0.000000,
0.000000, 0.000000, 1.14282, 1.000000,
0.000000, 0.000000, -571.408*SCALING*zoom, 0.000000,
};
myViewMatrix = D3DXMATRIX(VIEW_MATRIX);
myProjMatrix = D3DXMATRIX(PROJECTION_MATRIX);
D3DXMatrixMultiply(&myViewMatrix, &myLocationMatrix, &myViewMatrix);
D3DXMatrixIdentity(&myLocationMatrix);
}
Conveinently I think this is already set I am testing it not but mabye I broke it lol. So if anyone knows what I need to change in this please let me know.
www.tkodo.com
Obviously this thread is old but I did come across the same problem in my own quest to match ye olde Ultima Online VII angle.
In Cinema4D I managed to get it close enough but with C4D its next to impossable to get pixel perfect precision given there is no such thing as pixels.
I managed to achieve this by using a Perspective Camera and then moving the camera to the upper/left so its off center...then using Camera settings move it back x/y to give it a "central" render focal point. Then using Photoshop I compared the renders with a screenshot of the game to line up the entire process.
Obviously to make this happen in a 3D game itself would require Code like the above but if you're interested in rendering out sprites in this camera angle then this could also be done via C4D.
You can get the C4D settings here - https://dl.dropboxusercontent.com/u/2884044/CameraTests_0002.c4d
UOTest.jpg 97.8 Kb [185 download(s)]
In case it is useful for anyone, here is how to achieve the effect in povray:
#declare Distance = 100;
camera {
orthographic
location <1,-1,-1>*Distance
direction <-1,1,1>
right 8*x
up 6*y
}
light_source {<1,-2,-3>*Distance 1}
box {-1 1 texture {pigment {rgb 1}}}