聚焦3D地形编程第三章纹理化地形

聚焦3D地形编程第三章纹理化地形

翻译: 神杀中龙 邵小宁 microsoftxiao@163.com
原著: 《Focus on 3D Terrain Programming》
翻译的烂请见谅

现在你已经可以制作简单的地形网格,
你需要知道怎样如何使用纹理贴图来为那令人讨厌的网格添加细节。我将继续讨论简单的纹理并且一直到我们可以使用真正有趣的片段(地形算法)作为开始。我将停止浪费空间现在,并且仅仅告诉你将在本章学习什么:

n         怎样应用巨大的单模式纹理映射到地形网格上。

n         怎样以程序式手段来产生复杂的纹理地图使用各种各种的地形 tiles

n         怎样为地形添加纹理细节甚至以前产生的纹理的更多细节。

Simple Texture Mapping 简单纹理映射

我们将使用简单纹理映射。 你将学习怎样延长一个纹理到整个地形网格。大部分时间, 如果这个技术看起来是糟糕的,当然,你已经真的制作好了纹理映射,我们将在下节继续工作。立刻计算出你学习的怎样延长纹理最终结果看起来像什么。

延长单张纹理越过整个地形, 我们将为地形上的每个顶点带上范围在0.0f-1.0f(标准的纹理坐标范围)的分量。这么做比听起来容易。出发了, 看图3.1

像图3.1展示的, 左下角地形网格( 我们将选择256x256的高度图), (0, 0)纹理坐标为(0.0f, 0.0f), 而左上角地形(255, 255),纹理坐标为(1.0f, 1.0f) 基本上,所有我们需要的是找到顶点,包括当前渲染的和划分它使用高度图。(这么做是可以产生我们期望范围内的值日, 0.0f-1.0f, 没有超过我们的范围。注意这是重要的,因为我们在后续章节将超过这个范围。)在渲染每个顶点前, 我们需要计算三个东西: 纹理值的x, zz+1, 我们将叫做fTexLeft, fTexBottom, fTexTop, 这里我们该如何计算这些值呢:

fTexLeft = (float)x/m_iSize;

fTexBottom = (float)z/m_iSize;

fTexTop = (float)(z+1)/m_iSize;

那么你可以思考下这将是困难的!无论如何, 我们需要在每个顶点被渲染前计算他们内并且然后将纹理坐标传送给我们的渲染API。当我们渲染顶点(x, z)时, 我们发送(fTexLeft, fTexBottom)作为我们的纹理坐标, 并且当我们渲染(x, z+1)时, 我们发送(fTexLeft, fTexTop)作为我们的纹理坐标。看图3.2和在CDCode"Chapter3"demo3_1上的demo3_1将看到你劳动的成果。

屏幕截图后了比我们第二章地形更多的细节,地形 101”(然而注意我移除了渐变), 但是目睹了实际形式的地形它是困难的。如图3.3延长单一纹理, 甚至如果纹理在demo3_1内使用纹理被高分辨率, 在我们的纹理地形上我们可以捕捉到一切细节。

我们需要更多细节。 我们想要纹理地图像图3.4一样,通过使用一系列程序式方法产生 tiles(泥土, 草地, 岩石还有雪地等)

如图3.4有多少细节?这个纹理帮助我们区别高山区域到低矮的平原,如图3.3 你需要知道怎样产生真正酷的纹理像下面展示的。继续阅读!

Procedural Texture Generation 程序式产生纹理

Procedural texture generation是很酷并且很有用的技术对于任何地形引擎。之后我们完成我们的程序化纹理产生器,我们将允许用户加载一系列24个它可选择的tiles。然后我们将调用我们的纹理生成函数。(所有用户都需要知道他想要创建的纹理的大小。) 那时!我们怎样着手创建我们的纹理生成函数呢?首先,你需要知道我们这里实际的目标。我们将让它和产生的高度图表现一致(如高山,那就用高山纹理,如平原,就产生平原纹理)。我们将遍历我们纹理地图的每个像素,找到高度符合该像素的并计算出每个纹理tile的像素。(每个tile有一个规定好的区域。)非常罕见的tile将被100%的可见,所有我们需要合并tile到其他tile(RGB颜色上进行插值)。这个结果将如图3.5你可以看到插值是在草地和岩石tile间进行的。

The Region System 系统区域

开始编码前,我们需要创建一个表示每个tile信息的结构。一个region, 被使用,是一系列定义好高度值范围的三个值。这个结果我们创建出来:

struct STRN_TEXTURE_REGIONS

{

       int m_iLowHeight;                // lowest possible height(0%)

       int m_iOptimalHeight;             // optimal height(100%)

    int m_iHighHeight;                // highest possible height(0%)

};

解释每个值意思将由图3.6来完成。

根据解释, 我们将让m_iLowHeight等于63并且m_iOptimalHeight等于128 计算m_iHighHeight的值需要些简单的数学。我们将让m_iOptimalHeight减去m_iLowHeight 然后我们将加上m_iOptimalHeight和前一个处理的值。我们有我们的范围集(low:63, optimal:128, high:193) 所以如图3.6那样替换那些值。 现在想象一下怎样描绘当前tile的高度值, 说, 150 想象一这个值如图3.6在线上所示, 计算出我们的范围。To save you the trouble of trying to figure it out, 如图3.7

正如你看到的图像, 纹理presence在高度(150)时为70% 现在我们知道许多信息, 那我们怎么做呢,我们计算出RGB三个一组从纹理图内并且乘以0.7f 这个结果正似乎我们当前像素想要的值。

我们需要创建一个函数将计算出提供给我们的百分区域。 这个函数相当简单。它需要两个微不足道的测试看这个高度是否真的在这个范围内;如果不在这个范围内, 退出函数。 接着,我们需要标记出高度的位于哪个区域。它在最佳值之下还是之上,或者等于最佳值?微不足道的情况是高度等于最佳值;如果是这样, 那么当前tile纹理presence为当前像素的100% 而且我们不需要担心插值过程。

如果高度在最佳值之下, 我们需要减少这些片段的值。 之后,我们用这个高度值减去这个区域较低的值。然后我们最理想的范围值是减去低范围值。我们然后从第一个计算到最后一个计算值划分这个结果, 恩迷糊! 我们有了我们的百分比presence

这是我刚刚讨论的代码:

// 高度是位于最佳值之下

if(ucHeight<m_tiles.m_regions[tileType].m_iOptimalHeight)

{

       // calculate the texture percentage for the given tile’s region

       fTemp1 = (float)m_tiles.m_regions[tileType].m_iLowHeight – ucHeight;

       fTemp2 = (float)m_tiles.m_regions[tileType].m_iOptimalHeight – m_tiles.m_regions[tileType].m_iLowHeight;

       return (fTemp1/fTemp2);

}

最后的情况是如果这个高度值在最佳范围之上。计算就要考虑的复杂些比当高度在范围下复杂,但是他们仍然不是非常难的。代码形式比文字更容易理解:

// height is above the optimal height

else if(ucHeight>m_tiles.m_regions[tileType].m_iOptimalHeight)

{

       // calculate the texture percentage for the given tile’s region

       fTemp1 = (float)m_tiles.m_regions[tileType].m_iHighHeight – m_tiles.m_regions[tileType].m_iOptimalHeight;

       return ((fTemp1 – (ucHeight – m_tiles.m_regions[tileType].m_iOptimalHeight))/fTemp1);

}

这个计算,理论上,是基于同样的低比最佳,高好的情况,除了我们必须获取场景中100%时的小部分值,它低于某高度,取代的值高过某高度!

The Tile System Tile系统

好的,现在你知道如何从一个纹理tile内获得纹理的presence了和纹理像素内。现在你需要应用你刚刚学到的来为每个纹理tiles加上这个计算并创建出整个纹理地图。这虽然比听起来容易,所以别受打击!

首先,我们需要创建纹理tile结构以管理所有的纹理tiles每个tile我们不需要更多的信息;所有我们需要替换纹理信息值且每个纹理的结构区域。我们将也想明白tiles的数量,有多少个被加载。根据这些需求,我创建了STR_TEXTURE_TILES结构体,它是这样的:

struct STRN_TEXTURE_TILES

{

       STRN_TEXTURE_REGIONS  m_regions[TRN_NUM_TILES]; // texture regions

       CIMAGE                   textureTiles[TRN_NUM_TILES]; // texture tiles

       Int                        iNumTiles;

};

其次,你需要一些管理纹理tile的函数。我需要加载和卸载单个tile的函数,还有卸载所有tiles。这些函数的实现不值一提,所以我在这里不展示它们。如果你感兴趣可以去看代码。除此之外,你要阅读地形生成函数!

开始生成函数,我们需要计算出实际加载多少纹理。(我们想要用户可以没有纹理时产生纹理。)之后要完成它,我们需要重新循环的计算出区域范围内的每个tile(我们想要tile区域被0-255的范围内隔开)。这现在我将这么做:

iLastHeight = -1;

for(i = 0; i<TRN_NUM_TILES; i++)

{

       // we only want to perform these calculations if we

       // actually have a tile loaded

       if(m_tiles.textureTiles[i].IsLoaded())

       {

              // calculate the three height boundaries

              m_tiles.m_regions[i].m_iLowHeight = iLastHeight+1;

              iLastHeight+=255/m_tiles.iNumTiles;

              m_tiles.m_regions[i].m_iOptimalHeight = iLastHeight;

              m_tiles.m_regions[i].m_iHeighHeight = (iLastHeight – m_tiles.m_regions[i].m_iLowHeight) + iLastHeight;

       }

}

The only thing that should look remotely odd here is the last segment where we calculate m_iHighHeight, 甚至它看上去不是个单数。(如果它是单数,就涉及到开始我解释的区域范围了。)

Creating the Texture Data 创建纹理数据

现在到了创建纹理数据的时候了。要做这个,我们需要创建三个不同的循环: 一个是纹理地图的Z轴,一个是X轴,还有每个tile (在函数内tile将是第三个循环。)我们也许哟啊创建三个保存运行时我们计算出的每像素的当前RGB分量。实际的生成纹理函数像这样:

for( z = 0; z<uSize; z++)

{

       for(x = 0; x<uiSize; x++)

       {

              // set out total color counters to 0.0f

              fTotalRed = 0.0f;

              fTotalGreen = 0.0f;

              fTotalBlue = 0.0f;

              // loop through the tiles

              // for the third time in this function

              for(i=0; i<TRN_NUM_TILES;i++)

              {

                     // if the tile is loaded, we can perform the calculations

                     if( m_tiles.textureTiles[i].IsLoaded())

                     {

                     }

              }

       }

}

下面,我们需要从纹理中提取出RGB值的分量(当前像素)到我们的临时RGB unsigned char变量。一旦完成,我们

需要标记出当前tilepresence在当前像素上(使用我们前面创建的函数), 临时RGB变量乘以结果,并加上我们的总RGB数量。现在我们需要设置前面解释的代码:

// get the current color in the texture at the coordinates that we

// got in GetTexCoords

m_Tiles.textureTiles[i].GetColor(uiTexX, uiTexZ, &ucRed, &ucGreen, &ucBlue);

// get the current coordinate’s blending percentage for this tile

fBlend[i] = RegionPercent( I, InterpolateHeight(x, z, fMapRatio ) );

// calculate the RGB values that will be used

fTotalRed += ucRed*fBlend[i];

fTotalGreen += ucGreen*fBlend[i];

fTotalBlue += ucBlue*fBlend[i];

之后我们需要循环处理四个tiles 我们然后设置纹理上的像素颜色,然后对下一像素重复这一过程。当我们完全完成产生纹理完成值时, 我们使用我们的图形API来设置!

Improving the Texture Generator 改进纹理生成器

好的,我要倒下了。我们还没有准备就绪。我们的纹理生成函数还有一些纹理。这些问题是:

n         我们仅仅可以使用一种分辨率或在这之下,或与我们高度图相同的方式来创建纹理。

n         如果解决这个问题,那么我们仅可以创建在我们的纹理tiles之下分辨率的tiles

然而这两个问题是关联在一起的。让我们从高度图的分辨率开始。

Getting Rid of the Heightmap Resolution Dependency 摆脱高度图的分辨率依赖性

我需要让用户选择任何他想要的纹理大小。(好,几乎任何纹理大小。我想应该是2N次方)。前面的纹理生成函数中,我们输入巨大的一系列循环,我们需要计算出高度图内纹理地图像素的比率,可以这样完成:

fMapRatio = (float)m_iSize/uiSize;

然后我们需要创建一个函数将这个值和我们展开的高度图进行插值。我们将插值划分成两个部分: 一部分是X轴到Z轴。我们将获取两个部分结果的平均值,作为我们插值的高度。最后也许最好的方式是这个东西,但是它可以工作,工作的很快!

这个函数,我们需要三个参数。前两个为X, Z坐标,我们获取它们。这非常high, 像这样,超过高度图范围。第三个是我们计算出的纹理图像素比率(fMapRatio)。函数内,我们将缩放(x,z)坐标使用比率。计算出X轴的插值像这样:

// set the middle boundary

ucLow = GetTrueHeightAtPoint( ( int )fScaledX, (int)fScaledZ );

// set the high boundary

if( (fScaledX+1) > m_iSize )

       return ucLow;

else

       ucHighX = GetTrueHeightAtPoint( (int)fScaledX+1, (int)fScaledZ);

// calculate the interpolation (For the X axis)

fInterpolation = (fScaledX – (int)fScaledX);

ucX            = ((ucHighX-ucLow)*fInterpolation)+ucLow;

正如你看到的,我们这么做获取了低高度的值给第一个东西。然后我们核对看它是否超过高度图下一个高度。如果没有,那么我们必须满足低的值。如果下个高度在高度图上,那么我们可以获取它并准备为两个值插值。我们获取不同的浮点值Xunsigned char X值。(它将低值的精确到度,将定义出总的插值。)下个计算,我们计算沿着X轴的插值。然后同样为Z轴做一次将两个结果合并,并除以2这样就完成了。

Getting Rid of the Tile Resolution Dependency 摆脱Tile的分辨率依赖

好的,我们差不多了。我们仅仅才完成一个计算,我们还需要排除tile大小的限制。解决方法也许会让你惊讶,为什么我这么说? “ 好,相信我,这需要很长时间的一个计算来解决,所以感觉很坏。我们需要重复tile!我创建了简单的函数来新建纹理坐标并重复到我们的纹理。它是这样的:

void CTERRAIN::GetTexCoords(CIMAGE texture, unsigned int* x, unsigned int* y)

{

       unsigned int uiWidth = texture.GetWidth();

       unsigned int uiHeight = texture.GetHeight();

       int iRepeatX = -1;

       int iRepeatY = -1;

       int i = 0;

       // loop until we figure out how many time the tile

       // has repeated (on the X axis)

       while(iRepeatX == -1)

       {

              i++;

              //if x is less than the total width,

              // then we found a winner!

              if(*x<(uiWidth*i))

                     iRepeatX = i-1;

       }

       // prepare to figure out the repetition on the Y axis

       i = 0;

       // loop until we figure out how many times the tile has repeated

       // (on the Y axis)

       while(iRepeatY == -1)

       {

              i++;

              // if y is less than the total height, then we have a bingo!

              if( *y<(uiHeight*i))

                     iRepeatY = i-1;

       }

       // update the given texture coordinates

       *x = *x-(uiWidth*iRepeatX);

       *y = *y-(uiHeight*iRepeatY);

}

这个函数的大部分由两个while循环组成主要目的是为计算出需要重复多少个纹理,以在它到达纹理坐标时传递参数。之后计算,我们缩放他们使用纹理的范围值。(我们不想试着开出纹理范围外的信息。这将导致一个错误,错误是不好的。)

那么!我们的纹理产生函数现在完成了!如图3.8这个demo, 你将注意到菜单里的新选项叫做纹理映射。这个选项,你可以产生高分辨率的纹理并保存纹理到当前目录。谈到这个demo, 你可以看到你努力工作的成果在CD内的Code"Chapter3"demo3_2. 打开工作区用Microsoft Visual C++,开始有趣的把玩吧。

Using Detail Maps 使用细节地图

1024x1024是相当大的数据量,我想在我们的纹理上达到这个细节。必须通过另外一种方式来达到我们不浪费资源又兼有这些细节愿望。好的,不要多想!一种酷的方式可以使用细节地图来为你的地形添加这些细节。细节地图是如图3.9那样的灰度图重复多次加到地形上并添加上细微差别,像裂缝,凹陷,岩石和其他有趣的东西。

为你的地形引擎添加细节地图支持是个简单的过程。添加一些加载和卸载细节图的管理函数,函数将允许用户决定在地形上重复填充多少次,然后你必须编辑你的渲染代码。最难的决定是是否使用硬件多纹理或仅仅使用两个分离的渲染过程。因为地形网格变得相当大,你最好坚持赌上硬件多纹理。实现硬件多纹理在这本书之外了,但是如果你不知道怎样利用你的图形API做的话,它的实现也是相当简单的,你将学习它。这种情况是你不知道一种好的地方去学习API拿出OpenGL Game Programming(Astle/Hawkins)Special Effect Game Programming with DirectX 8.0)McCuskey), 都是Premier Press出版的——一个伟大的出版商,所以我这么说!

编辑你的渲染代码,仅仅设置基本纹理颜色(例如,我们前面产生的) 作为第一个纹理单位,然后设置你的细节纹理到第二个纹理单位。记得怎样为纹理坐标添加上颜色是这样计算的?

fTexLeft = ( float )x/m_iSize;

fTexBottom = ( float)z/m_iSize;

fTexTop = ( float)(z+1)/m_iSize;

好的,我仅仅必须做下轻微的修改这些计算获得我们细节纹理的纹理坐标:

fTexLeft = (float)(x/m_iSize)*m_iRepeatDetailMap);

fTexBottom = (float)(z/m_iSize)*m_iRepeatDetailMap);

fTexTop = (float)((z+1)/m_iSize)*m_iRepeatDetailMap);

m_iRepeatDetailMap是用户希望重复填充地形的次数。最难的部分是使用你的图形API设置多纹理,大拿市如果你使用OpenGL, 我将都给你设置好。 (Yeah, 我知道我是个和蔼的家伙,并且如果你感激要报答我的话,我的生日在311日!)看你的地形上的细节地图,使用256x256的程序式纹理没有使用细节图(如图3.10左边) 而使用了256x256细节图的在3.10右边。看这么多的细节看来是正确的?最好的部分是添加这些昂贵的细节是简单的。拿出CDCode"Chapter3"demo3_3下的demo3_3, 在运动中看新的细节图。仅仅是改变了休息T关闭细节图, D开起细节。(默认是开启的。)

摘要 Summary

我们学习了一些关于纹理化地形的东西。我们从简单的纹理出发(延长但个纹理覆盖整个地形)然后我们踢开它调整到程序式纹理生成。我们最后使用了简单而酷的技术叫做细节贴图。在下一章,我们将学习提高我们地形更多真实感的步骤: 光照化。如果喜欢纹理技术,你也许想看到Tobias Franke的文章标题”Terrain Textuere Generation” 或者Yordan Gyurchev’s 的文章标题”Generating Terrain Textures.”你也许对Jeff Lander的文章标题”Terrain Texturing,”感兴趣,描述了动态纹理tiling的解决方法。

参考 Reference

1 Franke, Tobias. “Terrain Texture Generation. “ 2001

http://www.flipcode.com/tutorials/tut_proctext.shtml

2. Gyurchev, Yordan, “Generaing Terrain Textures. “ 2001.

http://www.flipcode.com/tutorials/tut_terrtex.shtml

3. Lander, Jeff. “Terrain Texturing. “ Delphi3D-Rapid OpenGL

Development. 2002. http://www.delphi3d.net/articles/viewarticle.php?articel=terrainex.htm

转载于:https://www.cnblogs.com/microsoftxiao/archive/2008/07/05/1236312.html

《学习 3D 中的地形》,英文名《focus on 3D terrain》,作者 Trent Polack,大小 10 Mb,本书是为英文版。内容简介: 游戏开发:3D地形篇《Focus On 3D Terrain Programming》,此教程在全球销量相当好! 你是不是每次见到有相当漂亮地形图面的游戏感到特别吃惊!当你在完3D游戏时.是不是有时画面的真实度,敌方会从各各角度来攻击你.这就需要非常好的地形开发人员来完成游戏的界面设计.这套教程就像给予程序员一个最喜爱的主题.其实对于地形的开发.并不是一个简单的问题.而是相当复杂的过程.本游戏地形开发教程.给你最好的解决办法,并让你设计出一个完美的幻境世界的思维。其实,甚至可以说,目录游戏地形地貌的开发算法,很多已在书中提到。值得相关设计师阅读! 这一系列游戏开发方面的电子书的主编都是一个人!他就是大师Andre LaMothe,它写过一本叫做游戏开发大师技巧的书,好几千页,分上下卷,上卷已经出版了,由一位叫做沙鹰的人翻译的,书籍翻译的质量极高,而且书也是写的非常好!它不愧是大师,它的一整本书会让你读的茅塞顿开,让你恍然大悟,让你非常痛快.所有的大型书店应该都有这本书,大家不信可以去翻翻看看!或者到www.china-pub.com网站上看看关于这本书的书评.入门的话用这本书不会错! 游戏开发职业虽然含金量较高,但从业门槛也高高在上,因此,学习前一定要先看看自己是否适合这一职业。判断标准主要有以下七点:一看学历,一般需有大专以上的教育背景;二看年龄,游戏业是年轻人的行业,18-35岁是最佳年龄段;三看逻辑性,从业人员除具备IT基本知识外,还要有相当强的逻辑能力,否则将难以担当复杂的编程工作;四看知识面,游戏软件开发设计编剧、美术、音乐、动画、程序等诸多方面,知识面宽泛;五看创新能力,游戏开发需要经常翻花头,对开发者创新能力的要求较高;六看合作能力,游戏软件开发往往由一个小组负责,需要开发者有团队合作精神;七看兴趣,游戏开发是一项异常枯燥的工作,如果对此没有足够的兴趣,将很难做好。 发布的这些书都是原版的英文书籍,没有中文的,因为中文翻译的慢!大家如果想从事游戏开发,我真的建议大家先学英语,英语是你一切的一切的基础!大家也可以下载一个金山词霸来边翻译边看!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值