C语言3D制作2:摄像头成像方式+3D贴图

上一篇博客(C语言伪3D制作-CSDN博客)介绍了一种迷惑性的“伪3D”成像方式,这一次分享一种常用于第一人称游戏的摄像头成像方式和C语言3D贴图的制作。 

文件下载地址:文件下载-奶牛快传 Download |CowTransfer

(程序需要使用EGE图形库,不支持easyX,如果用的是easyX,请自行更改代码,或下载自带EGE的redpandaDEVc++(EGE与easyX不兼容),下载地址:文件下载-奶牛快传 Download |CowTransfer,EGE官网:Easy Graphics Engine))

为便于使用,把要用的函数都打包成.h文件了

成品:

514f4ecc119fd3f99b1e69ac102a5121.png

一、摄像头成像方式 

        这种成像方式简单来说就是模拟一个摄像头,把摄像头收集到的图像显示出来,我们可以创建一个镜头焦距可变的摄像头,镜头光心z轴为oz,像距为l,左右视角为va。

4ee0bcb9a67da61412968ca2ac1dcbd0.png

之后我们就可以根据va求出对应的像距l为getwidth()/2/tan(va*PI/360) (在上一次的“伪3D”里也适用),那么就可以定义函数defineva()为

void defineva(float a)
{
	va=a;
	l=getwidth()/2/tan(a*PI/360);
}

然后再在坐标上确定一个点t,但是因为摄像头成的是倒像,所以要把得到的像“倒”过来,那么t在屏幕上的坐标就为:(ox+t.x*l/(oz-t.z),oy+t.y*l/(oz-t.z))

(ox,oy为屏幕中心点x、y坐标)

df398e6bc3088aee4745803a0cad2b42.png

那么我们就可以定义一个函数来绘制3d点,如下:

void draw3ddot(VECTOR3D *t,color_t c)
{
	if(t->z>oz){
		return;
	}
    //mode为储存成像方式的bool变量,0为“伪3D”,1为摄像头成像方式
	if(mode==0){
		putpixel(ox+t->x*l/(l+oz-t->z),oy+t->y*l/(l+oz-t->z),c);
	}
	else
	{
		putpixel(ox+t->x*l/(oz-t->z),oy+t->y*l/(oz-t->z),c);
        //画点函数,根据计算出的位置和引用的color_t变量在屏幕上绘制点
	}
}

 二、3D贴图制作

1、原理

        制作3D贴图的主要原理就是把平面上的点全部列举出来,然后计算出点在屏幕上的位置和对应的颜色并绘制在屏幕上。

2、计算方法

        首先我们确定需要引用的元素,用VECTOR3D结构体lt,lb,rt,rb来表示图像的左上、左下、右上、右下坐标,再引用一个PIMAGE图片,把图片“贴”上去。

然后,创建int变量w和h(必须要用int,因为w和h代表的是分成的份数,尽量接近1像素),储存最大的横向边长和纵向边长,可以用3D两点距离公式 l=√(a.x-b.x)²+(a.y-b.y)²+(a.z-b.z)²求出,再取出平面中z轴最大的点,根据这个点的z轴对应的x,y平面大小与屏幕的大小之比给w、h进行缩放,减少漏算和不必要的计算。

ea8037e4f33436807a003aa0700c74ca.png

这样我们就可以依次遍历点。依靠遍历点的x(列)和y(行),根据图形的相似求出点t所在的两条线段的端点坐标为:

                a1.x=lt.x+(rt.x-lt.x)*i/w;
                a1.y=lt.y+(rt.y-lt.y)*i/w;
                a1.z=lt.z+(rt.z-lt.z)*i/w;
                a2.x=lb.x+(rb.x-lb.x)*i/w;
                a2.y=lb.y+(rb.y-lb.y)*i/w;
                a2.z=lb.z+(rb.z-lb.z)*i/w;(i为遍历的x)

                b1.x=lt.x+(lb.x-lt.x)*j/h;
                b1.y=lt.y+(lb.y-lt.y)*j/h;
                b1.z=lt.z+(lb.z-lt.z)*j/h;
                b2.x=rt.x+(rb.x-rt.x)*j/h;
                b2.y=rt.y+(rb.y-rt.y)*j/h;
                b2.z=rt.z+(rb.z-rt.z)*j/h;(j为遍历的y)

