Register
Starting from scratch Print E-mail
User Rating: / 0
PoorBest 
Written by Stuart   
Thursday, 22 April 2010 13:18

I've got a pretty tight schedule, and I want to spend most of my time tuning. So the general plan is to keep everything "live", and editable while the game is running (shaders, scripts, textures, models, music, etc). All the "hard" work will be done in shaders, including as much of the simulation as possible. All the game code, and most of the engine code, will be written in script (Lua). I'm going to be GPU-bound, so the script overhead shouldn't be a problem.

So on the C++ side I've got a generic object/factory/lifetime system, and a reflection interface (hacked together with embarrassing macros) for all the system objects. There are only about a dozen of them though, and I've gotten to the point where I don't have to rebuild the code very often because all my changes are in scripts or shaders. The reflection interface makes it easy to interact with script, though it's slower than code-gen.

So here's an example of creating a mesh from the script side. This is the triangle used to do blits between textures (i.e. "full screen passes", even though they're all offscreen). This poor triangle works very hard.

    1 function CreateBlitMesh()

    2 

    3     local vb =

    4     {

    5     --  POS            NORM          UV

    6         -130,    00, -1,    0, -1,

    7          3, -10,    00, -1,    21,

    8         -1, -10,    00, -1,    01,

    9     }

   10 

   11     local ib = { 0, 1, 2 }

   12 

   13     local vbobj = Factory:Create( "VertexBuffer" )

   14     vbobj:SetData( vb, 8 )

   15 

   16     local ibobj = Factory:Create( "IndexBuffer" )

   17     ibobj:SetData( ib )

   18 

   19     local mesh = Factory:Create( "Mesh" )

   20     mesh:BindVertexStream( vbobj, "POSITION"0, 3 )

   21     mesh:BindVertexStream( vbobj, "NORMAL",    3, 3 )

   22     mesh:BindVertexStream( vbobj, "TEXCOORD0", 6, 2 )

   23     mesh:BindIndexBuffer(  ibobj )

   24 

   25     return mesh

   26 

   27 end

Textures are passed around as Lua tables, and allocated through a simple cache (which is also implemented in Lua).

So this is the fun part, because I've never been able to do this before. While the game is running, you can create a new shader, like this one to run a Sobel filter:

    1 SAMPLER_DECLARE( Source ) { SAMPLER_ADDRESS_CLAMP };

    2 

    3 extern float    InvWidth;

    4 extern float    InvHeight;

    5 extern float    Gain;

    6 extern float    Power;

    7 extern float4   Coeff;

    8 

    9 PS_BLIT_OUTPUT_1 PS( PS_BLIT_INPUT input )

   10 {

   11     float2 center = input.mUV;

   12 

   13     float4 s1 = TEX2D( Source, center + float2( -InvWidth, -InvHeight ) );

   14     float4 s2 = TEX2D( Source, center + float2(         0, -InvHeight ) );

   15     float4 s3 = TEX2D( Source, center + float2(  InvWidth, -InvHeight ) );

   16     float4 s4 = TEX2D( Source, center + float2( -InvWidth,          0 ) );

   17     float4 s6 = TEX2D( Source, center + float2(  InvWidth,          0 ) );

   18     float4 s7 = TEX2D( Source, center + float2( -InvWidth,  InvHeight ) );

   19     float4 s8 = TEX2D( Source, center + float2(         0,  InvHeight ) );

   20     float4 s9 = TEX2D( Source, center + float2(  InvWidth,  InvHeight ) );

   21 

   22     float4 gradVert = (s1 + s2 + s2 + s3 - s7 - s8 - s8 - s9);

   23     float4 gradHorz = (s1 + s4 + s4 + s7 - s3 - s6 - s6 - s9);

   24     float4 mag      = sqrt( gradVert * gradVert + gradHorz * gradHorz );

   25     float  val      = pow( dot( mag, Coeff ) * Gain, Power );

   26     float4 result   = float4( val, val, val, 0 );

   27 

   28     RETURN_PS_BLIT_OUTPUT_1( result );

   29 }

To call that shader from the script looks something like this:

    1 function DoSobel( dest, source, coeff, gain, power )

    2 

    3     local shaderSobel = ShaderCache:Get( "Sobel" )

    4 

    5     shaderSobel.Val.InvWidth  = 1.0 / source.Width

    6     shaderSobel.Val.InvHeight = 1.0 / source.Height

    7     shaderSobel.Val.Gain      = gain

    8     shaderSobel.Val.Power     = power

    9     shaderSobel.Vec.Coeff     = coeff

   10     shaderSobel.Tex.SourceTex = source

   11 

   12     Blit( { dest }, shaderSobel )

   13 

   14 end

You fill out the shader parameters by populating a table, and then call Blit(). The first parameter to Blit() is a list of the target textures. Blit() detects if any source textures are also targets, and fixes it by allocating/shuffling buffers as needed.

Now you can start putting things together to build more interesting effects:

    1 function DoWatercolor( dest, source )

    2 

    3     local edgeTex = TextureCache:AllocLike( source )

    4 

    5     DoSoften( dest, source )

    6     DoSobel( edgeTex, dest, { 0.30, 0.59, 0.11, 0 }, 1, 3 )

    7     DoSharpen( edgeTex, edgeTex )

    8     DoApplyInverseMask( dest, dest, edgeTex )

    9 

   10     TextureCache:Free( edgeTex )

   11 

   12 end

Watercolor example

Ok, that might not be the greatest watercolor, but you can create the effect and tune it while the game is running, and see changes in realtime. I've found it to be a really fun way to work, because you can do looping and arbitrary scripted logic, instead of just changing shader code. And for creating procedural content, this is easier (for a programmer) than using a tool like Allegorithmic Substance, because I can type a line of script faster than I could connect all those little boxes together.

So, that's what I'm working on. I'll share more in the coming days, but it has taking me a surprisingly long amount of time to write this entry, so I've got to figure out how to blog better!

 

Add comment

Security code
Refresh

Licensed developer

Licensed PS3 developer

Subscribe

Follow us on Twitter!

S5 Box

Register

*
*
*
*
*

Fields marked with an asterisk (*) are required.

Copyright © 2011 Pure Energy Games, Inc. All rights reserved.