Search this blog

02 November, 2010

Stereoscopic test

Yesterday I did a small test with stereo rendering, trying to reproject the image from one eye to the other. It's very crude but I wanted to show the "theory" or the basic idea of the procedure as I got a few people asking me about it.

Stereo rendering (standard red/cyan glasses needed)

Of course the most interesting thing is how to correctly inpaint the holes in your visibility. This is a very general problem that also applies to other post effects like motion blur and depth of field blur, the solution I use in this test well... I won't even call it a solution. 

Much better methods have to be used for this to work decently, but that will (might...) be the topic of another post!

Reconstructed view
Rendered view




 float Script : STANDARDSGLOBAL <
    string UIWidget = "none";
    string ScriptClass = "scene";
    string ScriptOrder = "standard";
    string ScriptOutput = "color";
    string Script = "Technique=Main;";
> = 0.8; // version #

// -- Untweakables

float4x4 WorldViewProj : WorldViewProjection < string UIWidget="None"; >;
float4x4 WorldView : WorldView < string UIWidget="None"; >;
float4x4 Proj : Projection ;

float2 BufferSize : ViewportPixelSize < string UIWidget="None"; >;

float4 ClearParam < string UIWidget="None"; > = {0,0,0,0};

// -- Buffers and samplers

texture Bake : RENDERCOLORTARGET
<
    float2 ViewPortRatio = {1,1};
    string Format = "A8B8G8R8";
    string UIWidget = "None";
>;
sampler2D BakeSampler = sampler_state
{
    texture = ;
    MagFilter = Point;
    MinFilter = Point;
    AddressU = Clamp;
    AddressV = Clamp;
    MipFilter = Point;
};

// -- Data structures

struct GeomVS_Out
{
    float4 Pos : POSITION;
    float4 PosCopy : TEXCOORD0;
    float2 UV : TEXCOORD2;
};

struct FSQuadVS_InOut // fullscreen quad
{
    float4 Pos : POSITION;
    float2 UV : TEXCOORD0;
};

// -- Shaders

GeomVS_Out GeomVS(float4 pos : POSITION, float2 uv : TEXCOORD0)
{
    GeomVS_Out Out;

    Out.Pos = mul( pos, WorldViewProj );
    Out.PosCopy = Out.Pos;
    Out.UV = uv;
   
    return Out;
}

FSQuadVS_InOut FSQuadVS(FSQuadVS_InOut In)
{   
    return In;
}

// FX Composer does not pass near and far planes via a semantic, we have to "extract" them
float2 GetPlanesFromDxProjection()
{
    float near = -Proj[3][2] / Proj[2][2];
    float far = -Proj[3][2] / (Proj[2][2] - 1.0);
   
    return float2(near, far);
}

float4 EncUnitRangeToRGBA8( float v )
{
    float4 enc = float4(1.0, 255.0, 65025.0, 160581375.0) * v;
   
    enc = frac(enc);
    enc -= enc.yzww * float4(1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0);
   
    return enc;
}

float DecUnitRangeToRGBA8( float4 rgba )
{
    return dot( rgba, float4(1.0, 1/255.0, 1/65025.0, 1/160581375.0) );
}

float4 BakePS(GeomVS_Out In) : COLOR
{
    // 0 to 1 from near to far
    float linearDepth = In.PosCopy.z / GetPlanesFromDxProjection().y;
   
    float testColor = dot(frac(In.UV.xy * 10) ,0.5.xx) + 0.25;
   
    return float4(EncUnitRangeToRGBA8(linearDepth).xyz, testColor);
}

float4 DecodeBake(float2 uv)
{
    float4 sample = tex2D(BakeSampler, uv);
    float linearZ = DecUnitRangeToRGBA8(float4(sample.xyz,0));

    // back to viewspace...
    float2 nearFar = GetPlanesFromDxProjection();
    linearZ = linearZ * (nearFar.y-nearFar.x) + nearFar.x;

#define VIEWTOUVMULT (float2(Proj[0][0], -Proj[1][1]) * 0.5)
#define VIEWTOUVADD float2(0.5, 0.5)   
    float2 xy = ((uv - VIEWTOUVADD) * linearZ)/VIEWTOUVMULT;
   
    return float4(xy, linearZ, sample.w); // view xyz, pattern
}

float4 CopyPS(FSQuadVS_InOut In) : COLOR
{
    return float4(DecodeBake(In.UV).zzz / 5, 1);
    return float4(DecodeBake(In.UV).www, 1);
}

float3 ReprojectView(float3 viewPos)
{
    // Here we should go from the view position of the baked eye to the one of the other
    // it can be pretty generic but it assumes the difference will be only along the
    // screen X axis...
   
    return viewPos + float3(0.2,0,0);
}

float4 ReprojectPS(FSQuadVS_InOut In) : COLOR
{
    float reprojDepth = 9999;
    float reprojColor = 0;
   
    float nearestColor = 0;
    float nearestDistance = 9999;
   
    float4 decodedCenter = DecodeBake(In.UV); // xyz = view, w = test pattern
   
    float4 projectedCenter = mul(float4(decodedCenter.xyz,1), Proj);
    projectedCenter /= projectedCenter.w;
   
    for(float i=-63; i<64; i++) // note: brute force = bad, just a test
    {
        float2 sampleUV = In.UV + float2(i / BufferSize.x, 0);
       
        // we assume in this test that the "color" is baked in w
        // also, we assume that the scene fills the entire screen,
        // we draw something everywhere...
        float4 decoded = DecodeBake(sampleUV);   
       
        float3 newView = ReprojectView(decoded.xyz);
       
        float4 projectedNewView = mul(float4(newView,1), Proj);
        projectedNewView /= projectedNewView.w;
       
        float rowDist = abs(projectedNewView.x - projectedCenter.x);
       
        // Check if the new sample covers our pixel
        // note: al these tests can be made "fuzzy" by constructing
        // a weighting function instead
        if(rowDist < (2.0 / BufferSize.x))
        {
            if(reprojDepth>newView.z)
            {
                reprojColor = decoded.w;
                reprojDepth = newView.z;
            }
        }
       
        // We'll use this to fill the holes if any
        // note: this is a very crude inpaiting method, something
        // way better should be used for the method to work decently
        float heuristicDistance = rowDist*rowDist / newView.z;
        if(heuristicDistance < nearestDistance)
        {
            nearestColor = decoded.w;
            nearestDistance = heuristicDistance;
        }
    }
   
    // If we didn't find anything, we pull something from thin air...
    if(reprojColor==0) reprojColor = nearestColor;

    //return float4(decodedCenter.www,1);
    //return float4(reprojColor.xxx,1);
    return float4(reprojColor,decodedCenter.ww,1);
}

// -- Techniques

technique Main
<
    string Script =
        "Pass=Bake;"
        "Pass=FullScreen;"
    ;
>
{
    pass Bake
    <
        string Script =
            "RenderColorTarget0=Bake;"
            "ClearSetColor=ClearParam;"
            "Clear=Color;"
            "Clear=Depth;"
            "Draw=Geometry;";
    >
    {
        ZEnable = true;
        ZWriteEnable = true;
       
        VertexShader = compile vs_3_0 GeomVS();
        PixelShader = compile ps_3_0 BakePS();
    }
   
    pass FullScreen
    <
        string Script =
            "RenderColorTarget0=;"
            "Draw=Buffer;";
    >
    {
        ZEnable = false;
        ZWriteEnable = false;
   
        VertexShader = compile vs_3_0 FSQuadVS();
        PixelShader = compile ps_3_0 ReprojectPS(); //CopyPS();
    }
};

20 October, 2010

EASTL

