http://asawicki.info/ Graphics programming, game programming, C++, games, Windows, Internet and more...
Entries for tag "math", ordered from most recent. Entry count: 57.
Pitfalls of Floating-Point Numbers - Slides
Here you can find slides from my presentation, in Polish. It's called "Pu³apki liczb zmiennoprzecinkowych" ("Pitfalls of floating-point numbers").
Here are links to the Floating-Point Formats Cheatsheet (in English) that I mentioned in my presentation:
Watch out for reduced precision normalize/length in OpenGL ES
GLSL language for OpenGL ES introduces concept of precision. You can annotate variable declaration (both float and int/uint) with a precision qualifier:
mediump float a = 3.0;
You can also specify default precision qualifier by using precision statement. Language specification defines minimum required range and precision for each precision qualifier.
highpbasically means normal, single-precision, 32-bit float (IEEE 754), as we know it from CPU programming.
mediumpis said to have have range of at least -2^14 ... 2^14 and relative precision 2^-10, so it can be, for example, implemented using a 16-bit, half-precision float.
lowpis said to have range at least -2 ... 2 and absolute precision 2^-8, so basically it can be stored as a 10-bit, fixed-point number.
GPU vendors are free to use more precise data types, or even full 32-bit float for all of them. What exact precision is used depends on specific GPU and maybe even operating system or graphics driver version. Using smaller data types can occupy less memory, calculate faster and consume less battery power. But it comes at the price of reduced precision and range of these numbers. Tom Olson wrote interesting articles about this: "Benchmarking floating-point precision in mobile GPUs": Part I, Part II, Part III.
In this post I'd like to warn you against a specific problem related to it - usage of
distance() functions. Using smaller data types not only limits precision in terms of number of significant digits, but also available range (over which the value will saturate to -INF/+INF). For mediump, this range is defined as +/-2^14, which is only 16384.
This may still look like a lot, but let's remember that calculating vector length involves intermediate value that it sum of squares of this components. This can grow very big before a square root is applied. For example, for 3D vector:
length(a) = sqrt(a.x*a.x + a.y*a.y + a.z*a.z)
If you do this operation on a mediump vector, the term
a.x*a.x + a.y*a.y + a.z*a.z can exceed maximum value for vector as small as (74.0, 74.0, 74.0). It can be very dangerous if you do something like this in your fragment shader:
precision mediump float;
uniform vec3 light_pos;
vec3 dir_to_light = normalize(pos - light_pos);
// Calculate your lighting and so on...
You might ask: Why isn't this intermediate value stored in high precision before taking its square root to avoid this overflow problem? Obviously it could be, as precision in any place of the shader is free to be higher than the minimum allowed in that place, so some GPU vendors can do it this way, but you shouldn't rely on this. GLSL specification clearly says that the shader is free to use same, reduced precision for intermediate values.
The precision used to internally evaluate an operation, and the precision qualification subsequently associated with any resulting intermediate values, must be at least as high as the highest precision qualification of the operands consumed by the operation.
Conclusion is: When you write shaders for OpenGL ES, watch out for operations that involve calculating vector length (or dot product) like
highp precision for vectors involved and remember that what works on one GPU due to using precision higher than minimum required, may not work on another GPU and it’s still an application issue.
Floating-Point Formats Cheatsheet
Floating-point numbers (or floats in short) are not as simple as integer numbers. There is much to be understood when dealing with these numbers on low level - basic things like the sign + exponent + significand representation (and that exponent is biased, while significand has implicit leading 1), why you should never compare calculation results operator ==, that some fractions with finite decimal representation cannot be represented exactly in binary etc., as well as why there are two zeros -0 and +1, what are infinite, NaN (Not a Number) and denorm (denormal numbers) and how they behave. I won't describe it here. It's not an arcane knowledge - you can find many information about this on the Web, starting from Wikipedia article.
But after you understand these concepts, quantitative questions come to mind, like: how many significant decimal digits can we expect from precision of particular float representation (half, single, double)? What is the minimum non-zero value representable in that format? What range of integers can we represent exactly? What is the maximum value? And finally: if our game crashes with "Access violation, reading location 0x3f800000", what chances are that we mistaken pointer for a float number, as this is one of common values, meaning 1.0?
So to organize such knowledge, I created a "Floating-Point Formats" cheatsheet:
SBX - Scale-Bias Transform
A class of 2D or 3D point and vector transformations called affine transformations (that is - linear transformation plus translation) can be conveniently represented by matrix. But sometimes we don't need possibility of input x to influence output y or vice versa. For example, that's the case when we want to convert coordinates of mouse cursor from pixels, like we take it from WinAPI (where X goes right from 0 to e.g. 1280 and Y goes down from 0 to e.g. 720) to post-projective 3D space (where X goes right from -1.0 to +1.0 and Y goes up from -1.0 on the bottom to +1.0 on the top). Then every component can be transformed independently using linear transform, like this:
Parameters are: scale.xy, bias.xy.
Given input point i.xy, output point o.xy is:
o.x = i.x * scale.x + bias.x
o.y = i.y * scale.y + bias.y
This seems trivial, but what if we wanted to encapsulate such transformation in a new data structure and define operations on it, like we do on matrices? Let's call it SBX - Scale-Bias Transform. I propose following structure:
static const ScaleBiasXform IDENTITY;
And following functions:
void Construct(ScaleBiasXform& out,
const vec2& input1, const vec2& output1,
const vec2& input2, const vec2& output2);
void Scaling(ScaleBiasXform& out, float scale);
void Scaling(ScaleBiasXform& out, const vec2& scale);
void Translation(ScaleBiasXform& out, const vec2& vec);
void Inverse(ScaleBiasXform& out, const ScaleBiasXform& sbx);
void Combine(ScaleBiasXform& out, const ScaleBiasXform& sbx1, const ScaleBiasXform& sbx2);
void TransformPoint(vec2& out, const vec2& point, const ScaleBiasXform& sbx);
void UntransformPoint(vec2& out, const vec2& point, const ScaleBiasXform& sbx);
void TransformVector(vec2& out, const vec2& vec, const ScaleBiasXform& sbx);
void UntransformVector(vec2& out, const vec2& vec, const ScaleBiasXform& sbx);
Particle System - How to Store Particle Age?
Particle effects are nice because they are simple and look interesting. Besides, coding it is fun. So I code it again :) Particle systems can have state (when parameters of each particle are calculated based on previous values and time step) or stateless (when parameters are always recalculated from scratch using fixed function and current time). My current particle system has the state.
Today a question came to my mind about how to store age of a particle to delete it after some expiration time, determined by the emitter and also unique for each particle. First, let's think for a moment about the operations we need to perform on this data. We need to: 1. increment age by time step 2. check if particle expired and should be deleted.
If that was all, the solution would be simple. It would be enough to store just one number, let's call it TimeLeft. Assigned to the particle life duration at the beginning, it would be:
Step with dt: TimeLeft = TimeLeft - dt
Delete if: TimeLeft <= 0
But what if we additionally want to determine the progress of the particle lifetime, e.g. to interpolate its color of other parameters depending on it? The progress can be expressed in seconds (or whatever time unit we use) or in percent (0..1). My first idea was to simply store two numbers, expressed in seconds: Age and MaxAge. Age would be initialized to 0 and MaxAge to particle lifetime duration. Then:
Step with dt: Age = Age + dt
Delete if: Age > MaxAge
Percent progress: Age / MaxAge
Looks OK, but it involves costly division. So I came up with an idea of pre-dividing everything here by MaxAge, thus defining new parameters: AgeNorm = Age / MaxAge (which goes from 0 to 1 during particle lifetime) and AgeIncrement = 1 / MaxAge. Then it gives:
Step with dt: AgeNorm = AgeNorm + dt * AgeIncrement
Delete if: AgeNorm > 1
Progress: Age / AgeIncrement
Percent progress: AgeNorm
This needs additional multiplication during time step and division when we want to determine progress in seconds. But as I consider progress in percent more useful than the absolute progress value in seconds, that's my solution of choice for now.
DirectXMath - A First Look
Programmers using DirectX have access to the accompanying library full of routines that support various 3D math calculations. First it was D3DX - auxilliary library tightly coupled with DirectX itself. It's still there, but some time ago Microsoft released a new library called XNA Math. I've written about it here. This library also ships with latest versions of DirectX SDK (which was last updated in Jun 2010), but it is something completely new - a more independent, small, elegant and efficient library, portable between Windows (where it can use SSE2 or old FPU) and Xbox 360.
Now Microsoft comes up with another library called DirectXMath. See the official documentation and introductory blog post for details. As a successor of XNA Math, it looks very similar. Main differences are:
Sounds great? Well, I didn't tell yet how to get it. It's not shipped with DirectX SDK. You need the SDK for Windows 8. They say that:
The library is annotated using SAL2 (rather than the older VS-style or Windows-style annotations), so requires the standalone Windows SDK for Windows 8 Consumer Preview or some additional fixups to use with Visual C++ 2010.
So probably I'm not going to switch to this new library anytime soon :P
Checking collision between different kinds of 2D or 3D shapes is a subject I deal with for some time. It is useful in game development to determine if some object is visible or affected by light and this if it should be rendered. I have lots of function to check such collisions in the math module of CommonLib library.
But that is only the beginning if you want to make physics. Adding physical behavior to your game requires additional calculations to correct positions and apply forces to colliding bodies. I prepared a small code snippet that implements physical 2D collision between moving circle and another moving circle (calculated by CircleToCircleCollision function), static line (CircleToLineCollision function) and static axis-aligned rectangle (CircleToRectangleCollision function).
The code uses some good practices (like fixed time step) as well as bad practices (like Euler integration). It should be enough for a simple physics in game like a platformer.
Vector Register Size - Diagram
It may be hard to imagine and remember what is the exact number of bits, bytes, words or floats in some piece of data, like a SIMD register. So today I've made following diagram/cheatsheet:
Here you can find its "source" in OpenOffice Draw format: Vector_register_size.odg.