Archive for category PHP

PHP: Introduction

Now that we’ve learned some of the core software rendering concepts, it’s time to move onto some more advanced topics. We’re moving on to PHP because in order to teach blending, shading, lighting, etc it’s necessary to be able to use color. Games like Wolfenstein 3D, DooM and Quake had to deal with palettes since consumer level graphics cards tended to be limited to 256 colors. We’re not going to bother with palettes and instead will skip directly to true-color. Palettes are a problem seperate of what I’m trying to teach with these tutorials.

The goal of the PHP tutorials is to teach a number of ray tracing concepts starting with coloring vertices and then moving into lighting and texture generation. The end goal before moving to C++ is to be able to ray trace a scene.

Since PHP is a server side script these tutorials are going to contain only static images of the output. The source code will be in a text file and linked for you to download to run on your own system. In order to run the code you will need to download and install PHP from PHP.net and Apache from Apache.org. Use the latest version of PHP 4. PHP 5 is available but this code is not tested with it. If you don’t already have Apache installed I recommend using version 2.x since it’s a bit easier to setup. Once you have PHP properly installed (don’t forget to enable the GD graphics libary in the PHP.ini) simply download the tutorial code to anywhere Apache is serving up pages from and give it a .php extension. Then load it fire up your browser and point it at the tutorial on your local machine.

For Windows users you will need to download both the PHP installed and the ZIP package. Run the installer and then dump the contents of the zip file into the directory you installed PHP. Besides enabling the gd graphics library you will also need to set Register Globals to true. The PHP.ini file will be in your root Windows directory.

To enable the GD graphics library you will need to search for “php_gd2.dll” and then remove the semicolon from the beginning of the line. To enable globals (example: lesson17.php?debug=1) you will need to find “register_globals” and set it to “on.”

To enable PHP in Apache you will need to add the following two lines to your http.conf file.

1
2
   LoadModule php4_module "c:/php/sapi/php4apache.dll"
   AddType application/x-httpd-php .php

Make sure the path matches where the DLL is located on your system. Restart Apache and you should be good to go.

No Comments

PHP Lesson 22: The Z-Buffer


Get the Source

Setting up a z-buffer is a pretty trivial task. Since we’re ray tracing we don’t have to worry about objects behind the camera so we can base the z value on the distance from the eye to the point being rendered without having to worry about negative values. The first thing we do then is define our z-buffer.

1
2
3
4
5
6
7
8
9
10
11
	$zbuffer = array();
 
	for ($y=0; $y<$screenH; $y++)
	{
	   array_push($zbuffer,array());
	   for ($x=0; $x<$screenW; $x++)
	   {
		   array_push($zbuffer[$y],array());
		   $zbuffer[$x][$y]=-1;
	   }
	}

You can see that it’s exactly the same as the code used to intialize the video memory. Except that now we set the default value to a negative number. This is so we know if the value in the buffer is a calculated value or just the default. Next up we modify the PutPixel function to take a z value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	function PutPixel($x,$y,$z,$c)
	{
		global $screenH, $screenW, $screen, $zbuffer, $debug;
 
		//first thing is to make sure the
		//pixel is within range of the screen
		if($x<0 || $x>$screenW)
			return;
		if($y<0 || $y>$screenH)
			return;
 
		//make sure x and y are integers
		$x=floor($x);
		$y=floor($y);
 
		if($zbuffer[$x][$y] ==-1 || $z<$zbuffer[$x][$y])
		{
			$screen[$x][$y] = $c;
			$zbuffer[$x][$y] = $z;
		}
	}

The only change here is a check for the passed in z value compared to the z value in the buffer. We only want to store the pixel if the z value is smaller than the current value or the current value is -1 in which case this is the first pixel being plotted at that location. PHP doesn’t have variable types but in C++ if we used an unsigned int we would have a maximum depth of over 4 billion pixels. This should be plenty.

The final step is to define what the z value is. This snippet of code is from the draw routine.

1
2
	$z = sqrt(($cop-$P->z)*($cop-$P->z)+(160-$P->x)*(160-$P->x)+(120-$P->y)*(120-$P->y));
	PutPixel($x, $y,$z,RGB($r,$g,$b));