I'm way too busy with my game and I'm going through a lot of changes in my life and work, so my "todo" list for the blog keeps growing... but this is great and does deserve a quick post. EA seemingly released its version of STL. You can find it here:

Some more documentation here:

12 September, 2010

Platform specific features - poll results

Thanks for voting!

After 70 votes, these are the results. I was surprised a bit, before starting it I was more pessimistic about the average use of such things, but truth to be told I don't think the audience of this blog is totally unbiased too...

I don't think they require much comment, the relatively high numbers in 360 - other and ps3 - other probably say that the entries could have been better, I didn't receive any comments so if you did choose "other" and you want to specify what that meant, feel free to leave a comment on this post.

So here they are:

360 - tassellator - 11%
360 - shader memwrite - 12%
360 - v.shader texture fetch - 30%
360 - l2 cache -> GPU - 5%
360 - custom vfetch - 28%
360 - command buffer predication (other than MSAA) - 20%
360 - other - 21%
ps3 - texture as vertex stream fetch - 2%
ps3 - spu to spu cache communication - 8%
ps3 - spu rendering (i.e. depth/hi-z) - 10%
ps3 - halfs in shaders - 41%
ps3 - main/local memory bandwidth optimization - 27%
ps3 - spu to gpu fast path - 30%
ps3 - other - 24%
dx10 - api - 10%
dx10 - geometry shaders - 10%
dx10 - model 4 shaders - 15%
dx10.1 - features - 11%
dx10 - other - 4%
dx11 - api - 15%
dx11 - tassellator - 12%
dx11 - multithreading - 7%
dx11 - model 5 shaders - 11%
dx11 - other - 10%

my engine is console only - 21%
none - 24%

01 September, 2010

International movement for code rights

Code is data. Data is code. Everyone knows that, and not only in homoiconic languages, it's a general truth. Code, data, languages, frameworks, APIs, they are all the same. So why is it that code and engineers do not have the same rights that data and "creatives" have? Code is an art too!

We will fight until all our infrastructures and processes will give equal rights to code and data. In particular we want:

- Ease of change. Why data has little to no dependencies? Why it's easy to change a texture, or to replace a placeholder model with a ten times more refined one, while with code we can't? Why do we accept the existence of things like "code rot" or the "prototry pattern"? It's easy to throw data away or change it, it has to be easy to do the same with code.

- Ease of scale. Why have companies invested and continue to invest in data scaling, especially with every new generation, while the problem of code scaling is not considered as important? Why is it now easy to have games and projects that handle gigabytes of built data and terabytes of raw data while our compilers, linkers and code projects struggle under the size of our executables?

- Ease of iteration. Every good company cares about data iteration. We create tools for live editing of data values, models, textures, and parameters. We care about hot-reloading, streaming and direct "live" link between tools, content creation applications and our games or project.Why the same efforts are not spent on code iteration? Why we don't have live-coding? Why we have to use "scripting languages"? Does an artist have to choose between live editing in paint and waiting half an hour if he want to change the same image in photoshop? Why we restrict our fast iteration paths to "scripts"? All code is equal! Equal rights!

proposal: Frameworks and languages should be the infrastructure for code creation. They should be slim, they should care about coding ergonomics. Their role is to made coding easy and fast. They should care about code hot-swapping and live-editing. Fuck OOP. Fuck re-usability. We need an infrastructure for creative coding. A "character" does not "inherit" from a "mesh" that inherits from a "3dobject". A character is an object with some given code behaviors; as its visual representation is made of unique meshes and materials and textures, its code will be made of loading and updating and rendering functions.

- Tools. We want better tools. Tools to check code statically and at run-time. We want contracts and "schemas" for code. Why can't we easily model the constraints of a given subsystem, and make sure that coders of that subsystems can't emit values that will crash it, while many companies do have tools to check the correctness of a given art asset? Why it's easier to outsource art than code? Why I can click on a model in game and have the list of textures it's using, their names, how they look like, and I can't get a list of the code that is used to drive it, to animate it, to render it? Why I can't see into that code and see what it's executed and how. Why do I need a "debugger" to stop its execution and dissect it? We want the same procedures!

proposal: Interfaces or modules or function contracts will define inputs, outputs and persistent state of blocks of code. Types and inference will statically enforce constraints and correctness. Automated tests and code instrumentation will be used to test code. Run-time pre- and post-conditions and fallback defaults will handle run-time problems without crashing. Reflection, introspection and run-time visualization will be used to understand code while running. We will be able to see what code is hooked up to a given object, what are its modules and visualize their inputs, outputs, state and run-time errors.

- Equal dignity. Data is beautiful. Data is unique, it's made by "creatives" it's art. Why code can't be? Why it's perfectly logical to create ten unique characters for a game, but not ten unique algorithms to drive them? The creation of a main character can take weeks of planning and weeks of execution, a coder could craft its custom logic in the same time especially if he had the proper tools and ease of iteration (see the comments above). Why most of our code is "framework" or "infrastructure"? Does artists devote most of their time in building infrastructures? In building reusable component libraries? We want to have as much unique code as we have unique content! We don't want to reuse components! We want light frameworks!

Please, fight with us. Copy this article, mail it and spread the word. Talk with your technical directors, with your leads; help us change the world. We are tired of games that "stream" data, but where the game itself and its logic is always the same. We have unique levels and scenarios; we want to have unique driving logics and rendering techniques and more.

p.s. I've recently finished the single player campaign in Modern Warfare 2. It's probably the best game on the next-gen I've played so far (surely among the top 5) and the key to its beauty to me is that it's not recycling the same mechanics over and over. There is an almost equal density of content and mechanics, and frankly it's amazingly refreshing. I don't know if that was achieved with more unique code, with scripting or with a "data driven" tools but I do know that I want more custom, ad-hoc scenarios, and that we should be considered artists, we should create GAMES, not frameworks or tools but GAMES.

13 August, 2010

Know your Z

What will the following two commented functions do? Will they break things? Could they be useful?

float4 VS(float3 pos: POSITION) : POSITION
{
float4 hPos = mul(worldViewProj,float4(pos, 1.0));
   
// OPTION A:
//hPos /= hPos.w;
// OPTION B: 
//hPos.z *= hPos.w / farPlaneViewZ;

return hPos;
}

Answer: They will screw with your perspective interpolation in different ways. 

Option A just gives the non-homogeneous space coordinates to the pipeline. It will effectively disable perspective interpolation on your texcoords, and I can't find much use for such a thing :) 

Option B is more tricky. If you do the math, you'll find out that it will force the Z-buffer to contain a linear depth value, as the Z-buffer stores z/w, and z ranges from 0 to far after the projection, so by multiplying by w and dividing by far, we get the resulting z/w to be z/far. 
Interestingly, it won't screw with texture coordinates as it does not touch w, so it could be really useful. 

There is a problem though, that at first I overlooked untill I received a detailed mail from one of my coworkers explaining my naivete. Now z is not linear anymore when interpolated in screenspace. and that could cause all sorts of artifacts where the z on your triangles does not lie on a plane anymore... But in some cases can indeed work fine, if your objects are tassellated enough and you don't get parallel walls close to each other... Shadowmaps for your characters for example...

Update: some interesting documents that I've found around...

This is basically the same idea as the trick "B" but going into yet another space. The article fails to account for the errors the system introduces, but in the comments you can read basically the same conclusions that I wrote here:

This is an analysis that suggest to invert the z with floating point buffers, that is a very common practice. It does make sense in terms of precision but it's really required by hi-z, because of the way it works.

And last but not least, this article shows the effects of triangle orientation on the z-fighting: http://research.microsoft.com/apps/pubs/default.aspx?id=79213