2

I am creating a small 3d rendering application. I decided to use simple flat shading for my triangles - just calculate the cosine of angle between face normal and light source and scale light intensity by it.

But I'm not sure about how exactly should I apply that shading coefficient to my RGB colors.

For example, imagine some surface at 60 degree angle to light source. cos(60 degree) = 0.5, so I should retain only half of the energy in emitted light.

I could simply scale RGB values by that coefficient, as in following pseudocode:

double shade = cos(angle(normal, lightDir))
Color out = new Color(in.r * shade, in.g * shade, in.b * shade)

But the resulting colors get too dark even at smaller angles. After some thought, that seems logical - our eyes perceive the logarithm of light energy (it's why we can see both in the bright day, and in the night). And RGB values already represent that log scale.

My next attempt was to use that linear/logarithmic insight. Theoretically:

output energy = lg(exp(input energy) * shade)

That can be simplified to:

output energy = lg(exp(input energy)) + lg(shade)
output energy = input energy + lg(shade)

So such shading will just amount to adding logarithm of shade coefficient (which is negative) to RGB values:

double shade = lg(cos(angle(normal, lightDir)))
Color out = new Color(in.r + shade, in.g + shade, in.b + shade)

That seems to work, but is it correct? How it is done in real rendering pipelines?

Rogach
  • 26,050
  • 21
  • 93
  • 172

1 Answers1

2
  1. The color RGB vector is multiplied by the shade coefficient

    The cosine value as you initially assumed. The logarithmic scaling is done by the target imaging device and human eyes

  2. If your colors get too dark then the probable cause is:

    • the cosine or angle value get truncated to integer
    • or your pipeline does not have linear scale output (some gamma corrections can do that)
    • or you have a bug somewhere
    • or your angle and cosine uses different metrics (radians/degrees)
    • you forget to add ambient light coefficient to the shade value
    • your vectors are opposite or wrong (check them visually see the first link on how)
    • your vectors are not in the same coordinate system (light is usually in GCS and Normal vectors in model LCS so you need convert at least one of them to the coordinate system of the other)
  3. The cos(angle) itself is not usually computed by cosine

    As you got all data as vectors then just use dot product

    double shade = dot(normal, lightDir)/(|normal|.|lightDir|)
    

    if the vectors are unit size then you can discard the division by sizes ... that is why normal and light vectors are normalized ...

  4. Some related questions and notes

    GCS/LCS mean global/local coordinate system

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Thanks for comprehensive answer! But can you explain the second point a bit more? I read around, and found difference between linear RGB and sRGB - the latter seems to be log version of the former. I thought that RGB values I give to drawing apis are in sRGB, thus my problem. – Rogach May 27 '15 at 07:50
  • Indeed, java's Color docs explicitly state that it uses sRGB. – Rogach May 27 '15 at 07:53
  • @Rogach forget about RGB/sRGB first most APIs uses logarithmic output scheme due to HW capabilities and compatibility with old stuff. if you create linear gradient from 0.0 to 1.0 the shade is linear hence the RGB values you render you can consider as linear. In reality our senses have logarithmic scale and the HW is build in such way it outputs in logarithmic scale (that is what I meant the scaling is done by HW and eyes) This is because for a very long long time it was not known that our sight is also logarithmic until photometry has emerged. That is why star scale magnitudes are as are... – Spektre May 27 '15 at 07:58
  • @Rogach and the imaging devices was already build/used so the industry stays as is (due to compatibility) and just the terminology slightly changed – Spektre May 27 '15 at 08:00