参考自:用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. java 验证码生成

    import java.awt.Color; import java.awt.Font; import java.io.IOException; import java.io.OutputStream ...

  2. 【实践】源代码分析工具Doxygen+Graphviz

    拿到一个新项目或者一个开源框架,看源代码的方式不是先进行单步调试!最好的方式是先画出整个项目或者开源框架的类.协作.已经方法调用图,能够帮助你更快的理解框架或者项目的设计. 打包下载地址:http:/ ...

  3. POJ 1579 Function Run Fun 记忆化递归

    典型的记忆化递归问题. 这类问题的记忆主要是利用数组记忆.那么已经计算过的值就能够直接返回.不须要进一步递归了. 注意:下标越界.递归顺序不能错,及时推断是否已经计算过值了,不要多递归. 或者直接使用 ...

  4. 开源大数据技术专场(下午):Databircks、Intel、阿里、梨视频的技术实践

    摘要: 本论坛第一次聚集阿里Hadoop.Spark.Hbase.Jtorm各领域的技术专家,讲述Hadoop生态的过去现在未来及阿里在Hadoop大生态领域的实践与探索. 开源大数据技术专场下午场在 ...

  5. @NotEmpty、@NotNull 和 @NotBlank 的区别

    1. 三者主要区别如下: @NotEmpty :用于集合类,不能为null,且size>0 @NotNull:不能为null,但可以为empty,没有size的约束 @NotBlank:只用于S ...

  6. How to forcefully delete a daemonset or a pod in kubernetes cluster

    I have setup a kubernetes cluster which is working fine. I created deployment with type as daemonset ...

  7. Android中asset文件夹和raw文件夹区别(转载)

    原文地址:http://www.cnblogs.com/leizhenzi/archive/2011/10/18/2216428.html *res/raw和assets的相同点: 1.两者目录下的文 ...

  8. 转:void *

    void的含义 void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型. void指针使用规范①void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值.例 ...

  9. AP_应付模组在月结的处理

    2014-06-04 Created By BaoXinjian 1. 完成所有交易及检查Interface      (1). Invoice and Credits      (2). Prepa ...

  10. Python isspace() 方法

    描述 Python isspace() 方法检测字符串是否只由空格组成. 语法 isspace() 方法语法: S.isspace() 参数 无. 返回值 如果字符串中至少有一个字符,并且所有字符都是 ...