Reference from: Playing Computer Graphics with JavaScript (2) Basic Light Sources - Milo Yip - Blog Park , mainly about the three most basic light sources - parallel light, point light source, spotlight, which are actually three mathematical models.
code adjustment
In the previous code, the color was calculated by the geometry itself, so its use was limited. In the Phong material, the effect of the display is already very good, but the Phong material is to assume a light source. Our code needs to change from object-oriented rendering to light-oriented rendering.
New logic: https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DLight.cpp
Main logic code:
void PhysicsEngine::RenderLightIntern(World& world, const PerspectiveCamera& camera, BYTE* buffer, cint width, cint height) { for ( auto y = 0; y < height; y++) { const auto sy = 1.0f - (1.0f * y / height); for (auto x = 0; x < width; x++) { const auto sx = 1.0f * x / width; // sx and sy project the screen to the [0,1] interval // Generate rays const auto ray = camera.GenerateRay(sx, sy); // Test if the ray intersects the ball auto result = world.Intersect(ray); if (result.body) { color color; for ( auto & k : world.lights) { // this is different auto lightSample = k->Sample(world, result.position); if (!lightSample.empty()) { auto NdotL = DotProduct(result.normal, lightSample.L); // Calculate the angle // The included angle is an acute angle, the light source is in front of the plane if (NdotL >= 0) // Accumulate all rays // NdotL is the projection of the light source direction on the normal vector color = color + (lightSample.EL * NdotL); } } buffer[0] = BYTE(color.b * 255); buffer[1] = BYTE(color.g * 255); buffer[2] = BYTE(color.r * 255); buffer[3] = 255; } else { // no contact, just background color buffer[0] = 0; buffer[1] = 0; buffer[2] = 0; buffer[3] = 255; } buffer += 4; } } }
Simply put, when finding the intersection, do:
- Calculate the light color and light direction of each light source based on this point
- Calculate the projection of the ray on the normal vector of the intersection as the basis for the color mixing ratio
- add up all colors
Three light sources are described below
Parallel light
Parallel lightProperties of directional light:
// Parallel light class DirectionalLight : public Light { public: DirectionalLight(color irradiance, vector3 direction); LightSample Sample(World& world, vector3 position) override; color irradiance; // Amplitude vector3 direction; // Light direction vector3 L; // Light source direction }; DirectionalLight::DirectionalLight(color irradiance, vector3 direction) : irradiance(irradiance), direction(direction) { L = -Normalize(direction); } LightSample DirectionalLight::Sample(World& world, vector3 position) { static LightSample zero; if (shadow) { const Ray shadowRay(position, L); const auto shadowResult = world.Intersect(shadowRay); if (shadowResult.body) return zero; } return LightSample(L, irradiance); // just return the light source color }
Note here that L is the light direction unit vector.
For parallel light, we only need to know the direction of the light source and the color of the light source. Very simple, don't count projections, it's the job of the main logic.
Let's talk about shadows here. Parallel lights have shadows. When looking from the intersection to the direction of the light source, if there is an obstacle in the middle, it will return to black.
Point light source
Point light source// Point light class PointLight : public Light { public: PointLight(color intensity, vector3 position); LightSample Sample(World& world, vector3 position) override; color intensity; // radiation intensity vector3 position; // light source position }; static LightSample zero; LightSample PointLight::Sample(World& world, vector3 pos) { // Calculate L, but keep r and r^2 for later use const auto delta = position - pos; // distance vector const auto rr = SquareMagnitude(delta); const auto r = sqrtf(rr); // calculate light source distance to pos const auto L = delta / r; // distance unit vector if (shadow) { const Ray shadowRay(pos, L); const auto shadowResult = world.Intersect(shadowRay); // The intersection point within r will block the light source // shadowResult.distance <= r means: // with pos intersection point -> the light source position emits a shadow test ray // If the shadow test ray intersects with other objects, then the intersection distance <= r // Indicates that the pos position cannot directly see the light source if (shadowResult.body && shadowResult.distance <= r) return zero; } // Inverse square attenuation const auto attenuation = 1 / rr; // Return the light color after attenuation return LightSample(L, intensity * attenuation); }
The point light source has an inverse square decay law, so it is necessary to first calculate the distance r from the light source to the intersection point pos, and then find L. In fact, L is the unit vector of the direction from the light source position to the intersection point. Then to calculate the color, the color intensity of the point light source itself becomes intensity * attenuation due to attenuation.
Let's talk about the shadow, how to calculate the shadow of the point light source? This is more complicated than directional light. A ray is emitted from the intersection to the light source position. If there is an obstacle in it, it is blocked and returns to black (that is, the occlusion test).
spotlight
spotlight// Spotlight class SpotLight : public Light { public: SpotLight(color intensity, vector3 position, vector3 direction, float theta, float phi, float falloff); LightSample Sample(World& world, vector3 position) override; color intensity; // radiation intensity vector3 position; // light source position vector3 direction; // light direction float theta; // inner angle of inner cone float phi; // inner angle of outer cone float falloff; // attenuation /* The following are precomputed constants*/ vector3 S; // light source direction float cosTheta; // cos(inner cone angle) float cosPhi; // cos(outer cone angle) float baseMultiplier; // 1/(cosTheta-cosPhi) }; SpotLight::SpotLight(color intensity, vector3 position, vector3 direction, float theta, float phi, float falloff) : intensity(intensity), position(position), direction(direction), theta(theta), phi(phi), falloff(falloff) { S = -Normalize(direction); cosTheta = cosf(theta * float(M_PI) / 360.0f); cosPhi = cosf(phi * float(M_PI) / 360.0f); baseMultiplier = 1.0f / (cosTheta - cosPhi); } LightSample SpotLight::Sample(World& world, vector3 pos) { // Calculate L, but keep r and r^2 for later use const auto delta = position - pos; // distance vector const auto rr = SquareMagnitude(delta); const auto r = sqrtf(rr); // calculate light source distance to pos const auto L = delta / r; // distance unit vector /* * spot(alpha) = * * 1 * where cos(alpha) >= cos(theta/2) * * pow( (cos(alpha) - cos(phi/2)) / (cos(theta/2) - cos(phi/2)) , p) * where cos(phi/2) < cos(alpha) < cos(theta/2) * * 0 * where cos(alpha) <= cos(phi/2) */ // 计算spot auto spot = 0.0f; const auto SdotL = DotProduct(S, L); if (SdotL >= cosTheta) spot = 1.0f; else if (SdotL <= cosPhi) spot = 0.0f; else spot = powf((SdotL - cosPhi) * baseMultiplier, falloff); if (shadow) { const Ray shadowRay(pos, L); const auto shadowResult = world.Intersect(shadowRay); // The intersection point within r will block the light source // shadowResult.distance <= r means: // with pos intersection point -> the light source position emits a shadow test ray // If the shadow test ray intersects with other objects, then the intersection distance <= r // Indicates that the pos position cannot directly see the light source if (shadowResult.body && shadowResult.distance <= r) return zero; } // Inverse square attenuation const auto attenuation = 1 / rr; // Return the light source color after attenuation return LightSample(L, intensity * (attenuation * spot)); }
Spotlight is a very complex mathematical model, we don't explore why the formula is like this, just implement it.
There is not much to talk about pure mathematical calculations, there is mainly a spot (spotlight coefficient), so the final color is intensity * (attenuation * spot). Others are similar to the implementation of point light sources.
three primary color mixing
three primary colors of lightI originally thought about how to achieve this, but now I figured it out, that is, put three spotlights at a certain point (a point on the plane), and mix the final colors (add up).
A simple statement: the light of the three light sources are RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), mixed together, and the addition is RGB(255,255,255), white.
See a question in Playing Computer Graphics with JavaScript (2) Basic Light Source - Milo Yip - Blog Park :
What if the radiation intensity is negative? (Although the existence of antiphotons has not been confirmed, can the reader think of a graphical function?)
It feels like multiplication in PS. See How to simply understand multiplication and color filtering? .
Next, we will discuss the realization of painted light.
Backed up by https://zhuanlan.zhihu.com/p/31015884 .