• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Waaayoff

Terrain lighting seams?

13 posts in this topic

I'm drawing my terrain in chunks and there is a seam between the chunks, even though i'm including neighbour chunk vertices when calculating edge normals. I was thinking maybe it's because my normals are being interpolated when sampling the chunk normal map in the pixel shader?

 

2vt.png

0

Share this post


Link to post
Share on other sites

non-seamless normal map perhaps? or pehaps some out-of-phase type error ? notice how it looks like the shadows would line up better if they were shifted a bit along the seam.

 

it would seem to me that all types of maps (texture, normal, specular, bump, bakelite, etc) would need to be "seamless" at chunk edges. if all laid out  together they'd make one huge seamless map. then everything should match up, whether you're drawing things straight out or sampling across seams.

 

i use chunks that are 300x300 in size. chunks are generated on the fly as needed. the ground mesh for a chunk is generated from a procedurally heightmapped 10x10 quad, one mesh per ground texture used (currently 4 tiles). but all normals are pointing straight up.  i adjust the y values of the 4 corners of a quad, translate it to its proper location in the chunk, and add it to the ground mesh vb and ib. but i dont recompute the normals. i suppose i ought to do that eh?

 

[sharedmedia=gallery:images:4018]

 

0

Share this post


Link to post
Share on other sites


I was thinking maybe it's because my normals are being interpolated when sampling the chunk normal map in the pixel shader?
Sounds reasonable. If you're using REPEAT texture access you're set up for surprises. Real thing is, you don't assemble chunks to make a big terrain. We slice big terrains into chunks instead. All bounduary samples must use inter-chunk fetch. Seamless chunks hide the problem at an extreme authoring cost and limitation, don't do that.
1

Share this post


Link to post
Share on other sites

Is this seam there if you just go by normals, no normal maps?

 

What do your texture coordinates look like?

 

