26
Tags:
c++
Skrevet af
Bruger #1474
@ 11.08.2008
TGA KlasseVores lille program vil blive en simpel konsol. Konsol programmer har ingen grafisk brugerflade og kan derfor kun vise tekst. Det gør heldigvis ikke noget, for vi vil gemme de renderede pixels i et billedformat og til sidst gemme billedet til harddisken. Billedet kan derefter vises i et billedbehandligsprogram. Det er ganske almindelig at bruge konsol programmer når man skriver renderings software, da det giver den mulighed at man kan forstørre billedet for at studere nærmere detaljer. Vi vil derfor skrive en klasse der kan både gemme og indlæse et kendt billedformat. Indlæsningen vil blive brugt til senere brug, når vi vil lægge tekstur (texture) oven på vores 3D objekter. Der findes mange forskellige billedformater som sagtens kan anvendes til dette formål. Jeg har valgt at bruge TGA formatet, fordi dette er en ukomprimeret format der kan behandles på alle platforme.
TGA klassen minder meget om mange andre klasser af samme art. Den kan som sagt indlæse en TGA fil fra harddisken og skrive til en TGA fil. Der er dog et par små funktioner der er tilføjet. Alle pixels i et digitalt billede bliver normalt gemt i bytes. Det vil sige, at farve skalaen for et billede vil ligge fraogmed 0 tilogmed 255. Når vi beregner farver i forbindelse med en rendering, vil vi gerne bruge en større og mere nøjagtig skala. Vi vil også gerne havde muligheden for at gå ud over den almindelige farve skala. Det færdige resultat vil dog tilsidst blive gemt i bytes. Da komponenterne X, Y, Z og W i vores vektor klasse er af float typen: double, hvorfor så ikke bruge samme type for vores farver! Vi vil derfor gerne kunne tilskrive og aflæse pixels i TGA klassen, som om de var af float typen: double. Vi kunne sagtens gøre dette udenfor klassen, men for at undgå at havde samme kode skrevet mange forskellige steder, skriver vi det ind i vores klasse.
Der er en anden funktion i TGA klassen der er forskellig fra en almindelig billede klasse. Vi har muligheden for at aflæse en pixel ved hjælp af UVW eller tekstur (texture) koordinater. Normalt vil man aflæse en pixel ved at angive indekset for den ønskede pixel eller ved at angine X og Y koordinaterne. Vi vil få brug for denne funktion når vi vil skal til at rendere tekstur (texture) på vores geometri. Funktionen hedder GetPixel og er en overload til de mere almindelige funktioner af samme navn. TGA Klassen vil komme til at se således ud:
//Den officelle TGA fil header!
struct FILEHEADER
{
unsigned char IdentSize;
unsigned char ColorMapType;
unsigned char ImageType;
unsigned short ColorMapStart;
unsigned short ColorMapLength;
unsigned char ColorMapBits;
unsigned short XStart;
unsigned short YStart;
unsigned short Width;
unsigned short Height;
unsigned char Bits;
unsigned char Descriptor;
};
#include <stdio.h>
#include <stdlib.h>
#include "RGBA.h"
#include "UVW.h"
//Vores special designet TGA klasse!
class TGAFILE
{
public:
FILEHEADER Header;
unsigned char * Pixels;
TGAFILE( void );
TGAFILE( const unsigned short Width, const unsigned short Height, const bool AlphaChannel );
~TGAFILE( void );
unsigned short GetWidth( void );
unsigned short GetHeight( void );
bool AlphaChannel( void );
bool SetPixel( const unsigned short X, const unsigned short Y, const unsigned char Red, const unsigned char Green, const unsigned char Blue, const unsigned char Alpha );
bool SetPixel( const unsigned short X, const unsigned short Y, const double Red, const double Green, const double Blue, const double Alpha );
bool SetPixel( const unsigned short X, const unsigned short Y, RGBA & Color );
RGBA GetPixel( const unsigned short X, const unsigned short Y );
RGBA GetPixel( const unsigned long I );
RGBA GetPixel( UVW & Coord, const bool BilinearFilter );
bool Load( const char * Filename );
bool Save( const char * Filename );
};
#include "TGA.h"
TGAFILE::TGAFILE( void )
{
Header.Width = 0;
Header.Height = 0;
Header.Bits = 0;
Pixels = 0;
}
TGAFILE::TGAFILE( const unsigned short Width, const unsigned short Height, const bool AlphaChannel )
{
Header.Width = Width;
Header.Height = Height;
if ( AlphaChannel )
{
Header.Bits = 32;
Pixels = new unsigned char [ Width * Height * 4 ];
for ( int I = 0; I < Width * Height * 4; I++ )
Pixels[ I ] = 0;
}
else
{
Header.Bits = 24;
Pixels = new unsigned char [ Width * Height * 3 ];
for ( int I = 0; I < Width * Height * 3; I++ )
Pixels[ I ] = 0;
}
}
TGAFILE::~TGAFILE( void )
{
if ( Pixels != 0 )
delete [] Pixels;
}
unsigned short TGAFILE::GetWidth( void )
{
return Header.Width;
}
unsigned short TGAFILE::GetHeight( void )
{
return Header.Height;
}
bool TGAFILE::AlphaChannel( void )
{
if ( Header.Bits == 32 )
return true;
else
return false;
}
RGBA TGAFILE::GetPixel( const unsigned short X, const unsigned short Y )
{
if ( X < Header.Width && Y < Header.Height )
{
switch ( Header.Bits )
{
case 32: {
int I = ( Y * Header.Width + X ) * 4;
return RGBA( Pixels[ I + 2 ] / 255.0, Pixels[ I + 1 ] / 255.0, Pixels[ I + 0 ] / 255.0, Pixels[ I + 3 ] / 255.0 );
}
case 24: {
int I = ( Y * Header.Width + X ) * 3;
return RGBA( Pixels[ I + 2 ] / 255.0, Pixels[ I + 1 ] / 255.0, Pixels[ I + 0 ] / 255.0, 1 );
}
}
}
return RGBA( -1, -1, -1, -1 );
}
RGBA TGAFILE::GetPixel( const unsigned long I )
{
if ( I < static_cast<unsigned long>( Header.Width * Header.Height ) )
{
switch ( Header.Bits )
{
case 32: {
return RGBA( Pixels[ I * 4 + 2 ] / 255.0,
Pixels[ I * 4 + 1 ] / 255.0,
Pixels[ I * 4 + 0 ] / 255.0,
Pixels[ I * 4 + 3 ] / 255.0 );
}
case 24: {
return RGBA( Pixels[ I * 3 + 2 ] / 255.0,
Pixels[ I * 3 + 1 ] / 255.0,
Pixels[ I * 3 + 0 ] / 255.0,
1 );
}
}
}
return RGBA( -1, -1, -1, -1 );
}
RGBA TGAFILE::GetPixel( UVW & Coord, const bool BilinearFilter )
{
double U = Coord.U;
double V = Coord.V;
//Sikre os at tekstur koordinaterne vil ligge mellm 0 og 1
if ( U > 1.0 || U < -1.0 )
U = static_cast<int>( U ) - U;
if ( V > 1.0 || V < -1.0 )
V = static_cast<int>( V ) - V;
//Sikre os at tekstur koordinaterne altid vil være positive
if ( U < 0.0 )
U = U + 1;
if ( V < 0.0 )
V = V + 1;
//Find 2D koordinater udfra tekstur koordinaterne UVW
int X = static_cast<int>( ( Header.Height - 1 ) * U );
int Y = static_cast<int>( ( Header.Width - 1 ) * V );
//Find den endelig pixel i teksturen
RGBA Color = GetPixel( X, Y );
int I = 1;
RGBA C;
//Bilineær filter tager alle omkring liggende pixels og finder gennemsnittet!
if ( BilinearFilter )
{
C = GetPixel( X + 1, Y );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X - 1, Y );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X, Y + 1 );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X, Y - 1 );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X + 1, Y - 1 );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X + 1, Y + 1 );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X - 1, Y - 1 );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
C = GetPixel( X - 1, Y + 1 );
if ( C.R >= 0 )
{
Color.SetValues( Color.R + C.R, Color.G + C.G, Color.B + C.B, Color.A + C.A );
I = I + 1;
}
//Find gennemsnittet af de samlede farver
return RGBA( Color.R / I, Color.G / I, Color.B / I, Color.A / I );
}
else
return GetPixel( X, Y );
}
bool TGAFILE::SetPixel( const unsigned short X, const unsigned short Y, const unsigned char Red, const unsigned char Green, const unsigned char Blue, const unsigned char Alpha )
{
if ( X < Header.Width && Y < Header.Height )
{
switch ( Header.Bits )
{
case 32: {
int I = ( Y * Header.Width + X ) * 4;
Pixels[ I + 0 ] = Blue;
Pixels[ I + 1 ] = Green;
Pixels[ I + 2 ] = Red;
Pixels[ I + 3 ] = Alpha;
return true;
}
case 24: {
int I = ( Y * Header.Width + X ) * 3;
Pixels[ I + 0 ] = Blue;
Pixels[ I + 1 ] = Green;
Pixels[ I + 2 ] = Red;
return true;
}
}
}
return false;
}
bool TGAFILE::SetPixel( const unsigned short X, const unsigned short Y, const double Red, const double Green, const double Blue, const double Alpha )
{
if ( X < Header.Width && Y < Header.Height )
{
switch ( Header.Bits )
{
case 32: {
int I = ( Y * Header.Width + X ) * 4;
Pixels[ I + 0 ] = static_cast<unsigned char>( Blue * 255 );
Pixels[ I + 1 ] = static_cast<unsigned char>( Green * 255 );
Pixels[ I + 2 ] = static_cast<unsigned char>( Red * 255 );
Pixels[ I + 3 ] = static_cast<unsigned char>( Alpha * 255 );
return true;
}
case 24: {
int I = ( Y * Header.Width + X ) * 3;
Pixels[ I + 0 ] = static_cast<unsigned char>( Blue * 255 );
Pixels[ I + 1 ] = static_cast<unsigned char>( Green * 255 );
Pixels[ I + 2 ] = static_cast<unsigned char>( Red * 255 );
return true;
}
}
}
return false;
}
bool TGAFILE::SetPixel( const unsigned short X, const unsigned short Y, RGBA & Color )
{
if ( X < Header.Width && Y < Header.Height )
{
switch ( Header.Bits )
{
case 32: {
int I = ( Y * Header.Width + X ) * 4;
Pixels[ I + 0 ] = static_cast<unsigned char>( Color.B * 255 );
Pixels[ I + 1 ] = static_cast<unsigned char>( Color.G * 255 );
Pixels[ I + 2 ] = static_cast<unsigned char>( Color.R * 255 );
Pixels[ I + 3 ] = static_cast<unsigned char>( Color.A * 255 );
return true;
}
case 24: {
int I = ( Y * Header.Width + X ) * 3;
Pixels[ I + 0 ] = static_cast<unsigned char>( Color.B * 255 );
Pixels[ I + 1 ] = static_cast<unsigned char>( Color.G * 255 );
Pixels[ I + 2 ] = static_cast<unsigned char>( Color.R * 255 );
return true;
}
}
}
return false;
}
bool TGAFILE::Load( const char * Filename )
{
FILE * Handle = fopen( Filename, "rb" );
if ( !Handle )
return false;
if ( fread( &Header.IdentSize, 1, 1, Handle ) == 0 )
return false;
if ( fread( &Header.ColorMapType, 1, 1, Handle ) == 0 )
return false;
if ( fread( &Header.ImageType, 1, 1, Handle ) == 0 )
return false;
if ( fread( &Header.ColorMapStart, 2, 1, Handle ) == 0 )
return false;
if ( fread( &Header.ColorMapLength, 2, 1, Handle ) == 0 )
return false;
if ( fread( &Header.ColorMapBits, 1, 1, Handle ) == 0 )
return false;
if ( fread( &Header.XStart, 2, 1, Handle ) == 0 )
return false;
if ( fread( &Header.YStart, 2, 1, Handle ) == 0 )
return false;
if ( fread( &Header.Width, 2, 1, Handle ) == 0 )
return false;
if ( fread( &Header.Height, 2, 1, Handle ) == 0 )
return false;
if ( fread( &Header.Bits, 1, 1, Handle ) == 0 )
return false;
if ( fread( &Header.Descriptor, 1, 1, Handle ) == 0 )
return false;
//Denne klasse understøtter kun 24bits og 32bits format!
switch ( Header.Bits )
{
case 32: {
Pixels = new unsigned char [ Header.Width * Header.Height * 4 ];
if ( fread( &Pixels[ 0 ], 1, Header.Width * Header.Height * 4, Handle ) == 0 )
return false;
}
case 24: {
Pixels = new unsigned char [ Header.Width * Header.Height * 3 ];
if ( fread( &Pixels[ 0 ], 1, Header.Width * Header.Height * 3, Handle ) == 0 )
return false;
}
}
fclose( Handle );
return true;
}
bool TGAFILE::Save( const char * Filename )
{
Header.IdentSize = 0;
Header.ColorMapType = 0;
Header.ImageType = 2;
Header.ColorMapStart = 0;
Header.ColorMapLength = 0;
Header.ColorMapBits = 0;
Header.XStart = 0;
Header.YStart = 0;
Header.Descriptor = 0;
FILE * Handle = fopen( Filename, "wb" );
if ( !Handle )
return false;
if ( fwrite( &Header.IdentSize, 1, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.ColorMapType, 1, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.ImageType, 1, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.ColorMapStart, 2, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.ColorMapLength, 2, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.ColorMapBits, 1, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.XStart, 2, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.YStart, 2, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.Width, 2, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.Height, 2, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.Bits, 1, 1, Handle ) == 0 )
return false;
if ( fwrite( &Header.Descriptor, 1, 1, Handle ) == 0 )
return false;
//Denne klasse understøtter kun 24bits og 32bits format!
switch ( Header.Bits )
{
case 32: {
if ( fwrite( &Pixels[ 0 ], Header.Width * Header.Height * 4, 1, Handle ) == 0 )
return false;
}
case 24: {
if ( fwrite( &Pixels[ 0 ], Header.Width * Header.Height * 3, 1, Handle ) == 0 )
return false;
}
}
fclose( Handle );
return true;
}
Som du sikkert har bemærket bruger TGA klassen en anden klasse ved navn: RGBA. Denne klasse er en simpel klasse med komponenterne: R, G, B og A. De er alle af float typen: double. Denne klasse vil repræsentere vores farver. Der er en lille funktion i klassen der hedder: Clamp. Den har to parameterer: Min og Max. Med denne funktion kan farve komponenterne afskæres til et minimum og maksimum værdi. I de fleste tilfælde vil vi bruge en skala mellem 0 og 1. Her er klassen:
#pragma once
class RGBA
{
public:
double R, G, B, A;
RGBA( void );
RGBA( const double R, const double G, const double B, const double A );
void SetValues( const double R, const double G, const double B, const double A );
void SetValues( const double R, const double G, const double B );
void Clamp( const double Min, const double Max );
};
RGBA::RGBA( void )
{
R = 1;
G = 1;
B = 1;
A = 1;
}
RGBA::RGBA( const double R, const double G, const double B, const double A )
{
this->R = R;
this->G = G;
this->B = B;
this->A = A;
}
void RGBA::SetValues( const double R, const double G, const double B, const double A )
{
this->R = R;
this->G = G;
this->B = B;
this->A = A;
}
void RGBA::SetValues( const double R, const double G, const double B )
{
this->R = R;
this->G = G;
this->B = B;
}
void RGBA::Clamp( const double Min, const double Max )
{
if ( R > Max )
R = Max;
else
if ( R < Min )
R = Min;
if ( G > Max )
G = Max;
else
if ( G < Min )
G = Min;
if ( B > Max )
B = Max;
else
if ( B < Min )
B = Min;
if ( A > Max )
A = Max;
else
if ( A < Min )
A = Min;
}
Geometri KlasseDenne klasse vil holde alt data for vores geometri eller 3D objekter. Som nævnt tidligere er 3D objekter som regel bygget op af trekanter. Det vil vores 3D objekter derfor også være. Men der er forskellige måder at definere trekanter på. Det kommer helt an på, hvordan man ønsker at komprimere dataerne. Denne artkel vil ikke bruge komprimeringsmodeller. For at gøre denne artikel så simpel som muligt vil vi kun anvende ukomprimeret data. Klassen indeholder en array af vertex'er, hvoraf tre vertex'er vil definere en trekant.
En vertex indeholder følgende data:
1) Geometrisk koordinater
2) En farve
3) Tekstur (texture) koordinater
4) En normal
Den geometriske koordinat bliver repræsenteret af en vektor.
Farven er repræsenteret af en RGBA klasse.
Tekstur (texture) koordinaterne vil blive repræsenteret af en ny klasse som indeholder komponenterne U,V og W. Tidligere nævnte jeg at vi ikke vil komme til at bruge W komponenten men for en ordens skyld bør vi nok tilføje den. Denne klasse har ikke nogle nævneværdige funktioner og vil derfor komme til at se således ud:
#pragma once
class UVW
{
public:
double U, V, W;
UVW( void );
UVW( const double U, const double V, const double W );
void SetValues( const double U, const double V, const double W );
void SetValues( const double U, const double V );
};
UVW::UVW( void )
{
U = 0;
V = 0;
W = 0;
}
UVW::UVW( const double U, const double V, const double W )
{
this->U = U;
this->V = V;
this->W = W;
}
void UVW::SetValues( const double U, const double V, const double W )
{
this->U = U;
this->V = V;
this->W = W;
}
void UVW::SetValues( const double U, const double V )
{
this->U = U;
this->V = V;
}
Nomalen for vores vertex vil vi komme til at bruge når vi skal beregne lysfaldet for vores trekanter. Den bruger kun tre komponenter X, Yog Z. Af nemheds skyld vil vi derfor definer den som en almindelig vektor trods vi aldrig kommer til at bruge W komponenten. Det vil være nemmest at lave en lille vertex klasse der indeholder disse klasser:
#include "Vector.h"
#include "RGBA.h"
#include "UVW.h"
class VERTEX
{
public:
VECTOR Vertex;
VECTOR Normal;
RGBA Color;
UVW Coord;
};
Geometri klassen har et par nævneværdige funktioner: InterpolateVertexColors, InterpolateTextureCoordinates og InterpolateNormals. De alle har fire ens parametere. Den første parameter forventer en skæringsvektor og de sidste forventer tre vertex'er. Funktionen: InterpolateVertexColor, vil finde den farve som ligger tættest på skæreringspunktet ved at blande de tre vertex farver afhængigt af vektorens barycentriske koordinater. Resultatet vil blive returneret i en farve klasse. De to andre funktioner gør nøjagtig det samme dog bare med trekantens henholdsvis tekstur (texture) koordinater og normaler. Sidst i klassen er der en stribe funktioner der hver kan lave en geometrisk objekt så som en kasse og en cylinder. Af hensyn til denne artikels længde vil vi ikke komme nærmere indpå, hvordan algoritmerne i disse funktioner virker. Geometri klassen ser således ud:
#include <math.h>
#include "Vertex.h"
class GEOMETRY
{
private:
unsigned long VertexCount;
VERTEX * VertexList;
public:
GEOMETRY( void );
~GEOMETRY( void );
void SetVertexList( const unsigned long Count );
unsigned long GetVertexCount( void );
VERTEX * GetVertex( const unsigned long Index );
RGBA InterpolateVertexColors( VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3 );
UVW InterpolateTextureCoordinates( VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3 );
VECTOR InterpolateNormals( VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3 );
void CreatePlane( const double Width, const double Depth, const double X = 0, const double Y = 0, const double Z = 0 );
void CreateBox( const double Width, const double Height, const double Depth, const double X = 0, const double Y = 0, const double Z = 0 );
void CreateCylinder( const unsigned short XSegment, const unsigned short YSegment, const double Height, const double Radius, const double X = 0, const double Y = 0, const double Z = 0 );
};
GEOMETRY::GEOMETRY( void )
{
VertexCount = 0;
}
GEOMETRY::~GEOMETRY( void )
{
if ( VertexCount > 0 )
delete [] VertexList;
}
void GEOMETRY::SetVertexList( const unsigned long Count )
{
if ( VertexCount > 0 )
delete [] VertexList;
VertexList = new VERTEX [ Count ];
VertexCount = Count;
}
unsigned long GEOMETRY::GetVertexCount( void )
{
return VertexCount;
}
VERTEX * GEOMETRY::GetVertex( const unsigned long Index )
{
if ( Index < VertexCount )
return &VertexList[ Index ];
else
return 0;
}
RGBA GEOMETRY::InterpolateVertexColors( VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3 )
{
//Interpoler de tre vertex farver afhængig af vektor Ray!
return RGBA( V1.Color.R * ( 1 - Ray.X - Ray.Y ) + V2.Color.R * Ray.X + V3.Color.R * Ray.Y,
V1.Color.G * ( 1 - Ray.X - Ray.Y ) + V2.Color.G * Ray.X + V3.Color.G * Ray.Y,
V1.Color.B * ( 1 - Ray.X - Ray.Y ) + V2.Color.B * Ray.X + V3.Color.B * Ray.Y, 1 );
}
UVW GEOMETRY::InterpolateTextureCoordinates( VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3 )
{
//Interpoler de tre tekstur koordinater afhængig af vektor Ray!
return UVW( V1.Coord.U * ( 1 - Ray.X - Ray.Y ) + V2.Coord.U * Ray.X + V3.Coord.U * Ray.Y,
V1.Coord.V * ( 1 - Ray.X - Ray.Y ) + V2.Coord.V * Ray.X + V3.Coord.V * Ray.Y,
V1.Coord.W * ( 1 - Ray.X - Ray.Y ) + V2.Coord.W * Ray.X + V3.Coord.W * Ray.Y );
}
VECTOR GEOMETRY::InterpolateNormals( VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3 )
{
//Interpoler de tre normaler afhængig af vektor Ray!
return VECTOR( ( V1.Normal.X * ( 1 - Ray.X - Ray.Y ) + V2.Normal.X * Ray.X + V3.Normal.X * Ray.Y ) / 3,
( V1.Normal.Y * ( 1 - Ray.X - Ray.Y ) + V2.Normal.Y * Ray.X + V3.Normal.Y * Ray.Y ) / 3,
( V1.Normal.Z * ( 1 - Ray.X - Ray.Y ) + V2.Normal.Z * Ray.X + V3.Normal.Z * Ray.Y ) / 3, 1 );
}
void GEOMETRY::CreatePlane( const double Width, const double Depth, const double X, const double Y, const double Z )
{
//Lav en flade
SetVertexList( 6 );
int I = 0;
//Trekant 1
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
}
void GEOMETRY::CreateBox( const double Width, const double Height, const double Depth, const double X, const double Y, const double Z )
{
//Lav en kasse
SetVertexList( 6 * 6 );
int I = 0;
//Forsiden
//Trekant 1
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 0, 1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( 0, 0, 1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 0, 1 );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 0, 1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( 0, 0, 1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 0, 1 );
I = I + 1;
//Bagsiden
//Trekant 1
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 0, -1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( 0, 0, -1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 0, -1 );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 0, -1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( 0, 0, -1 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 0, -1 );
I = I + 1;
//Top
//Trekant 1
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
//Bund
//Trekant 1
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 0, 1, 0 );
I = I + 1;
//Venstre side
//Trekant 1
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( -1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( -1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( -1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( -1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( -1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( -Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( -1, 0, 0 );
I = I + 1;
//Højre side
//Trekant 1
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 0 );
VertexList[ I ].Normal.SetValues( 1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 1, 0, 0 );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, +Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 1, 1 );
VertexList[ I ].Normal.SetValues( 1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, +Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 1 );
VertexList[ I ].Normal.SetValues( 1, 0, 0 );
I = I + 1;
VertexList[ I ].Vertex.SetValues( +Width * 0.5 + X, -Height * 0.5 + Y, -Depth * 0.5 + Z );
VertexList[ I ].Coord.SetValues( 0, 0 );
VertexList[ I ].Normal.SetValues( 1, 0, 0 );
I = I + 1;
}
void GEOMETRY::CreateCylinder( const unsigned short XSegment, const unsigned short YSegment, const double Height, const double Radius, const double X, const double Y, const double Z )
{
//Lav en cylinder
SetVertexList( XSegment * YSegment * 6 );
double XStep = 360.0 / XSegment;
double YStep = Height / YSegment;
//Lav to midlertidige vektorer
VERTEX A;
VERTEX B;
int I = 0;
double DegToRad = 22.0 / 7.0 / 180.0;
for ( int IY = 0; IY < YSegment; IY++ )
for ( int IX = 0; IX < XSegment; IX++ )
{
//Beregn nye koordinater
A.Vertex.SetValues( sin( DegToRad * XStep * IX ) * Radius + X, YStep * IY - Height * 0.5 + Y, cos( DegToRad * XStep * IX ) * Radius + Z );
A.Coord.SetValues( static_cast<double>( IX ) / XSegment, static_cast<double>( IY ) / YSegment );
A.Normal.SetValues( sin( DegToRad * XStep * IX ), 0, cos( DegToRad * XStep * IX ) );
B.Vertex.SetValues( sin( DegToRad * XStep * ( IX + 1 ) ) * Radius + X, YStep * ( IY + 1 ) - Height * 0.5 + Y, cos( DegToRad * XStep * ( IX + 1 ) ) * Radius + Z );
B.Coord.SetValues( static_cast<double>( IX + 1 ) / XSegment, static_cast<double>( IY + 1 ) / YSegment );
B.Normal.SetValues( sin( DegToRad * XStep * ( IX + 1 ) ), 0, cos( DegToRad * XStep * ( IX + 1 ) ) );
//Trekant 1
VertexList[ I ].Vertex.SetValues( A.Vertex.X, A.Vertex.Y, A.Vertex.Z );
VertexList[ I ].Coord.SetValues( A.Coord.U, A.Coord.V );
VertexList[ I ].Normal.SetValues( A.Normal.X, A.Normal.Y, A.Normal.Z );
I = I + 1;
VertexList[ I ].Vertex.SetValues( B.Vertex.X, A.Vertex.Y, B.Vertex.Z );
VertexList[ I ].Coord.SetValues( B.Coord.U, A.Coord.V );
VertexList[ I ].Normal.SetValues( B.Normal.X, A.Normal.Y, B.Normal.Z );
I = I + 1;
VertexList[ I ].Vertex.SetValues( B.Vertex.X, B.Vertex.Y, B.Vertex.Z );
VertexList[ I ].Coord.SetValues( B.Coord.U, B.Coord.V );
VertexList[ I ].Normal.SetValues( B.Normal.X, B.Normal.Y, B.Normal.Z );
I = I + 1;
//Trekant 2
VertexList[ I ].Vertex.SetValues( B.Vertex.X, B.Vertex.Y, B.Vertex.Z );
VertexList[ I ].Coord.SetValues( B.Coord.U, B.Coord.V );
VertexList[ I ].Normal.SetValues( B.Normal.X, B.Normal.Y, B.Normal.Z );
I = I + 1;
VertexList[ I ].Vertex.SetValues( A.Vertex.X, B.Vertex.Y, A.Vertex.Z );
VertexList[ I ].Coord.SetValues( A.Coord.U, B.Coord.V );
VertexList[ I ].Normal.SetValues( A.Normal.X, B.Normal.Y, A.Normal.Z );
I = I + 1;
VertexList[ I ].Vertex.SetValues( A.Vertex.X, A.Vertex.Y, A.Vertex.Z );
VertexList[ I ].Coord.SetValues( A.Coord.U, A.Coord.V );
VertexList[ I ].Normal.SetValues( A.Normal.X, A.Normal.Y, A.Normal.Z );
I = I + 1;
}
}
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (26)
coolt
Wow!
Det er ikke hverdag man falder over en så gennemført tutorial. Du forklarer tingene rigtig godt og detaljeret uden at du forventer at folk er indforstået med hvad det lige er du mener. Jeg har arbejdet med 3D før i 3D's MAX og har tit tænkt på hvordan man kunne programmere sådan noget selv. Så du skal virkelig have mange tak for at vise mig hvordan man evt. kunne gøre det.
Keep up the good work
5/5 point
P.S. Lige et lille spørgsmål... Hvor har du alt din viden fra?
Mange tak for jeres kommentar.
Jeg har min viden fra forskellige bøger, websites og selvfølgeligt mit universitet. Jeg er netop blevet færdig som: Computer Spil Programmør på et Engelsk universitet. Da jeg altid har arbejdet med real-time rendering, har jeg i et stykke tid gået og fået lyst til at prøve software rendering. Håber at I kan drage nytte af min artikel.
Genialt! Det var lige hvad jeg havde brug for!
Søren du laver generalt DE BEDSTE artikler!
Jeg har et forslag til din vektor klasse. Jeg benytter operator overloading til ting som at addere, subtrahere og skalarproduktet. Jeg synes det gør brugen af vektorer meget mere overskuelig
Ellers thumbs up.
Jeg plejer også at bruge operator overloading i mine vektor klasser men jeg ville skrive C++ koden så den let kunne sammenlignes med Delphi koden (Se den samme artikel for Delphi:
http://www.udvikleren.dk/Delphi/Article.aspx/319/ ). Operator overloading er en forholdsvis ny feature i Delphi derfor har jeg ikke inkluderet den i Delphi versionen. I tilfælde af at en C++ programmør er interesseret i at lære Delphi (eller modsat) eller bare er interesseret i at se forskellen mellem de to sprog burde disse artikler være et godt udgangspunkt.
Mange tak for jeres kommentar. Hold jer endelig ikke tilbage hvis I har forslag, kritik eller ros
PS: Glem nu ikke rate mine artikler
Dejligt at kunne læse noget på dansk ;-) Super godt skrevet.
Hmm, god artikel, men ringe du har lavet PRÆCIS den samme artikel, bare med C++!
Undskyld men Delphi!
*Mener
Jonas:: Hvorfor ser du det som et negativt ting at have en artikel der viser implementationen i flere sprog? Skal Delphi udviklere ikke havde chancen for at implementere denne artikel?
Mange tak for rosen.
Jeg modtager gerne ris såvel som ros
Mener bare at du har lavet 2 af de samme artikler og fået dobblet point for det, du kunne bare ha' lavet det hvor artikel navnet var: "3D software rendering med C++ og Delphi"
Men synes det er ret mærkeligt
Hvorfor skulle jeg blande to sprog sammen når de er delt i to adskilte teknologier på dette forum? I så fald hvilken udviklings teknologi skulle jeg så poste dem under?
Er det fordi du mener at jeg har fået for mange points for de artikler?
Hvorfor skulle jeg blande to sprog sammen når de er delt i to adskilte teknologier på dette forum? I så fald hvilken udviklings teknologi skulle jeg så poste dem under?
Er det fordi du mener at jeg har fået for mange points for de artikler?
Jep
Jamen så må du jo snakke med de admins der har ansvar for artikeler her på forummet og tage problemstilling op hos dem. Det kan jeg næsten ikke gøre noget ved. Håber trods alt du kan bruge mine artikler til noget.
Det kan jeg da
SIKKERT! Artiklen er meget god
Men synes bare det er urætfærdigt at man får Up for begge
Jonas:
De der UP, er det ikke bare nogle tal, og så ikke mere i det?
Altså jeg laver altid Forumindlæg uden point..
Hey. Virkelig gennemført beskrivelse, og tror bestemt at jeg skal ha undersøgt mere på emnet.
Anyway. Har et par spørgsmål:
Den første kodestump på s. 4, som starter således: "//Den officelle TGA fil header!". Skal den gemmes som TGA.h? For hvis det er tilfældet så prøver den jo på at include sig selv
Og kan det her på udvikleren lade sig gøre at hente de filer som bliver brugt/lavet i artiklerne? For det ville da være en fordel hvis man sidder ligesom mig og roder rundt i hvordan det hele skal sidde sammen så man har en virkende bund at starte på, og at man ved fejl evt. ved fejl ved at det ikke er koden, men noget setup
Og til sidst. Dette burdet ikke have noget problem med at compile på en ubuntu-installation?
mvh. martin
Hej Martin,
Stukturen som jeg har kaldt for: FILEHEADER, er den officielle TGA fil header. Den skal ligge øverst i header filen TGA.h således den kan genkendes af TGA klassen. Du kan godt lægge den i en separat header fil, hvis du har lyst til det, men så skal den selvfølgelig inkluderes i TGA header filen inden TGA klassen defineres. Jeg vil nok anbefale at lægge den inden i selve TGA klassen da denne TGA fil format kun benyttes af TGA klassen! Med andre ord så er alt koden i den refererede tekst boks implementeringen af hele TGA klassen og kan skrives i en header fil eller en header med tilhørende cpp fil.
Angående kilde kode, du kan sende mig en udvikler post her på forummet med din mail adresse. Så vil jeg sende projektet til dig i en zip fil.
Alle andre er selvfølgelig også velkommen til at sende mig deres mail adresse.
Projektet skulle være platforms uafhængig. Der bliver ikke brugt nogle operative kommandoer eller API'er af nogen form, så ja, hvis din kompiler ellers kan kompile ganske almindelige console applikationer, så burde du også kunne kompilere dette projekt. Lad mig vide hvis du støder ind i nogle problemer.
Søren Klit Lambæk
Rigtig god artikel.
Du har dog glemt at fortælle at man skal huske at ændre RenderImage til at bruge den nye ShadePixel()
RGBA Color = ShadePixel(RayNear, RayFar, RayPixel, *V1, *V2, *V3, GeometryList[J], Texture, PointLight);
Og Phong laver compile errors
Da du her
RGBA VertexColor = Geometry.InterpolateVertexColors(Ray, V1, V2, V3);
parser V1, V2 og V3 til InterpolateVertexColors funktionen. Den ta'r vectors, men V1, V2 og V3 er vertexes.
Eller... Nu bliver jeg forvirret.
Error 1 error C2664: 'GEOMETRY::InterpolateNormals' : cannot convert parameter 2 from 'VECTOR' to 'VERTEX &' renderer.cpp 91
Nej, okay. er omvendt. Funktionen forventer vertexes, men V1, V2 og V3 er vectors.
Hvordan den være?
Denne side er da håbløst bugged.
Hvordan [b]skal[/b] den være?
Hej Morten, jeg er ikke helt med paa hvad du mener. Baade InterpolateNormals() og InterpolateColors() tager vertexer (Vertices). Kan du ikke give mig noget mere sammenhaengene kode saa jeg kan se mere detaljeret hvad du goer. Tak
Du skal være
logget ind for at skrive en kommentar.