Cara Menghitung Normal Permukaan untuk Geometri yang Dihasilkan

12

Saya memiliki kelas yang menghasilkan bentuk 3D berdasarkan input dari kode panggilan. Input adalah hal-hal seperti panjang, kedalaman, busur, dll. Kode saya menghasilkan geometri dengan sempurna, namun saya mengalami kesulitan ketika menghitung normals permukaan. Saat dinyalakan, bentuk saya memiliki pewarnaan / tekstur yang sangat aneh dari permukaan normal yang salah yang sedang dihitung. Dari semua penelitian saya, saya yakin matematika saya benar, sepertinya ada yang salah dengan teknik atau metode saya.

Pada level yang tinggi, bagaimana cara menghitung secara normal permukaan normal untuk bentuk yang dihasilkan? Saya menggunakan Swift / SceneKit di iOS untuk kode saya tetapi jawaban umum baik-baik saja.

Saya memiliki dua array yang mewakili bentuk saya. Salah satunya adalah array titik 3D yang mewakili simpul yang membentuk bentuk. Array lain adalah daftar indeks dari array pertama yang memetakan simpul menjadi segitiga. Saya perlu mengambil data itu dan menghasilkan array ke-3 yang merupakan kumpulan normals permukaan yang membantu dalam pencahayaan bentuk. (lihat SCNGeometrySourceSemanticNormaldi SceneKit` )

Daftar simpul dan indeks selalu berbeda tergantung pada input ke kelas jadi saya tidak bisa pra-menghitung atau hard code permukaan normal.

macinjosh
sumber
Perlu lebih banyak konteks. Apakah Anda mencoba menghitung normal analitik untuk permukaan parametrik? Permukaan tersirat? Atau Anda ingin menghitung normals dari mesh segitiga generik? Atau sesuatu yang lain?
Nathan Reed
Terima kasih, saya menambahkan lebih detail. Untuk menjawab pertanyaan Anda, saya perlu menghitung normals dari mesh segitiga generik. Meskipun harus jelas bahwa mesh berbeda tergantung pada input. Bentuk saya adalah panah 3D, sebagai contoh di sini adalah screenshot 2 bentuk yang berbeda (yaitu radial, dan linier). Kelas mengubah lebar, kedalaman, panjang, busur, dan jari-jari mesh seperti yang diminta. cl.ly/image/3O0P3X3N3d1d Anda dapat melihat pencahayaan aneh yang saya dapatkan dengan upaya buruk saya dalam memecahkan masalah ini.
macinjosh
3
Versi singkatnya adalah: hitung setiap simpul normal sebagai jumlah normal dari semua segitiga yang menyentuhnya. Namun, ini akan membuat semuanya lancar, yang mungkin bukan yang Anda inginkan untuk bentuk ini. Saya akan mencoba mengembangkan jawaban lengkapnya nanti.
Nathan Reed
Lancar adalah tujuan saya!
macinjosh
4
Dalam kebanyakan kasus, jika Anda menghitung posisi simpul secara analitis, Anda juga dapat menghitung normalnya secara analitis. Untuk permukaan parametrik, normalnya adalah produk silang dari dua vektor gradien. Menghitung rata-rata normal segitiga hanyalah perkiraan, dan seringkali menghasilkan kualitas yang jauh lebih buruk secara visual. Saya akan memposting jawaban, tetapi saya sudah memposting contoh detail pada SO ( stackoverflow.com/questions/27233820/… ), dan saya tidak yakin apakah kita ingin mereplikasi konten di sini.
Reto Koradi

Jawaban:

10

Anda tidak ingin hasil yang sepenuhnya mulus. Sementara metode yang dikomentari oleh Nathan Reed: "Hitung setiap titik untuk menghadapi normal, jumlah mereka, jumlah normalisasi", umumnya bekerja kadang-kadang gagal secara spektakuler. Tapi itu tidak penting di sini, kita bisa menggunakan metode itu dengan menambahkan klausa penolakan untuk itu.

Dalam hal ini Anda hanya ingin bagian-bagian tertentu tidak dihaluskan terhadap bagian-bagian tertentu lainnya. Anda ingin tepi keras selektif. Jadi misalnya flat bagian atas dan bawah terpisah membentuk strip segitiga di samping, seperti halnya setiap area datar.

Gambar yang kita kejar

Gambar 1 : Hasil yang Anda inginkan.

Akibatnya Anda hanya ingin rata-rata simpul dari daerah melengkung semua orang lain dapat menggunakan normal mereka mendapatkan bentuk segitiga mereka sendiri. Jadi Anda lebih baik memikirkan jala sebagai 9 wilayah terpisah yang ditangani tanpa yang lain.

Menampilkan mesh dan normals]

Gambar 2 : Gambar menunjukkan struktur mesh dan normals.

Anda tentu dapat secara otomatis menyimpulkan ini dengan tidak termasuk normals yang berada di luar sudut tertentu dari vertex primer normal. Kodesemu:

For vertex in faceVertex:
    normal = vertex.normal
    For adjVertex in adjacentVertices:
        if anglebetween(vertex.normal, adjVertex.normal )  < treshold:
            normal += adjVertex.normal
    normal = normalize(normal)

Itu bekerja, tetapi Anda bisa menghindari semua ini pada waktu pembuatan karena Anda memahami bahwa pesawat yang terpisah bekerja secara berbeda. Jadi hanya sisi melengkung yang membutuhkan penggabungan arah normal. Dan sebenarnya Anda bisa langsung menghitungnya dari bentuk matematika yang mendasarinya.

joojaa
sumber
10

Saya melihat terutama tiga cara komputasi normals untuk bentuk yang dihasilkan.

Normal analitik

Dalam beberapa kasus Anda memiliki informasi yang cukup tentang permukaan untuk menghasilkan normals. Sebagai contoh, normal suatu titik pada bola adalah sepele untuk dihitung. Sederhananya, ketika Anda mengetahui turunan dari fungsi, Anda juga tahu yang normal.

Jika kasing Anda cukup sempit sehingga Anda dapat menggunakan normals analitik, maka kemungkinan besar akan memberikan hasil terbaik dalam hal presisi. Teknik ini tidak terlalu baik skala: jika Anda juga perlu menangani kasus di mana Anda tidak dapat menggunakan normal analitik, mungkin lebih mudah untuk menjaga teknik yang menangani kasus umum dan menjatuhkan analitik sama sekali.

Vertex normals

Produk silang dari dua vektor memberikan vektor tegak lurus terhadap bidang yang menjadi miliknya. Jadi mendapatkan normal segitiga adalah hal yang mudah:

vec3 computeNormal(vec3 a, vec3 b, vec3 c)
{
    return normalize(crossProduct(b - a, c - a));
}

Selain itu, dalam contoh di atas, panjang produk silang sebanding dengan area di dalam abc . Jadi normal yang dihaluskan pada titik yang dibagi oleh beberapa segitiga dapat dihitung dengan menjumlahkan produk silang dan menormalkan sebagai langkah terakhir, sehingga membebani setiap segitiga berdasarkan luasnya.

vec3 computeNormal(vertex a)
{
    vec3 sum = vec3(0, 0, 0);
    list<vertex> adjacentVertices = getAdjacentVertices(a);
    for (int i = 1; i < adjacentVertices; ++i)
    {
        vec3 b = adjacentVertices[i - 1];
        vec3 c = adjacentVertices[i];
        sum += crossProduct(b - a, c - a);
    }
    if (norm(sum) == 0)
    {
        // Degenerate case
        return sum;
    }
    return normalize(sum);
}

Jika Anda bekerja dengan paha depan, ada trik bagus yang dapat Anda gunakan: untuk quad abcd , gunakan crossProduct(c - a, d - b)dan itu akan menangani dengan baik kasus di mana quad sebenarnya adalah segitiga.

Iñigo quilez menulis beberapa artikel pendek tentang topik: normalisasi cerdik , dan area poligon bersisi normal .

Normal dari turunan parsial

Normal dapat dihitung dalam shader fragmen dari turunan parsial. Matematika di baliknya sama, kecuali kali ini dilakukan di ruang layar. Artikel oleh Angelo Pesce ini menjelaskan teknik: Normal tanpa normal .

Julien Guertault
sumber
1
Ada cara keempat, artis menyediakan normals;)
joojaa
@ joojaa: Saya berasumsi Anda mengacu pada peta normal? Saya belum pernah mendengar normals yang ditulis secara manual.
Julien Guertault
1
Tidak, normals yang ditulis secara manual. Terkadang artis Anda tahu lebih banyak tentang perilaku normals daripada model programmer. Kadang-kadang agak bermasalah untuk mesin perhitungan jika mereka menganggap normal berasal dari perhitungan yang mendasarinya. Tapi tentu saja itu terjadi dan Anda menghemat banyak waktu dalam pemodelan matematika.
joojaa
1
Ini kadang-kadang disebut sebagai "normals eksplisit" (3ds maks dan terminologi maya).
Dusan Bosnjak 'pailhead'