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: 8768
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 19:29:34
Nice article. I would be interested to see a tutorial with animation...

Posted By: Patrick on Jan 06 2004 at 07: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 07 2004 at 02: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 14:40:27
@infiniteloop: OpenGL.

Posted By: XWider on Nov 25 2004 at 15: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 05 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
knock knock
General | News
By: MIFUNE | Dec 31 2017
 
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (3 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!