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

仓库:bajdcc/GameFramework

本节代码: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的倍数。

我们主要分析下材质接口需要哪些成分:

  1. 光线Ray,即入射光线的起点位置和方向、交点position、交点法向normal,光线方向和交点法向量是为了计算反射、折射。

  2. 交点位置 position
  3. 交点法向 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);
}

计算时需要四个参数:

  1. Ks:物体对于反射光线的衰减系数

  2. N:表面法向量
  3. H:光入射方向L和视点方向V的中间向量
  4. 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. 光线与几何物体有交点吗?

  2. 没有交点:返回黑色
  3. 有交点:对自身材质采样,占比为(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备份。

简单的图形学(二)——材质与反射的更多相关文章

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

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

  2. Action的三种实现方式,struts.xml配置的详细解释及其简单执行过程(二)

    勿以恶小而为之,勿以善小而不为--------------------------刘备 劝诸君,多行善事积福报,莫作恶 上一章简单介绍了Struts2的'两个蝴蝶飞,你好' (一),如果没有看过,请观 ...

  3. SqlHelper简单实现(通过Expression和反射)1.引言

    之前老大说要改变代码中充斥着各种Select的Sql语句字符串的情况,让我尝试着做一个简单的SqlHelper,要具有以下功能: 1.不要在业务代码中暴露DataTable或者DataSet类型: 2 ...

  4. html css的简单学习(二)

    html css的简单学习(二) <!Doctype html>告诉浏览器,这是一个html文档.lang="en" 默认是en,表示英语:zh-Hans 中文简体:z ...

  5. Win8 Metro(C#)数字图像处理--2.56简单统计法图像二值化

    原文:Win8 Metro(C#)数字图像处理--2.56简单统计法图像二值化  [函数名称] 简单统计法图像二值化 WriteableBitmap StatisticalThSegment(Wr ...

  6. 自己制作一个简单的操作系统二[CherryOS]

    自己制作一个简单的操作系统二[CherryOS] 我的上一篇博客 自己制作一个简单的操作系统一[环境搭建], 详细介绍了制作所需的前期准备工作 一. 一点说明 这个操作系统只是第一步, 仅仅是开机显示 ...

  7. 043-PHP简单获得一个类对应的反射信息

    <?php // 简单获得一个类对应的反射信息 class demo{ CONST CON_STR = '123456'; public $str_1; private $str_2; prot ...

  8. 《仙剑奇侠传柔情版》Java的简单实现(二)

    基于<仙剑奇侠传柔情版>Java的简单实现(二) 2018-12-02 by Kris 需要上次的GameFrame.class中窗口框架承载:https://www.cnblogs.co ...

  9. .Net开发笔记(二十一) 反射在.net中的应用

    反射概念在网上到处都有,但是讲到的具体的应用很少,一个重要的原因是现实中真的很少用得到它.引用msdn上对“反射”的解释: "通过 System.Reflection 命名空间中的类以及 S ...

随机推荐

  1. linux下查看线程数的几种方法

    1. cat /proc/${pid}/status 2.pstree -p ${pid} 3.top -p ${pid} 再按H   或者直接输入 top -bH -d 3 -p  ${pid} t ...

  2. 通过jaxws-ri创建webservice服务端和客户端

    1. 获得开发包 当然是到 SUN 的开发网站下载 JAX-WS RI,或者下载我的网盘备份 ,下载下来的只是一个jar包,参考官网上的方法在命令行调用:java -jar JAXWS2.1.2-20 ...

  3. 【Oracle】(savepoint)保存点的使用

    作用 保存点可以回退到事务的一部分,我们在操作数据库的过程中可以对事务分隔为几个部分,在操作失误的时候就可以回滚到某个点即可. 实现步骤 我们现在新建一张表TMP003 )); 第一步:插入第一条记录 ...

  4. 【重要】新浪微博api研究

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #新浪微博api研究 ''' 3.SDK的使用规则: 1)使用微博API,需要通过用户的授权,获取用户的授权码 ...

  5. 微信小程序条码、二维码生成模块

    代码地址如下:http://www.demodashi.com/demo/13994.html 一.前期准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.co ...

  6. PHP进行安全字段和防止XSS跨站脚本攻击过滤(通用版)

    废话不多说,直接贴使用方法和代码: 使用方式:1)写在公共方法里面,随时调用即可.2)写入类文件,使用是include_once 即可 代码: /* 进行安全字段和xss跨站脚本攻击过滤(通用版) - ...

  7. 有用的git片段

    世界上知识那么多,又岂是人力所能穷尽,于是术业有专攻.对于git,有用的命令片段其实非常少,而命令却是非常多.于是,掌握git常见的用法就足够了.不要在语句级别上记忆git命令,在代码片段级别上记忆g ...

  8. 【LeetCode】124. Binary Tree Maximum Path Sum

    Binary Tree Maximum Path Sum Given a binary tree, find the maximum path sum. The path may start and ...

  9. Fusion Tables 图层用于呈现 Google Fusion Tables 中包含的数据

    Google Maps API 允许您使用 FusionTablesLayer 对象将 Google Fusion Tables 中包含的数据呈现为地图上的图层.Google Fusion Table ...

  10. 【js】正则表达式(II)

    JavaScript中提供了一个名为RegExp的对象来完成有关正则表达式的操作和功能,每一条正则表达式模式对应一个RegExp对象实例. 在JavaScript中,有两种方式可以创建RegExp对象 ...