Gejala: Kerusakan Visual pada Surface PBR Material
Saat melakukan sinkronisasi dataset Physically Based Rendering (PBR) dari DCC tool (seperti Blender atau Substance Painter) ke backend service, pipeline rendering visual 3D web/engine menampilkan distorsi pencahayaan. Gejala utama yang ditemukan antara lain:
- Specular highlight pecah: Transisi specular reflectance pada sudut pandang miring (Fresnel F0) membentuk kontur tajam alih-alih gradasi halus.
- Roughness micro-banding: Permukaan material glossy (roughness rendah di rentang
[0.01, 0.08]) ter-snap menjadi nol absolut atau melompat ke nilai0.0625. Nilai ini menghasilkan artefak cermin sempurna palsu. - Perbedaan warna albedo: Warna base color tampak pudar (terlalu terang) saat di-render di engine downstream.
Analisis Root Cause
Investigasi log dan network payload menunjukkan dua sumber masalah utama pada backend data layer.
1. Truncation pada Tipe Data PostgreSQL FLOAT (REAL)
Skema database awal mendefinisikan kolom parameter material menggunakan tipe FLOAT tanpa spesifikasi presisi eksplisit. Pada PostgreSQL, FLOAT(1) hingga FLOAT(24) dipetakan ke REAL (IEEE 754 single precision, 32-bit floating point), yang hanya memiliki presisi sekitar 6-9 digit desimal signifikan dengan 23-bit mantissa.
Ketika nilai mikro-permukaan seperti roughness 0.040000000000000008 (standar dielektrik F0) disimpan dan dibaca berulang kali melalui query agregasi atau round-trip DB, representasi biner 32-bit memotong bit mantissa. Hasilnya adalah presisi yang terdegradasi secara permanen sebelum data diteruskan ke shader engine.
2. Parsing Color Space: Ambiguitas sRGB vs Linear RGB
Payload JSON menyuplai nilai albedo dan specular reflectivity dalam ruang warna sRGB ter-encode (non-linear). Renderer 3D modern membutuhkan kalkulasi pencahayaan dalam ruang warna linear. Handler backend langsung meng-unmarshal nilai sRGB ke dalam floating point tanpa transformasi fungsi transfer elektro-optik (EOTF), lalu menyimpannya mentah-mentah ke database. Engine rendering mengasumsikan input database sudah dalam format linear, menyebabkan over-brightening pada mid-tone.
Langkah Remediasi dan Implementasi
1. Migrasi Skema Database
Ubah skema kolom material dari REAL menjadi DOUBLE PRECISION (64-bit float IEEE 754) atau NUMERIC jika dibutuhkan presisi desimal eksak deterministik. Untuk pipeline render, DOUBLE PRECISION memberikan keseimbangan optimal antara efisiensi memori, performa komputasi GPU/CPU, dan presisi yang memadai.
-- Migrasi skema material PBR
ALTER TABLE pbr_materials
ALTER COLUMN roughness TYPE DOUBLE PRECISION,
ALTER COLUMN metallic TYPE DOUBLE PRECISION,
ALTER COLUMN specular_f0 TYPE DOUBLE PRECISION,
ALTER COLUMN base_color_r TYPE DOUBLE PRECISION,
ALTER COLUMN base_color_g TYPE DOUBLE PRECISION,
ALTER COLUMN base_color_b TYPE DOUBLE PRECISION;
-- Tambahkan constraint untuk memastikan domain nilai PBR valid [0.0, 1.0]
ALTER TABLE pbr_materials
ADD CONSTRAINT chk_roughness_range CHECK (roughness >= 0.0 AND roughness <= 1.0),
ADD CONSTRAINT chk_metallic_range CHECK (metallic >= 0.0 AND metallic <= 1.0),
ADD CONSTRAINT chk_specular_f0_range CHECK (specular_f0 >= 0.0 AND specular_f0 <= 1.0);
2. Perbaikan Serializer dan Normalisasi Linear (Go)
Implementasikan parser DTO yang membaca payload, memvalidasi boundary range, dan menerapkan konversi sRGB ke linear secara eksplisit sebelum persistensi.
package material
import (
"errors"
"math"
)
type PBRMaterialPayload struct {
Roughness float64 `json:"roughness"`
Metallic float64 `json:"metallic"`
BaseColor [3]float64 `json:"base_color"` // Input: sRGB ter-encode [0.0, 1.0]
}
// SRGBToLinear mengonversi nilai komponen sRGB ke Linear sRGB standar IEC 61966-2-1
func SRGBToLinear(c float64) float64 {
if c <= 0.04045 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
func (p *PBRMaterialPayload) NormalizeAndValidate() error {
if p.Roughness < 0.0 || p.Roughness > 1.0 {
return errors.New("roughness di luar rentang valid [0.0, 1.0]")
}
if p.Metallic < 0.0 || p.Metallic > 1.0 {
return errors.New("metallic di luar rentang valid [0.0, 1.0]")
}
for i := 0; i < 3; i++ {
val := p.BaseColor[i]
if val < 0.0 || val > 1.0 {
return errors.New("komponen base_color di luar rentang valid [0.0, 1.0]")
}
p.BaseColor[i] = SRGBToLinear(val)
}
return nil
}
Unit Testing Validasi Presisi Floating Point
Floating-point testing tidak boleh menggunakan perbandingan persamaan eksak (==). Gunakan pendekatan delta epsilon (math.Abs(a - b) <= epsilon) untuk memverifikasi akurasi deserialisasi dan transformasi linear.
package material
import (
"math"
"testing"
)
const Epsilon = 1e-9
func TestSRGBToLinearPrecision(t *testing.T) {
cases := []struct {
name string
input float64
expected float64
}{
{name: "Batas threshold bawah", input: 0.04045, expected: 0.04045 / 12.92},
{name: "Zero", input: 0.0, expected: 0.0},
{name: "One (Max)", input: 1.0, expected: 1.0},
{name: "Midtone 0.5", input: 0.5, expected: 0.21404114048223255},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
actual := SRGBToLinear(tc.input)
diff := math.Abs(actual - tc.expected)
if diff > Epsilon {
t.Fatalf("Input %f: hasil %f berbeda dari expected %f (selisih: %e)",
tc.input, actual, tc.expected, diff)
}
})
}
}
Rekomendasi Operasional
- Gunakan
DOUBLE PRECISIONdi PostgreSQL untuk semua parameter optik mikro PBR guna menghindari truncation 23-bit mantissa pada level storage. - Terapkan konversi ruang warna sRGB-ke-Linear di batas API payload ingestion, bukan di database view atau rendering pass frontend.
- Pastikan serializer driver backend (misalnya database driver
pgxataunode-postgres) mengikat nilai sebagai native float64 tanpa konversi string perantara yang memicu rounding lokal.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!