ZFXEngine开发笔记之Bump Mapping(1)

作者:i_dovelemon

日期:2014 / 9 / 7

来源:CSDN博客

主题:Bump Mapping, Tangent Space, Normal Map, Height Map

引言

我们知道,在真实世界中,任何物体的表面都不是非常光滑的,这种粗糙的表面效果,在光源的照射下会有明暗的感觉。那么,如何在3D图形中模拟这种效果了?是通过建模的方法,为模型的表面建造出粗糙的表面?这种方法很难做出真实的粗糙感出来,毕竟表面的粗糙程度是很细微的效果,很难用建模的方式建模出来。所以,只能用其他的方法来代替了。在3D图形学中,有一种称为Bump Mapping的技术,这个技术可以实现模型的表面粗糙感,而不需要额外的为模型增加顶点数据。

Bump Mapping

想要营造这种明暗效果,很显然,我们需要通过光照计算来模拟。要进行光照计算,自然需要光源和法线。光源,我们可以很简单的使用颜色值,光源位置来定义就可以了。而对于法线,因为我们不是增加顶点,改变模型的方式,所以,仅仅有顶点的法线,然后经过插值很难做出粗糙的感觉。我们需要的是,在这个模型渲染到最后的表面的时候,每一个像素的法线都需要不同,都需要根据不同的明度和暗度来营造。

而每一个像素的法线,如何计算了?想象一下,我们可以为模型的表面贴上一个凹凸不平的贴图,但是这种贴图在光源的效果下,并不会有粗糙材质在光源照耀下的明暗感觉。为了能够让这个贴图有那种凹凸明暗的感觉,我们就需要为每一个像素弄出一个法线出来。这样,随着光源不断的移动,我们对每一个像素进行光照计算,这种,每一个像素的明暗效果就可以出现了。所以剩下的问题就是,如何设计贴图每一个像素的法线,从而来营造出我们想要的凹凸粗糙感了?

在3D图形学中,有一种图,称为Height Map(高度图)的东西。如下所示为一张高度图:

从上面的图中可以看出,高低不平,黑色的地方表示很低,白色的地方表示很高。这种高度图,实际上是一种灰度图,也就是说,它的颜色值中RGB的各个分量实际上都是一样的。

好了,有了这种凹凸的效果,接下来,我们的任务就是根据这张高度图来构建每一个像素法线。由于我们不能在Shader中实时的构建,那样效率太差,所以我们需要预先根据这种图来构建所有纹理像素的法线,由于法线是向量,所以也可以使用三个分量来表示,所以,我们完全可以将法线保存在RGB值中,只不过要将法线各个分量的值缩放到0-255范围之类就可以了。

下面的函数用于将BMP格式的高度图计算为法线,并且存在在另外一个相同尺寸BMP位图中,这张图就称为Normal Map:

<span style="font-family:Microsoft YaHei;">/**
 * Suppose the texture is 32 Bit format heightmap and needs to be
 * converted to be a normal map instead.
 */