f0db48bc3e73d91540c0a2ff0f3af883.png

由此,可以列一个方程组,设ta1:a1a2=a,tb1:b1b2=b,则:

                      (1)  a1.x+a*(a2.x-a1.x)=b1.x+b*(b2.x-b1.x),
                      (2) a1.y+a*(a2.y-a1.y)=b1.y+b*(b2.y-b1.y),
                      (3) a1.z+a*(a2.z-a1.z)=b1.z+b*(b2.z-b1.z),

解方程(1)、(2),得:

a=((a1.x-b1.x)/(b2.x-b1.x)-(a1.y-b1.y)/(b2.y-b1.y))/((a2.y-a1.y)/(b2.y-b1.y)-(a2.x-a1.x)/(b2.x-b1.x));

但是当a2.x-a1.x=0或b2.x-b1.x=0时,方程的解就会出错,所以我们应该根据不同的情况列出不同的方程,如下:

当a2.x==a1.x && b2.x!=b1.x:

b=(a1.x-b1.x)/(b2.x-b1.x);

当b2.x==b1.x && a2.x!=a1.x:

a=(b1.x-a1.x)/(a2.x-a1.x);

当a1.y==a2.y && b1.y!=b2.y:

b=(a1.y-b1.y)/(b2.y-b1.y);

当b1.y==b2.y && a1.y!=a2.y:

a=(b1.y-a1.y)/(a2.y-a1.y);

当a1.z==a2.z && b1.z!=b2.z:

b=(a1.z-b1.z)/(b2.z-b1.z);

当b1.z==b2.z && a1.z!=a2.z:

a=(b1.x-a1.x)/(a2.x-a1.x);

当a1.x!=a2.x && b1.x!=b2.x && a1.y!=a2.y && b1.y!=b2.y:

a=((a1.x-b1.x)/(b2.x-b1.x)-(a1.y-b1.y)/(b2.y-b1.y))/((a2.y-a1.y)/(b2.y-b1.y)-(a2.x-a1.x)/(b2.x-b1.x));

当a1.y!=a2.y && b1.y!=b2.y && a1.z!=a2.z && b1.z!=b2.z:

a=((a1.y-b1.y)/(b2.y-b1.y)-(a1.z-b1.z)/(b2.z-b1.z))/((a2.z-a1.z)/(b2.z-b1.z)-(a2.y-a1.y)/(b2.y-b1.y));

当a1.x!=a2.x && b1.x!=b2.x && a1.z!=a2.z && b1.z!=b2.z:

a=((a1.x-b1.x)/(b2.x-b1.x)-(a1.z-b1.z)/(b2.z-b1.z))/((a2.z-a1.z)/(b2.z-b1.z)-(a2.x-a1.x)/(b2.x-b1.x));

解出a,b后就可以根据

t.x=a1.x+a*(a2.x-a1.x);                        t.x=b1.x+b*(b2.x-b1.x);
t.y=a1.y+a*(a2.y-a1.y);                        t.y=b1.y+b*(b2.y-b1.y);
t.z=a1.z+a*(a2.z-a1.z);                        t.z=b1.z+b*(b2.z-b1.z);

求出t的坐标,使用这种方法不止可以实现3D贴图,还可以实现图像的局部缩放。

求出t的坐标后,我们还可以根据遍历t点的x(列)、y(行)求出图像中对应的像素颜色。

首先,我们要先定义一个color_t*指针p=getbuffer(image);,用于查看和改变图像像素,查看方式为p[y*getwidth(image)+x],则遍历t点的x(列)、y(行)对应的图像像素就是