(Your normal map isn't in a texture atlas, is it?)

1

Share this post


Link to post
Share on other sites

 


I was thinking maybe it's because my normals are being interpolated when sampling the chunk normal map in the pixel shader?
Sounds reasonable. If you're using REPEAT texture access you're set up for surprises. Real thing is, you don't assemble chunks to make a big terrain. We slice big terrains into chunks instead. All bounduary samples must use inter-chunk fetch. Seamless chunks hide the problem at an extreme authoring cost and limitation, don't do that.

 

 

I already use neighbour chunk vertices for border cases if that's what you mean

 

 

Is this seam there if you just go by normals, no normal maps?

 

What do your texture coordinates look like?

 

(Your normal map isn't in a texture atlas, is it?)

 

Yes, i just tested and the normals look fine when passed with the vertex. I guess it is a problem with the texture sampling... Any idea how to fix it? Or rather, why not just pass the normals instead of using normal maps? Does bump mapping even work when the texture resolution is the same as the vertex resolution?

 

And no i'm not using an atlas; a separate texture per chunk

 

 

If you are using bezier patches, make sure you are joining your patches with at least C1 continuity.

 

I don't even know what that means tongue.png

Edited by Waaayoff
1

Share this post


Link to post
Share on other sites
Does bump mapping even work when the texture resolution is the same as the vertex resolution?

 

Normal mapping has nothing at all to do with how many vertices there are.  Normal mapping is a per-pixel operation, the best normal map resolution is the one that most closely matches how many pixels the model takes up on the screen.  If your screen size is 1024x1024 and your model takes up 1/4 of the screen, then you should use a normal map that is 512x512.  If the normal map is heavily blurred then you can use a smaller resolution with out much noticeable decrease in apparent detail.

 

The idea of normal mapping is to fill in the space between vertices, with the illusion of detail.  This makes a low-poly model look like a high-poly model.  This illusion breaks down at the silhouette of a model and also at steep angles.

 

If you use very small textures then those textures will be sampled up in size for models that take up a lot of screen space, this will result in blocky artifacts.

 

To clear up those seam edges you will likely have to make the textures seamless. 

 

I Googled -> how to make seamless textures and chose a couple to get you started.

 

http://www.obsidiandawn.com/creating-seamless-textures-in-photoshop-tutorial

http://www.vickiwenderlich.com/2011/06/tutorial-how-to-create-a-seamless-texture-in-gimp/

 

Also, I suppose I should also mention.  The PhotoShop tutorial and the GIMP tutorial are using two very different methods to achieve seamless-ness. 

The GIMP method is more of an optical illusion created by a plugin.  However, the method used on the PhotoShop page will work in GIMP as well, both have the offset function.  It's just that GIMP also has a quick and dirty plugin trick that may work for many circumstances.

0

Share this post


Link to post
Share on other sites
I'm having a similar problem. Wish I knew how to fix it. I figured my over coordinates were off but they are correct. The chunks are also perfectly aligned. Nonetheless there is a visible seam on the x and zaxis where chunks meet. They also have exactly the same amount of vertices no lod here just bruteforce 64x64 terrain quads. I've basically moved onto other things can't figure it out even tried skirts didn't help sigh
0

Share this post


Link to post
Share on other sites

I'm having a similar problem. Wish I knew how to fix it. I figured my over coordinates were off but they are correct. The chunks are also perfectly aligned. Nonetheless there is a visible seam on the x and zaxis where chunks meet. They also have exactly the same amount of vertices no lod here just bruteforce 64x64 terrain quads. I've basically moved onto other things can't figure it out even tried skirts didn't help sigh

 

Are you using neighbour vertices to calculate the edge normals? Because if you are, if you send the normals with the vertex the problem goes away. Even if you don't and choose to sample them from a texture, the seams are not noticeable in the slightest when you texture the terrain.

0

Share this post


Link to post
Share on other sites

Well I had a crack at using neighbour patches to calculate the edge normals and here is my result:

scs-20130816.png

 

Which is worse than before with no neighbour patch sampling:

scs-20130816-2.png

 

I purposely made it flat to debug the terrain. I also disabled normal map sampling and parallax mapping in the shader to make sure its not that.

I'm positing the code here.

 

GenerateTerrainPatch

bool const cTerrainRender::GenerateTerrainPatch( cTerrainPatch* const& pSourcePatch,
												 LPTERRAINRENDERACTIVEPATCH const& pActivePatch )                                                                            
{
	__m128 fRegOne, fRegTwo, fRegXZScale, fRegHeightMapRadius, fRegUVScale;

	D3DX_ALIGN16 unsigned int vertexiDx[4];
	D3DX_ALIGN16 float fXCoord[4],
					   fZCoord,
					   fUCoord[4],
					   fVCoord;
	XMFLOAT3A const vStartNormal(0.0f, 1.0f, 0.0f);
	D3DX_ALIGN16 float fX(0.0f), fZ(0.0);
	D3DX_ALIGN16 unsigned int vertexiDz(0);

	// Resolve Unique Vertex Arrays //
	D3DX_ALIGN16 float const* const pHeightMap( pSourcePatch->getHeightMapRaw() );
	D3DX_ALIGN16 TERRAINVERTEX* const pTerrainSrcPatchBuffer(pActivePatch->getUniqueVertexArray());
	
	fRegHeightMapRadius = XMM128::LoadAll( m_fLocalSpaceHeightMapRadius );
	fRegXZScale = XMM128::LoadAll( m_fLocalSpaceXZScale );
	fRegUVScale = XMM128::LoadAll( m_fLocalSpaceUVScale );
	
	// Build Terrain Patch from Heightmap Data //
	for ( unsigned int iDz = 0; iDz < m_uiNumVerticesXZ ; ++iDz, fZ += 1.0f )
	{
		fX = 0.0f;
		vertexiDz = ( iDz * m_uiNumVerticesXZ );

		fRegOne = XMM128::LoadOne( fZ );
		fRegTwo = _mm_mul_ss( fRegOne, fRegUVScale );
		XMM128::StoreOne( &fVCoord, fRegTwo );

		fRegOne = _mm_mul_ss( fRegOne, fRegXZScale );
		fRegOne = _mm_sub_ss( fRegOne, fRegHeightMapRadius );
		XMM128::StoreOne( &fZCoord, fRegOne );
		
		for ( unsigned int iDx = 0; iDx < m_uiNumVerticesXZ ; iDx += 4, fX += 4.0f ) 
		{
			// to map the 2D array coordaintes into a 1D array
			vertexiDx[0] = vertexiDz + iDx;
			vertexiDx[1] = vertexiDz + iDx + 1;
			vertexiDx[2] = vertexiDz + iDx + 2;
			vertexiDx[3] = vertexiDz + iDx + 3;
						
			fRegOne = XMM128::Load( fX, fX + 1.0f, fX + 2.0f, fX + 3.0f);
			fRegTwo = _mm_mul_ps( fRegOne, fRegUVScale );
			XMM128::Store( fUCoord, fRegTwo );

			fRegOne = _mm_mul_ps( fRegOne, fRegXZScale );
			fRegOne = _mm_sub_ps( fRegOne, fRegHeightMapRadius );
			XMM128::Store( fXCoord, fRegOne );
			
			pTerrainSrcPatchBuffer[vertexiDx[0]].p = XMFLOAT3A(fXCoord[0], pHeightMap[vertexiDx[0]], fZCoord);
			pTerrainSrcPatchBuffer[vertexiDx[1]].p = XMFLOAT3A(fXCoord[1], pHeightMap[vertexiDx[1]], fZCoord);
			pTerrainSrcPatchBuffer[vertexiDx[2]].p = XMFLOAT3A(fXCoord[2], pHeightMap[vertexiDx[2]], fZCoord);
			pTerrainSrcPatchBuffer[vertexiDx[3]].p = XMFLOAT3A(fXCoord[3], pHeightMap[vertexiDx[3]], fZCoord);

			// Temporary Tangent & Binormal & Normal //
			//pTerrainSrcPatchBuffer[vertexiDx[0]].t = pTerrainSrcPatchBuffer[vertexiDx[0]].b = pTerrainSrcPatchBuffer[vertexiDx[0]].n = vStartNormal;
			//pTerrainSrcPatchBuffer[vertexiDx[1]].t = pTerrainSrcPatchBuffer[vertexiDx[1]].b = pTerrainSrcPatchBuffer[vertexiDx[1]].n = vStartNormal;
			//pTerrainSrcPatchBuffer[vertexiDx[2]].t = pTerrainSrcPatchBuffer[vertexiDx[2]].b = pTerrainSrcPatchBuffer[vertexiDx[2]].n = vStartNormal;
			//pTerrainSrcPatchBuffer[vertexiDx[3]].t = pTerrainSrcPatchBuffer[vertexiDx[3]].b = pTerrainSrcPatchBuffer[vertexiDx[3]].n = vStartNormal;

			// Set the u,v values so one texture covers the entire terrain
			pTerrainSrcPatchBuffer[vertexiDx[0]].tu = fUCoord[0];
			pTerrainSrcPatchBuffer[vertexiDx[1]].tu = fUCoord[1];
			pTerrainSrcPatchBuffer[vertexiDx[2]].tu = fUCoord[2];
			pTerrainSrcPatchBuffer[vertexiDx[3]].tu = fUCoord[3];

			pTerrainSrcPatchBuffer[vertexiDx[0]].tv = fVCoord;
			pTerrainSrcPatchBuffer[vertexiDx[1]].tv = fVCoord;
			pTerrainSrcPatchBuffer[vertexiDx[2]].tv = fVCoord;
			pTerrainSrcPatchBuffer[vertexiDx[3]].tv = fVCoord;
		}
	}

	// Remap Vertices for Vertex Cache Optimization //
	// Copy the vertex data into a temp buffer.
	TERRAINVERTEX *OldVertices = new TERRAINVERTEX[ m_uiNumVertices ];
	memcpy( OldVertices, pTerrainSrcPatchBuffer, sizeof(TERRAINVERTEX) * m_uiNumVertices );
	
	// Perform the remapping
	const DWORD *CurrentRemap = m_pVertexCacheIndexRemap;
	const TERRAINVERTEX *OldVertex = OldVertices;
	for( DWORD i = 0; i < m_uiNumVertices; ++i )
	{
		pTerrainSrcPatchBuffer[ *( CurrentRemap++ ) ] = *( OldVertex++ );
	}
	
	delete[] OldVertices;

	return(true);
}

GenerateTangentBinormalNormal

bool const cTerrainRender::GenerateTangentBinormalNormal( TERRAINVERTEX* const& pTerrainSrcPatchBuffer,
														  float const* const pHeightMap,
														  ZoneSphere const* const* const pAdjacentZoneSpheres ) const
{
	D3DX_ALIGN16 unsigned int vertexiDx(0),
							  iDx(0), iDz(0),
							  iDzMin(0),
							  iDzMax(0),
							  iDxMin(0),
							  iDxMax(0);
	
	D3DX_ALIGN16 DWORD const* const CurrentRemap(m_pVertexCacheIndexRemap);

	/*D3DX_ALIGN16 cTerrainPatch const* pTerrainPatchAdj(NULL);
	D3DX_ALIGN16 float const* pHeightMapAdj(NULL);
	D3DX_ALIGN16 TERRAINVERTEX const* pTerrainSrcPatchBufferAdj(NULL);
	
	// Check to see if we have adjacent terrain patches we need //
	if ( !pAdjacentZoneSpheres[ZONE_LEFT] || !pAdjacentZoneSpheres[ZONE_LEFT]->getTerrainPatch()->getOwnedActivePatch() )
		return(false);
	if ( !pAdjacentZoneSpheres[ZONE_RIGHT] || !pAdjacentZoneSpheres[ZONE_RIGHT]->getTerrainPatch()->getOwnedActivePatch() )
		return(false);
	if ( !pAdjacentZoneSpheres[ZONE_TOP] || !pAdjacentZoneSpheres[ZONE_TOP]->getTerrainPatch()->getOwnedActivePatch() )
		return(false);
	if ( !pAdjacentZoneSpheres[ZONE_BOTTOM] || !pAdjacentZoneSpheres[ZONE_BOTTOM]->getTerrainPatch()->getOwnedActivePatch() )
		return(false);

	// Now we have todo the edge vertices on z and axis' by sampling adjacent terrain patches that have already
	// been generated. They have already been generated as where this function is called from // 
	
	// Top of this patch, bottom of adjacent patch
	pTerrainPatchAdj = pAdjacentZoneSpheres[ZONE_BOTTOM]->getTerrainPatch();

	CalculateNormalsTOP(m_uiNumVerticesXZ-1, 
						pTerrainSrcPatchBuffer, pTerrainPatchAdj->getOwnedActivePatch()->getUniqueVertexArray(),
						pHeightMap, pTerrainPatchAdj->getHeightMapRaw(),
						CurrentRemap);
	
	// Bottom of this patch, top of adjacent patch
	pTerrainPatchAdj = pAdjacentZoneSpheres[ZONE_TOP]->getTerrainPatch();

	CalculateNormalsBOTTOM(0, 
						   pTerrainSrcPatchBuffer, pTerrainPatchAdj->getOwnedActivePatch()->getUniqueVertexArray(),
						   pHeightMap, pTerrainPatchAdj->getHeightMapRaw(),
						   CurrentRemap);

	// Left of this patch, right of adjacent patch
	pTerrainPatchAdj = pAdjacentZoneSpheres[ZONE_RIGHT]->getTerrainPatch();

	CalculateNormalsLEFT(m_uiNumVerticesXZ-1, 
						 pTerrainSrcPatchBuffer, pTerrainPatchAdj->getOwnedActivePatch()->getUniqueVertexArray(),
						 pHeightMap, pTerrainPatchAdj->getHeightMapRaw(),
						 CurrentRemap);

	// Right of this patch, left of adjacent patch
	pTerrainPatchAdj = pAdjacentZoneSpheres[ZONE_LEFT]->getTerrainPatch();

	CalculateNormalsRIGHT(0, 
						  pTerrainSrcPatchBuffer, pTerrainPatchAdj->getOwnedActivePatch()->getUniqueVertexArray(),
						  pHeightMap, pTerrainPatchAdj->getHeightMapRaw(),
						  CurrentRemap);*/


	// ZONE_NUM_ADJACENT

	// Now the rest of the inside of the patch //
	tbb::parallel_for( tbb::blocked_range<unsigned int>(0, m_uiNumVerticesXZ),
			[=](const tbb::blocked_range<unsigned int>& r) {
	for( unsigned int iDz = r.begin(); iDz != r.end(); ++iDz )
	{
		unsigned int vertexiDx(0),
					 iDzMin(0),
					 iDzMax(0),
					 iDxMin(0),
					 iDxMax(0);

		iDzMin = iDz > 0 ? iDz-1 : iDz; iDzMax = iDz < m_uiNumVerticesXZ - 1 ? iDz+1 : iDz;

		// iDz -1 : iDz +1
		//iDzMin = iDz-1;
		//iDzMax = iDz+1;

		for ( unsigned int iDx = 0 ; iDx < m_uiNumVerticesXZ ; ++iDx )
		{
			iDxMin = iDx > 0 ? iDx-1 : iDx; iDxMax = iDx < m_uiNumVerticesXZ - 1 ? iDx+1 : iDx;

			// iDx -1 : iDx +1
			//iDxMin = iDx-1; 
			//iDxMax = iDx+1;

			// to map the 2D array coordaintes into a 1D array
			vertexiDx = CurrentRemap[( iDz * m_uiNumVerticesXZ ) + iDx];

			CalculateNormal(iDx, iDz, iDxMin, iDxMax, iDzMin, iDzMax, vertexiDx,
				            pTerrainSrcPatchBuffer, pHeightMap, CurrentRemap);
		}
	}
	});

	return(true);
}

CalculateNormal

void cTerrainRender::CalculateNormal( unsigned int const& iDx, unsigned int const& iDz,
									  unsigned int const& iDxMin, unsigned int const& iDxMax, 
									  unsigned int const& iDzMin, unsigned int const& iDzMax,
									  unsigned int const& vertexiDx,
									  TERRAINVERTEX* const& pTerrainSrcPatchBuffer,
									  float const* const& pHeightMap,
									  DWORD const* const& CurrentRemap ) const
{
	XMVECTOR xmX, xmZ, xmN;
	D3DX_ALIGN16 float tl,
						l,
						bl,
						t,
						b,
						tr,
						r,
						br,
						dX, dY;
	D3DX_ALIGN16 float const normalStrength(4.0f);

	tl = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDxMin ];
	l  = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDx ];
	bl = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDxMax ];
	t  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMin ];
	b  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMax ];
	tr = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDxMin ];
	r  = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDx ];
	br = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDxMax ];

	// Compute dx using Sobel:
	//           -1 0 1 
	//           -2 0 2
	//           -1 0 1

	dX = tr + 2*r + br -tl - 2*l - bl;

	// Compute dy using Sobel:
	//           -1 -2 -1 
	//            0  0  0
	//            1  2  1

	dY = bl + 2*b + br -tl - 2*t - tr;

	// Build the DETAIL normal
	xmN = XMLoadFloat3A( &XMFLOAT3A(dX, normalStrength, dY) );
			
	// Use the adjoing vertices along both axis to compute the correct normal
	//
	// Tangent Vector :  t = (1, 0, heightmap[x+1, y] - heightmap[x-1, y]) 
	// Binormal Vector : ß = (0, 1, heightmap[x, y+1] - heightmap[x, y-1]) 
			
	xmX = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMax] ].p), 
							XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMin] ].p) );

	xmZ = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMax * m_uiNumVerticesXZ) + iDx] ].p), 
							XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMin * m_uiNumVerticesXZ) + iDx] ].p) );

	// Average DETAIL normal from sobel calculation of heightmap with normal computed from vertices
	xmN = XMVectorAdd(XMVector3Normalize(xmN), XMVector3Normalize(XMVector3Cross(xmZ, xmX)));
	xmN = XMVectorMultiply(xmN, XMM128::LoadAll(0.5f));
	xmN = XMVector3Normalize( xmN );
	XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].n, xmN);

	// Assign Correct Tangent //
	xmX = XMVector3Normalize(xmX);
	XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].t, xmX);

	// Calculate BiNormal From CrossProduct of Tangent and the Final Normal //
	xmZ = XMVector3Normalize( XMVector3Cross(xmN, xmX) );
	XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].b, xmZ);
}

