• Create Account

Awesome job so far everyone! Please give us your feedback on how our article efforts are going. We still need more finished articles for our May contest theme: Remake the Classics

# Chris_F

Member Since 04 Oct 2010
Offline Last Active Today, 06:06 AM

Posted by on 30 April 2013 - 01:16 PM

Well yeah...was just not sure if it would be this strong of a blue tone since gold would be kind of yellowish I guess (the specular itself is more so, so I guess its okay...)

I removed the multiplication, I realized it was wrong since I was multiplying it two times in a row with the fresnel term.

About the GGX, do you have a sample code / paper on how to implement that ?

```float NdotH_2 = NdotH * NdotH;

float a_2 = a * a;

float D = a_2 / pow(NdotH_2 * (a_2 - 1.0) + 1.0, 2.0);
```

Posted by on 30 April 2013 - 12:43 PM

You shouldn't be multiplying your environment map with Fresnel_spec, which uses the half vector.

I would not recommend Gaussian distribution function, or Beckmann, or texture look-ups. GGX is about as cheap as Blinn-Phong to compute, and supports very rough surfaces like Beckmann. Out of all of them I think it looks the best too.

As for your gold looking "blueish", of course it does. It's in a blue environment. If everything around it is blue, then what else is there to reflect but blue?

### #5055851random numbers in for loop (rand())

Posted by on 22 April 2013 - 03:46 PM

If this is C++, then you can potentially do a lot better than the C library's rand function.

http://www.cplusplus.com/reference/random/

### #5050167Making an optimized 3d mmo in open gl c++

Posted by on 04 April 2013 - 06:34 PM

Is Direct x portable to mac??

Yes, with OpenGL.

### #5048381Mesh Format Confusion

Posted by on 30 March 2013 - 02:10 PM

Create your own simple format that can be easily loaded into a vertex buffer. It's a video game, not a 3d modeling suite. There is no reason for it to support a bunch of formats and there is certainly no reason for it to be parsing textual mesh formats at runtime.

### #5048169Why do we need shadow maps?

Posted by on 29 March 2013 - 05:22 PM

It should probably be clear why shadow maps are necessary based solely off the observation that light occluders are often off screen.

### #5047119Direct3D 11 Deferred Shading Banding

Posted by on 26 March 2013 - 08:51 PM

OK, here I did a test with a little C++ and the FreeImage library. I rendered a test cube in Blender with low lighting and no textures, then exported it as a 16-bit EXR. I quantized the image to 8-bits:

Top left you see the results if you output floor(pixelColor * 255f + 0.5f), and then on the right you see floor(pixelColor * 255f + rand), where rand is between 0 and 1. At the bottom I adjusted the contrast to show it off better. The random bias is added after gamma correction but before quantization, so I don't think there is any way to utilize a sRGB backbuffer for this technique.

### #5044937Area lights (Forward and Deferred)

Posted by on 20 March 2013 - 11:11 AM

http://www.gamedev.net/topic/552315-glsl-area-light-implementation/

I think someone may have extended this technique for use in Blender Game Engine:

### #5042506Fresnel equation

Posted by on 12 March 2013 - 05:18 PM

OK, I'm bumping this thread because I'm revisiting the Fresnel equation, this time using complex IOR values. I'm having a hard time converting this to complex numbers.

```float Fresnel(float CosThetaI, float n)
{
float CosThetaT = sqrt(max(0, 1 - (1 - CosThetaI * CosThetaI) / (n * n)));
float NCosThetaT = n * CosThetaT;
float NCosThetaI = n * CosThetaI;
float Rs = pow(abs((CosThetaI - NCosThetaT) / (CosThetaI + NCosThetaT)), 2);
float Rp = pow(abs((CosThetaT - NCosThetaI) / (CosThetaT + NCosThetaI)), 2);
return (Rs + Rp) / 2;
}
```

This is the basic formula, but I need to re-write it so that it looks like:

```float Fresnel(float CosThetaI, vec3 n, vec3 k)
{
...
}
```

Where n and k make up the complex IOR (n + ki). I've taken a few stabs at it, but it's gotten me nowhere. Here is my train wreck of an attempt:

