Showing posts with label ogre3d. Show all posts
Showing posts with label ogre3d. Show all posts

Tuesday, April 1, 2014

3D Puzzle Game "PZL" Released!

Back in, oh, last year I threw together a game for Ludum Dare 26 called "Puzzle Cube" that was built entirely on Ogre3D.   Shortly after, I ported that game to iOS and showed it to a friend who really encouraged me to develop it into a full-on mobile game with levels, scores, and everything. Since then I've made 30 code commits and added a bunch of features and levels and released it on the iTunes Store  under the official name PZL.  Dayum.
"IT'S LIKE SOME KINDA FRIKIN MAZE"
Well actually, in PZL you control a glowing blue orb that starts at the base of the puzzle tower.  Each level of the tower can be rotated so you can move the blue orb up till you reach the end of the level - and eventually the top of the tower.  Along the way you'll discover tunnels through the puzzle, prizes, and monsters to evade.



The Journey.


So how did a game that started out looking like this:

Ludum Dare 26 - PuzzleCube
Get to this?



First of all, porting to an iOS mobile device when you start out with a Windows build isn't a walk in the park.  In Windows your render loop is probably some endless while loop or frame listener.  On iOS you have an Objective C app that sets up a CADisplayLink callback on a frame interval.  The callback then manually instructs Ogre to renderOneFrame.  Then there's the matter of getting input from the keyboard, touch input, outputting audio, not to mention any kind of networking or saving to disk you might need - ouch!

Let's talk bout some of those -

Ogre3D


I used the OgreDemoApp/OgreFramework for iOS as a base so I had to refactor my initialization code so it worked with the OgreFramework.  I had to pass down some things like the scene manager, camera and window handle to my main game class.  I also decided to make my Game class a singleton mainly referenced through the OgreFramework so I could easily pass down frame render events and input events from OIS, but I probably could have architected it all differently and merged my main game class with the OgreDemoApp class.   The downside of merging would have been that I would have a lot more code in that merged class that wouldn't get used on say the Windows version.

Also, I had to remove all the OgreBites tray code and the majority of the camera manipulation so my game could control it instead.

Audio


Things got a bit hairy when I needed to expose some things to C++ code that were only available in Objective C, namely audio, keyboard input and session data (stored in NSUserDefaults).

I chose to use the SimpleAudioEngine for audio which comes from CocosDenshion (Cocos2D also uses this).  It's a really simple audio player that easily handles background and event audio.  So how do you access an Objective C class from inside game code which is written in C++?

What I did was create a GameAudio Objective C class:

The implementation file for GameAudio (named GameAudio.mm) has my functions that call the SimpleAudioEngine class like so:

To call those GameAudio functions from C++ I provide another header file called GameAudioInterface.h with headers for the functions in GameAudio.mm


Keyboard Input


WARNING HAX.  I created a GameKeyboard Objective C class with an invisible UITextField added to the main UIWindow.


Inside the implementation file I provided functions to show/hide the keyboard and called functions in my main game code when a key was pressed.  Because the game code used OIS I had to map every character to the OIS equivalent. YUCK.


Lastly, I exposed the keyboard show/hide functions in a header included in the C++ game code called


Hopefully that code saves somebody some time.


Other  Objective-C Stuff n' Thangs


OK besides audio and keyboard libraries I used NSUserDefaults to save some basic settings and the excellent Scoreoid API for saving player scores.  For those who are unaware, Scoreoid lets you save scores for free using their platform agnostic API so this means if I port the game to Android I can access the same score data in the future.

Lastly, I implemented the Chartboost SDK to serve ads.

Technology Used


Graphics Engine: Ogre3D
GUI: Gorilla
Input: OIS
Audio: SimpleAudioEngine (CocosDenshion)
Scores: Scoreoid
Animation: CppTweener
Network: AFNetworking


That pretty much about sums things up as far as the development goes.  I do plan on adding some more gameplay elements in the future if the game has enough of an audience to warrant updates.

You can download PZL via the iTunes App Store on your mobile device or click the link here:
https://itunes.apple.com/us/app/pzl/id812626888

Thanks and enjoy!

Wednesday, July 6, 2011

Let there be Bogeys

bogeys
(click to enlarge)

It hasn't been very productive these last few months. Got the usual family stuff that is, obviously, more important than this - but what I have done is experiment more with MyGUI and added bogey trackers to some mine objects.

The bogey trackers are actually 2D GUIe widgets and each corner of a box surrounding a mine is it's own widget. The performance ain't hot let me say that much. I'd like to also try 3D bogey trackers and actually put billboards in 3d space around the targets, but I kinda doubt that will be any faster.

I've also tried to smooth the input and framerate a bit more. In the process I implemented a mouse look system for the ships that is similar to the one in X3.

In the process I broke my multiplayer input so now client ships can't move, and there is still a bit of jitter when moving fast, ugh. I plan on attempting to fix the network bug and then do some renovations to the lasers & thrusters. After that it's some basic menus - joy! One of those menus should be a menu to jump to another planet or solar system, so that will open a whole new can 'o worms.

Here's a little vid I recorded - just shows the bogey tracker.

Wednesday, April 28, 2010

Atmosphere Shader Update And Tree/Grass Test



I've gone and made the atmosphere shader more complex, sorry. The basic idea is still pretty straight forward, but I added mie scattering to the pixel shader and added extra code to fix issues with the atmosphere being too transparent when near the surface of the planet (stars were showing through).

Also, I worked on pre-calculated/static shadow maps for the terrain and used those with the grass and trees. JohnJ created Paged Geometry, which is built for 2d heightmaps, but I semi-adapted it so that each face of the planet cube has its' own paged geometry instance. Here's a video of that:


Note: the atmosphere shader in this video is the old atmosphere shader so the atmosphere is still too transparent on the sunny side of the planet

It has issues like, the billboard trees don't display right when viewed top down and they don't fade out based on height like they should. Also there is an abrupt transition from one paged geometry instance to the next when you cross over from one cube planet face to another. I may just have to roll my own simplified version.

And now, here is the NVIDIA FX Composer shader for the current atmosphere shader I'm using:


// outer atmosphere radius
float AtmosphereRadius <
string UIName = "Atmosphere Radius";
string UIWidget = "Slider";
float UIMin = 0.0;
float UIMax = 10000.0;
float UIStep = 1.0;
> = {1200.0f};

// planet surface radius
float SurfaceRadius <
string UIName = "Surface Radius";
string UIWidget = "Slider";
float UIMin = 0.0;
float UIMax = 10000.0;
float UIStep = 1.0;
> = {1024.0f};

// this is the sun position/direction
float4 gLamp0DirPos : POSITION < // or direction, if W==0
string Object = "Light0";
string UIName = "Lamp 0 Position/Direction";
string Space = (LIGHT_COORDS);
> = {10.0f,10.0f,10.0f,1.0};

// this is the atmosphere 2d gradient
texture gTex <
string ResourceName = "AtmosphereGradient";
string ResourceType = "2D";
string UIName = "Gradient Texture";
>;
sampler2D gTexSampler = sampler_state
{
Texture = ;
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Clamp;
AddressV = Clamp;
};

// this is for setting where the horizon should fall on the sphere
float StretchAmt <
string UIName = "Stretch Amount";
string UIWidget = "Slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.01;
> = {0.25f};

// this is for mie scattering
float Atmosphere_G <
string UIName = "Atmosphere G";
string UIWidget = "Slider";
float UIMin = -1.0;
float UIMax = -0.5;
float UIStep = 0.001;
> = {-0.95f};

float4x4 WorldViewProj : WorldViewProjection;
float4x4 ViewIXf : ViewInverse;
float4x4 WorldXf : World;