p[x*getheight(image)/h*getwidth(image)+y*getwidth(image)/w]

这样就可以改变图像的形状(凹四边形除外),如果不需要,可以直接利用等比例线段来求交点,但是这样只能把图像转换成长方形、梯形、平行四边形或三角形。

3、判断重合

        加载图像的时候如果直接绘制图片就会失去空间感,没有层次,那么我们就应该在画点的时候加一个判断,判断是否被其他点挡住。

为了判断重合,我们应该创建一个数组,储存屏幕上所有像素对应点的z轴,数组长度就应该为屏幕高度*屏幕宽度,但是如果屏幕太大,就会超过C语言的最大数组长度,那么我们可以使用动态内存定义float*指针wxy = (float*)malloc(l*sizeof(float));,l为分配长度,如果要改变长度,可以使wxy=(float*)realloc(wxy,l*sizeof(float));,查询方式和一维数组一样(本来想用这个来创建队列,结果一运行程序就直接退出了)

4、代码

wxy的定义和“归零”(默认值为屏幕z轴):

float *wxy = (float*)malloc(0*sizeof(float));
void emptywxy(float *wxy)
{
	int i;
	for(i=0;i<getwidth()*getheight();i++){
		*(wxy+i)=oz;
	}
}

求3D直线交点函数(为了节省内存,所以都引用指针,反正也不改变变量的值):

VECTOR3D findIntersection(VECTOR3D *a1, VECTOR3D *a2, VECTOR3D *b1, VECTOR3D *b2) {
    VECTOR3D t;
    float p=0;
    if(a2->x==a1->x && b2->x!=b1->x)
	{
		p=(a1->x-b1->x)/(b2->x-b1->x);
		t.x=b1->x+p*(b2->x-b1->x);
		t.y=b1->y+p*(b2->y-b1->y);
		t.z=b1->z+p*(b2->z-b1->z);
		return t;
	}
	if(b2->x==b1->x && a2->x!=a1->x)
	{
		p=(b1->x-a1->x)/(a2->x-a1->x);
		t.x=a1->x+p*(a2->x-a1->x);
		t.y=a1->y+p*(a2->y-a1->y);
		t.z=a1->z+p*(a2->z-a1->z);
		return t;
	}
	if(a1->y==a2->y && b1->y!=b2->y)
	{
		p=(a1->y-b1->y)/(b2->y-b1->y);
		t.x=b1->x+p*(b2->x-b1->x);
		t.y=b1->y+p*(b2->y-b1->y);
		t.z=b1->z+p*(b2->z-b1->z);
		return t;
	}
	if(b1->y==b2->y && a1->y!=a2->y)
	{
		p=(b1->y-a1->y)/(a2->y-a1->y);
		t.x=a1->x+p*(a2->x-a1->x);
		t.y=a1->y+p*(a2->y-a1->y);
		t.z=a1->z+p*(a2->z-a1->z);
		return t;	
	}
	if(a1->z==a2->z && b1->z!=b2->z)
	{
		p=(a1->z-b1->z)/(b2->z-b1->z);
		t.x=b1->x+p*(b2->x-b1->x);
		t.y=b1->y+p*(b2->y-b1->y);
		t.z=b1->z+p*(b2->z-b1->z);
		return t;	
	}
	if(b1->z==b2->z && a1->z!=a2->z)
	{
		p=(b1->x-a1->x)/(a2->x-a1->x);
		t.x=a1->x+p*(a2->x-a1->x);
		t.y=a1->y+p*(a2->y-a1->y);
		t.z=a1->z+p*(a2->z-a1->z);
		return t;
	}
	if(a1->x!=a2->x && b1->x!=b2->x && a1->y!=a2->y && b1->y!=b2->y)
	{
		p=((a1->x-b1->x)/(b2->x-b1->x)-(a1->y-b1->y)/(b2->y-b1->y))/((a2->y-a1->y)/(b2->y-b1->y)-(a2->x-a1->x)/(b2->x-b1->x));
		t.x=a1->x+p*(a2->x-a1->x);
		t.y=a1->y+p*(a2->y-a1->y);
		t.z=a1->z+p*(a2->z-a1->z);
		return t;
	}
	if(a1->y!=a2->y && b1->y!=b2->y && a1->z!=a2->z && b1->z!=b2->z)
	{
		p=((a1->y-b1->y)/(b2->y-b1->y)-(a1->z-b1->z)/(b2->z-b1->z))/((a2->z-a1->z)/(b2->z-b1->z)-(a2->y-a1->y)/(b2->y-b1->y));
		t.x=a1->x+p*(a2->x-a1->x);
		t.y=a1->y+p*(a2->y-a1->y);
		t.z=a1->z+p*(a2->z-a1->z);
		return t;
	}
	if(a1->x!=a2->x && b1->x!=b2->x && a1->z!=a2->z && b1->z!=b2->z)
	{
		p=((a1->x-b1->x)/(b2->x-b1->x)-(a1->z-b1->z)/(b2->z-b1->z))/((a2->z-a1->z)/(b2->z-b1->z)-(a2->x-a1->x)/(b2->x-b1->x));
		t.x=a1->x+p*(a2->x-a1->x);
		t.y=a1->y+p*(a2->y-a1->y);
		t.z=a1->z+p*(a2->z-a1->z);
		return t;
	}
}

 贴图函数:

