參考自:用JavaScript玩轉計算機圖形學(二)基本光源 - Milo Yip - 博客園,主要講述三種最基本的光源——平行光、點光源、聚光燈,其實就是三種數學模型。html
先前的代碼中,顏色是由幾何物體自身計算得出,所以使用頗有限。在Phong材質中,顯示的效果已經很不錯了,然而Phong材質是要假定有一個光源的。咱們的代碼須要從以面向物體渲染爲面向光源渲染。git
新的邏輯:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DLight.cppgithub
主邏輯 代碼:ide
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和sy將屏幕投影到[0,1]區間 // 產生光線 const auto ray = camera.GenerateRay(sx, sy); // 測試光線與球是否相交 auto result = world.Intersect(ray); if (result.body) { color color; for (auto & k : world.lights) { // 這裏不同了 auto lightSample = k->Sample(world, result.position); if (!lightSample.empty()) { auto NdotL = DotProduct(result.normal, lightSample.L); // 計算角度 // 夾角爲銳角,光源在平面前面 if (NdotL >= 0) // 累計全部光線 // NdotL 就是光源方向在法向量上的投影 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 { // 沒有接觸,就是背景色 buffer[0] = 0; buffer[1] = 0; buffer[2] = 0; buffer[3] = 255; } buffer += 4; } } }
簡單來講,就是求出交點時,作:測試
下面介紹三種光源spa
平行光的屬性:code
// 平行光 class DirectionalLight : public Light { public: DirectionalLight(color irradiance, vector3 direction); LightSample Sample(World& world, vector3 position) override; color irradiance; // 幅照度 vector3 direction; // 光照方向 vector3 L; // 光源方向 }; 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); // 就返回光源顏色 }
這裏注意L是光源方向單位向量。orm
平行光咱們只須要知道光源方向和光源顏色就能夠了。很是簡單,不用算投影,這是主邏輯的工做。htm
這裏說一下陰影,平行光有陰影,當從交點向光源方向看時,若是中間有障礙物,就返回黑色。blog
// 點光源 class PointLight : public Light { public: PointLight(color intensity, vector3 position); LightSample Sample(World& world, vector3 position) override; color intensity; // 幅射強度 vector3 position; // 光源位置 }; static LightSample zero; LightSample PointLight::Sample(World& world, vector3 pos) { // 計算L,但保留r和r^2,供以後使用 const auto delta = position - pos; // 距離向量 const auto rr = SquareMagnitude(delta); const auto r = sqrtf(rr); // 算出光源到pos的距離 const auto L = delta / r; // 距離單位向量 if (shadow) { const Ray shadowRay(pos, L); const auto shadowResult = world.Intersect(shadowRay); // 在r之內的相交點纔會遮蔽光源 // shadowResult.distance <= r 表示: // 以pos交點 -> 光源位置 發出一條陰影測試光線 // 若是陰影測試光線與其餘物體有交點,那麼相交距離 <= r // 說明pos位置沒法直接看到光源 if (shadowResult.body && shadowResult.distance <= r) return zero; } // 平方反比衰減 const auto attenuation = 1 / rr; // 返回衰減後的光源顏色 return LightSample(L, intensity * attenuation); }
點光源有一個平方反比衰減規律,故而要先算光源到交點pos的距離r,而後求出L,實際上L就是光源位置到交點的方向單位向量。接着要計算顏色,點光源自己顏色intensity,因爲有衰減,所以變成了intensity * attenuation。
再說下陰影,如何計算點光源的陰影?這比平行光復雜些。從交點處向光源位置發出一條光線,若是當中有障礙物,那麼被遮擋,返回黑色(就是遮擋測試)。
// 聚光燈 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; // 幅射強度 vector3 position; // 光源位置 vector3 direction; // 光照方向 float theta; // 內圓錐的內角 float phi; // 外圓錐的內角 float falloff; // 衰減 /* 如下爲預計算常量 */ vector3 S; // 光源方向 float cosTheta; // cos(內圓錐角) float cosPhi; // cos(外圓錐角) 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) { // 計算L,但保留r和r^2,供以後使用 const auto delta = position - pos; // 距離向量 const auto rr = SquareMagnitude(delta); const auto r = sqrtf(rr); // 算出光源到pos的距離 const auto L = delta / r; // 距離單位向量 /* * 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); // 在r之內的相交點纔會遮蔽光源 // shadowResult.distance <= r 表示: // 以pos交點 -> 光源位置 發出一條陰影測試光線 // 若是陰影測試光線與其餘物體有交點,那麼相交距離 <= r // 說明pos位置沒法直接看到光源 if (shadowResult.body && shadowResult.distance <= r) return zero; } // 平方反比衰減 const auto attenuation = 1 / rr; // 返回衰減後的光源顏色 return LightSample(L, intensity * (attenuation * spot)); }
聚光燈是很是複雜的數學模型,咱們不去探究爲何公式這樣的,只要實現就行。
純數學計算很少講,這裏主要有一個spot(聚光燈係數),因此最後的顏色是intensity * (attenuation * spot)。其它跟點光源的實現也差很少。
原想這東西怎麼實現啊,如今想通了,就是在某點處(plane上一點)三個聚光燈打上去,將最終的顏色混合起來(加起來)。
簡單表述:三個光源的光分別爲RGB(255,0,0)、RGB(0,255,0)、RGB(0,0,255),混合起來,加一下就是RGB(255,255,255),白色。
看到用JavaScript玩轉計算機圖形學(二)基本光源 - Milo Yip - 博客園 中的一個問題:
若是,幅射強度是負值的話,會怎麼樣?(雖然未證明反光子(antiphoton)的存在,但讀者能想到圖形學上的功能麼?)
感受就是PS中的正片疊底啊,見如何簡單的理解正片疊底和濾色?。
接下來會探討畫光的實現。