Rendering Q3 BSP

Started by
15 comments, last by Enrico 17 years, 6 months ago
I'm writing a renderer for Q3 BSPs. Sadly, I'm off to a bad start. Loading in the BSP is fine, all my sanity checks work ok. When I come to render it using D3D9, I just get a black screen. The clear colour is magenta so I suppose it's positive that *something* is being drawn. I've tried to narrow down all the candidates that I can and here's what I've got. 1) The fvf could be wrong. I've specified D3DFVF_POSITION | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX2. That's also the order I've declared the vertex, which is where it matters: struct cBspQuake3Vertex { float position[3]; // x, y, z float normal[3]; // x, y, z unsigned byte diffuse[4]; // r, g, b, a float texture[2][2]; // texU, texV, lightMapU, lightMapV }; 2) Lighting is off (because the color is stored in the vertex). 3) Z Buffer is enabled and is cleared each frame. 4) I've set the world, view and projection matrices. The view is set at the player spawn for Q3DM1, the map I'm using. 5) I've converted Quake's coords to DX's LH system (I just swap y and z in position, right?). 6) Cull mode is set to none. 7) I've dumped the whole vertex lump into a vertex buffer and I'm using the meshVerts lump as an index buffer. This is suspect to me - why does Q3 use ints for this when D3DFMT_INDEX32 isn't recommended? Is this where I'm going wrong? Ok, I realise there's a good 1,000,000 things that could be causing this, can anyone think of the most obvious that I might have missed. Cheers, ZdlR
Advertisement
Quote:5) I've converted Quake's coords to DX's LH system (I just swap y and z in position, right?).


Are you talking about converting RH to LH? If so, you only need to invert z and be careful on the winding. I dont know about the quake bsp format, but max exports z up, so in that case I do need to swap y and z, but its for a different reason.

Also, the only other thing I can think of is maybe your camera is too close also, or something with scaling.

But check on that handedness issue.

Jeff.
Quote:Original post by zdlr
When I come to render it using D3D9, I just get a black screen. The clear colour is magenta so I suppose it's positive that *something* is being drawn.


Are you currently implementing any of the culling methods (BSP/PVS)? If so, try disabling those to see if they are causing your problem.

Try moving your camera "back" a long way to see if the general shape of the level is being drawn.

Quote:
2) Lighting is off (because the color is stored in the vertex).


Are you sure your vertex colors are correct (not black)?

Quote:
5) I've converted Quake's coords to DX's LH system (I just swap y and z in position, right?).


I think so. At least for OpenGL (RH system), you swap y and z, then invert z. So, a simple swap looks correct for LH.

webjeff: Quake maps use the Z coordinate for up, so swapping Y and Z is necessary.
I believe Q3 is like Max in that x/y is the player's general movement plane and z is the jumping plane. As it is an Id game, and they use GL, it could be that I also need to switch the handedness from RH to LH?

The view's near is 1.0f and far is 10000.0f (which seemed sensible given the values I saw in some of the BSP leaves).

As for winding, surely cos culling is off that shouldn't matter for now?

I won't be able to fiddle with it until tonight but you can be sure it's all I'm going to be doing this weekend!

ZdlR
Good idea re moving the camera back a long way, I'll try that and see if I'm not just way too close to something. The vertex colours are taken straight from the vertex lump in the file so I'm guessing they're correct! I'll scope a few of them out with the debugger.

I'm not culling anything as yet, throwing the entire tree at the gfx card. I know this isn't 'proper' but, as you said, for debugging purposes it's sufficient for now.

ZdlR
Quote:Original post by zdlr
2) Lighting is off (because the color is stored in the vertex).

The colors look weird most of the time.

Quote:5) I've converted Quake's coords to DX's LH system (I just swap y and z in position, right?).

Don't forget to convert the texture coordinates and the culling order.

Quote:7) I've dumped the whole vertex lump into a vertex buffer and I'm using the meshVerts lump as an index buffer. This is suspect to me - why does Q3 use ints for this when D3DFMT_INDEX32 isn't recommended? Is this where I'm going wrong?

This sounds wrong. Try to render only with the vertex buffer with points instead of triangles. There you can check if that works. When that works, you can start creating triangles. To do that, you need the "MeshVertices"-lump and the "Surfaces"-lump. The first step is to create an IndexBuffer for every face (wasteful). Try this code:
void CreateIndexBuffer(std::vector &faces, std::vector &indices){    std::vector::iterator face = faces.begin();    for(; face != faces.end(); face++)    {        if(1 == face->type || 3 == face->type)        {            // create an Index Buffer for every face            spg::IIndexBuffer *ib = (spg::IIndexBuffer*) m_pRenderDevice->CreateObject(spg::RDO_INDEXBUFFER);                           // create indices array            spg::Array renderIndices;                        renderIndices.Reserve(face->numOfFaceIndices);                        for(size_t i=0; i < (size_t)face->numOfFaceIndices; i++)                        {                                const spg::Index32 index = face->vertexIndex + indices[face->faceVertexIndex + i];                                renderIndices.PushBack(index);                        }                        // create IndexBuffer from indices array and save the Index Buffer            ib->Append(renderIndices);            IndexBuffers.add(ib);        }    }}

This is the code from some articles I have written about Loading and Rendering Quake3 Files (maps and models). They are currently unpublished due to problems with the server. I can send copies via e-mail, just drop me a PM ;)
--
Yeah, thanks Enrico, you've certainly hit on something there. I figured it was a little *too* easy just to throw the meshVerts lump at an index buffer. I've PM'd you.

ZdlR
Quake 3's indices are relative to the face's vertices, not the vertex list as a whole. Q3 loads a separate vertex buffer pointer for every face, hence why the indices are relative to just those face's vertices.
Here is my loader code:

btBoolean btWorld::CreateFromFile(const char *filename){	FILE *fp = NULL;		btErrorLog::GetPtr()->Printf("Loading level:  %s", filename);	// Check if the .bsp file could be opened	if((fp = fopen(filename, "rb")) == NULL)	{ 		//m_pApp->Log("BSP File could not be opened: %s", strFileName);		btErrorLog::GetPtr()->Printf("Could not open map %s!!", filename);		return BT_FALSE;	}	// Initialize the header and lump structures	BSP_HEADER header;	// Read in the header	// This is a really cool chunk of info that	// Tells us where to find all the good stuff	// in the file. Kind of like a "Table of Contents"	fread(&header, 1, sizeof(BSP_HEADER), fp);	m_EntityString = new char[header.entries[lEntities].length / sizeof(char)];	//allocate the space needed for our data structures 	m_NumVerts = header.entries[lVertcies].length / sizeof(BSP_VERTEX);	m_pVerts = new BSP_VERTEX[m_NumVerts];	m_NumFaces = header.entries[lFaces].length / sizeof(BSP_FACE);	//m_Faces = new BSP_FACE[m_NumFaces];	m_Faces.resize(m_NumFaces);	m_NumIndices = header.entries[lIndices].length / sizeof(BSP_INDEX);	m_pIndices = new BSP_INDEX[m_NumIndices];	m_NumTextures = header.entries[lTextures].length / sizeof(BSP_TEXTURE);	m_Textures = new BSP_TEXTURE [m_NumTextures];	m_Materials.resize(m_NumTextures);	for (size_t k = 0; k < m_NumTextures; k++)		m_Materials[k] = NULL;	// Now this part is kinda messy. In reality, I should be setting this	// up so that we never have to seek backwards in the file, BUUUUUTTT...	// since this isn't a commercial project, and we're not too concerned	// about load times we're just gonna let it slide for now.		// Seek to the position of the entities	fseek(fp, header.entries[lEntities].offset, SEEK_SET);	// Read in the entity string	fread(m_EntityString, header.entries[lEntities].length, sizeof(char), fp);	// Log them to file//	btErrorLog::GetPtr()->Printf("%s\n", m_EntityString);	// Seek to the position in the file that stores the vertex information	fseek(fp, header.entries[lVertcies].offset, SEEK_SET);		// Read in the vertex information 	fread(m_pVerts, m_NumVerts, sizeof(BSP_VERTEX), fp);	// Seek to the position in the file that stores the index information	fseek(fp, header.entries[lIndices].offset, SEEK_SET);		// Read in the index information 	fread(m_pIndices, m_NumIndices, sizeof(BSP_INDEX), fp);	// Seek to the position in the file that stores the face information	fseek(fp, header.entries[lFaces].offset, SEEK_SET);	// Read in all the face information	for (u32 i = 0; i < m_NumFaces; i++)		fread(&m_Faces, 1, sizeof(BSP_FACE), fp);	// Seek to the position in the file that stores the texture information	fseek(fp, header.entries[lTextures].offset, SEEK_SET);	// Read in all the texture information	fread(m_Textures, m_NumTextures, sizeof(BSP_TEXTURE), fp);	// Close the file	fclose(fp);	IbtEngine *engine = btEngine_GetSingleton();	// First, we're going to go through all of the textures	// All we're given is file path (not even an extension!)	// So we're going to first locate the file, and then load	// it into a DirectX texture.	for(u32 i = 0; i < m_NumTextures; i++)	{		char								normalmapname[64];		ZeroMemory(normalmapname, 64);		strcpy(normalmapname, m_Textures.name);		strcat(normalmapname, "_n");		// Find the texture path/extension		FindTextureExtension(m_Textures.name);		FindTextureExtension(normalmapname);		// Load the texture based off of what we found.		//if(FAILED(D3DXCreateTextureFromFile( m_pDevice, m_pTextures.name,        //                           &m_pTextureMaps )))		m_Materials = engine->CreateMaterial();		if (!m_Materials->SetTextureLayer(0, IbtMaterial::LAYER_BASE, m_Textures.name))		{			//m_pApp->Log("Texture not Found: %s", m_pTextures.name);			// If the texture can't be loaded, simply make it NULL.			//m_pTextureMaps = NULL;			btErrorLog::GetPtr()->Printf("Could not create material %s!!", m_Textures.name);			BT_SAFE_RELEASE(m_Materials);			if (m_Materials != NULL)				btErrorLog::GetPtr()->Printf("Material #%d is not NULL!!", i);		}		else			btErrorLog::GetPtr()->Printf("Loaded texture:  %s", m_Textures.name);		FILE	*test = NULL;		if ((test = fopen(normalmapname, "rb")) != NULL)		{			btErrorLog::GetPtr()->Printf("Adding bump map to %s", m_Textures.name);			fclose(test);			if (!m_Materials->SetTextureLayer(1, IbtMaterial::LAYER_BUMPMAP, normalmapname))				continue;		}	}	HRESULT									hres;	// Occlusion Culling stuff	hres = m_pDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, &m_pOccluder);	if (FAILED(hres))	{		btErrorLog::GetPtr()->Printf("Could not create occluder!!");		return BT_FALSE;	}	D3DDISPLAYMODE					mode;	hres = m_pDevice->GetDisplayMode(0, &mode);	if (FAILED(hres))	{		btErrorLog::GetPtr()->Printf("Could not get display mode!!");		return BT_FALSE;	}	hres = D3DXCreateTexture(m_pDevice, 320, 240, 1, D3DUSAGE_RENDERTARGET, D3DFMT_UNKNOWN, D3DPOOL_DEFAULT, &m_pOccluderTexture);	if (FAILED(hres))	{		btErrorLog::GetPtr()->Printf("Could not create occluder texture!!");		return BT_FALSE;	}	D3DSURFACE_DESC					desc;	m_pOccluderTexture->GetSurfaceLevel(0, &m_pOccluderSurface);	m_pOccluderSurface->GetDesc(&desc);	hres = D3DXCreateRenderToSurface(m_pDevice, desc.Width, desc.Height, desc.Format, TRUE, D3DFMT_D16, &m_pOccluderRender);	if (FAILED(hres))	{		btErrorLog::GetPtr()->Printf("Could not create occluder!!");		return BT_FALSE;	}	// Create Vertex Buffer. I shouldn't need to explain this.	// The only thing to notice is that we're creating exactly	// enough space for all of our Vertices, yet we're using a	// different struct than what we read the verts into.	// (We read them into a BSP_VERTEX, and are creating space	// for a BSP_CUSTOM_VERTEX)	m_pDevice->CreateVertexBuffer( m_NumVerts * sizeof(BSP_CUSTOM_VERTEX), 0,								   BSP_CUSTOM_VERTEX_FVF, D3DPOOL_DEFAULT, &m_pVB, NULL );	BSP_CUSTOM_VERTEX *pVertices = NULL;	// Again, no explanation needed, right? Typical DirectX stuff.	m_pVB->Lock(0, m_NumVerts * sizeof(BSP_CUSTOM_VERTEX), (VOID**) &pVertices, 0);		// Now, at this point it would be simply LOVELY if we could 	// simply do this:	//	// memcpy(pVertices, m_pVerts, numVerts * sizeof(BSP_VERTEX));	//	// However, no such luck here, since there's a slight 	// Quirk in how DirectX processes it's vertices. You see,	// The information inside the vertex had to be in a 	// particular order for DirectX to draw it correctly. In	// this case that order is: 	//	// Position->Normal->Color->Texture1->Texture2	//	// DirectX is happy with this, and all is right in the world.	// However, the BSP file says differently! It's data comes to	// you in THIS order:	//	// Position->Texture1->Texture2->Normal->Color	//	// Feed this to DirectX and it will puke random red triangles 	// all over your screen. (Trust me, I've tried!)	// SO, what this means is that instead of a nice and easy memcopy	// we get to loop through each vert like so:	for(i = 0; i < m_NumVerts; i++)	{		pVertices.x = m_pVerts.position[0];		pVertices.y = m_pVerts.position[2];		pVertices.z = m_pVerts.position[1];		pVertices.nx = m_pVerts.normal[0];		pVertices.ny = m_pVerts.normal[2];		pVertices.nz = m_pVerts.normal[1];						pVertices.color = D3DCOLOR_RGBA(m_pVerts.color[0],m_pVerts.color[1],										   m_pVerts.color[2],m_pVerts.color[3]);		pVertices.tu = m_pVerts.texcoord[0];		pVertices.tv = m_pVerts.texcoord[1];		pVertices.lu = m_pVerts.lightmap[0];		pVertices.lv = m_pVerts.lightmap[1];	}	m_pVB->Unlock();		// Create Index Buffer	// NOTICE: This has been altered from past tutorials since we had several	// complaints that it wasn't working on older cards. Now it will check	// the caps for the Index Buffer Type and create the proper buffer accordingly.	D3DCAPS9 DevCaps;	((btEngine*)engine)->GetCaps(&DevCaps);	if(DevCaps.MaxVertexIndex > 0x0000FFFF)	{		m_pDevice->CreateIndexBuffer( m_NumIndices * sizeof(BSP_INDEX), 0,									D3DFMT_INDEX32, D3DPOOL_DEFAULT, &m_pIB, NULL );				BSP_INDEX *pIndices = NULL;					m_pIB->Lock(0, m_NumIndices * sizeof(BSP_INDEX), (VOID**) &pIndices, 0);				// Ah, now HERE we have the luxury of a memcpy, because, well		// a number is a number. And there was much rejoicing!		memcpy(pIndices, m_pIndices, m_NumIndices * sizeof(BSP_INDEX));		m_pIB->Unlock();	}	else	{		//m_pApp->Log("Hardware does not support 32 Bit Indcies. Forcing to 16 Bit");				m_pDevice->CreateIndexBuffer( m_NumIndices * sizeof(BSP_INDEX), 0,									D3DFMT_INDEX16, D3DPOOL_DEFAULT, &m_pIB, NULL );				SHORT *pIndices = NULL;					m_pIB->Lock(0, m_NumIndices * sizeof(SHORT), (VOID**) &pIndices, 0);				for(i = 0; i < m_NumIndices; i++)		{			pIndices = (SHORT) m_pIndices;		}		m_pIB->Unlock();	}	// Sort faces by texture	std::sort(m_Faces.begin(), m_Faces.end(), FaceSortFunctor());	// Create vertex declaration	hres = m_pDevice->CreateVertexDeclaration(g_VDecl, &m_pVDecl);	if (FAILED(hres))	{		btErrorLog::GetPtr()->Printf("Could not create vertex declaration!!");		return BT_FALSE;	}		btErrorLog::GetPtr()->Printf("Num Faces:  %d", m_NumFaces);	btErrorLog::GetPtr()->Printf("Num Vertices:  %d", m_NumVerts);	btErrorLog::GetPtr()->Printf("Level %s loaded successfully...", filename);	//And we're done building the level! Was that too hard?	return BT_TRUE;}
Anthony Rufrano
RealityFactory 2 Programmer
Cheers for that, paradox, much appreciated.

Quote:
// Now, at this point it would be simply LOVELY if we could
// simply do this:
//
// memcpy(pVertices, m_pVerts, numVerts * sizeof(BSP_VERTEX));


Bah. I didn't even think about it being in a diff order in the bsp file! How've I missed that? Looks like I've a long weekend ahead of me.

In other news, throwing the vertex buffer at the gfx card as a point list was still fruitful, thanks to the position being in the right order in the vertex format. I can actually make out the starting courtyard of Q3DM1. Progress!

Thanks again,
ZdlR

This topic is closed to new replies.

Advertisement