I'm trying to recreate a Return To Castle Wolfenstein renderer using Direct3D 11, but I can't seem to be able to render the lightmaps as they should be. I have not found much information on the internet on how Quake3/RTCW lightmaps are actually created from the BSP files. Basically, the result is as below:

Lightmaps not scaled right

And this is how the result should be:

Correct lightmap display

With the info taken from Q3 BSP file format, I extracted the lightmap x and y origin inside the lightmap data taken from the lightmap index lmindex, in the BSP file from lm_start[0] and lm_start1 and the width and height from lm_size[0] and lm_size1. The problem seems to be in finding the lightmap texture coordinates, which I assumed were the same as the face textures. After each lightmap extracted, I assigned the lightmap index to the face index. A negative index indicates that no lightmap is associated to the face.

Here is the code I used to create lightmaps from the file:

bool XWorldMap::CreateLightmaps(ID3D11Device* pd3d, ID3D11DeviceContext* 

    for (int i = 0; i < m_numFaces; ++i)
        ID3D11ShaderResourceView* pTexture = nullptr;
        int x = m_q3Faces[i].lmstart[0];
        int y = m_q3Faces[i].lmstart[1];
        int lightMapWidth = m_q3Faces[i].lmsize[0];
        int lightMapHeight = m_q3Faces[i].lmsize[1];
        int lightMapIndex = m_q3Faces[i].lmindex;
        m_q3Faces[i].lmindex = (lightMapIndex < 0) ? -1 : i;

        if (lightMapIndex < 0 || lightMapWidth == 0 || lightMapHeight == 0)

        std::vector<char> lightMapData;

        for (int j = y; j < y + lightMapHeight; ++j)
            for (int k = x; k < x + lightMapWidth; ++k)

        if (!CreateTexture(pd3d, pd3dDeviceContext, lightMapData, lightMapHeight, lightMapWidth, &pTexture))
            return false;


    return true;

This is the code to create the lightmap textures:

bool CreateTexture(ID3D11Device* pd3d, ID3D11DeviceContext* 
pd3dDeviceContext, std::vector<char> imgData, unsigned int height, unsigned 
int width, ID3D11ShaderResourceView ** pTexture)
    HRESULT hr;
    ID3D11Texture2D *pTex2D = nullptr;
    D3D11_TEXTURE2D_DESC texDesc;
    memset(&texDesc, 0, sizeof(D3D11_TEXTURE2D_DESC));
    texDesc.Width = width;
    texDesc.Height = height;
    texDesc.MipLevels = texDesc.ArraySize = 1;
    texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    texDesc.SampleDesc.Count = 1;
    texDesc.SampleDesc.Quality = 0;
    texDesc.Usage = D3D11_USAGE_DYNAMIC;
    texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    texDesc.MiscFlags = 0;

    hr = pd3d->CreateTexture2D(&texDesc, nullptr, &pTex2D);

    if (FAILED(hr))
       return false;

    D3D11_MAPPED_SUBRESOURCE  mappedTex;
    pd3dDeviceContext->Map(pTex2D, 0, D3D11_MAP_WRITE_DISCARD, 0, 

    UCHAR* pTexels = (UCHAR*)mappedTex.pData;
    int j = 0;
    for (UINT row = 0; row < texDesc.Height; row++)
        UINT rowStart = row * mappedTex.RowPitch;
        for (UINT col = 0; col < texDesc.Width; col++)
            UINT colStart = col * 4;
            pTexels[rowStart + colStart + 0] = imgData[j++];
            pTexels[rowStart + colStart + 1] = imgData[j++];
            pTexels[rowStart + colStart + 2] = imgData[j++];
            pTexels[rowStart + colStart + 3] = imgData[j++];

    pd3dDeviceContext->Unmap(pTex2D, D3D11CalcSubresource(0,0,1));

    memset(&srvDesc, 0, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
    srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = 1;
    hr = pd3d->CreateShaderResourceView(pTex2D, &srvDesc, pTexture);

    if (FAILED(hr))
       return false;

    return true;

1 Answer 1


The problem seems to be in finding the lightmap texture coordinates, which I assumed were the same as the face textures.

They're not.

If we examine the RTCW source code, paying particular attention to the ParseTriSurf function, we see the code for loading lightmap texture coordinates is as follows:

    for ( j = 0 ; j < 2 ; j++ ) {
        tri->verts[i].st[j] = LittleFloat( verts[i].st[j] );
        tri->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] );

And the drawVert_t structure is:

typedef struct {
    vec3_t xyz;
    float st[2];
    float lightmap[2];
    vec3_t normal;
    byte color[4];
} drawVert_t;

Here, "st" is the standard OpenGL convention for diffuse texture coordinates (Direct3D typically uses "uv") so we can see that lightmap texture coordinates follow them in the struct.

  • \$\begingroup\$ Figured it out in the end, the lightmap coordinates were stored in the BSP vertices, not in the faces. Everything works now. Wonder how I could have missed that earlier. \$\endgroup\$
    – Hermetix
    Commented Jul 12, 2017 at 14:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .