写在前面

源码:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DScene5.cpp

本文主要内容:

  1. 三角形的渲染

  2. 聚光效果的实现
  3. 色散的简易版实现

三角形的渲染

在上一文中,主要介绍了矩形的渲染,其实三角形也跟它差不多,无非就是判断线与线的关系罢了。

三角形的数据结构:

// 三角形
class Geo2DTriangle : public Geo2DShape
{
public:
Geo2DTriangle(vector2 p1, vector2 p2, vector2 p3, color L, color R, float eta, color S);
~Geo2DTriangle() = default; Geo2DResult sample(vector2 ori, vector2 dir) const override; vector2 get_center() const override; vector2 center, p1, p2, p3;
vector2 n[3];
};

要注意的地方:

  1. 三角形的中心点(重心)要计算出来

  2. 缓存三角形三条边的法线
  3. 确保三个点p1~p3的按顺时针方向排列的

顶点排序与法线设置

// 假定三顶点是顺时针方向
const auto p12 = p2 - p1;
const auto p13 = p3 - p1;
if (p12.x * p13.y - p12.y * p13.x < 0) // 确保点1、2、3是顺时针
{
const auto tmp = p2;
p2 = p3;
p3 = tmp;
}
n[0] = p2 - p1;
n[0] = Normalize(vector2(n[0].y, -n[0].x));
n[1] = p3 - p2;
n[1] = Normalize(vector2(n[1].y, -n[1].x));
n[2] = p1 - p3;
n[2] = Normalize(vector2(n[2].y, -n[2].x));

怎样知道三个点是顺时针排列的呢?本质上是求一个点在另外两个点形成的线段的哪一侧。

求点P在直线L的左侧还是右侧?可以用叉乘法,我们只要知道叉乘结果的符号就可以了。

三角形的采样方法类似于矩形的:

Geo2DResult Geo2DTriangle::sample(vector2 ori, vector2 dir) const
{
const vector2 pts[3] = { p1,p2,p3 }; static int m[3][2] = { { 0,1 },{ 1,2 },{ 2,0 } };
float t[2];
vector2 p[2];
int ids[2];
int cnt = 0;
for (int i = 0; i < 3 && cnt < 2; i++)
{
if (IntersectWithLineAB(ori, dir, pts[m[i][0]], pts[m[i][1]], t[cnt], p[cnt]))
{
ids[cnt++] = i;
}
}
if (cnt == 2)
{
const auto td = ((t[0] >= 0 ? 1 : 0) << 1) | (t[1] >= 0 ? 1 : 0);
switch (td)
{
case 0: // 双反,无交点,在外
break;
case 1: // t[1],有交点,在内
return Geo2DResult(this, true,
Geo2DPoint(t[0], p[0], n[ids[0]]),
Geo2DPoint(t[1], p[1], n[ids[1]]));
case 2: // t[0],有交点,在内
return Geo2DResult(this, true,
Geo2DPoint(t[1], p[1], n[ids[1]]),
Geo2DPoint(t[0], p[0], n[ids[0]]));
case 3: // 双正,有交点,在外
if (t[0] > t[1])
{
return Geo2DResult(this, false,
Geo2DPoint(t[1], p[1], n[ids[1]]),
Geo2DPoint(t[0], p[0], n[ids[0]]));
}
else
{
return Geo2DResult(this, false,
Geo2DPoint(t[0], p[0], n[ids[0]]),
Geo2DPoint(t[1], p[1], n[ids[1]]));
}
default:
break;
}
}
return Geo2DResult();
}

聚光效果

要做一个色散就要一束平行的光,实现很简单,限制角度!

我们在圆的采样方法中,做一个判断:当光线来的角度不在聚光灯有效范围内时,就返回黑色。

Geo2DResult Geo2DCircle::sample(vector2 ori, vector2 dir) const
{
auto v = ori - center;
auto a0 = SquareMagnitude(v) - rsq;
auto DdotV = DotProduct(dir, v); //if (DdotV <= 0)
{
auto discr = (DdotV * DdotV) - a0; // 平方根中的算式 if (discr >= 0)
{
// 非负则方程有解,相交成立
// r(t) = o + t.d
auto distance = -DdotV - sqrtf(discr); // 得出t,即摄影机发出的光线到其与圆的交点距离
auto distance2 = -DdotV + sqrtf(discr);
auto position = ori + dir * distance; // 代入直线方程,得出交点位置
auto position2 = ori + dir * distance2;
auto normal = Normalize(position - center); // 法向量 = 光线终点(球面交点) - 球心坐标
auto normal2 = Normalize(position2 - center);
if (a0 > 0 && angle && !(A1.x * dir.y < A1.y * dir.x && A2.x * dir.y > A2.y * dir.x))
{ // 判断三条线之间的时针顺序
return Geo2DResult();
}
return Geo2DResult((a0 <= 0 || distance >= 0) ? this : nullptr, a0 <= 0,
Geo2DPoint(distance, position, normal),
Geo2DPoint(distance2, position2, normal2));
}
} return Geo2DResult(); // 失败,不相交
}

色散效果

色散其实就是不同频率的光在介质内的折射率不同,我们就简化一下,按照RGB修改折射率,如:红光=原折射率,绿光=原折射率+0.1,等。

对于没有明确修改折射率(默认为1.0)的图形,不对它做色散检查。

if (r.body->eta == 1.0f) // 不折射
{
// 按照先前的折射方法,不变!
}
else // 色散测试
{
const auto eta = r.inside ? r.body->eta : (1.0f / r.body->eta);
const auto k = 1.0f - eta * eta * (1.0f - idotn * idotn);
if (k >= 0.0f) // 可以折射,不是全反射
{
const auto a = eta * idotn + sqrtf(k);
const auto refraction = eta * d - a * normal;
const auto cosi = -(DotProduct(d, normal));
const auto cost = -(DotProduct(refraction, normal));
refl = refl * (r.inside ? fresnel(cosi, cost, eta, 1.0f) : fresnel(cosi, cost, 1.0f, eta));
refl.Normalize();
//下面不一样了
color par;//求三个维度的分量和
sum.Set(0.0f);//光源的光就不纳入计算
par.Add(trace5(pos - BIAS * normal, refraction, depth + 1));//加上红光的分量
auto n = par.Valid() ? 1 : 0;
par.g *= ETAS;//ETAS=0.1 对红光分量而言,绿和蓝分量就削减它
par.b *= ETAS;
for (int i = 1; i < 3; ++i)//求蓝光和绿光分量
{
//ETAD=0.1 折射率:绿=红+0.1 蓝=红+0.2
const auto eta0 = r.inside ? (r.body->eta + ETAD * i) : (1.0f / (r.body->eta + ETAD * i));
const auto k0 = 1.0f - eta0 * eta0 * (1.0f - idotn * idotn);
if (k >= 0.0f) // 可以折射,不是全反射
{
const auto a0 = eta0 * idotn + sqrtf(k0);
const auto refraction0 = eta0 * d - a0 * normal;
auto c = trace5(pos - BIAS * normal, refraction0, depth + 1);//做折射计算
if (c.Valid())
{
if (i == 1)
{
c.r *= ETAS;//削减其他两个颜色分量
c.b *= ETAS;
}
else
{
c.r *= ETAS;
c.g *= ETAS;
}
n++;//如果这一分量不为黑色,就有效,加一,原本要加最终值做下平均的,现在暂不用它
}
par.Add(c);// 加上蓝和绿分量
}
}
sum.Add((refl.Negative(1.0f)) * par);//再加上三个折射分量的和
}
else // 不折射则为全内反射
refl.Set(1.0f);

局部扫描

当光源很亮(RGB>10f)时,仅256的采样还不能有很好的效果,用下面的方法:

static color sample5(float x, float y) {
color sum;
for (auto i = 0; i < N; i++) {
const auto a = PI2 * (i + float(rand()) / RAND_MAX) / N;
const auto c = trace5(vector2(x, y), vector2(cosf(a), sinf(a)));
if (c.Valid())
{
color par;
for (auto j = 0; j < NP; j++) {//进一步计算
const auto a0 = PI2 * (i + (j + float(rand()) / RAND_MAX) / NP) / N;
const auto c0 = trace5(vector2(x, y), vector2(cosf(a0), sinf(a0)));
par.Add(c0);
}
sum.Add(par * (1.0f / NP));
}
}
return sum * (1.0f / N);
}

当第一层抖动采样结果有效时,做第二层抖动采样,精度更高。


最终结果1080P,一层采样数=512,二层采样数=8,双核四线程渲染用时差不多半小时。

进一步更真实的话,我只想到再增加一些折射测试,将原本的RGB分量扩展为七彩色,转换用RGB跟HSL的,其中的问题就是七彩色各分量并不正交,如何将它们整合起来还待研究。

题图的设定为RGB分量的折射率递增为0.1,也就是说1.4~1.6,颜色削减为0.1。另外,光源的光也不是严格的平行光,更优的效果还需要不断调整参数。

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

用C++画光(三)——色散的更多相关文章

  1. 用C++画光(一)——优化

    写在前面 在先前的画光系列中,实现实体几何.反射.折射等效果,但是最大的一个缺陷是复杂度太高.当采样是1024时,渲染时间直线上升(用4线程),以至好几个小时才能完成一副作品,实现太慢.然而,当我看到 ...

  2. MATLAB 画出三个通信小区cell边界示意图

    d=1000; %两个小区中心间距离的一半 rcell=2*d/sqrt(3); %小区半径 ncell=3; %小区个数 cellposition=zeros(ncell,2); %初始化小区中心位 ...

  3. 用C++画光(二)——矩形

    在上篇文章的基础上,做了许多调整,修复了许多BUG.在解决bug的过程中,我逐渐领悟到一个要领:枯燥地一步步调试太痛苦了,找不到问题的根源!所以我选择将中间结果打到图片上.如: (注意,里面的点是我随 ...

  4. 简单的图形学(三)——光源

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

  5. [js高手之路] html5 canvas系列教程 - arcTo(弧度与二次,三次贝塞尔曲线以及在线工具)

    之前,我写了一个arc函数的用法:[js高手之路] html5 canvas系列教程 - arc绘制曲线图形(曲线,弧线,圆形). arcTo: cxt.arcTo( cx, cy, x2, y2, ...

  6. 对TCP三次握手四次分手还不清楚的速度进,超简单解析,明白了就很好记!

    关于TCP三次握手四次分手,之前看资料解释的都很笼统,很多地方都不是很明白,所以很难记,前几天看的一个博客豁然开朗,可惜现在找不到了.现在把之前的疑惑总结起来,方便一下大家. 先上个TCP三次握手和四 ...

  7. c提高第三次作业

    1. char buf[] = "abcdef"; //下面有啥区别? const char *p = buf; //p指向的内存不能变 char const *p = buf; ...

  8. TCP三次握手与四次分手超简单解析

    关于TCP三次握手四次分手,之前看资料解释的都很笼统,很多地方都不是很明白,所以很难记,前几天看的一个博客豁然开朗,可惜现在找不到了.现在把之前的疑惑总结起来,方便一下大家. 先上个TCP三次握手和四 ...

  9. 对TCP三次握手四次分手还不清楚,超简单解析

      关于TCP三次握手四次分手,之前看资料解释的都很笼统,很多地方都不是很明白,所以很难记,前几天看的一个博客豁然开朗,可惜现在找不到了.现在把之前的疑惑总结起来,方便一下大家. 先上个TCP三次握手 ...

随机推荐

  1. vb sendmessage 详解1

    SendMessage函数的常用消息及其应用(有点长,希望能对大家有所帮助)函数原型: Declare Function SendMessage Lib "user32" Alia ...

  2. vue 钩子函数 使用async await

    示例: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <tit ...

  3. Android4.0-4.4 加入实体按键振动支持的方法(java + smali版本号)

    有些手机比方泛泰A820L, 泛泰A890 A900 以及Nubia Z5S 和Z5S mini具有实体按键(这里所说的实体按键是相对于虚拟按键而言, 包括物理按键和触摸屏上多出来的触摸实体按键), ...

  4. opencv实现gamma灰阶检測

    简单介绍 本篇解说使用opencv来測试,表示camera gamma參数的灰阶卡图片指标:YA Block.DynamicRange.Gray Scale. 详细实现 实现代码 #include & ...

  5. [SceneKit] 不会 Unity3D 的另一种选择

    概述 SceneKit和SpriteKit的区别简单的来说就是二维和三维的区别 详细 代码下载:http://www.demodashi.com/demo/10664.html 上周一, 相信很多人和 ...

  6. 使用PhoneGap开发基于Html5应用二:第一个PhoneGap应用:百度

    上一篇博文使用PhoneGap开发基于Html5应用一:PhoneGap简单介绍 中我介绍了怎样从phonegap官网上下载源代码并启动第一个应用,今天我们把phonegap的应用略微改一下,让他实现 ...

  7. [CXF REST标准实战系列] 二、Spring4.0 整合 CXF3.0,实现测试接口(转)

    转自:[CXF REST标准实战系列] 二.Spring4.0 整合 CXF3.0,实现测试接口 文章Points: 1.介绍RESTful架构风格 2.Spring配置CXF 3.三层初设计,实现W ...

  8. Servlet乱码问题

    数据像水流一样从一个地方流向另一个地方. 文本流是特殊的二进制流. 既然提到乱码问题,那就必然是用错误的编码去解释二进制流. 在传输过程中必然都是以二进制流传输的. 所以,我们需要考虑的是: 有几个数 ...

  9. Fn键

    需求分析 我想开机禁用触摸板. 方案设计 安装驱动:比较麻烦,驱动也不一定支持开机禁用触摸板. 编程实现,让一段代码开机禁用触摸板 编程实现也分好几种方法: 使用windows API禁用触摸板,这需 ...

  10. Oracle截取字符串函数和查找字符串函数,连接运算符||

    参考资料:Oracle截取字符串和查找字符串 oracle自定义函数学习和连接运算符(||) oracle 截取字符(substr),检索字符位置(instr) case when then else ...