void image(VECTOR3D *lt,VECTOR3D *lb,VECTOR3D *rt,VECTOR3D *rb,PIMAGE *image,float *wxy,bool s)//s为是否判断重合,0为判断重合,1为不判断重合
{
	int w;
	int h;
	int tx,ty;
	float wt=sqrt(pow(lt->x-rt->x,2)+pow(lt->y-rt->y,2)+pow(lt->z-rt->z,2));
	float wb=sqrt(pow(lb->x-rb->x,2)+pow(lb->y-rb->y,2)+pow(lb->z-rb->z,2));
	float hl=sqrt(pow(lt->x-lb->x,2)+pow(lt->y-lb->y,2)+pow(lt->z-lb->z,2));
	float hr=sqrt(pow(rt->x-rb->x,2)+pow(rt->y-rb->y,2)+pow(rt->z-rb->z,2));
    //计算4条边的长度
	int i,j;
	VECTOR3D a1;
	VECTOR3D a2;
	VECTOR3D b1;
	VECTOR3D b2;
	VECTOR3D t;
	t.z=lt->z;
	if(t.z<lb->z){
		t.z=lb->z;
	}
	if(t.z<rt->z){
		t.z=rt->z;
	}
	if(t.z<rb->z){
		t.z=rb->z;
	}
    //求出最前端的点的z轴值
	if(wt>=wb)
	{
		w=wt*1.1;//为了避免偶然漏算,所以乘个1.1
		if(mode==0)
		{
			w*=l/(oz-t.z+l);
		}
		else
		{
			w*=l/(oz-t.z);
		}
	}
	else
	{
		w=wb*1.1;
		if(mode==0)
		{
			w*=l/(oz-t.z+l);
		}
		else
		{
			w*=l/(oz-t.z);
		}
	}
	if(hl>=hr)
	{
		h=hl*1.1;
		if(mode==0)
		{
			h*=l/(oz-t.z+l);
		}
		else
		{
			h*=l/(oz-t.z);
		}
	}
	else
	{
		h=hr*1.1;
		if(mode==0)
		{
			h*=l/(oz-t.z+l);
		}
		else
		{
			h*=l/(oz-t.z);
		}
	}
	color_t* p=getbuffer(*image);
    //定义图片指针
	for(i=0;i<w;i++)
	{
		a1.x=lt->x+(rt->x-lt->x)*i/w;
		a1.y=lt->y+(rt->y-lt->y)*i/w;
		a1.z=lt->z+(rt->z-lt->z)*i/w;
		a2.x=lb->x+(rb->x-lb->x)*i/w;
		a2.y=lb->y+(rb->y-lb->y)*i/w;
		a2.z=lb->z+(rb->z-lb->z)*i/w;
		for(j=0;j<h;j++)
		{
			b1.x=lt->x+(lb->x-lt->x)*j/h;
			b1.y=lt->y+(lb->y-lt->y)*j/h;
			b1.z=lt->z+(lb->z-lt->z)*j/h;
			b2.x=rt->x+(rb->x-rt->x)*j/h;
			b2.y=rt->y+(rb->y-rt->y)*j/h;
			b2.z=rt->z+(rb->z-rt->z)*j/h;
			t=findIntersection(&a1,&a2,&b1,&b2);
            //调用求交点函数
			if(mode==0){
				tx=ox+t.x*l/(l+oz-t.z);
				ty=oy+t.y*l/(l+oz-t.z);
			}
			else
			{
				tx=ox+t.x*l/(oz-t.z);
				ty=oy+t.y*l/(oz-t.z);
			}
            //判断成像方式并储存点在屏幕上的位置
			if((*(wxy+tx+ty*getwidth())<t.z or *(wxy+tx+ty*getwidth())==oz)&&t.z<=oz or s)//判断是否重合
			{
				*(wxy+tx+ty*getwidth())=t.z;
				putpixel(tx,ty,p[j*getheight(*image)/h*getwidth(*image)+i*getwidth(*image)/w]);
			}
		}
	}
}

