PDA

View Full Version : [OpenGL] Earth atmosphere effect



Brainer
27-06-2008, 09:32 AM
Hello. :)

I would like to write (based on my own, not-ready yet engine. :D) a little program that displays our Earth, stars, and generally space. :) I've already coded the basics (the starfield, displaying Earth), but I'd like to know how do I achieve the effect similar to the one in this (http://bp2.blogger.com/_NpINLHeo8rM/RmBKBptWnsI/AAAAAAAABSc/Gjhlwz-OKl4/s400/15.jpg) picture?

I thought of using border detection algorithms, I've heard there are a few of them. But maybe there is another solution? Please explain it to me.

Greetings.:)

User137
27-06-2008, 10:02 AM
Simple 1 is to render a quad with high enough quality bi-linear texture (512 perhaps enough, maybe 1024) with black and white smooth sphere behind the planet, using additive blending and no depth-test. You don't need alpha channel for the texture.

Or you could render it on top of planet but then you need to fade out big portion of sphere center in your image editor. This technique would allow texture light up a little of planet surface too that may look more realistic.

Size and position of the quad shouldn't be a problem.

Edit: Oh, you need to invert Modelview matrix, null its position values (x,y,z are on 4th column) and then use it with glMultMatrixf before rendering quad.

Brainer
27-06-2008, 10:13 AM
Simple 1 is to render a quad with high enough quality bi-linear texture (512 perhaps enough, maybe 1024) with black and white smooth sphere behind the planet, using additive blending and no depth-test.

Sounds very interesting. Unfortunately I don't have any ideas how to do it. Can you show me an example of this? Is this the same technique as projected textures?

Also, I've found out that GLScene has the thing I'm looking for, but I can't understand how it works. Can someone explain it? Here's the code:

procedure TGLCustomAtmosphere.DoRender(var rci: TRenderContextInfo; renderSelf, renderChildren: Boolean);
var
radius, invAtmosphereHeight: Single;
sunPos, eyePos, lightingVector: TVector;
diskNormal, diskRight, diskUp: TVector;


function AtmosphereColor(const rayStart, rayEnd: TVector): TColorVector;
var
I, n: Integer;
atmPoint, normal: TVector;
altColor: TColorVector;
alt, rayLength, contrib, decay, intensity, invN: Single;
begin
Result := clrTransparent;
rayLength := VectorDistance(rayStart, rayEnd);
n := Round(3 * rayLength * invAtmosphereHeight) + 2;
if n > 10 then
n := 10;
invN := cIntDivTable[n];//1/n;
contrib := rayLength * invN * Opacity;
decay := 1 - contrib * 0.5;
contrib := contrib * (1 / 1.1);
for I := n - 1 downto 0 do
begin
VectorLerp(rayStart, rayEnd, I * invN, atmPoint);
// diffuse lighting normal
normal := VectorNormalize(atmPoint);
// diffuse lighting intensity
intensity := VectorDotProduct(normal, lightingVector) + 0.1;
if PInteger(@intensity)^ > 0 then
begin
// sample on the lit side
intensity := intensity * contrib;
alt := (VectorLength(atmPoint) - FPlanetRadius) * invAtmosphereHeight;
VectorLerp(LowAtmColor.Color, HighAtmColor.Color, alt, altColor);
Result[0] := Result[0] * decay + altColor[0] * intensity;
Result[1] := Result[1] * decay + altColor[1] * intensity;
Result[2] := Result[2] * decay + altColor[2] * intensity;
end
else
begin
// sample on the dark sid
Result[0] := Result[0] * decay;
Result[1] := Result[1] * decay;
Result[2] := Result[2] * decay;
end;
end;
Result[3] := n * contrib * Opacity * 0.1;
end;


function ComputeColor(var rayDest: TVector; mayHitGround: Boolean): TColorVector;
var
ai1, ai2, pi1, pi2: TVector;
rayVector: TVector;
begin
rayVector := VectorNormalize(VectorSubtract(rayDest, eyePos));
if RayCastSphereIntersect(eyePos, rayVector, NullHmgPoint,
FAtmosphereRadius, ai1, ai2) > 1 then
begin
// atmosphere hit
if mayHitGround and (RayCastSphereIntersect(eyePos, rayVector,
NullHmgPoint, FPlanetRadius, pi1, pi2) > 0) then
begin
// hit ground
Result := AtmosphereColor(ai1, pi1);
end
else
begin
// through atmosphere only
Result := AtmosphereColor(ai1, ai2);
end;
rayDest := ai1;
end
else
Result := clrTransparent;
end;

var
I, J, k0, k1: Integer;
begin
if FSun <nil> FPlanetRadius);

sunPos := VectorSubtract(FSun.AbsolutePosition, AbsolutePosition);
eyepos := VectorSubtract(rci.CameraPosition, AbsolutePosition);

diskNormal := VectorNegate(eyePos);
NormalizeVector(diskNormal);
diskRight := VectorCrossProduct(rci.CameraUp, diskNormal);
NormalizeVector(diskRight);
diskUp := VectorCrossProduct(diskNormal, diskRight);
NormalizeVector(diskUp);

invAtmosphereHeight := 1 / (FAtmosphereRadius - FPlanetRadius);
lightingVector := VectorNormalize(sunPos); // sun at infinity

glPushAttrib(GL_ENABLE_BIT);
glDepthMask(False);
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
EnableGLBlendingMode;
for I := 0 to 13 do
begin
if I < 5 then
radius := FPlanetRadius * Sqrt(I * (1 / 5))
else
radius := FPlanetRadius + (I - 5.1) * (FAtmosphereRadius - FPlanetRadius) * (1 / 6.9);
radius := SphereVisibleRadius(VectorLength(eyePos), radius);
k0 := (I and 1) * (FSlices + 1);
k1 := (FSlices + 1) - k0;
for J := 0 to FSlices do
begin
VectorCombine(diskRight, diskUp,
cosCache[J] * radius, sinCache[J] * radius,
pVertex[k0 + J]);
if I < 13 then
pColor[k0 + J] := ComputeColor(pVertex[k0 + J], I <7> 1 then
begin
if I = 13 then
begin
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUAD_STRIP);
for J := FSlices downto 0 do
begin
glColor4fv(@pColor[k1 + J]);
glVertex3fv(@pVertex[k1 + J]);
glColor4fv(@clrTransparent);
glVertex3fv(@pVertex[k0 + J]);
end;
glEnd;
end
else
begin
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_COLOR);
glBegin(GL_QUAD_STRIP);
for J := FSlices downto 0 do
begin
glColor4fv(@pColor[k1 + J]);
glVertex3fv(@pVertex[k1 + J]);
glColor4fv(@pColor[k0 + J]);
glVertex3fv(@pVertex[k0 + J]);
end;
glEnd;
end;
end
else if I = 1 then
begin
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_TRIANGLE_FAN);
glColor4fv(@pColor[k1]);
glVertex3fv(@pVertex[k1]);
for J := k0 + FSlices downto k0 do
begin
glColor4fv(@pColor[J]);
glVertex3fv(@pVertex[J]);
end;
glEnd;
end;
end;
glDepthMask(True);
glPopAttrib;
end;
inherited;
end;

User137
27-06-2008, 01:03 PM
Don't know what the other code did, but here's my idea demonstrated:
Glow texture http://i30.tinypic.com/o8fcqr.jpg
Test application (with sphere actually rotating realtime) http://i25.tinypic.com/jaja6q.jpg
You can have it better depending on scaling and glow texture. This one is 256x256 only cause i wanted to see how it looks :P

unit Unit1;

interface

uses
Windows, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, dglOpenGL, nxGL, Geometry2;

type
TForm1 = class(TForm)
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Timer1Timer(Sender: TObject);
private
sphere: pgluQuadric;
public
end;

var Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var f: array[0..3] of single;
begin
nxCreateGlWindow(handle);
tex.AddTexture('glow','glow.jpg');
sphere:=gluNewQuadric;
gluQuadricNormals(sphere,GLU_SMOOTH);
DefaultLights;
f[0]:=7;
f[1]:=-2;
f[2]:=6;
f[3]:=0;
glLightfv(GL_LIGHT0,GL_POSITION,@f);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
nxKillGLWindow;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
const A_rad = 1.33;
var mat: TMatrix;
begin
nxClear(true,true);

glLoadIdentity;
glTranslatef(0,0,-4);
glRotatef(GetTickCount*0.02,0,1,0);

// Planet
glEnable(GL_DEPTH_TEST);
tex.SetTex(-1);
glColor3f(0.2,0.2,0.2);
gluSphere(sphere,1,30,30);
glDisable(GL_DEPTH_TEST);

