スポンサーリンク

レイキャストを実装してみる(4) 三角形との交差

球の代わりに三角形を使ってみる。

raycast.hpp

#pragma once

#include "Grid.hpp"

#include <array>

//////////////////////////////////////////////
//////////////////////////////////////////////
// 球オブジェクト
class object_sphere {
  glm::vec3 _center;
  glm::vec3 _color;
  float _r;
public:
  object_sphere(glm::vec3 center_, glm::vec3 color_,float radius_) :
    _center(center_), _color(color_),_r(radius_) {}
  object_sphere() {}

  //! @brief レイと球の交点を求める
  //! @param [out] pos_ 交点の座標
  //! @param [out] color_ 球オブジェクトの色
  //! @param [out] normal_ 交点の面の法線
  //! @param [in] ray_ レイ
  //! @retval true 交差している
  //! @retval false 交差していない
  bool intersect(
    glm::vec3* pos_, 
    glm::vec3* color_,
    glm::vec3* normal_,
    const std::array<glm::vec3, 2>& ray_)const;

  const glm::vec3& color()const { return _color; }
  glm::vec3& color() { return _color; }

};

//////////////////////////////////////////////
//////////////////////////////////////////////
// 三角形オブジェクト
class object_triangle {
  std::array<glm::vec3, 3> _points;
  glm::vec3 _color;
public:
  object_triangle(
    std::array<glm::vec3, 3> points_, glm::vec3 color_) :
    _points(points_), _color(color_) {}
  object_triangle() {}

  bool intersect(
    glm::vec3* pos_,
    glm::vec3* color_,
    glm::vec3* normal_,
    const std::array<glm::vec3, 2>& ray_)const;

  const glm::vec3& color()const { return _color; }
  glm::vec3& color() { return _color; }

};

      
//////////////////////////////////////////////
//////////////////////////////////////////////
// ポイントライト
class light_point {
  glm::vec3 _position;
  glm::vec3 _diffuse_color;
  glm::vec3 _ambient_color;
  glm::vec3 _specular_color;
public:

  light_point(
    const glm::vec3& position_, 
    const glm::vec3& diffuse_color_,
    const glm::vec3& ambient_color_,
    const glm::vec3& specular_color_
  ) :
    _position(position_), 
    _diffuse_color(diffuse_color_),
    _ambient_color(ambient_color_),
    _specular_color(specular_color_)
  {}
  light_point() {}

  glm::vec3 calc_vector_point_to_light(const glm::vec3& point_)const {
    // from -> _position
    return glm::normalize(_position - point_);
  }

  const glm::vec3& position()const {return _position;}
  glm::vec3& position() { return _position; }

  const glm::vec3& diffuse_color()const {return _diffuse_color;}
  glm::vec3& diffuse_color() { return _diffuse_color; }

  const glm::vec3& ambient_color()const { return _ambient_color; }
  glm::vec3& ambient_color() { return _ambient_color; }

  const glm::vec3& specular_color()const { return _specular_color; }
  glm::vec3& specular_color() { return _specular_color; }

};

struct hit_pixel_t {
  glm::vec3 _position;
  glm::vec3 _color;
  hit_pixel_t(const glm::vec3& position_, const glm::vec3& color_) :
    _position(position_), _color(color_) {}

};


// レイキャスト用のクラス
class my_raycast {
  grid g[2];
  float _startZ;
  float _endZ;
  int _pxwidth;
  int _pxheight;
public:

  //! @brief 視点を取得
  //! @details 普通は視点、near , farからレイを作成すると思うが、今回は
  //! グリッドを二枚作ることでnear,farを決定しているので、視点を計算で求める必要がある。
  //! 視点はspecular計算の時に必要となる。
  glm::vec3 get_eye_position()const {
    float a = g[0].ywidth() / 2.0;
    float b = g[1].ywidth() / 2.0;
    float d = glm::length(g[1].center() - g[0].center());
    float c = a * d / (b - a);


    // 始点 → 終点
    glm::vec3 veceye = g[1].center() - g[0].center();
    veceye = glm::normalize(veceye);
    return g[0].center()  - veceye * c;
  }

  my_raycast() {}

  //! @brief 結果のピクセル数(幅)を取得
  int width()const { return _pxwidth; }
  //! @brief 結果のピクセル数(高さ)を取得
  int height()const { return _pxheight; }

  //! @brief レイの開始点となるグリッド、終点となるグリッドを作成する
  //! @param [in] startZ_   始点のグリッドのZ座標
  //! @param [in] endZ_   終点のグリッドのZ座標
  //! @param [in] pxwidth_  ピクセル数 X
  //! @param [in] pxheight_ ピクセル数 Y
  void set(const float startZ_, const float endZ_, const int pxwidth_, const int pxheight_);

  //グリッド取得
  const grid& get_grid(const size_t index)const {
    return g[index];
  }

  //グリッドから作成するレイの数を取得
  int ray_count() {
    return g[0].cellCount();
  }

  //グリッドから作成するレイを取得
  std::array<glm::vec3, 2> get_ray(const size_t index)const;

  //始点グリッド、終点グリッドの中間の位置を取得(表示用)
  float lookatZ()const {
    return (_startZ + _endZ) / 2.f;
  }


  //////////////////////////////////////////////
  //////////////////////////////////////////////

private:
  // object_sphere _obj;
  object_triangle _obj;

  std::vector< hit_pixel_t > _hits;
public:
  void raycast();
  std::vector< hit_pixel_t >& get_intersects() {return _hits;}

  //void set_object(const object_sphere& obj_);
  void set_object(const object_triangle& obj_);

  //////////////////////////////
private:
  light_point _light;
public:
  void set_light(const light_point& light_);

  const light_point& get_light()const { return _light; }
};

//! @brief diffuse計算
//! @param [in] lightDiffuse ライトの色
//! @param [in] materialDiffuse オブジェクトの色
//! @param [in] lightIncidence ライトの入射ベクトル
//! @param [in] objectNormal 面法線
//! @return diffuse色
inline glm::vec3 calc_diffuse(
  const glm::vec3& lightDiffuse, 
  const glm::vec3& materialDiffuse,
  const glm::vec3& lightIncidence,
  const glm::vec3& objectNormal) 
{
  // 拡散反射は「そのポイント→光源」と面法線の内積を取る

  float sDotN = glm::max(glm::dot(-lightIncidence, objectNormal), 0.f);

  return lightDiffuse * materialDiffuse *sDotN;
}

//! @brief ambient計算
//! @param [in] lightAmbient ライトの色
//! @param [in] materialAmbient オブジェクトの色
//! @return ambient色
inline glm::vec3 calc_ambient(
  const glm::vec3& lightAmbient, 
  const glm::vec3& materialAmbient) 
{
  return lightAmbient * materialAmbient;
}

//! @brief specular計算
//! @param [in] eyePosition 視点の座標
//! @param [in] lightSpecularColor ライトの色
//! @param [in] materialSpecularColor オブジェクトの色
//! @param [in] lightIncidence ライトの入射ベクトル
//! @param [in] materialShininess 光沢度
//! @param [in] objectNormal レイがヒットした位置の面法線
//! @param [in] objectPosition レイがヒットした座標
//! @return specular色
inline glm::vec3 calc_specular(
  const glm::vec3& eyePosition,
  const glm::vec3& lightSpecularColor,
  const glm::vec3& materialSpecularColor,
  const glm::vec3& lightIncidence, // 入射
  const float materialShininess,
  const glm::vec3& objectNormal,
  const glm::vec3& objectPosition
)
{

  // 参考文献:
  // https://araramistudio.jimdo.com/2017/10/02/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-directx-11%E3%81%A7%E9%8F%A1%E9%9D%A2%E5%8F%8D%E5%B0%84-specular-reflection/
  // https://amengol.github.io/game_physics/using-glm-reflect-to-react/
  // https://learnopengl.com/Lighting/Basic-Lighting

  // レイとの交点→カメラ位置 のベクトル
  glm::vec3 VertexToEyeVec = glm::normalize(eyePosition - objectPosition);

  // ライトの入射に対する反射ベクトル
  glm::vec3 LightReflect = glm::normalize(glm::reflect(lightIncidence, objectNormal));

  float SpecularFactor = glm::pow(glm::max(glm::dot(VertexToEyeVec, LightReflect),0.f), materialShininess);

  glm::vec3 SpecularColor(0, 0, 0);
  SpecularColor = SpecularFactor * lightSpecularColor * materialSpecularColor;

  return SpecularColor;

}

raycast.cpp

#include "raycast.hpp"

#include <glm/gtx/intersect.hpp>


#include <glm/gtx/normal.hpp>

bool object_sphere::intersect(
  glm::vec3* pos_, 
  glm::vec3* color_,
  glm::vec3* normal_,
  const std::array<glm::vec3, 2>& ray_) const{

  // レイから正規化ベクトルを作成
  glm::vec3 nray = glm::normalize(ray_[1] - ray_[0]);

  // レイの始点から交点までの距離
  float distance;
  bool valid = glm::intersectRaySphere(ray_[0], nray, _center, _r*_r, distance);

  //レイが衝突した点の座標
  *pos_ = ray_[0] + nray * distance;

  //色の設定
  *color_ = _color;


  //面法線
  // 球中心 → 衝突点
  *normal_ = glm::normalize(*pos_ - _center);

  // 交差していればtrue
  return valid;
}

/////////////////////////////////
// 三角形とレイの交差
bool object_triangle::intersect(
  glm::vec3* pos_,
  glm::vec3* color_,
  glm::vec3* normal_,
  const std::array<glm::vec3, 2>& ray_)const {

  glm::vec3 nray = glm::normalize(ray_[1] - ray_[0]);

  float distance;

  glm::vec2 bary;

  bool valid = glm::intersectRayTriangle(
    ray_[0],
    nray,
    _points[0],
    _points[1],
    _points[2],
    bary,
    distance
  );

  //レイが衝突した点の座標
  *pos_ = ray_[0] + nray * distance;


  //色の設定
  *color_ = _color;

  //面法線
  *normal_ = glm::triangleNormal(
    _points[0],
    _points[1],
    _points[2]
  ); // normalize済

  return valid;

}
void my_raycast::set(const float startZ_, const float endZ_, const int pxwidth_, const int pxheight_) {
  _startZ = startZ_;
  _endZ = endZ_;

  _pxwidth = pxwidth_;
  _pxheight = pxheight_;

  glm::vec3 vecx(1, 0, 0);
  glm::vec3 vecy(0, 1, 0);

  g[0] = grid(
    vecx,
    vecy,
    glm::vec3(0, 0, _startZ),
    1.f, 1.f,
    pxwidth_, pxheight_
  );
  g[1] = grid(
    vecx,
    vecy,
    glm::vec3(0, 0, _endZ),
    2.f, 2.f,
    pxwidth_, pxheight_
  );

}

//! @brief レイの取得
//! @param [in] index レイの番号 最大 pxwidth_ × pxheight_
std::array<glm::vec3, 2> my_raycast::get_ray(const size_t index)const {
  return
    std::array<glm::vec3, 2>{
    g[0][index].center(),
      g[1][index].center()
  };

}

//void my_raycast::set_object(const object_sphere& obj_) {
//  _obj = obj_;
//}
void my_raycast::set_object(const object_triangle& obj_) {
  _obj = obj_;
}

void my_raycast::set_light(const light_point& light_) {
  _light = light_;
}

void my_raycast::raycast() {

  float inf = std::numeric_limits<float>::infinity();
  // 結果の初期化
  _hits.clear();
  _hits.resize(
    _pxwidth * _pxheight,
    hit_pixel_t(
      glm::vec3(inf, inf, inf),
      glm::vec3(0, 0, 0)
    )
  );

  // 
  for (size_t y = 0; y < _pxheight; y++) {
    for (size_t x = 0; x < _pxwidth; x++) {
      size_t index = y * _pxwidth + x;
      auto ray = get_ray(index);

      glm::vec3 p; // レイがヒットした座標
      glm::vec3 c; // レイがヒットした位置の色
      glm::vec3 n; // レイがヒットした位置の法線
      if (_obj.intersect(&p, &c,&n, ray) == true) {

        _hits[index]._position = p;
        
        // ライトの入射ベクトル
        glm::vec3 lightIncidence = -_light.calc_vector_point_to_light(p);

        // diffuse色の計算
        glm::vec3 diffuse = calc_diffuse(_light.diffuse_color(),c, lightIncidence, n);

        // ambient色の計算
        glm::vec3 ambient = calc_ambient(
          _light.ambient_color(), c
        );

        // specular色の計算
        glm::vec3 specular = calc_specular(
          get_eye_position(),
          _light.specular_color(), 
          c,
          lightIncidence,
          80, n, p);


        _hits[index]._color = ambient + diffuse + specular;

      }

    }
  }
}

main.cpp

#include <iostream>
#include <array>

#include<GL/freeglut.h>
#include<gl/GL.h>

#include "raycast.hpp"

#include<glm/gtc/type_ptr.hpp>

#include<glm/gtc/matrix_transform.hpp>

my_raycast mydata;


//! @brief グリッドを表示
//! @param [in] g グリッドオブジェクト
//! @param [in] dispcenter セルの中央を表示するか
void drawGrid(const grid& g, const bool dispcenter) {

  glLineWidth(1);
  glColor3d(1, 1, 1);
  for (size_t i = 0; i < g.cellCount(); i++) {
    glBegin(GL_LINE_LOOP);
    glVertex3fv(glm::value_ptr(g.p0(i)));
    glVertex3fv(glm::value_ptr(g.p1(i)));
    glVertex3fv(glm::value_ptr(g.p2(i)));
    glVertex3fv(glm::value_ptr(g.p3(i)));

    glEnd();
  }

  if (dispcenter) {
    glPointSize(1);
    glColor3d(1, 1, 1);
    glBegin(GL_POINTS);
    for (size_t i = 0; i < g.cellCount(); i++) {
      glVertex3fv(
        glm::value_ptr(g[i].center()));
    }
    glEnd();
  }
}



void drawXYZ() {
  glLineWidth(3);
  glBegin(GL_LINES);
  glColor3f(1, 0, 0);
  glVertex3f(0, 0, 0);
  glVertex3f(1, 0, 0);

  glColor3f(0, 1, 0);
  glVertex3f(0, 0, 0);
  glVertex3f(0, 1, 0);

  glColor3f(0, 0, 1);
  glVertex3f(0, 0, 0);
  glVertex3f(0, 0, 1);
  glEnd();
  glLineWidth(1);
}

void drawRays() {
  glBegin(GL_LINES);
  for (size_t i = 0; i < mydata.ray_count(); i++) {
    auto ray = mydata.get_ray(i);
    glColor3f(1.0, 1.0, 1.0);
    glVertex3fv(glm::value_ptr(ray[0]));
    glColor3f(0.0, 0.0, 1.0);
    glVertex3fv(glm::value_ptr(ray[1]));
  }
  glEnd();
}



//ウィンドウの幅と高さ
int width, height;

//描画関数
void disp(void) {

  glViewport(0, 0, width, height);

  glClearColor(0.2, 0.2, 0.2, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glm::mat4 proj = glm::perspectiveFov(glm::radians(45.f), (float)width, (float)height, 0.1f, 50.f);
  glLoadMatrixf(glm::value_ptr(proj));

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  //////////////////////////////////////////
  //////////////////////////////////////////
  // カメラの設定
  float z = mydata.lookatZ();
  glm::mat4 look = glm::lookAt(
    glm::vec3(5, 4, z-5 ),// eye
    glm::vec3(0, 0, z ), // lookat
    glm::vec3(0,1, 0)   // up
  );
  glLoadMatrixf(glm::value_ptr(look));
  //////////////////////////////////////////
  // グリッドを表示
  drawGrid(mydata.get_grid(0), false);//from
  drawGrid(mydata.get_grid(1), false);//to
  //////////////////////////////////////////
  // 作成したレイを表示
  //drawRays();
  //////////////////////////////////////////
  // レイと球の衝突位置を三次元空間に可視化
  glPointSize(5);
  glBegin(GL_POINTS);
  for (size_t i = 0; i < mydata.get_intersects().size(); i++) {
    glColor3fv(
      glm::value_ptr(mydata.get_intersects()[i]._color)
    );
    glVertex3fv(
      glm::value_ptr(mydata.get_intersects()[i]._position)
    );
  }
  glEnd();
  glPointSize(1);

  drawXYZ();

  // レイと球の衝突位置を二次元に可視化
  glPointSize(5);
  glBegin(GL_POINTS);
  for (size_t i = 0; i < mydata.get_intersects().size(); i++) {

    if (isinf(mydata.get_intersects()[i]._position.x) == false) {
      glColor3fv(
        glm::value_ptr(mydata.get_intersects()[i]._color)
      );
      glVertex3fv(
        glm::value_ptr(mydata.get_grid(0)[i].center())
      );
    }
  }
  glEnd();
  glPointSize(1);

  //ライトの位置表示
  glColor3d(1, 1, 0);
  glPointSize(10);
  glBegin(GL_POINTS);
  glVertex3fv(glm::value_ptr(mydata.get_light().position()));
  glEnd();
  glPointSize(1);
  glColor3d(1, 0, 0);

  //視点表示
  glColor3d(0, 0, 1);
  glPointSize(35);
  glBegin(GL_POINTS);
  glm::vec3 eyepos = mydata.get_eye_position();
  glVertex3fv(glm::value_ptr(eyepos));
  glEnd();
  glPointSize(1);



  glFlush();
}

//ウィンドウサイズの変化時に呼び出される
void reshape(int w, int h) {
  width = w; height = h;

  disp();
}

//エントリポイント
int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitWindowPosition(100, 50);
  glutInitWindowSize(500, 500);
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);


  mydata.set(0.f, 3.f,100,100);
  ///////////////////////////////////
  //球の設定
  /*
  mydata.set_object(
    object_sphere(
      glm::vec3(0.0, 0.0, 2.0), // 球の中心
      glm::vec3(1.0, 0.0, 0.0),   // 球の色
      0.5
    )
  );
  */
  //////////////////////////////////
//三角形の設定 mydata.set_object( object_triangle( std::array< glm::vec3, 3>{ glm::vec3(0.916876, -0.440909, 1.58713),//第一頂点 glm::vec3(-0.633301, -0.622765, 0.585358),//第二頂点 glm::vec3(-0.513684, 0.853145, 1.330333)//第三頂点 }, glm::vec3(1, 1, 0) ) ); /////////////////////////////////// mydata.set_light( light_point( glm::vec3(1.0, 1.0, 0.5), // ライトの位置 glm::vec3(0.5, 0.5, 0.5), // diffuse色 glm::vec3(0.2, 0.2, 0.2), // ambient色 glm::vec3(1.0, 1.0, 1.0) // specular色 ) ); mydata.raycast(); glutCreateWindow("sample"); glutDisplayFunc(disp); glutReshapeFunc(reshape); glutMainLoop(); return 0; }

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: