简单的图形学(二)——材质与反射
在上一篇【游戏框架系列】简单的图形学(一)文章中,我们讲述了光线追踪的一个最简单的操作——依每个像素延伸出一条追踪光线,光线打到球上(产生交点),就算出这条线的长度,作为最终的灰度,打不到球上,就显示为黑色。
本节代码:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/RenderMaterial.cpp
几何图形算法接口:https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Geometries.h。
无论是材质,还是反射,还是折射等,无非就是在这个交点处依不同算法计算出颜色而已,这个颜色最终会显示到屏幕上对应的像素上。下面介绍的就是计算交点处颜色的方法。
参考自miloyip的文章用JavaScript玩转计算机图形学(一)光线追踪入门 - Milo Yip - 博客园。
实现材质
Phong材质效果
这里就实现两种材质:棋盘和Phong,老实说,我都没听说过Phong这个东西,应该又是纯数学推导出来的一种结论吧。
先写好接口:
// 材质接口
class Material
{
public:
Material(float reflectiveness);
virtual ~Material();
virtual color Sample(Ray ray, vector3 position, vector3 normal) = 0; float reflectiveness;
}; // 棋盘材质
class CheckerMaterial : public Material
{
public:
CheckerMaterial(float scale, float reflectiveness); color Sample(Ray ray, vector3 position, vector3 normal) override; float scale;
}; // Phong材质
class PhongMaterial : public Material
{
public:
PhongMaterial(color diffuse, color specular, float shininess, float reflectiveness); color Sample(Ray ray, vector3 position, vector3 normal) override; color diffuse;
color specular;
float shininess;
};
棋盘是在平面上的,所以我们事先要实现一个光线与平面的相交算法。
判断直线与平面相交
IntersectResult Plane::Intersect(Ray ray)
{
const auto a = DotProduct(ray.direction, normal); if (a >= 0)
// 反方向看不到平面,负数代表角度为钝角
// 举例,平面法向量n=(0,1,0),距离d=0,
// 我从上面往下看,光线方向为y轴负向,而平面法向为y轴正向
// 所以两者夹角为钝角,上面的a为cos(夹角)=负数,不满足条件
// 当a为0,即视线与平面平行时,自然看不到平面
// a为正时,视线从平面下方向上看,看到平面的反面,因此也看不到平面
return IntersectResult(); // 参考 http://blog.sina.com.cn/s/blog_8f050d6b0101crwb.html
/* 将直线方程写成参数方程形式,即有:
L(x,y,z) = ray.origin + ray.direction * t(t 就是距离 dist)
将平面方程写成点法式方程形式,即有:
plane.normal . (P(x,y,z) - plane.position) = 0
解得 t = {(plane.position - ray.origin) . normal} / (ray.direction . plane.normal )
*/
const auto b = DotProduct(normal, ray.origin - position); const auto dist = -b / a;
return IntersectResult(this, dist, ray.Eval(dist), normal);
}
纯数学推导较多,这里有个优化:先看光线方向和平面法向量方向是否同向(夹角为锐角),是则直接判定不相交。
确定好算法后,看看棋盘材质的实现:
color CheckerMaterial::Sample(Ray ray, vector3 position, vector3 normal)
{
static color black(Gdiplus::Color::Black);
static color white(Gdiplus::Color::White);
return fabs(int(floorf(position.x * 0.1f) + floorf(position.z * scale)) % 2) < 1 ? black : white;
}
简单来说,就是“x坐标+z坐标”取整是否是2的倍数。
我们主要分析下材质接口需要哪些成分:
- 光线Ray,即入射光线的起点位置和方向、交点position、交点法向normal,光线方向和交点法向量是为了计算反射、折射。
- 交点位置 position
- 交点法向 normal
接下来看看Phong材质,参考里面的网址说明,完全的数学公式。
color PhongMaterial::Sample(Ray ray, vector3 position, vector3 normal)
{
/*
参考 https://www.cnblogs.com/bluebean/p/5299358.html Blinn-Phong模型
Ks:物体对于反射光线的衰减系数
N:表面法向量
H:光入射方向L和视点方向V的中间向量
Shininess:高光系数 Specular = Ks * lightColor * pow(dot(N, H), shininess) 当视点方向和反射光线方向一致时,计算得到的H与N平行,dot(N,H)取得最大;当视点方向V偏离反射方向时,H也偏离N。
简单来说,入射光与视线的差越接近法向量,镜面反射越明显
*/ const auto NdotL = DotProduct(normal, lightDir);
const auto H = Normalize(lightDir - ray.direction);
const auto NdotH = DotProduct(normal, H);
const auto diffuseTerm = diffuse * fmax(NdotL, 0.0f); // N * L 入射光在镜面法向上的投影 = 漫反射
const auto specularTerm = specular * powf(fmax(NdotH, 0.0f), shininess);
return lightColor * (diffuseTerm + specularTerm);
}
计算时需要四个参数:
- Ks:物体对于反射光线的衰减系数
- N:表面法向量
- H:光入射方向L和视点方向V的中间向量
- Shininess:高光系数
我们看到的Phong材质颜色其实是它表面反射出的光,如何计算反射的光什么颜色?
Phong材质有三个参数:diffuse漫反射颜色、specular镜面反射颜色、shininess高光系数(其实就是调节前两者的混合比例)。如题图中所示,diffuse就是球本身材质的颜色,而specular就是外界光打上去的颜色。因此,这里会假设有一个外界光源lightColor/lightDir,故而在上述计算过程中会用到它。
实现反射
反射效果
看到反射,其实就是用递归实现的,同时要限制递归的深度。
color PhysicsEngine::RenderReflectRecursive(World& world, const Ray& ray, int maxReflect)
{
static color black(Gdiplus::Color::Black); auto result = world.Intersect(ray); if (result.body) {
// 参见 https://www.cnblogs.com/bluebean/p/5299358.html // 取得反射系数
const auto reflectiveness = result.body->material->reflectiveness; // 先采样(取物体自身的颜色)
auto color = result.body->material->Sample(ray, result.position, result.normal); // 加上物体自身的颜色成份(与反射的颜色相区分)
color = color * (1.0f - reflectiveness); if (reflectiveness > 0 && maxReflect > 0) { // 公式 R = I - 2 * N * (N . I) ,求出反射光线
const auto r = result.normal * (-2.0f * DotProduct(result.normal, ray.direction)) + ray.direction; // 以反射光线作为新的光线追踪射线
const auto reflectedColor = RenderReflectRecursive(world, Ray(result.position, r), maxReflect - 1); // 加上反射光的成份
color = color + (reflectedColor * reflectiveness);
}
return color;
}
return black;
}
如代码中所示,基本思路是:
- 光线与几何物体有交点吗?
- 没有交点:返回黑色
- 有交点:对自身材质采样,占比为(1-反射系数),计算出反射光线,继续追踪,并将追踪到的采样占比为(反射系数)
即:
color reflect_sample(world, ray, 深度)
{
__ 如果world和ray不相交, 返回 黑色
__ 如果world和ray相交,假设这个相交物体为body
____ 1. 加上 body的材质颜色 * 比例(1.0f - reflectiveness)
____ 2. 计算出反射光线ray_f
____ 3. 加上反射颜色reflect_sample(world, ray_f, 深度-1) * 比例(reflectiveness)
}
这里重点是根据入射光线和法向量求反射光线,这是道数学題,参考过程在代码中的网址中。
到这里,miloyip的光线追踪入门就尝试完成了,接下来是讲述基本光源。
由https://zhuanlan.zhihu.com/p/31012319备份。
简单的图形学(二)——材质与反射的更多相关文章
- 简单的图形学(三)——光源
参考自:用JavaScript玩转计算机图形学(二)基本光源 - Milo Yip - 博客园,主要讲述三种最基本的光源--平行光.点光源.聚光灯,其实就是三种数学模型. 代码的调整 先前的代码中,颜 ...
- Action的三种实现方式,struts.xml配置的详细解释及其简单执行过程(二)
勿以恶小而为之,勿以善小而不为--------------------------刘备 劝诸君,多行善事积福报,莫作恶 上一章简单介绍了Struts2的'两个蝴蝶飞,你好' (一),如果没有看过,请观 ...
- SqlHelper简单实现(通过Expression和反射)1.引言
之前老大说要改变代码中充斥着各种Select的Sql语句字符串的情况,让我尝试着做一个简单的SqlHelper,要具有以下功能: 1.不要在业务代码中暴露DataTable或者DataSet类型: 2 ...
- html css的简单学习(二)
html css的简单学习(二) <!Doctype html>告诉浏览器,这是一个html文档.lang="en" 默认是en,表示英语:zh-Hans 中文简体:z ...
- Win8 Metro(C#)数字图像处理--2.56简单统计法图像二值化
原文:Win8 Metro(C#)数字图像处理--2.56简单统计法图像二值化 [函数名称] 简单统计法图像二值化 WriteableBitmap StatisticalThSegment(Wr ...
- 自己制作一个简单的操作系统二[CherryOS]
自己制作一个简单的操作系统二[CherryOS] 我的上一篇博客 自己制作一个简单的操作系统一[环境搭建], 详细介绍了制作所需的前期准备工作 一. 一点说明 这个操作系统只是第一步, 仅仅是开机显示 ...
- 043-PHP简单获得一个类对应的反射信息
<?php // 简单获得一个类对应的反射信息 class demo{ CONST CON_STR = '123456'; public $str_1; private $str_2; prot ...
- 《仙剑奇侠传柔情版》Java的简单实现(二)
基于<仙剑奇侠传柔情版>Java的简单实现(二) 2018-12-02 by Kris 需要上次的GameFrame.class中窗口框架承载:https://www.cnblogs.co ...
- .Net开发笔记(二十一) 反射在.net中的应用
反射概念在网上到处都有,但是讲到的具体的应用很少,一个重要的原因是现实中真的很少用得到它.引用msdn上对“反射”的解释: "通过 System.Reflection 命名空间中的类以及 S ...
随机推荐
- 解决openssh TimeOut
SSH Client:ServerAliveInterval 100 SSH server:ClientAliveInterval 30TCPKeepAlive yes ClientAliveCoun ...
- 谷歌GSON的字符与对象的互转
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; i ...
- CAS 5.1.x 的搭建和使用(四)—— 配置使用HTTP协议访问的服务端
CAS单点登录系列: CAS 5.1.x 的搭建和使用(一)—— 通过Overlay搭建服务端 CAS5.1.x 的搭建和使用(二)—— 通过Overlay搭建服务端-其它配置说明 CAS5.1.x ...
- docker login harbor出现的报错Error response from daemon: Get https://172.16.1.99/v1/users/: dial tcp 172.16.1.99:443: getsockopt: connection refused解决方法
出现的问题 [root@master01 ~]# docker login 172.16.1.99 Username: admin Password: Error response from daem ...
- servlet下根据相对路径找资源
1.在web项目中如果直接添加一个资源,那么相对路径相对的是tomcat的bin目录. 2.在包中直接指定资源,那么可以使用以下的相对路径直接获取资源: InputStream in = this.g ...
- Android学习系列(16)--App列表之圆角ListView
有些东西看多了,就厌烦了:extjs对我这种感觉最为强烈.甚至,有时觉得设计之殇是审美疲劳.直角看多了,就想看看圆角,不知何时,这几年刮起了一阵阵的圆角设计风:CSS新标准纳入圆角元素,iphone中 ...
- 【Linux】撷取命令grep
什么是撷取命令啊?说穿了,就是将一段数据经过分析后,取出我们所想要的.或者是经由分析关键词,取得我们所想要的那一行! 不过,要注意的是,一般来说,撷取信息通常是针对『一行一行』来分析的, 并不是整篇信 ...
- List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac
List多个字段标识过滤 class Program{ public static void Main(string[] args) { List<T> list = new List& ...
- 搭建Selenium-Grid环境
转帖 在搭建Selenium-Grid之前,首先要搭建每台测试机上的Selenium环境: 安装Java 配置Java环境变量 下载ie.chrome的driver程序[如果要测试其它浏览器还需额外下 ...
- 分析一帧基于UDP的TFTP协议帧
下图是UDP的段格式: 相比TCP段格式,UDP要简单得多,也没啥好说的,需要注意的是UDP数据长度指payload加上首部的长度. 下面分析一帧基于UDP的TFTP协议帧: 以太网首部 0000: ...