Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
506 views
in Technique[技术] by (71.8m points)

java - Perspective Vision on Canvas

today I'm bringing a subject about Pseudo 3D and perspective.

I was checking the video #1 Java Classical 3D Rendering Tutorial : Creating 3D World where he used a method for render pseudo-3D ceil and floor. I tried to find some tutorial or the name of the method that he used but I didn't find. I saw the algorithm but it is not clear to understand. I started to search about perspective graphics (vanishing points, horizon...) but the unique thing that I got was static drawing. I wanna apply an illusion moving putting the camera inside the plan and moving it. Under follow an example about the perspective floor and ceiling that I wanna make.

Static Perspective Image Static Perspective Image

This is just an image, but my first question is: "I realy can make a movement of the camera in this ambient, like rotation and move x, and y axis?". I tried to make 2 vanishing point in a canvas, creating lines for each degree of 15o, and I got a perspective illusion, but i couldn't find a way to make the rotation, or the movement. In that video I saw the pixels creating 2 dimensions using just the colors green and blue, but I wanna make this using lines, to understand how it works.

enter image description here

There isn't a place that is teaching step by step how to make the perspective with movements. I didn't find. I checked the videos of 3D game maker in Java and the Markus Person creating the game called by "Prelude of the chambered" using the method of the video, but I didn't find an explanation for this king of rendering.

enter image description here

Lets supose I have to create a plan using a grid. how is the logic that I have to apply in the lines to create the movement? I realy wanna understand the logic to make this kind of pseudo-3D, without using frameworks or thing like that. Thanks for help me! I will wait for your answer.

I checked something about MODE 7 of SNES. This is a good way for make it I think. I have just to understand how it works, and how to make the rotation.

enter image description here

** Note: I don't what to use raycasting for it. Raycasting I'll use to create the walls.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Interesting problem. I did not resist and code it for fun so here some insights... Well there are 2 basic approaches for this. One is raster fake and second is Vector based. I will describe the latter as you can do much more with it.

Vector approach

This approach is not faking anything it really is 3D. The rest depends on the rendering you want to use this for... For now I assume you can render 2D lines. All the code chunks are in C++.

  1. Transformations

    You need vector math to transform points between world and camera space and back again. In 3D graphics are usually 4x4 homogenuous transform matrices used for this and many programing APIs support them natively. I will base my math on OpenGL matrix layout which determine the order of multiplication used. For more info I strongly recommend to read this:

    As I use a lot from it. The linked answers there are also useful especially the 3D graphics pipeline and Full pseudo inverse matrix. The Answer itself is basic knowledge needed for 3D rendering in a nutshell (low level without the need for any lib apart of the rendering stuff).

    There are also libs for this like GLM so if you want you can use any linear algebra supporting 4x4 matrices and 4D vectors instead of my code.

    So lets have two 4x4 matrices one (camera) representing our camera coordinate system and second (icamera) which is its inverse. Now if we want to transform between world and screen space we simply do this:

    P = camera*Q
    Q = icamera*P
    

    where P(x,y,z,1) is point in camera coordinate system and Q(x,y,z,1) is the same point in global world coordinate system.

  2. Perspective

    This is done simply by dividing P by its z coordinate. That will scale objects around (0,0) so the more far object is the smaller will be. If we add some screen resolution and axis correction we can use this:

    void perspective(double *P) // apply perspective transform on P
        {
        // perspectve division
        P[0]*=znear/P[2];
        P[1]*=znear/P[2];
        // screen coordinate system
        P[0]=xs2+P[0];          // move (0,0) to screen center
        P[1]=ys2-P[1];          // axises: x=right, y=up
        }
    

    so point 0,0 is center of screen. The xs2,ys2 is half of resolution of the screen and znear is focal length of the projection. So XY plane rectangle with screen resolution and center at (0,0,znear) will cover the screen exactly.

  3. Rendering 3D line

    We can use any primitives for rendering. I chose line as it is very simple and can achieve much. So what we want is to render 3D line using 2D line rendering API (of any kind). I am VCL based so I chose VCL/GDI Canvas which should be very similar to your Canvas.

    So as input we got two 3D points in global world coordinate system. In order to render it with 2D line we need to convert the 3D position to 2D screen space. That is done by matrix*vector multiplication.

    From that we obtain two 3D points but in camera coordinate system. Now we need to clip the line by our view area (Frustrum). We can ignore x,y axises as 2D line api usually does that for us anyway. So the only thing left is clip z axis. Frustrum in z axis is defined by znear and zfar. Where zfar is our max visibility distance from camera focal point. So if our line is fully before or after our z-range we ignore it and do not render. If it is inside we render it. If it crosses znear or zfar we cut the outside part off (by linear interpolation of the x,y coordinates).

    Now we just apply perspective on both points and render 2D line using their x,y coordinates.

    My code for this looks like this:

    void draw_line(TCanvas *can,double *pA,double *pB)  // draw 3D line
        {
        int i;
        double D[3],A[3],B[3],t;
        // transform to camera coordinate system
        matrix_mul_vector(A,icamera,pA);
        matrix_mul_vector(B,icamera,pB);
        // sort points so A.z<B.z
        if (A[2]>B[2]) for (i=0;i<3;i++) { D[i]=A[i]; A[i]=B[i]; B[i]=D[i]; }
        // D = B-A
        for (i=0;i<3;i++) D[i]=B[i]-A[i];
        // ignore out of Z view lines
        if (A[2]>zfar) return;
        if (B[2]<znear) return;
        // cut line to view if needed
        if (A[2]<znear)
            {
            t=(znear-A[2])/D[2];
            A[0]+=D[0]*t;
            A[1]+=D[1]*t;
            A[2]=znear;
            }
        if (B[2]>zfar)
            {
            t=(zfar-B[2])/D[2];
            B[0]+=D[0]*t;
            B[1]+=D[1]*t;
            B[2]=zfar;
            }
        // apply perspective
        perspective(A);
        perspective(B);
        // render
        can->MoveTo(A[0],A[1]);
        can->LineTo(B[0],B[1]);
        }
    
  4. Rendering XZ plane

    We can visualize the ground and sky planes using our 3D line as grid of squares. So we just create for loops rendering the x-axis aligned lines and y-axis aligned lines covering some square of some size around some origin position O. The lines should be some step far between each other equal to grid cell size.

    The origin position O should be near our frustrun center. If it would be constant then we could walk out of the plane edges so it woul dnot cover the whole (half)screen. We can use our camera position and add 0.5*(zfar+znear)*camera_z_axis to it. To maintain the illusion of movement we need to align the O to step size. We can exploit floor,round or integer cast for this.

    The resulting plane code looks like this:

    void draw_plane_xz(TCanvas *can,double y,double step) // draw 3D plane
        {
        int i;
        double A[3],B[3],t,size;
        double U[3]={1.0,0.0,0.0};  // U = X
        double V[3]={0.0,0.0,1.0};  // V = Z
        double O[3]={0.0,0.0,0.0};  // Origin
        // compute origin near view center but align to step
        i=0; O[i]=floor(camera[12+i]/step)*step;
        i=2; O[i]=floor(camera[12+i]/step)*step;
        O[1]=y;
        // set size so plane safely covers whole view
        t=xs2*zfar/znear;               size=t; // x that will convert to xs2 at zfar
        t=0.5*(zfar+znear); if (size<t) size=t; // half of depth range
        t+=step;                                // + one grid cell beacuse O is off up to 1 grid cell
        t*=sqrt(2);                             // diagonal so no matter how are we rotate in Yaw
        // U lines
        for (i=0;i<3;i++)
            {
            A[i]=O[i]+(size*U[i])-((step+size)*V[i]);
            B[i]=O[i]-(size*U[i])-((step+size)*V[i]);
            }
        for (t=-size;t<=size;t+=step)
            {
            for (i=0;i<3;i++)
                {
                A[i]+=step*V[i];
                B[i]+=step*V[i];
                }
            draw_line(can,A,B);
            }
        // V lines
        for (i=0;i<3;i++)
            {
            A[i]=O[i]-((step+size)*U[i])+(size*V[i]);
            B[i]=O[i]-((step+size)*U[i])-(size*V[i]);
            }
        for (t=-size;t<=size;t+=step)
            {
            for (i=0;i<3;i++)
                {
                A[i]+=step*U[i];
                B[i]+=step*U[i];
                }
            draw_line(can,A,B);
            }
        matrix_mul_vector(A,icamera,A);
        }
    

