assignment1是光线跟踪部分的起始,这里我们先建立了框架,建立一个纯虚的Object3D类作为父类,然后Group类作为子类,这里我们只建立了Sphere类这种实体。他们拥有bool intersect(const Ray &r, Hit &h, float tmin)这个函数,这个函数就是为了进行一道光打过去具体和那个物体相交的判定。另外我们还需要建立纯虚的Camera类,以及在这个assignment中用到的OrthographicCamera类,他们拥有Ray generateRay(Vec2f point)这个函数来生成光线。
下面是各部分的代码:
为了优化点,我们改动了hit.h的代码,主要是因为material本来就是指针,所以感觉原来的函数 Hit(const Hit &h)的过程中 material = h.material; 这一句不太好,我们改成new出一个material;然后顺带把material类写在hit.h里面。hit.h代码如下:
#ifndef _HIT_H
#define _HIT_H
#include "vectors.h"
#include "ray.h"
// ====================================================================
// ====================================================================
class Material{
public:
Material(){};
Material(Vec3f c):color(c){}
Material(const Material& ma)
{
color = ma.color;
}
~Material(){};
void setMaterial(Vec3f c)
{
color=c;
}
Vec3f getColor(){return color;}
private:
Vec3f color;
};
class Hit {
public:
// CONSTRUCTOR & DESTRUCTOR
Hit() { material = NULL; }
Hit(float _t, Material *m, Vec3f n)
{
t = _t; material = m; normal = n;
}
Hit(const Hit &h)
{
t = h.t;
material =new Material( *(h.material)) ;
normal = h.normal;
intersectionPoint = h.intersectionPoint;
}
~Hit() {}
// ACCESSORS
float getT() const { return t; }
Material* getMaterial() const { return material; }
Vec3f getNormal() const { return normal; }
Vec3f getIntersectionPoint() const { return intersectionPoint; }
// MODIFIER
void set(float _t, Material *m, Vec3f n, const Ray &ray) {
t = _t; material = m; normal = n;
intersectionPoint = ray.pointAtParameter(t); }
private:
// REPRESENTATION
float t;
Material *material;
Vec3f normal;
Vec3f intersectionPoint;
};
inline ostream &operator<<(ostream &os, const Hit &h) {
os << "Hit <" <<h.getT()<<", "<<h.getNormal()<<">";
return os;
}
// ====================================================================
// ====================================================================
#endif
对于scene_parser.c这个文件,把里面和这次assignment不想关的东西注释掉就可以了,这里是一个读入场景文件到各个变量中的一个流程,写类的成员时候,需要参考里面的函数。
然后是camera.h和camera.c文件,注意下Ray generateRay(Vec2f point);函数,我们让他读入的point是将其单位化到(0,1)*(0,1)区间的,这样写的时候不需要考虑size大小什么的。具体代码如下:
camera.h:
#ifndef _CAMERA_H
#define _CAMERA_H
#include "vectors.h"
class Ray;
class Camera
{
public:
Camera(){};
~Camera(){};
virtual Ray generateRay(Vec2f point) = 0;
virtual float getsize() = 0;
};
class OrthographicCamera:public Camera
{
public:
OrthographicCamera(Vec3f c,Vec3f p,Vec3f u,float s)
:center(c)
,projection(p)
,up(u)
,size(s)
{
Vec3f h;
Vec3f::Cross3(h,p,u);
horizontal=h;
}
OrthographicCamera(){};
~OrthographicCamera(){};
float getsize(){return size;}
virtual Ray generateRay(Vec2f point);
private:
Vec3f center;
Vec3f projection;
Vec3f up;
Vec3f horizontal;
float size;
};
#endif
camera.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "camera.h"
#include "ray.h"
#include "vectors.h"
#include "matrix.h"
#include "image.h"
#include "scene_parser.h"
#include "hit.h"
#include "object3d.h"
Ray OrthographicCamera::generateRay(Vec2f apoint)
{
Vec3f Porigin;
Vec3f nh=horizontal;
Vec3f nu=up;
nh.Normalize();
nu.Normalize();
projection.Normalize();
Porigin=center+nh*(apoint.x()-0.5)*size+nu*(apoint.y()-0.5)*size;
Ray cameraray=Ray(projection,Porigin);
return cameraray;
}
然后是object3D.h和object3D.c文件,需要注意的有两点,一个是对于Group,由于要带addObject操作,所以要带成员Object3D数组,另外注意到,在addObject的时候,我们读入的是一个指针,所以 Object3D **objects定义私有成员,另外就是注意Object3D是纯虚类,不能初始化成员,所以我们用malloc来分配空间给它。代码如下:
object3D.h:
#ifndef _OBJECT3D_H
#define _OBJECT3D_H
#include "hit.h"
#include "ray.h"
#include "vectors.h"
class Object3D{
public:
Object3D(){};
~Object3D(){};
virtual bool intersect(const Ray &r, Hit &h, float tmin) = 0;
private:
Vec3f color;
};
class Sphere :public Object3D{
public:
Sphere(){};
Sphere(Vec3f ce,float r,Vec3f co)
:center(ce)
,radius(r)
,color(co)
{
}
~Sphere(){};
virtual bool intersect(const Ray &r, Hit &h, float tmin);
private:
Vec3f center;
float radius;
Vec3f color;
};
class Group :public Object3D{
public:
Group(){};
Group(const int gnum)
:num_object(gnum)
{
int n = sizeof(Object3D *);
objects = (Object3D** )malloc(gnum * n);
}
~Group(){};
bool intersect(const Ray &r, Hit &h, float tmin);
void addObject(int index, Object3D *obj);
private:
int num_object;
Object3D **objects;
};
#endif
对于object3D.c,我写的时候开始发生的一个问题就是sphere的intersect函数的实现中,material没有new出来,然后后读入的覆盖了前读入的,发生了错误。代码如下:
#include <stdlib.h>#include <stdio.h>
#include <string.h>
#include <math.h>
#include "camera.h"
#include "ray.h"
#include "vectors.h"
#include "matrix.h"
#include "object3d.h"
#include "hit.h"
bool Sphere::intersect(const Ray &ar, Hit &ah, float tmin){
float a,b,c,d,sradius,newt,oldt;
Vec3f sd,sr,sn;
sradius=this->radius;
a=1;
sd=ar.getDirection();
sr=ar.getOrigin();
sr=sr-center;
b=2*sd.Dot3(sr);
c=sr.Dot3(sr)-sradius*sradius;
if(b*b-4*a*c>=0)
{
Material* sm=new Material(color);
d=sqrt(b*b-4*a*c);
newt=(-b-d)/(2*a);
oldt=ah.getT();
Vec3f intersection=ar.pointAtParameter(newt);
sn=center-intersection;
sn.Normalize();
if(newt>=tmin && newt<oldt)
{
ah.set(newt,sm,sn,ar);
return 1;
}
return 0;
delete sm;
}
return 0;
};
bool Group::intersect(const Ray &br, Hit &bh, float tmin){
bool x=0;
for(int i=0;i<=num_object-1;i++){
if(objects[i]->intersect(br,bh,tmin))
x=1;
}
return x;
}
void Group::addObject(int in, Object3D *ob){
if (in<num_object && in>=0)
{
objects[in]=ob;
}
}
最后是parse.c函数,要注意的也和上面一样,如果没改hit.h文件的话,这里的material就要在循环体里面new出来,不过由于改了hit.h,我们在循环外new出来也没事.代码如下:
parse.c:
#include <stdio.h>
#include <string.h>
#include "image.h"
#include "vectors.h"
#include "matrix.h"
#include "scene_parser.h"
#include "object3d.h"
#include "camera.h"
// ========================================================
// ========================================================
// Some sample code you might like to use for parsing
// command line arguments
Image* render(char* filename,float width,float height)
{
SceneParser scene=SceneParser(filename);
Image* myimage=new Image(width,height);
const Vec3f groundcolor=scene.getBackgroundColor();
Camera* myCamera=scene.getCamera();
float csize = myCamera->getsize();
Group* myGroup=scene.getGroup();
Material* orignm=new Material(groundcolor);
myimage->SetAllPixels(groundcolor);
for(int i=0;i<width;i++)
for(int j=0;j<height;j++)
{
Ray oneray = myCamera->generateRay(Vec2f(i/width,j/height));
int tmin=numeric_limits<float>::min();
Hit rhit=Hit::Hit(numeric_limits<float>::max(),orignm,Vec3f(0,0,0));
bool l=myGroup->intersect(oneray,rhit,tmin);
Material *currentmaterial=rhit.getMaterial();
Vec3f currentcolor=currentmaterial->getColor();
myimage->SetPixel(i,j,currentcolor);
}
delete orignm;
return myimage;
}
int main(int argc,char *argv[]){
char *input_file = NULL;
int width = 100;
int height = 100;
char *output_file = NULL;
float depth_min = 0;
float depth_max = 1;
char *depth_file = NULL;
// sample command lines:
// raycast -input input.txt -size 100 100 -output output.tga
// raycast -input input.txt -size 100 100 -depth 5.5 8.8 output.tga
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i],"-input")) {
i++; assert (i < argc);
input_file = argv[i];
} else if (!strcmp(argv[i],"-size")) {
i++; assert (i < argc);
width = atoi(argv[i]);
i++; assert (i < argc);
height = atoi(argv[i]);
} else if (!strcmp(argv[i],"-output")) {
i++; assert (i < argc);
output_file = argv[i];
} else if (!strcmp(argv[i],"-depth")) {
i++; assert (i < argc);
depth_min = atof(argv[i]);
i++; assert (i < argc);
depth_max = atof(argv[i]);
i++; assert (i < argc);
depth_file = argv[i];
} else {
printf ("whoops error with command line argument %d: '%s'\n",i,argv[i]);
assert(0);
}
}
Image *myimage=render(input_file,width,height);
myimage->SaveTGA(output_file);
delete myimage;
}
// ========================================================
// ========================================================
这样整个assignment1就算完成了.