Source Eyes: Taking a closer look

I’ve been working on a few projects recently, but I think I have enough to talk about to write up something substantive. I’ve been doing a lot with faceposing, either doling out advice, working on commission, or doing my own projects with it. On other fronts, I’ve been breaking into high poly hard surface modeling, and have even had a chance to do a bit of mapping. Today’s focus, though, will be talking about valve’s eye shader and why it breaks. You can find that about in the middle of the post.

Making characters
I’ve been focusing a good bit of my efforts in honing my craft making face flexes for source models. I’m not the fastest, most experienced, or the best artistically, but I do like to focus on conforming to valve standards for maximum compatibility and am generally ready to discuss the process with people that are serious about making some for their own characters. Needless to say, I find myself discussing the subject quite often, and I’ve been doing a few projects to improve my craft and experience in hopes that I can dole out better advice.
My recent public work since Dr. Schwaiger has been two female heads, and I have learned a ton from working on both of them. I question the potential use by the community – single character releases are usually highly niche and rarely see regular usage from the producer (screenshots, movies, derivative works) or consumer (roleplay and playermodels) communities, but as case studies and skill builders these projects have been invaluable.

This was an overly-ambitious undertaking that ended up taking way more then planned at every turn. My goal was to completely convert an undead/zombie model from Resident Evil: Operation Raccoon City. (Credit to luxox_18 for grabbing the model)

Strait form the port
Strait form the port

I chose the model for a few solid reasons:
The model wasn’t some named character. There is literally no reference of this head anywhere and it was designed to be non-unique, which is nice for a background or unimportant character that has a speaking role.
I’d have a go at restructuring a face because the default state was nonstandard (open mouth, wide eyes).
I would have to heavily modify the texture – I had intentions to convert it the skin to a ‘living state.’
It had good vertex distribution for a background character, at least in my initial opinion.

Well I asked for a challenge and I got it. Reshaping the face proved to be a huge hurdle. I assumed this have been easy given the fact that reshaping faces is what I was going to do anyway, but this was a bit less of moving a mouth to match a specific look and more like reconstructive surgery.

A rough first pass
A rough first pass

The texture also proved to be a huge hurdle. The face had unusual unwrapping; for some reason the lips alone had substantially higher pixel density then the rest of the face, and clone brushing the original texture from dead to alive proved to be a lost cause. My alternative was taking another texture (in this case Lara Croft’s from the reboot because the chin-centric center of the scalp split unwrap was similar) and modifying it for my needs.I ended up reshaping everything and still got an incredibly flat and uninteresting result. The following weeks turned into a battle of tweaking and fine tuning, I baked AO maps with the hair, without the hair, in a bowl, and in a void. On top of all that I still manually shaded areas and did further retouching. I can’t think of many instances of having to work so hard to make a texture not look good but just not look bad.

Incremental progress, and the starter head, for reference
Incremental progress, and the starter head, for reference

Once things got underway, faceposing was a bit more difficult then I had anticipated mostly because of a not completely symmetrical mesh but also because the head was a little lower poly then ideal where it counts. I found that having anything less then a sizable number of vertices around the mouth causes problems down the line and specific flexes can start looking terrible due to poor interpolation. Finally, I couldn’t make a convincing smile, which frustrates me to no end.

I decided to have a go at rigging the body as well. I’m still not happy with my own skill in that, but I’d say it’s on the lower end of ‘passable.’ Rigging is a skill you develop mostly with practice and experience, so the more rigging I do the better, even if my current efforts feel in vain.


In the end, although a frustrating project, I’m glad I did the work. You can pick up a version in the SFM workshop – I’m hoping to barter with a few of my peers to get better rigging on the bodies and also better hands before I make a large scale release.

The finished product
The finished product

And she’s available on the SFM workshop.

The second model that I’ve recently done flexes for is a character from Eve Online, or so I’m told. I can’t say too much about the origin of the head, the mesh and textures were sent to me by wraithcat, who manages to have a way to grab any model from anywhere with impressive skill and efficiency.


It’s all in the eyes
Anyway, this was a great opportunity for me to test out a working theory, or at the very least implement a fix to a common problem when I was supposed to. If you’ve ever seen models in source screenshots or machinima and their eyes seem to be ‘split,’ this is a problem that is created from a conflict between the actual geometry of the model and the eye shader’s implied geometry.


So without going to far into the math behind it (mostly because it’d be primarily be conjecture on my part – there’s no official documentation of the underlying shader code, and although the source code for eyerefract does exist out there, I haven’t looked at it), the bottom line is that the shader takes a flat surface and uses key values grabbed from the .qc and engine to draw a sphere with the right lighting and refraction and with a cornea and pupil that’s pointed in the right direction. That’s right, there is no ‘model’ that the eye is drawn from, it’s better to think of it as a dynamic layer in photoshop that uses a surface with the right shader applied as a mask.

The .qc tells the shader where the ‘eye’ is – it exists at a specified XYZ offset from the root of the model, and takes into account the parent bone its attached to. From there, it finds out where the root of the model is in the map. At that point it has where the sphere needs to be drawn. The other half of the problem is what it’s looking at. In source, this is a view target, be it an invisible bone attached to the model, a point of interest the mapper put in, or a dynamically generated one that an npc would look at, like a ragdoll from a corpse or even the player character. Once the shader knows these two points, it knows what it needs to draw an ‘eye’ pointed in the right direction onto a flat surface. That’s why the eyeball lines are so important in a qc. The shader will draw a sphere like it’s supposed to, but if you don’t tell it the two correct spots, the shader will draw the spheres in the wrong location and look ‘broken.’

The eyes weren't properly centered in the .qc and I got a strange result.
The eyes weren’t properly centered in the .qc and I got a strange result. Other errors are the classic lazy-eye/cross-eye syndrome

The other important bit, and this is far more insidious in its problem, is that you can’t draw a sphere onto a sphere very well. Sure, you can pinpoint the correct XYZ and get it all perfect, but it still might still give you bad results because of this. Source eyes are kind of a special case; not many games implement eyes as a pure shader. Most choose to make an actual hemisphere and texture it traditionally, where the hemisphere rotates up and down, left and right on a pivot. This is a common sense approach and completely fair, what with the advent of graphics cards made since 2005. Modeling the eyes out is a simpler solution – after all, what’s 200 extra triangles in a modern engine? That said, models ported to source or made for source without knowledge of the eye shader will generally have hemisphere eyeballs. When applying the shader to these hemispheres, a unique problem crops up along the outer edges of the mesh at specific angles, producing a fractured or duplicated looking cornea. This is an inherent problem with the shader. I can’t say specifically why it breaks at extreme angles, but it is most likely due to a similar effect as taking a picture of a piece of grid paper with a fisheye lens. The image distorts at the far angles and straight lines become curved. Now imagine you take the flat piece of paper, glue it to a basketball, and then take a picture of it with a fisheye lens. You most likely won’t see straight lines at all, and if you do, they’re at the very center. Source is expecting a flat plane to draw the eye onto, not a curved surface. Since it is a pixel shader (as in it runs independently for each pixel that it touches and not as a group) that uses the angle of the surface it is drawing to relative to the viewer (you) as part of its calculations, having adjacent pixels that are expected to be roughly the same angle not at all the same (as in the rim of the sphere from a viewer’s perspective) completely changes what’s drawn. The illusion breaks down at sharp angles, and there is an apparent threshold of effective rendering that is likely never encountered by on flat surface but is common on a spherical one if the ‘eyes’ are pointed near the outer rim. Simply put, the shader was written to be ‘good enough’ to work on a flat surface, not any surface.

Bottom line is if you’re going to port over something with eyes and you’re starting from scratch with the flexes, you should take the first step of modeling out flat planes to replace the spheres. It’s okay if it’s not completely flat, just bridge the lower eyelid to the upper eyelid to fill the hole and apply the eyeball texture to that. The shader will do the rest. The source eye shader is incredibly awesome, I just wish more people knew how to use it correctly.


Getting back to the narrative of recent work of mine, that’s exactly what I did with Eve – I replaced the spheres with planes and boy did it work like a charm. This project was a test-bed in applying the lessons learned in my previous misadventures in making flexes. I took care to apply the eye fix, and I make sure every vertex was welded where it should have been. Since this was a port/rip, the mesh required cleanup – a good deal of vertices had been split along invisible seams, and the inner mouth was not at all attached to the lips. Once I combined everything, including the planes I made for the eye shader, I started working with the flexes.

All 40 base shapes
All 40 base shapes

Following the valve templates for FACS, the first four flexes you make are the eyelid shapes. That brings me to the next major topic of discussion for this post: eyelids.

It’s in the eyelids too
Not to be outdone by dynamic eyeball shaders, the programmers at valve took it one step further with the eyelids. Here, they use data from the qc as well as the data for rendering the eyeballs and dynamically move the eyelids as needed. If properly entered, the first four frames of your base shapes (blink, squint, droop, and bulge) are blended and applied in real time depending on where the model is looking. Forgive me for gushing but this is freakin sweet and sorely unappreciated. The best way to illustrate the effect is through video, but I’ll settle for second best with an image. Keep in mind, no sliders were manipulated here, this is just the viewtarget being moved about.


It’s a subtle but very effective feature. Implementing it as a custom content author is a bit trickier to do then eyeballs, which is why I think it’s not often implemented at the ‘casual addon’ level. Fear not though! The VDC has some excellent documentation on both subjects if you take the time to read through it. To top it off, you can dig through the bin folder (same spot as mdlviewer and hammer) to find qceyes, which will take the input values and spit out half of a .qc. I also wrote a slightly less obtrusive tool that has the same function, and has tooltips for just about every field.

Simple, but better then a piece of paper
Simple, but better then a piece of paper

You can grab it here. It’ll spit out the following lines for your .qc:

eyeball righteye ValveBiped.Bip01_Head1 -1.263 -3.275 64.894 eyeball_r 1 4 "unused" 0.63
eyeball lefteye ValveBiped.Bip01_Head1 1.263 -3.275 64.894 eyeball_l 1 -4 "unused" 0.63

eyelid upper_right “eve_flex” lowerer 1 -0.244 neutral 0 0.314 raiser 2 0.381 split 0.1 eyeball righteye
eyelid lower_right “eve_flex” lowerer 3 -0.334 neutral 0 -0.282 raiser 4 -0.008 split 0.1 eyeball righteye
eyelid upper_left “eve_flex” lowerer 1 -0.244 neutral 0 0.314 raiser 2 0.381 split -0.1 eyeball lefteye
eyelid lower_left “eve_flex” lowerer 3 -0.334 neutral 0 -0.282 raiser 4 -0.008 split -0.1 eyeball lefteye

So to wrap up talking about Eve, I spent a little time changing the look of the head, so here’s a collection of wip shots outlining the incrimental changes as I added flexes.

Not as dramatic as Rita's transition but well worth the effort.
Not as dramatic as Rita’s transition but well worth the effort.


I’d also like to link to Eve’s source files. I took a great deal of time fully documenting my .qc and also the FACS .qci! I tried my best to write a short description of the base shape for quick reference. I also included a max file (and basic textures) that has all of the morph targets neatly laid out and verbosely named so you can match up with the .qci. The file is 3dsmax10+ compatible. I hope it helps, and I apologize in advance for typos in the inline documentation.

Before I go, here’s some shots of my latest changes to my scifi map. Thanks for reading!




Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s