voxel:3D地表生成及渲染 (VOXEL)



想 跟 着 云 风 讲 解 来 慢 慢 体 会 吗? 那 么 就 先 看 看 右 边 效 果 图, 来 个 感 性 认 识 吧. yeah! 这 就 是 我 们 要 达 到 效 果 ;-) 是 不 是 和 某 些 游 戏 里 采 用 Engine 效 果 不 大 样? 是 , 我 们 不 准 备 使 用 多 边 形. 这 个 算 法 产 生 3D 地 表比 用 多 边 形 产 生 出 来 更 平 滑, 在 英 文 里 我 们 称 其 为 voxel. 它 大 量 被 用 于 现 在 模 拟 飞 行 游 戏 中, 记 得 Commache I 就 是 因 为 采 用 这 个 技 术, 而 使 我 耳 目 新, 顿 时 爱 上 了 这 个 游 戏 :-)
闲 话 不 提 了, 我 在 学 习 3D 地 表 生 成 算 法 时, 有 幸 拜 读 了 段 程 序, 深 受 启 发. 原 本 早 就 应 该 将 其 介 绍 给 大 家, 直 没 有 时 间 写 这 篇 文 章. 这 两 天 没 有 更 新 主 页, 来 这 儿 访 问 朋 友 还 是 那 么 多, 真 有 点 不 好 意 思. 就 为 大 家 花 点 时 间, 再 写 篇 有 价 值 东 东 吧:-)

和 这 个 3D 地 表 有 关 , 有 两 个 部 分: 是 生 成 算 法; 2 是 显 示 算 法.地 表 和 其 它 物 体 不 同, 它 没 有 复 杂 3D 结 构, 所 以 可 以 不 用 多 边 形 去 描 述 它. 这 里 我 们 采 用 了 个 256 x 256 数 组 来 储 存 这 个 范 围 内 个 点 高 度. 而 地 表 光 泽, 也 是 预 先 算 好 ;) 同 样 储 存 在 个 256 x 256 数 组 了. 渲 染 方 法 是 利 用 坡 度 (即 和 周 围 点 高 度 差 来 决 定 ), 当 然 你 也 可 以 考 虑 点 绝 对 高 度, 比 如 在 绝 对 高 度 高 区 域 白 些, 以 造 成 种 山 顶 积 雪 感 觉, 这 就 是 你 自 己 发 挥 了. 在 生 成 地 表 时,第 步 是 决 定 外 形 概 况,这 里 采 用 是 随 机 方 式. 由 粗 到 细, 逐 步 细 化, 每 次 地 表 上 下 波 动 幅 度, 由 运 算 面 积 来 决 定. 而 在 实 际 运 用 时, 可 以 在 随 机 过 程 中, 加 入 些 限 制, 来 控 制 地 表 生 成. 第 2 步, 就 是 将 前 面 生 成 图 象 做 平 滑 处 理, 让 每 个 点 去 和 周 围 点 运 算, 取 平 均 值, 使 不 至 于 出 现 过 大 变 化, 这 个 过 程 多 重 复 几 次 (这 里 是 3 次), 就 可 以 得 到 上 佳 效 果 了 :-) 由 于 所 有 生 成 部 分 都 是 预 先 算 好, 所 以 可 以 不 考 虑 速 度 问 题.

显 示 时, 同 样 不 需 要 过 多 数 学 知 识. 我 们 由 近 及 远 画 出 地 表 就 可 以 了. 只 要 知 道, 距 离 视 点 越 远, 看 到 高 度 就 越 低, 利 用 实 际 高 度 和 距 离, 不 难 计 算 出 应 当 在 屏 幕 上 绘 制 高 度. 如 果 你 以 前 稍 微 研 究 过 3D Engine, 就 不 难 理 解 我 意 思. 而 出 于 地 表 3D 结 构 简 单, 远 处 部 分 是 不 会 遮 挡 住 近 处 部 分 , 这 个 减 小 了 许 多 设 计 难 度. 只 是 在 处 理 视 线 和 我 们 生 成 地 图 x,y 轴 成 定 角 度 时, 需 要 使 用 点 3 角 知 识.

