Skip to content

Commit 8623b79

Browse files
committed
Anisotropic metals, stage 1
1 parent 1208a71 commit 8623b79

File tree

11 files changed

+565
-22
lines changed

11 files changed

+565
-22
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Anisotropic Metals Test Scene
2+
# Demonstrates the GGX microfacet anisotropic conductor materials
3+
#
4+
# Back row (gold): SAME roughness (0.45), VARYING anisotropy: 0.0 → 0.5 → 0.95
5+
# Front row (gold): SAME anisotropy (0.8), VARYING roughness: 0.15 → 0.35 → 0.6
6+
# This isolates each variable so the effect is clearly visible.
7+
8+
camera:
9+
position: [0, 3.5, 7]
10+
look_at: [0, 0.3, -1]
11+
fov: 38
12+
13+
settings:
14+
use_bvh: true
15+
background_color: [0.04, 0.04, 0.8]
16+
background_intensity: 0.05
17+
adaptive_sampling: false
18+
19+
materials:
20+
- name: "ground"
21+
type: "lambertian"
22+
albedo: [0.4, 0.4, 0.4]
23+
24+
- name: "light_top"
25+
type: "light"
26+
color: [1.0, 0.875, 0.75]
27+
emission_intensity: 8
28+
29+
- name: "light_side"
30+
type: "light"
31+
color: [1.0, 0.875, 0.75]
32+
emission_intensity: 2
33+
34+
# --- Back row: SAME roughness 0.45, VARYING anisotropy ---
35+
- name: "gold_aniso_00"
36+
type: "anisotropic_metal"
37+
roughness: 0.45
38+
anisotropy: 0.0
39+
preset: "gold"
40+
41+
- name: "gold_aniso_50"
42+
type: "anisotropic_metal"
43+
roughness: 0.45
44+
anisotropy: 0.5
45+
preset: "gold"
46+
47+
- name: "gold_aniso_95"
48+
type: "anisotropic_metal"
49+
roughness: 0.45
50+
anisotropy: 0.95
51+
preset: "gold"
52+
53+
# --- Front row: SAME anisotropy 0.8, VARYING roughness ---
54+
- name: "gold_rough_15"
55+
type: "anisotropic_metal"
56+
roughness: 0.15
57+
anisotropy: 0.8
58+
preset: "gold"
59+
60+
- name: "gold_rough_35"
61+
type: "anisotropic_metal"
62+
roughness: 0.35
63+
anisotropy: 0.8
64+
preset: "gold"
65+
66+
- name: "gold_rough_60"
67+
type: "anisotropic_metal"
68+
roughness: 0.6
69+
anisotropy: 0.8
70+
preset: "gold"
71+
72+
# Colored walls for visible environment reflections
73+
- name: "wall_red"
74+
type: "lambertian"
75+
albedo: [0.85, 0.12, 0.12]
76+
77+
- name: "wall_blue"
78+
type: "lambertian"
79+
albedo: [0.12, 0.12, 0.85]
80+
81+
geometry:
82+
# Ground
83+
- type: "sphere"
84+
material: "ground"
85+
center: [0, -1000, -1]
86+
radius: 1000
87+
88+
# Top area light (narrow strip to show anisotropic stretching)
89+
- type: "rectangle"
90+
material: "light_top"
91+
corner: [-1.0, 5, -3]
92+
u: [2.0, 0, 0]
93+
v: [0, 0, 4]
94+
95+
# Side fill light
96+
- type: "rectangle"
97+
material: "light_side"
98+
corner: [5, 1, -4]
99+
u: [0, 2, 0]
100+
v: [0, 0, 4]
101+
102+
# --- Back row: varying anisotropy (left=isotropic, right=brushed) ---
103+
- type: "sphere"
104+
material: "gold_aniso_00"
105+
center: [-2.5, 0.8, -2.5]
106+
radius: 0.8
107+
108+
- type: "sphere"
109+
material: "gold_aniso_50"
110+
center: [0, 0.8, -2.5]
111+
radius: 0.8
112+
113+
- type: "sphere"
114+
material: "gold_aniso_95"
115+
center: [2.5, 0.8, -2.5]
116+
radius: 0.8
117+
118+
# --- Front row: varying roughness (left=smooth, right=rough) ---
119+
- type: "sphere"
120+
material: "gold_rough_15"
121+
center: [-2.5, 0.8, 0.5]
122+
radius: 0.8
123+
124+
- type: "sphere"
125+
material: "gold_rough_35"
126+
center: [0, 0.8, 0.5]
127+
radius: 0.8
128+
129+
- type: "sphere"
130+
material: "gold_rough_60"
131+
center: [2.5, 0.8, 0.5]
132+
radius: 0.8
133+
134+
# Colored side walls (provide reflectable environment)
135+
- type: "rectangle"
136+
material: "wall_red"
137+
corner: [-6, 0, -5]
138+
u: [0, 4, 0]
139+
v: [0, 0, 8]
140+
141+
- type: "rectangle"
142+
material: "wall_blue"
143+
corner: [6, 0, -5]
144+
u: [0, 4, 0]
145+
v: [0, 0, 8]

resources/scenes/03_platonic_solids.yaml

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ camera:
77
fov: 40
88

99
settings:
10-
background_color: [0.08, 0.08, 0.12]
11-
background_intensity: 0.2
10+
background_color: [0.04, 0.04, 0.8]
11+
background_intensity: 0.1
1212
use_bvh: true
1313

1414
materials:
@@ -38,11 +38,13 @@ materials:
3838

3939
- name: "area_light"
4040
type: "light"
41-
emission: [12, 11, 10]
41+
color: [1.0, 0.875, 0.75]
42+
emission_intensity: 8
4243

4344
- name: "fill_light"
4445
type: "light"
45-
emission: [3, 3, 4]
46+
color: [1.0, 1.167, 1.0]
47+
emission_intensity: 6
4648

4749
geometry:
4850
- type: "rectangle"
@@ -61,7 +63,7 @@ geometry:
6163
# Fill light (behind camera, subtle)
6264
- type: "rectangle"
6365
material: "fill_light"
64-
corner: [-2, 3, 8]
66+
corner: [-2, 3, -8]
6567
u: [4, 0, 0]
6668
v: [0, 0, 1]
6769
visible: false
@@ -72,33 +74,33 @@ geometry:
7274
- type: "obj"
7375
material: "isc_yellow"
7476
file: "../models/tetrahedron.obj"
75-
position: [-4, 0.5, 0]
76-
scale: [1.0, 1.0, 1.0]
77+
position: [-4, 0.8, 0]
78+
scale: [0.8, 0.8, 0.8]
7779

7880
# 2. Cube (6 faces) - ISC Blue
7981
- type: "obj"
8082
material: "isc_blue"
8183
file: "../models/cube.obj"
82-
position: [-2, 0.1, 0]
83-
scale: [1.0, 1.0, 1.0]
84+
position: [-2, 0.8, 0]
85+
scale: [0.8, 0.8, 0.8]
8486

8587
# 3. Octahedron (8 faces) - ISC Violet
8688
- type: "obj"
8789
material: "isc_violet"
8890
file: "../models/octahedron.obj"
89-
position: [0, 0.5, 0]
90-
scale: [1.0, 1.0, 1.0]
91+
position: [0, 0.8, 0]
92+
scale: [0.8, 0.8, 0.8]
9193

9294
# 4. Dodecahedron (12 faces) - ISC Rose
9395
- type: "obj"
9496
material: "isc_rose"
9597
file: "../models/dodecahedron.obj"
96-
position: [2, 0.1, 0]
97-
scale: [1.0, 1.0, 1.0]
98+
position: [2, 0.8, 0]
99+
scale: [0.8, 0.8, 0.8]
98100

99101
# 5. Icosahedron (20 faces) - ISC Green
100102
- type: "obj"
101103
material: "isc_green"
102104
file: "../models/icosahedron.obj"
103-
position: [4, 0.5, 0]
104-
scale: [1.0, 1.0, 1.0]
105+
position: [4, 0.8, 0]
106+
scale: [0.8, 0.8, 0.8]

src/rayon/constants.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace constants
66
{
77
const std::string ver_major = "1";
88
const std::string ver_minor = "5";
9-
const std::string ver_patch = "2";
9+
const std::string ver_patch = "5";
1010
const std::string version = ver_major + "." + ver_minor + "." + ver_patch;
1111

1212
// Image specifics settings

src/rayon/gpu_renderers/cuda_raytracer.cuh

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "cuda_float3.cuh"
44
#include "cuda_scene.cuh"
55
#include "cuda_utils.cuh"
6+
#include "microfacet_ggx.cuh"
67

78
#include <cfloat>
89
#include <cmath>
@@ -47,20 +48,25 @@ enum LegacyMaterialType
4748
LIGHT = 3,
4849
ROUGH_MIRROR = 4,
4950
CONSTANT = 5,
50-
SHOW_NORMALS = 6
51+
SHOW_NORMALS = 6,
52+
ANISOTROPIC_METAL = 7
5153
};
5254

5355
struct hit_record_simple
5456
{
5557
f3 p, normal;
58+
f3 tangent; // Surface tangent for anisotropic materials
5659
float t;
5760
bool front_face;
5861
LegacyMaterialType material;
5962
f3 color;
6063
float refractive_index;
6164
f3 emission;
6265
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)
6470
};
6571

6672
//==============================================================================
@@ -209,6 +215,12 @@ __device__ inline bool hit_sphere(const f3 &center, float radius, const ray_simp
209215
f3 outward_normal = (rec.p - center) / radius;
210216
rec.front_face = dot(r.dir, outward_normal) < 0;
211217
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);
212224
return true;
213225
}
214226

@@ -245,6 +257,8 @@ __device__ inline bool hit_rectangle(const f3 &corner, const f3 &u, const f3 &v,
245257
rec.p = intersection;
246258
rec.front_face = dot(r.dir, normal) < 0;
247259
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);
248262
return true;
249263
}
250264

@@ -306,6 +320,8 @@ __device__ inline bool hit_triangle(const f3 &v0, const f3 &v1, const f3 &v2,
306320
}
307321

308322
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);
309325
return true;
310326
}
311327

@@ -414,6 +430,14 @@ __device__ __forceinline__ void apply_material(const CudaScene::Material &mat, h
414430
case MaterialType::SHOW_NORMALS:
415431
rec.material = SHOW_NORMALS;
416432
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;
417441
case MaterialType::SDF_MATERIAL: // TODO: Implement SDF materials
418442
rec.material = LAMBERTIAN;
419443
rec.color = mat.albedo;
@@ -632,6 +656,66 @@ __device__ __forceinline__ bool scatter_material(const hit_record_simple &rec, c
632656
emitted = 0.5f * (rec.normal + f3(1.0f, 1.0f, 1.0f));
633657
return false;
634658
}
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+
}
635719
default:
636720
return false;
637721
}

0 commit comments

Comments
 (0)