HRESULT ZFXD3DSkinManager::convertToNormalmap(ZFXTEXTURE *pTexture)
{
   HRESULT hr= ZFX_OK;

   D3DLOCKED_RECT  d3dRect;
   D3DSURFACE_DESC desc;

   // get a dummy pointer
   LPDIRECT3DTEXTURE9 pTex = ((LPDIRECT3DTEXTURE9)pTexture->pData);

   pTex->GetLevelDesc(0, &desc);

   if (FAILED(pTex->LockRect(0, &d3dRect, NULL, 0))) {
      log("error: cannot lock texture to copy pixels \"%s\"", pTexture->chName);
      return ZFX_BUFFERLOCK;
      }

   // get pointer to pixel data
   DWORD* pPixel = (DWORD*)d3dRect.pBits;

   // build normals at each pixel
   for (DWORD j=0; j<desc.Height; j++) {
      for (DWORD i=0; i<desc.Width; i++) {
         DWORD color00 = pPixel[0];
         DWORD color10 = pPixel[1];
         DWORD color01 = pPixel[d3dRect.Pitch/sizeof(DWORD)];

         float fHeight00 = (float)((color00&0x00ff0000)>>16)/255.0f;
         float fHeight10 = (float)((color10&0x00ff0000)>>16)/255.0f;
         float fHeight01 = (float)((color01&0x00ff0000)>>16)/255.0f;

         ZFXVector vcPoint00( i+0.0f, j+0.0f, fHeight00 );
         ZFXVector vcPoint10( i+1.0f, j+0.0f, fHeight10 );
         ZFXVector vcPoint01( i+0.0f, j+1.0f, fHeight01 );
         ZFXVector vc10 = vcPoint10 - vcPoint00;
         ZFXVector vc01 = vcPoint01 - vcPoint00;

         ZFXVector vcNormal;
         vcNormal.cross(vc10, vc01);
         vcNormal.normalize();

         // store normal as RGBA in normalmap
         *pPixel++ = VectortoRGBA( &vcNormal, fHeight00 );
         }
      }
   pTex->UnlockRect(0);

	LPDIRECT3DSURFACE9 pSurface=NULL;
	pTex->GetSurfaceLevel(0, &pSurface);
	D3DXSaveSurfaceToFile("normal.bmp", D3DXIFF_BMP, pSurface, NULL, NULL);

   return ZFX_OK;
} // ConvertToNormalmap</span>

从高度图中计算法线的方法是取出相邻的是三个点来构建这三个点的法线。比如,当前像素点为A,那么第二个点为A右边相邻的一个像素点B,接下来的第三个点就是A点下面的那个相邻的点C。由于是灰度图,所以颜色值的三个分量都是相同的,所以我选择R分量来构建出与A像素点相对应的向量点的Z值,并且使用A点像素在位图中的位置xy,作为构建向量的x,y。通过同样的方法,我们依次建立三个点的向量,然后计算出最终的法线。我们对法线进行归一化操作,然后通过VectortoRGBA将法线转化为RGB值,保存在新的BMP图像中。

下面是VectortoRGBA的函数:

<span style="font-family:Microsoft YaHei;">// encode vector data as RGBA color value for normal map
DWORD VectortoRGBA(ZFXVector *vc, float fHeight) {
   DWORD r = (DWORD)( 127.0f * vc->x + 128.0f );
   DWORD g = (DWORD)( 127.0f * vc->y + 128.0f );
   DWORD b = (DWORD)( 127.0f * vc->z + 128.0f );
   DWORD a = (DWORD)( 255.0f * fHeight );
   return( (a<<24L) + (r<<16L) + (g<<8L) + (b<<0L) );
} // VectortoRGBA</span>

这个函数还将Normal map保存在normal.bmp中,下面是上面的高度图对应的normal.bmp:

总结

这篇文章,暂时简单的讲述了如何根据高度图来构建法线图。在接下来的文章中,将会向大家讲述如何根据这个法线图来进行光照计算,从而达到凹凸明暗的效果。并且在接下来将会讲述在3D图形学中很重要的一个概念:Tangent Space。

时间: 2024-10-29 19:10:02

ZFXEngine开发笔记之Bump Mapping(1)的相关文章

ZFXEngine开发笔记之Bump Mapping(2)

作者:i_dovelemon 日期: 2014 / 9 / 13 来源 : CSDN 主题 :Bump Mapping, Tangent Space, Normal Map, Height Map 引言 在上篇文章中,我讲述了如何根据高度图来创建法线图.并且承诺在后面会讲述3D几何学中的重要工具Tangent Space的相关知识.今天,就在这里,向大家讲述如何接下来的工作部分. Tangent Space 我们知道,在前篇文章中,讲述的法线图中的法线是在纹理图的空间中也就是Tangent Sp

ZFXEngine开发笔记-SSE版本的ZFXVector

SSE介绍 在学习3D游戏编程大师技巧的时候,就了解到,可是使用一种称之为"单指令,多数据(SIMD)"的技术来编写3D数学库.通过这样的方法,可以将我们经常使用的诸如向量计算,矩阵变换等操作加快很多倍.这次,在学习3D引擎开发的时候,也用到了这个技术.SIMD是一种技术的名称,而并不是具体的工具.实现这种技术,不同的CPU厂商推出了不同的技术,像MMX, 3DNow!, SSE, SSE2, SSE3....由于我的计算机上使用的是Intel的处理器,它支持MMX,SSE,SSE2,

ZFXEngine开发笔记之3D模型格式支持(1)

作者:i_dovelemon 来源:CSDN 日期:2014 / 9 / 17 主题:3D Format, Milk 3D Shape, Chunk Based System, Skeleton Animation (文中以红色中字标示的文字,是整个文章的注意项,请读者留心) 引言 在3D游戏领域,艺术家们通过3D建模软件建立自己的艺术品模型和绚丽的3D场景.当我们想要在自己的游戏中使用这些模型的时候,我们就需要将这种模型变成能够被我们的引擎所识别的文件格式.每一个建模软件都有自己的文件格式,不

ZFXEngine开发笔记之Shadow Volume

作者:i_dovelemon 来源:CSDN 日期:2014 / 10 / 20 主题: Shadow Volume 引言 游戏中,往往有很多的光影效果.想要营造出很好的光影效果,物体在光源照射下的阴影就必不可少.本节内容就向大家讲述如何构建阴影. Shadow Volume 构建阴影的方法有很多,常用的两种方法是Shadow Volume和Shadow mapping.本片博文将向大家讲述使用Shadow Volume来构建阴影的方法. Shadow Volume的算法是由Frank Crow

ZFXEngine开发笔记之Omni Light

作者:i_dovelemon 日期:2014 / 9 / 5 来源:CSDN博客 主题:Per-pixel lighting, Omni light, early-z, Multi-pass, Assembly Shader 引言 在第一人称射击游戏中,经常需要点光源来完成光照计算.所以,这篇文章中将会讲述如何创建这个点光源,并且如何设计让引擎可以支持多光源渲染. Omni light介绍 所谓的Omni light,就是指点光源.不过在这里,我们进行点光源的光照计算的时候,没有用到顶点的法线来

ZFXEngine开发笔记- GPU硬件架构

作者:i_dovelemon 日期:2014 / 8 / 31 来源: CSDN博客 文章: GPU硬件架构 引言 在3D图形学中,可编程渲染管道的出现,无疑是一种创举.在下面的文章中,会向大家简要的介绍现如今的可编程渲染管道中最重要的Vertex Shader和Pixel Shader的硬件架构以及如何使用汇编语言来编写Shader. Vertex Shader 在硬件上,Vertex Shader中所有的运算都在一个名为vertex arithmetic logic unit(ALU)中进行

张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231

原文:张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231 GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/DS3231 注意:不包含闹钟设置

Android开发笔记(一百零三)地图与定位SDK

集成地图SDK 国内常用的地图SDK就是百度和高德了,二者的用法大同小异,可按照官网上的开发指南一步步来.下面是我在集成地图SDK时遇到的问题说明: 1.点击基本地图功能选项,不能打开地图,弹出"key验证出错!请在AndroidManifest.xml文件中检查key设置的"的红色字提示.查看日志提示"galaxy lib host missing meta-data,make sure you know the right way to integrate galaxy&

微信订阅号开发笔记(二)

微信开发的流程其实很简单 o(∩_∩)o 哈哈!在微信网站的编辑操作 额,就不说了.虽然有人问过.下面是我的微信开发过程,简单记录下. 成为开发者 材料:1.自己的服务器资源,百度的BAE,新浪的SAE都不错. 2.懂那么点编程语言. 3.注册微信公众号. 上面的都有了之后,就可以自己动手开发了.哇咔咔,好兴奋.有木有. 在登录进去之后,怎么成为开发者?不知道,自己看去. 开始coding吧. 1.验证 if (! empty ( $_GET ['echostr'] ) && ! empt