Thursday 30 May 2013

Thursday Rotate, Hide & Delete Em

Almost Deferred

I was all set to start some deferred rendering code when I realized that my editor still had a few missing ingredients. It could not control the limb visibility of the new instance stamp objects, it did not respect the rotation of the object either and you could not delete the object from the edited scene. Essential features you will agree.

Hide, Rotate and Delete

It took some time to ensure the base functionality was in place, and then it was a quick step to re-compile the editor, first making sure the limb visibility was set on the instance object before making it into an instance stamp, and then sitting back and watching it happen.

It was quite a buzz seeing the logic that once served a different object handling system work nicely with the new one, as internally it could not be more different to what we had before.

Hiding limbs and rotating objects, a key need within the editor, where to some degree anticipated and they presented no problems. Delete however was a minefield..

So much of a minefield that it's not entirely perfect (or finished) yet, as this process involves the pain of locating the mesh data within the buffer, deleting it and then shuffling the remaining valid mesh data (vertex and index data). There are also multiple instances in the editor that add and remove the objects so I need to catch each type and ensure the right data is produced.

Further, as I expand the capabilities of the instance stamp system further, my ancient reliance on the MAP() array inside the current FPSC code is also starting to appear quite redundant. No sense storing this reference data twice, once in DBP arrays and once in the DBP object engine. My thinking is to detect and remove all reliance on the DBP array side, effectively deleting all references to this map array so that when I choose to increase the level size, whatever that becomes, I don't need to worry about fixed size arrays in the actual DBP engine code. It's a big job though so going to tackle that when other more identifiable tasks have been put to bed.

Deferred Sample Selected

Many coders learn more from a simple cut and paste example than any amount of books and documentation on the subject, and I am one of them. I have now selected my uber simple demo and it looks something like this:


That's right, the old teapot makes a comeback once again.  This little fellow taught me DirectX in the early years :)  What this new demo does is produce a very simple four render target example of deferred rendering, and does so almost entirely within a single shader. I have copied the code below, so I hope the author does not mind (credited below), so you can see how simple it really is:

//-------------------------------------------------------------------------------
//           Name: deferred.fx
//         Author: Nikita Kindt (nk47@bk.ru)
//  Last Modified: 17/06/06
//-------------------------------------------------------------------------------

float4x3 c_mWorld;
float4x3 c_mView;
float4x3 c_mViewInverse;
float4x4 c_mProjection;
float4x4 c_mViewProjection;

// light parameters
float3 c_vLightDir;
float4 c_vLightPos;
float4 c_vLightDiffuse;
float4 c_vLightSpecular;

// material parameters
float4 c_vMaterialDiffuse;
float4 c_vMaterialSpecular;
float c_fSpecularPower;

// textures
texture2D c_tDiffuseMap;
texture2D c_tSceneMaterialMap;
texture2D c_tSceneNormalMap;
texture2D c_tScenePositionXYMap;
texture2D c_tScenePositionZMap;


sampler DiffuseSampler = sampler_state
{
Texture = <c_tDiffuseMap>;

AddressU = Wrap;
AddressV = Wrap;

MagFilter = Linear;
MinFilter = Linear;
MipFilter = Linear;
};

// rendertargets
sampler SceneMaterialSampler = sampler_state
{
Texture = <c_tSceneMaterialMap>;
MagFilter = Point;
MinFilter = Point;
};

sampler SceneNormalSampler = sampler_state
{
Texture = <c_tSceneNormalMap>;
MagFilter = Point;
MinFilter = Point;
};

sampler ScenePositionXYSampler = sampler_state
{
Texture = <c_tScenePositionXYMap>;
MagFilter = Point;
MinFilter = Point;
};

sampler ScenePositionZSampler = sampler_state
{
Texture = <c_tScenePositionZMap>;
MagFilter = Point;
MinFilter = Point;
};



struct VS_INPUT_BUILD
{
    float3  vPos            : POSITION0;
    float2  vTex0           : TEXCOORD0;
    float3  vNrm            : NORMAL0;
    float3  vTan            : TANGENT0;
    float3  vBin            : BINORMAL0;
};

struct VS_OUTPUT_BUILD
{
float4 vPos : POSITION0;
    float2  vTex0           : TEXCOORD0;
float3 vWorldPos : TEXCOORD1;
float3 vWorldNrm : TEXCOORD2;
};

struct PS_OUTPUT_BUILD
{
float4 vMaterial : COLOR0;
float4 vWorldNrm : COLOR1;
float4 vWorldPosXY : COLOR2;
float4 vWorldPosZ : COLOR3;
};

VS_OUTPUT_BUILD vsBuild(VS_INPUT_BUILD i)
{
VS_OUTPUT_BUILD o;

o.vWorldPos = mul(float4(i.vPos, 1), c_mWorld);
o.vPos = mul(float4(o.vWorldPos, 1), c_mViewProjection);
o.vTex0 = i.vTex0;
o.vWorldNrm = normalize(mul(float4(i.vNrm, 0), c_mWorld));

return o;
};

// psBuild()
// put geometry data into render targets
PS_OUTPUT_BUILD psBuild(VS_OUTPUT_BUILD i) : COLOR0
{
PS_OUTPUT_BUILD o;

// material
float4 vDiffuseMaterial = tex2D(DiffuseSampler, i.vTex0);

o.vMaterial.rgb = vDiffuseMaterial;
o.vMaterial.a = 1.0;

// convert normal to texture space [-1;+1] -> [0;1]
o.vWorldNrm.xyz = i.vWorldNrm * 0.5 + 0.5;
o.vWorldNrm.w = 0.0;

// position
o.vWorldPosXY = float4(i.vWorldPos.xy, 0, 0);
o.vWorldPosZ = float4(i.vWorldPos.z, 0, 0, 0);

return o;
};



struct PS_INPUT_LIGHT
{
float2 vTex0 : TEXCOORD0;
};

// psLighting()
// uses data from textures (previous render targets)
float4 psLighting(PS_INPUT_LIGHT i) : COLOR0
{
float3 vDiffuseMaterial = tex2D(SceneMaterialSampler, i.vTex0).rgb;
float3 vSpecularMaterial = tex2D(SceneMaterialSampler, i.vTex0).a;

// normals are stored in texture space [0,1] -> convert them back to [-1,+1] range
float3 vWorldNrm = (tex2D(SceneNormalSampler, i.vTex0) - 0.5) * 2;

float3 vWorldPos;
vWorldPos.xy = tex2D(ScenePositionXYSampler, i.vTex0).xy;
vWorldPos.z = tex2D(ScenePositionZSampler, i.vTex0).x;
float3 vLightDir = normalize(c_vLightPos - vWorldPos);
float3 vEyeVec = normalize(c_mViewInverse[3].xyz - vWorldPos);
float3 vDiffuseIntensity = dot(vLightDir, vWorldNrm);
float3 vSpecularIntensity = pow(max(0, dot(vEyeVec, reflect(-vLightDir, vWorldNrm))), c_fSpecularPower);

float4 color;
color.rgb = vDiffuseIntensity * c_vLightDiffuse.xyz * vDiffuseMaterial + 
vSpecularIntensity * c_vLightSpecular.xyz * vSpecularMaterial;
color.a = 1.0;

// here we add color to show how lighting pass affects the scene
color.rgb += i.vTex0.rgr * 0.5;

return color;
};


technique buildPass
{
pass p0
{
VertexShader = compile vs_1_1 vsBuild();
PixelShader = compile ps_2_0 psBuild();

CullMode = ccw;
FillMode = solid;
Zenable = true;
ZWriteEnable = true;
ZFunc = less;
StencilEnable = false;
AlphaBlendEnable = false;
AlphaTestEnable = false;
ColorWriteEnable = red | green | blue;
}
}

technique lightPass
{
    pass p0
{
VertexShader = NULL;
PixelShader = compile ps_2_0 psLighting();

CullMode = none;
FillMode = solid;
Zenable = false;
StencilEnable = false;
AlphaBlendEnable = true;
Srcblend = One;
Destblend = One;
AlphaTestEnable = false;
ColorWriteEnable = red | green | blue;
}

};

Well, maybe if you are not 100% schooled on reading shader code, it might seem a little daunting, but for anyone who has written a shader, you will start to ask what all the fuss was about.

I am not keen on using FOUR render targets, and the above example is really just a skeleton for what the final shader will end up looking like, but I really like the fact we have a VERY simple template to start from. It is much better to start from a simple shader that works to a complex shader that does not work, or worse an empty shader you have to write from scratch. If anyone knows of a good shader authoring IDE that detects compile errors in real-time and works with DirectX 9.0c shader code (i.e. VS1/PS1/PS2/PS3), send me a few links!

Signing Off

Hopefully going to get an early night today so I can be up bright and early for my drive to Crewe and train down to London. Friday I meet the secret FPSC investor and the guy you will all owe a beer to when the Reloaded project is complete. When the Kickstarter campaign failed, this amazing fellow swooped in and backed the whole project personally. The least I can do is buy the fellow a meal in our countries capital (if he lets me pay). I will be staying in London until Saturday so there will be no blog tomorrow, but I will provide a special report on my secret meeting over the weekend (or Monday), but given it's secret there will be precious little facts divulged. I will see if I can take some photos to lighten the blog and provide some relief from the sea of code I am drowning you in.

4 comments:

  1. Wow, that really does boil down to a simple shader! Is there an opportunity to have different material types by passing different variables in, or is everything required to use the same render path? Or what about things like guns that can also use cubemaps, how would those fit into the pipeline? I have the entire series of ShaderX books, and there are tons of articles on deferred rendering, which up to this point I've ignored. Maybe it's time to have a look at them!

    ReplyDelete
  2. Great question and answered twice below. Firstly I am going to splat a forward renderer over the top of the deferred rendering, so we can have things like transparent glass and alpha effects, and by extension we can have our weapon HUD rendered in the usual way with existing shaders. Second, once I have a basic deferred render sequence running, I will be experimenting with those four render targets to see how better to store the original build screen-space as four seems overkill. The general rule however is that deferred uses a single shader to render the entire scene, but there is no reason why we cannot render the scene with multiple final shaders as we're only really decoupling the lighting calculation from the final render. I also like LRmod dynamic shadows as a way to solve internal shadows, so we can look at cube map based dynamic lighting when the time comes. I dread to think what kind of card we will need when this is all over :)

    ReplyDelete
  3. Well LRMods handling of shadows was rather bad (I know it, because it was me writing it :D).
    What I did on the coding side, was taking the whole universe twice, one for the lighting shader stuff and one for the different shaders, than combine both images (Lighting + normal FPSC Diffuse) and render that image to the screen. This made the performance quite bad, as the whole universe was rendered 2 times.

    In an unreleased prototype I started to do everything based on 1 unvierse object (which is a pain with the static universe stuff in current FPSC code) and let the shader combine everything (even World Normals and stuff). This brought the performance a bit to the upper level, but I than got some problems with the gun object, if I remember correctly. Everytime a gun was inside, the performance hit was huge, because of the disabled z order.

    But if you want to know more about LRMods idea of how to render things, feel free to ask.

    ReplyDelete
  4. Just remember, Lee, immersiveness is entirely lost if the player's hands and held weapons are bright lit when standing in a pitch-black room. Whatever you do, DO NOT light the HUDGUN the way you lit every dynamic crate in FPSCx9. It looks like CRAP. The HUDGUN (and hands) MUST be pixel-light-lit AND dynamic-shadow-shadowed.

    Also, remember that whatever happens, if it starts to run below 120fps on your PC, Lee, it won't run at all on any normal person's PC. Even if it takes you another month, do whatever is necessary to make FPSC-R run FAST.

    ReplyDelete