Now if I put all this together in small VCL/GDI/Canvas application I got this:

//---------------------------------------------------------------------------
#include <vcl.h> // you can ignore these lines
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm" // up to here.
TMain *Main; // this is pointer to my VCL window (you do not need it)
//--- Here starts the important stuff: --------------------------------------
// perspective
double znear= 100.0;    // focal length for perspective
double zfar = 2100.0;   // visibility
// view
double xs2=0.0;         // screen half resolution
double ys2=0.0;
// camera
double yaw=0.0;         // euler yaw angle [rad]
double camera[16];      // camera direct transform matrix
double icamera[16];     // camera inverse transform matrix
// keyboard bools
bool _forw=false,_back=false,_right=false,_left=false;
//---------------------------------------------------------------------------
void matrix_inv(double *a,double *b) // a[16] = Inverse(b[16])
    {
    double x,y,z;
    // transpose of rotation matrix
    a[ 0]=b[ 0];
    a[ 5]=b[ 5];
    a[10]=b[10];
    x=b[1]; a[1]=b[4]; a[4]=x;
    x=b[2]; a[2]=b[8]; a[8]=x;
    x=b[6]; a[6]=b[9]; a[9]=x;
    // copy projection part
    a[ 3]=b[ 3];
    a[ 7]=b[ 7];
    a[11]=b[11];
    a[15]=b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
    y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
    z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
    a[12]=-x;
    a[13]=-y;
    a[14]=-z;
    }
//---------------------------------------------------------------------------
void  matrix_mul_vector(double *c,double *a,double *b) // c[3] = a[16]*b[3]
    {
    double q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
void compute_matrices() // recompute camera,icamera after camera position or yaw change
    {
    // bound angle
    while (yaw>2.0*M_PI) yaw-=2.0*M_PI;
    while (yaw<0.0     ) yaw+=2.0*M_PI;
    // X = right
    camera[ 0]= cos(yaw);
    camera[ 1]=     0.0 ;
    camera[ 2]= sin(yaw);
    // Y = up
    camera[ 4]=     0.0 ;
    camera[ 5]=     1.0 ;
    camera[ 6]=     0.0 ;
    // Z = forward
    camera[ 8]=-sin(yaw);
    camera[ 9]=     0.0 ;
    camera[10]= cos(yaw);
    // no projection
    camera[ 3]=     0.0 ;
    camera[ 7]=     0.0 ;
    camera[11]=     0.0 ;
    camera[15]=     1.0 ;
    // compute the inverse matrix
    matrix_inv(icamera,camera);
    }
//---------------------------------------------------------------------------
void perspective(double *P) // apply perspective transform
    {
    // perspectve division
    P[0]*=znear/P[2];
    P[1]*=znear/P[2];
    // screen coordinate system
    P[0]=xs2+P[0];          // move (0,0) to screen center
    P[1]=ys2-P[1];          // axises: x=right, y=up
    }
//---------------------------------------------------------------------------
void draw_line(TCanvas *can,d

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...