2016-08-20 11 views
2

私は問題があります:article on scratchapixelをraytracingのC++コードで読みました。 C++は問題ありません。私はそれをPythonに変換しようとしましたが、それは機能しました(17倍の遅い結果と4倍の解像度低下)。私はそれをC#に変換しようとしましたが、私のコードは動作しません。私が見ることができるのは白い白い800x600画像だけです。 C++コードについては、以前にリンクされた記事を参照してください。Raytracerが期待する出力を出力しない

これは、C#のコードのように、それの私の解釈です:

using System; 
using System.Collections.Generic; 

namespace raytracer 
{ 
class Program 
{ 
    const int MAX_RAY_DEPTH = 8; 
    const float FAR = 100000000; 

    public static void Main(string[] args) 
    { 
     Sphere[] spheres = new Sphere[7]; 
     spheres[0] = new Sphere(new Vec3f(0.0f, -10004, -20), 10000, new Vec3f(0.20f, 0.20f, 0.20f), 0, 0.0f); 
     spheres[1] = new Sphere(new Vec3f(0.0f,  0, -20),  4, new Vec3f(1.00f, 0.32f, 0.36f), 1, 0.5f); 
     spheres[2] = new Sphere(new Vec3f(5.0f,  -1, -15),  2, new Vec3f(0.90f, 0.76f, 0.46f), 1, 0.0f); 
     spheres[3] = new Sphere(new Vec3f(5.0f,  0, -25),  3, new Vec3f(0.65f, 0.77f, 0.97f), 1, 0.0f); 
     spheres[4] = new Sphere(new Vec3f(-5.5f,  0, -15),  3, new Vec3f(0.90f, 0.90f, 0.90f), 1, 0.0f); 
     spheres[5] = new Sphere(new Vec3f( 2f,  2, -30),  4, new Vec3f(0.53f, 0.38f, 0.91f), 1, 0.7f); 
     spheres[6] = new Sphere(new Vec3f( 0,  20, -25),  3, new Vec3f(0.00f, 0.00f, 0.00f), 0, 0.0f, new Vec3f(3)); 
     Render(spheres); 
    } 

    public class Collision 
    { 
     public float t0, t1; 
     public bool collide; 
     public Collision(bool col, float tt0 = 0, float tt1 = 0) 
     { 
      t0 = tt0; 
      t1 = tt1; 
      collide = col; 
     } 
    } 

    public class Vec3f 
    { 
     public float x, y, z; 
     public Vec3f(){ x = y = z = 0; } 
     public Vec3f(float v){ x = y = z = v; } 
     public Vec3f(float xx, float yy, float zz){ x = xx; y = yy; z = zz; } 

     public Vec3f normalize() 
     { 
      float nor2 = length2(); 
      if (nor2 > 0) 
      { 
       float invNor = 1/(float)Math.Sqrt(nor2); 
       x *= invNor; y *= invNor; z *= invNor; 
      } 
      return this; 
     } 
     public static Vec3f operator *(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x * r.x, l.y * r.y, l.z * r.z); 
     } 
     public static Vec3f operator *(Vec3f l, float r) 
     { 
      return new Vec3f(l.x * r, l.y * r, l.z * r); 
     } 
     public float dot(Vec3f v) 
     { 
      return x * v.x + y * v.y + z * v.z; 
     } 
     public static Vec3f operator -(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x - r.x, l.y - r.y, l.z - r.z); 
     } 
     public static Vec3f operator +(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x + r.x, l.y + r.y, l.z + r.z); 
     } 
     public static Vec3f operator -(Vec3f v) 
     { 
      return new Vec3f(-v.x, -v.y, -v.z); 
     } 
     public float length2() 
     { 
      return x * x + y * y + z * z; 
     } 
     public float length() 
     { 
      return (float)Math.Sqrt(length2()); 
     } 
    } 

    public class Sphere 
    { 
     public Vec3f center, surfaceColor, emissionColor; 
     public float radius, radius2; 
     public float transparency, reflection; 
     public Sphere(Vec3f c, float r, Vec3f sc, float refl = 0, float transp = 0, Vec3f ec = null) 
     { 
      center = c; radius = r; radius2 = r * r; 
      surfaceColor = sc; emissionColor = (ec == null) ? new Vec3f(0) : ec; 
      transparency = transp; reflection = refl; 
     } 

     public Collision intersect(Vec3f rayorig, Vec3f raydir) 
     { 
      Vec3f l = center - rayorig; 
      float tca = l.dot(raydir); 
      if (tca < 0){ return new Collision(false); } 
      float d2 = l.dot(l) - tca * tca; 
      if (d2 > radius2){ return new Collision(false); } 
      Collision coll = new Collision(true); 
      float thc = (float)Math.Sqrt(radius2 - d2); 
      coll.t0 = tca - thc; 
      coll.t1 = tca + thc; 
      return coll; 
     } 
    } 

    public static float mix(float a, float b, float mix) 
    { 
     return b * mix + a * (1 - mix); 
    } 

    public static Vec3f trace(Vec3f rayorig, Vec3f raydir, Sphere[] spheres, int depth) 
    { 
     float tnear = FAR; 
     Sphere sphere = null; 
     foreach(Sphere i in spheres) 
     { 
      float t0 = FAR, t1 = FAR; 
      Collision coll = i.intersect(rayorig, raydir); 
      if (coll.collide) 
      { 
       if (coll.t0 < 0) { coll.t0 = coll.t1; } 
       if (coll.t0 < tnear) { tnear = coll.t0; sphere = i; } 
      } 
     } 
     if (sphere == null){ return new Vec3f(2); } 
     Vec3f surfaceColor = new Vec3f(0); 
     Vec3f phit = rayorig + raydir * tnear; 
     Vec3f nhit = phit - sphere.center; 
     nhit.normalize(); 
     float bias = 1e-4f; 
     bool inside = false; 
     if (raydir.dot(nhit) > 0){ nhit = -nhit; inside = true; } 
     if ((sphere.transparency > 0 || sphere.reflection > 0) && depth < MAX_RAY_DEPTH) 
     { 
      float facingratio = -raydir.dot(nhit); 
      float fresneleffect = mix((float)Math.Pow(1 - facingratio, 3), 1, 0.1f); 
      Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit); 
      refldir.normalize(); 
      Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1); 
      Vec3f refraction = new Vec3f(0); 
      if (sphere.transparency > 0) 
      { 
       float ior = 1.1f; float eta = 0; 
       if (inside){ eta = ior; } else { eta = 1/ior; } 
       float cosi = -nhit.dot(raydir); 
       float k = 1 - eta * eta * (1 - cosi * cosi); 
       Vec3f refrdir = raydir * eta + nhit * (eta * cosi - (float)Math.Sqrt(k)); 
       refrdir.normalize(); 
       refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1); 
      } 
      surfaceColor = 
      (
       reflection * fresneleffect + refraction * 
       (1 - fresneleffect) * sphere.transparency) * sphere.surfaceColor; 
     } 
     else 
     { 
      foreach(Sphere i in spheres) 
      { 
       if (i.emissionColor.x > 0) 
       { 
        Vec3f transmission = new Vec3f(1); 
        Vec3f lightDirection = i.center - phit; 
        lightDirection.normalize(); 
        foreach(Sphere j in spheres) 
        { 
         if (i != j) 
         { 
          Collision jcoll = j.intersect(phit + nhit * bias, lightDirection); 
          if (jcoll.collide) 
          { 
           transmission = new Vec3f(0); 
           break; 
          } 
         } 
        } 
        surfaceColor += sphere.surfaceColor * transmission * Math.Max(0, nhit.dot(lightDirection)) * i.emissionColor; 

       } 
      } 
     } 
     return surfaceColor; 
    } 

    public static void Render(Sphere[] spheres) 
    { 
     int width = 800, height = 600; 
     List<Vec3f> image = new List<Vec3f>(); 
     float invWidth = 1/width, invHeight = 1/height; 
     float fov = 30, aspectratio = width/height; 
     float angle = (float)Math.Tan(Math.PI * 0.5 * fov/180); 
     for (int y = 0; y < height; y++) 
     { 
      for(int x = 0; x < width; x++) 
      { 
       float xx = (2 * ((x + 0.5f) * invWidth) - 1) * angle * aspectratio; 
       float yy = (1 - 2 * ((y + 0.5f) * invHeight)) * angle; 
       Vec3f raydir = new Vec3f(xx, yy, -1); 
       raydir.normalize(); 
       image.Add(trace(new Vec3f(0), raydir, spheres, 0)); 
      } 
     } 
     Console.Write("P3 800 600 255\r\n"); 
     int line = 150; 
     for(int i = 0; i < width * height; ++i) 
     { 
      if(line <= 0) {line = 150; Console.Write("\r\n");} 
      line--; 
      Vec3f pixel = GetColor(image[i]); 
      Console.Write(pixel.x + " " + pixel.y + " " + pixel.z); 
     } 
    } 

    public static Vec3f GetColor(Vec3f col) 
    { 
     return new Vec3f(Math.Min(1, col.x)* 255, Math.Min(1, col.y)* 255, Math.Min(1, col.z)* 255); 
    } 
} 
} 

誰もが間違っているものを参照してください?

EDIT プログラムはコンソール画面にトレースカラーを書き込んでいます。次に、windowsバッチファイルを使用してppmファイルに書き込むことができます。 私はあなたのC#コードは、基本的な問題 「raytracer.exe> out.ppm」とcsc.exe 「csc.exeのraytracer.cs」 し、実行プログラムを使用して

+0

C++で動作し、C++が通常より速いので、おそらくC#からP/InvokeするDLLに入れるべきでしょうか?私はそれを変換する理由を知らないが、それは私がやることだ。 –

+0

さて、結果のppmファイルをチェックしましたか?実際には何が入っていますか? – Cubic

+1

@VisualVincentパフォーマンスに関してではなく、彼らはエクササイズとして変換しています – Cubic

答えて

2

を実行した作成どこint値を使用しています浮動小数点の結果が必要です。 C++コードの場合と同じように、元のintの値は、除算で使用する前にfloatに変換されます。これをC#コードでも行う必要があります。具体的には、あなたのinvHeightinvWidth、すべてではなく、整数数学の浮動小数点演算使用して実行する必要がありaspectratio計算:また

float invWidth = 1f/width, invHeight = 1f/height; 
    float fov = 30, aspectratio = (float)width/height; 

を、あなたのテキスト出力は、実際には、画素間のスペースが不足しています。コードのバージョンでは、行の最初を除いて、各画素値の前にスペースを挿入することによって、この問題を解決することができます

for(int i = 0; i < width * height; ++i) 
    { 
     if(line <= 0) {line = 150; Console.Write("\r\n");} 
     else if (line < 150) Console.Write(" "); 
     line--; 
     Vec3f pixel = GetColor(image[i]); 
     Console.Write(pixel.x + " " + pixel.y + " " + pixel.z); 
    } 

それとも、もちろん、ただ常にスペースを書き込むことができます。

 Console.Write(pixel.x + " " + pixel.y + " " + pixel.z + " "); 

はまた、あなたがtrace()メソッドの最後にsphere.emissionColorを追加することができなかったという点で、変換時のマイナーなエラーが発生しました:

 return surfaceColor + sphere.emissionColor; 

これらの3つの変化が修正されますよあなたが望む結果を生み出すことができます。


今、IMHOでは、いくつかの変更を検討する価値があると言いました。最も顕著なのは、Vec3fCollisionにはclassの代わりにstructタイプを使用することです。 structclassの唯一の実際の違いがメンバーのデフォルトアクセシビリティであるC++とは異なり、これらの2種類のタイプは基本的な動作が大きく異なります。このようなプログラムでは、classの代わりにstructを使用すると、ヒープ割り当てデータの量を最小限に抑えることでパフォーマンスを大幅に向上させることができます。特に一時的にしか存在せず、ガベージコレクタで収集する必要がありますあなたのプログラムは他の仕事をしようとしています。

また、データタイプをfloatからdoubleに変更することを検討することもできます。私は両方の方法でコードをテストしました。視覚的な出力に違いはありませんが、レンダリングは平均でdoubleで2.1秒、平均では2.8秒でfloatと表示されていました。スピードの25%の向上はおそらくあなたが持っていると思うものです。 :)に

限りstructclass VS質問は算術演算のためのより高速なdoubleタイプを使用して、私のテストでは、行くように、私はこれらのタイプのclassを使用して代わりにclassstructを使用して高速で36%の改善が(で実行見ました3.3秒、structは2.1秒で実行されます)。

同時に、値が変更可能なタイプstructでは、見つけにくいバグが発生する可能性があります。 A structは本当に不変でなければならないので、変更の一部として、タイプを調整しました。これはCollisionタイプでは比較的単純でしたが、Vec3fの場合、コードにはこれらの値が(normalize()を呼び出して)変更された場所がいくつかあります。変更不可能なstructの値を変更するには、元の値の代わりにnormalize()メソッドの戻り値が使用されるようにこれらをすべて変更する必要がありました。私が作った

その他の変更が含まれます:

  • Vec3f()コンストラクタを削除します。これは、とにかくstruct型では許可されていません。デフォルトコンストラクタが適切な処理を行うため、これは必要ありません。
  • t0 < 0の衝突チェックをCollisionタイプに移動すると、そのタイプの不変性がサポートされます。
  • Sphereを変更すると、元のC++のように整数インデックスを使用するようにループが戻ります。 foreachステートメントには、ループごとに列挙子を割り当てる必要があります。配列を直接索引付けすることで、これらの不要な割り振りを避けることができます。つまり、変数名がより意味をなさないことを意味します(iおよびjは通常索引用に予約されています。
  • また、etaの初期化やC++コードに似たコードの整列など、他の場所のC++コードに似ています。
  • コードをList<Vec3f>から代わりに配列に変更しました。これはより効率的であり、リストのバッキングストレージを定期的に再割り当てする必要がなくなります。

最後に、私はプログラムの出力を大幅に変更しました。私はコンソールウィンドウがすべての出力を印刷するのを待つことに興味がなく、テキストベースのイメージ出力を読み込んで表示するプログラムを追跡してインストールしようとすることにも関心がありませんでした。

代わりに、メモリ内の文字列にのみ書き込むようにテキスト出力を変更し、コードを追加して、プログラムが実際にPNGファイルを生成するようにしました。パーティープログラム。

すべて言ったと行って、これは私が得たものである:上記のために、あなたをコンパイルすること

class Program 
{ 
    const int MAX_RAY_DEPTH = 8; 
    const float FAR = 100000000; 

    public static void Main(string[] args) 
    { 
     Sphere[] spheres = new Sphere[7]; 
     spheres[0] = new Sphere(new Vec3f(0.0f, -10004, -20), 10000, new Vec3f(0.20f, 0.20f, 0.20f), 0, 0.0f); 
     spheres[1] = new Sphere(new Vec3f(0.0f,  0, -20),  4, new Vec3f(1.00f, 0.32f, 0.36f), 1, 0.5f); 
     spheres[2] = new Sphere(new Vec3f(5.0f,  -1, -15),  2, new Vec3f(0.90f, 0.76f, 0.46f), 1, 0.0f); 
     spheres[3] = new Sphere(new Vec3f(5.0f,  0, -25),  3, new Vec3f(0.65f, 0.77f, 0.97f), 1, 0.0f); 
     spheres[4] = new Sphere(new Vec3f(-5.5f,  0, -15),  3, new Vec3f(0.90f, 0.90f, 0.90f), 1, 0.0f); 
     spheres[5] = new Sphere(new Vec3f( 2f,  2, -30),  4, new Vec3f(0.53f, 0.38f, 0.91f), 1, 0.7f); 
     spheres[6] = new Sphere(new Vec3f( 0,  20, -30),  3, new Vec3f(0.00f, 0.00f, 0.00f), 0, 0.0f, new Vec3f(3)); 
     Render(spheres); 
    } 

    public struct Collision 
    { 
     public readonly float t0, t1; 
     public readonly bool collide; 

     public Collision(bool col, float tt0, float tt1) 
     { 
      t0 = tt0 < 0 ? tt1 : tt0; 
      t1 = tt1; 
      collide = col; 
     } 
    } 

    public struct Vec3f 
    { 
     public readonly float x, y, z; 
     public Vec3f(float v) { x = y = z = v; } 
     public Vec3f(float xx, float yy, float zz) { x = xx; y = yy; z = zz; } 

     public Vec3f normalize() 
     { 
      float nor2 = length2(); 
      if (nor2 > 0) 
      { 
       float invNor = 1/(float)Math.Sqrt(nor2); 

       return new Vec3f(x * invNor, y * invNor, z * invNor); 
      } 

      return this; 
     } 
     public static Vec3f operator *(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x * r.x, l.y * r.y, l.z * r.z); 
     } 
     public static Vec3f operator *(Vec3f l, float r) 
     { 
      return new Vec3f(l.x * r, l.y * r, l.z * r); 
     } 
     public float dot(Vec3f v) 
     { 
      return x * v.x + y * v.y + z * v.z; 
     } 
     public static Vec3f operator -(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x - r.x, l.y - r.y, l.z - r.z); 
     } 
     public static Vec3f operator +(Vec3f l, Vec3f r) 
     { 
      return new Vec3f(l.x + r.x, l.y + r.y, l.z + r.z); 
     } 
     public static Vec3f operator -(Vec3f v) 
     { 
      return new Vec3f(-v.x, -v.y, -v.z); 
     } 
     public float length2() 
     { 
      return x * x + y * y + z * z; 
     } 
     public float length() 
     { 
      return (float)Math.Sqrt(length2()); 
     } 
    } 

    public class Sphere 
    { 
     public readonly Vec3f center, surfaceColor, emissionColor; 
     public readonly float radius, radius2; 
     public readonly float transparency, reflection; 
     public Sphere(Vec3f c, float r, Vec3f sc, float refl = 0, float transp = 0, Vec3f? ec = null) 
     { 
      center = c; radius = r; radius2 = r * r; 
      surfaceColor = sc; emissionColor = (ec == null) ? new Vec3f() : ec.Value; 
      transparency = transp; reflection = refl; 
     } 

     public Collision intersect(Vec3f rayorig, Vec3f raydir) 
     { 
      Vec3f l = center - rayorig; 
      float tca = l.dot(raydir); 
      if (tca < 0) { return new Collision(); } 
      float d2 = l.dot(l) - tca * tca; 
      if (d2 > radius2) { return new Collision(); } 
      float thc = (float)Math.Sqrt(radius2 - d2); 
      return new Collision(true, tca - thc, tca + thc); 
     } 
    } 

    public static float mix(float a, float b, float mix) 
    { 
     return b * mix + a * (1 - mix); 
    } 

    public static Vec3f trace(Vec3f rayorig, Vec3f raydir, Sphere[] spheres, int depth) 
    { 
     float tnear = FAR; 
     Sphere sphere = null; 
     for (int i = 0; i < spheres.Length; i++) 
     { 
      Collision coll = spheres[i].intersect(rayorig, raydir); 
      if (coll.collide && coll.t0 < tnear) 
      { 
       tnear = coll.t0; 
       sphere = spheres[i]; 
      } 
     } 
     if (sphere == null) { return new Vec3f(2); } 
     Vec3f surfaceColor = new Vec3f(); 
     Vec3f phit = rayorig + raydir * tnear; 
     Vec3f nhit = (phit - sphere.center).normalize(); 
     float bias = 1e-4f; 
     bool inside = false; 
     if (raydir.dot(nhit) > 0) { nhit = -nhit; inside = true; } 
     if ((sphere.transparency > 0 || sphere.reflection > 0) && depth < MAX_RAY_DEPTH) 
     { 
      float facingratio = -raydir.dot(nhit); 
      float fresneleffect = mix((float)Math.Pow(1 - facingratio, 3), 1, 0.1f); 
      Vec3f refldir = (raydir - nhit * 2 * raydir.dot(nhit)).normalize(); 
      Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1); 
      Vec3f refraction = new Vec3f(); 
      if (sphere.transparency > 0) 
      { 
       float ior = 1.1f; float eta = inside ? ior : 1/ior; 
       float cosi = -nhit.dot(raydir); 
       float k = 1 - eta * eta * (1 - cosi * cosi); 
       Vec3f refrdir = (raydir * eta + nhit * (eta * cosi - (float)Math.Sqrt(k))).normalize(); 
       refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1); 
      } 
      surfaceColor = (
       reflection * fresneleffect + 
       refraction * (1 - fresneleffect) * sphere.transparency) * sphere.surfaceColor; 
     } 
     else 
     { 
      for (int i = 0; i < spheres.Length; i++) 
      { 
       if (spheres[i].emissionColor.x > 0) 
       { 
        Vec3f transmission = new Vec3f(1); 
        Vec3f lightDirection = (spheres[i].center - phit).normalize(); 
        for (int j = 0; j < spheres.Length; j++) 
        { 
         if (i != j) 
         { 
          Collision jcoll = spheres[j].intersect(phit + nhit * bias, lightDirection); 
          if (jcoll.collide) 
          { 
           transmission = new Vec3f(); 
           break; 
          } 
         } 
        } 
        surfaceColor += sphere.surfaceColor * transmission * 
         Math.Max(0, nhit.dot(lightDirection)) * spheres[i].emissionColor; 

       } 
      } 
     } 

     return surfaceColor + sphere.emissionColor; 
    } 

    public static void Render(Sphere[] spheres) 
    { 
     int width = 800, height = 600; 
     Vec3f[] image = new Vec3f[width * height]; 
     int pixelIndex = 0; 
     float invWidth = 1f/width, invHeight = 1f/height; 
     float fov = 30, aspectratio = (float)width/height; 
     float angle = (float)Math.Tan(Math.PI * 0.5 * fov/180); 
     for (int y = 0; y < height; y++) 
     { 
      for (int x = 0; x < width; x++, pixelIndex++) 
      { 
       float xx = (2 * ((x + 0.5f) * invWidth) - 1) * angle * aspectratio; 
       float yy = (1 - 2 * ((y + 0.5f) * invHeight)) * angle; 
       Vec3f raydir = new Vec3f(xx, yy, -1).normalize(); 

       image[pixelIndex] = trace(new Vec3f(), raydir, spheres, 0); 
      } 
     } 

     StringWriter writer = new StringWriter(); 
     WriteableBitmap bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Rgb24, null); 

     bitmap.Lock(); 

     unsafe 
     { 
      byte* buffer = (byte*)bitmap.BackBuffer; 

      { 
       writer.Write("P3 800 600 255\r\n"); 
       for (int y = 0; y < height; y++) 
       { 
        for (int x = 0; x < width; ++x) 
        { 
         if (x > 0) { writer.Write(" "); } 
         Vec3f pixel = GetColor(image[y * width + x]); 
         writer.Write(pixel.x + " " + pixel.y + " " + pixel.z); 

         int bufferOffset = y * bitmap.BackBufferStride + x * 3; 
         buffer[bufferOffset] = (byte)pixel.x; 
         buffer[bufferOffset + 1] = (byte)pixel.y; 
         buffer[bufferOffset + 2] = (byte)pixel.z; 
        } 

        writer.WriteLine(); 
       } 
      } 
     } 

     bitmap.Unlock(); 


     var encoder = new PngBitmapEncoder(); 

     using (Stream stream = File.OpenWrite("temp.png")) 
     { 
      encoder.Frames.Add(BitmapFrame.Create(bitmap)); 
      encoder.Save(stream); 
     } 

     string result = writer.ToString(); 
    } 

    public static Vec3f GetColor(Vec3f col) 
    { 
     return new Vec3f(Math.Min(1, col.x) * 255, Math.Min(1, col.y) * 255, Math.Min(1, col.z) * 255); 
    } 
} 

注」:ここで

ray-traced balls

はコードの私の最終バージョンですプロジェクト内の参照をPresentationCore、WindowsBase、およびSystemに追加する必要があります。Xamlアセンブリ。また、プロジェクト設定で「安全でないコードを許可する」オプションをチェックする必要があります。

+0

ありがとうございます!今私は間違いを見る。私はプログラミングで浮かんでいるのは実際の数学(1/2のような)のようだと思っていました。今、私はなぜC++コードがどこでも式のように0.0を使う理由を理解しています。ありがとうございました。私はこれらを私の心に留めておきます。 –

関連する問題