用C++画光(一)——优化
写在前面
在先前的画光系列中,实现实体几何、反射、折射等效果,但是最大的一个缺陷是复杂度太高。当采样是1024时,渲染时间直线上升(用4线程),以至好几个小时才能完成一副作品,实现太慢。然而,当我看到用C++画光(一)这篇文章时,我有了一些思路。
我想到了【游戏框架系列】简单的图形学(一)系列文章中的思路,对啊,何必用SDF去慢慢逼近呢?用现成的解析几何算法去做不是更快吗?
过了一番摸索,终于有了题图。
要注意的地方
检测圆与直线相交的算法,我从用JavaScript玩转计算机图形学(一)光线追踪入门 - Milo Yip - 博客园抄来:
intersect : function(ray) {
var v = ray.origin.subtract(this.center);
var a0 = v.sqrLength() - this.sqrRadius;
var DdotV = ray.direction.dot(v);
if (DdotV <= 0) {
var discr = DdotV * DdotV - a0;
if (discr >= 0) {
var result = new IntersectResult();
result.geometry = this;
result.distance = -DdotV - Math.sqrt(discr);
result.position = ray.getPoint(result.distance);
result.normal = result.position.subtract(this.center).normalize();
return result;
}
}
return IntersectResult.noHit;
}
但这里有所不同:3D中的光线追踪是有视角的,也就是说,光线来自同一个点,即摄像机的位置,因此,光线的起点不会在几何图形内部!!而2D中,我们的渲染方式有所不同,光线来自2D区域中的每一个点上,因此,光线起点可能在图形内部。
所以修改后的代码是这样:
Geo2DResult Geo2DCircle::sample(vector2 ori, vector2 dir) const
{
// 圆上点x满足: || 点x - 圆心center || = 圆半径radius
// 光线方程 r(t) = o + t.d (t>=0)
// 代入得 || o + t.d - c || = r
// 令 v = o - c,则 || v + t.d || = r // 化简求 t = - d.v - sqrt( (d.v)^2 + (v^2 - r^2) ) (求最近点) // 令 v = origin - center
auto v = ori - center; // a0 = (v^2 - r^2)
auto a0 = SquareMagnitude(v) - rsq; // DdotV = d.v
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 normal = Normalize(position - center); // 法向量 = 光线终点(球面交点) - 球心坐标
if (a0 <= 0 || distance >= 0)// 这里不一样!!
return Geo2DResult(this, a0 <= 0, distance, distance2, position, normal);
}
} return Geo2DResult(); // 失败,不相交
}
相交算法实质上是用参数方程代入求解,得出参数t,其实就是距离。有一种情况是无效的,需要注意,就是当距离为负且光线起点不在图形内部时,这样一种解是无效的,因为我们的线是射线。
为什么渲染速度变快了
原来的方法是SDF,即不断迭代,最终逼近相交点。这种方法的问题就是迭代的次数太多,每次迭代都进行了同样的计算。
优化之后,计算相交点,我们直接用解析法,一个方程就能搞定,更棒的是,我们可以求出相交的最近点和最远点(这很重要!)。
方法的不同影响了渲染时间的多少。
怎样安排代码
我们先来看顶层调用(https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DScene2.cpp#L72):
root = Geo2DFactory:: or (
Geo2DFactory:: and (
Geo2DFactory::new_circle(1.3f, 0.5f, 0.4f, color(2.0f, 1.0f, 1.0f)),
Geo2DFactory::new_circle(1.7f, 0.5f, 0.4f, color(2.0f, 1.0f, 1.0f))),
Geo2DFactory:: sub (
Geo2DFactory::new_circle(0.5f, 0.5f, 0.4f, color(1.0f, 1.0f, 2.0f)),
Geo2DFactory::new_circle(0.9f, 0.5f, 0.4f, color(1.0f, 1.0f, 2.0f))));
结果就是题图,代码定义了两个图形,一个是两圆相交and,一个是两圆sub,两个图形用or串联起来。
当然,后面还可以用重载让代码更简洁。
直线与圆相交算法在https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Geometries2D.cpp#L163中,上面已贴过。
如何实现两个图形的交、并、差?
这里才是本文重点,而实现这功能用了很多时间。
并:
if (op == t_union)
{
const auto r1 = obj1->sample(ori, dst);
const auto r2 = obj2->sample(ori, dst);
return r1.distance < r2.distance ? r1 : r2;
}
很好解释,直线扫到两个图形上,如果没交点,那么distance就是无穷大,如果有交点,就取距离较近的图形。
交:
if (op == t_intersect)
{
const auto r1 = obj1->sample(ori, dst);
if (r1.body)
{
const auto r2 = obj2->sample(ori, dst);
if (r2.body)
{
const auto rd = ((r1.inside ? 1 : 0) << 1) | (r2.inside ? 1 : 0);
switch (rd)
{
case 0: // not(A or B)
if (r1.distance < r2.distance)
{
if (r2.distance2 > r1.distance && r2.distance > r1.distance2)
break;
return r2;
}
if (r2.distance < r1.distance)
{
if (r1.distance2 > r2.distance && r1.distance > r2.distance2)
break;
return r1;
}
break;
case 1: // B
if (r1.distance < r2.distance2)
return r1;
break;
case 2: // A
if (r2.distance < r1.distance2)
return r2;
break;
case 3: // A and B
return r1.distance > r2.distance ? r1 : r2;
default:
break;
}
}
}
}
代码中distance是最近交点(较小根),distance2是最远交点(较大根)。
交就复杂得多,首先,光线必须与两个图形都有交点,其次,分四种情况(我喜欢这样写两个bool的分类讨论。。),讨论光线起点与两个图形的位置关系。
第一,讨论光线起点不在两圆中。
交集的情况
分六种情况(C{2,4}=6),其中两种情况为不相交,剩下四种情况为相交,代码中就是这个思路。
第二,讨论光线起点在B中,显而易见,交点就是A的边界
第三,讨论光线起点在A中,显而易见,交点就是B的边界
第四,讨论光线起点在A交B中,这里必相交
差:
if (op == t_subtract)
{
const auto r1 = obj1->sample(ori, dst);
const auto r2 = obj2->sample(ori, dst);
const auto rd = ((r1.body ? 1 : 0) << 1) | (r2.body ? 1 : 0);
switch (rd)
{
case 0: // not(A or B)
break;
case 1: // B
break;
case 2: // A
return r1;
case 3: // A and B
if (r2.inside)
{
if (r1.distance2 > r2.distance2)
{
auto r(r2);
r.body = r1.body;
r.inside = false;
r.distance = r.distance2;
return r;
}
break;
}
if (r1.inside)
{
return r1;
}
if (r2.distance < r1.distance)
{
if (r1.distance2 < r2.distance2)
{
break;
}
auto r(r2);
r.body = r1.body;
r.inside = false;
r.distance = r.distance2;
return r;
}
return r1;
default:
break;
}
}
差的实现也不简单,讨论射线与两圆的相交情况:
第一:射线与两圆都不相交,那射线与A-B也不相交
第二:射线与B相交,与A不相交,那射线与A-B也不相交
第三:射线与A相交,与B不相交,射线必定与A-B相交
第四:射线与A交B相交,这时情况复杂了
差集的情况
只考虑两种不相交的情况,如图。反映在代码中就是两个break。
小结
渲染的结果有所不同,发光图形本身的颜色是白色的,这是因为定义时的颜色是RGB(2.0f,1.0f,1.0f),最终采样经平均后呈现时还是RGB(2.0f,1.0f,1.0f),做了一个截断之后就是RGB(1.0f,1.0f,1.0f)即白色。
本文重点即两圆之间的交集、差集的解析法实现,还有直线与圆的相交算法。
程序下载:bajdcc/GameFramework
由https://zhuanlan.zhihu.com/p/32150887备份。
用C++画光(一)——优化的更多相关文章
- 用C++画光(三)——色散
写在前面 源码:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Render2DScene5 ...
- 用C++画光(二)——矩形
在上篇文章的基础上,做了许多调整,修复了许多BUG.在解决bug的过程中,我逐渐领悟到一个要领:枯燥地一步步调试太痛苦了,找不到问题的根源!所以我选择将中间结果打到图片上.如: (注意,里面的点是我随 ...
- 实战UITableview深度优化
演示项目下载地址:https://github.com/YYProgrammer/YYTableViewDemo 项目里的低性能版是常规写法实现的tableview,高性能版是做了相关优化后的tabl ...
- [Deep Learning] 常用的Active functions & Optimizers
深度学习的基本原理是基于人工神经网络,输入信号经过非线性的active function,传入到下一层神经元:再经过下一层神经元的activate,继续往下传递,如此循环往复,直到输出层.正是因为这些 ...
- Signed Distance Field Shadow in Unity
0x00 前言 最近读到了一个今年GDC上很棒的分享,是Sebastian Aaltonen带来的利用Ray-tracing实现一些有趣的效果的分享. 其中有一段他介绍到了对Signed Distan ...
- UnicodeMath数学公式编码_翻译(Unicode Nearly Plain - Text Encoding of Mathematics Version 3)
目录 完整目录 1. 简介 2. 编码简单数学表达式 2.1 分数 2.2 上标和下标 2.3 空白(空格)字符使用 3. 编码其他数学表达式 3.1 分隔符 强烈推荐本文简明版UnicodeMath ...
- 简单的图形学(三)——光源
参考自:用JavaScript玩转计算机图形学(二)基本光源 - Milo Yip - 博客园,主要讲述三种最基本的光源--平行光.点光源.聚光灯,其实就是三种数学模型. 代码的调整 先前的代码中,颜 ...
- 纪中OJ 2019.01.25【NOIP提高组】模拟 B 组 T2 数字对
声明 数字对 Time Limits: 2000 ms Memory Limits: 262144 KB Description 小 H 是个善于思考的学生,现在她又在思考一个有关序列的问题. ...
- JavaScript绘图类 (DIV绘图)
主要的图形算法抄自一个叫w_jsGraphics.js的类库,第一次看到那个库的时候,感觉那是十分神奇的存在.不过估计现在那个库早就已经找不到了. 这是很早之前的一个DIV绘图类,那时候VML+SVG ...
随机推荐
- taro 引用相对路径图片
直接将相对路径放在src属性中,不起作用, 需要先import进来,最好把图片放到服务器上,然后直接写http路径 错误写法: <Image src="./images/front.p ...
- margin和padding的学习
你在学习margin和padding的时候是不是懵了--什么他娘的内边距,什么他娘的外边距.呵呵呵,刚開始我也有点不理解,后来通过查资料学习总算弄明确了,如今我来谈一下自己对margin和paddin ...
- Ubuntu中iptables的使用
(一) 设置开机启动iptables# sysv-rc-conf --level 2345 iptables on (二) iptables的基本命令 1. 列出当前iptables的策略和规则# i ...
- Unable to read TLD "META-INF/c.tld" from JAR file
Unable to read TLD "META-INF/c.tld" from JAR file CreationTime--2018年7月18日17点46分 Author: ...
- 30、java中递归算法
1.已知有一个数列f(0)=1,f(1)=4,f(n+2)=2*f(n+1)+f(n),其中n是大于0的正数,求f(10)的值. 分析:设x=n+2 => f(x)=2*f(n-1)+f(n-2 ...
- 将Memcached作为服务自动启动
1.最简单的做法 通常:启动Memcache的服务器端的命令为: /usr/local/bin/memcached -d -m 256 -u root -l 127.0.0.1 -p 12000 -c ...
- skimage exposure模块解读
exposure模块包括: 直方图均衡化 gamma调整.sigmoid调整.log调整 判断图像是否对比度太低 exposure模块包括以下函数: histogram 统计颜色的直方图,基于nump ...
- eclipse代码格式化设置
http://www.cnblogs.com/zhxiaomiao/archive/2010/06/19/1760995.html java---code style ---formatter 首先新 ...
- pdb文件 PDB文件:每个开发人员都必须知道的 .NET PDB文件到底是什么?
pdb文件包含了编译后程序指向源代码的位置信息,用于调试的时候定位到源代码,主要是用来方便调试的. 在程序发布为release模式时,建议将 pdb文件删除, 同时,对外发布的时候,也把 pdb删除, ...
- Android vcard使用示例,生成vcf文件
Android vcard使用示例,生成vcf文件 我们备份手机联系人时,导出到SD卡时,会在SD卡中生成一个vcf文件,用于保存联系人姓名,手机号码. vCard 规范容许公开交换个人数据交换 ( ...