ok.. i'm trying learn much more about 3D... but i don't find a nice tutorial about it
for start i need understand how can i draw: X,Y,Z to X' and Y'... yes the screen don't have the Z coordinate.
can anyone explain to me or just give me a tutorial for i learn it?
Last edited by joaquim; Mar 30th, 2020 at 09:36 AM.
If you do a search like "3d coordinate to screen", are you saying the first few hits are not "nice" enough tutorials or explanations for what you need?
I would think that if one of those is not sufficient, I'm not sure what you might be looking for, and I presume I certainly wouldn't provide any better explanation.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
thanks i wasn't using the right words.
before i start more: let me ask you... the screen uses X and Y.... so how can i convert 3D(X,Y,Z) to 2D(X,Y)?
for i start something
Well, that is what the first link that was presented when I did the search was explaining.
Of course there are a lot of illustrations and math discussion involved in the subject, so it is easy to be overwhelmed. I don't think I can make it any easier.
Many years ago, I was a bit interested in investigating some simple 3D projections myself, so the examples I was looking at at the time were written in plain old GWBasic or QBasic, and would draw simple wireframe versions of a collection of points, and you could spin those points around the three axis.
I later reused those same calculations in a VB3 program to draw a Rubic's Cube type cube, and was experimenting with rotating some of the layers to possible allow a full rotational model of all the layers allowing replication of rotating the cube as a whole and the various layers to emulate the manipulation of the cube as you would a Rubic's cube, but I lost interest in that before getting very far.
I added other things to the program to test things like rotating a sphere of dots in addition to the points that made up the junctions of the cube pieces. Also, changing the color of the dot with the resulting Z value so that the dot faded as it was further from the eyepoint.
I added various checkboxes to enable or disable various portions of the drawing process, so you could see various things without the others, e.g. the cube by itself without the cloud of dots around it, or the cloud of dots without the cube, or drawing a circle inside the cloud of dots, so that it looked like dots were passing in front and behind the circle that was in the center of the sphere.
It was just a mishmash of various experimental pieces of code, so there is a lot of commented out code in it, as well as unlabeled buttons and other GUI elements.
The functions that rotate the points to calculate the new X,Y and Z points from the original X,Y and Z points after rotation, are essentially unrolled versions of what happens when you run the points through a matrix transformation. The preferred method would be to use a Matrix because a number of rotations or other transforms could be applied to the matrix, and then the final matrix would be used to transform the points once, moving them from their original definition, to their new position in one shot since the matrix would be a combination of all the transforms needed to go from original position to current position.
Once you have the points all transformed, then the rendering needs to convert the resulting 3D points into 2D points for display, and that is projection part of the process. And there is more than just determining where the point on the screen is. There is the matter of determining which points should be drawn and which should not, and in the case of surfaces, which should be drawn and which shouldn't. And with a better drawing algorithm, depending on how the surfaces may overlap because of the Z depth of the surface changing across the polygon being filled, the existing polygon should be broken into smaller polygons at the point of overlap.
The code I used in my tests were quite simplistic compared to what should really be done to draw things correctly.
I ported the VB3 code to VB6 long ago, and I have a several versions of it hanging around on various computers.
I guess I can post one of the VB6 versions, as is, and you can play around with it. I can't really afford the time to get back into what it does and how it does it, but maybe you can pull something from it.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
Not really.
To draw the left side properly, two things would need to happen.
The first is Y scale needs to have fore-shortening, which means you have less Y scale the closer the object gets to you.
So, the Y scale should increase as the left side goes further away from you, which means the bitmap should shrink as it goes away from you.
If you're looking at the box with the front face being perpendicular to your view, and you're viewing the box with the eye level centered on the front face, then the left side should end up being a trapazoid shape.
Since you don't have fore-shortening, then the left side looks like it is increasing in size, if your mind can accept that the left side of the image is further back, then the fact that it didn't shrink in size on the screen is interpreted as increasing in size in 3D.
The second point, is that while you squeeze the texture into a narrower space, this is done linearly across the narrow space, where in "reality", the squeezing should be less closer to the eye and more as the texture gets further from the eye.
To do this properly, you either should use a 3D library that can apply a texture to a surfaces with perspective, or if you want to do it yourself, you will have to do a lot more work.
The regular drawing capabilities of VB6 (GDI) and .Net (GDI+) don't support doing 3D texture mapping with perspective. .Net does support some perspective drawing in a limited fashion, but not with textures.
I did do some other experiments with mapping a texture to a trapazoid in the limited case where the eyepoint was always perpendicular to the vertical, i.e. think of running around in a Maze game, where you don't look up or down, and you are always moving around the maze with the walls perpendicular to your view.
What you have to do for the side walls in that situation is determine for each vertical line of pixels in the side wall, which vertical line of pixels from the texture maps to it, and then squeeze that line of pixels and draw it vertically where it should be drawn.
So instead of one transfer of the texture to a shape, you have to do a series of transfers of vertical slices of the texture to match the perspective at that pixel location. It is a lot of work if you want to do the primitives involved yourself.
So, the question is what is your desired purpose for the post? Are you interested in the math involved, which is explained in the various posts you can find by searching. Are you trying to manually draw some 3D objects using the available drawing primitives within VB6 or .Net?
You can do an isometric (not perspective) type of 3D using plgblt type operations in either VB6 or VB.Net, but isometric does suffer from not fore-shortening with depth, so generally only looks relatively good when looking down at perhaps a 30 degree angle, which a lot of simple games depend on. This is sometimes referred to as 2.5D as it is a look that is between 2D and true 3D.
There is no simple answer or trick if you want to do this yourself. There are probably a number of different libraries to make this type of thing easier, but even then it is still a fair amount of work to become competent with those tools.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
i want draw the image\cube using Geometric Math for learn it. i know that theres several libraries for it... but i want to learn draw the cube, camera and the move.
the fog and the atmosphere\effects isn't the goal now
Have you looked at this post.
I haven't looked at it in detail, and it won't help with texturing the cube because that is a whole other level of complication, but it will probably give you another look at the math involved in transforming points from 3D space to a projection on a 2D plane.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
that is for rotate the cube... but lets go back a little...
i'm trying work with scale... from i have seen i must multiply the positions and sizes by scale:
I certainly can't tell what you're doing wrong.
If it works sometime, and not other times, then the basic function call is likely syntactically correct and the problem is with the parameters passed to the function.
If the only thing you are changing is scale, then the value for Scale must be incorrect in some situations.
That is something you will have to test.
I am not going to try to create a test project to test anything, that is for sure.
I don't have your bitmap, and from the little bit of code posted, I can't tell if the language being used is C#, C++ or something else.
I can guess that the readimagefile function may be from the old Borland port of the BGI library to Windows, but I don't even know that for sure.
If I wanted to test this, then I probably would use four variables to hold the X, Y , Width and Height values and set them before the function call.
That way you can examine then or print out their values as you change scale, to see if the values make sense for passing to the function.
At the very least, you can see at what limits of scale the function manages to draw something. I'm pretty sure if the width or height end up being less than a pixel in size, probably nothing ends up getting drawn.
You could easily verify that the function is working on some basic standard cases, e.g. like:
a Scale of 1.0 should create an image the size of the original
2.0 should be twice the size
0.5 should be half the size
0.25 should be 1/4 the size.
If you declared Size as an Integer, then that obviously would be a problem.
I don't really see how this will help with drawing a cube. The function readimagefile can resize the image, but it will always be a resized rectangle, and to draw a cube, most of the sides would not be rectangles on the screen.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
"I don't really see how this will help with drawing a cube. The function readimagefile can resize the image, but it will always be a resized rectangle, and to draw a cube, most of the sides would not be rectangles on the screen."
that you have right!!!!
on win32 theres 1 function that can draw an image on that way?
First, you are better of using something like DirectX or another game engine rather than reinventing the wheel, because they do other tasks that you might find that you get to do latter. With that in mind, here is a quick simple formula I used in the past:
x2D = (x * L) / (z + L)
y2D = (y * L) / (z + L)
L is a constant representing the distance from the user to the screen in pixels, typically set to 1024 or 2048, which makes the above formula very fast as VB6 uses shifts when you use a number that is multiple of the power of two.
Here is a function that return x, y when supplied with xyz. I have used Long data type, but if you have trouble, use Double:
VB Code:
Public Type My2DPoint
x As Long
y As Long
End Type
Public Function To2D(ByVal x As Long, ByVal y As Long, ByVal z As Long) As My2DPoint
' L = Distance from screen to the user in pixels.
' It's not necessary to turn this into a variable, just use a typical value.
Const L As Long = 2048 ' User is at 2048 pixels away from monitor
thank you so much for the formula.. thank you...
now i can work more easy...
thinking on 2D to 3D or Z is more like:
Z=(x*L-Lx2D)/x2D
right?
Correct, but you are missing "*":
z = (x*L-L*x2D) / x2D
Or:
z = L * (x-x2D) / x2D
Originally Posted by joaquim
can i use brushes on polygons?
You have to do that on your own, and I am sure that you will have many other questions in the future as you move forward. A game engine saves you development time. These simple solutions are for limited use case scenarios.
...
but i need another correction: the scale affect the position too?
If you don't want to scale the whole view, just the object, then don't multiply the X,Y position by the Scale, just the size, i.e. width and height.
Originally Posted by joaquim
"I don't really see how this will help with drawing a cube. The function readimagefile can resize the image, but it will always be a resized rectangle, and to draw a cube, most of the sides would not be rectangles on the screen."
...
on win32 theres 1 function that can draw an image on that way?
Originally Posted by joaquim
..., even the cube, isn't all faces rectangles but polygons.
can i use brushes on polygons?
No. There is nothing in the windows (GDI or GDI+) APIs that would draw a perspective image using a texture brush, or to fill a polygon with such a brush that would look correct.
You didn't mention what language you're using, or what libraries you're using to draw with.
On the surface, it looks like you may be using C++ and the "Ported to Windows" version of the Borland Graphics Interface. I don't think that WinBGIm even supports texture brushes, or brushes in general, if that is what you are using. WinBGIm is pretty primitive stuff.
If you were using GDI+, then you could have a texture brush, and you could transpose the brush to rotate, skew, and translate the brush when it is used to fill a polygon, but that would never look right with polygons that represent a 3D polygon projected to a 2D surface. It would always be a parallelogram shaped pattern filling an arbitrary shaped figure.
The best you could do with a texturebrush is, as I mentioned before, doing an Isometric type drawing, where the shape to be filled would always be a parallelogram so a parallelogram shaped texture would fit the shape more realistically. The texture would still not be variably distributed along the perceived Z-Axis as would be needed for "true" 3D texture mapping. It would be linearly distributed across the perceived Z-Axis. This would be along the same lines as what could be done using GDI with the parallel blit (plgblt) function.
For an example of what this isometric 3D texture looks like, I played around with plgblt in a VB6 project at least 12 years ago (found a zip file with the project in it from 2008).
Again, it is just code used to experiment with plgblt and textures, so is a mishmash of code and commented out code.
I went ahead and made a couple of changes to it today, so that it starts up with the plgblt checkbox enabled, (used flat color fills if that option is not selected), and it starts with the view already rotated and tilted so that you see three sides of the visible blocks when you start up the program.
I also added a Sleep API declaration and do a "Sleep 1" in the loop so the program isn't using 100% of the core it runs on. On my machine, with the Sleep 1 in place, the executable uses 0% CPU when you aren't moving, and generally less than 8%, when the scene is in motion.
The Orange Circle, which would represent the object moving through the space, really never moves in the window. Everything else in the environment moves to show relative motion.
There are various controls that were part of the experiment, but to start with, you really only need to know three things to see the basic drawing in action.
1. Use the Up Arrow to move "forward" and the Down Arrow to move "backward" and the Left and Right arrows to rotate the heading.
2. Use the "a" and "z" keys to tilt the view up and down. The tilt will rotate continuously through a full circle in either direction. I don't know if it would eventually crash if you rotated in one direction too long.
3. If you move away from the drawing environment area (i.e. you get lost), hit the button to reset the view back to the beginning view.
Last edited by passel; Mar 31st, 2020 at 11:12 PM.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
ok.. now i know convert the 3D to 2D and using the camera\me distance(Z).
but thinking on my position... how can i compare with my position(left-right and up-down)?
ok.. now i know convert the 3D to 2D and using the camera\me distance(Z).
but thinking on my position... how can i compare with my position(left-right and up-down)?
Who are you addressing?
The formula I posted is not accurate, but it's good enough for games. The limitation is that objects don't become smaller as they go right or left, but become smaller and larger as they go backward or forward. For example, if you are looking at a wall 100 Meters away, and a person walks right or left along the wall, he will not become smaller.
Here is an accurate formula, but it's slower(VB6 Code, Sqr=Square root):
x2D = x * L / Sqr((z + L) ^ 2 + y ^ 2)
y2D = y * L / Sqr((z + L) ^ 2 + x ^ 2)
The formula I posted is not accurate, but it's good enough for games. The limitation is that objects don't become smaller as they go right or left, but become smaller and larger as they go backward or forward. For example, if you are looking at a wall 100 Meters away, and a person walks right or left along the wall, he will not become smaller.
Here is an accurate formula, but it's slower(VB6 Code, Sqr=Square root):
x2D = x * L / Sqr((z + L) ^ 2 + y ^ 2)
y2D = y * L / Sqr((z + L) ^ 2 + x ^ 2)
i need ask more 2 things:
- we use the angle directly on 'x' or after we get the 'x2D'?
- these formula is used, too, for make the floor?
yes i'm trying creating, now, the floor without success
Code:
//Position and Size Floor:
int FloorX=0, FloorY=1000, FloorZ=1,FloorWidth=1000, FloorHeight=10, FloorZWidth=100;
//floor vectors:
POINT FloorPoints[5] =
{
{ FloorWidth+FloorX, (FloorY) / (Z) },
{ FloorWidth+FloorX, FloorHeight-FloorY },
{ (FloorX *MeDistance) / (Z +MeDistance), FloorHeight-FloorY },
{ (FloorX * MeDistance) / (Z + MeDistance), (FloorY * MeDistance) / (Z + MeDistance) }
};
i'm totally new and i can't get a nice tutorial for learn much more about it
(these code isn't updated with sqr(), but the C\C++ have it)
thanks for all
i need ask more 2 things:
- we use the angle directly on 'x' or after we get the 'x2D'?
- these formula is used, too, for make the floor?
Yes, you need to do rotation/moving on x, y, z before calculating x2D, y2D. This also apply to the floor, but there maybe some situations where you don't have to do that, I am not sure. I haven't made a game before, but I plan to make one using Unreal game engine(free, but they take 5% after you make a lot of money). You can use C++, or a visual programming language called Blueprint. There are many YouTube tutorials on how to use it.
to be honest i didn't found tutorials about these.
thanks to you, i can start with something, because you give me the key formula.
and maybe tomorrow i can draw the floor
i love when works and i hates when fails hehehehehe
The fast formula that I posted, is something I developed myself, but others may have come up with the same formula. I am not sure about the accurate formula. It might be me or someone else. These are meant for apps that want to display 3D shapes, like engineering apps.
For games, and even engineering apps; a game engine is a better choice. Game engines do textures, object collision, physics, lighting, shadows, audio, and more. Are you planning to spend months(more like a year or two) in duplicating these futures? If you don't like the main two(Unreal/Unity), here is a list:
correct me 1 thing: the Polygon() function uses the POINT arrays for the vectors, but uses LONG type instead float... that's why i get a warning.
theres is another function for get several dots\vectors for create the lines using a float type?
(is only for i don't get these warnings)
You can't use type casting to convert data. That just tells the compiler to treat the binary data as LONG. Use "double" data type instead of "long", and remove "(LONG)".
You can't use type casting to convert data. That just tells the compiler to treat the binary data as LONG. Use "double" data type instead of "long", and remove "(LONG)".
maybe i didn't understand what you said.
i changed from 'float' to 'double' and i delete the "(LONG)"... but the warning remains
the casting have, too, an operator precedence and i never knew.
i must test more 1 thing abut drawing and the distance... then i will be back
thanks for all
...if the 'L' is more big, the object\rectangle is more big too..
to be honest: the more big the 'L', the rectangle is more little?
L is the distance from you to the screen.
Imagine if you had an empty picture frame that represented the screen, and you sat at one end of the table with the frame 400cm in front of you, and I was at the other end of the table and you were looking at me through the frame.
Picture how much of me you might see in the frame.
Now move the frame further from you, and thus closer to me.
Would I look smaller in the frame now, or larger?
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
i'm sorry, but i continue confused: if the 'L' is less than 50, the object will be more far\small... but if is more than 50, the object will be more close\big.
i'm sorry, but i continue confused: if the 'L' is less than 50, the object will be more far\small... but if is more than 50, the object will be more close\big.
Yes, that is true. If you understand that you are not confused.
If you don't understand that, then I'm not sure I can make it clearer.
L is the distance from your eyepoint to the screen. The screen is a window, i.e. a viewport into your 3D world. The closer your eye is to the window, the more of the world you see, if you see more of the world in your window, the world objects have to appear smaller compared to the window.
If your eye is at the window, you can see 180 degrees of your world, so all the objects you see have to fit in that window, so they have to be small compared to the window.
If your window is 60 cm wide, and your eye is 60 cm from the window, you can only see 90 degrees of your world, so you see much less of your world. Since you see less things in your window, those things you see appear larger compared to the frame of the window.
Once you choose a value for L, you should not be changing it. It decides what your field of view is, i.e. how many degrees vertically and horizontally you can see. As you move around, your field of view doesn't change, just the angle of direction that you are looking.
You would only change L if for instance, you looked through Binoculars, or some other device, that changed your field of view.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
passel, to be honest, i was using 'L' like the scale... thanks for correct me.
so now i understand what i can do for scale...
i need ask more 1 thing that is kill me now.
see these sample code:
It is a little hard to follow what you're doing. You should probably create a function like qvb6 gave you and pass the 3D value in and get the 2d Value back.
If you store those in variables or arrays of structure, then it would be easier to see the values going in and the resulting X,Y values coming back so you can analyze the values.
The X,Y,Z values being passed in have to be relative to your eye point.
So, the first step is to transform your world coordinates into local relative points to where you are looking from in the world.
Once you've translated the world coordinates into eye point relative coordinates, then those coordinates are what you convert from 3d to 2d for projection.
It looks like you are using values that are off to one side from the eye point, e.g. ranging from 0 to 1000, rather than -500 to 500.
Then, if you are not going to use graphics commands to offset the drawing on the screen, you would want to add the center offset when you draw.
You're not showing what you do with the points after you create them, so I don't know if you're taking care of that or not.
For an example, I don't have VB6 on this machine, so fell back to using VB.Net in VS2010 which is on this machine.
I just took qvb6 function and changed the Longs to Integers since they are the 32-bit type in .Net, and changed the Type declaration to a Structure declaration.
So, treat the your eyepoint as always being (0,0,0) and the points of what you're drawing being relative to that.
I originally just did the four points with Y as a positive value which I figured would put the rectangle below the eye point (i.e. the floor).
That looked OK, so I copied the four presets and just changed the Y value to a negative number, so they should be above the eye point (i.e. the ceiling). And that also drew the four points the way I would have expected them to.
So, the basic function works to the degree that it does, and gives results that you would expect.
Your primary problem is most likely that your 3d points are not relative to your eye point, but there may be other issues as your example reference variables that are not defined in the snippet you posted.
Here is the test code I tested with (VB.Net)
Code:
Public Class Form1
Private Structure My2DPoint
Public x As Integer
Public y As Integer
End Structure
Private Structure my3DPoint
Public x As Integer
Public y As Integer
Public z As Integer
End Structure
Dim my2dArray(7) As My2DPoint
Dim my3dArray(7) As my3DPoint
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
With my3dArray(0)
.x = -200
.y = 100
.z = 0
End With
With my3dArray(1)
.x = -200
.y = 100
.z = 400
End With
With my3dArray(2)
.x = 200
.y = 100
.z = 400
End With
With my3dArray(3)
.x = 200
.y = 100
.z = 0
End With
With my3dArray(4)
.x = -200
.y = -100
.z = 0
End With
With my3dArray(5)
.x = -200
.y = -100
.z = 400
End With
With my3dArray(6)
.x = 200
.y = -100
.z = 400
End With
With my3dArray(7)
.x = 200
.y = -100
.z = 0
End With
For i As Integer = 0 To 7
With my3dArray(i)
my2dArray(i) = To2D(.x, .y, .z)
End With
Next
End Sub
Private Function To2D(ByVal x As Integer, ByVal y As Integer, ByVal z As Integer) As My2DPoint
' L = Distance from screen to the user in pixels.
' It's not necessary to turn this into a variable, just use a typical value.
Const L As Long = 1024 ' User is at 2048 pixels away from monitor
Dim p2d As My2DPoint
Dim temp As Long
temp = z + L
p2d.x = x * L / temp
p2d.y = y * L / temp
To2D = p2d
End Function
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Dim g As Graphics = e.Graphics
Dim cx As Integer = 300, cy As Integer = 300
For i As Integer = 0 To 7
With my2dArray(i)
g.FillEllipse(Brushes.Black, cx + .x, cy + .y, 8, 8)
End With
Next
End Sub
End Class
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
- on X, we have Width;
- on Y, we have Height;
- so on Z we have ZWidth(it's what i call, unless theres a technical name that i don't know it).
for now i'm trying draw a rectangle for the floor and then the sky.....
Please see the attached VB6 project for a sample that draws a cube. The 3D Origin constants could have been better, but I didn't have time to make them based on PictureBox size.
VB Code:
Option Explicit
Private Type T2DPoint
x As Long
y As Long
End Type
Private Type T3DPoint
x As Long
y As Long
z As Long
End Type
Private Origin2D As T2DPoint
Private Origin3D As T3DPoint
Const CubeSize As Long = 100 ' Pixels
Private Function To2D(ByVal x As Long, ByVal y As Long, ByVal z As Long) As T2DPoint
' L = Distance from screen to the user in pixels.
' It's not necessary to turn this into a variable, just use a typical value.
Const L As Long = 2048 ' User is at 2048 pixels away from monitor
Dim p2d As T2DPoint
Dim temp As Long
If chkUseFastFormula.Value <> 0 Then
temp = z + L
p2d.x = x * L \ temp
p2d.y = y * L \ temp
Else
p2d.x = x * L / Sqr((z + L) ^ 2 + y ^ 2)
p2d.y = y * L / Sqr((z + L) ^ 2 + x ^ 2)
End If
To2D = p2d
End Function
Private Sub Form_Load()
Picture1.ScaleMode = vbPixels
Picture1.AutoRedraw = True
' 2D Origin
Origin2D.x = Picture1.ScaleWidth / 2
Origin2D.y = Picture1.ScaleHeight / 2
' 3D Origin
Origin3D.x = -300
Origin3D.y = -300 '-50
Origin3D.z = 1000
Timer1.Interval = 100
DrawCube
End Sub
Private Sub Draw3DLine(ByVal x1 As Long, ByVal y1 As Long, ByVal z1 As Long, ByVal x2 As Long, ByVal y2 As Long, ByVal z2 As Long)