Welcome, Guest! Login | Register

"Dynamic" skies in Half-Life [Print this Article]
Posted by: Patrick
Date posted: May 16 2003
User Rating: 4.5 out of 5.0
Number of views: 5112
Number of comments: 5
Description: Controllable skies
Originally I was going to make this a copy-and-paste tutorial, but it got way too complex; I have a custom polygon-organization system, and making a new-but-simpler one specifically for a tutorial wouldn't be worth the effort. Besides, any mod that really needs anything like this probably has one already.

So, you want skies that you render and control yourself, eh? I'll start off be desribing the sky "effect" in Doom/Quake2 etc. style engines. Basically, you have 6 textures/images rendered in such a way that when they're mapped properly to a perfect cube, the appearance is a seemless sky. In other words, you have an up, down, left, right, front, and back sky image (up, dn, lf, rt, ft, bk). Imagine 6 quads forming a perfect cube around the player/view position, and map the appropriate images to the appropriate quads. Kinda simple huh?

So, how does one do this in Half-Life? There's quite a few steps, but it's not really difficult. First of all, think about what information is needed to do this. You need to know how far away each quad should be (they're all equidistant, so one number for the distance works fine); you also need to know the "base name" for the images to use (the "dusk" in "duskup", "duskdn", "dusklf", "duskrt", "duskft", and "duskbk"). Depending on what you plan on doing with this, you might also want to know how many frames there are (for animation), along with the color of the sky and the transparency value. For the sake of this "article," let's assume you're going to use one color and alpha/transparency value for all six quads, and you're not doing animations.

So, on the server-side dll project (hl.dll or mp.dll, whatever), you need to make a new entity (I called mine CSkyBox). Whether it's brush-based or point-based is up to you and how you want it to function, along with whether or not you want it to be toggle'able.

Here's what my .fgd looks like for this entity:
 CODE  

@PointClass base (Target, Targetname) = env_skybox : "Sky Box"
[
    renderamt(integer) : "FX Amount (1 - 255)"
    rendercolor(color255) : "FX Color (R G B)" : "0 0 0"
    starton (choices) : "Start on?" : 1 =
    [
        0 : "No"
        1 : "Yes"
    ]
    distance (string) : "Distance" : "500"
    skyset (string) : "Sky set name" : ""
]


When coding this class, I made use of a custom think method, and when the entity spawns, I set the think to fire in a few seconds (to make sure the client's there and all). That think method sends the on/off state, and if it's on, it sends the keyvalue data (WRITE_COORD for the distance, WRITE_STRING for the base name, WRITE_BYTE for the color and alpha).

On the client-side (cl_dll), I wrote a corresponding HUD class, CHudSkyBox. I use the HUD_ACTIVE bit and m_iFlags for the on/off state. After the message is done being received, I attempt to load all of the .tga images using gEngfuncs.LoadMapSprite. If and only if all images were successfully loaded, I set HUD_ACTIVE. Instead of the Draw method, I have a Render method, which takes a triangleapi_t pointer (for my typing ease, that's all). If HUD_ACTIVE is set in m_iFlags, I go ahead and render the quads. For the sake of this article, I wrote a sample Render method that would work without any polygon-organization system. However, there's one little problem. LoadMapSprite returns a model_s pointer containing the .tga split up into possibly several sprite frames. I think 128x128 (didn't it use to be 256x256?) is the base size, and any size larger gets split up into more than one frame. What does that mean? You need to draw multiple quads for actual image. (So, a 256x256 .tga would get split into 4, or 2x2, sprites. That means, for all six sky panels, you'd draw four sub-quads.)

 CODE  
// (this works perfectly if the .tgas are 128x128)
void CHudSkyBox :: Render (triangleapi_t *pRender)
{
    if (!(m_iFlags & HUD_ACTIVE))
        return;

    float view[3], upv[3], rightv[3], vpnv[3];  // we only want view, but the others
                    // are to avoid any errors
    IEngineStudio.GetViewInfo (view, upv, rightv, vpnv);

    float x = view[0];
    float y = view[1];
    float z = view[2];
    float d = m_flDistance;

    float verts[8][3] = // 8x3--8 sets of 3 floats, so 8 vertexes
    {
        { -d + x, -d + y, -d + z }, // front-left-bottom
        { -d + x, -d + y,  d + z }, // fromt-left-top
        {  d + x, -d + y,  d + z }, // front-right-top
        {  d + x, -d + y, -d + z }, // front-right-bottom

        { -d + x,  d + y, -d + z }, // back-left-bottom
        { -d + x,  d + y,  d + z }, // back-left-top
        {  d + x,  d + y,  d + z }, // back-right-top
        {  d + x,  d + y, -d + z }  // back-right-bottom
    };

    float texCoords[4][2] =
    {
        { 0.0f, 1.0f }, // bottom-left
        { 0.0f, 0.0f }, // top-left
        { 1.0f, 0.0f }, // top-right
        { 1.0f, 1.0f }  // bottom-right
    };

    float *quads[NUM_QUADS][4] =
    {
        // front
        {
            verts[3],
            verts[2],
            verts[1],
            verts[0]
        },
        // back
        {
            verts[4],
            verts[5],
            verts[6],
            verts[7]
        },
        // left
        {
            verts[0],
            verts[1],
            verts[5],
            verts[4]
        },
        // right
        {
            verts[7],
            verts[6],
            verts[2],
            verts[3]
        },
        // top
        {
            verts[6],
            verts[5],
            verts[1],
            verts[2]
        },
        // bottom
        {
            verts[4],
            verts[7],
            verts[3],
            verts[0]
        }
    };

    for (int i = 0; i < NUM_QUADS; i++)
    {
        pRender->SpriteTexture (m_pTextures[i], 0);
        pRender->CullFace (TRI_NONE);
       
        if (m_iRenderAmount < 255)
            pRender->RenderMode (kRenderTransAlpha);
        else
            pRender->RenderMode (kRenderNormal);

        pRender->Begin (TRI_QUADS);

        pRender->Color4ub (m_vecRenderColor.x, m_vecRenderColor.y,
                 m_vecRenderColor.z, m_iRenderAmount);

        // Notice that we're not looping through each vertex.  This is called
        // unrolling loops.  It's a good performance booster.
        pRender->TexCoord2f (texCoords[0][0], texCoords[0][1]);
        pRender->Vertex3fv (quads[i][0]);

        pRender->TexCoord2f (texCoords[1][0], texCoords[1][1]);
        pRender->Vertex3fv (quads[i][1]);

        pRender->TexCoord2f (texCoords[2][0], texCoords[2][1]);
        pRender->Vertex3fv (quads[i][2]);

        pRender->TexCoord2f (texCoords[3][0], texCoords[3][1]);
        pRender->Vertex3fv (quads[i][3]);

        pRender->End ();
    }
}


Seeing as how this is my first "article"/tutorial, any and all feedback is greatly appreciated. Maybe I'll eventually turn it into a full-fledged tutorial like I originally planned.

Rate This Article
This article is currently rated: 4.5 out of 5.0 (2 Votes)

You have to register to rate this article.
User Comments Showing comments 1-5

Posted By: infiniteloop on Jan 05 2004 at 13:29:34
Nice article. I would be interested to see a tutorial with animation...

Posted By: Patrick on Jan 06 2004 at 01:11:57
This is not technically a tutorial. I simply wanted to point out with a simple example of how possible and easy it would be to create your own dynamic sky system, hence why I made it an article.

Posted By: infiniteloop on Jan 06 2004 at 20:40:26
I should have been more clear... I meant I would be interested to see a tutorial of this sort with animated textures (or something) in place of static textures for the skybox...

Posted By: XENO on Mar 21 2004 at 08:40:27
@infiniteloop: OpenGL.

Posted By: XWider on Nov 25 2004 at 09:20:22
AFAIK, texture splitting depends on 'gl_max_size' DVar.


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 04 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
Spinning Corpses Simple Fix
Half-Life | Coding | Snippets
By: darkPhoenix | Sep 05 2008
 
Where do we go from here
General | News
By: MIFUNE | Jun 09 2008
 
The Input/Output system
Half-Life 2 | Level Design
By: nazitaco | Dec 23 2007
 
Where do we go from here
General | News
By: Rob_F | Nov 22 2007
 
Rescaling Half-Life
Half-Life | Coding | Shared Tutorials
By: christoph | Nov 12 2007
 
GameUI
Half-Life 2 | Coding | Client Side Tutorials
By: Evil_j | Oct 29 2007
 
3 State Zoom For Any Weapon
Half-Life 2 | Coding | Server Side Tutorials
By: Ennuified | Oct 18 2007
 
Storing weapons on ladder
Half-Life 2 | Coding | Snippets
By: cct | Sep 07 2007
 
CTF Gameplay Part 1
Half-Life | Coding | Shared Tutorials
By: DarkNight | Aug 28 2007
 
CTF Gameplay Part 1
Half-Life | Coding | Shared Tutorials
By: deedok | Aug 20 2007
 

Site Info
296 Approved Articless
5 Pending Articles
3940 Registered Members
1 Person Online (54 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!