```vec3 Fresnel(float CosThetaI, vec3 n, vec3 k)
{
float temp = 1 - CosThetaI * CosThetaI;

vec3 NKSqr_real = n * n - k * k;
vec3 NKSqr_imag = n * k * 2;

vec3 temp2_real = (temp * NKSqr_real) / (NKSqr_real * NKSqr_real + NKSqr_imag * NKSqr_imag);
vec3 temp2_imag = -(temp * NKSqr_imag) / (NKSqr_real * NKSqr_real + NKSqr_imag * NKSqr_imag);

temp2_real = 1 - temp2_real;
temp2_imag = -temp2_imag;

vec3 CosThetaT_real = sqrt((temp2_real + sqrt(temp2_real * temp2_real + temp2_imag * temp2_imag)) / 2);
vec3 CosThetaT_imag = sign(temp2_imag) * sqrt((-temp2_real + sqrt(temp2_real * temp2_real + temp2_imag * temp2_imag)) / 2);

vec3 NCosThetaT_real = n * CosThetaT_real - k * CosThetaT_imag;
vec3 NCosThetaT_imag = k * CosThetaT_real + n * CosThetaT_imag;

vec3 NCosThetaI_real = n * CosThetaI;
vec3 NCosThetaI_imag = k * CosThetaI;

vec3 CosThetaI_minus_NCosThetaT_real = CosThetaI - NCosThetaT_real;
vec3 CosThetaI_minus_NCosThetaT_imag = -NCosThetaT_imag;

vec3 CosThetaI_plus_NCosThetaT_real = CosThetaI + NCosThetaT_real;
vec3 CosThetaI_plus_NCosThetaT_imag = NCosThetaT_imag;

vec3 a, b, c, d;

a = CosThetaI_minus_NCosThetaT_real;
b = CosThetaI_minus_NCosThetaT_imag;
c = CosThetaI_plus_NCosThetaT_real;
d = CosThetaI_plus_NCosThetaT_imag;

vec3 Rs_real = (a * c + b * d) / (c * c + d * d);
vec3 Rs_imag = (b * c + a * d) / (c * c + d * d);

vec3 Rs = sqrt(Rs_real * Rs_real + Rs_imag * Rs_imag);
Rs = Rs * Rs;

vec3 CosThetaT_minus_NCosThetaI_real = CosThetaT_real - NCosThetaI_real;
vec3 CosThetaT_minus_NCosThetaI_imag = CosThetaT_imag - NCosThetaI_imag;

vec3 CosThetaT_plus_NCosThetaI_real = CosThetaT_real + NCosThetaI_real;
vec3 CosThetaT_plus_NCosThetaI_imag = CosThetaT_imag + NCosThetaI_imag;

a = CosThetaT_minus_NCosThetaI_real;
b = CosThetaT_minus_NCosThetaI_imag;
c = CosThetaT_plus_NCosThetaI_real;
d = CosThetaT_plus_NCosThetaI_imag;

vec3 Rp_real = (a * c + b * d) / (c * c + d * d);
vec3 Rp_imag = (b * c + a * d) / (c * c + d * d);

vec3 Rp = sqrt(Rp_real * Rp_real + Rp_imag * Rp_imag);
Rp = Rp * Rp;

return (Rs + Rp) / 2;
}
```

It would be so much easier if HLSL/GLSL had first class support for complex values.

EDIT:

Never mind. I managed to come up with this.

```vec2 CADD(vec2 a, vec2 b) {    return a + b; }
vec2 CSUB(vec2 a, vec2 b) {    return a - b; }
vec2 CMUL(vec2 a, vec2 b) {    return vec2(a.x * b.x - a.y * b.y, a.y * b.x + a.x * b.y); }
vec2 CDIV(vec2 a, vec2 b) {    return vec2((a.x * b.x + a.y * b.y) / (b.x * b.x + b.y * b.y), (a.y * b.x - a.x * b.y) / (b.x * b.x + b.y * b.y)); }
float CABS(vec2 a) { return sqrt(a.x * a.x + a.y * a.y); }
vec2 CSQRT(vec2 a) { return vec2(sqrt((a.x + sqrt(a.x * a.x + a.y * a.y)) / 2), sign(a.y) * sqrt((-a.x + sqrt(a.x * a.x + a.y * a.y)) / 2)); }

float _Fresnel(float _CosThetaI, vec2 n)
{
vec2 CosThetaI = vec2(_CosThetaI, 0);
vec2 CosThetaT = CSQRT(CSUB(vec2(1.0, 0), CDIV(CSUB(vec2(1.0, 0), CMUL(CosThetaI, CosThetaI)), CMUL(n, n))));
vec2 NCosThetaI = CMUL(n, CosThetaI);
vec2 NCosThetaT = CMUL(n, CosThetaT);
float Rs = pow(CABS(CDIV(CSUB(CosThetaI, NCosThetaT), CADD(CosThetaI, NCosThetaT))), 2);
float Rp = pow(CABS(CDIV(CSUB(CosThetaT, NCosThetaI), CADD(CosThetaT, NCosThetaI))), 2);
return (Rs + Rp) / 2;
}

vec3 Fresnel(float CosThetaI, vec3 n, vec3 k)
{
return vec3(
_Fresnel(CosThetaI, vec2(n.r, k.r)),
_Fresnel(CosThetaI, vec2(n.g, k.g)),
_Fresnel(CosThetaI, vec2(n.b, k.b))
);
}
```

