参考自:用JavaScript玩转计算机图形学(二)基本光源 - Milo Yip - 博客园,主要讲述三种最基本的光源——平行光、点光源、聚光灯,其实就是三种数学模型。

代码的调整

先前的代码中,颜色是由几何物体自身计算得出,因此使用很有限。在Phong材质中,显示的效果已经很不错了,然而Phong材质是要假定有一个光源的。我们的代码需要从以面向物体渲染为面向光源渲染。

新的逻辑:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DLight.cpp

主逻辑 代码:

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;
}
}
}

简单来说,就是求出交点时,做:

  1. 计算各大光源基于该点的光线颜色和光线方向

  2. 计算光线在交点法向量上的投影,作为颜色混合比的依据
  3. 将所有颜色累计起来

下面介绍三种光源

平行光

平行光

平行光的属性:

// 平行光
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是光源方向单位向量。

平行光我们只需要知道光源方向和光源颜色就可以了。非常简单,不用算投影,这是主逻辑的工作。

这里说一下阴影,平行光有阴影,当从交点向光源方向看时,如果中间有障碍物,就返回黑色。

点光源

点光源

// 点光源
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中的正片叠底啊,见如何简单的理解正片叠底和滤色?

接下来会探讨画光的实现。

https://zhuanlan.zhihu.com/p/31015884备份。

简单的图形学(三)——光源的更多相关文章

  1. 简单的图形学(二)——材质与反射

    在上一篇[游戏框架系列]简单的图形学(一)文章中,我们讲述了光线追踪的一个最简单的操作--依每个像素延伸出一条追踪光线,光线打到球上(产生交点),就算出这条线的长度,作为最终的灰度,打不到球上,就显示 ...

  2. iOS开发UI篇—Quartz2D简单使用(三)

    iOS开发UI篇—Quartz2D简单使用(三) 一.通过slider控制圆的缩放 1.实现过程 新建一个项目,新建一个继承自UIview的类,并和storyboard中自定义的view进行关联. 界 ...

  3. XML系列之--对电文格式XML的简单操作(三)

    前两章介绍了关于Linq创建.解析SOAP格式的XML,在实际运用中,可能会对xml进行一些其它的操作,比如基础的增删该查,而操作对象首先需要获取对象,针对于DOM操作来说,Linq确实方便了不少,如 ...

  4. python练习题-简单方法判断三个数能否组成三角形

    python简单方法判断三个数能否组成三角形 #encoding=utf-8 import math while True: str=raw_input("please input thre ...

  5. html css的简单学习(三)

    html css的简单学习(三) 前端开发工具:Dreamweaver.Hbuilder.WebStorm.Sublime.PhpStorm...=========================== ...

  6. 【OGG】OGG简单配置双向复制(三)

    [OGG]OGG简单配置双向复制(三) 一.1  BLOG文档结构图 一.2  前言部分 一.2.1  导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O ...

  7. UWP简单示例(三):快速开发2D游戏引擎

    准备 IDE:Visual Studio 图形 API:Win2D MSDN 教程:UWP游戏开发 游戏开发涉及哪些技术? 游戏开发是一门复杂的艺术,编码方面你需要考虑图形.输入和网络 以及相对独立的 ...

  8. Android--Retrofit+RxJava的简单封装(三)

    1,继续接着上一篇的讲讲,话说如果像上一篇这样的话,那么我们每一次请求一个结构都要创建一堆的Retrofit对象,而且代码都是相同的,我们可以试试封装一下 先创建一个HttpMethods类,将Ret ...

  9. SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 SignalR 简单示例 通过三个DEMO学会SignalR的三种实现方式 SignalR推送框架两个项目永久连接通讯使用 SignalR 集线器简单实例2 用SignalR创建实时永久长连接异步网络应用程序

    SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论   异常汇总:http://www ...

随机推荐

  1. 算法笔记_122:蓝桥杯第七届省赛(Java语言A组)试题解答

     目录 1 煤球数目 2 生日蜡烛 3 搭积木 4 分小组 5 抽签 6 寒假作业 7 剪邮票 8 取球博弈 9 交换瓶子 10 压缩变换   前言:以下试题解答代码部分仅供参考,若有不当之处,还请路 ...

  2. $ionicModal

    Ionic中[弹出式窗口]有两种(如下图所示),$ionicModal和$ionicPopup; $ionicModal是完整的页面: $ionicPopup是(Dialog)对话框样式的,直接用Ja ...

  3. How to set up OpenERP for various timezone kindly follow the following steps to select timezone in OpenERP

        How to set up OpenERP for different Time Zones Click on the "Edit Preferences" wheel a ...

  4. oracle tnsnames.ora文件用法说明

      oracle tnsnames.ora文件用法说明 CreationTime--2018年8月10日08点32分 Author:Marydon 1.用途 oracle客户端所需要的一个文件,通过该 ...

  5. 使用Block在两个界面之间传值

    首先,创建两个视图控制器,在第一个视图控制器中创建一个UILabel和一个UIButton,其中UILabel是为了显示第二个视图控制器传过来的字符串,UIButton是为了push到第二个界面. 第 ...

  6. oracle中的一些基本概念

    Oracle数据库的物理文件是存储在磁盘上的数据文件.控制文件和日志文件的总称.数据文件和日志文件是数据库中最重要的文件.数据库由若干个表空间组成,表空间由表组成,表由段组成,段由区间组成,区间由数据 ...

  7. redis 学习札记4-sortset

    redis 学习笔记4--sortset redis学习笔记3--sortSet 终于到最后一个数据结构了,加油!! 整体结构图: http://dl.iteye.com/upload/picture ...

  8. Unix环境高级编程(十六)进程间通信

    进程间通信(IPC)是指能在两个进程间进行数据交换的机制.现代OS都对进程有保护机制,因此两个进程不能直接交换数据,必须通过一定机制来完成. IPC的机制的作用: (1)一个软件也能更容易跟第三方软件 ...

  9. 使用 C# 开发智能手机软件:推箱子(十四)

    这是"使用 C# 开发智能手机软件:推箱子"系列文章的第十四篇.在这篇文章中,介绍 Window/ErrorMsgDlg.cs 源程序文件.这个源程序文件包括 ErrorMsgDl ...

  10. Python ljust() 方法

    描述 ljust() 方法返回一个原字符串左对齐,并使用指定字符填充至指定长度的新字符串,默认的填充字符为空格.如果指定的长度小于原字符串的长度则返回原字符串. 语法 ljust() 方法语法: S. ...