CalculateNormalsTop

void cTerrainRender::CalculateNormalsTOP( unsigned int const iDzADJ,
										  TERRAINVERTEX* const& pTerrainSrcPatchBuffer, TERRAINVERTEX const* const& pTerrainSrcPatchBufferAdj,
										  float const* const& pHeightMap, float const* const& pHeightMapAdj,
										  DWORD const* const& CurrentRemap ) const
{
	XMVECTOR xmX, xmZ, xmN;
	D3DX_ALIGN16 float tl,
						l,
						bl,
						t,
						b,
						tr,
						r,
						br,
						dX, dY;
	D3DX_ALIGN16 float const normalStrength(4.0f);

	// iDz -1 : iDz +1
	// must sample adjacent iDzMin = iDz-1;
	D3DX_ALIGN16 unsigned int const iDz(0),
								    iDzMax(iDz+1);

	D3DX_ALIGN16 unsigned int vertexiDx(0),
							  iDxMin(0), iDxMax(0);

	for ( unsigned int iDx = 1 ; iDx < m_uiNumVerticesXZ - 1 ; ++iDx ) // except sides on xaxis & corners
	{
		// to map the 2D array coordaintes into a 1D array
		vertexiDx = CurrentRemap[( iDz * m_uiNumVerticesXZ ) + iDx];

		// iDx -1 : iDx +1
		iDxMin = iDx-1; 
		iDxMax = iDx+1;

		tl = pHeightMapAdj[ ( iDzADJ * m_uiNumVerticesXZ) + iDxMin ];
		l  = pHeightMapAdj[ ( iDzADJ * m_uiNumVerticesXZ) + iDx ];
		bl = pHeightMapAdj[ ( iDzADJ * m_uiNumVerticesXZ) + iDxMax ];
		t  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMin ];
		b  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMax ];
		tr = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDxMin ];
		r  = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDx ];
		br = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDxMax ];

		// Compute dx using Sobel:
		//           -1 0 1 
		//           -2 0 2
		//           -1 0 1

		dX = tr + 2*r + br -tl - 2*l - bl;

		// Compute dy using Sobel:
		//           -1 -2 -1 
		//            0  0  0
		//            1  2  1

		dY = bl + 2*b + br -tl - 2*t - tr;

		// Build the DETAIL normal
		xmN = XMLoadFloat3A( &XMFLOAT3A(dX, normalStrength, dY) );
			
		// Use the adjoing vertices along both axis to compute the correct normal
		//
		// Tangent Vector :  t = (1, 0, heightmap[x+1, y] - heightmap[x-1, y]) 
		// Binormal Vector : ß = (0, 1, heightmap[x, y+1] - heightmap[x, y-1]) 
			
		xmX = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMax] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMin] ].p) );

		xmZ = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMax * m_uiNumVerticesXZ) + iDx] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBufferAdj[ CurrentRemap[( iDzADJ * m_uiNumVerticesXZ) + iDx] ].p) );

		// Average DETAIL normal from sobel calculation of heightmap with normal computed from vertices
		xmN = XMVectorAdd(XMVector3Normalize(xmN), XMVector3Normalize(XMVector3Cross(xmZ, xmX)));
		xmN = XMVectorMultiply(xmN, XMM128::LoadAll(0.5f));
		xmN = XMVector3Normalize( xmN );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].n, xmN);

		// Assign Correct Tangent //
		xmX = XMVector3Normalize(xmX);
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].t, xmX);

		// Calculate BiNormal From CrossProduct of Tangent and the Final Normal //
		xmZ = XMVector3Normalize( XMVector3Cross(xmN, xmX) );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].b, xmZ);
	}
}

