|
3 | 3 | #include "cuda_float3.cuh" |
4 | 4 | #include "cuda_scene.cuh" |
5 | 5 | #include "cuda_utils.cuh" |
| 6 | +#include "microfacet_ggx.cuh" |
6 | 7 |
|
7 | 8 | #include <cfloat> |
8 | 9 | #include <cmath> |
@@ -47,20 +48,25 @@ enum LegacyMaterialType |
47 | 48 | LIGHT = 3, |
48 | 49 | ROUGH_MIRROR = 4, |
49 | 50 | CONSTANT = 5, |
50 | | - SHOW_NORMALS = 6 |
| 51 | + SHOW_NORMALS = 6, |
| 52 | + ANISOTROPIC_METAL = 7 |
51 | 53 | }; |
52 | 54 |
|
53 | 55 | struct hit_record_simple |
54 | 56 | { |
55 | 57 | f3 p, normal; |
| 58 | + f3 tangent; // Surface tangent for anisotropic materials |
56 | 59 | float t; |
57 | 60 | bool front_face; |
58 | 61 | LegacyMaterialType material; |
59 | 62 | f3 color; |
60 | 63 | float refractive_index; |
61 | 64 | f3 emission; |
62 | 65 | float roughness; |
63 | | - bool visible; // Whether the hit geometry is visible (invisible geometry still emits light) |
| 66 | + float anisotropy; // Anisotropy ratio [0-1] |
| 67 | + f3 eta; // Complex IOR real part (conductor Fresnel) |
| 68 | + f3 k_extinction; // Complex IOR imaginary part (conductor Fresnel) |
| 69 | + bool visible; // Whether the hit geometry is visible (invisible geometry still emits light) |
64 | 70 | }; |
65 | 71 |
|
66 | 72 | //============================================================================== |
@@ -209,6 +215,12 @@ __device__ inline bool hit_sphere(const f3 ¢er, float radius, const ray_simp |
209 | 215 | f3 outward_normal = (rec.p - center) / radius; |
210 | 216 | rec.front_face = dot(r.dir, outward_normal) < 0; |
211 | 217 | rec.normal = rec.front_face ? outward_normal : f3(-outward_normal.x, -outward_normal.y, -outward_normal.z); |
| 218 | + // Compute tangent for anisotropic materials (azimuthal direction) |
| 219 | + f3 up_dir(0.0f, 1.0f, 0.0f); |
| 220 | + f3 tangent = cross(up_dir, outward_normal); |
| 221 | + if (tangent.length_squared() < 1e-6f) |
| 222 | + tangent = f3(1.0f, 0.0f, 0.0f); // Degenerate at poles |
| 223 | + rec.tangent = normalize(tangent); |
212 | 224 | return true; |
213 | 225 | } |
214 | 226 |
|
@@ -245,6 +257,8 @@ __device__ inline bool hit_rectangle(const f3 &corner, const f3 &u, const f3 &v, |
245 | 257 | rec.p = intersection; |
246 | 258 | rec.front_face = dot(r.dir, normal) < 0; |
247 | 259 | rec.normal = rec.front_face ? normal : f3(-normal.x, -normal.y, -normal.z); |
| 260 | + // Compute tangent for anisotropic materials (along u edge) |
| 261 | + rec.tangent = normalize(u); |
248 | 262 | return true; |
249 | 263 | } |
250 | 264 |
|
@@ -306,6 +320,8 @@ __device__ inline bool hit_triangle(const f3 &v0, const f3 &v1, const f3 &v2, |
306 | 320 | } |
307 | 321 |
|
308 | 322 | rec.normal = rec.front_face ? shading_normal : f3(-shading_normal.x, -shading_normal.y, -shading_normal.z); |
| 323 | + // Compute tangent for anisotropic materials (along edge v0->v1) |
| 324 | + rec.tangent = normalize(edge1); |
309 | 325 | return true; |
310 | 326 | } |
311 | 327 |
|
@@ -414,6 +430,14 @@ __device__ __forceinline__ void apply_material(const CudaScene::Material &mat, h |
414 | 430 | case MaterialType::SHOW_NORMALS: |
415 | 431 | rec.material = SHOW_NORMALS; |
416 | 432 | break; |
| 433 | + case MaterialType::ANISOTROPIC_METAL: |
| 434 | + rec.material = ANISOTROPIC_METAL; |
| 435 | + rec.color = mat.albedo; |
| 436 | + rec.roughness = mat.roughness; |
| 437 | + rec.anisotropy = mat.anisotropy; |
| 438 | + rec.eta = mat.eta; |
| 439 | + rec.k_extinction = mat.k; |
| 440 | + break; |
417 | 441 | case MaterialType::SDF_MATERIAL: // TODO: Implement SDF materials |
418 | 442 | rec.material = LAMBERTIAN; |
419 | 443 | rec.color = mat.albedo; |
@@ -632,6 +656,66 @@ __device__ __forceinline__ bool scatter_material(const hit_record_simple &rec, c |
632 | 656 | emitted = 0.5f * (rec.normal + f3(1.0f, 1.0f, 1.0f)); |
633 | 657 | return false; |
634 | 658 | } |
| 659 | + case ANISOTROPIC_METAL: |
| 660 | + { |
| 661 | + // Anisotropic GGX microfacet conductor (PBR Book §9.6) |
| 662 | + |
| 663 | + // Convert roughness + anisotropy to alpha_x/alpha_y (Disney convention) |
| 664 | + float aspect = sqrtf(fmaxf(1e-4f, 1.0f - 0.9f * rec.anisotropy)); |
| 665 | + float r2 = rec.roughness * rec.roughness; |
| 666 | + float alpha_x = fmaxf(1e-4f, r2 / aspect); |
| 667 | + float alpha_y = fmaxf(1e-4f, r2 * aspect); |
| 668 | + |
| 669 | + // Effectively smooth: fall back to perfect mirror with Fresnel tint |
| 670 | + if (fmaxf(alpha_x, alpha_y) < 1e-3f) |
| 671 | + { |
| 672 | + f3 refl = reflect(normalize(current_ray.dir), rec.normal); |
| 673 | + scattered_ray = ray_simple(rec.p + 0.0001f * rec.normal, refl); |
| 674 | + float cos_i = fmaxf(dot(-normalize(current_ray.dir), rec.normal), 0.0f); |
| 675 | + attenuation = FrComplex(cos_i, rec.eta, rec.k_extinction); |
| 676 | + return dot(refl, rec.normal) > 0.0f; |
| 677 | + } |
| 678 | + |
| 679 | + // Build TBN frame (tangent, bitangent, normal) |
| 680 | + f3 N = normalize(rec.normal); |
| 681 | + f3 T = normalize(rec.tangent - dot(rec.tangent, N) * N); // Re-orthogonalize |
| 682 | + f3 B = cross(N, T); |
| 683 | + |
| 684 | + // Transform outgoing direction (toward viewer) to local shading space |
| 685 | + f3 wo_world = normalize(f3(-current_ray.dir.x, -current_ray.dir.y, -current_ray.dir.z)); |
| 686 | + f3 wo_local(dot(wo_world, T), dot(wo_world, B), dot(wo_world, N)); |
| 687 | + |
| 688 | + // Ensure wo is in upper hemisphere |
| 689 | + if (wo_local.z <= 0.0f) |
| 690 | + return false; |
| 691 | + |
| 692 | + // Sample microfacet normal via VNDF |
| 693 | + f3 wm = Sample_wm_GGX(wo_local, alpha_x, alpha_y, |
| 694 | + rand_float(state), rand_float(state)); |
| 695 | + |
| 696 | + // Reflect wo around wm to get wi |
| 697 | + f3 wi_local = reflect(f3(-wo_local.x, -wo_local.y, -wo_local.z), wm); |
| 698 | + |
| 699 | + // Check that reflected direction is in upper hemisphere |
| 700 | + if (wi_local.z <= 0.0f) |
| 701 | + return false; |
| 702 | + |
| 703 | + // Transform wi back to world space |
| 704 | + f3 wi_world = wi_local.x * T + wi_local.y * B + wi_local.z * N; |
| 705 | + |
| 706 | + // Compute Fresnel reflectance at microfacet |
| 707 | + float cos_theta_i = fmaxf(dot(wo_local, wm), 0.0f); |
| 708 | + f3 F = FrComplex(cos_theta_i, rec.eta, rec.k_extinction); |
| 709 | + |
| 710 | + // VNDF importance sampling weight: F * G(wo,wi) / G1(wo) |
| 711 | + float G = G_GGX(wo_local, wi_local, alpha_x, alpha_y); |
| 712 | + float G1 = G1_GGX(wo_local, alpha_x, alpha_y); |
| 713 | + f3 weight = F * (G / fmaxf(G1, 1e-6f)); |
| 714 | + |
| 715 | + scattered_ray = ray_simple(rec.p + 0.0001f * rec.normal, wi_world); |
| 716 | + attenuation = weight; |
| 717 | + return true; |
| 718 | + } |
635 | 719 | default: |
636 | 720 | return false; |
637 | 721 | } |
|
0 commit comments