All we’re doing is getting the distance from the eye to the point on the sphere being rendered. We then pass that into the PutPixel function as the z value. The final result is that only the pixels closest to the eye are shown to the user. Looking at the render loop you can see how ray tracing gets computationally expensive very fast.

1
2
3
4
5
6
7
	$m = sizeof($lights);
	$s = sizeof($spheres);
 
	for($x=0;$x<$screenW;$x++)
	for($y=0;$y<$screenH;$y++)
	for($k=0;$k<$s;$k++)
	{

$s is the number of spheres. The total number of rays we have to shoot is screenH * screenW * number of spheres. And for every intersection we have to check every light in the scene. PHP is far from suited for doing this many calculations in real time. Over at SlimeLand you can find a more feature rich ray tracer written entirely in JavaScript. It’s able to do complex scenes and shadows.

It took 2 hours and 18 minutes of render time to complete the flower scene on a 1200mhz system. This is just a tad too slow. JavaScript and PHP are great for teaching the core concepts since there’s minimal code that isn’t directly related to what you’re trying to teach. Unless, like the above ray tracer, you opt to use Dynamic HTML in order to use color which results in a mess of code just to “plot” a pixel. With C++ you have to deal with libraries and headers which can overly complicate things. But now it’s time to move on to more advanced topics and for that we need speed.

No Comments

PHP Lesson 21: Intro to Ray Tracing


Get the Source

Anti-Planet is a first person shooter that uses Ray Tracing for all of its graphics. As you can see in the below image, everything is made up of spheres.

You may be wonder why it’s all spheres. Well the reason is that the sphere is the easiest shape to ray trace. Anti-Planet requires a very fast processor and a very fast graphics card (which it treats as a second CPU) in order to run smoothly. The net result is quite amazing. If the author had allowed for more complex shapes, Anti-Planet would require even more power to run in real time.

The theory behind ray tracing is simply that you pick a position for the eye and then you shoot a number of rays from the eye to the view area and see what the ray intersects with. Unlike ray casting which only shoots out a single row of rays, ray tracing shoots out a ray for every pixel you see. So if the view area is 320×240, you shoot out 76,800 rays and test the ray against every object in the scene. Ray casting would only require 320 rays be shot out. But, ray casting only allows you to rotate on one axis. Ray tracing allows you to rotate on all three axis.

The first thing we’ve added to our program is the cSphere class. Below is the list of variables we will be using contained in the class.

1
2
3
4
	class cSphere
	{
		var $x, $y, $z;
		var $radius;

As you can see, all we need to know is where it’s at and how big it is. The next step is to add a function that when passed a ray, will return the point on the sphere that the ray intersects with, if there is an intersection. To give you a visual on what math we are doing, here is a diagram showing what each of the variables will represent.

E is where our eye located, V is the unit vector of the ray, which in this case happens to intersect the sphere at point P. O is the center of the sphere. We know, E, V and O. What we need to find is P. Not so surprisingly, we use the dot product to solve this problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
	function ray_sphere($x,$y,$z,$x2,$y2,$z2)
	{
		$V = new point();
		$EO = new point();
 
		$V->x = $x2 - $x;
		$V->y = $y2 - $y;
		$V->z = $z2 - $z;
		$dist = sqrt($V->x * $V->x + $V->y * $V->y + $V->z * $V->z);
 
		$V->x/=$dist;
		$V->y/=$dist;
		$V->z/=$dist;
 
		$EO->x = $this->x - $x;
		$EO->y = $this->y - $y;
		$EO->z = $this->z - $z;
 
		$v = $EO->x * $V->x + $EO->y * $V->y + $EO->z * $V->z;
 
		$disc = $this->radius * $this->radius -
				($EO->x * $EO->x +
				 $EO->y * $EO->y +
				 $EO->z * $EO->z - $v*$v);
 
		if($disc>=0)
		{
			$d = sqrt($disc);
			$P = new point();
 
			$P->x = $x + ($v-$d)*$V->x;
			$P->y = $y + ($v-$d)*$V->y;
			$P->z = $z + ($v-$d)*$V->z;
 
			return $P;
		}
		return 0;
	}

This function is contained in the sphere class. The first thing we do is find the unit vector V. x,y,z and x2,y2,z2 define two points on our ray. From that we derive the unit vector V. Next up we calculate the difference from the center of the sphere to the eye point. We then find the dot product of that vector and V. This gives us the distance from E to the point which gives us the large right triangle. $dist is the distance from that point to P squared. If it’s less than 0 then that means that the ray doesn’t intersect with the sphere so we return 0. If disc is greater than or equal to 0 then we determine the x,y and z coordinates of the P and return that.

You may remember that in the Wolf 5K ray caster we defined each ray by an angle. For now we’re using a simpler method. We set the eye point to the center of the screen and then move it backwards on the z axis to adjust our center of perspective.

1
2
3
4
5
6
7
8
9
	for($x=0;$x<$screenW;$x++)
	for($y=0;$y<$screenH;$y++)
	{
		//define the color of the pixel
		$r=255;
		$g=255;
		$b=255;
 
		$P = $sphere->ray_sphere(160,120,$cop,$x,$y,0);

We then use the center of the screen and the cop as the starting point of the ray and the x,y position with a depth of 0 for our second point on the ray. Once we start moving around and rotating the view this will get slightly more complicated. If $P is not zero we then plot the point using the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	//apply lighting
	$lr=0;
	$lg=0;
	$lb=0;
	$a = $sphere->normal_at($P->x,$P->y,$P->z);
	for($j=0;$j<$m;$j++)
	{
		$q = $lights[$j]->getIntensityAtAngle($P->x,$P->y,$P->z,$a);
		$lr += $lights[$j]->r*$q;
		$lg += $lights[$j]->g*$q;
		$lb += $lights[$j]->b*$q;
	}
 
	$r=$lr/255.0*$r;
	$g=$lg/255.0*$g;
	$b=$lb/255.0*$b;
 
	PutPixel($x, $y,RGB($r,$g,$b));

Just like before we calculate the lighting at the point except instead of calculating the point on the screen, we calculate based on the point of intersection. And since we’re using the normal to generate more realistic lighting, we call teh normal_at() function contained in the sphere class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	function normal_at($x,$y,$z)
	{
		$a = new point();
 
		$a->x = $x-$this->x;
		$a->y = $y-$this->y;
		$a->z = $z-$this->z;
 
		$a->d = sqrt($a->x * $a->x + $a->y * $a->y + $a->z * $a->z);
 
		$a->x/=$a->d;
		$a->y/=$a->d;
		$a->z/=$a->d;
 
		return $a;
	}

The normal of any point on a sphere is found simply by finding the unit vector from the point on the sphere to the center of the sphere. It doesn’t get much simpler than this. Now that we have the normal for the point and the location of the point we can apply lighting just like we’ve done before.


Lit Sphere Without the Normal

If we didn’t take the normal into consideration we’d have light wrapping around the sphere as you can see in the above screenshot.

And that concludes this tutorial. You now know how to ray trace a sphere and apply normal based lighting to it. In the final PHP 3D tutorial I will show you how to implement a z-buffer and render multiple spheres using it.

No Comments

PHP Lesson 19: The Normal and Lighting


Get the Source

The first change to this lesson is the addition of another class. The point class.

1
2
3
4
	class point
	{
		var $x,$y,$z;
	}

This just gives us an easy way to define points and pass them around. Is this lesson we aren’t defining an actual surface. We’re just using the cosine function to create the illusion of a wavy surface with the help of lighting and normals which really helps to drive in the point that lighting makes a huge impact on how your graphics look.

For every pixel of our surface we’re going to calculate three points. You’ll also notice that every pixel is pure white.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	//define the color of the pixel
	$r=255;
	$g=255;
	$b=255;
 
	//calculate the location of the point and two neighbors
	$v2->x = $x;
	$v2->y = $y;
	$v2->z = cos($v2->x*5.0*$rad)*10.0;
 
	$v1->x = $x+0.001;
	$v1->y = $y;
	$v1->z = cos($v1->x*5.0*$rad)*10.0;
 
	$v3->x = $x;
	$v3->y = $y+0.001;
	$v3->z = cos($v3->x*5.0*$rad)*10.0;

If you have a more complex surface you really should pick four neighbors. But for our purposes it’s a mathematically generated surface so we can simplify a bit. $v2 is the current pixel, $v1 is slightly to the right and $v3 is slightly above. The reason for this is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	//calculate the two vectors
	$v->x = $v1->x - $v2->x;
	$v->y = $v1->y - $v2->y;
	$v->z = $v1->z - $v2->z;
 
	$w->x = $v2->x - $v3->x;
	$w->y = $v2->y - $v3->y;
	$w->z = $v2->z - $v3->z;
 
	//find the cross product
	$n->x = $v->y*$w->z - $w->y*$v->z;
	$n->y = $v->z*$w->x - $w->z*$v->x;
	$n->z = $v->x*$w->y - $w->x*$v->y;
 
	//normalize
	$d = sqrt($n->x * $n->x + $n->y * $n->y + $n->z * $n->z);
	$n->x/=$d;
	$n->y/=$d;
	$n->z/=$d;

We’re finding the two vectors created by the three points. Then we find the cross product which gives us the normal. The cross product is perpendicular to the two vectors. The normal is a vector which has a length of one. So to “normalize” the vector we divide each of the components by the length of the vector. Next we apply the lighting.

1
2
3
4
5
6
7
8
9
10
11
12
	//apply lighting
	$lr=0;
	$lg=0;
	$lb=0;
 
	for($j=0;$j<$m;$j++)
	{
		$q = $lights[$j]->getIntensityAtAngle($x,$y,$v2->z,$n);
		$lr += $lights[$j]->r*$q;
		$lg += $lights[$j]->g*$q;
		$lb += $lights[$j]->b*$q;
	}

It’s exactly the same as the previous lesson except now we’re calling getIntensityAtAngle() which takes an additional parameter: the normal of the point we are lighting. Let’s take a look at this new function we have defined in our light class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	function getIntensityAtAngle($x=0,$y=0,$z=0,$normal)
	{
		$dist = sqrt(($this->x-$x)*($this->x-$x) +
				($this->y-$y)*($this->y-$y) +
				($this->z-$z)*($this->z-$z) );
		if($dist>=1.0)
			$intensity = $this->i/$dist;
		else
			$intensity = 1.0;
 
		$a = new point();
		$a->x = $this->x-$x;
		$a->y = $this->y-$y;
		$a->z = $this->z-$z;
 
		$a->d = sqrt($a->x * $a->x + $a->y * $a->y + $a->z * $a->z);
 
		$a->x/=$a->d;
		$a->y/=$a->d;
		$a->z/=$a->d;
 
		$angle = ($a->x*$normal->x + $a->y*$normal->y + $a->z*$normal->z);
 
		if($angle<=0)
			return 0;
 
		return $intensity * $angle;
	}

The first thing we do is find the distance to the point from the location of the light source. This isn’t anything new. However, now we find the normal of the ray of light going towards the point. If a ray of light is parallel to a surface it doesn’t illuminate it. If a ray of light hits a surface directly then it’s at the maximum brightness. In this case the maximum brightness is defined by the intensity as adjusted for distance. The final step is to find the angle between the two vectors. If the vectors are perpendicular then $angle will be 1. If they are parallel then the angle will be 0. If the vectors are perpendicular then that means the surface is facing the ray of light directly which means maximum intensity. If the angle is negative then that means that the surface is facing away from the light so no light is shining on it.

So now you know the math behind finding the normal of a plane as defined by two vectors and how to compare that normal to a ray of light. So now, let’s see what a difference it makes when lighting a surface. The first image is without taking the normals into account. The second is with normals taken into account.


Without Normals


With Normals

You can see what a huge difference it makes in the realism of light shining on a wavy surface. The surfaces facing away from the light are shadowed much deeper than when simply taking distance into account. The image you saw at the beginning is simply using three colored lights and applying the normal.

This is as advanced as we’re going to get with lighting. At least in PHP. Since PHP can’t be used to do real time graphics I’m going to move quickly through the core concepts of ray tracing so we can get into real time ray tracing using C++. There we can tackle more advanced concepts since I think it will be more interesting to see these things in action rather than just still images.

No Comments

PHP Lesson 18: The basics of lighting


Get the Source

The basic principle of lighting is that the more light you have the brighter an object is. In terms of 3D there are two basic types of lighting: per vertex and per pixel. Per vertex lighting simply means that the light is applied only to the vertex. Say for example that in the previous tutorial the triangle was white. And then we apply a red light to it. With per vertex lighting the value of the light would be calculated only at each vertex and then just like in the previous tutorial, the entire triangle would be colored. With per pixel lighting, the amount and color of the light would be calculated at every pixel in the triangle. Per vertex is an okay approximation used commonly in video games when rendering speed is more important than realism. Per pixel lighting is being used more and more often as graphics hardware and PCs get faster. It’s possible to generate much more realistic scenes.

First lets examine some images of a couple light sources. One has a constant intensity, the other increases in intensity.



You can see that as the light source on the right becomes brighter, the left side also becomes brighter. In these graphics we’re applying a bluish light to a white surface. Now lets take a look at the lights that we will be applying to a more colorful surface.

This is what the lights would look like if we applied them to a white surface. The brighter the color, the closer to the light source the pixel is. Now, let’s take a look at the texture we will be applying these lights to.

The texture looks pretty bland. Now when we apply the two together, we get the final composite image you saw at the top of this lesson.

The first step to being able to apply lighting is to create a light class. Object orientated programming makes things much easier. Using classes, we’ll be able to contain everything that needs to be known about a light source in a single variable type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
	class light
	{
		var $x;
		var $y;
		var $z;
		var $r;
		var $g;
		var $b;
		var $i;
 
		function light()
		{
			$this->setPos();
			$this->setCol();
			$this->setIntensity();
		}
 
		function setPos($px=0,$py=0,$pz=0)
		{
			$this->x=$px;
			$this->y=$py;
			$this->z=$pz;
		}
 
		function setCol($pr=0,$pg=0,$pb=0)
		{
			$this->r=$pr;
			$this->g=$pg;
			$this->b=$pb;
		}
 
		function setIntensity($pi=0)
		{
			$this->i = $pi;
		}
 
		function getIntensityAt($x=0,$y=0,$z=0)
		{
			$dist = sqrt(($this->x-$x)*($this->x-$x) +
					($this->y-$y)*($this->y-$y) +
					($this->z-$z)*($this->z-$z) );
			if($dist>0)
				return $this->i/($this->i+$dist);
			return 1.0;
		}
	}

We’re working with a flat surface but the light class is ready to go to be used in three dimensions. The first bit of code simply tells PHP what variables the class will contain. They are prepended with the “var” keyword and are not set to any value. The light function is the constructor which is called whenever the class is instantiated. It in turn calls the three functions that define the light source. No parameters are specified as the functions all have default parameter values. In our case everything is set to zero.

The interesting part of this class is the getIntensityAt function. The first thing we do is find the distance from the light source to the location we pass in. If that distance is less than or equal to the intensity we return 1.0 which is maximum intensity. If the distance is greater than 0 we divide it by the distance added to the intensity and return that instead. The farther away from the light source the less the intensity is. This formula should look familiar. It’s the same one used to determine the perspective multiple based on z and the center of perspective.

Now that we have a class that can do the math to determine the intensity of the light at a point we need to initialize a light source or few.

1
2
3
4
5
	$lights = array();
	$lights[] = new light();
	$lights[0]->setPos(0,120,-100);
	$lights[0]->setCol(0,0,255);
	$lights[0]->setIntensity(50);

Since we intend to have multiple lights we define our $lights variable to be an array. We then use empty brackets which indicates we’re adding a new object to the end of the array and add a new light. We then reference it by number and define it’s position, color and intensity. We’re not dealing with scale or anything so you’ll just need to play with intensity and see how it looks.

Now that we’ve defined our light class and set up light, it’s time to draw the scene and apply the lights to our generated texture.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
	function draw()
	{
		global 	$screenH, $screenW, $screen,
				$rad, $lights, $debug;
 
		$r=0;
		$g=0;
		$b=0;
		$m = sizeof($lights);
 
		for($x=0;$x<$screenW;$x++)
		for($y=0;$y<$screenH;$y++)
		{
			$r=(sin($x*5.0*$rad)+1)*255.0/2.0
				+(cos($y*5.0*$rad)+1)*255.0/2.0;
			$g=(sin($x*5.0*$rad)+1)*255.0/2.0
				+(sin($y*5.0*$rad)+1)*255.0/2.0;
			$b=(cos($x*5.0*$rad)+1)*255.0/2.0
				+(cos($y*5.0*$rad)+1)*255.0/2.0;
 
			$lr=0;
			$lg=0;
			$lb=0;
 
			for($j=0;$j<$m;$j++)
			{
				$q = $lights[$j]->getIntensityAt($x,$y,0);
				$lr += $lights[$j]->r*$q;
				$lg += $lights[$j]->g*$q;
				$lb += $lights[$j]->b*$q;
			}
 
			$r=$lr/255.0*$r;
			$g=$lg/255.0*$g;
			$b=$lb/255.0*$b;
 
			$screen[$x][$y]=RGB($r,$g,$b);
		}
 
	}

The initial $r, $g, and $b values generate the original unlit texture. You could set those to anything. $lr, $lg and $lb are the lightmap values at the current pixel. $m stores the number of lights we have and then for every pixel we run through all the lights. The first thing we do is grab the intensity of the light at our current position. We have a flat surface so we just use 0 for the z value. The next thing we do is add the component colors of the current light multiplied by the intensity of the light to the total light values. It’s quite possible that you will end up with a value greater than 255 for any of the component colors.

Now that we have the color of the texture and the color of the light we combine the two. We first divide the component color of the light source by 255 to get a value from 0 to possibly infinity if you were to have that many lights. We then multiply the result by the component color of the texture. And that’s it. We have successfully applied light to a pixel.

So what happens if a component color from the combined light is greater than 255? You may recall that the RGB function caps the r,g, and b values at 255. So in our case if a component color is greater than 255 we render it with the value 255. If you wanted to be clever you could take the run off value and apply it to the other two component values. In that case you would see that as the light becomes overly bright it would start turning the pixel to white regardless of the actual color of the light.

And that concludes this tutorial. You know know the math behind basic lighting. In the next tutorial you’ll learn how to apply basic shadows.

No Comments

PHP Lesson 17: Getting Started with PHP


Get the Source

The first thing we need to do is get our global variables set up.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	if(!isset($debug))
		$debug=0;
 
	//***************
 
	//what size we want out screen to be
	$screenW = 400;
	$screenH = 400;
 
 
	//********************
	//code to initialize the
	//screen memory
	//********************
	$screen = array();
 
	for ($y=0; $y<$screenH; $y++)
	{
	   array_push($screen,array());
	   for ($x=0; $x<$screenW; $x++)
	   {
		   array_push($screen[$y],array());
		   $screen[$x][$y]=RGB(64,128,255);
	   }
	}

$debug will tell our code if we want to display the final graphic or the text debug information. You’ll see what we can’t have both later. The next thing is the size of our screen. The larger the screen the longer it’s going to take to display the final result. At 400×400 we’re drawing 16,000 pixels. The final part actually intializes the screen to a background color. RGB is a custom function which will be explained later. Since we’re only rendering one image we don’t need to bother with a function to clear the screen later on.

The next step is to set up the main function. This is a very basic tutorial so it contains only a single line.

1
2
3
4
	function main()
	{
		draw();
	}

Next in line is the function to plot a pixel. We’re back to two dimensions so we only care about x,y and the color of the pixel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	function PutPixel($x,$y,$c)
	{
		global $screenH, $screenW, $screen;
 
		//first thing is to make sure the
		//pixel is within range of the screen
		if($x<0 || $x>$screenW)
			return;
		if($y<0 || $y>$screenH)
			return;
 
		//make sure x and y are integers
		$x=floor($x);
		$y=floor($y);
 
		$screen[$x][$y] = $c;
	}

You may notice that this is identical to the JavaScript version. This is because JavaScript uses pretty much the same syntax. The only difference is that in PHP variables must have a $ sign in front of them. The next important piece of code is the RGB function.

1
2
3
4
5
6
7
8
9
10
11
	function RGB($r,$g,$b)
	{
		if($r>255)
			$r=255;
		if($g>255)
			$g=255;
		if($b>255)
			$b=255;
 
		return floor($r%256 + ($g%256) *256 + ($b%256) * 256 * 256);
	}

To my knowledge PHP has no function to convert individual r,g,b values into a universal int. You have to use imagecolorallocate which I don’t imagine is as simple as the function I use. The problem is that PHP has to allocate color in relation to the image which it will be used with. Since we’re defining our own video memory we can use a simpler function. Another disadvantage of not being able to define variable types is that if you don’t use the exact above code you end up with problems from PHP treating the component colors like floats which screws up the final result.

Next up is the draw routine which, just like in the JavaScript tutorials, defines what is to be rendered.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
	$pointX = array(-100, -100, 100);
	$pointY = array(-100,  100, 100);
 
	$numPoints = 3;
 
	$cameraX = 5;
	$cameraY = 5;
 
	$angle = 30;
 
	$rad = 3.14 / 180.0;
 
	function draw()
	{
		global 	$screenH, $screenW, $screen, $numPoints,
				$cameraX, $cameraY, $pointX, $pointY, $rad, $angle;
 
		$px = array();
		$py = array();
		for($j=0;$j<$numPoints;$j++)
		{
			//rotate the points
			$px[] = ($pointX[$j]-$cameraX) * cos($angle*$rad)
					- ($pointY[$j]-$cameraY) * sin($angle*$rad);
			$py[] = ($pointX[$j]-$cameraX) * sin($angle*$rad)
					+ ($pointY[$j]-$cameraY) * cos($angle*$rad);
		}
 
		//draw the filled triangle
		DrawTriangleFilled(	$screenW/2+$px[0],$screenH/2-$py[0],RGB(0,0,255),
					$screenW/2+$px[1],$screenH/2-$py[1],RGB(0,255,0),
					$screenW/2+$px[2],$screenH/2-$py[2],RGB(255,0,0)
					);
	}

You’ll notice the global keyword. This tells PHP that we’re using variables defined outside this function. Then, just like in the JavaScript tutorials we position and rotate based on the camera and then we’re going to render a filled triangle with colored vertices.

This function starts out identical to all the other DrawTriangle functions except that now we also pass in a color value for each of the corners. We also removed the check for the longest side because it’s really not necessary. It just results in drawing the fewest possible scanlines. But it has no impact on the number of pixels we will be drawing. We’ve also changed how we calculate the area of the triangle. Previously we were using a more complicated forumla which required finding the length of all the sides. Now we are simply using the cross product of two vectors defined by two sides of the triangle. We get the absolute value for h because it must be a positive number but we leave the area alone. You’ll see why later. Area is equal to a * h / 2 where a is the length of one of the sides. We then simply solve for h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	function DrawTriangleFilled(	$x1,$y1,$c1,
					$x2,$y2,$c2,
					$x3,$y3,$c3)
	{
		global $debug;
 
		$dist3 = sqrt(($x3-$x1)*($x3-$x1)+($y3-$y1)*($y3-$y1));
 
		$area = ($x2 - $x1) * ($y3 - $y1) - ($x3 - $x1) * ($y2 - $y1);
		$h = abs(2 * $area / $dist3);
 
		$px1=$x1;
		$px2=$x3;
		$py1=$y1;
		$py2=$y3;
 
		$pc1 = $c1;
		$pc2 = $c2;
		$pc3 = $c3;
 
		$dx1 = ($x2-$x1)/$h;
		$dx2 = ($x2-$x3)/$h;
		$dy1 = ($y2-$y1)/$h;
		$dy2 = ($y2-$y3)/$h;

Once we’ve calculated everything we need to know it’s time to move on to actually rendering the triangle.

Using OpenGL it’s possible to see how it’s supposed to work.

As you can see, the gradient goes from the color of the vertex to 0 perpendicular to the opposite side. At the vertex, it is 100% the color of the vertex. At the opposite side it is 0% of the color of the vertex. This is true for all three vertices. When combined, at any given point the total percentage from all three vertices is 100%. At dead center when using pure red, green and blue it will be gray since it will be 33.33% red, 33.33% green and 33.33% blue. Now, moving on to how I’ve implemented the render function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
	for($j=0;$j<$h;$j++)
	{
		$dist = sqrt(($px2-$px1)*($px2-$px1)+($py2-$py1)*($py2-$py1));
		if($dist>0)
		{
			$px=$px1;
			$py=$py1;
			$dx = ($px2-$px1)/$dist;
			$dy = ($py2-$py1)/$dist;
 
			for($k=0;$k<$dist;$k++)
			{
				//barycentric coordinates, sum always equal to 1
				$colp1  = (($x1 - $px) * ($y2 - $py) -
						($x2 - $px) * ($y1 - $py)) / $area;
				$colp3  = (($x2 - $px) * ($y3 - $py) -
						($x3 - $px) * ($y2 - $py)) / $area;
				$colp2  = (($x3 - $px) * ($y1 - $py) -
						($x1 - $px) * ($y3 - $py)) / $area;
 
				$r = $colp1 * ($pc1%256);
				$g = $colp1 * (($pc1/256)%256);
				$b = $colp1 * (($pc1/256/256)%256);
 
				$r += $colp2 * ($pc2%256);
				$g += $colp2 * (($pc2/256)%256);
				$b += $colp2 * (($pc2/256/256)%256);
 
				$r += $colp3 * ($pc3%256);
				$g += $colp3 * (($pc3/256)%256);
				$b += $colp3 * (($pc3/256/256)%256);
 
				PutPixel($px,$py,RGB($r,$g,$b));
				$px+=$dx;
				$py+=$dy;
			}
 
			$px1+=$dx1;
			$px2+=$dx2;
			$py1+=$dy1;
			$py2+=$dy2;
		}
	}

It’s again identical to the filled triangle tutorial except now we’re calculating a color percentage based on where we are in the triangle using barycentric coordinates. If you consider a point inside a triangle you can draw a line from each of the vertices to that point to create three smaller triangles. The barycentric value is the area of the smaller triangle divided by the total area of the triangle. You then simply use the percentage to determine the amount of color from the corresponding vertice to apply to the current pixel location. The reason for leaving the total area as negative if that’s how it turns out is that if the total area is negative, all the smaller areas will also be negative and the net result is a positive number.

The next chunk of code calculating r,g, and b simply breaks the composite value passed into the function into it’s core components, applies the percentage and then PutPixel calls RGB to put the values back into a single integer.

And finally we take our video buffer and show it to the user. This is all PHP specific stuff.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	main();
 
	if($debug==0)
	{
		// Date in the past
		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
		// always modified
		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
 
		// HTTP/1.1
		header("Cache-Control: no-store, no-cache, must-revalidate");
		header("Cache-Control: post-check=0, pre-check=0", false);
 
		// HTTP/1.0
		header("Pragma: no-cache");
		Header("Content-type: image/png");
 
		$im = ImageCreateTrueColor($screenW,$screenH);
 
		for($y=0;$y<$screenH;$y++)
		for($x=0;$x<$screenW;$x++)
		{
				$c = imagecolorallocate($im,
					floor($screen[$x][$y])%256,
					floor($screen[$x][$y]/256.0)%256,
					floor($screen[$x][$y]/256.0/256.0)%256);
				Imagesetpixel($im,$x,$y,$c);
		}
 
		Imagejpeg($im);
		ImageDestroy($im);
	}

Since the debug information appears before the header information is sent, it isn’t possible to display both the image and the debug output at the same time. The header information prevents the browser from caching the image so if you change something you don’t have to worry about the changes not showing up. $im is PHPs image buffer. We define it to be True Color. We then simply loop through the video buffer and plot all the pixels on the image and then pass it to the browser for the user to see.

As you can see, there is a lot of redundancy with coverting back and forth between component and compsite colors which greatly slows things done. But it doesn’t matter because we’re not trying to do anything real-time.

And this concludes the first lesson. The only purpose of even attempting colored vertices is that most first OpenGL tutorials do the same thing. Except in OpenGL this is all it takes to draw a colored triangle once everything is properly intialized:

1
2
3
4
5
6
7
8
9
10
	glBegin (GL_TRIANGLES);
		glColor3ub(0, 0, 255);
		glVertex3f(0, 1, 0);
 
		glColor3ub(0, 255, 0);
		glVertex3f(-1, 0, 0);
 
		glColor3ub(255, 0, 0);
		glVertex3f(1, 0, 0);
	glEnd();

No Comments