### #5039318Baking Ambient Occlusion maps and dynamic lighting...

Posted by on 04 March 2013 - 09:25 PM

If you keep the ambient occlusion separate from other things (e.g. diffuse) and only use it in the ambient portion of your lighting calculation, then it will look just fine.

### #5038326Your preferred or desired BRDF?

Posted by on 02 March 2013 - 02:28 AM

I've been having an issue with different Cook-Torrance geometry terms though - most of these BRDF's are modulated by NdotV in some way, with the assumption that this can't be negative, else the fragment wouldn't be visible. However, in actual game scenes, this assumption doesn't hold! Simply picture a cube that's had normals generated without hard-edges / with every face in the same smoothing group (or alternatively, picture a sphere that's been LOD'ed super-aggressively into a cube - same thing). In this case there's a huge number of fragments where NdotV will be negative, but simply cutting off the lighting for these fragments looks really unnatural.
To get around these unnatural cut-offs in my game scenes, I've simply scaled/biased NdotV (and NdotL to maintain reciprocity) into the 0-1 range right before doing the specular calculations, which produces a "wrapped geometry term", instead of one that becomes zero at the horizon...
Has anyone else dealt with this issue?

Kelemen Szirmay-Kalos! Kelemen Szirmay-Kalos! Kelemen Szirmay-Kalos!

Okay, okay, I'll try and add some useful content later on. But seriously, it's designed to be a Cook-Torrance geometry term that doesn't suck. It succeeds.

The Kelemen Szirmay-Kalos visibility approximation is indeed very handy. My minimalistic Cook-Torrance uses it because it's dirt cheap and gives good results, but it suffers from the same shortcoming as the original Cook-Torrance geometry term, and that is that it does not take the roughness of the surface into account at all.

I'm still not sure about the way retroreflection is being handled. It seems to me that most natural materials display very little in the way of retroreflection, and mostly at grazing angles. This is captured by Oren-Nayar and GGX. Objects with very high levels of retroreflection are synthetic and consist of macroscopic corner reflectors, or similar. It would be nice if these could be modeled more accurately.

Path tracing of a flat surface made up of corner reflectors. L = V = ~45°

### #5036943Universal OpenGL Version

Posted by on 26 February 2013 - 07:06 PM

There is a tradeoff between features and audience size. Increasing the minimum system requirements gives you greater abilities but may potentially decreases your audience size. What is more important to you, graphics fidelity or broadest possible audience? If it's the former, go with OpenGL 1.1, if it's the latter, go with OpenGL 4.3, if it's somewhere in between... Nobody can tell you whats best for your game. Are you making a FarmVille or are you making a Crysis? What features do you feel you need to reach your artistic goals? Picking the minimum spec that gives you what you need is probably the best option.

### #5036603Your preferred or desired BRDF?

Posted by on 25 February 2013 - 10:58 PM

Wow, thanks! I notice that the math for that distribution is exactly equal to your previous GGX distribution when the two roughness parameters are equal too... does the original GGX paper define this aniso version?

I'm still going to need some kind of retro-reflection hack (or, alternative physical BRDF) in my game so I can boost the effect right up for certain bits of paint and signage and... actual retro-reflector devices (like you put on your bicycle). You're right that there is a bit inherently in this BRDF, but it's mostly only at a grazing angle which is lost to N.L.
A macro-scale retro-reflector like you put on your bike -- a collection of 45º angled "V" shaped mirrored facets -- will direct almost all of the incoming light back towards the incident ray when lit from overhead, but performs worse at glancing angles, and it's this kind of behaviour that I'd ideally like to be able to model.

Here is my own hack. I think it works similarly to yours. The assumption is that the retroreflectiveness decreases at glancing angles.

```analytic

::begin parameters
color Diffuse 1 0 0
color Specular 1 1 1
float DiffuseScale 0 1 0.5
float SpecularScale 0 0.999 .028
float RoughnessX 0.005 2 0.2
float RoughnessY 0.005 2 0.2
float RetroReflection 0 1 0
bool isotropic 1
::end parameters

float saturate(float x) { return clamp(x,0,1); }

vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
float PI = 3.1415926535897932;
vec3 Kd = Diffuse * DiffuseScale;
vec3 Ks = Specular * SpecularScale;

float ax = RoughnessX;
float ay = (isotropic) ? RoughnessX : RoughnessY;

vec3 H = normalize(L + V);
float NdotL = saturate(dot(N, L));
float NdotV = dot(N, V);
float NdotH = dot(N, H);
float LdotH = dot(L, H);
float LdotV = dot(L, V);
float HdotX = dot(H, X);
float HdotY = dot(H, Y);

float ax_2 = ax * ax;
float ay_2 = ay * ay;
float a_2 = (ax_2 + ay_2) / 2;
float NdotL_2 = NdotL * NdotL;
float NdotV_2 = NdotV * NdotV;
float NdotH_2 = NdotH * NdotH;
float LdotV_2 = LdotV * LdotV;
float HdotX_2 = HdotX * HdotX;
float HdotY_2 = HdotY * HdotY;
float OneMinusNdotL_2 = 1.0 - NdotL_2;
float OneMinusNdotV_2 = 1.0 - NdotV_2;

vec3 Fd = 1.0 - Ks;

float gamma = saturate(dot(V - N * NdotV, L - N * NdotL));
float A = 1.0 - 0.5 * (a_2 / (a_2 + 0.33));
float B = 0.45 * (a_2 / (a_2 + 0.09));
float C = sqrt(OneMinusNdotL_2 * OneMinusNdotV_2) / max(NdotL, NdotV);
float OrenNayar = A + B * gamma * C;

vec3 Rd = (Kd / PI) * Fd * OrenNayar;

float GGX_forward = 1.0 / (PI * ax * ay * pow(HdotX_2 / ax_2 + HdotY_2 / ay_2 + NdotH_2, 2.0));
float GGX_retro = a_2 / (PI * pow(LdotV_2 * (a_2 - 1.0) + 1.0, 2.0));

float G1_1 = 2.0 / (1.0 + sqrt(1.0 + a_2 * (OneMinusNdotL_2 / NdotL_2)));
float G1_2 = 2.0 / (1.0 + sqrt(1.0 + a_2 * (OneMinusNdotV_2 / NdotV_2)));
float G_Smith = G1_1 * G1_2;

float G_Retro = NdotV_2 * NdotL;

float DG = mix(GGX_forward * G_Smith, GGX_retro * G_Retro, RetroReflection);

vec3 Fs = Ks + Fd * exp(-6 * LdotH);

vec3 Rs = (DG * Fs) / (4 * NdotV * NdotL);

return Rd + Rs;
}

```

I hope to maybe figure out how to model retroreflection in a more physically accurate way, and to maybe explore if the Smith G can be tailored for the anisotropic version of the distribution.

### #5036404Your preferred or desired BRDF?

Posted by on 25 February 2013 - 12:50 PM

If you are interested in a good overview of the semi-standard lighting models, take a look in the Lighting section of Programming Vertex...

Sorry for intrusion on this thread. I have a question related to "cook_torrance" shader shown on that link.

```float NdotH = saturate( dot( normal, half_vector ) );

...
if( ROUGHNESS_LOOK_UP == roughness_mode )
{
// texture coordinate is:
float2 tc = { NdotH, roughness_value };

// Remap the NdotH value to be 0.0-1.0
tc.x += 1.0f;
tc.x /= 2.0f;

// look up the coefficient from the texture:
roughness = texRoughness.Sample( sampRoughness, tc );
}
```

See author comments in code. Is this a bug? Saturate already clamps value to 0.0 - 1.0 range?

This is indeed unnecessary, and it wouldn't be the first time I saw a mistake or oversight on gpwiki. In any case, I think you can probably do a lot better than a Beckmann lookup texture. The Beckmann distribution is not expensive to calculate and modern GPUs are limited by memory bandwidth, not instruction throughput. Lookup textures only make sense if you can use them to kill a lot of expensive instructions.

### #5036399Your preferred or desired BRDF?

Posted by on 25 February 2013 - 12:29 PM

The features that I think I need so far are: Non-lambertian diffuse, IOR/F(0º)/spec-mask, anisotropic roughness, metal/non-metal, retro-reflectiveness and translucency.

I took Chris_F's BRDF containing Cook-Torrence/Schlick/GGX/Smith and Oren-Nayar, and re-implemented it with hacked support for anisotropy (based roughly on Ashikhmin-Shirley) and retroreflectivity.

If both the roughness factors are equal (or if the isotropic bool is true), then the distribution should be the same as GGX, otherwise it behaves a bit like Ashikhmin-Shirley. Also, the distribution isn't properly normalized any more though when using anisotropic roughness.

The retro-reflectivity is a complete hack and won't be energy conserving. When the retro-reflectivity factor is set to 0.5, you get two specular lobes -- a regular reflected one, and one reflected back at the light source -- without any attempt to split the energy between them. At 0 you just get the regular specular lobe, and at 1 you only get the retro-reflected one.

BRDF Explorer file for anyone interested: http://pastebin.com/6ZpQGgpP

Thanks again for sending me on a weekend BRDF exploration quest, Chris and Promit

Actually, it's a lot easier to convert it to anisotropic than that.

```analytic

::begin parameters
color Diffuse 1 0 0
color Specular 1 1 1
float DiffuseScale 0 1 0.5
float SpecularScale 0 0.999 .028
float RoughnessX 0.005 2 0.2
float RoughnessY 0.005 2 0.2
bool isotropic 1
::end parameters

float saturate(float x) { return clamp(x,0,1); }

vec3 BRDF( vec3 L, vec3 V, vec3 N, vec3 X, vec3 Y )
{
float PI = 3.1415926535897932;
vec3 Kd = Diffuse * DiffuseScale;
vec3 Ks = Specular * SpecularScale;

float ax = RoughnessX;
float ay = (isotropic) ? RoughnessX : RoughnessY;

vec3 H = normalize(L + V);
float NdotL = saturate(dot(N, L));
float NdotV = dot(N, V);
float NdotH = dot(N, H);
float LdotH = dot(L, H);
float HdotX = dot(H, X);
float HdotY = dot(H, Y);

float ax_2 = ax * ax;
float ay_2 = ay * ay;
float a_2 = (ax_2 + ay_2) / 2;
float NdotL_2 = NdotL * NdotL;
float NdotV_2 = NdotV * NdotV;
float NdotH_2 = NdotH * NdotH;
float HdotX_2 = HdotX * HdotX;
float HdotY_2 = HdotY * HdotY;
float OneMinusNdotL_2 = 1.0 - NdotL_2;
float OneMinusNdotV_2 = 1.0 - NdotV_2;

vec3 Fd = 1.0 - Ks;

float gamma = saturate(dot(V - N * NdotV, L - N * NdotL));
float A = 1.0 - 0.5 * (a_2 / (a_2 + 0.33));
float B = 0.45 * (a_2 / (a_2 + 0.09));
float C = sqrt(OneMinusNdotL_2 * OneMinusNdotV_2) / max(NdotL, NdotV);
float OrenNayar = A + B * gamma * C;

vec3 Rd = (Kd / PI) * Fd * OrenNayar;

float D = 1.0 / (PI * ax * ay * pow(HdotX_2 / ax_2 + HdotY_2 / ay_2 + NdotH_2, 2.0));

vec3 Fs = Ks + Fd * exp(-6 * LdotH);

float G1_1 = 2.0 / (1.0 + sqrt(1.0 + a_2 * (OneMinusNdotL_2 / NdotL_2)));
float G1_2 = 2.0 / (1.0 + sqrt(1.0 + a_2 * (OneMinusNdotV_2 / NdotV_2)));
float G = G1_1 * G1_2;

vec3 Rs = (D * Fs * G) / (4 * NdotV * NdotL);

return Rd + Rs;
}