5、示例代码:

#include"found3d.h"//引用自制.h文件
main()
{
	initgraph(450,300,INIT_RENDERMANUAL);
	setcaption("3D贴图");
	wxy=(float*)realloc(wxy,getwidth()*getheight()*sizeof(float));
    //设置wxy长度
	defineva(70);
    //设置视角,成像模式为默认的摄像头成像方式
	ox=(getwidth()+1)/2;
	oy=(getheight()+1)/2;
    //计算屏幕中心点坐标
	oz=400;
    emptywxy(wxy);
    //“归零”wxy
	PIMAGE a=newimage();
	getimage(a,"3D贴图.png");
	PIMAGE t=newimage();
	VECTOR3D point[8];
	int i,j,k;
	for(i=0;i<2;i++)
	{
		for(j=0;j<2;j++)
		{
			for(k=0;k<2;k++)
			{
				point[i*4+j*2+k].x=50*pow(-1,i);
				point[i*4+j*2+k].y=50*pow(-1,j);
				point[i*4+j*2+k].z=50*pow(-1,k);
			}
		}  
	}
    //定义图片和方块点坐标
	while(1)
	{	
		for(i=0;i<8;i++)
		{
			turn3d_y(&point[i],3);//自制旋转函数,绕y轴旋转
		}
        //依次旋转点
		getimage(t,a,209,209,209,209);
		image(&point[7],&point[5],&point[3],&point[1],&t,wxy,0);
		getimage(t,a,209,0,209,209);
		image(&point[6],&point[7],&point[2],&point[3],&t,wxy,0);
		getimage(t,a,0,209,209,209);
		image(&point[6],&point[4],&point[7],&point[5],&t,wxy,0);
		getimage(t,a,209*2,209,209,209);
		image(&point[3],&point[1],&point[2],&point[0],&t,wxy,0);
		getimage(t,a,209*3,209,209,209);
		image(&point[2],&point[0],&point[6],&point[4],&t,wxy,0);
		getimage(t,a,209,209*2,209,209);
		image(&point[5],&point[4],&point[1],&point[0],&t,wxy,0);
        //在6个面上绘制图片
		emptywxy(wxy);
        //“归零”wxy,准备下一次绘制
		delay_fps(30);
		cleardevice();
        //刷新屏幕
	}
}

20240205_203124


  • 22
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值