// Atmosphere effect
glDisable(GL_LIGHTING);
tex.SetTex(0);
AddBlend(true);
glColor3f(0.4,0.6,1);
glGetfloatv(GL_MODELVIEW_MATRIX,@mat);
MatrixInvert(mat);
mat[3,0]:=0;
mat[3,1]:=0;
mat[3,2]:=0;
glMultMatrixf(@mat);
RectT(A_rad,-A_rad,-A_rad,A_rad);
AddBlend(false);
glEnable(GL_LIGHTING);

nxFlip;
end;

end.

Brainer
27-06-2008, 01:55 PM
Whoa, looks very good! :D The only question left is what does RectT do?

User137
27-06-2008, 05:05 PM
RectT renders a quad with texture coordinates.

procedure RectT(x1,y1,x2,y2: single);
begin
glBegin(GL_QUADS);
glTexCoord2f(0,0); glVertex2f(x1, y1);
glTexCoord2f(0,1); glVertex2f(x1, y2);
glTexCoord2f(1,1); glVertex2f(x2, y2);
glTexCoord2f(1,0); glVertex2f(x2, y1);
glEnd;
end;

Brainer
27-06-2008, 05:18 PM
Thank you very much, man. :D

efilnukefesin
28-06-2008, 07:03 AM
perhaps also an interesting link for this topic:
http://www.gamedev.net/community/forums/topic.asp?topic_id=498700

Brainer
28-06-2008, 07:17 PM
I've discussed this matter with User137, but we couldn't find the answer together, so I decided to ask you, guys, to help me out.

The problem is that the ring doesn't seem to be correctly aligned with the sphere. Look at the screenshot's top-left corner. You can see that the texture is not at the border of the sphere, but now it overlaps the other texture.
http://i29.tinypic.com/2jebnrl.jpg
How do I fix it?

JSoftware
28-06-2008, 09:39 PM
It's probably because it's screen aligned. I think the solution is to use billboards instead, so that the normal always faces the viewer

User137
28-06-2008, 09:42 PM
What i was thinking to fix that is calculating the rotation matrix differently. Find out x,y,z for the center of planet as it is transformed with Modelview matrix and then calculate a "look-at" matrix from that point to eye center, that is in the middle of near plane in frustum... or something :D

