• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
sheep19

Raytracing - circle looks "ugly"

11 posts in this topic

Hi, I'm implementing raytracing to become familiar with it.

So far, I create two spheres.
Sphere 1 is at (0, 0, 5), radius = 1
Sphere 2 is at (0, 10, 50), radius = 20

Camera is at (0, 0, -1)

This is the Sphere class [only the hit function is interesting]
class Sphere : Surface
{
Material material;
Vector3 center;
float radius;

this()
{
}

this(const ref Vector3 _center, float _radius)
{
center = _center;
radius = _radius;
}

bool hit(const Ray r, float p0, float p1, ref HitInfo hitInfo) const
{
Vector3 d = r.d, e = r.e, c = center;

float discriminant = dot(d, e-c) * dot(d, e-c) - dot(d, d) * (dot(e-c, e-c) - radius * radius);
if( discriminant >= 0 )
{
//float t1 = (dot(-d, e-c) + sqrt(discriminant)) / dot(d, d);
float t2 = (dot(-d, e-c) - sqrt(discriminant)) / dot(d, d);

// TODO: don't forget to change this if needed
if( t2 < p0 || t2 > p1 )
return false;

hitInfo.t = t2;
hitInfo.hitPoint = e + d * t2;
hitInfo.ray = d;
hitInfo.surfaceNormal = (hitInfo.hitPoint - c) * 2; // is this correct?
}

return discriminant >= 0; // TODO: implement
}

Box boundingBox() const
{
Vector3 min = {center.x - radius * 0.5f, center.y - radius * 0.5f, center.z - radius * 0.5f};
Vector3 max = {center.x + radius * 0.5f, center.y + radius * 0.5f, center.z + radius * 0.5f};

Box b = {min, max};

return b;
}

Vector3 shade(HitInfo hitInfo) const
{
return material.shade(hitInfo);
}
}
So, Spheres need to have a Material, that decides how they are going to be drawn.
In the simplest case, my material can be:
class SimpleColor : Material
{
private Vector3 color;

this(ubyte r, ubyte g, ubyte b)
{
color.x = r / 255.0f;
color.y = g / 255.0f;
color.z = b / 255.0f;
}

Vector3 shade(HitInfo hitInfo) const
{
return color;
}
}
So, I chose red and green colours. Everything looks fine.
[img=http://[url%3Dhttp://i50.tinypic.com/35b7b49.png]http://i50.tinypic.com/35b7b49.png[/url]]



The next step was to make the shade function a bit more complicated:
Vector3 shade(HitInfo hitInfo) const
{
Vector3 l = {0, 1, 0}; // let's say that the light is at this position. (It's hardcoded for now - just for the test)
Vector3 lightVector = l - hitInfo.hitPoint; // vector from the hit point to the light

// TODO: fix
if( hitInfo.surfaceNormal.dot(lightVector) <= 0 )
{
return color * 0;
}
else
{
return color * hitInfo.surfaceNormal.dot(lightVector);
}
}
So I just return the colour multipled by the dot product of the surface normal and the light direction vector. What I get is this:
[img=http://[url%3Dhttp://i45.tinypic.com/29zeek1.png]http://i45.tinypic.com/29zeek1.png[/url]]

Are all those normals negative? Or am I doing something wrong? (I bet I am smile.png )
0

Share this post


Link to post
Share on other sites
I'm going to stick my neck out here, seeing as how I have never created a raytracer of any kind. What jumps out to me is that you are not normalizing either the normal or "lightVector".
0

Share this post


Link to post
Share on other sites
Think about what spaces l, lightVector and hitInfo.hitPoint are in -- it might be that you actually do want to do what you're doing, but it seems unlikely to me.

a normalized l implies you want directional lighting (e.g. l is essentially at infinity for all purposes, like light from the sun), but that you take a vector between l and hitPoint implies local point lighting which exists in the same coordinate space as hitPoint.

There are probably also normalization issues, and possibly signedness as well. Edited by Ravyne
0

Share this post


Link to post
Share on other sites
I didn't fully read your post, but seeing how the outline of your object is visible, but the inside isn't
A) Culling?
B) Inverted Normals?
I don't really know much about this are, but those seem to be the common issues
0

Share this post


Link to post
Share on other sites
[quote][CODE]hitInfo.surfaceNormal = (hitInfo.hitPoint - c) * 2; // is this correct?[/CODE][/quote]

That surface normal is incorrect. The correct one is:

[CODE]hitInfo.surfaceNormal = normalize(hitInfo.hitPoint - c);[/CODE]

Or, alternatively, to potentially trade some accuracy for speed:

[CODE]hitInfo.surfaceNormal = (hitInfo.hitPoint - c) / radius;[/CODE]

