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

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

问题描述

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

function 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 轴绘制一条到最近单元格的线(步长=10,ds=0.0

    for (let x3 = width; x3 >= - width; x3 -= step) {for (let y3 = -height; y3 <= height; y3 += step) {for (让 s = 0; s 

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

function drawPixel3d(x3, y3, z3) {让 x2 = (x3 + y3) * Math.sin((60 * pi)/180);让 y2 = z3 - ((x3 - y3) * Math.sin((30 * pi)/180))/4;绘制像素(x2,y2);}

如下图所示,我得到了一个不错的图形,但有一个问题:我绘制了所有的点,而不仅仅是那些可见的点.

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

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

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

为了实现这一点,我修改了我的drawPixel3d"方法:

function 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 图形?

谢谢!

附言程序的最后一段(说明实际绘制时Y坐标的反转):

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

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

解决方案

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

你有 3 个明显的选择.

  1. 使用光线追踪

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

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

    当你渲染线框时,你需要分 2 次完成:

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

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

    但是您需要使用面部渲染(三角形或四边形...),我认为这是软件渲染,因此如果您不熟悉此类内容,请参阅:

    • 这里同样没有深度条件 pixel(x,y,z,col)

      pbuf 结构包含将在水平线的最后一次渲染插值中插值的所有内容.因此,如果您想要 gourard、纹理或其他任何东西,您只需将变量添加到此结构并将插值添加到代码中(模仿 pbuf[].z 插值代码)

      然而,这种方法有一个缺点.您当前的方法是逐个像素地插入一个轴,另一个是按网格大小步进.这是按网格大小步进两个轴.因此,如果您想拥有相同的行为,您可以使用 1 x 1 四边形而不是 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天全站免登陆