Brainer
29-06-2008, 06:04 PM
What i was thinking to fix that is calculating the rotation matrix differently. Find out x,y,z for the center of planet as it is transformed with Modelview matrix and then calculate a "look-at" matrix from that point to eye center, that is in the middle of near plane in frustum... or something :D
What do you think of this (http://www.lighthouse3d.com/opengl/billboarding/index.php?billCyl)? Is this of any help maybe?

User137
29-06-2008, 07:30 PM
Yes, billboarding is exactly the technique used in the case. The glow is sort of sprite that you turn towards camera.

Brainer
30-06-2008, 06:09 PM
Yip, I agree, but isn't it the technique that is used in my case? Why doesn't it work?

Mirage
01-07-2008, 04:19 AM
Yip, I agree, but isn't it the technique that is used in my case? Why doesn't it work?

Obviously, because 3D sphere and 2D billboard will be deformed differently by perspective. Try to flattern the sphere in vertex shader along view vector.

Brainer
01-07-2008, 04:19 PM
I don't know anything about vertex shaders, so that's all black magic to me. I found a little demo on Nehe's website, here's the conversion:

{ .: GetCameraPosAndUp :. }
procedure GetCameraPosAndUp(var camPos, camUp: TAffineVector);
var
ViewMatrix: array[0..15] of Single;
M: TMatrix;
begin
glGetFloatv(GL_MODELVIEW_MATRIX, @ViewMatrix);

camPos := AffineVectorMake(-ViewMatrix[12], -ViewMatrix[13], -ViewMatrix[14]);
camUp := AffineVectorMake(ViewMatrix[1], ViewMatrix[5], ViewMatrix[9]);
ViewMatrix[12] := 0.0;
ViewMatrix[13] := 0.0;
ViewMatrix[14] := 0.0;
M := ConvertArrayToMatrix(ViewMatrix);
TransposeMatrix(M);

camPos := AffineVectorMake(VectorTransform(VectorMake(camPos ), M));
end;

{ .: CreateBillboardMatrix :. }
procedure CreateBillboardMatrix(var AMat: TMatrix; const ARight, AUp, ALook,
APos: TAffineVector);
begin
AMat[0][0] := ARight[0];
AMat[0][1] := ARight[1];
AMat[0][2] := ARight[2];
AMat[0][3] := 0.0;
AMat[1][0] := AUp[0];
AMat[1][1] := AUp[1];
AMat[1][2] := AUp[2];
AMat[1][3] := 0.0;
AMat[2][0] := ALook[0];
AMat[2][1] := ALook[1];
AMat[2][2] := ALook[2];
AMat[2][3] := 0.0;
// Add the translation in as well.
AMat[3][0] := APos[0];
AMat[3][1] := APos[1];
AMat[3][2] := APos[2];
AMat[3][3] := 1.0;
end;

{ .: BillboardAxis :. }
procedure BillboardAxis(const Pos, Axis, camPos: TAffineVector);
var
Look, Up, Right: TAffineVector;
begin
Look := VectorSubtract(camPos, Pos);
NormalizeVector(Look);

Up := Axis;
Right := VectorCrossProduct(Up, Look);
NormalizeVector(Right);

Look := VectorCrossProduct(Right, Up);

CreateBillboardMatrix(M, Right, Up, Look, Pos);
end;

But this doesn't work either. Here are the screenshots - Screen #1 (http://i27.tinypic.com/1zn3l1d.jpg), Screen #2 (http://i29.tinypic.com/6xyus7.jpg).

Any more ideas?

Mirage
01-07-2008, 04:36 PM
I did this once. Sphere (planet) is volumetric and due to perspective its projection to the screen is not always a circle. You need to make your planet flat too. This most easily can be done by a vertex shader. But you can flattern it while drawing since you are using glBegin/glEnd, not VBO.
In other words, for each vertice of the planet you do the following:

DirP &#58;= DotProduct&#40;Vertices&#91;i&#93;, Direction&#41;;
DrawVertices&#91;i&#93; &#58;= SubVector&#40;Vertices&#91;i&#93;, ScaleVector&#40;Direction, DirP&#41;;
Direction is normalised direction vector from camera to center of the planet.

Brainer
01-07-2008, 04:40 PM
Don't get me wrong, but what's the point in doing so? I think that the problem is with billboard matrix calculation, not the sphere itself.

farcodev_
01-07-2008, 06:26 PM
just try to take a look, in the GLScene CVS branch, for GLAtmosphere, i use it in my game and work well.

Brainer
01-07-2008, 06:52 PM
Yip, I tried to do so, but no success. Besides, as you can read in the comments, it uses up a lot of processing resources, and for all I can see it's possible to achieve the same result in a more efficient way with that glow texture.

Brainer
01-07-2008, 08:35 PM
Ok, nevermind. I decided to go with the TGLAtmosphere class from GLScene and here's the result (no optimizations yet). :)
http://i25.tinypic.com/2pp0wnl.jpg.

User137
01-07-2008, 09:07 PM
Well maybe that looks better but doesn't mean glow texture couldn't be split and colorized like that aswell :wink:

This is what i came up with the glow texture (new texture):
http://i30.tinypic.com/iqlt8x.jpg

Read the nehe link or something about rotating the matrix and it works. In my sample program the planet follows cursor and glow is always placed properly around planet.

// * Atmosphere effect *
glPushMatrix;
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);

glGetfloatv(GL_MODELVIEW_MATRIX,@mat);
// Store position from modelview
pos.x:=mat[3,0]; pos.y:=mat[3,1]; pos.z:=mat[3,2];
// Look-at vector from position to 0 (camera)
look.x:=-pos.x;
look.y:=-pos.y;
look.z:=-pos.z;
norm(@look.x);
// Define a temporary up vector
up.x:=0; up.y:=1; up.z:=0;
// Crossproduct look and up to get right
right:=crossproduct(@look,@up);
// Crossproduct look and right to get proper up vector
up:=crossproduct(@look,@right);
// Place vectors back to matrix
copymemory(@mat[0,0], @right.x, sizeof(single)*3);
copymemory(@mat[1,0], @up.x, sizeof(single)*3);
copymemory(@mat[2,0], @look.x, sizeof(single)*3);

glLoadIdentity;
glMultMatrixf(@mat);
tex.SetTex(0);
AddBlend(true);
glColor3f(0.4,0.6,1);
RectT(A_rad,-A_rad,-A_rad,A_rad); // Render quad

AddBlend(false);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
glPopMatrix;
// * End atmosphere effect *

Brainer
01-07-2008, 09:13 PM
Thanks for your effort, User137. :) I'mma use your code, too. :) Looks decent! :D

Brainer
01-07-2008, 10:38 PM
Oops, I've just noticed that it works, but not quite good enough yet. :roll: When the observer is too far away from the sphere, the glow is too big, but when it's too close, the glow is too small! :o Here are the screenshots.
Screen #1 - far from the planet (http://i30.tinypic.com/eks5rm.jpg)
Screen #2 - close to the planet (http://i26.tinypic.com/153s8kj.jpg)
What do you think about it?

User137
01-07-2008, 11:10 PM
Far and normal ranges are fine in my testing, but when you get too close it starts to shrink the glow inside sphere for some reason. :? Tried normalizing all vectors but doesn't do anything.

Could be that perspective "eye point" is not at 0 but more behind or forward some amount. Fov near plane was at least not that.

Edit: Oh i know what happens... Because of perspective view the sphere front becomes much wider than center part of sphere when zoomed close in and therefore glow will be smaller than visible sphere. Imagine looking at a cube from straight front, the front face will cover everything up when watching it closer, and it is the same thing. Solution could be scaling up the glow when coming down to certain range... just goes very unformal and tricky again. I have no idea how to calculate correct scaling and if it's worth it.

Brainer
01-07-2008, 11:48 PM
Oh i know what happens... Because of perspective view the sphere front becomes much wider than center part of sphere when zoomed close in and therefore glow will be smaller than visible sphere. Imagine looking at a cube from straight front, the front face will cover everything up when watching it closer, and it is the same thing. Solution could be scaling up the glow when coming down to certain range... .
Yip, you are absoultely right. When it comes to calculating the scaling - I think that it could be something with the distance between the observer and the sphere. I believe that there's an equation that would help us solve it, but I'm not good at maths. :?

But there's a ray of hope. :) This function taken from GLScene looks interesting and could help us getting the scaling work properly:

procedure TGLCamera.AdjustDistanceToTarget(distanceRatio : Single);
var
vect : TVector;
begin
if Assigned(FTargetObject) then begin
// calculate vector from target to camera in absolute coordinates
vect:=VectorSubtract(AbsolutePosition, TargetObject.AbsolutePosition);
// ratio -> translation vector
ScaleVector(vect, -(1-distanceRatio));
AddVector(vect, AbsolutePosition);
if Assigned(Parent) then
vect:=Parent.AbsoluteToLocal(vect);
Position.AsVector:=vect;
end;
end;


Anyway, I'm waiting for your opinions. :)

User137
02-07-2008, 11:59 AM
I don't think i have GLScene installed and not fussed to :P But seeing the code it looks that distanceRatio parameter given to function may be the key, where is it calculated and how?

Brainer
02-07-2008, 02:20 PM
It's not calculated anywhere, the user is supposed to use something as a value. Here's a little example of how it's done in one of GLScene's demos:

procedure TForm1.FormMouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
f : Single;
begin
if (WheelDelta>0) or (GLCameraControler.Position.VectorLength>0.90) then begin
f:=Power(1.05, WheelDelta*(1/120));
GLCameraControler.AdjustDistanceToTarget(f);
end;
Handled:=True;
end;

farcodev_
03-07-2008, 12:28 AM
Ok, nevermind. I decided to go with the TGLAtmosphere class from GLScene and here's the result (no optimizations yet). :)
http://i25.tinypic.com/2pp0wnl.jpg.

hehe good news, yup its rock :)

you can even change colors and density, i'll upgrade my engine with atmosphere colors following atmosphere type, doesn't change much for the game itself but i must do it lol.

farcodev_
03-07-2008, 12:31 AM
Yip, you are absoultely right. When it comes to calculating the scaling - I think that it could be something with the distance between the observer and the sphere. I believe that there's an equation that would help us solve it, but I'm not good at maths. :?

But there's a ray of hope. :) This function taken from GLScene looks interesting and could help us getting the scaling work properly:

procedure TGLCamera.AdjustDistanceToTarget(distanceRatio : Single);
var
vect : TVector;
begin
if Assigned(FTargetObject) then begin
// calculate vector from target to camera in absolute coordinates
vect:=VectorSubtract(AbsolutePosition, TargetObject.AbsolutePosition);
// ratio -> translation vector
ScaleVector(vect, -(1-distanceRatio));
AddVector(vect, AbsolutePosition);
if Assigned(Parent) then
vect:=Parent.AbsoluteToLocal(vect);
Position.AsVector:=vect;
end;
end;


Anyway, I'm waiting for your opinions. :)

Its the function i use for the zoom/unzoom in my game, so its ok ;)