mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.74k stars 35.38k forks source link

WebGLRenderer: Specular component implementation #4363

Closed mrdoob closed 9 years ago

mrdoob commented 10 years ago

@tstanev suggests here a tweaked code to calculate the specular component:

"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
"float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
"vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
"dirSpecular += specular * schlick * directionalLightColor[ i ] * dirSpecularWeight;",

It goes a bit over my head at the moment so some comments would be very much appreciated.

WestLangley commented 10 years ago

We actually implemented this two years ago, but it is coming to light now. We just made physically-based shading the default in three.js r.65.

The short answer to the referenced comment is the that cosine term is part of the reflectance equation.

    Lo( x, ωo ) = ∫ fr( x, ωo, ωi ) Li( x, ωi ) cos( θi ) dωi

The specular normalization constant is required to normalize the integrand of the fr term, which is the bi-directional reflectance distribution (BRDF).

There are many references for how to implement the lighting equation for point lights, but here are two excellent ones from the 2010 SIGRAPH: Hoffman_a, Hoffman_b.

It is especially important to use reasonable constants now that a physically-based model is the default in three.js.

Set the ambient reflectance of the material to match the material's diffuse reflectance (three.js calls this color).

Set the specular reflectance for non-metals in the 0x050505 to 0x101010 range, or so. The shininess parameter can range from 1 to 10000.

For metals, you will have to experiment. The specular term can be anything up to 0xffffff. (For metals, the specular term is the color.) In theory at least, the diffuse and ambient terms should be zero for metals, but I have achieved better-looking results with non-zero diffuse.

tstanev commented 10 years ago

OK, I can see that the shader now implements the physically based model given in the second linked paper and there is justification for the formula, but I'd argue that it has nothing to do with being a "Phong" material anymore, so using "usual" Phong material settings would result in strange looking output... I realize I can manually change material settings to get reasonable appearance again, but for the most part I don't control the materials of the models I am displaying... Anyway, thanks for the explanation, easy enough to work around on my side, but the regular Phong model should have at least been kept as an ifdef option for the "MeshPhongMaterial"...

WestLangley commented 10 years ago

I'd argue that it has nothing to do with being a "Phong" material anymore

You have to differentiate between the shading model and the illumination model.

For MeshLambertMaterial, the illumination model is Lambertian, and the shading model is Gouraud.

For MeshPhongMaterial, the illumination model is (modified) Blinn-Phong, and the shading model is Phong.

the regular Phong model should have at least been kept as an ifdef option for the "MeshPhongMaterial"

There are dozens of other models that could have been pre-packaged for the user, but these were the two that were selected.

Fortunately, using ShaderMaterial, you can implement any illumination/shading model you like.

mrdoob commented 10 years ago

I wonder if it would make more sense if MeshLambertMaterial got renamed to MeshGouraudMaterial...

WestLangley commented 10 years ago

I wonder if it would make more sense if MeshLambertMaterial got renamed to MeshGouraudMaterial...

I think the names should be based on what is important about about the material's illlumination model, not the shading technique...

To review, MeshPhongMaterial uses the phong shader to implement a physically-based, microfacet BRDF illumination model.

The model has

The Distribution term is derived from the Blinn-Phong model.

The MeshPhongMaterial shading model is Phong: vertex normals are interpolated across the surface of the polygon, and the illumination calculation is performed at each texel.

MeshLambertMaterial uses the lambert shader to implement a simple Lambertian illumination model.

The MeshLambertMaterial shading model is Gouraud: the illumination calculation is performed at each vertex, and the resulting color is interpolated across the face of the polygon.

MeshPhongMaterial and MeshLambertMaterial and MeshBasicMaterial support

In addition, MeshPhongMaterial and MeshLambertMaterial also support

And MeshPhongMaterial adds

CanvasRender uses Gouraud shading because it has to.

Do we really need to implement Gouraud shading with WebGLRenderer?

Do we need MeshLambertMaterial at all?

tstanev commented 10 years ago

Usually those things are named after the lighting model, not after being per-vertex or per-pixel interpolated. Since the new lighting formula is decidedly not the Blinn-Phong model (Hoffman started with it, but changed pretty much all the terms), yet the material does all the usual things one expects to see in a gaming uber-material, I'd suggest renaming MeshPhongMaterial to MeshUberMaterial (or MeshStandardMaterial?).

While it's inconvenient that the change to the lighting computation regressed appearance of existing scenes, it was easy enough to track down, and if I'm the only one complaining, please ignore my shader patch.