成功すれば画面中央に反射を行う球が1つとそれぞれ市松模様の背景と床が表示される、という内容になっています。
実行環境はVisualstudio2019です。また、Dxlibを使用しています。
現在のソースコードでは背景と床の表示はできているのですが球ができません。
また、Primitive.cpp内のPlane::IsHitにあるhについて逆向きにするために
auto h = Dot(-ray.vec, N);
とすると
重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態
エラー (アクティブ) E0349 これらのオペランドと一致する演算子 "-" はありません RayTracing C:\Users\xqbbc\Desktop\新しいフォルダー (2)\レイトレーシング\RayTracing2\RayTracing\RayTracing\Primitive.cpp 56
のエラーがでてしまいました。
最初に記載した通りの表示を行いたいのですが私では解決法に思い当たりませんでしたのでどなたかにご助力願いたいです。
main.cpp
#include<dxlib.h>
#include"Geometry.h"
#include"Primitive.h"
#include<cmath>
#include<algorithm>
const int screen_width = 640;
const int screen_height = 480;
///入射ベクトルに対する反射ベクトルを返す
///@param inVec 入射ベクトル
///@param normalVec 法線ベクトル
///@return 反射ベクトル
///@note 法線ベクトルは正規化されている前提で計算する
Vector3 ReflectVector(const Vector3& inVec, const Vector3& normalVec) {
// 実際の計算は
//R=I-2(I・N)N
//です。これをそのままプログラムにしてください
// ただし、スカラー*ベクトルの演算はないため
//式のスカラー*ベクトルはベクトル*スカラーに直してください
return inVec - normalVec * 2.0f * Dot(inVec, normalVec);
}
float Clamp(float value, float minVal = 0.0f, float maxVal = 0.0f)
{
return min(max(value, minVal), maxVal);
}
Vector3 Max(const Vector3& lval, const Vector3& rval)
{
return Vector3(max(lval.x, rval.x), max(lval.y, rval.y), max(lval.z, rval.z));
}
void
DrawPixelWithFloat(int x, int y, float r, float g, float b) {
DrawPixel(x, y, GetColor(r * 0xff, g * 0xff, b * 0xff));
}
///レイ(光線)と球体の当たり判定
///@param eye 視点の座標
///@param ray (視点からスクリーンピクセルへのベクトル)
///@param sphere 球
///@param t 視点から
///@hint レイは正規化しといたほうが使いやすいだろう
bool IsHitRayAndObject(const Position3& eye,const Vector3& ray,const Sphere& sp, float& t) {
//レイが正規化済みである前提で…
//↑正規化済み
//視点から球体中心へのベクトル(視線)を作ります
//球体の中心座標から視点の座標を引く
auto C = sp.pos - eye;
//中心から視線への内積をとります=>ベクトル長
//Dot関数で内積が分かる
auto d = Dot(C, ray);
//視線ベクトルとベクトル長をかけて、
//中心からの垂線下した点までのベクトルR'を求めます
auto rdash = ray * d;
//あとは中心ベクトル-R'の大きさと半径の大きさを
//比較して、|C-R'|<rであれば当たっています。
auto V = (C - rdash);
if (V.Magnitude() <= sp.radius) {
//視点からの距離tを求める
//①半径とベクトルVから垂線下した点
//から、衝突点までの距離wを求める
//w - √(半^2-V^2)
//
//②射影の長さdからwを引くこれが
//視点からの距離tとなる
//t=d-w;
float w = sqrt(sp.radius * sp.radius - V.SQMagnitude());
t = d - w; // +(isInner ? w : -w);
return true;
}
else {
return false;
}
}
Vector3 GetCheckerColorFromPos(const Position3& P) {
auto checkFlag = ((int)P.x / 40 + (int)P.z / 40) % 2 == 0;
if (P.x < 0) {
checkFlag = !checkFlag;
}
if (P.z < 0) {
checkFlag = !checkFlag;
}
if (checkFlag)
{
return Vector3(1, 1, 0);
}
else
{
return Vector3(1, 0.5, 0);
}
}
// ベクトル全体に0.0~1.0までの制限をかける
Vector3 Clamp(const Vector3 v) {
return Vector3(Clamp(v.x), Clamp(v.y), Clamp(v.z));
}
///float3(0.0~1.0)のカラー値をDxLib用のカラー値に変換する
UINT32 GetColorFromVector3(const Vector3& color) {
return GetColor(color.x * 255, color.y * 255, color.z * 255);
}
///レイトレーシング
///@param eye 視点座標
///@param sphere 球オブジェクト(そのうち複数にする)
void RayTracing(const Position3& eye,const Sphere& sphere) {
Vector3 light(1, -1, -1);
light.Normalize();
auto plane = Plane(Vector3(0.0, 1.0, 0.0), 150);
for (int y = 0; y < screen_height; ++y) {//スクリーン縦方向
for (int x = 0; x < screen_width; ++x) {//スクリーン横方向
// ①視点とスクリーン座標から視線ベクトルを作る
// スクリーンはz=0で、スクリーンの幅、高さの半分を
// x=0, y=0とする。
auto targetPos = Position3(x - screen_width / 2, screen_height / 2 - y, 0);
Ray ray;
ray.vec = targetPos - eye;
ray.pos = eye;
// ②正規化しとく
ray.vec.Normalize();
// ③IsHitRay関数がtrueだったら白く塗りつぶす
float t = 0.0f;
Vector3 hitNorm;
if (sphere.IsHit(ray, t, hitNorm)) {
// ①tとrayと球体中心から法線ベクトルNを求める
// ②法線ベクトルを正規化する
// ③法線ベクトルNとライトベクトル反転を内積とする
// ④明るさ=cosθ=内積なので、それもbに代入する
Vector3 N = hitNorm;
N.Normalize();
// ディフーズ(拡散反射)
// bはbrightness(輝度)のb(0.0~1.0)
float b = Clamp(Dot(N, - light));
// すぺきゅら(鏡面反射)は、光の反射ベクトルと
// 視線の逆ベクトルとの内積の○○乗で表す。
// ちなみにC言語ではw乗を表すためには
// pow関数を使用するpow(値,乗数)とする。
// つまり、pow(s,w);みたいにする。
// 出来てる人は、これで出てきた値を
// 明るさbに足してください。
float s = pow(Clamp(Dot(-ray.vec, ReflectVector(light, N))), 15);
auto diff = Vector3(0.5f, 1.0f, 0.5f) * b;
auto spec = Vector3(1.0f, 1.0f, 1.0f) * s;
auto col = Clamp(diff + spec);
col = Max(col, Vector3(0.1f, 0.1f, 0.1f));
auto lastCol = GetColorFromVector3(col);
// 反射ベクトルが床と当たるかどうかを計算する
// 当たれば、その床の色と乗算する
// 交点=わかってる[eye+ray*t]です
// 反射ベクトルは?反射ベクトルといっても
// 「視線の」反射ベクトルであることに注意
// 法線ベクトルが分かってるのでReflectVectorで
// 計算できる
// この交点(第2の出発点)と反射ベクトルから、
// 平面との交点を求める。
// 平面との交点が求まったら1射目と同様に色分けする。
// 手順①まず床と当たるかどうかを判定
Ray rray;
rray.pos = ray.pos + ray.vec * t;
rray.vec = ReflectVector(ray.vec, N);// 反射ベクトル
//屈折
// 反射ベクトルと床の内積から当たるかどうか判定する
if (plane.IsHit(rray, t, hitNorm) < 0.0f/*反射ベクトルが床と当たる*/) {
// ②当たってるはずだから内積を用いてtを求める
// ③tが求まったらintersectPos+rray*tで座標を求める
// ④座標から色分けを行う
auto rate = max(1.0f - Clamp(t / 1280.0f), 0.2f);
// tが求まりました。もともと求めたいのは交点Pです。
// P=E+Vtでした。このtが求まっています。つまり
// 交点の「座標」がわかるわけです。
// この座標をもとに市松模様を描いてみましょう。
auto P = rray.pos + rray.vec * t;
auto refVal = 0.75f;// 反射率
DrawPixel(x, y, GetColorFromVector3(GetCheckerColorFromPos(P) * refVal + col * (1.0 - refVal)));
}
else {
DrawPixel(x, y, lastCol);
}
}
else {
// 球体にあたりませんでした。
// 法線ベクトル(0, 1, 0)との内積が>=0だったら
// 当たりません。逆に言うと<だったら当たるので、
// そこを一定の色で塗りつぶしてください
if (plane.IsHit(ray, t, hitNorm)) {
// ここにきてる時点で必ず平面と当たる
// まずE+Vtが平面に到達した時のtを求める
// W=視点から平面までの距離
// h=視線ベクトル1個当たり平面に近づく距離
// だとすると求めたいt=W/hである。
// Wは座標ベクトルとすれば法線との内積
// VとNの内積分、平面に近づくただし
// 向きが逆なのでマイナスしておくこと
auto rate = max(1.0f - Clamp(t / 1280.0f), 0, 2f);
// tが求まりました。もともと求めたいのは交点Pです。
// P=E+Vtでした。このtが求まっています。つまり
// 交点の「座標」がわかるわけです。
// この座標をもとに市松模様を描いてみましょう。
auto P = ray.pos + ray.vec * t;
auto col = GetCheckerColorFromPos(P);
// ①床との交点Pから、光線(の逆)方向にレイを飛ばす
// ②もしそのレイが、球体と交差すれば色を暗くする
// timaricolni1.0未満の値を乗算する
// ③その結果の色を床の色として表示する
if (IsHitRayAndObject(P, -light, sphere, t)) {
col *= 0.25f;
}
DrawPixel(x, y, GetColorFromVector3(col));
}
else {
// ※塗りつぶしはDrawPixelという関数を使う。
if ((x / 40 + y / 40) % 2 == 0) {
DrawPixel(x, y, GetColor(0, 128, 0));
}
}
}
}
}
}
int WINAPI WinMain(HINSTANCE , HINSTANCE,LPSTR,int ) {
ChangeWindowMode(true);
SetGraphMode(screen_width, screen_height, 32);
SetMainWindowText(_T("1916220_中神麻由"));
DxLib_Init();
// スクリーンをz=0ということに
// しています。
auto eye = Vector3(0, 0, 300);
auto pos = Position3(0, 0, -150);
Sphere sp(150, pos);
SetDrawScreen(DX_SCREEN_BACK);
char keystate[256] = {};
while (ProcessMessage() != -1) {
ClearDrawScreen();
GetHitKeyStateAll(keystate);
if (keystate[KEY_INPUT_UP]) {
sp.pos.y += 5.0f;
}
if (keystate[KEY_INPUT_DOWN]) {
sp.pos.y -= 5.0f;
}
if (keystate[KEY_INPUT_RIGHT]) {
sp.pos.x += 5.0f;
}
if (keystate[KEY_INPUT_LEFT]) {
sp.pos.x -= 5.0f;
}
RayTracing(eye, sp);
ScreenFlip();
}
//WaitKey();
DxLib_End();
}
#include"Geometry.h"
#include<Dxlib.h>
#include<cmath>
void
Rect::Draw() {
DxLib::DrawBox(Left()*2, Top()*2, Right()*2, Bottom()*2, 0xffffffff, false);
}
void
Rect::Draw(Vector2& offset) {
DxLib::DrawBox((Left()+offset.x)*2, (Top()+offset.y)*2, (Right()+offset.x)*2, (Bottom()+offset.y)*2, 0xffffffff, false);
}
void
Vector2::operator*=(float scale) {
x *= scale;
y *= scale;
}
Vector2
Vector2::operator*(float scale) {
return Vector2(x*scale, y*scale);
}
Vector2 operator+(const Vector2& va, const Vector2 vb) {
return Vector2(va.x + vb.x, va.y + vb.y);
}
Vector2 operator-(const Vector2& va, const Vector2 vb){
return Vector2(va.x - vb.x, va.y - vb.y);
}
float
Vector2::Magnitude()const {
return hypot(x, y);
}
void
Vector2::Normalize() {
float mag = Magnitude();
x /= mag;
y /= mag;
}
Vector2
Vector2::Normalized() {
float mag = Magnitude();
return Vector2(x / mag, y /mag);
}
///内積を返す
float
Dot(const Vector2& va, const Vector2& vb) {
return va.x*vb.x + va.y*vb.y;
}
///外積を返す
float
Cross(const Vector2& va, const Vector2& vb) {
return va.x*vb.y - vb.x*va.y;
}
///内積演算子
float
operator*(const Vector2& va, const Vector2& vb) {
return Dot(va, vb);
}
///外積演算子
float
operator%(const Vector2& va, const Vector2& vb) {
return Cross(va, vb);
}
void
Vector2::operator+=(const Vector2& v) {
x += v.x;
y += v.y;
}
void
Vector2::operator-=(const Vector2& v) {
x -= v.x;
y -= v.y;
}
//-------ここから3Dのターン------
void
Vector3::operator*=(float scale) {
x *= scale;
y *= scale;
z *= scale;
}
Vector3
Vector3::operator*(float scale)const {
return Vector3(x*scale, y*scale,z*scale);
}
Vector3 operator+(const Vector3& va, const Vector3 vb) {
return Vector3(va.x + vb.x, va.y + vb.y,va.z+vb.z);
}
Vector3 operator-(const Vector3& va, const Vector3 vb) {
return Vector3(va.x - vb.x, va.y - vb.y,va.z-vb.z);
}
float
Vector3::SQMagnitude()const {
return x * x + y * y + z * z;
}
float
Vector3::Magnitude()const {
return sqrt(SQMagnitude());
}
void
Vector3::Normalize() {
float mag = Magnitude();
x /= mag;
y /= mag;
z /= mag;
}
Vector3
Vector3::Normalized() {
float mag = Magnitude();
return Vector3(x / mag, y / mag,z/mag);
}
///内積を返す
float
Dot(const Vector3& va, const Vector3& vb) {
return (va.x*vb.x + va.y*vb.y + va.z*vb.z);
}
///外積を返す
Vector3
Cross(const Vector3& va, const Vector3& vb) {
return Vector3(va.z*vb.y-va.y*vb.z, va.z*vb.x-va.x*vb.z, va.x*vb.y - vb.x*va.y);
}
///内積演算子
float
operator*(const Vector3& va, const Vector3& vb) {
return Dot(va, vb);
}
///外積演算子
Vector3
operator%(const Vector3& va, const Vector3& vb) {
return Cross(va, vb);
}
void
Vector3::operator+=(const Vector3& v) {
x += v.x;
y += v.y;
z += v.z;
}
void
Vector3::operator-=(const Vector3& v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
#pragma once
//サイズを表す構造体
struct Size {
float w;//幅
float h;//高さ
};
//2D座標・ベクトルを表す構造体
struct Vector2 {
Vector2():x(0),y(0){}
Vector2(float inx,float iny):x(inx),y(iny){}
float x, y;
///ベクトルの大きさを返します
float Magnitude()const;
///正規化(大きさを1に)します
void Normalize();
///正規化ベクトルを返します
Vector2 Normalized();
void operator+=(const Vector2& v);
void operator-=(const Vector2& v);
void operator*=(float scale);
Vector2 operator*(float scale);
Vector2 operator-() {
return Vector2(-x, -y);
}
};
Vector2 operator+(const Vector2& va, const Vector2 vb);
Vector2 operator-(const Vector2& va, const Vector2 vb);
///内積を返す
float Dot(const Vector2& va, const Vector2& vb);
///外積を返す
float Cross(const Vector2& va, const Vector2& vb);
///内積演算子
float operator*(const Vector2& va, const Vector2& vb);
///外積演算子
float operator%(const Vector2& va, const Vector2& vb);
//とりあえず「座標」って意味だとベクタより
//Positionのほうがよくね?って理由でこの名前
typedef Vector2 Position2;
//3D座標・ベクトルを表す構造体
struct Vector3 {
Vector3() :x(0), y(0) ,z(0){}
Vector3(float inx, float iny,float inz) :x(inx), y(iny) ,z(inz){}
float x, y,z;
///ベクトルの大きさを返します
float Magnitude()const;
///ベクトルの大きさの二乗を返します
float SQMagnitude()const;
///正規化(大きさを1に)します
void Normalize();
///正規化ベクトルを返します
Vector3 Normalized();
void operator+=(const Vector3& v);
void operator-=(const Vector3& v);
void operator*=(float scale);
Vector3 operator*(float scale)const;
Vector3 operator-() {
return Vector3(-x, -y,-z);
}
};
Vector3 operator+(const Vector3& va, const Vector3 vb);
Vector3 operator-(const Vector3& va, const Vector3 vb);
///内積を返す
float Dot(const Vector3& va, const Vector3& vb);
///外積を返す
Vector3 Cross(const Vector3& va, const Vector3& vb);
///内積演算子
float operator*(const Vector3& va, const Vector3& vb);
///外積演算子
Vector3 operator%(const Vector3& va, const Vector3& vb);
typedef Vector3 Position3;
///円を表す構造体
struct Circle {
float radius;//半径
Position2 pos; //中心座標
Circle() :radius(0), pos(0, 0) {}
Circle(float r, Position2& p) :radius(r), pos(p) {}
};
///矩形を表す構造体
struct Rect {
Position2 pos; //中心座標
int w, h;//幅,高さ
Rect() : pos(0, 0), w(0), h(0) {}
Rect(float x, float y, int inw, int inh) :
pos(x, y), w(inw), h(inh) {}
Rect(Position2& inpos, int inw, int inh) :
pos(inpos), w(inw), h(inh)
{}
void SetCenter(float x, float y) {
pos.x = x;
pos.y = y;
}
void SetCenter(const Position2& inpos) {
pos.x = inpos.x;
pos.y = inpos.y;
}
Vector2 Center() {
return pos;
}
float Left() { return pos.x - w / 2; }
float Top() { return pos.y - h / 2; }
float Right() { return pos.x + w / 2; }
float Bottom() { return pos.y + h / 2; }
void Draw();//自分の矩形を描画する
void Draw(Vector2& offset);//自分の矩形を描画する(オフセット付き)
};
#include "Primitive.h"
#include<cmath>
bool
Sphere::IsHit(const Ray& ray, float& t, Vector3& norm) const {
//レイが正規化済みである前提で…
//↑正規化済み
//視点から球体中心へのベクトル(視線)を作ります
//球体の中心座標から視点の座標を引く
auto C = pos - ray.pos;
//中心から視線への内積をとります=>ベクトル長
//Dot関数で内積が分かる
auto d = Dot(C, ray.vec);
//視線ベクトルとベクトル長をかけて、
//中心からの垂線下した点までのベクトルR'を求めます
auto rdash = ray.vec * d;
//あとは中心ベクトル-R'の大きさと半径の大きさを
//比較して、|C-R'|<rであれば当たっています。
auto V = (C - rdash);
if (V.Magnitude() <= radius) {
//視点からの距離tを求める
//①半径とベクトルVから垂線下した点
//から、衝突点までの距離wを求める
//w - √(半^2-V^2)
//
//②射影の長さdからwを引くこれが
//視点からの距離tとなる
//t=d-w;
float w = sqrt(radius * radius - V.SQMagnitude());
t = d -w; // (isInner ? w : -w);
auto intersectPos = ray.pos + ray.vec * t;
Vector3 N = intersectPos - pos;
norm = N.Normalized();
return true;
}
else {
return false;
}
}
bool
Plane::IsHit(const Ray& ray, float& t, Vector3& norm) const{
if (Dot(ray.vec, N) < 0.0f) {
// ここにきてる時点で必ず平面と当たる
// まずE+Vtが平面に到達した時のtを求める
// W=視点から平面までの距離
// h=視線ベクトル1個当たり平面に近づく距離
// だとすると求めたいt=W/hである。
// Wは座標ベクトルとすれば法線との内積
// VとNの内積分、平面に近づくただし
// 向きが逆なのでマイナスしておくこと
auto W = Dot(ray.pos, N);
auto h = Dot(-ray.vec, N);
t = (W + offset) / h;
norm = N;
return true;
}
else {
return false;
}
}
Plane::Plane(const Vector3& n, float ofset) :
N(n),
offset(ofset)
{
N.Normalize();
}
#pragma once
#include "Geometry.h"
/// <summary>
/// レイを表す
/// </summary>
struct Ray {
Position3 pos; // レイの始点
Vector3 vec; // レイの方向ベクトル
};
/// <summary>
/// 平面クラス
/// </summary>
enum class Pattern {
none, // 単色(模様無し)
stripe, // 縞模様
checker, // 市松模様
texture // 外部テクスチャ
};
/// <summary>
/// 表面材質に関する情報
/// </summary>
struct Material {
Vector3 col; // 色(0.0~1.0)
float reflectivity; // 反射率(0.0~1.0) 1.0の時全反射
Pattern pattern; // 各パターン
int handle; // パターンが画像だった場合のハンドル
float patternBlocksize; // none以外の時のパターン1個当たりのサイズを登録
};
/// <summary>
/// 図形の基底クラス
/// </summary>
class Primitive {
public:
Material material;
/// <summary>
/// 当たったかどうかとそれに付随する情報を返す
/// </summary>
/// <param name="ray"> 視線ベクトル(正規化済み)</param>
/// <param name="t">交点までの距離(当たったときのみ有効)</param>
/// <param name="norm">交点における法線(当たったと気のみ有効)</param>
/// <returns>true当たり/falseはずれ</returns>
virtual bool IsHit(const Ray& ray, float& t, Vector3& norm)const = 0;
};
/// <summary>
/// 球体クラス
/// </summary>
class Sphere :public Primitive {
public:
float radius;//半径
Position3 pos; //中心座標
Sphere() :radius(0), pos(0, 0, 0) {}
Sphere(float r, Position3& p) :radius(r), pos(p) {}
/// <summary>
/// 当たったかどうかを返す
/// </summary>
/// <param name="ray">レイ</param>
/// <param name="t">交点までの距離</param>
/// <param name="norm">交点における法線</param>
/// <returns>当たったかどうか(true/false)</returns>
/// <remarks>球体そのものに影響を与えない(なのでconst)</remarks>
bool IsHit(const Ray& ray, float& t, Vector3& norm)const;
};
/// <summary>
/// 平面クラス
/// </summary>
class Plane :public Primitive {
public:
Vector3 N; // 法線ベクトル
float offset; // 原点からのオフセット
Plane(const Vector3& n, float ofset = 0.0f);
bool IsHit(const Ray& ray, float& t, Vector3& norm)const;
};