← 返回项目列表

简易我的世界

基于Perlin Noise的程序化地图生成,包含草地、泥土、海滩、雪原、河流、海洋并且根据不同的地形生成各种植被

C++DirectXVoxelProcedural

程序化生成

​程序化生成

void Chunk::GenerateBlocks()
{
	//Hillness constant
	constexpr int waterLevel = Z_CHUNK_SIZE / 2;
	constexpr int waterDepth = 5;
	constexpr int waterBed = waterLevel - waterDepth;
	constexpr int maxTerrainElevation = Z_CHUNK_SIZE - waterBed;
	constexpr int HALF_Z_SIZE = Z_CHUNK_SIZE / 2;
	constexpr int maxOceanDownOffset = 10;
	constexpr float sandThreshold = 0.4f;
	constexpr float iceThreshold = 0.4f;
	constexpr float treeDensityThreshold = 0.68f;
	constexpr int treeParameter = 5;
	constexpr int treeOutterRadius = treeParameter / 2 + 1;
	constexpr int treeInnnerRadius = treeParameter / 2;
	std::map blockTreeRawNoises;
	std::map blockHeights;
	std::map blockHumidity;
	for (int localX = -treeOutterRadius; localX < X_CHUNK_SIZE + treeOutterRadius; localX++)
	{
		for (int localY= -treeOutterRadius; localY < Y_CHUNK_SIZE + treeOutterRadius; localY++)
		{
			float globalX = float( localX + m_chunkCoordinate.x * X_CHUNK_SIZE );
			float globalY = float( localY + m_chunkCoordinate.y * Y_CHUNK_SIZE );

			//Hillness
			float hillness = rng->Compute2dPerlinNoise( globalX, globalY, 200.f, 7, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
			hillness = hillness * 0.5f + 0.5f;
			hillness = SmoothStep3( hillness );

			//Temperature
			float temperature = rng->Compute2dPerlinNoise( globalX, globalY, 200.f, 7, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
			temperature = temperature * 0.5f + 0.5f;

			//Oceanness
			float oceanness = rng->Compute2dPerlinNoise( globalX, globalY, 500.f, 7, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
			oceanness = oceanness * 0.5f + 0.5f;
			oceanness = SmoothStep3( oceanness );
			float oceanDownOffset = oceanness* maxOceanDownOffset;

			//Humidity
			float humidity = rng->Compute2dPerlinNoise( globalX, globalY, 150.f, 7, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
			humidity = humidity * 0.5f + 0.5f;
			blockHumidity[IntVec2( localX, localY )] = humidity;
			//Tree Density
			float treeDensityValue = rng->Compute2dPerlinNoise( globalX, globalY, 100.f, 5, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
			treeDensityValue = treeDensityValue * 0.5f + 0.5f;
			treeDensityValue = SmoothStep3( treeDensityValue );
			//Terrain height above sea bed

			float mountainPerlinValue = fabsf( rng->Compute2dPerlinNoise( globalX, globalY, 500.f, 5, 0.5f, 2.0f, true, m_world->m_worldSeedNumber ) );
			mountainPerlinValue = SmoothStep3( mountainPerlinValue );
			//mountainPerlinValue = SmoothStop3( mountainPerlinValue );
			float maxRegionalTerrainElevation = maxTerrainElevation * mountainPerlinValue;
			//Create ocean
			maxRegionalTerrainElevation = maxRegionalTerrainElevation - oceanDownOffset;
			float maxRegionalMountainElevation = maxRegionalTerrainElevation - waterDepth;
			float maxRegionalTerrainHeight = maxRegionalTerrainElevation + waterBed;
			if (maxRegionalTerrainHeight > waterLevel)
			{
				maxRegionalMountainElevation *= hillness;
			}	
			int terrainHeightZ = int( maxRegionalMountainElevation ) + waterLevel;

			blockHeights[IntVec2( localX, localY )] = terrainHeightZ;

			if (localX >= 0 && localX < X_CHUNK_SIZE && localY >= 0 && localY < Y_CHUNK_SIZE)
			{
				for (int localZ = 0; localZ < Z_CHUNK_SIZE; localZ++)
				{
					int currenBlockIndex = GetBlockIndexByCoordinate( IntVec3( localX, localY, localZ ) );

					if (localZ > terrainHeightZ)
					{
						if (localZ <= HALF_Z_SIZE)
						{
							if (temperature < 0.4f)
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Ice;
							}
							else
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Water;
							}
						}
						else
						{
							m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Air;
						}
					}
					else if (localZ == terrainHeightZ)
					{
						if (humidity > sandThreshold && temperature > iceThreshold)
						{
							m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Grass;
						}
						else if (humidity > sandThreshold && temperature < iceThreshold)
						{
							m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::SnowyGrass;
						}
						else
						{
							m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Sand;
						}
					}
					else if (localZ < terrainHeightZ)
					{
						int randomDirtDepth = rng->RollRandomIntInRange( 3, 4 );
						if (terrainHeightZ - localZ <= randomDirtDepth)
						{
							if (humidity > 0.4f)
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Dirt;
							}
							else
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Sand;
							}
						}
						else
						{
							if (rng->RollRandomChance( 0.05f ))
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Coal;
							}
							else if (rng->RollRandomChance( 0.02f ))
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Iron;
							}
							else if (rng->RollRandomChance( 0.005f ))
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Gold;
							}
							else if (rng->RollRandomChance( 0.001f ))
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Diamond;
							}
							else
							{
								m_blocks[currenBlockIndex].m_blockTypeIndex = (unsigned char)BlockType::Stone;
							}
						}
					}
					m_blocks[currenBlockIndex].StartUp();
				}
			}			
		}
	}

	//Tree
	for (int localY = -treeParameter + 1; localY < Y_CHUNK_SIZE + treeParameter - 1; localY++)
	{
		for (int localX = -treeParameter + 1; localX < X_CHUNK_SIZE + treeParameter - 1; localX++)
		{
			//Tree noise value generation
			int globalX = (localX + m_chunkCoordinate.x * X_CHUNK_SIZE);
			int globalY = (localY + m_chunkCoordinate.y * Y_CHUNK_SIZE);
			blockTreeRawNoises[IntVec2( localX, localY )] = rng->Get2dNoiseZeroToOne( globalX, globalY, m_world->m_worldSeedNumber );
		}
	}
	//For loop for each block check if it's a tree, if so spawn a tree template
	for (int localY = -treeInnnerRadius; localY < Y_CHUNK_SIZE + treeInnnerRadius; localY++)
	{
		for (int localX = -treeInnnerRadius; localX < X_CHUNK_SIZE + treeInnnerRadius; localX++)
		{
			IntVec2 localCoordinate( localX, localY );
			bool isHighestTreeValue = true;
			for (int treeSearchX = -treeInnnerRadius; treeSearchX <= treeInnnerRadius; treeSearchX++)
			{
				for (int treeSearchY = -treeInnnerRadius; treeSearchY <= treeInnnerRadius; treeSearchY++)
				{
					if (treeSearchX == 0 && treeSearchY == 0)
					{
						continue;
					}
					IntVec2 currentCoordinate( localX + treeSearchX, localY + treeSearchY );
					//If any raw num larger, it is not tree
					if (blockTreeRawNoises[currentCoordinate] >= blockTreeRawNoises[localCoordinate])
					{
						isHighestTreeValue = false;
					}
				}
			}
			if (isHighestTreeValue)
			{
				IntVec3 currentTreeBaseCoordinate( localX, localY, blockHeights[localCoordinate] + 1 );
				float globalX = float( localX + m_chunkCoordinate.x * X_CHUNK_SIZE );
				float globalY = float( localY + m_chunkCoordinate.y * Y_CHUNK_SIZE );
				//Tree Density
				float treeDensityValue = rng->Compute2dPerlinNoise( globalX, globalY, 100.f, 5, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
				treeDensityValue = treeDensityValue * 0.5f + 0.5f;
				float newThreshold = RangeMapClamped( treeDensityValue, treeDensityThreshold, 1.f, 1.f, 0.85f );
				//treeDensityValue = SmoothStep3( treeDensityValue );

				if (currentTreeBaseCoordinate.z > waterLevel && treeDensityValue > treeDensityThreshold)//Equals to if this block is air 
				{
					float currentTreeNoise = rng->Get2dNoiseZeroToOne( (int)globalX, (int)globalY, m_world->m_worldSeedNumber );
					if (currentTreeNoise > newThreshold)
					{
						//Temperature
						float temperature = rng->Compute2dPerlinNoise( globalX, globalY, 200.f, 7, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
						temperature = temperature * 0.5f + 0.5f;
						//Humidity
						float humidity = rng->Compute2dPerlinNoise( globalX, globalY, 150.f, 7, 0.5f, 2.0f, true, m_world->m_worldSeedNumber );
						humidity = humidity * 0.5f + 0.5f;
						blockHumidity;
						BlockTemplate tree;
						if (humidity < sandThreshold)
						{
							tree = BlockTemplate::s_blockTemplates[(int)BlockTemplateType::CactusTree];
						}
						else if (temperature < iceThreshold)
						{
							tree = BlockTemplate::s_blockTemplates[(int)BlockTemplateType::PruceTree];
						}
						else
						{
							tree = BlockTemplate::s_blockTemplates[(int)BlockTemplateType::OakTree];
						}
						for (int i = 0; i < tree.m_blockTemplateEntries.size(); i++)
						{
							IntVec3 currentTreeBlockCoordinate = currentTreeBaseCoordinate + tree.m_blockTemplateEntries[i].m_relativeOffset;
							if (IsBlockOutOfBound( currentTreeBlockCoordinate ))//Only change the block type in this chunk
							{
								continue;
							}
							int currentTreeBlockIndex = GetBlockIndexByCoordinate( currentTreeBlockCoordinate );
							if (m_blocks[currentTreeBlockIndex].m_blockTypeIndex == (unsigned char)BlockType::Air)
							{
								m_blocks[currentTreeBlockIndex].m_blockTypeIndex = tree.m_blockTemplateEntries[i].m_blockTypeIndex;
								m_blocks[currentTreeBlockIndex].StartUp();
							}

						}
					}

				}


			}
		}
	}
}

每个Chunk程序生成代码

Hidden Surface Removal

通过使用背面剔除来剔除 99% 的面以优化性能,即使有1 亿个块,也能保持大约600 FPS。对于每个块,只有当相邻块为空气时,我才将顶点信息放入顶点缓冲区。

Hidden Surface Removal

void Chunk::AddVertsForBlock( std::vector& cpuMesh, std::vector& cpuMeshIndex, int blockIndex )
{
	BlockDef const& currentBlockDef = BlockDef::GetBlockDefByType( (BlockType)m_blocks[blockIndex].m_blockTypeIndex );
	IntVec3 blockCoordinates = GetBlockCoordinateByIndex( blockIndex );
	Vec3 blockWorldPos( (float)(blockCoordinates.x + m_chunkCoordinate.x * X_CHUNK_SIZE), (float)(blockCoordinates.y + m_chunkCoordinate.y * Y_CHUNK_SIZE), (float)blockCoordinates.z );
	AABB3 blockAABB3( Vec3( blockWorldPos.x, blockWorldPos.y, blockWorldPos.z ), Vec3( blockWorldPos.x + 1.f, blockWorldPos.y + 1.f, blockWorldPos.z + 1.f ) );
	BlockIterator currentBlockIterator( this, blockIndex );
	Block* currentBlock = currentBlockIterator.GetBlock();
	Block* eastBlock = currentBlockIterator.GetEastNeighbor().GetBlock();
	Block* westBlock = currentBlockIterator.GetWestNeighbor().GetBlock();
	Block* northBlock = currentBlockIterator.GetNorthNeighbor().GetBlock();
	Block* southBlock = currentBlockIterator.GetSouthNeighbor().GetBlock();
	Block* upBlock = currentBlockIterator.GetUpNeighbor().GetBlock();
	Block* downBlock = currentBlockIterator.GetDownNeighbor().GetBlock();
	if (currentBlock->IsBlockVisible())
	{
		Rgba8 currentFaceColor;
		currentFaceColor.b = 127;
		if (eastBlock != nullptr && !eastBlock->IsBlockOpaque())
		{
			currentFaceColor.g = eastBlock->GetIndoorLight() * 17;
			currentFaceColor.r = eastBlock->GetOutdoorLight() * 17;
			AddVertsForForwardFace( cpuMesh, cpuMeshIndex, blockAABB3, currentFaceColor, currentBlockDef.m_sideSpriteUV );
		}
		if (westBlock != nullptr && !westBlock->IsBlockOpaque())
		{
			currentFaceColor.g = westBlock->GetIndoorLight() * 17;
			currentFaceColor.r = westBlock->GetOutdoorLight() * 17;
			AddVertsForBackwardFace( cpuMesh, cpuMeshIndex, blockAABB3, currentFaceColor, currentBlockDef.m_sideSpriteUV );
		}
		if (northBlock != nullptr && !northBlock->IsBlockOpaque())
		{
			currentFaceColor.g = northBlock->GetIndoorLight() * 17;
			currentFaceColor.r = northBlock->GetOutdoorLight() * 17;
			AddVertsForLeftFace( cpuMesh, cpuMeshIndex, blockAABB3, currentFaceColor, currentBlockDef.m_sideSpriteUV );
		}
		if (southBlock != nullptr && !southBlock->IsBlockOpaque())
		{
			currentFaceColor.g = southBlock->GetIndoorLight() * 17;
			currentFaceColor.r = southBlock->GetOutdoorLight() * 17;
			AddVertsForRightFace( cpuMesh, cpuMeshIndex, blockAABB3, currentFaceColor, currentBlockDef.m_sideSpriteUV );
		}
		if (upBlock != nullptr && !upBlock->IsBlockOpaque())
		{
			currentFaceColor.g = upBlock->GetIndoorLight() * 17;
			currentFaceColor.r = upBlock->GetOutdoorLight() * 17;
			AddVertsForTopFace( cpuMesh, cpuMeshIndex, blockAABB3, currentFaceColor, currentBlockDef.m_topSpriteUV );
		}
		if (downBlock != nullptr && !downBlock->IsBlockOpaque())
		{
			currentFaceColor.g = downBlock->GetIndoorLight() * 17;
			currentFaceColor.r = downBlock->GetOutdoorLight() * 17;
			AddVertsForBottomFace( cpuMesh, cpuMeshIndex, blockAABB3, currentFaceColor, currentBlockDef.m_bottomSpriteUV );
		}
	}
}

每个方块Hidden Surface Removal的代码

基于体素的光照

每当玩家放置一个方块时,该方块都会使用方块迭代器检查所有附近的方块(16x16),并进行光强度衰减(初始强度为 16,迭代每个方块时为 -1)重建Chunk,并根据光强度在着色器中设置亮度。

将 GlowStone 放到世界中

体素光照传播

void World::ProcessNextDirtyLightBlock()
{
	BlockIterator currentBlockIterator = m_dirtyBlocks.front();
	if (currentBlockIterator.m_currentChunk == nullptr)
	{
		return;
	}
	BlockIterator eastBlockIterator = currentBlockIterator.GetEastNeighbor();
	BlockIterator westBlockIterator = currentBlockIterator.GetWestNeighbor();
	BlockIterator northBlockIterator = currentBlockIterator.GetNorthNeighbor();
	BlockIterator southBlockIterator = currentBlockIterator.GetSouthNeighbor();
	BlockIterator upBlockIterator = currentBlockIterator.GetUpNeighbor();
	BlockIterator downBlockIterator = currentBlockIterator.GetDownNeighbor();

	Block& currentBlock = currentBlockIterator.m_currentChunk->m_blocks[currentBlockIterator.m_blockIndex];

	unsigned char theoreticallyCorrectIndoorValue = 0;
	unsigned char theoreticallyCorrectOutdoorValue = 0;

	if (eastBlockIterator.m_currentChunk != nullptr)
	{
		if (theoreticallyCorrectIndoorValue < eastBlockIterator.GetBlock()->GetIndoorLight() - 1)
		{
			theoreticallyCorrectIndoorValue = eastBlockIterator.GetBlock()->GetIndoorLight() - 1;
		}
	}
	if (westBlockIterator.m_currentChunk != nullptr)
	{
		if (theoreticallyCorrectIndoorValue < westBlockIterator.GetBlock()->GetIndoorLight() - 1)
		{
			theoreticallyCorrectIndoorValue = westBlockIterator.GetBlock()->GetIndoorLight() - 1;
		}
	}
	if (northBlockIterator.m_currentChunk != nullptr)
	{
		if (theoreticallyCorrectIndoorValue < northBlockIterator.GetBlock()->GetIndoorLight() - 1)
		{
			theoreticallyCorrectIndoorValue = northBlockIterator.GetBlock()->GetIndoorLight() - 1;
		}
	}
	if (southBlockIterator.m_currentChunk != nullptr)
	{
		if (theoreticallyCorrectIndoorValue < southBlockIterator.GetBlock()->GetIndoorLight() - 1)
		{
			theoreticallyCorrectIndoorValue = southBlockIterator.GetBlock()->GetIndoorLight() - 1;
		}
	}
	if (upBlockIterator.m_currentChunk != nullptr)
	{
		if (theoreticallyCorrectIndoorValue < upBlockIterator.GetBlock()->GetIndoorLight() - 1)
		{
			theoreticallyCorrectIndoorValue = upBlockIterator.GetBlock()->GetIndoorLight() - 1;
		}
	}
	if (downBlockIterator.m_currentChunk != nullptr)
	{
		if (theoreticallyCorrectIndoorValue < downBlockIterator.GetBlock()->GetIndoorLight() - 1)
		{
			theoreticallyCorrectIndoorValue = downBlockIterator.GetBlock()->GetIndoorLight() - 1;
		}
	}

	if (currentBlock.GetIsSky())
	{
		theoreticallyCorrectOutdoorValue = 15;	
	}
	else
	{
		if (eastBlockIterator.m_currentChunk != nullptr)
		{
			if (theoreticallyCorrectOutdoorValue < eastBlockIterator.GetBlock()->GetOutdoorLight() - 1)
			{
				theoreticallyCorrectOutdoorValue = eastBlockIterator.GetBlock()->GetOutdoorLight() - 1;
			}
		}
		if (westBlockIterator.m_currentChunk != nullptr)
		{
			if (theoreticallyCorrectOutdoorValue < westBlockIterator.GetBlock()->GetOutdoorLight() - 1)
			{
				theoreticallyCorrectOutdoorValue = westBlockIterator.GetBlock()->GetOutdoorLight() - 1;
			}
		}
		if (northBlockIterator.m_currentChunk != nullptr)
		{
			if (theoreticallyCorrectOutdoorValue < northBlockIterator.GetBlock()->GetOutdoorLight() - 1)
			{
				theoreticallyCorrectOutdoorValue = northBlockIterator.GetBlock()->GetOutdoorLight() - 1;
			}
		}
		if (southBlockIterator.m_currentChunk != nullptr)
		{
			if (theoreticallyCorrectOutdoorValue < southBlockIterator.GetBlock()->GetOutdoorLight() - 1)
			{
				theoreticallyCorrectOutdoorValue = southBlockIterator.GetBlock()->GetOutdoorLight() - 1;
			}
		}
		if (upBlockIterator.m_currentChunk != nullptr)
		{
			if (theoreticallyCorrectOutdoorValue < upBlockIterator.GetBlock()->GetOutdoorLight() - 1)
			{
				theoreticallyCorrectOutdoorValue = upBlockIterator.GetBlock()->GetOutdoorLight() - 1;
			}
		}
		if (downBlockIterator.m_currentChunk != nullptr)
		{
			if (theoreticallyCorrectOutdoorValue < downBlockIterator.GetBlock()->GetOutdoorLight() - 1)
			{
				theoreticallyCorrectOutdoorValue = downBlockIterator.GetBlock()->GetOutdoorLight() - 1;
			}
		}
	}

	if (currentBlock.IsBlockOpaque())
	{
		if (currentBlock.m_blockTypeIndex == (unsigned char)BlockType::GlowStone)
		{
			if (currentBlock.GetIndoorLight() < 15)
			{
				theoreticallyCorrectIndoorValue = 15;
			}
		}
		else
		{
			theoreticallyCorrectIndoorValue = 0;
			theoreticallyCorrectOutdoorValue = 0;
		}
	}
	

	if (currentBlockIterator.GetBlock()->GetIndoorLight() != theoreticallyCorrectIndoorValue || currentBlockIterator.GetBlock()->GetOutdoorLight() != theoreticallyCorrectOutdoorValue)
	{
		currentBlockIterator.GetBlock()->SetIndoorLight( theoreticallyCorrectIndoorValue );
		currentBlockIterator.GetBlock()->SetOutdoorLight( theoreticallyCorrectOutdoorValue );
		if (eastBlockIterator.m_currentChunk != nullptr)
		{
			if (!eastBlockIterator.GetBlock()->IsBlockOpaque())
			{
				MarkLightingDirty( eastBlockIterator );
			}
		}
		if (westBlockIterator.m_currentChunk != nullptr)
		{
			if (!westBlockIterator.GetBlock()->IsBlockOpaque())
			{
				MarkLightingDirty( westBlockIterator );
			}
		}
		if (northBlockIterator.m_currentChunk != nullptr)
		{
			if (!northBlockIterator.GetBlock()->IsBlockOpaque())
			{
				MarkLightingDirty( northBlockIterator );
			}
		}
		if (southBlockIterator.m_currentChunk != nullptr)
		{
			if (!southBlockIterator.GetBlock()->IsBlockOpaque())
			{
				MarkLightingDirty( southBlockIterator );
			}
		}
		if (upBlockIterator.m_currentChunk != nullptr)
		{
			if (!upBlockIterator.GetBlock()->IsBlockOpaque())
			{
				MarkLightingDirty( upBlockIterator );
			}
		}
		if (downBlockIterator.m_currentChunk != nullptr)
		{
			if (!downBlockIterator.GetBlock()->IsBlockOpaque())
			{
				MarkLightingDirty( downBlockIterator );
			}
		}
	}
	//Pop out after all calculation
	currentBlock.SetIsLightDirty( false );
	m_dirtyBlocks.pop_front();
}

void Chunk::RebuildMesh()
{
	if (m_eastNeighbor && m_westNeighbor && m_northNeighbor && m_southNeighbor)
	{
		if (m_isMeshDirty)
		{
			DeleteAllPointers();
			BindNeighbors();
			//Initialization
			std::vector		cpuMesh;
			std::vector	cpuMeshIndex;
			cpuMesh.reserve( BLOCK_SIZE * 24 );
			cpuMeshIndex.reserve( BLOCK_SIZE * 36 );
			for (int i = 0; i < BLOCK_SIZE; i++)
			{
				AddVertsForBlock( cpuMesh, cpuMeshIndex, i );
			}
			m_vertexBuffer = g_theRenderer->CreateVertexBuffer( 1 );
			m_indexBuffer = g_theRenderer->CreateIndexBuffer( 1 );
			g_theRenderer->CopyCPUToGPU( cpuMesh.data(), (int)cpuMesh.size() * sizeof( Vertex_PCU ), m_vertexBuffer );
			g_theRenderer->CopyCPUToGPU( cpuMeshIndex.data(), (int)cpuMeshIndex.size() * sizeof( unsigned int ), m_indexBuffer );
			m_indexNum = (int)cpuMeshIndex.size();
			m_vertsNum = (int)cpuMesh.size();
			m_isMeshDirty = false;
		}
	}
}

放置/移除方块时把Chunk标记为Dirty并处理灯光

放置/挖掘方块

我使用了快速射线投射与方块相交,并且在此基础上,为了提高性能,我还使用方块迭代器来加速相交过程。

将方块放置到世界里

struct BlockIterator
{
public:
	BlockIterator();
	~BlockIterator();
	BlockIterator( Chunk* currentChunk, int blockIndex );
	Block* GetBlock();
	IntVec3 GetBlockCoordinate();
	Vec3 GetWorldCenter();
	BlockIterator GetEastNeighbor() const;
	BlockIterator GetWestNeighbor() const;
	BlockIterator GetNorthNeighbor() const;
	BlockIterator GetSouthNeighbor() const;
	BlockIterator GetUpNeighbor() const;
	BlockIterator GetDownNeighbor() const;

public:
	Chunk* m_currentChunk = nullptr;
	int m_blockIndex = 0;
};

方块迭代器类的代码

bool World::RaycastBlock3D( GameRaycastResult3D& result )
{
	IntVec3 startTileCoord = GetPlayerCurrentWorldCoordinate();
	Chunk* currentChunk = GetPlayerCurrentChunk();
	if (currentChunk == nullptr)
	{
		return false;
	}
	int startBlockIndex = currentChunk->GetBlockIndexByCoordinate( GetCurrentBlockCoordinateOnCurrentChunk3D( startTileCoord ) );

	if (currentChunk->m_blocks[startBlockIndex].IsBlockSolid())
	{
		if (startTileCoord.x > 0  && startTileCoord.y > 0)
		{
			result.m_didImpact = true;
			result.m_impactDistance = 0.f;
			result.m_impactPosition = result.m_rayStartPosition;
			return true;
		}
	}
	//X
	float fwdDistPerXCrossing = 1.f / abs( result.m_rayDirection.x );
	int tileStepDirectionX = (result.m_rayDirection.x < 0) ? -1 : 1;
	float xAtFirstXCrossing = (float)(startTileCoord.x + (tileStepDirectionX + 1) / 2);
	float xDistToFirstCrossing = xAtFirstXCrossing - result.m_rayStartPosition.x;
	float fwdDistAtNextXCrossing = fabsf( xDistToFirstCrossing ) * fwdDistPerXCrossing;
	//Y
	float fwdDistPerYCrossing = 1.f / abs( result.m_rayDirection.y );
	int tileStepDirectionY = (result.m_rayDirection.y < 0) ? -1 : 1;
	float yAtFirstYCrossing = (float)(startTileCoord.y + (tileStepDirectionY + 1) / 2);
	float yDistToFirstCrossing = yAtFirstYCrossing - result.m_rayStartPosition.y;
	float fwdDistAtNextYCrossing = fabsf( yDistToFirstCrossing ) * fwdDistPerYCrossing;
	//Z
	float fwdDistPerZCrossing = 1.f / abs( result.m_rayDirection.z );
	int tileStepDirectionZ = (result.m_rayDirection.z < 0) ? -1 : 1;
	float zAtFirstZCrossing = (float)(startTileCoord.z + (tileStepDirectionZ + 1) / 2);
	float zDistToFirstCrossing = zAtFirstZCrossing - result.m_rayStartPosition.z;
	float fwdDistAtNextZCrossing = fabsf( zDistToFirstCrossing ) * fwdDistPerZCrossing;

	BlockIterator currentBlockIterator( currentChunk, startBlockIndex );
	//result
	while(true)
	{
		if (fwdDistAtNextXCrossing < fwdDistAtNextYCrossing && fwdDistAtNextXCrossing < fwdDistAtNextZCrossing)
		{
			if (fwdDistAtNextXCrossing > result.m_rayLength)
			{
				result.m_didImpact = false;
				return false;
			}
			//Go to next block
			if (tileStepDirectionX > 0)
			{
				currentBlockIterator = currentBlockIterator.GetEastNeighbor();
			}
			else
			{
				currentBlockIterator = currentBlockIterator.GetWestNeighbor();
			}
			if (currentBlockIterator.m_currentChunk == nullptr)
			{
				return false;
			}

			if (currentBlockIterator.m_currentChunk->m_blocks[currentBlockIterator.m_blockIndex].IsBlockOpaque())
			{
				result.m_didImpact = true;
				result.m_impactDistance = fwdDistAtNextXCrossing;
				result.m_impactPosition = result.m_rayStartPosition + result.m_rayDirection * fwdDistAtNextXCrossing;
				result.m_blockIterator = currentBlockIterator;
				if (tileStepDirectionX > 0)
				{
					result.m_blockFace = 3;
				}
				else
				{
					result.m_blockFace = 0;
				}
				return true;
			}
			fwdDistAtNextXCrossing += fwdDistPerXCrossing;
		}
		else if (fwdDistAtNextYCrossing < fwdDistAtNextXCrossing && fwdDistAtNextYCrossing < fwdDistAtNextZCrossing)
		{
			if (fwdDistAtNextYCrossing > result.m_rayLength)
			{
				result.m_didImpact = false;
				return false;
			}

			//Go to next block
			if (tileStepDirectionY > 0)
			{
				currentBlockIterator = currentBlockIterator.GetNorthNeighbor();
			}
			else
			{
				currentBlockIterator = currentBlockIterator.GetSouthNeighbor();
			}
			if (currentBlockIterator.m_currentChunk == nullptr)
			{
				return false;
			}

			if (currentBlockIterator.m_currentChunk->m_blocks[currentBlockIterator.m_blockIndex].IsBlockOpaque())
			{
				result.m_didImpact = true;
				result.m_impactDistance = fwdDistAtNextYCrossing;
				result.m_impactPosition = result.m_rayStartPosition + result.m_rayDirection * fwdDistAtNextYCrossing;
				result.m_blockIterator = currentBlockIterator;
				//result.m_targetBlock = ¤tBlockIterator.m_currentChunk->m_blocks[currentBlockIterator.m_blockIndex];
				if (tileStepDirectionY > 0)
				{
					result.m_blockFace = 4;
				}
				else
				{
					result.m_blockFace = 1;
				}
				return true;
			}
			fwdDistAtNextYCrossing += fwdDistPerYCrossing;
		}
		else if (fwdDistAtNextZCrossing < fwdDistAtNextXCrossing && fwdDistAtNextZCrossing < fwdDistAtNextYCrossing)
		{
			if (fwdDistAtNextZCrossing > result.m_rayLength)
			{
				result.m_didImpact = false;
				return false;
			}
			//Go to next block
			if (tileStepDirectionZ > 0)
			{
				currentBlockIterator = currentBlockIterator.GetUpNeighbor();
			}
			else
			{
				currentBlockIterator = currentBlockIterator.GetDownNeighbor();
			}
			if (currentBlockIterator.m_currentChunk == nullptr)
			{
				return false;
			}

			if (currentBlockIterator.m_currentChunk->m_blocks[currentBlockIterator.m_blockIndex].IsBlockOpaque())
			{
				result.m_didImpact = true;
				result.m_impactDistance = fwdDistAtNextZCrossing;
				result.m_impactPosition = result.m_rayStartPosition + result.m_rayDirection * fwdDistAtNextZCrossing;
				result.m_blockIterator = currentBlockIterator;

				if (tileStepDirectionZ > 0)
				{
					result.m_blockFace = 5;
				}
				else
				{
					result.m_blockFace = 2;
				}
				return true;
			}
			fwdDistAtNextZCrossing += fwdDistPerZCrossing;
		}
	}
}

使用方块迭代器进行射线投射

地图流式加载

我使用多线程根据玩家的世界位置每帧仅加载一个Chunk(32768个方块) ,并取消最大半径之外的Chunk。

通过距离雾来掩盖新Chunk的加载。

地图流式加载和雾

void World::UpdateChunckAmortization()
{
	bool didActivate = false;
	if (m_activeChunck.size() < m_maxChunks)
	{
		didActivate = ActivateNearestMissingChunckInRange();
	}
	if (!didActivate)
	{
		DeactivateFarestChunckOutofRange();
	}
	//if the job is retrieved, do light initilization and other stuff
	Job* completedChunkjob = g_theJobSystem->RetriveFirstJob();
	ChunkGenerateJob* generateJob = (ChunkGenerateJob*)completedChunkjob;
	if (generateJob)
	{
		generateJob->m_currentChunk->m_chunkState = ChunkState::ACTIVATING_GENERATE_COMPLETE;
		m_chunksBeingGenerated.erase( generateJob->m_currentChunk->m_chunkCoordinate );
		generateJob->m_currentChunk->ActivateThisChunk();
	}
}

地图流式加载代码

截图