Cách hiệu quả nhất để tìm tọa độ barycentric là gì?


45

Trong hồ sơ của tôi, việc tìm tọa độ nhị phân rõ ràng là một phần của nút cổ chai. Tôi đang tìm cách để làm cho nó hiệu quả hơn.

Nó theo phương pháp trong shirley , trong đó bạn tính diện tích các hình tam giác được hình thành bằng cách nhúng điểm P bên trong tam giác.

bary

Mã số:

Vector Triangle::getBarycentricCoordinatesAt( const Vector & P ) const
{
  Vector bary ;

  // The area of a triangle is 
  real areaABC = DOT( normal, CROSS( (b - a), (c - a) )  ) ;
  real areaPBC = DOT( normal, CROSS( (b - P), (c - P) )  ) ;
  real areaPCA = DOT( normal, CROSS( (c - P), (a - P) )  ) ;

  bary.x = areaPBC / areaABC ; // alpha
  bary.y = areaPCA / areaABC ; // beta
  bary.z = 1.0f - bary.x - bary.y ; // gamma

  return bary ;
}

Phương pháp này hiệu quả, nhưng tôi đang tìm kiếm một phương pháp hiệu quả hơn!


2
Coi chừng các giải pháp hiệu quả nhất có thể là ít chính xác nhất.
Peter Taylor

Tôi đề nghị bạn thực hiện một bài kiểm tra đơn vị để gọi phương thức này ~ 100 nghìn lần (hoặc một cái gì đó tương tự) và đo hiệu suất. Bạn có thể viết một bài kiểm tra đảm bảo nó ít hơn một số giá trị (ví dụ: 10 giây) hoặc bạn có thể sử dụng nó đơn giản để điểm chuẩn cũ so với triển khai mới.
tro999

Câu trả lời:


54

Phiên âm từ Phát hiện va chạm thời gian thực của Christer Ericson (trong đó, tình cờ, là một cuốn sách tuyệt vời):

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point p, Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v0 = b - a, v1 = c - a, v2 = p - a;
    float d00 = Dot(v0, v0);
    float d01 = Dot(v0, v1);
    float d11 = Dot(v1, v1);
    float d20 = Dot(v2, v0);
    float d21 = Dot(v2, v1);
    float denom = d00 * d11 - d01 * d01;
    v = (d11 * d20 - d01 * d21) / denom;
    w = (d00 * d21 - d01 * d20) / denom;
    u = 1.0f - v - w;
}

Đây thực sự là quy tắc của Cramer để giải quyết một hệ thống tuyến tính. Bạn sẽ không đạt được hiệu quả cao hơn nhiều so với điều này nếu điều này vẫn còn là một nút cổ chai (và có thể là: nó không giống như thuật toán tính toán khác nhiều so với thuật toán hiện tại của bạn), có lẽ bạn sẽ cần tìm một nơi khác để tăng tốc.

Lưu ý rằng một số lượng lớn các giá trị ở đây không phụ thuộc vào p hèthey có thể được lưu trữ với tam giác nếu cần thiết.


7
# hoạt động có thể là một cá trích đỏ. Làm thế nào họ phụ thuộc và lịch trình rất nhiều vấn đề trên CPU hiện đại. luôn luôn kiểm tra các giả định và hiệu suất "cải tiến".
Sean Middleditch

1
Hai phiên bản trong câu hỏi có độ trễ gần như giống hệt nhau trên con đường quan trọng, nếu bạn chỉ nhìn vào ops toán học vô hướng. Điều tôi thích ở cái này là bằng cách trả không gian cho chỉ hai phao, bạn có thể cạo một phép trừ và một phép chia từ đường dẫn quan trọng. Là rằng giá trị của nó? Chỉ có một bài kiểm tra hiệu suất mới biết chắc chắn
John Calsbeek

1
Anh ta mô tả làm thế nào anh ta có được điều này trên trang 137-138 với phần "điểm gần nhất trên tam giác tới điểm"
bobobobo

1
Lưu ý nhỏ: không có đối số pcho chức năng này.
Bart

2
Lưu ý thực hiện nhỏ: Nếu cả 3 điểm nằm chồng lên nhau, bạn sẽ gặp lỗi "chia cho 0", vì vậy hãy chắc chắn kiểm tra trường hợp đó trong mã thực tế.
frodo2975

9

Quy tắc của Cramer nên là cách tốt nhất để giải quyết nó. Tôi không phải là một anh chàng đồ họa, nhưng tôi đã tự hỏi tại sao trong cuốn sách Phát hiện va chạm thời gian thực họ không làm điều đơn giản sau:

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point p, Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v0 = b - a, v1 = c - a, v2 = p - a;
    den = v0.x * v1.y - v1.x * v0.y;
    v = (v2.x * v1.y - v1.x * v2.y) / den;
    w = (v0.x * v2.y - v2.x * v0.y) / den;
    u = 1.0f - v - w;
}

Điều này trực tiếp giải quyết hệ thống tuyến tính 2x2

v v0 + w v1 = v2

trong khi phương pháp từ cuốn sách giải quyết hệ thống

(v v0 + w v1) dot v0 = v2 dot v0
(v v0 + w v1) dot v1 = v2 dot v1

Không phải giải pháp đề xuất của bạn đưa ra các giả định về chiều thứ ba ( .z) (cụ thể là nó không tồn tại)?
Bắp ngô

1
Đây là phương pháp tốt nhất ở đây nếu một người làm việc trong 2D. Chỉ cần một cải tiến nhỏ: người ta nên tính toán đối ứng của mẫu số để sử dụng hai phép nhân và một phép chia thay vì hai phép chia.
rubik

8

Nhanh hơn một chút: Tính toán trước mẫu số và nhân lên thay vì chia. Phân chia đắt hơn nhiều so với phép nhân.

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v0 = b - a, v1 = c - a, v2 = p - a;
    float d00 = Dot(v0, v0);
    float d01 = Dot(v0, v1);
    float d11 = Dot(v1, v1);
    float d20 = Dot(v2, v0);
    float d21 = Dot(v2, v1);
    float invDenom = 1.0 / (d00 * d11 - d01 * d01);
    v = (d11 * d20 - d01 * d21) * invDenom;
    w = (d00 * d21 - d01 * d20) * invDenom;
    u = 1.0f - v - w;
}

Tuy nhiên, trong quá trình thực hiện, tôi đã lưu trữ tất cả các biến độc lập. Tôi pre-calc sau đây trong constructor:

Vector v0;
Vector v1;
float d00;
float d01;
float d11;
float invDenom;

Vì vậy, mã cuối cùng trông như thế này:

// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
void Barycentric(Point a, Point b, Point c, float &u, float &v, float &w)
{
    Vector v2 = p - a;
    float d20 = Dot(v2, v0);
    float d21 = Dot(v2, v1);
    v = (d11 * d20 - d01 * d21) * invDenom;
    w = (d00 * d21 - d01 * d20) * invDenom;
    u = 1.0f - v - w;
}

2

Tôi sẽ sử dụng giải pháp mà John đã đăng, nhưng tôi sẽ sử dụng SSS 4.2 chấm nội tại và sse RCpss phân chia nội tại, giả sử bạn chấp nhận giới hạn bản thân với Nehalem và các quy trình mới hơn và độ chính xác hạn chế.

Ngoài ra, bạn có thể tính toán một số tọa độ barycentric cùng một lúc bằng cách sử dụng sse hoặc avx để tăng tốc 4 hoặc 8 lần.


1

Bạn có thể chuyển đổi vấn đề 3D của mình thành vấn đề 2D bằng cách chiếu một trong các mặt phẳng thẳng hàng trục và sử dụng phương pháp do user5302 đề xuất. Điều này sẽ dẫn đến các tọa độ chính xác giống nhau miễn là bạn đảm bảo tam giác của bạn không chiếu vào một đường. Tốt nhất là chiếu lên mặt phẳng thẳng hàng càng gần với hướng của bộ ba của bạn. Điều này tránh các vấn đề đồng tuyến tính và đảm bảo độ chính xác tối đa.

Thứ hai, bạn có thể tính toán trước mẫu số và lưu trữ nó cho mỗi tam giác. Điều này tiết kiệm tính toán sau đó.


1

Tôi đã cố sao chép mã của @ NielW sang C ++, nhưng tôi không nhận được kết quả chính xác.

Dễ dàng hơn để đọc https://en.wikipedia.org/wiki/Barycentric_coordine_system#Barycentric_coordins_on_triangles và tính toán lambda1 / 2/3 như được đưa ra ở đó (không cần hàm vectơ).

Nếu p (0..2) là các Điểm của tam giác có x / y / z:

Precalc cho tam giác:

double invDET = 1./((p(1).y-p(2).y) * (p(0).x-p(2).x) + 
                   (p(2).x-p(1).x) * (p(0).y-p(2).y));

sau đó lambdas cho một điểm "điểm" là

double l1 = ((p(1).y-p(2).y) * (point.x-p(2).x) + (p(2).x-p(1).x) * (point.y-p(2).y)) * invDET; 
double l2 = ((p(2).y-p(0).y) * (point.x-p(2).x) + (p(0).x-p(2).x) * (point.y-p(2).y)) * invDET; 
double l3 = 1. - l1 - l2;

0

Đối với một điểm N đã cho bên trong tam giác ABC, bạn có thể lấy trọng số nhị phân của điểm C bằng cách chia diện tích của phần phụ ABN cho tổng diện tích tam giác AB C.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.