我 最 大 遗 憾 是, 目 前 还 没 有 搞 清 视 线 不 是 水 平 时 算 法. (如 果 使 用 多 边 形 产 生 地 表, 却 很 简 单, 这 个 是 另 篇 文 章 内 容 了)

也 许 解 说 太 简 单, 但 是 我 认 为 你如 果 能 欣 赏 下 源 代 码, 切 都 会 变 简 单. 原 来 程 序 已 经 是 很 清 晰 了, 但 作 者 还 是 加 入 了 少 许 优 化. 为 了 写 这 篇 教 学 性 质 文 章,云 风 又 将 程 序 重 写 了 遍 (使 用 Djgpp 编 译),更 是 添 加 了 非 常 详 细 中 文 注 解. 大 家 慢 慢 品 味 吧 :-)



# <stdio.h># <dos.h># <go32.h># <conio.h># <stdlib.h># <math.h># <.h># <sys/movedata.h># <sys/segments.h>// 将值限制在 0..255 的间# Clamp(x) ((x)<0 ? 0 : ((x)>255 ? 255 : (x)))// 取 x 低字节位, 即对 255 取模 (HMap 和 CMap 都是 256 x 256 )# L(x) ((x)&0xff)typedef unsigned char ; HMap[256][256]; // 地表高度 CMap[256][256]; // 色彩值 Video[320*200]; // 屏幕缓冲区// 地表高度和色彩表计算void ComputeMap(void){ p,i,j,k,k2,p2; // 从个平坦地表开始 HMap[0][0]=128; for ( p=256; p>1; p=p2 ) { p2=p/2; k=p*8+20; k2=k/2; for ( i=0; i<256; ip ) { for ( j=0; j<256; jp ) { a,b,c,d; a=HMap[i][j]; b=HMap[ L(i+p) ][j]; c=HMap[i][ L(j+p) ]; d=HMap[ L(i+p)][ L(j+p) ]; HMap[i][ L(j+p2) ]= // 在 a,c 中点,以a,c平均高度为基准 Clamp(((a+c)>>1)+(rand%k-k2)); // 产生随机高度 HMap[ L(i+p2) ][ L(j+p2) ]= // 在 a,b,c,d 区域中心,以平均高度 Clamp(((a+b+c+d)>>2)+(rand%k-k2)); // 为基准,产生随机高度 HMap[ L(i+p2) ][j]= // 在 a,b 中点,以a,b平均高度为基准 Clamp(((a+b)>>1)+(rand%k-k2)); // 产生随机高度 } } } // 平滑处理 for ( k=0; k<3; k ) for ( i=0; i<256; i ) for ( j=0; j<256; j ) { HMap[i][j]=(HMap[ L(i+1) ][j]+HMap[i][ L(j+1) ]+ //将前后左右, 4个点取 HMap[ L(i-1) ][j]+HMap[i][ L(j-1) ])/4; //平均值,这样做平滑 } // 颜色计算 (地表高度衍生物) for ( i=0; i<256; i ) for ( j=0; j<256; j ) { k=128+(HMap[ L(i+1) ][ L(j+1) ]-HMap[i][j])*4; CMap[i][j]=Clamp(k); // 以坡度决定灰度 }} lasty[320], // 画在指定列上最后个点lastc[320]; // 最后颜色// 画地表个\"部分\"; 它能画出距离视点定远处图象// 使用 lasty 中保存上次画过位置, 保正了这个部分不会// 覆盖掉以前画部分. x0,y0 和 x1,y1 和 xy 坐标描述// 地表高度, hy 是视点高度, s 是由距离决定比例因子.// x0,y0,x1,y1 是 16.16 定点数,// 比例因子是 16.8 定点值.void Line( x0, y0, x1, y1, hy, s){ i,sx,sy; // 计算 xy 速度 sx=(x1-x0)/320; sy=(y1-y0)/320; for ( i=0; i<320; i ) { c,y,h,u0,v0,u1,v1,a,b,h0,h1,h2,h3; // 计算 xy 坐标; a 和 b 将被定位于 // 个 (0..255)(0..255) 区间里面. u0=L(x0>>16); a=L(x0>>8); v0=L(y0>>16); b=L(y0>>8); u1=L(u0+1); v1=L(v0+1); // 由周围 4 个点来决定里面高度 h0=HMap[v0][u0]; h2=HMap[v1][u0]; h1=HMap[v0][u1]; h3=HMap[v1][u1]; h0=(h0<<8)+a*(h1-h0); h2=(h2<<8)+a*(h3-h2); h=((h0<<8)+b*(h2-h0))>>16; // 由周围 4 个点来决定里面颜色 (颜色值是 16.16 定点数) h0=CMap[v0][u0]; h2=CMap[v1][u0]; h1=CMap[v0][u1]; h3=CMap[v1][u1]; h0=(h0<<8)+a*(h1-h0); h2=(h2<<8)+a*(h3-h2); c=((h0<<8)+b*(h2-h0)); // 使用比例因子计算屏幕高度 y=(((h-hy)*s)>>11)+100; // 画 ( y<(a=lasty[i]) ) { unsigned char *b=Video+a*320+i; sc,cc; ( lastc[i]-1 ) lastc[i]=c; sc=(c-lastc[i])/(a-y); cc=lastc[i]; ( a>199 ) { b-=(a-199)*320; cc(a-199)*sc; a=199; } ( y<0 ) y=0; while ( y>18; ccsc; b-=320; a--; } lasty[i]=y; } lastc[i]=c; // 进步计算下个 xy 坐标 x0sx; y0sy; }}float FOV=3.141592654/4; // 45 度宽视角// 画出从点 x0,y0 (16.16) 以 a 角 看到图象void View( x0, y0,float aa){ d; a,b,h,u0,v0,u1,v1,h0,h1,h2,h3; // 清除屏幕缓冲 mem(Video,0,320*200); // 化 last-y 和 last-color for ( d=0; d<320; d ) { lasty[d]=200; lastc[d]=-1; } // 计算视点高度变量 // 计算 xy 坐标; a 和 b 将被定位于 // 个 (0..255)(0..255) 区间里面. u0=(x0>>16)&0xFF; a=(x0>>8)&255; v0=(y0>>16)&0xFF; b=(y0>>8)&255; u1=(u0+1)&0xFF; v1=(v0+1)&0xFF; // 由周围 4 个点来决定里面高度 h0=HMap[v0][u0]; h2=HMap[v1][u0]; h1=HMap[v0][u1]; h3=HMap[v1][u1]; h0=(h0<<8)+a*(h1-h0); h2=(h2<<8)+a*(h3-h2); h=((h0<<8)+b*(h2-h0))>>16; // 无覆盖由近及远画地表 for ( d=0; d<100; d1+(d>>6) ) { Line(x0+d*65536*cos(aa-FOV),y0+d*65536*sin(aa-FOV), x0+d*65536*cos(aa+FOV),y0+d*65536*sin(aa+FOV), h-30,100*256/(d+1)); } // 将最终图象 blit 到屏幕 _movedatal(_my_ds, (unsigned)Video, _dos_ds, 0xa0000, 16000); //320*200/4}void (void){ union REGS r; i,k; float ss,sa,a,s; x0,y0; // 进入 320x200x256 模式 r.w.ax=0x13; 386(0x10,&r,&r); // 设置前 64 个颜色为 64 级灰度 for ( i=0; i<64; i ) { outp(0x3C8,i); outp(0x3C9,i); outp(0x3C9,i); outp(0x3C9,i); } // 计算地图高度 ComputeMap; // 主循环 // a = 角度 // x0,y0 = 当前坐标 // s = 固定速度 // ss = 当前向前/向后速度 // sa = 旋转角速度 a=0; k=x0=y0=0; s=4096; ss=0; sa=0; while(k!=27) { // 画帧 View(x0,y0,a); // 刷新位置/角度 x0ss*cos(a); y0ss*sin(a); asa; // 处理用户输入 ( kbhit ) { ( (k=getch)0 ) k=-getch; switch(k) { -75: sa-=0.005; ; // 左 -77: sa0.005; ; // 右 -72: sss; ; // 前 -80: ss-=s; ; // 后 } } } // 退回到文本模式 r.w.ax=0x03; 386(0x10,&r,&r);} 完





Tags:  voxelhead voxel

延伸阅读

最新评论

发表评论