Showing posts with label graphics. Show all posts
Showing posts with label graphics. Show all posts

Tuesday, September 23, 2014

NEW Spacescape gets HDR, OSX and more

Version 0.5 of Spacescape has been released -  Download it!



If you were waiting for Spacescape to run on OSX, your wait is over! Not only is it over, Spacescape got some requested features that are really cool.

v0.5 FEATURE LIST

  • Support for OSX
  • HDR mode for working in high dynamic range and exporting 32bit per channel .exr or .dds skybox images.  Also allows for using HDR billboards (.exr)
  • Import billboard data file to manually specify billboard positions, brightness, distance and  colour
  • More export options! Export now adds support for UNREAL (3d cube map with correct rotations and .dds output), UNITY (correct naming), SOURCE (correct naming and .tga output)
  • Debug box option for those trying to determine the orientation of the exported skybox faces for importing into a different engine
  • Converted billboard file field to support a browse button so you can choose any file on your computer instead of having to put them in the media/materials/textures folder

About this HDR thing..

You can thank the good folks at NVidia Demos for financing the addition for a recent demo they released!  You can see part of the Spacescape generated HDR starfield with actual star values, positions, brightness values, colours and some generated nebulas around 6:54.


It's really cool, but for most games you'll probably stick with non-HDR because you just don't need the extra data.  HDR skyboxes are cool when you want to accurately adjust the exposure in your game for, say, a daytime scene when the stars are not visible because the atmosphere is so bright, and then as night comes and the atmosphere is no longer bright the stars become visible.  Keep in mind a few things:
  1. Use HDR billboards in .exr format or the outlines of your billboard stars will get too blown out as you adjust the exposure.  Some sample .exr billboards are included in the app or you can make your own in Gimp or Photoshop.
  2. Use the new HDR Power field to push most of your stars into the background if you are using randomly generated star positions, otherwise use a data file.
  3. If you use a data file to specify star data, the input is a csv file that requires an X, Y, Z, ABSMAG, and DISTANCE.  All these values are based on actual star data and there is a sample data.csv file with a few constellations in it in the Files section of project.  You can also find a massive star database at http://www.astronexus.com/hyg
  4. Use HDR Multiplier to make background stars and nebulas really faint and near stars bright
  5. A good range for HDR brightness values is between 0 and 10, even though the colour values for stars is from 0 to 255, when in HDR mode those numbers are actually converted to the 0 to 1 range then the HDR power function is applied and the result is multiplied by the HDR multiplier.

There's lots left to do

And that's an understatement.   Spacescape is nothing like the final tool I want it to be. I want to implement a simple mode for non-wizards, and a skybox library - a way to share skyboxes from within the app amongst many other things.  I'd also like improve the way layer masks work and possibly add terrain and planets!

Kickstarter?

To add all the features I want would take a lot of time.  I was only able to add in these recent features because Nvidia was able to help pay for those hours.  So I'm considering putting up a kickstarter or indiegogo project to get the other features funded.  Maybe nobody will notice? On the other hand, Spacescape has been downloaded 41,000 times since I posted it in 2010.

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!

Friday, April 23, 2010

Sphere to Cube Mapping

The following unit cube to unit sphere mapping is nice because the resulting sphere vertices are distributed somewhat evently:

Math Proofs: Mapping a Cube to a Sphere, by Phil

Here's the c++ version where x,y,z are the cube coords and sx,sy,sz are the sphere coords:

sx = x * sqrtf(1.0f - y * y * 0.5f - z * z * 0.5f + y * y * z * z / 3.0f);

sy = y * sqrtf(1.0f - z * z * 0.5f - x * x * 0.5f + z * z * x * x / 3.0f);

sz = z * sqrtf(1.0f - x * x * 0.5f - y * y * 0.5f + x * x * y * y / 3.0f);



Recently, I've been working on the reverse mapping (from unit sphere to unit cube) and have come up with this solution:

First determine the cube face the sphere point projects to. This step is simple - just find the component of the sphere vector with the greatest length.

Next, for each face, take the remaining cube vector components denoted as s and t and solve for them using these equations, which are based on the remaining sphere vector components denoted as a and b:

s = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)+2 a^2-2 b^2+3)/sqrt(2)
t = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)-2 a^2+2 b^2+3)/sqrt(2)

You should see that the inner square root is used in both equations, so only do that part once.

Here's the final function with the equations thrown in and checks for 0.0 and -0.0 and the code to properly set the sign of the cube component - it should be equal to the sign of the sphere component.


void cubizePoint(Vector3& position)
{
double x,y,z;
x = position.x;
y = position.y;
z = position.z;

double fx, fy, fz;
fx = fabsf(x);
fy = fabsf(y);
fz = fabsf(z);

const double inverseSqrt2 = 0.70710676908493042;

if (fy >= fx && fy >= fz) {
double a2 = x * x * 2.0;
double b2 = z * z * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);

if(x == 0.0 || x == -0.0) { 
position.x = 0.0; 
}
else {
position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}

if(z == 0.0 || z == -0.0) {
position.z = 0.0;
}
else {
position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}

if(position.x > 1.0) position.x = 1.0;
if(position.z > 1.0) position.z = 1.0;

if(x < 0) position.x = -position.x;
if(z < 0) position.z = -position.z;

if (y > 0) {
// top face
position.y = 1.0;
}
else {
// bottom face
position.y = -1.0;
}
}
else if (fx >= fy && fx >= fz) {
double a2 = y * y * 2.0;
double b2 = z * z * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);

if(y == 0.0 || y == -0.0) { 
position.y = 0.0; 
}
else {
position.y = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}

if(z == 0.0 || z == -0.0) {
position.z = 0.0;
}
else {
position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}

if(position.y > 1.0) position.y = 1.0;
if(position.z > 1.0) position.z = 1.0;

if(y < 0) position.y = -position.y;
if(z < 0) position.z = -position.z;

if (x > 0) {
// right face
position.x = 1.0;
}
else {
// left face
position.x = -1.0;
}
}
else {
double a2 = x * x * 2.0;
double b2 = y * y * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);

if(x == 0.0 || x == -0.0) { 
position.x = 0.0; 
}
else {
position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}

if(y == 0.0 || y == -0.0) {
position.y = 0.0;
}
else {
position.y = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}

if(position.x > 1.0) position.x = 1.0;
if(position.y > 1.0) position.y = 1.0;

if(x < 0) position.x = -position.x;
if(y < 0) position.y = -position.y;

if (z > 0) {
// front face
position.z = 1.0;
}
else {
// back face
position.z = -1.0;
}
}


I posted a stackoverflow question and got a lot of help from there and from mathoverflow too. I used wolframalpha.com to get the equations for s and t.

My thanks to gmatt and Leonid Kovalev!

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: