Full Version: VDrift lighting, materials, textures
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
This thread is meant to be a dump of info about VDrift lighting, materials, textures which will end up in the wiki in a more distilled form.

The second texture has been giving me some headaches. I will be reviewing the shader code and try to document what actually happens there.

RGB channels of the second texture store fresnel reflection at normal incidence (angle of incidence = 0°).

Fresnel reflectance describes the reflectivity of a surface in dependency of the view angle. It looks like this:

[Image: Fresnel_reflection.svg]

The reflectance will go up with increasing angle of incidence. VDrift is using Schlick's approximation, but with power of 3 instead of 5.

Why are 3 channels (RGB) used for it? It turns out that the reflectance is frequency dependent. For gold, silver and aluminium it looks like this:

[Image: reflectance.jpg]

What does that mean for an artist? With RGB set to 1.0 and A to 0.0 he should get a perfect mirror. This is unfortunately not what happens in VDrift currently, needs to be fixed.

So what is the deal with the A channel? The A channel stores the info about surface roughness. With higher values surface reflections should become blurrier, with reduced specular highlights. It will look more dull.

With higher roughness the diffuse component will be increased and the specular reduced:

[Image: Gloss2.jpg]
(05-01-2013, 11:04 PM)joevenzon Wrote: [ -> ]I think this is a cool setup for a material system:

Hmm, looks quite clever and their material editor is open source too.

But we would need at least two textures for material parameters?
1. Texture (roughness, metallic, anisotropic)
2. Texture (specular, specularTint, clearcoat, clearcoatGloss)

Remaining parameters could use fixed values, are not so interesting for us?
sheen, sheenTint, subsurface

0. texture (diffuse rgb + a mask)
1. texture (roughness, metallic, specular, specularTint)
2. optional texture (clearcoat, clearcoatGloss) mostly interesting for carpaint, overrides fixed parameters

Other parameters (anisotropic, subsurface, sheen, sheenTint) fixed or per model?

Link to the Disney BRDF viewer:
Hm, we need specular rgb for metallic materials.

0: diffuse rgb, mask
1: specular rgb, specularTint
2: metallic, roughness, clearcoat, clearcoatTint
3: normal rgb

This means diffuse map, specular map, surface map, normal map.
It sounds like they don't use a specular RGB; instead, the metallic model uses the base color as the specular color, with black diffuse. Clearcoat uses a white specular color. Specular tint sounds like it tints the non-metallic model's specular toward the base color (rather than white).

One other alternative, which I haven't thought fully through yet, would be to use the artist-facing material parameters to generate a 2D lookup texture similar to the "image slices" they use often to visualize BRDFs in the course notes PDF (or some other lookup representation). Then, atlas a bunch of those lookups into a single texture that represents a shared material library for the entire car (or even game). The per-car textures could be base color tint plus a material ID lookup. This might save some shader computation -- instead of computing the BRDF based on all the input parameters, precompute a lot of it and store it in the material lookup atlas. I don't know if it's worth it, though, because it complicates the artist workflow and requires a material editor tool of some kind.
"It sounds like they don't use a specular RGB; instead, the metallic model uses the base color as the specular color, with black diffuse." Ah, that is the part I've missed somehow. This reduces the number of interesting parameters to 6. And if we use fixed clearcoat values for car paint there would be only 4: metallic, roughness, spec, spec tint.

For the material lookup texture, you mean something like a discrete number of materials (4096*4096)? But how many parameters are needed per BRDF, would 4 channels be enough?

Hmm, 4096 material texture would need a 24bit material index...
I like VDrift's car material and those shaders. Looked only once inside and they seem quite complicated.
How is the metallic or roughness map made ? Is it drawn by hand, generated somehow form others or baked in blender or yet another way ?
Can you tell in short how do you achieve roughness in shader ? So far i know reflection map is quite big, had to be small to make it blured. Is there some filter that samples it multiple times ? Or how does one get a not so detailed reflection, some sort of glossines and not exact mirror ?
The material map has to be done by the artist.

Roughness (or glossiness) value is used to select the mip level of the environment map.
float reflection_lod = mix(5, 0, roughness);
textureCube(envmap, refdir, reflection_lod);
I've got the disney(GGX) brdf running with current vdrift material system, just to see the differences... roughness value 1.0 (smooth) and specular reflectance 0.5

Current brdf without clearcoat (gl2 renderer)
[Image: W8VSTx1.jpg]

