Assignment 2¶
本次作业需要完成的任务是
- 增加平面以及三角形等基础图形
- 增加 Transform
- 实现 perspective camera
- 完成三种shading 分别是 normal shading, diffuse shading, depth shading
尤其关注一下这里的 diffuse shading,其实就是简单的实现了一下直接光照部分以及环境光部分
Plane¶
class Plane : public Object3D {
public:
Plane(const Vec3f &normal, float d, Material *m):
normal(normal), d(d), Object3D(m) {}
Plane(): Plane(Vec3f(),0,nullptr){}
Vec3f getNormal() const { return normal; }
float getD() const {return d;}
bool intersect(const Ray &r, Hit &h, float tmin) override;
private:
Vec3f normal;
float d;
};
实现 intersect
函数
bool Plane::intersect(const Ray &r, Hit &h, float tmin) {
Vec3f normal = getNormal();
//光线与平面几乎平行的情况
if (fabs(normal.Dot3(r.getDirection())) <EPSILON) return false;
float t = (d-r.getOrigin().Dot3(normal)) / r.getDirection().Dot3(normal);
if (t < tmin-EPSILON) return false;
h.set(t,material,normal, r);
return true;
}
Warning
我第一次写的时候,犯的错误是处理光线与平面几乎平行的情况时,没有增加 fabs,导致出错
Triangle¶
class Transform : public Object3D {
public:
Transform(Matrix m,Object3D *object) : object(object), m(m) {
m.Inverse(mInv);
mInv.Transpose(mInvT);
}
bool intersect (const Ray &r, Hit &h, float tmin) override;
private:
Object3D *object;
Matrix m,mInv,mInvT;
};
实现 intersect
函数
bool Triangle::intersect (const Ray &r, Hit &h, float tmin)
{
Vec3f e1 = v1 - v0;
Vec3f e2 = v2 - v0;
Vec3f s1,s2;
Vec3f::Cross3(s1,r.getDirection(), e2);
Vec3f::Cross3(s2,r.getOrigin()-v0, e1);
float det = 1/s1.Dot3(e1);
float tnear = s2.Dot3(e2)* det;
float u = s1.Dot3(r.getOrigin() - v0)*det;
float v =s2.Dot3(r.getDirection())*det;
if (tnear >=tmin-EPSILON && u >= -EPSILON && v >=-EPSILON && (u+v) <= 1+EPSILON)
{
h.set(tnear, material,normal, r);
return true;
}
return false;
}
这里三角形求交的方法是 Moller Trumbore Algorithm
Transform¶
我们将 Transform 也理解为 Object3D
的一个子类,虽然比较抽象
class Transform : public Object3D {
public:
Transform(Matrix m,Object3D *object) : object(object), m(m) {
m.Inverse(mInv);
mInv.Transpose(mInvT);
}
bool intersect (const Ray &r, Hit &h, float tmin) override;
private:
Object3D *object;
Matrix m,mInv,mInvT;
};
包含了一个指向 Object3D
类的指针(可能是group啥的)
实现 intersect
函数
bool Transform::intersect(const Ray &r, Hit &h, float tmin) {
Vec3f ori = r.getOrigin();
Vec3f dir = r.getDirection();
Vec4f ori1(ori.x(), ori.y(), ori.z(), 1);
Vec4f dir1(dir.x(), dir.y(), dir.z(), 0);
mInv.Transform(ori1);
mInv.Transform(dir1);
dir = Vec3f(dir1.x(), dir1.y(), dir1.z());
float dir_len = dir.Length();
dir.Normalize();
Ray r1(Vec3f(ori1.x()/ori1.w(), ori1.y()/ori1.w(), ori1.z()/ori1.w()), dir);
bool intersect = object->intersect(r1, h, tmin);
float t = h.getT()/dir_len;
if (intersect) {
Vec4f normal(h.getNormal().x(), h.getNormal().y(), h.getNormal().z(), 0);
mInvT.Transform(normal);
Vec3f n = Vec3f(normal.x(), normal.y(), normal.z());
n.Normalize();
h.set(t, h.getMaterial(),n, r);
}
return intersect;
}
对于物体的变换操作比较困难,我们选择保持物体不变,变换其他的来达到同样的效果。
为使光线达到的位置与变换后打到的位置相同,我们对光线进行逆变换,然后打到正常的物体上,得到应该打到的位置。由于要生成深度图,也要相应的变换时间 float dir_len = dir.Length();
确定打到的位置后,法线也要做正确的变换。这个比较复杂,推导过程如下
即
Warning
注意变换后都要将向量变为单位向量,在处理点的变换后,四维转三维一定要除以第四维的值。
我感觉这个版本的代码写的更加清晰,可以借鉴一下
//
// Created by kskun on 2021/10/11.
//
#ifndef ASSIGNMENT2_TRANSFORM_H
#define ASSIGNMENT2_TRANSFORM_H
#include "object3d.h"
#include "matrix.h"
#include "global.h"
class Transform : public Object3D {
public:
Transform(Matrix &m, Object3D *o) : Object3D(), mat(m), obj(o) {
mat.Inverse(matInv, EPSILON);
matInv.Transpose(matInvT);
}
bool intersect(const Ray &r, Hit &h, float tMin) override;
protected:
static void TransformPosition(Vec3f &dst, const Vec3f &src, const Matrix &mat);
static void TransformDirection(Vec3f &dst, const Vec3f &src, const Matrix &mat);
Matrix mat, matInv, matInvT;
Object3D *obj;
};
#endif //ASSIGNMENT2_TRANSFORM_H
//
// Created by kskun on 2021/10/11.
//
#include "transform.h"
void Transform::TransformPosition(Vec3f &dst, const Vec3f &src, const Matrix &mat) {
auto src4 = Vec4f(src.x(), src.y(), src.z(), 1);
mat.Transform(src4);
dst = Vec3f(src4.x() / src4.w(), src4.y() / src4.w(), src4.z() / src4.w());
}
void Transform::TransformDirection(Vec3f &dst, const Vec3f &src, const Matrix &mat) {
auto src4 = Vec4f(src.x(), src.y(), src.z(), 0);
mat.Transform(src4);
dst = Vec3f(src4.x(), src4.y(), src4.z());
}
bool Transform::intersect(const Ray &r, Hit &h, float tMin) {
Vec3f nRo, nRd;
TransformPosition(nRo, r.getOrigin(), matInv);
TransformDirection(nRd, r.getDirection(), matInv);
auto transformedRdLen = nRd.Length();
nRd.Normalize();
auto nRay = Ray(nRo, nRd);
if (!obj->intersect(nRay, h, tMin)) return false;
auto t = h.getT() / transformedRdLen;
Vec3f n = h.getNormal();
TransformDirection(n, h.getNormal(), matInvT);
n.Normalize();
h.set(t, h.getMaterial(), n, r);
return true;
}
将处理点和处理线的步骤单独用函数进行处理,代码更加简洁清晰。
Perspective Camera¶
class PerspectiveCamera : public Camera {
public:
PerspectiveCamera(const Vec3f ¢er, const Vec3f &direction, const Vec3f &up, float angle)
{
this->center = center;
this->direction = direction;
this->direction.Normalize();
Vec3f::Cross3(this->horizontal, direction, up);
this->horizontal.Normalize();
Vec3f::Cross3(this->up, this->horizontal, direction);
this->up.Normalize();
side = tan(angle/2)*2;
}
Ray generateRay(Vec2f point) override;
float getTMin() const override;
private:
Vec3f center,up,direction,horizontal;
float side;
};
Ray PerspectiveCamera::generateRay(Vec2f point)
{
auto pScr = center + direction +
horizontal * (point.x() - 0.5f) * side + up * (point.y() - 0.5f) * side;
auto dir = pScr - center;
dir.Normalize();
return {center, dir};
}
float PerspectiveCamera::getTMin() const
{
return 0.0f;
}
这里透视相机的原理如下
the distance to the image plane and the size of the image plane are unnecessary. Why?
我们只需要角度信息就可以了,产生的光线角度就可以确定了。
Light¶
本作业的光线为平行光 DirectionalLight,有方向和颜色
class DirectionalLight : public Light {
public:
// CONSTRUCTOR & DESTRUCTOR
DirectionalLight(const Vec3f &d, const Vec3f &c) {
direction = d; direction.Normalize();
color = c; }
~DirectionalLight() {}
// VIRTUAL METHOD
void getIllumination (const Vec3f &p, Vec3f &dir, Vec3f &col) const {
// the direction to the light is the opposite of the
// direction of the directional light source
dir = direction * (-1.0f);
col = color;
}
private:
DirectionalLight(); // don't use
// REPRESENTATION
Vec3f direction;
Vec3f color;
};
当然后面还会有不同的光,构成 Light
的派生类
Renderer¶
将渲染器单独写了两个文件,用来实现 DepthRenderer
,ColorRenderer
,nomaralRenderer
,DiffuseRenderer
前两个在之前的作业中已经写过
这个是 NormalRenderer
根据法线来染色
void NormalRenderer::Render()
{
assert(image);
image->SetAllPixels(Vec3f(0, 0, 0));
auto camera = scene->getCamera();
for (int i = 0; i < image->Width(); i++)
{
for (int j = 0; j < image->Height(); j++)
{
Ray ray = camera->generateRay(Vec2f((float)i / image->Width(),(float)j / image->Height()));
Hit hit;
auto interRes = group->intersect(ray, hit, camera->getTMin());
if (interRes)
{
auto n = hit.getNormal();
n.Set(fabs(n.x()), fabs(n.y()), fabs(n.z()));
image->SetPixel(i, j, n);
}
}
}
}
重点是DiffuseRenderer
void DiffuseRenderer::Render()
{
Renderer::Render();
auto camera = scene->getCamera();
for (int i = 0; i < image->Width(); i++)
{
for (int j = 0; j < image->Height(); j++)
{
Ray ray = camera->generateRay(Vec2f((float)i / image->Width(),(float)j / image->Height()));
Hit hit;
auto interRes = group->intersect(ray, hit, camera->getTMin());
if (!interRes) continue;
Vec3f color = Vec3f(0, 0, 0);
//获得基础的漫反射颜色
auto diffuseCol = hit.getMaterial()->getDiffuseColor();
auto n = hit.getNormal();
for (int k = 0; k < scene->getNumLights(); k++)
{
auto light = scene->getLight(k);
Vec3f dir, col;
light->getIllumination(hit.getIntersectionPoint(), dir, col);
//是否考虑背面打来的光线
if (shadeBack && ray.getDirection().Dot3(n) > 0) {
n.Scale(-1, -1, -1);
}
auto d = max(dir.Dot3(n), 0.0f);
col.Scale(d * diffuseCol.x(), d * diffuseCol.y(), d * diffuseCol.z());
color += col;
}
//环境光
auto ambientCol = scene->getAmbientLight();
ambientCol.Scale(diffuseCol.x(), diffuseCol.y(), diffuseCol.z());
color += ambientCol;
color.Clamp();
image->SetPixel(i, j, color);;
}
}
}