You also need to normalize the vector between the light source and the hitpoint, in your shading code.

Remember the cardinal rule of ray-tracing: if in doubt, normalize. The vectors which need to have a non-unit magnitude are few and far in between and are usually obvious. Edited by Bacterius
0

Share this post


Link to post
Share on other sites
[quote name='Bacterius' timestamp='1355448917' post='5010425']
[CODE]hitInfo.surfaceNormal = normalize(hitInfo.hitPoint - c);[/CODE]
[/quote]

Are you sure? That's what my book says (Fundamentals of Computer Graphics, 3rd edition)
0

Share this post


Link to post
Share on other sites
[quote name='sheep19' timestamp='1355486778' post='5010571']
[quote name='Bacterius' timestamp='1355448917' post='5010425']
[CODE]hitInfo.surfaceNormal = normalize(hitInfo.hitPoint - c);[/CODE]
[/quote]

Are you sure? That's what my book says (Fundamentals of Computer Graphics, 3rd edition)
[/quote]
I'm positive. I mean, assuming your hitPoint function does what I think it does. Edited by Bacterius
0

Share this post


Link to post
Share on other sites
[quote name='Bacterius' timestamp='1355487317' post='5010575']
[quote name='sheep19' timestamp='1355486778' post='5010571']
[quote name='Bacterius' timestamp='1355448917' post='5010425']
[CODE]hitInfo.surfaceNormal = normalize(hitInfo.hitPoint - c);[/CODE]
[/quote]

Are you sure? That's what my book says (Fundamentals of Computer Graphics, 3rd edition)
[/quote]
I'm positive. I mean, assuming your hitPoint function does what I think it does.
[/quote]

Actually, it's the same. I normalized them both and the result is the same.

OK, now I am normalizing the vectors (I had forgotten to do so), and things are better. Much better.

But there's a small problem:

[img]http://i49.tinypic.com/20a8hw2.png[/img]

I don't understand why this happens.

This is the new shade function:
[CODE]
Vector3 shade(HitInfo hitInfo) const
{
Vector3 l = {0, 1, 0}; // let's say that the light is at this position
Vector3 lightVector = l - hitInfo.hitPoint; // vector from the hit point to the light

Vector3 I = {1.0f, 1.0f, 1.0f}; // color of the light

lightVector.normalize();
hitInfo.surfaceNormal.normalize();

// TODO: fix
if( hitInfo.surfaceNormal.dot(lightVector) <= 0 )
{
return I * color * 0;
}
else
{
return I * color * hitInfo.surfaceNormal.dot(lightVector);
}
}
[/CODE]

By the way, this is the loop in which the rays are casted:
[CODE]
for(int x = 0; x < SCREEN_WIDTH; ++x)
{
for(int y = 0; y < SCREEN_HEIGHT; ++y)
{
//x = SCREEN_WIDTH / 2;
//y = SCREEN_HEIGHT / 2;

Vector3 p = {(x - SCREEN_WIDTH * 0.5f) / (SCREEN_WIDTH * 0.5f), (y - SCREEN_HEIGHT * 0.5f) / (SCREEN_HEIGHT * 0.5f), 0};
Ray r = {cameraPos, p - cameraPos};

HitInfo hitInfo;
Surface closestObject; // default initialized to null
float t = float.max;

foreach(obj; scene.objects)
{
if( obj.hit(r, 0.1f, 1000, hitInfo) && hitInfo.t < t ) // hit?
{
t = hitInfo.t;
closestObject = obj;
}
}

if( closestObject !is null )
{
Vector3 color = closestObject.shade(hitInfo);

writePixel(screen, x, SCREEN_HEIGHT - 1 - y, cast(ubyte)(color.x * 255), cast(ubyte)(color.y * 255), cast(ubyte)(color.z * 255));
}

//x = y = 1000;
}
}
[/CODE]

Oh, I forgot. Why do the spheres look like ovals? Edited by sheep19
0

Share this post


Link to post
Share on other sites
[quote name='sheep19' timestamp='1355489944' post='5010587']
Actually, it's the same. I normalized them both and the result is the same.
[/quote]
Yes, the vector you had before has the correct [i]direction[/i] (the [font=courier new,courier,monospace]hitpoint - c[/font] part), but the length was incorrect. That's why you need to normalize it (or, divide by the radius, which is equivalent if the hitpoint is on the sphere's surface). Normalizing just sets the vector's length to 1, leaving its direction unchanged, so if two vectors have the same direction but different lengths, normalizing them both will produce the same result.

