ฉันต้องการส่วนประกอบ 'w' ในคลาส Vector ของฉันหรือไม่


21

สมมติว่าคุณกำลังเขียนรหัสเมทริกซ์ที่จัดการการหมุนการแปลและอื่น ๆ สำหรับพื้นที่ 3 มิติ

ตอนนี้เมทริกซ์การแปลงต้องเป็น 4x4 เพื่อให้พอดีกับองค์ประกอบการแปล

อย่างไรก็ตามคุณไม่จำเป็นต้องจัดเก็บwส่วนประกอบในเวกเตอร์ใช่ไหม?

แม้แต่ในการแบ่งเปอร์สเปคทีฟคุณสามารถคำนวณและจัดเก็บwนอกเวคเตอร์และแบ่งเปอร์สเปคทีฟก่อนกลับจากเมธอด

ตัวอย่างเช่น:

// post multiply vec2=matrix*vector
Vector operator*( const Matrix & a, const Vector& v )
{
  Vector r ;
  // do matrix mult
  r.x = a._11*v.x + a._12*v.y ...

  real w = a._41*v.x + a._42*v.y ...

  // perspective divide
  r /= w ;

  return r ;
}

มีจุดในการจัดเก็บwในคลาส Vector หรือไม่


2
นั่นไม่ใช่การนำมาใช้สำหรับการคูณเวกเตอร์เมทริกซ์ปกติ, การหารเปอร์สเปคทีฟไม่ได้อยู่ที่นั่น นอกจากนี้ยังเป็นการเข้าใจที่ค่อนข้างผิดเนื่องจากมีการเน้นส่วนที่ไม่ถูกต้องของการคำนวณ หากคุณต้องการทราบว่าองค์ประกอบ w คืออะไรให้ดูที่การนำไปใช้งานอย่างสมบูรณ์จากนั้นคุณจะเห็นว่าแถว / คอลัมน์สุดท้าย (ส่วนการแปล) ของเมทริกซ์นั้นจะถูกนำไปใช้เฉพาะในกรณีที่องค์ประกอบ w คือ 1 เช่น สำหรับคะแนน คุณควรเน้นส่วนเหล่านั้น: r.x = ... + a._14*v.w; r.y = ... + a._24*v.w; r.z = ... + a._34*v.w; r.w = ... + a._44*v.w;ดูคำตอบของฉันสำหรับรายละเอียด
Maik Semder

คำตอบ:


27

ข้อสงวนสิทธิ์การแก้ไข : เพื่อความสะดวกในคำตอบนี้เวกเตอร์ที่มี w == 0 เรียกว่าเวกเตอร์และ w == 1 เรียกว่าคะแนน แม้ว่า FxIII จะชี้ให้เห็น แต่นั่นไม่ใช่คำศัพท์ที่ถูกต้องทางคณิตศาสตร์ อย่างไรก็ตามเนื่องจากจุดของคำตอบไม่ใช่คำศัพท์ แต่จำเป็นต้องแยกความแตกต่างของเวกเตอร์ทั้งสองชนิดฉันจะยึดมัน ด้วยเหตุผลเชิงปฏิบัติการประชุมนี้ใช้กันอย่างแพร่หลายในการพัฒนาเกม


มันเป็นไปไม่ได้ที่จะแยกแยะความแตกต่างระหว่างเวกเตอร์และจุดที่ไม่มีองค์ประกอบ 'w' มันคือ 1 สำหรับคะแนนและ 0 สำหรับเวกเตอร์

หากมีการคูณเวกเตอร์ด้วยเมทริกซ์การแปลง 4x4 เลียนแบบที่มีการแปลในแถว / คอลัมน์สุดท้ายเวกเตอร์นั้นก็จะถูกแปลด้วยเช่นกันซึ่งไม่ถูกต้องจะต้องแปลเฉพาะคะแนนเท่านั้น ศูนย์ในองค์ประกอบ 'w' ของเวกเตอร์จะดูแลมัน

การเน้นส่วนนี้ของการคูณเมทริกซ์ - เวกเตอร์ทำให้ชัดเจนยิ่งขึ้น:

    r.x = ... + a._14 * v.w; 
    r.y = ... + a._24 * v.w; 
    r.z = ... + a._34 * v.w; 
    r.w = ... + a._44 * v.w;

a._14, a._24 and a._34 is the translational part of the affine matrix.
Without a 'w' component one has to set it implicitly to 0 (vector) or to 1 (point) 

เช่นมันจะผิดในการแปลเวกเตอร์, ตัวอย่างเช่นแกนหมุน, ผลลัพธ์นั้นผิด, โดยมีส่วนประกอบที่ 4 เป็นศูนย์คุณยังสามารถใช้เมทริกซ์เดียวกันที่แปลงคะแนนเพื่อเปลี่ยนแกนหมุนและผลลัพธ์จะถูกต้อง และความยาวของมันจะคงอยู่ตราบใดที่ไม่มีสเกลในเมทริกซ์ นั่นคือพฤติกรรมที่คุณต้องการสำหรับเวกเตอร์ หากไม่มีส่วนประกอบที่ 4 คุณจะต้องสร้างเมทริกซ์ 2 ตัว (หรือฟังก์ชันการคูณที่แตกต่างกัน 2 ตัวพร้อมพารามิเตอร์ตัวที่สี่โดยนัยและทำให้ฟังก์ชันที่แตกต่างกัน 2 แบบเรียกหาจุดและเวกเตอร์

ในการใช้ vector register ของ CPU ที่ทันสมัย ​​(SSE, Altivec, SPUs) คุณต้องผ่าน 4x32 บิตลอยต่อไป (การลงทะเบียน 128 บิต) รวมทั้งคุณต้องดูแลการจัดตำแหน่งโดยปกติ 16 ไบต์ ดังนั้นคุณจึงไม่มีโอกาสที่จะปลอดภัยพื้นที่สำหรับองค์ประกอบที่ 4 อยู่ดี


แก้ไข: คำตอบสำหรับคำถามนั้นเป็นพื้น

  1. ให้เก็บส่วนประกอบ w: 1 สำหรับตำแหน่งและ 0 สำหรับเวกเตอร์
  2. หรือเรียกใช้ฟังก์ชันการคูณเมทริกซ์เวกเตอร์ที่แตกต่างกันและส่งผ่านองค์ประกอบ 'w' โดยการเลือกฟังก์ชันใดฟังก์ชันหนึ่ง

หนึ่งต้องเลือกหนึ่งในนั้นมันเป็นไปไม่ได้ที่จะเก็บเพียง {x, y, z} และยังคงใช้ฟังก์ชั่นการคูณเมทริกซ์เวกเตอร์เดียวเท่านั้น ตัวอย่างเช่น XNA ใช้วิธีการหลังโดยมี 2 แปลงฟังก์ชันในVector3ชั้นเรียกว่าTransformและTransformNormal

นี่คือตัวอย่างของรหัสที่แสดงให้เห็นทั้งสองวิธีและแสดงให้เห็นถึงความจำเป็นที่จะต้องแยกแยะเวกเตอร์ทั้งสองชนิดด้วยวิธีที่เป็นไปได้ 1 ใน 2 วิธี เราจะย้ายเกมเอนทิตีที่มีตำแหน่งและทิศทางในโลกด้วยการเปลี่ยนมันด้วยเมทริกซ์ หากเราไม่ใช้องค์ประกอบ 'w' เราจะไม่สามารถใช้การคูณเมทริกซ์ - เวกเตอร์เดียวกันได้อีกต่อไปเนื่องจากตัวอย่างนี้แสดงให้เห็น หากเราทำเช่นนั้นเราจะได้คำตอบที่ผิดสำหรับlook_dirเวกเตอร์ที่แปลงแล้ว:

#include <cstdio>
#include <cmath>

struct vector3
{
    vector3() {}
    vector3(float _x, float _y, float _z) { x = _x; y = _y; z = _z; }
    float x, y, z;    
};

struct vector4
{
    vector4() {}
    vector4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; }
    float x, y, z, w;
};

struct matrix
{
    // convenience column accessors
    vector4&        operator[](int col)         { return cols[col]; }
    const vector4&  operator[](int col) const   { return cols[col]; }
    vector4 cols[4];
};

// since we transform a vector that stores the 'w' component, 
// we just need this one matrix-vector multiplication
vector4 operator*( const matrix &m, const vector4 &v )
{
    vector4 ret;
    ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + v.w * m[3].x;
    ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + v.w * m[3].y;
    ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + v.w * m[3].z;
    ret.w = v.x * m[0].w + v.y * m[1].w + v.z * m[2].w + v.w * m[3].w;
    return ret;
}

// if we don't store 'w' in the vector we need 2 different transform functions
// this to transform points (w==1), i.e. positions
vector3 TransformV3( const matrix &m, const vector3 &v )
{
    vector3 ret;
    ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + 1.0f * m[3].x;
    ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + 1.0f * m[3].y;
    ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + 1.0f * m[3].z;
    return ret;
}

// and this one is to transform vectors (w==0), like a direction-vector
vector3 TransformNormalV3( const matrix &m, const vector3 &v )
{
    vector3 ret;
    ret.x = v.x * m[0].x + v.y * m[1].x + v.z * m[2].x + 0.0f * m[3].x;
    ret.y = v.x * m[0].y + v.y * m[1].y + v.z * m[2].y + 0.0f * m[3].y;
    ret.z = v.x * m[0].z + v.y * m[1].z + v.z * m[2].z + 0.0f * m[3].z;
    return ret;
}

// some helpers to output the results
void PrintV4(const char *msg, const vector4 &p )  { printf("%-15s: %10.6f %10.6f %10.6f %10.6f\n",  msg, p.x, p.y, p.z, p.w ); }
void PrintV3(const char *msg, const vector3 &p )  { printf("%-15s: %10.6f %10.6f %10.6f\n",         msg, p.x, p.y, p.z); }

#define STORE_W     1

int main()
{
    // suppose we have a "position" of an entity and its 
    // look direction "look_dir" which is a unit vector

    // we will move this entity in the world

    // the entity will be moved in the world by a translation 
    // in x+5 and a rotation of 90 degrees around the y-axis 
    // let's create that matrix first

    // the rotation angle, 90 degrees in radians
    float a = 1.570796326794896619f;
    matrix moveEntity;
    moveEntity[0] = vector4( cos(a), 0.0f, sin(a), 0.0f);
    moveEntity[1] = vector4(   0.0f, 1.0f,   0.0f, 0.0f);
    moveEntity[2] = vector4(-sin(a), 0.0f, cos(a), 0.0f);
    moveEntity[3] = vector4(   5.0f, 0.0f,   0.0f, 1.0f);

#if STORE_W

    vector4 position(0.0f, 0.0f, 0.0f, 1.0f);
    // entity is looking towards the positive x-axis
    vector4 look_dir(1.0f, 0.0f, 0.0f, 0.0f);

    // move the entity using the matrix
    // we can use the same function for the matrix-vector multiplication to transform 
    // the position and the unit vector since we store 'w' in the vector
    position = moveEntity * position;
    look_dir = moveEntity * look_dir;

    PrintV4("position", position);
    PrintV4("look_dir", look_dir);

#else

    vector3 position(0.0f, 0.0f, 0.0f);
    // entity is looking towards the positive x-axis
    vector3 look_dir(1.0f, 0.0f, 0.0f);

    // move the entity using the matrix
    // we have to call 2 different transform functions one to transform the position 
    // and the other one to transform the unit-vector since we don't 
    // store 'w' in the vector
    position = TransformV3(moveEntity, position);
    look_dir = TransformNormalV3(moveEntity, look_dir);

    PrintV3("position", position);
    PrintV3("look_dir", look_dir);

#endif

    return 0;
}

สถานะเอนทิตีเริ่มต้น:

position       :   0.000000   0.000000   0.000000   1.000000
look_dir       :   1.000000   0.000000   0.000000   0.000000

ตอนนี้การแปลงที่มีการแปล x + 5 และการหมุน 90 องศารอบแกน y จะถูกนำไปใช้กับเอนทิตีนี้ คำตอบที่ถูกต้องหลังจากการเปลี่ยนรูปคือ:

position       :   5.000000   0.000000   0.000000   1.000000
look_dir       :   0.000000   0.000000   1.000000   0.000000

เราจะได้รับคำตอบที่ถูกต้องก็ต่อเมื่อเราแยกเวกเตอร์ด้วย w == 0 และตำแหน่งที่มี w == 1 ในวิธีใดวิธีหนึ่งที่นำเสนอข้างต้น


@ Maik Semder คุณผิดเล็กน้อย ... มันเป็นไปไม่ได้ที่จะแยกความแตกต่างระหว่างเวกเตอร์กับจุดเพราะมันเป็นสิ่งเดียวกัน! (พวกมัน isomorphic) 1 สำหรับเวกเตอร์และ 0 สำหรับผู้กำกับทิศทางไม่มีที่สิ้นสุด (อย่างที่ฉันบอกในคำตอบของฉัน) . ส่วนที่เหลือของการตอบสนองมีความรู้สึกน้อยเนื่องจากสมมติฐานผิด
FxIII

1
@FxIII ฉันไม่เห็นจุดของคุณ (ไม่มีการเล่นสำนวนเจตนา) และความเกี่ยวข้องกับคำถามนี้ คุณกำลังพูดว่าเวกเตอร์และจุดเหมือนกันดังนั้นจึงไม่มีเหตุผลที่จะเก็บ 'w' เอาไว้อย่างจริงจังเหรอ? ตอนนี้คุณจะปฏิวัติกราฟิกคอมพิวเตอร์หรือคุณไม่เข้าใจคำถามนี้
Maik Semder

1
@FxIII นั่นเป็นเรื่องไร้สาระคุณอาจต้องการศึกษากรอบคณิตศาสตร์ 3 มิติที่ใช้ในการพัฒนาเกมเช่นvectormath ของ Sonyคุณจะพบกับการใช้งานจำนวนมากดูการใช้ vmathV4MakeFromV3 ใน vec_aos.h และสิ่งที่พวกเขาใส่ไว้ในองค์ประกอบที่ 4, 1.0 สำหรับ P3 และ 0.0 สำหรับ V3, จุด 3D และเวกเตอร์ 3 มิติอย่างเห็นได้ชัด
Maik Semder

3
@FxIII นั้นเป็นเหตุผลว่าทำไมคลาสXNA Vector3มีฟังก์ชัน "Transform" และฟังก์ชันสมาชิก "TransformNormal" เหตุผลคือคณิตศาสตร์ของพีชคณิตเชิงเส้น สิ่งที่คุณทำโดยการเลือกฟังก์ชั่นการแปลงอย่างใดอย่างหนึ่งคือการส่งผ่านพารามิเตอร์ 'w' โดยปริยายของ '1' หรือ '0' ซึ่งโดยทั่วไปจะรวมแถวที่ 4 ของเมทริกซ์ในการคำนวณหรือไม่ สรุป: หากคุณไม่เก็บส่วนประกอบ 'w' คุณต้องปฏิบัติต่อเวกเตอร์เหล่านั้นต่างกันโดยการเรียกฟังก์ชันการแปลงค่าต่าง ๆ
Maik Semder

1
เวกเตอร์และคะแนน isomorphic ตามที่กล่าวไว้ดังนั้นจึงไม่มีความแตกต่างระหว่างพีชคณิต อย่างไรก็ตามสิ่งที่แบบจำลองที่เป็นเนื้อเดียวกันของพื้นที่โปรเจกต์พยายามที่จะเป็นตัวแทนคือตลาดหลักทรัพย์แห่งเวกเตอร์อวกาศและจุดต่าง ๆ นั้นไม่ได้เป็นแบบมอร์ฟิค ชุดของช่องว่างแบบเวกเตอร์มีผลบังคับใช้เป็นประเภทของการปิดสำหรับ R ^ 3 ที่มีจุดบนทรงกลมที่ไม่มีที่สิ้นสุด คะแนนที่มี = 0 มักจะเรียกว่า "เวกเตอร์" ไม่ถูกต้อง - สิ่งเหล่านี้คือ isomorphic กับทิศทางของทรงกลมและจะเรียกว่า "ทิศทาง" อย่างแม่นยำมากขึ้นเพียงแค่ ... และไม่การสูญเสีย w อาจใช้งานได้ แต่ส่วนใหญ่คุณจะ ค้นหาปัญหา
Crowley9

4

หากคุณกำลังสร้างคลาส Vector ฉันจะถือว่าคลาสจะเก็บคำอธิบายของเวกเตอร์ 3 มิติ เวกเตอร์ 3 มิติมีขนาด x, y และ z ดังนั้นถ้าเวกเตอร์ของคุณต้องการขนาดโดยพลการไม่คุณจะไม่เก็บไว้ในชั้นเรียน

มีความแตกต่างใหญ่ระหว่างเวกเตอร์กับเมทริกซ์การแปลง เนื่องจาก DirectX และ OpenGL จัดการกับเมทริกซ์สำหรับคุณฉันมักจะไม่เก็บ 4x4 เมทริกซ์ในรหัสของฉัน ค่อนข้างฉันเก็บผลัดออยเลอร์ (หรือ Quaternions หากคุณต้องการ - ซึ่งบังเอิญมีองค์ประกอบ aw) และ x, y, z แปล การแปลคือเวกเตอร์หากคุณต้องการและการหมุนจะพอดีกับเวกเตอร์ด้วยโดยที่แต่ละส่วนประกอบจะเก็บจำนวนการหมุนรอบแกนของมัน

หากคุณต้องการดำน้ำลึกลงไปในคณิตศาสตร์ของเวกเตอร์เวกเตอร์แบบยุคลิดเป็นเพียงทิศทางและขนาด ดังนั้นโดยทั่วไปแล้วสิ่งนี้จะถูกแสดงด้วยทริปเลตของตัวเลขโดยที่แต่ละตัวเลขนั้นมีขนาดตามแกน ทิศทางของมันถูกบอกเป็นนัยโดยการรวมกันของขนาดทั้งสามนี้และขนาดสามารถพบได้ด้วยสูตรระยะทางแบบยุคลิด หรือบางครั้งมันถูกจัดเก็บเป็นทิศทาง (เวกเตอร์ที่มีความยาว = 1) และขนาด (ลอย) ถ้านั่นคือสิ่งที่สะดวก (เช่นถ้าขนาดเปลี่ยนบ่อยกว่าทิศทางมันอาจจะสะดวกกว่า เปลี่ยนจำนวนขนาดนั้นให้เป็นเวกเตอร์ทำให้เป็นมาตรฐานและคูณองค์ประกอบด้วยขนาดใหม่)


6
Modern OpenGL ไม่ได้เกี่ยวข้องกับเมทริกซ์สำหรับคุณ
SurvivalMachine

4

มิติที่สี่ในเวกเตอร์ 3 มิติใช้เพื่อคำนวณการแปลงเลียนแบบที่จะไม่สามารถคำนวณได้โดยใช้เมทริกซ์เพียงอย่างเดียว พื้นที่ยังคงเป็นสามมิติดังนั้นนี่หมายความว่าส่วนที่สี่ถูกแมปในพื้นที่สามมิติในทางใดทางหนึ่ง

การทำแผนที่มิติหมายความว่าเวกเตอร์ 4D ที่แตกต่างกันบ่งชี้จุด 3D เดียวกัน แผนที่คือถ้าA = [x ', y', z'.w '] และB = [x ", y", z ", w"] พวกมันหมายถึงจุดเดียวกันถ้า x' / x "= y ' / y "= z '/ z" = w' / w "= αเช่นส่วนประกอบเป็นสัดส่วนสำหรับค่าสัมประสิทธิ์αเดียวกัน

กล่าวว่าคุณสามารถแสดงจุด - พูด (1,3,7) - ในลักษณะที่ไม่มีที่สิ้นสุดเช่น (1,3,7,1) หรือ (2,6,14,2) หรือ (131,393,917,131) หรือทั่วไป (α· 1 α· 3, α· 7 α)

ซึ่งหมายความว่าคุณสามารถปรับขนาดเวกเตอร์ 4D เป็นอีกมิติหนึ่งแทนจุด 3D เดียวกันดังนั้น w = 1: รูปแบบ (x, y, z, 1) เป็นรูปแบบบัญญัติ

เมื่อคุณใช้เมทริกซ์กับเวกเตอร์นี้คุณอาจได้เวกเตอร์ที่ไม่มี w = 1 แต่คุณสามารถปรับขนาดผลลัพธ์เพื่อเก็บไว้ในรูปแบบบัญญัติ ดังนั้นคำตอบที่ดูเหมือนจะเป็น"คุณควรใช้ 4D เวกเตอร์เมื่อทำคณิตศาสตร์ แต่ไม่ได้เก็บองค์ประกอบที่สี่"

นี่เป็นเรื่องจริง แต่มีบางจุดที่คุณไม่สามารถใส่ในรูปแบบบัญญัติ: คะแนนเช่น (4,2,5,0) คะแนนเหล่านั้นเป็นคะแนนพิเศษพวกเขาเป็นตัวแทนของอนันต์ชีทและสามารถทำให้เป็นเวกเตอร์หน่วยได้อย่างต่อเนื่อง: คุณสามารถไปที่อนันต์และส่งคืนได้อย่างปลอดภัย (แม้สองครั้ง) โดยไม่ถูกโยนทิ้ง คุณจะได้ส่วนที่ไม่มีความสุขเป็นศูนย์ถ้าคุณพยายามบังคับให้เวกเตอร์เหล่านั้นอยู่ในรูปแบบบัญญัติ

ตอนนี้คุณรู้แล้วทางเลือกเป็นของคุณ!


1

ใช่คุณทำ การแปลงของคุณไม่ถูกต้องสำหรับเวกเตอร์บางประเภท คุณสามารถเห็นสิ่งนี้ได้ในห้องสมุดคณิตศาสตร์ D3DX ซึ่งมีฟังก์ชันการคูณเมทริกซ์ - เวกเตอร์ที่ต่างกันสองฟังก์ชันหนึ่งฟังก์ชันสำหรับ w = 0 และอีกหนึ่งสำหรับ w = 1


0

ขึ้นอยู่กับสิ่งที่คุณต้องการและต้องการ :)

ฉันจะเก็บมันไว้, b / c จำเป็นสำหรับการแปลงและ (คุณไม่สามารถคูณ 3 เวกเตอร์กับเมทริกซ์ 4x4), แม้ว่าคุณจะมี aw 1 เสมอ, ฉันคิดว่าคุณสามารถปลอมมันได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.