Update: Disney brdf (GGX) without clearcoat (gl2 renderer)
[Image: m5maNdb.jpg]

Disney brdf (GGX) without clearcoat max roughness (gl2 renderer)
[Image: IhtbWvt.jpg]

I am plugging roughness here directly into the brdf, which is not exactly correct, need to use some mapping like disney guys maybe.

// Fresnel term F [f0, 1]
// Schlick: F(f0, hv) = f0 + (1 - f0) * (1 - hv)^5
vec3 F(const vec3 f0, const float hv)
    return f0 + (vec3(1) - f0) * pow(1 - hv, 5);

// Microfacet normal distribution D [0, 1]
// Statistical distribution of surface normals
// ag (width) determines shape, ag -> 0 dirac delta
// GGX: D(nh, ag) = ag^2 / (pi * (nh^2 * (ag^2 - 1) + 1)^2)
float D(const float nh, const float ag)
    float ag2 = ag * ag;
    float nh2 = nh * nh;
    float s = (nh2 * (ag2 - 1) + 1);
    float s2 = s * s;
    return ag2 / (PI * s2);

// Shadow masking function G [0, 1]
// Needed to maintain energy conservation of D
// ag (width) determines shape, ag -> 0 box function
// GGX: G = G1(nv, ag) * G1(nl, ag) with G1(x, ag) = 2 / (1 + sqrt(ag^2 / x^2 - ag^2 + 1))
float G(const float nv, const float nl, const float ag)
    float ag2 = ag * ag;
    float nl2 = nl * nl;
    float nv2 = nv * nv;
    return 2 / ((1 + sqrt(ag2 / nl2 - ag2 + 1)) * 2 / (1 + sqrt(ag2 / nv2 - ag2 + 1)));

vec3 BRDF(const vec3 fd, const vec3 f0, const float hv, const float nv, const float nl, const float nh, const float roughness)
    return fd / PI + D(nh, roughness) * F(f0, hv) * G(nv, nl, roughness) / (4 * nl * nv)
It will need some more testing, but the difference seems not too large (even without any roughness tweaks in GGX). So I think we can stick with current brdf have ggx brdf as option (as it is a bit more complex).

Next step is rewriting the material encoding in the second texture to use (spec, metallic, roughness, clearcoat) and update existing misc1 textures.
The guys at epic got inspired by the disney paper and applied some of its ideas to the unreal engine. Notes are here:
(07-27-2013, 03:40 PM)joevenzon Wrote: [ -> ]The guys at epic got inspired by the disney paper and applied some of its ideas to the unreal engine. Notes are here:

Thanks. They've certainly spent some time looking into this, are using simpler G and F terms.

They reduced the parameters to Metallic and Roughness (+ Cavity for detail self shadows) which is nice too.

Will give it a try.

PS: And this IBL stuff also looks interesting, even if we are not using it.
Have spent the afternoon staring at car photos and trying to get somewhat realistic lighting with the disney model.

Something like this:

Without satisfying results unfortunately. Would need brdf samples of the paint and run a solver to fit the parameters I guess, LOL. Maybe I am trying too hard...
Here the vdrift.brdf (specular only) to be used with Disney BRDF Explorer ( ):

::begin parameters
float roughness 0.001 1 .1
float metallic 0.001 1 .1
color color 1 1 1
::end parameters

::begin shader

const float pi = 3.14159265358979323846;

vec3 Fresnel(vec3 f0, float cos_theta)
    float b = 1 - cos_theta;
    float b2 = b * b;
    return f0 + (1 - f0) * b2 * b2 * b;

vec3 VDriftBRDF(vec3 f0, float r, float nv, float nl, float nh, float vh)
    float a = r * r;
    float a2 = a * a;
    float d = nh * nh * (a2 - 1) + 1;
    float r1 = r + 1;
    float k = r1 * r1 / 8;
    vec3 f = Fresnel(f0, vh);
    return f * a2 / (4 * pi * d * d * (nl * (1 - k) + k) * (nv * (1 - k) + k));

vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
    vec3 f0 = mix(vec3(0.04), color, metallic);
    vec3 H = normalize(V + L);
    float nv = max(0.0, dot(N, V));
    float nl = max(0.0, dot(N, L));
    float nh = max(0.0, dot(N, H));
    float vh = max(0.0, dot(V, H));
    return VDriftBRDF(f0, roughness, nv, nl, nh, vh) ;

::end shader

Try to match some of the brdf samples from here Wink