CalculateNormalsBOTTOM

void cTerrainRender::CalculateNormalsBOTTOM( unsigned int const iDzADJ,
										     TERRAINVERTEX* const& pTerrainSrcPatchBuffer, TERRAINVERTEX const* const& pTerrainSrcPatchBufferAdj,
										     float const* const& pHeightMap, float const* const& pHeightMapAdj,
										     DWORD const* const& CurrentRemap ) const
{
	XMVECTOR xmX, xmZ, xmN;
	D3DX_ALIGN16 float tl,
						l,
						bl,
						t,
						b,
						tr,
						r,
						br,
						dX, dY;
	D3DX_ALIGN16 float const normalStrength(4.0f);

	// iDz -1 : iDz +1
	// must sample adjacent iDzMax = iDz+1;
	D3DX_ALIGN16 unsigned int const iDz(m_uiNumVerticesXZ - 1),
								    iDzMin(iDz-1);

	D3DX_ALIGN16 unsigned int vertexiDx(0),
							  iDxMin(0), iDxMax(0);

	for ( unsigned int iDx = 1 ; iDx < m_uiNumVerticesXZ - 1 ; ++iDx ) // except sides on xaxis & corners
	{
		// to map the 2D array coordaintes into a 1D array
		vertexiDx = CurrentRemap[( iDz * m_uiNumVerticesXZ ) + iDx];

		// iDx -1 : iDx +1
		iDxMin = iDx-1; 
		iDxMax = iDx+1;

		tl = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDxMin ];
		l  = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDx ];
		bl = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDxMax ];
		t  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMin ];
		b  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMax ];
		tr = pHeightMapAdj[ ( iDzADJ * m_uiNumVerticesXZ) + iDxMin ];
		r  = pHeightMapAdj[ ( iDzADJ * m_uiNumVerticesXZ) + iDx ];
		br = pHeightMapAdj[ ( iDzADJ * m_uiNumVerticesXZ) + iDxMax ];

		// Compute dx using Sobel:
		//           -1 0 1 
		//           -2 0 2
		//           -1 0 1

		dX = tr + 2*r + br -tl - 2*l - bl;

		// Compute dy using Sobel:
		//           -1 -2 -1 
		//            0  0  0
		//            1  2  1

		dY = bl + 2*b + br -tl - 2*t - tr;

		// Build the DETAIL normal
		xmN = XMLoadFloat3A( &XMFLOAT3A(dX, normalStrength, dY) );
			
		// Use the adjoing vertices along both axis to compute the correct normal
		//
		// Tangent Vector :  t = (1, 0, heightmap[x+1, y] - heightmap[x-1, y]) 
		// Binormal Vector : ß = (0, 1, heightmap[x, y+1] - heightmap[x, y-1]) 
			
		xmX = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMax] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMin] ].p) );

		xmZ = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBufferAdj[ CurrentRemap[( iDzADJ * m_uiNumVerticesXZ) + iDx] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMin * m_uiNumVerticesXZ) + iDx] ].p) );

		// Average DETAIL normal from sobel calculation of heightmap with normal computed from vertices
		xmN = XMVectorAdd(XMVector3Normalize(xmN), XMVector3Normalize(XMVector3Cross(xmZ, xmX)));
		xmN = XMVectorMultiply(xmN, XMM128::LoadAll(0.5f));
		xmN = XMVector3Normalize( xmN );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].n, xmN);

		// Assign Correct Tangent //
		xmX = XMVector3Normalize(xmX);
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].t, xmX);

		// Calculate BiNormal From CrossProduct of Tangent and the Final Normal //
		xmZ = XMVector3Normalize( XMVector3Cross(xmN, xmX) );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].b, xmZ);
	}
}

CalculateNormalsLEFT

void cTerrainRender::CalculateNormalsLEFT( unsigned int const iDxADJ,
										   TERRAINVERTEX* const& pTerrainSrcPatchBuffer, TERRAINVERTEX const* const& pTerrainSrcPatchBufferAdj,
										   float const* const& pHeightMap, float const* const& pHeightMapAdj,
										   DWORD const* const& CurrentRemap ) const
{
	XMVECTOR xmX, xmZ, xmN;
	D3DX_ALIGN16 float tl,
						l,
						bl,
						t,
						b,
						tr,
						r,
						br,
						dX, dY;
	D3DX_ALIGN16 float const normalStrength(4.0f);

	// iDx -1 : iDx +1
	// must sample adjacent iDxMin = iDx-1;
	D3DX_ALIGN16 unsigned int const iDx(0),
									iDxMax(iDx+1);
	
	D3DX_ALIGN16 unsigned int vertexiDx(0),  
							  iDzMin(0), iDzMax(0);

	for ( unsigned int iDz = 1 ; iDz < m_uiNumVerticesXZ - 1 ; ++iDz ) // except sides on zaxis & corners
	{
		// to map the 2D array coordaintes into a 1D array
		vertexiDx = CurrentRemap[( iDz * m_uiNumVerticesXZ ) + iDx];

		// iDx -1 : iDx +1
		iDzMin = iDz-1; 
		iDzMax = iDz+1;

		tl = pHeightMapAdj[ ( iDzMin * m_uiNumVerticesXZ) + iDxADJ ];
		l  = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDx ];
		bl = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDxMax ];
		t  = pHeightMapAdj[ ( iDz * m_uiNumVerticesXZ) + iDxADJ ];
		b  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMax ];
		tr = pHeightMapAdj[ ( iDzMax * m_uiNumVerticesXZ) + iDxADJ ];
		r  = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDx ];
		br = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDxMax ];

		// Compute dx using Sobel:
		//           -1 0 1 
		//           -2 0 2
		//           -1 0 1

		dX = tr + 2*r + br -tl - 2*l - bl;

		// Compute dy using Sobel:
		//           -1 -2 -1 
		//            0  0  0
		//            1  2  1

		dY = bl + 2*b + br -tl - 2*t - tr;

		// Build the DETAIL normal
		xmN = XMLoadFloat3A( &XMFLOAT3A(dX, normalStrength, dY) );
			
		// Use the adjoing vertices along both axis to compute the correct normal
		//
		// Tangent Vector :  t = (1, 0, heightmap[x+1, y] - heightmap[x-1, y]) 
		// Binormal Vector : ß = (0, 1, heightmap[x, y+1] - heightmap[x, y-1]) 
			
		xmX = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMax] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBufferAdj[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxADJ] ].p) );

		xmZ = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMax * m_uiNumVerticesXZ) + iDx] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMin * m_uiNumVerticesXZ) + iDx] ].p) );

		// Average DETAIL normal from sobel calculation of heightmap with normal computed from vertices
		xmN = XMVectorAdd(XMVector3Normalize(xmN), XMVector3Normalize(XMVector3Cross(xmZ, xmX)));
		xmN = XMVectorMultiply(xmN, XMM128::LoadAll(0.5f));
		xmN = XMVector3Normalize( xmN );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].n, xmN);

		// Assign Correct Tangent //
		xmX = XMVector3Normalize(xmX);
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].t, xmX);

		// Calculate BiNormal From CrossProduct of Tangent and the Final Normal //
		xmZ = XMVector3Normalize( XMVector3Cross(xmN, xmX) );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].b, xmZ);
	}
}

CalculateNormalsRIGHT

void cTerrainRender::CalculateNormalsRIGHT( unsigned int const iDxADJ,
										    TERRAINVERTEX* const& pTerrainSrcPatchBuffer, TERRAINVERTEX const* const& pTerrainSrcPatchBufferAdj,
										    float const* const& pHeightMap, float const* const& pHeightMapAdj,
										    DWORD const* const& CurrentRemap ) const
{
	XMVECTOR xmX, xmZ, xmN;
	D3DX_ALIGN16 float tl,
						l,
						bl,
						t,
						b,
						tr,
						r,
						br,
						dX, dY;
	D3DX_ALIGN16 float const normalStrength(4.0f);
	// iDx -1 : iDx +1
	// must sample adjacent iDxMax = iDx+1;
	D3DX_ALIGN16 unsigned int const iDx(m_uiNumVerticesXZ - 1),
									iDxMin(iDx-1);
	
	D3DX_ALIGN16 unsigned int vertexiDx(0),  
							  iDzMin(0), iDzMax(0);

	for ( unsigned int iDz = 1 ; iDz < m_uiNumVerticesXZ - 1 ; ++iDz ) // except sides on zaxis & corners
	{
		// to map the 2D array coordaintes into a 1D array
		vertexiDx = CurrentRemap[( iDz * m_uiNumVerticesXZ ) + iDx];

		// iDx -1 : iDx +1
		iDzMin = iDz-1; 
		iDzMax = iDz+1;

		tl = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDxMin ];
		l  = pHeightMap[ ( iDzMin * m_uiNumVerticesXZ) + iDx ];
		bl = pHeightMapAdj[ ( iDzMin * m_uiNumVerticesXZ) + iDxADJ ];
		t  = pHeightMap[ ( iDz * m_uiNumVerticesXZ) + iDxMin ];
		b  = pHeightMapAdj[ ( iDz * m_uiNumVerticesXZ) + iDxADJ ];
		tr = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDxMin ];
		r  = pHeightMap[ ( iDzMax * m_uiNumVerticesXZ) + iDx ];
		br = pHeightMapAdj[ ( iDzMax * m_uiNumVerticesXZ) + iDxADJ ];

		// Compute dx using Sobel:
		//           -1 0 1 
		//           -2 0 2
		//           -1 0 1

		dX = tr + 2*r + br -tl - 2*l - bl;

		// Compute dy using Sobel:
		//           -1 -2 -1 
		//            0  0  0
		//            1  2  1

		dY = bl + 2*b + br -tl - 2*t - tr;

		// Build the DETAIL normal
		xmN = XMLoadFloat3A( &XMFLOAT3A(dX, normalStrength, dY) );
			
		// Use the adjoing vertices along both axis to compute the correct normal
		//
		// Tangent Vector :  t = (1, 0, heightmap[x+1, y] - heightmap[x-1, y]) 
		// Binormal Vector : ß = (0, 1, heightmap[x, y+1] - heightmap[x, y-1]) 
			
		xmX = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBufferAdj[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxADJ] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDz * m_uiNumVerticesXZ) + iDxMin] ].p) );

		xmZ = XMVectorSubtract( XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMax * m_uiNumVerticesXZ) + iDx] ].p), 
								XMLoadFloat3(&pTerrainSrcPatchBuffer[ CurrentRemap[( iDzMin * m_uiNumVerticesXZ) + iDx] ].p) );

		// Average DETAIL normal from sobel calculation of heightmap with normal computed from vertices
		xmN = XMVectorAdd(XMVector3Normalize(xmN), XMVector3Normalize(XMVector3Cross(xmZ, xmX)));
		xmN = XMVectorMultiply(xmN, XMM128::LoadAll(0.5f));
		xmN = XMVector3Normalize( xmN );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].n, xmN);

		// Assign Correct Tangent //
		xmX = XMVector3Normalize(xmX);
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].t, xmX);

		// Calculate BiNormal From CrossProduct of Tangent and the Final Normal //
		xmZ = XMVector3Normalize( XMVector3Cross(xmN, xmX) );
		XMStoreFloat3(&pTerrainSrcPatchBuffer[vertexiDx].b, xmZ);
	}
}

And the shader:

VS_OUTPUT vsTerrainParallax( VS_INPUT i )
{
	VS_OUTPUT o;
	
	o.vPosition = mul(i.vPosition,uModelViewProj); 
	o.vTex0 = i.vTex0;

	float3x3 TBN = {i.vTangent,i.vBinormal,i.vNormal};
	TBN = transpose(mul(TBN,uModel));
	
	float3 WPos = mul(i.vPosition,uModel);
	
	// Rotate Light & View Vector Into Tangent-Space
	o.vLightDir = mul(uLightVec - WPos,TBN);
 	o.vViewDir  = mul(uViewVec - WPos,TBN);  
 	o.vHalfAngleDir = o.vLightDir + o.vViewDir;

 	o.vShadowTex = mul(i.vPosition, uTexBiasModelLightViewProj);

	return(o);				
}
     
void psTerrainParallax( PS_INPUT i,
			            out float4 oColor0 : COLOR0 )
{
	float3 vViewDirNormalized = normalize(i.vViewDir);
	
	// Sample Height GrayScale Portion of Parallax Map and Calculate New UV Coordinates //
	//float2 vNewUv = ParallaxMappedUV(tParallaxMap, i.vTex0, vViewDirNormalized.xy);
	
	// Sample Main Diffuse Texture //
	float3 vDiffuseTex = tex2D(tDiffuseMap,i.vTex0);
	
	/*this is how you uncompress a normal map*/
	//float3 vNormalMap = tex2D(tParallaxMap,vNewUv) * 2.0f - 1.0f;
	float3 vNormalMap = float3(0.0f,1.0f,0.0f);//tex2D(tParallaxMap,vNewUv) * 2.0f - 1.0f;

	oColor0 = LitDiffuseSpecular( vDiffuseTex, vNormalMap, normalize(i.vLightDir), 
								  vViewDirNormalized, normalize(i.vHalfAngleDir), 
								  i.vShadowTex );
}
1

Share this post


Link to post
Share on other sites

If your terrain is flat, there shouldn't be any seams even if you aren't using neighbour patches. Are you sure you're calculating your normals correctly? (i have no experience with the sobel operator). Try visualizing the normals with the pixel or geometry shader.

 

Also, i think you're overcomplicating your calculations. The way i do it is to simply store the neighbour vertices with the patches. So if my chunk is 65x65, i store a border around it so that it's 67x67 and use the extra vertices to calculate the normals. This way you won't have to consider each case (top, left, etc..) explicitly. Here's how the final code turns out:

for (int y = 1; y <= mChunkSamples; y++)
{
	for (int x = 1; x <= mChunkSamples; x++)
	{
		float sx = pHeights[(x + 1) + mHeightSamples*y] - pHeights[(x - 1) + mHeightSamples*y];
		float sy = pHeights[x + mHeightSamples*(y+1)] - pHeights[x + mHeightSamples*(y-1)];

		nor.x = -sx * mVerticalScale;
		nor.y = 2 * pChunk->mScale;
		nor.z = -sy * mVerticalScale;
		D3DXVec3Normalize(&nor, &nor);

		// ... store
	}


Edit: I just realized that you are hardcoding your normal to (0, 1, 0). This only leaves the texture... Did you try rendering with lighting only? 

Edited by Waaayoff
0

Share this post


Link to post
Share on other sites
Haha a border so simple. Will implement and get back to you. Also will see if I can verify the normals. I added sobel calculation as it gives a noticeable enhancement with normal mapping. It is based purely off the height map. The tangent and binomial are calculated differently with the y axis now factored in with is important for surfaces that are not flat. The normal from this final calculation is then averaged with the sobel calc to give a good enough, visually, result.
0

Share this post


Link to post
Share on other sites

Well I ran into some problems implementing the border vertices for sampling normals actually became quite complex. So I reverted it and now I'm back to where I was before. I'll have to tackle this bug again. I have to move onto some important features and mechanics. 

Edited by Carandiru
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0