跳转至

Assignment 2

文本统计:约 571 个字 • 287 行代码

本次作业需要完成的任务是

  • 增加平面以及三角形等基础图形
  • 增加 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啥的)

Object3D's class Hierarchy

实现 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();

确定打到的位置后,法线也要做正确的变换。这个比较复杂,推导过程如下

\[ N'=(M^{-1})^TN \]

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 &center, 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

将渲染器单独写了两个文件,用来实现 DepthRendererColorRenderernomaralRendererDiffuseRenderer 前两个在之前的作业中已经写过

这个是 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);;
        }
    }
}

评论区

对你有帮助的话请给我个赞和 star => GitHub stars
欢迎跟我探讨!!!