如何隐藏3D绘图的不可见元素? [英] How to hide invisible elements of the 3d drawing?

查看:81
本文介绍了如何隐藏3D绘图的不可见元素?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试绘制显示波纹的3d图像:

 函数myFunc(x,y){令zRipple =Math.pow(2,-0.005 *(Math.abs(x)+ Math.abs(y)))*Math.cos((((x * x + y * y)* 2 * pi)/180/宽度)*高度;返回zRipple;} 

宽度和高度是定义绘图区域的常数,在我的测试中等于200.

我的方法基于我30年前读过的一篇文章的回忆,现在又想回想起.

想法是:

  • 将整个绘图板分割成10像素的网格

  • 对于网格中的每个单元格",沿着Y轴和X轴在最近的单元格上画一条线(step = 10,ds = 0.0

      for(让x3 =宽度; x3> =-宽度; x3-=步长){for(let y3 = -height; y3< = height; y3 + = step){for(let s = 0; s< step; s + = ds){令x = x3 + s;如果(x<宽度){令z3 = myFunc(x,y3);drawPixel3d(x,y3,z3);}}for(let s = 0; s< step; s + = ds){令y = y3 + s;如果(y<高度){令z3 = myFunc(x3,y);drawPixel3d(x3,y,z3);}}}}} 

这是我将3d坐标转换为2d的方法:

 函数drawPixel3d(x3,y3,z3){令x2 =(x3 + y3)* Math.sin((60 * pi)/180);令y2 = z3-((x3-y3)* Math.sin((30 * pi)/180))/4;drawPixel(x2,y2);} 

从下面的图片中可以看到,我得到了不错的图形,但是有一个问题:我绘制了所有可见的点,不仅是那些点.

问题: 如何检查是否需要显示任何像素?

根据我在那篇文章中所记得的,我们应该遵循以下方法:

  • 从场景的前部开始绘制(我相信我是这样做的,如果点的坐标为(宽度,-高度),则最接近查看器或屏幕
  • )
  • 对于每个像素列-记住"Z"坐标,并且仅当其Z坐标大于最后记录的像素时才绘制新像素

为此,我修改了我的"drawPixel3d"方法:

 函数drawPixel3d(x3,y3,z3){令x2 =(x3 + y3)* Math.sin((60 * pi)/180);令y2 = z3-((x3-y3)* Math.sin((30 * pi)/180))/4;令n = Math.round(x2);让可见=假;如果(zs [n] ===未定义){zs [n] = z3;可见=真;} 别的 {如果(z3> zs [n]){可见= z3>zs [n];zs [n] = z3;}}如果(可见)drawPixel(x2,y2);} 

但是结果并不理想:

我做错了什么?或另一个问题:如何绘制简单的3d图形?

谢谢!

P.S.程序的最后一部分(说明实际绘制过程中Y坐标的反转):

 函数drawPixel(x:数字,y:数字){ctx.fillRect(cX + x,cY-y,1,1);//TS在画布上绘制像素的方式是绘制一个矩形}//cX和cY是绘图画布中心的坐标 

P.P.S.我对算法解决方案有所了解,因此添加了"algorithm"标签:也许该社区中的某人可以提供帮助?

解决方案

您的表面是凹形的,这意味着您不能使用基于法线和相机视线方向之间的点积的简单方法.

为此您有3个明显的选择.

  1. 使用光线跟踪

    当您获得表面的解析方程时,这可能是更好的方法

  2. 使用深度缓冲掩盖不可见的东西

    渲染线框时,您需要分两遍进行此操作:

    1. 渲染不可见的填充表面(仅填充深度缓冲区而不填充屏幕)
    2. 渲染线框

    您的深度缓冲区条件也必须包含相等的值,因此 z< = depth [y] [x] z> = depth [y] [x]

    但是您需要使用人脸渲染(三角形或四边形...),并且我认为这是软件渲染,因此,如果您对此类工具不熟悉,请参阅:

    • 这里没有 pixel(x,y,z,col)

      中的深度条件

      pbuf 结构保存将在水平线的最后一个渲染插值中插值的所有内容.因此,如果您需要古拉德,纹理或其他任何东西,只需将变量添加到此结构中,然后将插值添加到代码中即可(模拟 pbuf [].z 插值代码)

      但是,这种方法有一个缺点.您当前的方法是一个像素一个像素地插值,而另一个则是按照网格大小逐步插值.这是通过网格大小步进两个轴.因此,如果您希望具有相同的行为,则可以使用 1 x 1 quads而不是 ds x ds 进行第一次遍历,然后像现在一样执行行.如果视图中的1对应于像素,那么您可以在不进行面部渲染的情况下仅对像素执行此操作,但是您可能会在输出中出现漏洞.

      I'm trying to draw a 3d image that displays a ripple:

      function myFunc(x, y) {
        let zRipple =
          Math.pow(2, -0.005 * (Math.abs(x) + Math.abs(y))) *
          Math.cos(((x * x + y * y) * 2 * pi) / 180 / width) *
          height;
      
        return zRipple;
      }
      

      width and height here are constants that define a drawing area and are equal to 200 in my tests.

      My approach is based on what I recall from an article that I read 30 years ago and trying to recall now.

      The idea is to:

      • split the whole drawing board into the 10-pixel grid

      • for each 'cell' of the grid, draw a line to the nearest cell along the Y- and the X-axis' (step=10, ds=0.0

        for (let x3 = width; x3 >= - width; x3 -= step) {
          for (let y3 = -height; y3 <= height; y3 += step) {
            for (let s = 0; s < step; s += ds) {
              let x = x3 + s;
                if (x < width) {
                  let z3 = myFunc(x, y3);
                  drawPixel3d(x, y3, z3);
                }
              }
        
              for (let s = 0; s < step; s += ds) {
                let y = y3 + s;
                if (y < height) {
                  let z3 = myFunc(x3, y);
                  drawPixel3d(x3, y, z3);
                }
              }
            }
          }
        }
        

      Here is how I convert 3d coordinates to 2d:

      function drawPixel3d(x3, y3, z3) {
        let x2 = (x3 + y3) * Math.sin((60 * pi) / 180);
        let y2 = z3 - ((x3 - y3) * Math.sin((30 * pi) / 180)) / 4;
        drawPixel(x2, y2);
      }
      

      As you see from the image below, I get a decent graphic, but there is a problem: I draw ALL dots, not only those, that are VISIBLE.

      Question: How do I check if any pixel needs to be displayed or not?

      From what I can recall in that article, we should follow the approach:

      • start drawing from the front part of the scene (which I believe I do, the closest to the viewer or screen if dot with coordinates (width, -height)
      • for each pixel column - remember the 'Z' coordinate and only draw the new pixel if its Z-coordinate is bigger than the last recorded one

      To achieve this I've modified my 'drawPixel3d' method:

      function drawPixel3d(x3, y3, z3) {
        let x2 = (x3 + y3) * Math.sin((60 * pi) / 180);
        let y2 = z3 - ((x3 - y3) * Math.sin((30 * pi) / 180)) / 4;
      
        let n = Math.round(x2);
        let visible = false;
        if (zs[n] === undefined) {
          zs[n] = z3;
          visible = true;
        } else {
          if (z3 > zs[n]) {
            visible = z3 > zs[n];
            zs[n] = z3;
          }
        }
      
        if (visible) drawPixel(x2, y2);
      }
      

      But the result is not expected:

      What do I do wrong? Or an alternative question: how to draw a simple 3d graphic?

      Thanks!

      P.S. The last piece of the program (that illustrates inversion of Y-coordinate during actual drawing):

      function drawPixel(x: number, y: number) {
        ctx.fillRect(cX + x, cY - y, 1, 1); // TS-way to draw pixel on canvas is to draw a rectangle
      }   // cX and cY are coordinates of the center of the drawing canvas
      

      P.P.S. I have an idea of the algorithmic solution, so added an 'algorithm' tag: maybe someone from this community can help?

      解决方案

      Your surface is concave which means you can not use simple methods based on dot product between face normal and camera view direction.

      You got 3 obvious options for this.

      1. use ray tracing

        as you got analytical equation of the surface this might be even better way

      2. use depth buffering to mask out the invisible stuff

        As you render wireframe then you need to do this in 2 passes:

        1. render invisible filled surface (fill just depth buffer not the screen)
        2. render wireframe

        your depth buffer condition must contain also equal values so either z<=depth[y][x] or z>=depth[y][x]

        However you need to use face rendering (triangles or quads ...) and I assume this is software rendering so if you not familiar on such stuff see:

      3. use depth sorting by exploiting topology

        If you do not have view transform so your x,y,z coordinates are directly corresponding to camera space coordinates then you can render the grid in back to front order simply by ordering the for loops and direction of iteration (its common in isometric views). This does not need depth buffering however you need to render filled QUADS in order to obtain correct output (border is set to the plot color and the inside is filled with background color).

      I did go for the #2 approach. When I ported the last link into 3D I got this (C++ code):

      //---------------------------------------------------------------------------
      const int col_transparent=-1;   // transparent color
      class gfx_main
          {
      public:
          Graphics::TBitmap *bmp; // VCL bitmap for win32 rendering
          int **scr,**zed,xs,ys;  // screen,depth buffers and resolution
          struct pbuf             // convex polygon rasterization line buffer
              {
              int x,z;            // values to interpolate during rendering
              pbuf()  {}
              pbuf(pbuf& a)   { *this=a; }
              ~pbuf() {}
              pbuf* operator = (const pbuf *a) { *this=*a; return this; }
              //pbuf* operator = (const pbuf &a) { ...copy... return this; }
              } *pl,*pr;          // left,right buffers
          gfx_main();
          gfx_main(gfx_main& a)   { *this=a; }
          ~gfx_main();
          gfx_main* operator = (const gfx_main *a) { *this=*a; return this; }
          //gfx_main* operator = (const gfx_main &a) { ...copy... return this; }
          void resize(int _xs=-1,int _ys=-1);
          void clear(int z,int col);              // clear buffers
          void pixel(int x,int y,int z,int col);  // render 3D point
          void line(int x0,int y0,int z0,int x1,int y1,int z1,int col); // render 3D line
          void triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,int col); // render 3D triangle
          void _triangle_line(int x0,int y0,int z0,int x1,int y1,int z1); // this is just subroutine
          };
      //---------------------------------------------------------------------------
      gfx_main::gfx_main()
          {
          bmp=new Graphics::TBitmap;
          scr=NULL;
          zed=NULL;
          pl =NULL;
          pr =NULL;
          xs=0; ys=0;
          resize(1,1);
          }
      //---------------------------------------------------------------------------
      gfx_main::~gfx_main()
          {
          if (bmp) delete bmp;
          if (scr) delete[] scr;
          if (zed)
              {
              if (zed[0]) delete[] zed[0];
              delete[] zed;
              }
          if (pl) delete[] pl;
          if (pr) delete[] pr;
          }
      //---------------------------------------------------------------------------
      void gfx_main::resize(int _xs,int _ys)
          {
          // release buffers
          if (scr) delete[] scr;
          if (zed)
              {
              if (zed[0]) delete[] zed[0];
              delete[] zed;
              }
          if (pl) delete[] pl;
          if (pr) delete[] pr;
          // set new resolution and pixelformat
          if ((_xs>0)&&(_ys>0)) bmp->SetSize(_xs,_ys);
          xs=bmp->Width;
          ys=bmp->Height;
          bmp->HandleType=bmDIB;
          bmp->PixelFormat=pf32bit;
          // allocate buffers
          scr=new int*[ys];
          zed=new int*[ys];
          zed[0]=new int[xs*ys];              // allocate depth buffer as single block
          for (int y=0;y<ys;y++)
              {
              scr[y]=(int*)bmp->ScanLine[y];  // screen buffer point directly to VCL bitmap (back buffer)
              zed[y]=zed[0]+(y*xs);           // just set pointers for each depth line instead of allocating it
              }
          pl=new pbuf[ys];
          pr=new pbuf[ys];
          }
      //---------------------------------------------------------------------------
      int rgb2bgr(int col)                    // just support function reversing RGB order as VCL/GDI and its direct pixel access are not the same pixelformat
          {
          union
              {
              BYTE db[4];
              int  dd;
              } c;
          BYTE q;
          c.dd=col;
          q=c.db[0]; c.db[0]=c.db[2]; c.db[2]=q;
          return c.dd;
          }
      //---------------------------------------------------------------------------
      void gfx_main::clear(int z,int col)
          {
          // clear buffers
          int x,y;
          col=rgb2bgr(col);
          for (y=0;y<ys;y++)
           for (x=0;x<xs;x++)
              {
              scr[y][x]= 0x00000000; // black
              zed[y][x]=-0x7FFFFFFF; // as far as posible
              }
          }
      //---------------------------------------------------------------------------
      void gfx_main::pixel(int x,int y,int z,int col)
          {
          col=rgb2bgr(col);
          if ((x>=0)&&(x<xs)&&(y>=0)&&(y<ys))         // inside screen
           if (zed[y][x]<=z)                          // not after something already rendered (GL_LEQUAL)
              {
                                        zed[y][x]=z;  // update depth
              if (col!=col_transparent) scr[y][x]=col;// update color
              }
          }
      //---------------------------------------------------------------------------
      void gfx_main::line(int x0,int y0,int z0,int x1,int y1,int z1,int col)
          {
          int i,n,x,y,z,kx,ky,kz,dx,dy,dz,cx,cy,cz;
          // DDA variables (d)abs delta,(k)step direction
          kx=0; dx=x1-x0; if (dx>0) kx=+1;  if (dx<0) { kx=-1; dx=-dx; }
          ky=0; dy=y1-y0; if (dy>0) ky=+1;  if (dy<0) { ky=-1; dy=-dy; }
          kz=0; dz=z1-z0; if (dz>0) kz=+1;  if (dz<0) { kz=-1; dz=-dz; }
          n=dx; if (n<dy) n=dy; if (n<dz) n=dz; if (!n) n=1;
          // integer DDA
          for (x=x0,y=y0,z=z0,cx=cy=cz=n,i=0;i<n;i++)
              {
              pixel(x,y,z,col);
              cx-=dx; if (cx<=0){ cx+=n; x+=kx; }
              cy-=dy; if (cy<=0){ cy+=n; y+=ky; }
              cz-=dz; if (cz<=0){ cz+=n; z+=kz; }
              }
          }
      //---------------------------------------------------------------------------
      void gfx_main::triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,int col)
          {
          int x,xx0,xx1,y,yy0,yy1,z,zz0,zz1,dz,dx,kz,cz;
          // boundary line coordinates to buffers
          _triangle_line(x0,y0,z0,x1,y1,z1);
          _triangle_line(x1,y1,z1,x2,y2,z2);
          _triangle_line(x2,y2,z2,x0,y0,z0);
          // y range
          yy0=y0; if (yy0>y1) yy0=y1; if (yy0>y2) yy0=y2;
          yy1=y0; if (yy1<y1) yy1=y1; if (yy1<y2) yy1=y2;
          // fill with horizontal lines
          for (y=yy0;y<=yy1;y++)
           if ((y>=0)&&(y<ys))
              {
              if (pl[y].x<pr[y].x){ xx0=pl[y].x; zz0=pl[y].z; xx1=pr[y].x; zz1=pr[y].z; }
              else                { xx1=pl[y].x; zz1=pl[y].z; xx0=pr[y].x; zz0=pr[y].z; }
                    dx=xx1-xx0;
              kz=0; dz=zz1-zz0; if (dz>0) kz=+1;  if (dz<0) { kz=-1; dz=-dz; }
              for (cz=dx,x=xx0,z=zz0;x<=xx1;x++)
                  {
                  pixel(x,y,z,col);
                  cz-=dz; if (cz<=0){ cz+=dx; z+=kz; }
                  }
              }
          }
      //---------------------------------------------------------------------------
      void gfx_main::_triangle_line(int x0,int y0,int z0,int x1,int y1,int z1)
          {
          pbuf *pp;
          int i,n,x,y,z,kx,ky,kz,dx,dy,dz,cx,cy,cz;
          // DDA variables (d)abs delta,(k)step direction
          kx=0; dx=x1-x0; if (dx>0) kx=+1;  if (dx<0) { kx=-1; dx=-dx; }
          ky=0; dy=y1-y0; if (dy>0) ky=+1;  if (dy<0) { ky=-1; dy=-dy; }
          kz=0; dz=z1-z0; if (dz>0) kz=+1;  if (dz<0) { kz=-1; dz=-dz; }
          n=dx; if (n<dy) n=dy; if (n<dz) n=dz; if (!n) n=1;
          // target buffer according to ky direction
          if (ky>0) pp=pl; else pp=pr;
          // integer DDA line start point
          x=x0; y=y0;
          // fix endpoints just to be sure (wrong division constants by +/-1 can cause that last point is missing)
          if ((y0>=0)&&(y0<ys)){ pp[y0].x=x0; pp[y0].z=z0; }
          if ((y1>=0)&&(y1<ys)){ pp[y1].x=x1; pp[y1].z=z1; }
          // integer DDA (into pbuf)
          for (x=x0,y=y0,z=z0,cx=cy=cz=n,i=0;i<n;i++)
              {
              if ((y>=0)&&(y<ys))
                  {
                  pp[y].x=x;
                  pp[y].z=z;
                  }
              cx-=dx; if (cx<=0){ cx+=n; x+=kx; }
              cy-=dy; if (cy<=0){ cy+=n; y+=ky; }
              cz-=dz; if (cz<=0){ cz+=n; z+=kz; }
              }
          }
      //---------------------------------------------------------------------------
      

      Just ignore/port the VCL stuff. I just added z coordinate to interpolation and rendering and also depth buffer. The rendering code looks like this:

      //---------------------------------------------------------------------------
      gfx_main gfx;
      //---------------------------------------------------------------------------
      float myFunc(float x,float y)
          {
          float z;
          x-=gfx.xs/2;
          y-=gfx.ys/2;
          z=sqrt(((x*x)+(y*y))/((gfx.xs*gfx.xs)+(gfx.ys*gfx.ys)));    // normalized distance from center
          z=((0.25*cos(z*8.0*M_PI)*(1.0-z))+0.5)*gfx.ys;
          return z;
          }
      //---------------------------------------------------------------------------
      void view3d(int &x,int &y,int &z)   // 3D -> 2D view (projection)
          {
          int zz=z;
          z=y;
          x=x +(y/2)-(gfx.xs>>2);
          y=zz+(y/2)-(gfx.ys>>2);
          }
      //---------------------------------------------------------------------------
      void draw()
          {
          int i,x,y,z,ds,x0,y0,z0,x1,y1,z1,x2,y2,z2,x3,y3,z3,col;
      
          gfx.clear(-0x7FFFFFFF,0x00000000);
      
          // render
          ds=gfx.xs/50;
          for (i=0;i<2;i++)   // 2 passes
           for (y=ds;y<gfx.ys;y+=ds)
            for (x=ds;x<gfx.xs;x+=ds)
              {
              // 4 vertexes of a quad face
              x0=x-ds; y0=y-ds; z0=myFunc(x0,y0);
              x1=x;    y1=y0;   z1=myFunc(x1,y1);
              x2=x;    y2=y;    z2=myFunc(x2,y2);
              x3=x0;   y3=y;    z3=myFunc(x3,y3);
              // camera transform
              view3d(x0,y0,z0);
              view3d(x1,y1,z1);
              view3d(x2,y2,z2);
              view3d(x3,y3,z3);
              if (i==0) // first pass
                  {
                  // render (just to depth)
                  col=col_transparent;
                  gfx.triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,col);
                  gfx.triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,col);
                  }
              if (i==1) // second pass
                  {
                  // render wireframe
                  col=0x00FF0000;
                  gfx.line(x0,y0,z0,x1,y1,z1,col);
                  gfx.line(x1,y1,z1,x2,y2,z2,col);
                  gfx.line(x2,y2,z2,x3,y3,z3,col);
                  gfx.line(x3,y3,z3,x0,y0,z0,col);
                  }
              }
      // here gfx.scr holds your rendered image
      //---------------------------------------------------------------------------
      

      Do not forget to call gfx.resize(xs,ys) with resolution of your view before rendering. As you can see I used different function (does not matter) here the output:

      And here the same without depth condition in pixel(x,y,z,col)

      The pbuf structure holds all the stuff that will be interpolated in the last rendering interpolation of the horizontal lines. So if you want gourard, textures or whatever you just add the variable to this structure and add the interpolation to the code (mimic the pbuf[].z interpolation code)

      However this approach has one drawback. Your current approach interpolates one axis pixel by pixel and the other is stepping by grid size. This one is stepping both axises by grid size. So if you want to have the same behavior you might to do the first pass with 1 x 1 quads instead of ds x ds and then do the lines as you do now. In case 1 in your view is corresponding to pixel then you can do this on pixels alone without the face rendering however you risk holes in the output.

      这篇关于如何隐藏3D绘图的不可见元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