您的表面是凹的,这意味着您无法使用基于面法线和相机视图方向之间的点积的简单方法。
为此,您有 3 个明显的选择。
-
使用光线追踪
当你得到表面的解析方程时,这可能是更好的方法
-
使用深度缓冲来掩盖不可见的东西
当渲染线框时,您需要分两遍完成此操作:
- 渲染不可见的填充表面(仅填充深度缓冲区而不是屏幕)
- 渲染线框
您的深度缓冲区条件还必须包含相等的值,因此z<=depth[y][x]
or z>=depth[y][x]
但是,您需要使用面部渲染(三角形或四边形...),我认为这是软件渲染,因此如果您不熟悉此类内容,请参阅:
- 如何光栅化旋转矩形(通过 setpixel 在 2d 中)
- 填充三角形的算法
-
通过利用拓扑来使用深度排序
如果您没有视图变换,那么您的x,y,z
坐标直接对应于相机空间坐标,然后您可以在其中渲染网格从后到前只需通过对 for 循环和迭代方向进行排序(常见于等距意见)。这不需要深度缓冲,但是您需要渲染填充QUADS 以获得正确的输出(边框设置为绘图颜色,内部填充背景颜色)。
我确实去了#2方法。当我将最后一个链接移植到 3D 中时,我得到了这个(C++ 代码):
//---------------------------------------------------------------------------
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; }
}
}
//---------------------------------------------------------------------------
只需忽略/移植 VCL 内容即可。我刚刚添加了z
坐标插值和渲染以及深度缓冲区。渲染代码如下所示:
//---------------------------------------------------------------------------
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
//---------------------------------------------------------------------------
别忘了打电话gfx.resize(xs,ys)
渲染前视图的分辨率。正如你所看到的,我在这里使用了不同的函数(无关紧要),输出:
这里同样没有深度条件pixel(x,y,z,col)
The pbuf
结构保存将在水平线的最后渲染插值中插值的所有内容。因此,如果你想要 gourard、纹理或其他任何东西,你只需将变量添加到这个结构中,并将插值添加到代码中(模仿pbuf[].z
插值代码)
然而这种方法有一个缺点。您当前的方法是逐个像素地对一个轴进行插值,另一个轴则按网格大小步进。这个是按网格大小步进两个轴。因此,如果您想要具有相同的行为,您可能会执行第一遍1 x 1
四边形而不是ds x ds
然后像现在一样做线条。如果您视图中的 1 对应于像素,那么您可以单独对像素执行此操作,而无需进行面部渲染,但是您可能会面临输出中出现漏洞的风险。