For your problem, look closely at your intersection code for every object. Notice hitInfo is a storage variable, which is trashed at every ray-sphere intersection. The actual closest intersection results are located in the variables t and closestObject. You correctly identify that in the loop, but upon shading (at line 26) you make a mistake and use hitInfo (which now contains the intersection data for the last sphere intersected, which is not necessarily the closest) instead of the correct variable t. You want to keep track of the distance in its own HitInfo structure, instead of just the distance, since you need the other stuff (intersection point, normal, etc...) too for shading. So something like this:

[CODE]HitInfo current, closest;
Surface closestObject; // default initialized to null
closest.t = float.max;

foreach(obj; scene.objects)
{
if( obj.hit(r, 0.1f, 1000, current) && current.t < closest.t ) // hit?
{
closest = current;
closestObject = obj;
}
}[/CODE]

And then:

[CODE]Vector3 color = closestObject.shade(closest);[/CODE]


[quote]Oh, I forgot. Why do the spheres look like ovals?[/quote]
Aspect ratio. You are rendering your 1024x768 (or whatever) bitmap as if it was a square, with (x, y) coordinates between -1 and 1, which will stretch the spheres horizontally into ovals. To take into account aspect ratio, you need to either scale x and y to account for it, like so:

[CODE]float aspectRatio = (float)SCREEN_WIDTH / SCREEN_HEIGHT; // [w/h]

Vector3 p = {aspectRatio * (x - SCREEN_WIDTH * 0.5f) / (SCREEN_WIDTH * 0.5f), (y - SCREEN_HEIGHT * 0.5f) / (SCREEN_HEIGHT * 0.5f), 0};[/CODE]

This can be simplified but you get the idea. You can also contract the height instead of expanding the width, these are conventions depending on your definition of "aspect ratio". Not something you want to worry about at this point. Edited by Bacterius
1

Share this post


Link to post
Share on other sites

Hello again. I noticed that, when having two lights, it's like one of them over-writes the other's color (for the specular highlights). Here's what I mean:

 

20ku0kk.jpg

 

If I remove one of the two lights, it is shown correctly.

 

Here is my shade function. Is there anything wrong?

 

Vector3 shade(HitInfo hitInfo, ref Scene scene) const
	{
		Vector3 finalColor = Vector3(0.0f, 0.0f, 0.0f); // the final color
		
		// normalize the surface normal
		hitInfo.surfaceNormal.normalize();
		
		foreach(light; scene.lights)
		{
			Vector3 lightVector = light.position - hitInfo.hitPoint; // vector from the hit point to the light
		
			lightVector.normalize();
			
			HitInfo hitInfo2;
			Ray ray = {hitInfo.hitPoint, light.position - hitInfo.hitPoint};
			ray.d.normalize();
			
			if( !scene.trace(ray, hitInfo2, 0.1f) )
			{
				// diffuse shading
				if( hitInfo.surfaceNormal.dot(lightVector) > 0 )
				{
					finalColor = finalColor + light.I * color * hitInfo.surfaceNormal.dot(lightVector);

					hitInfo.ray = -hitInfo.ray;
					hitInfo.ray.normalize();
					
					// specular shading
					Vector3 H = (lightVector + hitInfo.ray) * 0.5f; // find the half vector, H
					H.normalize();
					
					float specularDotProduct = dot(hitInfo.surfaceNormal, H);
					
					if( specularDotProduct > 0.0f )
						finalColor = finalColor + light.I * std.math.pow(specularDotProduct, 10.0f);
				}
			}
			else
			{
				// no color is added, shadow is shown
			}
		}
		
		return finalColor;
	}

 

By the way, I have triangles now! I will write an .obj loader soon, and I will have 3D meshes!!! :)

Edited by sheep19
1

Share this post


Link to post
Share on other sites

You're modifying hitInfo at every light (it's declared outside the foreach loop), this will trash the calculations at the second light. Basically, it'd do the first light right, and then on the second light, it'll incorrectly flip the hitInfo.ray vector once again, messing up the specular term (and not the diffuse, as you observed, since it doesn't use that variable). The third light would come out right, since the vector is flipped in the right direction once again, the fourth one will be wrong, and so on.

 

Easiest way to fix it would be to just get a temporary HitInfo variable inside the loop's scope, and make it equal to your original hitInfo at the start of each iteration, though in this case you might also be better off just hoisting the vector flip outside the loop, though that's less flexible and might bite you later if you modify more of this variable.

 

 

By the way, I have triangles now! I will write an .obj loader soon, and I will have 3D meshes!!!

 

Nice! Though if you want to go into really complex meshes you'll need some sort of scene graph to accelerate ray-triangle intersections, like an octree or a bounding volume hierarchy, otherwise it's going to take forever! But that's for later smile.png

Edited by Bacterius
1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0