void main2VS(
float3 pos : POSITION,
uniform float4 lightPos,

out float4 oPosition: POSITION,
out float2 oUV: TEXCOORD0,
out float oAlpha: TEXCOORD1,
out float3 oCamToPos: TEXCOORD2,
out float3 oLightDir :TEXCOORD3
)
{
float4 Po = float4(pos.xyz,1);
float4 Pw = mul(Po,WorldXf);
float3 position = Pw.xyz;
float4 camPos = float4(ViewIXf[3].xyz,1);

oPosition = mul(Po, WorldViewProj);

float radius = length(position);
float radius2 = radius * radius;
float camHeight = length(camPos.xyz);
float3 camToPos = position - camPos.xyz;
float farDist = length(camToPos);

float3 lightDir = normalize(lightPos.xyz);
float3 normal = normalize(position);

float3 rayDir = camToPos / farDist;
float camHeight2 = camHeight * camHeight;

// Calculate the closest intersection of the ray with the outer atmosphere
float B = 2.0 * dot(camPos.xyz, rayDir);
float C = camHeight2 - radius2;
float det = max(0.0, B*B - 4.0 * C);
float nearDist = 0.5 * (-B - sqrt(det));
float3 nearPos = camPos.xyz + (rayDir * nearDist);
float3 nearNormal = normalize(nearPos);

// get dot products we need
float lc = dot(lightDir, camPos / camHeight);
float ln = dot(lightDir, normal);
float lnn = dot(lightDir, nearNormal);

// get distance to surface horizon
float altitude = camHeight - SurfaceRadius;
float horizonDist = sqrt((altitude*altitude) + (2.0 * SurfaceRadius * altitude));
float maxDot = horizonDist / camHeight;

// get distance to atmosphere horizon - use max(0,...) because we can go into the atmosphere
altitude = max(0,camHeight - AtmosphereRadius);
horizonDist = sqrt((altitude*altitude) + (2.0 * AtmosphereRadius * altitude));

// without this, the shift between inside and outside atmosphere is jarring
float tweakAmount = 0.1;
float minDot = max(tweakAmount,horizonDist / camHeight);

// scale minDot from 0 to -1 as we enter the atmosphere
float minDot2 = ((camHeight - SurfaceRadius) * (1.0 / (AtmosphereRadius - SurfaceRadius))) - (1.0 - tweakAmount);
minDot = min(minDot, minDot2);

// get dot product of the vertex we're looking out
float posDot = dot(camToPos / farDist,-camPos.xyz / camHeight) - minDot;

// calculate the height from surface in range 0..1
float height = posDot * (1.0 / (maxDot - minDot));

// push the horizon back based on artistic taste
ln = max(0,ln + StretchAmt);
lnn = max(0,lnn + StretchAmt);

// the front color is the sum of the near and far normals
float brightness = saturate(ln + (lnn * lc));

// use "saturate(lc + 1.0 + StretchAmt)" to make more of the sunset side color be used when behind the planet
oUV.x = brightness * saturate(lc + 1.0 + StretchAmt);
oUV.y = height;

// as the camera gets lower in the atmosphere artificially increase the height
// so that the alpha value gets raised and multiply the increase amount
// by the dot product of the light and the vertex normal so that
// vertices closer to the sun are less transparent than vertices far from the sun.
height -= min(0.0,minDot2 + (ln * minDot2));
oAlpha = height * brightness;

// normalised camera to position ray
oCamToPos = -rayDir;

oLightDir = normalize(lightPos.xyz - position.xyz);
}

float4 mainBPS(
float2 uv : TEXCOORD0,
float alpha : TEXCOORD1,
float3 camToPos : TEXCOORD2,
float3 lightDir :TEXCOORD3,
uniform sampler2D TexSampler
) : COLOR {

const float fExposure = 1.5;
float g = Atmosphere_G;
float g2 = g * g;

// atmosphere color
float4 diffuse = tex2D(TexSampler,uv);

// sun outer color - might could use atmosphere color
float4 diffuse2 = tex2D(TexSampler,float2(min(0.5,uv.x),1));

// this is equivilant but faster than fCos = dot(normalize(lightDir.xyz),normalize(camToPos));
float fCos = dot(lightDir.xyz,camToPos) * rsqrt( dot(lightDir.xyz,lightDir.xyz) * dot(camToPos,camToPos));
float fCos2 = fCos * fCos;

// apply alpha to atmosphere
float4 diffuseColor = diffuse * alpha;

// sun glow color
float fMiePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos2) /(1.0 + g2 - 2.0*g*fCos);
float4 mieColor = diffuse2 * fMiePhase * alpha;

// use exponential falloff because mie color is in high dynamic range
// boost diffuse color near horizon because it gets desaturated by falloff
return 1.0 - exp((diffuseColor * (1.0 + uv.y) + mieColor) * -fExposure);
}

technique technique1 {
pass p0 {
ZEnable = false;
ZWriteEnable = false;
CullMode = CCW;
AlphaBlendEnable = true;
SrcBlend = One ;
DestBlend = InvSrcAlpha;
VertexShader = compile vs_3_0 main2VS(gLamp0DirPos);
PixelShader = compile ps_3_0 mainBPS(gTexSampler);
}
}


Possible Improvements
- pre-calculate camera height, camera height squared and other such variables. you should notice that a lot of the vertex shader code would be the same for every vertex including calculating horizon distance, camera and light only based calculations and parts of the sphere intersection calculations.
- simplify mie scattering equation and remove hdr
- when the camera is inside the atmosphere we no longer need to calculate the atmosphere closest intersection so the program should switch to a simpler version of the shader once inside.

Known Issues
- transition from outer atmosphere to inner atmosphere is not smooth. I suspect this is due to my atmosphere being an un-realistic size so I had to add a tweak amount so that the transition point is actually a bit inside the atmosphere at a point where only the inside of the sphere is visible to the camera. At the point where the camera is the same height as the atmosphere, it can still see the outside of the atmosphere shell.
- when on the surface of the planet looking back at the horizon it looks more like a painted shell than a spherical haze
- when you get really close to the edge of the atmosphere on the side facing the sun there is an artifact that appears that I haven't fixed yet.
- there are banding issues in the sky gradient when on the side of the planet facing the sun that I haven't solved yet and I think they're related to the color choices in my gradient, but I'm not sure.

If you have any suggestions, improvements or bug fixes please let me know in the comments!

Wednesday, February 17, 2010

Spacescape - Now With Billboards

spacescape image with billboard sprites
(click to enlarge)

Just a minor update - I've added billboard sprites to the mix where as before I was just using point sprites. I also added an extra line to the noise shader like this:

noiseSum = pow(noiseSum / powerAmount);


This allows me to control the slope of the noise gradient so it is more steep or more gradual, which is something I needed for masks.

The image above really taxes my system (3-4 fps). It has 12 layers: one ridged noise mask for the brightest stars, then another for the middle level of stars and then two more ridged noise haze layers and the rest are mixes of billboard and point stars.

For kicks I threw the generated skybox into the planet app and it's definitely a bit over the top and low-res/blurry. Still it is "fun"

(click images to enlarge)
planet with generated skybox

planet with generated skybox 2

As usual, I uploaded these images and more to the My First Planet gallery.

I re-implemented writeToFile() in the plugin so I can save to files. I also added a writeToMaterial() function to the plugin so I can preview the skybox in ogre.

One thing I've noticed is that the resolution of skybox really affects the brightness of small stars. When I save a 2048x2048 skybox out the single pixel point stars are crisp, but get lost when imported into a program with a low screen resolution. So you kinda have to take that into account when making the skyboxes. It might look great at the resolution on your monitor, but not at the resolution on someone else's monitor. This is where the writeToMaterial() function will be useful so you can preview the skybox at different resolutions to see if it works OK at low resolutions.

I might mess with the mipmap generation for lower levels so that stars don't loose their brightness as quickly when the high resolution image is scaled down.

Here's a sample generated skybox (click for 1024x1024 version - open in new window):
right
right
left
left
top
top
bottom
bottom
front
front
back
back

And here's a funny little screenshot of what a skybox layered sphere looks like from the outside (not the same as the one above):
outside skybox layer sphere

Monday, February 15, 2010

Introducting Spacescape! A Space Skybox Creator.

Spacescape Alpha

Spacescape is a tool that uses Ogre to create / generate space scene skyboxes. The way it works is a camera sits inside a number of user defined layers / shells. Each layer can be a bunch of point sprites ( for stars), or a noise field (for nebulas/haze etc.) You can define how many stars you want and the color based on distance. In the screenshots I'm posting there are 7 layers. 3 of the layers are regular blue/white stars, 1 is a few red stars, 2 layers are ridged fbm noise of different colours and the 3rd layer is regular fbm noise used as a mask to make stars cluster by darkening regions of space.

This has been a really fun tool to make and I still have a lot left to do like the GUI. At present it reads in xml config files or you can use the plugin api to manipulate the skybox via code. Did I mention this will be an Ogre plugin too? I plan on releasing the plugin and tool as soon as I get a GUI and finish refactoring the save to file code.

How it works


Making a bunch of point sprites is easy. I use an Ogre::ManualObject for that with pointSprites enabled on the material.

For making the haze I create an inverted unit sphere and use a glsl shader on it that implements fbm and ridged fbm noise using Perlin's improved noise (not simplex noise). Lintfordpickle at britonia-game.com has a good explanation with code samples about how fbm and ridged fbm noise work

To create the skyboxes I just create a camera with FOV set to 90degrees and render to texture the six images of the skybox. I have it set up to render to texture the mipmap levels too, so if a user wants to save a dds file with mipmaps they can, or they can just save out the six individual images. With the Ogre plugin it will also be possible to not save out the file but just tell the plugin to create a skybox with a given config file and use it in your own application.


Here's some more images (click to enlarge - you'll need to in order to see the stars better):







A note about dithering:
I've had a tough time figuring out what to do about gradient banding with dark colors in the space backgrounds. Without some kind of dithering or noise the gradient is very obvious. You can see the banding clearly in all the videos and screenshots I've posted prior to this post. The skybox I was using before was generated with blender and spiced up in photoshop, but there was no dithering on the haze so it's really ugly. To get around this with Spacescape, I provide the option to dither a noise layer by a controllable amount and this seems to make the banding issue go away.

The plan is to use this plugin in MyFirstPlanet to let users design their own space skyboxes easily too.

Hopefully, I'll have a demo and some source code posted in the next week or so.

Wednesday, May 9, 2007

Island Soup


I've been working on a project to get me familiar with Ogre3d, which is an open source free graphics engine that runs on all major platforms. Anyways, the project is an island generator that uses random noise, and some math to make random island shapes. Here's one




I googled for ideas and related tutorial/articles and came up with this one about perturbing existing 2d images, on a site created by the guy who wrote this useful forum post.


here's some other screen captures: