9. 同次座標と平行移動
第8章では、行列で物を拡大縮小、回転、せん断、鏡映する方法を学びました。
然し、どのゲームでも1フレームに何千回も行うのに、まだ行列で書けない変換が一つあります。
単に物を動かす事です。
モデルをある場所から別の場所へ動かす事を平行移動と言います。
此の章では、何故平行移動が3×3行列に収まらないのか、そして其れでも収める為のとてもシンプルな工夫を紹介します。
其の工夫とは、w と言う4つ目の数を追加する事です。
此れを同次座標と言い、此れから書く全ての頂点シェーダーが float4 / vec4 と4×4行列を使う理由でもあります。
此の章の終わりには、物体を世界の好きな場所に置くモデル行列を作れる様になります。
9.1 問題:平行移動は線型ではない
点を平行移動するとは、点が何処にあっても同じオフセットを足す事です。
点を 、オフセットを とすると、移動後の点は次の通りです:
テーブルの上でコップを滑らせる所を想像して下さい。
コップの全ての部分が、同じ方向に同じ距離だけ動きます。
回転もしないし、大きくもならないし、曲がりもしません。
一番簡単な変換に見えますね。
では、何故第8章に入っていなかったのでしょうか?
第8章で線型変換について言った事を思い出して下さい:線型変換は原点を必ず其の場に残します。
どんな行列にゼロベクトルを掛けても、全ての列に0が掛かるので、結果はゼロベクトルです:
然し、平行移動は原点を動かします。
なら、原点 は に移ります。
従って、平行移動が出来る2×2行列(3Dなら3×3行列)は存在しません。
もう一つの規則 も破れます。
和を平行移動すると、 は1回だけ足されます:。
其れぞれを平行移動してから足すと、 は2回足されます:。
此の二つは同じではないので、平行移動は線型ではありません。
此れは本当に困ります。
拡大縮小、回転、平行移動を一つの行列にまとめて、何百万もの頂点に其の一つの行列を掛けたいのです(第7章の結合法則を思い出して下さい)。
平行移動だけ別の「ベクトルを足す」ステップにしなければならないなら、此の計画は崩れてしまいます。
9.2 工夫:座標を一つ増やす
アイデアはこうです:全ての点に、常に1である座標を一つ追加します。
此の追加の座標を w と呼びます。
2Dの点 は になり、此れで3×3行列が使えます。
何故其れで上手く行くのでしょうか?
最後の数が1のベクトルに3×3行列を掛けると、どうなるか見てみましょう:
3列目には w の1が掛かるので、結果に其のまま足されます。
此れが平行移動です!
そして一番下の行 のお陰で、結果も のままなので、更に別の行列を続けて掛けられます。
2Dでウォーミングアップ
点 を だけ動かしてみましょう。
先ず、点を 付きで書きます:。
次に、出力の各成分は、行列の1行とベクトルの内積です(第6章):
w を外して答えを読むと です。
丁度 ですね。
此れはズルではない
手品の様に感じるかもしれないので、実際に何が起きているのかを図で見てみましょう。
平らな2Dの世界を、3D空間の原点から1だけ浮いた平面 の上に置きます。
上の3×3行列は、此の3D空間でのせん断(第8章)です:各層を高さ w に応じた量だけ横にずらします。
高さ1の層は丁度 だけずれます。
3Dの原点(高さ0)は全く動かないので、3×3行列は依然として完全な線型変換です。
1階上に住んでいるあたし達の2Dの世界だけが、平行移動した様に見えるのです。
つまり、規則は何も破っていません。
次元を一つ上げただけで、其処では平行移動がせん断になるのです。
9.3 公式
3Dでも4つの数で同じ事をします:点 は になります。
此の4つの数を、其の点の同次座標と言います。
4×4の平行移動行列は次の通りです:
点に掛けると:
此の章のもう一つの大事な規則は、w の値についてです:
| 種類 | w | 例 | 平行移動の影響を受けるか |
|---|---|---|---|
| 点(位置) | 1 | 頂点の位置、カメラの位置 | 受ける |
| 方向(矢印) | 0 | 法線、速度、光の方向 | 受けない |
9.4 仕組み
計算例:点を動かす
点 を だけ動かしましょう。
を付けると点は です。
1行ずつ計算します:
移動後の点は 、つまり です。
同じ計算を、第6章のもう一つの見方でも見てみましょう:結果は列の線型結合で、重みはベクトルの成分です。
此の見方だと、工夫の全体がとてもよく分かります。
最初の3列は手付かずの基底ベクトルなので、x、y、zは其のまま通り抜けます。
4列目に平行移動が入っていて、其れには w が掛かります。
なら、平行移動が1回足されます。
計算例:方向は動かない
今度は方向 を で、同じ行列に掛けます:
4列目には0が掛かったので、平行移動は消えてしまいました。
方向は入った時と全く同じまま、 のまま出て来ます。
何故方向は平行移動を無視するべきなのか
此れは偶然ではなく、正に欲しい振る舞いです。
方向は矢印であって、場所ではありません。
長さがあって何処かを指していますが、何処かに住んでいる訳ではないのです。
- 北へ秒速20mで走っている車を、丸ごと東へ100m動かしても、北へ秒速20mで走り続けます。速度は方向です。
- +X軸を向いている壁を動かしても、+Xを向いたままです。面の法線は方向です。
- 太陽光は、東京にいても自分の庭にいても同じ方向から来ます。平行光源は方向です。
回転と拡大縮小は方向にも影響するべきです(車の向きを変えたら速度も向きを変えます)。
そして実際に影響します。其れらは最初の3列にあって、普段通りx、y、zが掛かるからです。
で無効になるのは、平行移動の列だけです。
注: 法線については、拡大縮小が不均一な場合にもう一つ落とし穴があります。其れは第10章の「法線行列」で直します。
wの帳簿付け
w の値は、点と方向で計算する時の間違いも防いでくれます。
w も他の成分と同じ様に足したり引いたりするだけです:
- 点 − 点 = 方向、何故なら 。AからBへの矢印(第2章)は方向です。
- 点 + 方向 = 点、何故なら 。ある場所から矢印に沿って歩けば、別の場所に着きます。
- 方向 + 方向 = 方向、何故なら 。
- 点 + 点は になり、どちらでもありません。二つの場所を足す事に意味は無いのです(「東京 + パリ」とは何でしょう?)。
但し、二点の中点を見て下さい: は で、ちゃんとした点です。
一般に、点の重み付き和は、重みの合計が1の時に限って点になります。
だから第2章のlerp、 は常に点になるのです:。
9.5 アフィン変換
「線型変換の後に平行移動」と言う変換をアフィン変換と言います。
拡大縮小、回転、せん断、鏡映、平行移動のどんな組み合わせもアフィン変換です。
式で書くと次の通りです:
は3×3行列(線型部分、第8章)で、 は平行移動です。
同次座標を使えば、両方を一つの4×4行列に詰め込めます:
右側の短い書き方をブロック行列と言います: は左上の3×3の部分全体、 は右側の3つの数の列、 は一番下の3つの0の行を表します。
だからこそ、第8章の scale や rotateX / rotateY / rotateZ は最初から4×4行列を返していたのです:其れらは がゼロのアフィン行列です。
そして translate は が単位行列のアフィン行列です。
デバッグにとても便利な事実があります: は4列目そのものなので、モデル行列の最後の列は、物体の原点が世界の何処に行くかを表します。
物体が変な場所に表示されたら、先ず最後の列を表示してみましょう。
アフィン変換は、直線を直線のまま、平行線を平行のままに保ち、中点も中点のままに保ちます。
保たないのは原点です。
そして二つのアフィン行列を掛けると、必ず又アフィン行列になります(一番下の行は のままです)。
ブロックごとに掛け算をすると、次の様になります:
右上のブロックをよく見て下さい:右側の行列の平行移動 が、左側の行列の線型部分 で変換されています。
此の一点が、次の節の全てを説明します。
9.6 モデル行列:T R S
全ての3Dモデルは、オブジェクト空間又はローカル空間と呼ばれる自分専用の小さな座標系で作られています(全ての空間は第11章で扱います)。
アーティストは普通、原点を分かり易い場所に置きます:キャラクターの足元、箱の中心、ドアの蝶番などです。
モデルを世界に置く為に、3つの部品からモデル行列を作ります:
第7章を思い出して下さい。ベクトルに一番近い行列が最初に作用するので、右から左へ読みます:
- S でモデルを、自分の原点を中心に拡大縮小します。
- R でモデルを、自分の原点を中心に回転します。
- T でモデルを、世界の中の位置へ動かします。
大事なのは「自分の原点を中心に」と言う部分です。
拡大縮小と回転は常に原点を中心に行われる(第8章)ので、モデルがまだ原点にいる内に、つまり動かす前に済ませたいのです。
計算例:板を置く
ローカルX軸方向に長さ1の板のモデルがあり、其れを長さ2にして、立てて、位置 に置きたいとします。
つまり scale({2, 1, 1})、 rotateZ(radians(90.0f))、 translate({5, 0, 0}) です。
、 なので、3つの行列は次の通りです:
板の先端、ローカルの点 を各ステップで追いかけてみましょう:
- 拡大縮小:。板の長さが2になりました。
- Z軸周りに90°回転:。板が上を向きました。
- 平行移動:。板がx = 5に立ちました。
板の原点 はSでもRでも変わらず、Tで に移ります。
正に欲しかった結果です。
3つの行列を先に一度だけ掛け合わせる事も出来ます。
先ず (Rの各列に、Sの対応する対角成分を掛けたもの)、其の前にTを掛けます:
確認:。
同じ答えですが、今度はGPUに送れる一つの行列になっています。
最後の列が丁度 になっている事に注目して下さい:Tが一番左にあると、9.5の公式により其のまま足されるのです。
順番を間違えるとどうなるか
行列の掛け算は交換法則が成り立たない(第7章)ので、順番が違えば結果も違います。
次の二つの間違いはとてもよくあるので、グラフィックスプログラマーなら誰でも一度はやります。
間違い1:回転の前に平行移動する()。
板の原点を追うと、Sは何もせず、Tで に動き、其の後Rが其れを世界の原点を中心に回転して に持って行きます。
板が間違った場所に立っています!
9.5の公式で言うと、平行移動が ではなく になってしまったのです。
角度をアニメーションさせると、物体は其の場で回らず、太陽の周りの惑星の様に世界の原点の周りを公転します。
其れが欲しい時もあります(惑星の周りの月など)が、普通はバグです。
間違い2:回転の後に拡大縮小する()。
今度は板が先に回転するので、長い軸が世界のY方向を向き、其の後で世界のX方向に2倍に伸ばされます。
先端 は になり、其のまま で、最後に になります。
つまり板の長さは1のままで、長くなる代わりに太くなってしまいました。
90°の倍数でない角度だと、此の順番は長方形を斜めのひし形にさえしてしまいます。物体に対して斜めの方向に引き伸ばしているからです。
従って覚え方は:T R S、右から読んで「拡大縮小、回転、平行移動」です。
9.7 ピボット点を中心に回転する
回転は常に原点を中心に回ります。
では、ドアを蝶番を中心に回したり、スプライトを其の中心で回したりしたいのに、其の点が原点にない時はどうするのでしょうか?
3ステップの小さなダンスを踊ります:
右から左へ読むと:
- で全体を動かして、ピボット を原点に持って来ます。
- で原点、つまり今はピボットを中心に回転します。
- で全体を元の場所へ戻します。
計算例
点 を、ピボット を中心に、Z軸周りに90°回転させましょう。
点はピボットの1つ右にあるので、反時計回りに4分の1回転すれば、ピボットの1つ上に来るはずです。
- :。
- (Z軸周りの90°はxをyに送る):。
- :。
答えは で、図の通りピボットの1つ上です。
3つの行列を掛け合わせると、一つのアフィン行列になります。
9.5の公式を使うと、線型部分はRで、平行移動は です。
ここでは なので、平行移動は です:
で確認:1行目は 、2行目は 。
同じく です。
ピボットを中心にした拡大縮小も、同じダンスで出来ます:。
9.8 少し先取り:wが1でない時
ここまで w は常に1か0でした。
では、 は何を意味するのでしょうか?
同次座標の本当の規則は:全てを w で割ると実際の点になる、と言うものです。
従って は と同じ点です。
同次ベクトルを何倍しても同じ点を表す事、此れが「同次」と言う名前の由来です。
の方向は、其の方向に「無限に遠い点」と考える事も出来ます。w を小さくすればする程、割った結果の点はどんどん遠くへ行くからです。
アフィン行列は w を変えません(一番下の行が だからです)。なので此の章の内容では、割り算は何もしません。
第12章では透視投影行列が登場します。其の一番下の行は違っていて、カメラからの距離を w に入れます。
するとGPUが自動的に w で割り、其の割り算こそが遠くの物を小さく見せるのです。
此れが、グラフィックスパイプライン全体が4次元で動いている本当の理由です。
9.9 グラフィックスプログラミングでの使い方
頂点シェーダーのモデル行列
シーンの全ての物体は自分専用のモデル行列を持ちます。普通はCPUで translate(position) * rotation * scale(size) として作り、uniformとして送ります。
頂点シェーダーでは、各頂点の位置を の float4 に、各法線を の float4 にします。
其れだけをする最小限の頂点シェーダーが此方です。
ビュー行列と投影行列(view、proj)は第11章と第12章で扱います。今は「カメラ」だと思っておいて下さい。
struct Matrices {
float4x4 model;
float4x4 view;
float4x4 proj;
};
ConstantBuffer<Matrices> matrices;
struct VSInput {
float3 position : POSITION;
float3 normal : NORMAL;
};
struct VSOutput {
float4 position : SV_Position;
float3 worldNormal : NORMAL;
};
[shader("vertex")]
VSOutput vertexMain(VSInput input) {
VSOutput output;
// Point: w = 1, so the translation is applied
float4 worldPos = mul(matrices.model, float4(input.position, 1.0));
output.position = mul(matrices.proj, mul(matrices.view, worldPos));
// Direction: w = 0, so the translation is ignored
output.worldNormal = mul(matrices.model, float4(input.normal, 0.0)).xyz;
return output;
}#version 450
layout(set = 0, binding = 0) uniform Matrices {
mat4 model;
mat4 view;
mat4 proj;
};
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 0) out vec3 worldNormal;
void main() {
// Point: w = 1, so the translation is applied
vec4 worldPos = model * vec4(inPosition, 1.0);
gl_Position = proj * (view * worldPos);
// Direction: w = 0, so the translation is ignored
worldNormal = (model * vec4(inNormal, 0.0)).xyz;
}struct Matrices {
float4x4 model;
float4x4 view;
float4x4 proj;
};
ConstantBuffer<Matrices> matrices;
struct VSInput {
float3 position : POSITION;
float3 normal : NORMAL;
};
struct VSOutput {
float4 position : SV_Position;
float3 worldNormal : NORMAL;
};
VSOutput vertexMain(VSInput input) {
VSOutput output;
// Point: w = 1, so the translation is applied
float4 worldPos = mul(matrices.model, float4(input.position, 1.0));
output.position = mul(matrices.proj, mul(matrices.view, worldPos));
// Direction: w = 0, so the translation is ignored
output.worldNormal = mul(matrices.model, float4(input.normal, 0.0)).xyz;
return output;
}
よくあるバグは、コピペで float4(input.normal, 1.0) と書いてしまう事です。
すると全ての法線に物体の位置が足されてしまい、物体が原点から離れた途端にライティングが滅茶苦茶になります。
原点ではライティングが正しいのに、物体を動かすと壊れる場合は、先ず法線の w を確認しましょう。
沢山のインスタンスを置く
森に千本の木のモデルは要りません。
必要なのは、木のモデル一つと、千個のモデル行列です。
CPU側では次の様になります:
for (int i = 0; i < 3; ++i) {
float height = 1.0f + 0.5f * i;
Mat4 model = translate({3.0f * i, 0.0f, 0.0f}) * rotateY(0.7f * i) * scale({1.0f, height, 1.0f});
Vec4 top = model * Vec4{0.0f, 2.0f, 0.0f, 1.0f};
printf("tree %d top: (%.2f, %.2f, %.2f)\n", i, top.x, top.y, top.z);
}
tree 0 top: (0.00, 2.00, 0.00)
tree 1 top: (3.00, 3.00, 0.00)
tree 2 top: (6.00, 4.00, 0.00)
各木は違う位置、回転、高さを持ち、木の天辺(ローカルの点 )は其れぞれ違う場所に来ます。
インスタンス描画では、此れらの行列を全て一つのバッファに入れ、インスタンス数を指定して木を一度だけ描画します。シェーダーはインスタンス番号で自分の行列を選びます:
StructuredBuffer<float4x4> instanceModels;
[shader("vertex")]
float4 instancedMain(float3 position : POSITION, uint instanceId : SV_InstanceID) : SV_Position {
float4x4 model = instanceModels[instanceId];
return mul(model, float4(position, 1.0));
}#version 450
layout(std430, set = 0, binding = 1) readonly buffer Instances {
mat4 instanceModels[];
};
layout(location = 0) in vec3 inPosition;
void main() {
mat4 model = instanceModels[gl_InstanceIndex];
gl_Position = model * vec4(inPosition, 1.0);
}StructuredBuffer<float4x4> instanceModels;
float4 instancedMain(float3 position : POSITION, uint instanceId : SV_InstanceID) : SV_Position {
float4x4 model = instanceModels[instanceId];
return mul(model, float4(position, 1.0));
}(短くする為に此の例はワールド空間で止めています。実際には上の様にビュー行列と投影行列も掛けます。)
親子の変換
第7章で、子のワールド行列は親のワールド行列に子のローカル行列を掛けたものだと学びました。
ローカル行列に平行移動を含められる様になったので、此れを具体的に出来ます。
手に持った剣は handWorld * swordLocal で、swordLocal は例えば translate({0.0f, 0.1f, 0.0f}) * rotateX(angle)、つまり「手の原点の少し上で、ある角度だけ傾ける」です。
車の車輪は carWorld * translate(wheelOffset) * rotateX(spin) です。
車が動けば車輪も自動的に付いて来て、車輪は自分の車軸を中心に回り続けます。回転が平行移動の右側にあるからで、此れも又T R Sの規則です。
3×3行列で2DのスプライトとUI
2DゲームやUIなら、9.2のウォーミングアップ版で十分です:3×3行列と float3(x, y, 1.0) です。
スプライトの変換もやはり で、2Dになっただけです。ボタンを其の中心で回すには9.7のピボットの工夫を使います。
float2 transformPoint2D(float3x3 m, float2 p) {
return mul(m, float3(p, 1.0)).xy;
}
float2 transformDirection2D(float3x3 m, float2 d) {
return mul(m, float3(d, 0.0)).xy;
}vec2 transformPoint2D(mat3 m, vec2 p) {
return (m * vec3(p, 1.0)).xy;
}
vec2 transformDirection2D(mat3 m, vec2 d) {
return (m * vec3(d, 0.0)).xy;
}float2 transformPoint2D(float3x3 m, float2 p) {
return mul(m, float3(p, 1.0)).xy;
}
float2 transformDirection2D(float3x3 m, float2 d) {
return mul(m, float3(d, 0.0)).xy;
}
ヒント: uniformバッファやストレージバッファの中では、3×3行列の各列はfloat 4個分の大きさに詰め物(パディング)されます。其の為、多くのエンジンはレイアウトの驚きを避けて、2Dの変換も4×4行列(又は float4 3個)として送ります。
9.10 其の他の使い道
同次座標と4×4のアフィン行列は、グラフィックスだけの物ではありません:
- ロボット工学: ロボットアームは関節の連鎖で、各関節の前の関節に対する位置と向きは4×4の同次変換です(デナビット・ハーテンバーグ記法は其れを作る標準的な方法です)。連鎖を掛け合わせると、グリッパーの位置が分かります。
- CADのアセンブリ: 機械の各部品はサブアセンブリの中に4×4行列で置かれ、各サブアセンブリはメインのアセンブリの中に別の行列で置かれます。シーングラフと全く同じです。
- 画像編集とコンピュータビジョン: アフィン画像変換(写真の回転、拡大縮小、傾け、移動)は3×3の同次行列の上の2行を使います。OpenCVの
warpAffineは正に其の2×3行列を受け取ります。 - ベクターグラフィックス: SVGの
matrix(a, b, c, d, e, f)、PDF、HTMLのcanvasのtransformメソッドは、全て同じ6つの数を使います:2×2の線型部分と平行移動です。 - GISと地図: 位置情報付きの衛星画像は、ピクセル座標を地図座標に対応付ける6つの数(ジオトランスフォーム又はワールドファイルと呼ばれる)を持っています。此れも又2Dのアフィン変換です。
9.11 コード
此の章で linalg.hh に追加するのは此れです。
短いのは、仕事が全て行列の構造の方にあるからです:単位行列から始めて、4列目(c[3]、あたし達の Mat4 は列を格納している事を思い出して下さい)を に置き換えます。
// 平行移動 / translation (chapter 9)
inline Mat4 translate(Vec3 t) {
Mat4 m = Mat4::identity();
m.c[3] = {t.x, t.y, t.z, 1.0f};
return m;
}
そして、此の章の計算例を全て再現するテストプログラムです。
角度には第1章の radians() を使います。
浮動小数点の計算では cos(radians(90.0f)) は丁度0にはならない(約−0.00000004)ので、出力に見苦しい -0.00 が出ない様に小さな tidy ヘルパーを足しました。
#include "linalg.hh"
#include <cstdio>
// 浮動小数点の-0.00を避ける / avoid printing -0.00
static float tidy(float v) { return std::fabs(v) < 1e-5f ? 0.0f : v; }
static void print(const char *name, Vec4 v) {
printf("%-10s (%.2f, %.2f, %.2f, %.2f)\n", name, tidy(v.x), tidy(v.y), tidy(v.z), tidy(v.w));
}
int main() {
// 1. Translation alone
Mat4 t = translate({4.0f, -1.0f, 2.0f});
print("point", t * Vec4{1.0f, 2.0f, 3.0f, 1.0f});
print("direction", t * Vec4{1.0f, 2.0f, 3.0f, 0.0f});
// 2. A model matrix: scale, then rotate, then translate
Mat4 model = translate({5.0f, 0.0f, 0.0f}) * rotateZ(radians(90.0f)) * scale({2.0f, 1.0f, 1.0f});
print("tip", model * Vec4{1.0f, 0.0f, 0.0f, 1.0f});
print("origin", model * Vec4{0.0f, 0.0f, 0.0f, 1.0f});
print("axis", model * Vec4{1.0f, 0.0f, 0.0f, 0.0f});
// 3. The wrong order: translate first, then rotate
Mat4 orbit = rotateZ(radians(90.0f)) * translate({5.0f, 0.0f, 0.0f}) * scale({2.0f, 1.0f, 1.0f});
print("orbit", orbit * Vec4{0.0f, 0.0f, 0.0f, 1.0f});
// 4. Rotating around a pivot point
Vec3 pivot{2.0f, 1.0f, 0.0f};
Mat4 aroundPivot = translate(pivot) * rotateZ(radians(90.0f)) * translate(-pivot);
print("pivot", aroundPivot * Vec4{3.0f, 1.0f, 0.0f, 1.0f});
return 0;
}
期待される出力:
point (5.00, 1.00, 5.00, 1.00)
direction (1.00, 2.00, 3.00, 0.00)
tip (5.00, 2.00, 0.00, 1.00)
origin (5.00, 0.00, 0.00, 1.00)
axis (0.00, 2.00, 0.00, 0.00)
orbit (0.00, 5.00, 0.00, 1.00)
pivot (2.00, 2.00, 0.00, 1.00)
「axis」の行を見て下さい:方向 は拡大縮小と回転を受けて になりましたが、x = 5へは動いていません。
がちゃんと仕事をしています。
9.12 まとめ
- 平行移動 は原点を動かすので線型ではなく、3×3行列にはなれません。
- 同次座標は4つ目の数 w を追加します。点は 、方向は です。
- 4×4の平行移動行列は、最後の列に が入った単位行列で、其の列には w が掛かります。
- 方向(法線、速度、光の方向)は w が0なので、自動的に平行移動を無視します。
- アフィン変換 = 線型部分 + 平行移動:。モデル行列の最後の列は物体の位置です。
- モデル行列は :拡大縮小、回転、平行移動の順です。他の順番だと公転したり、間違った軸方向に伸びたりします。
- ピボットを中心に回転:。
- 一般的な規則は w で割る事です。此れは第12章の透視投影で重要になります。
9.13 課題
- ★ 4×4行列 を書き、点 に1行ずつ掛けて下さい。 になるはずです。次に方向 に掛けて下さい:何が出て来て、其の w は幾つですか?
- ★ 2Dの3×3行列で を一つの行列として計算し、点 に掛けて下さい。 になるはずです。
- ★ 2Dで、 と の両方を点 に掛けて下さい。片方は 、もう片方は になります。どちらの順番が「公転」しますか?其れは何故ですか?
- ★ 9.7の3ステップを使って、2Dの点 をピボット を中心に反時計回りに90°回転させて下さい。 になるはずです。
- ★★
Vec3 transformPoint(const Mat4 &m, Vec3 p)とVec3 transformDirection(const Mat4 &m, Vec3 d)(w = 1とw = 0)を書いて下さい。translate({1, 2, 3}) * scale({2, 2, 2})に対して、点 は 、方向 は になるはずです。 - ★★ ピボットの工夫と
rotateYを使ってMat4 rotateYAround(Vec3 pivot, float angle)を書いて下さい。 をピボット を中心に90°回転させると、(1.00, 0.00, -1.00)と表示されるはずです。 - ★★★ を一般の形で掛け算して下さい。何になりますか?順番は関係ありますか?次に、9.5のブロックの公式を使って、何故 の平行移動が になるのか、そして其の平行移動がゼロになるのはどんな時かを説明して下さい。
コメント(0)
まだコメントはありません。
ログインするとコメント出来ます。