[MetalKit]32-Shadows-in-Metal-part-2阴影2
本系列文章是对 metalkit.org 上面MetalKit内容的全面翻译和学习.
MetalKit系统文章目录
在本系列的第二部分中,我们将学习soft shadows软阴影.我们将使用在Raymarching in Metal
中的playground,因为它已经建立了3D
物体.让我们建立一个基本场景,包含一个球体,一个平面,一个灯光和一个射线:
struct Ray {float3 origin;float3 direction;Ray(float3 o, float3 d) {origin = o;direction = d;}
};struct Sphere {float3 center;float radius;Sphere(float3 c, float r) {center = c;radius = r;}
};struct Plane {float yCoord;Plane(float y) {yCoord = y;}
};struct Light {float3 position;Light(float3 pos) {position = pos;}
};
下一步,我们创建几个distance operation距离运算
函数来帮助我们确定元素到场景之间的距离:
float unionOp(float d0, float d1){return min(d0, d1);
}float differenceOp(float d0, float d1){//差集return max(d0, -d1);
}float distToSphere(Ray ray, Sphere s){return length(ray.origin - s.center) - s.radius;
}float distToPlane(Ray ray, Plane plane){return ray.origin.y - plane.yCoord;
}
下一步,我们创建一个distanceToScene() 函数,它给出场景中到任意物体的最近距离.我们用这些函数来生成一个形状,它看起来像是一个带有几个洞的空心球体:
float distToScene(Ray r){Plane p = Plane(0.0);float d2p = distToPlane(r, p);Sphere s1 = Sphere(float3(2.0), 1.9);Sphere s2 = Sphere(float3(0.0, 4.0, 0.0), 4.0);Sphere s3 = Sphere(float3(0.0, 4.0, 0.0), 3.9);Ray repeatRay = r;repeatRay.origin = fract(r.origin / 4.0) * 4.0;float d2s1 = distToSphere(repeatRay, s1);float d2s2 = distToSphere(r, s2);float d2s3 = distToSphere(r, s3);float dist = differenceOp(d2s2, d2s3);dist = differenceOp(dist, d2s1);dist = unionOp(d2p, dist);return dist;
}
目前我们写的都是旧代码,只是对Raymarching文章中的重构.让我们谈谈normals法线及为什么需要法线.如果我们有一个平板-比如我们的平面-它的法线总是(0,1,0)
也就是指向上方.本例中却很繁琐.法线在3D
空间是一个float3
而且我们需要知道它在射线上的位置.假设射线刚好接触到球体的左侧.法线应是(-1,0,0)
,就是指向左边并远离球体.如果射线稍稍移动到该点的右边,在球体内部(如-0.001
).如果射线稍稍移动到该点的左边,在球体外部(如0.001
).如果我们从左边减去左边得到-0.001 - 0.001 = -0.002
它指向左边,所以这就是我们法线的x
坐标.然后对y
和z
重复同样操作.我们使用一个名为eps的2D
向量,来让向量调和vector swizzling更容易操作,每次都使用选定的值0.001
作为各个坐标值:
float3 getNormal(Ray ray){float2 eps = float2(0.001, 0.0);float3 n = float3(distToScene(Ray(ray.origin + eps.xyy, ray.direction)) -distToScene(Ray(ray.origin - eps.xyy, ray.direction)),distToScene(Ray(ray.origin + eps.yxy, ray.direction)) -distToScene(Ray(ray.origin - eps.yxy, ray.direction)),distToScene(Ray(ray.origin + eps.yyx, ray.direction)) -distToScene(Ray(ray.origin - eps.yyx, ray.direction)));return normalize(n);
}
最后,我们已经准备好看到图形了.我们再次使用Raymarching
代码,放在已经添加了法线的内核函数的末尾,这样我们就可以给每个像素插值出颜色:
kernel void compute(texture2d<float, access::write> output [[texture(0)]],constant float &time [[buffer(0)]],uint2 gid [[thread_position_in_grid]]) {int width = output.get_width();int height = output.get_height();float2 uv = float2(gid) / float2(width, height);uv = uv * 2.0 - 1.0;uv.y = -uv.y;Ray ray = Ray(float3(0., 4., -12), normalize(float3(uv, 1.)));float3 col = float3(0.0);for (int i=0; i<100; i++) {float dist = distToScene(ray);if (dist < 0.001) {col = float3(1.0);break;}ray.origin += ray.direction * dist;}float3 n = getNormal(ray);output.write(float4(col * n, 1.0), gid);
}
如果你现在运行playground你将看到类似的图像:
现在我们有了法线,我们可以用lighting() 函数来计算场景中每个像素的光照.首先我们需要知道灯光的方向(lightRay光线
),我们用规范化的灯光位置和当前射线来取得灯光方向.对diffuse漫反射光照我们需要知道法线和光线间的角度,也就是两者的点积.对specular高光光照我们需要在表面进行反射,它们依赖于我们寻找的角度.不同之处在于,本例中,我们首先发射一个射线到场景中,从表面反射回来,再测量反射线和lightRay光线
间的角度.然后对这个值进行一个高次乘方运算来让它更锐利.最后我们返回混合光线:
float lighting(Ray ray, float3 normal, Light light){float3 lightRay = normalize(light.position - ray.origin);float diffuse = max(0.0, dot(normal, lightRay));float3 reflectedRay = reflect(ray.direction, normal);float specular = max(0.0, dot(reflectedRay, lightRay));specular = pow(specular, 200.0);return diffuse + specular;
}
在内核函数中用下面几行替换最后一行:
Light light = Light(float3(sin(time) * 10.0, 5.0, cos(time) * 10.0));
float l = lighting(ray, n, light);
output.write(float4(col * l, 1.0), gid);
如果你现在运行playground你将看到类似的图像:
下一步,阴影!我们几乎从本系列的第一部分就开始使用shadow() 函数到现在,只做过少许修改.我们规范化灯光方向(lightDir
),并在步进射线时不断更新disAlongRay
:
float shadow(Ray ray, Light light){float3 lightDir = light.position - ray.origin;float lightDist = length(lightDir);lightDir = normalize(lightDir);float distAlongRay = 0.01;for (int i=0; i<100; i++) {Ray lightRay = Ray(ray.origin + lightDir * distAlongRay, lightDir);float dist = distToScene(lightRay);if (dist < 0.001) {return 0.0;break;}distAlongRay += dist;if (distAlongRay > lightDist) { break; }}return 1.0;
}
用下面几行替换内核函数中的最后一行:
float s = shadow(ray, light);
output.write(float4(col * l * s, 1.0), gid);
如果你现在运行playground你将看到类似的图像:
让我们给场景添加点soft shadows软阴影
.在现实生活中,离物体越远阴影散布越大.例如,如果地板上有个立方体,在立方体的顶点我们得到清晰的阴影,但离立方体远的地方看起来像一个模糊的阴影.换句话说,我们从地板上的某点出发,向着灯光前进,要么撞到要么错过.硬阴影很简单:我们撞到了什么东西,这个点主在阴影中.软阴影则处于两者之间.用下面几行更新shadow() 函数:
float shadow(Ray ray, float k, Light l){float3 lightDir = l.position - ray.origin;float lightDist = length(lightDir);lightDir = normalize(lightDir);float eps = 0.1;float distAlongRay = eps * 2.0;float light = 1.0;for (int i=0; i<100; i++) {Ray lightRay = Ray(ray.origin + lightDir * distAlongRay, lightDir);float dist = distToScene(lightRay);light = min(light, 1.0 - (eps - dist) / eps);distAlongRay += dist * 0.5;eps += dist * k;if (distAlongRay > lightDist) { break; }}return max(light, 0.0);
}
你会注意到,我们这次从白色(1.0
)灯光开始,通过使用一个衰减器(k)来得到不同的(中间的)灯光值.eps变量告诉我们当光线进入场景中时beam波束有多宽.窄波束意味着锐利的阴影,而宽波束意味着软阴影.我们从小distAlongRay
到大开始,不然的话该点所在的曲面会投射阴影到自己身上.然后我们像硬阴影中那样沿射线前进,并得到离场景的距离,之后我们从eps
(beam width波束宽度)中减掉dist
并除以eps
.这样给出了波束覆盖的百分比.如果我们颠倒它(1 - beam width
)就得到了处于灯光中的百分比.当我们沿着射线前进时,我们取这个新的值和light
值中的最小值,来让阴影保持最黑.然后再沿射线前进,并根据行进距离均匀地增加beam width波束宽度,并缩放k
倍.如果超过了灯光,就跳出循环.最后,我们想要避免给灯光一个负值,所以我们返回0.0和灯光值之间的最大值.现在让我们用新的shadow()
函数来改写内核函数:
float3 col = float3(1.0);
bool hit = false;
for (int i=0; i<200; i++) {float dist = distToScene(ray);if (dist < 0.001) {hit = true;break;}ray.origin += ray.direction * dist;
}
if (!hit) {col = float3(0.5);
} else {float3 n = getNormal(ray);Light light = Light(float3(sin(time) * 10.0, 5.0, cos(time) * 10.0));float l = lighting(ray, n, light);float s = shadow(ray, 0.3, light);col = col * l * s;
}
Light light2 = Light(float3(0.0, 5.0, -15.0));
float3 lightRay = normalize(light2.position - ray.origin);
float fl = max(0.0, dot(getNormal(ray), lightRay) / 2.0);
col = col + fl;
output.write(float4(col, 1.0), gid);
注意我们切换到了默认的白色.然后我们添加一个布尔值叫hit,它来告诉我们碰撞到物体没有.我们限定当我们到场景的距离在0.001之内就是碰撞了,如果我们没有碰到任何东西,则用灰色着色,否则确定阴影的数值.在最后我们只需要在场景前面添加另一个(固定的)光源,就能看到阴影的更多细节.如果你现在运行playground你将看到类似的图像:
要看这份代码的动画效果,我在下面使用一个Shadertoy
嵌入式播放器.只要把鼠标悬浮在上面,并单击播放按钮就能看到动画:<译者注:这里不支持嵌入播放器,我用gif代替https://www.shadertoy.com/embed/XltSWf>
源代码source code已发布在Github上.
下次见!
[MetalKit]32-Shadows-in-Metal-part-2阴影2相关推荐
- [MetalKit]45-Using eGPUs with Metal 在 eGPU上使用 Metal
本系列文章是对 metalkit.org 上面MetalKit内容的全面翻译和学习. MetalKit系统文章目录 对于那些像我一样需要原生 GPU 性能,却只有一个笔记本电脑,又不想再买一个台式机的 ...
- [MetalKit]33-Ambient-Occlusion-in-Metal环境光遮蔽
本系列文章是对 metalkit.org 上面MetalKit内容的全面翻译和学习. MetalKit系统文章目录 今天我们将学习ambient occlusion环境光遮蔽.我们将使用Shadows ...
- 苹果框架学习(二) Metal
文章目录 苹果框架学习(二) Metal Metal简介 1. Essentials 1.1 基本任务和概念 1.2 将OpenGL代码迁移到Metal 1.3 将您的Metal代码移植到苹果Arm芯 ...
- Unity SRP自定义渲染管线 -- 5.Directional Shadows
原文:https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/directional-shadows/ 支持多个方向光 ...
- UNITY关于阴影打开关闭的设置
对于单个物体可以在Mesh Renderer中的CAST SHADOWS选择是否产生阴影, Receive Shadows中选择是否接受阴影 对于整个地图可以在光源的inspector面板打开和关闭 ...
- 一种软阴影的实现方法
转载自:http://hi.baidu.com/laizhishen/blog/item/b4c219dee23df1e177c63851.html 软阴影 www.GameDev.net 作者:An ...
- 【Unity3D】阴影原理及应用
1 阴影原理 光源照射到不透明物体上,会向该物体的后面投射阴影,如果阴影区域存在其他物体,这些物体不被光源照射的部分就需要渲染阴影.因此,我们可以将阴影的生成抽象出 2 个流程:物体投射阴影.物体接收 ...
- 【UE4】【笔记】7、照明阴影和后期处理
UE4官方视频学习笔记--照明阴影和后期处理 大纲:光照.阴影.后期处理 这三个东西相互牵扯,他们将会是性能问题的最大来源,一旦处理不好会引起巨大的性能消耗 光照(Lighting) 光照有三种可移动 ...
- cocos creator 3D学习(六)光照+阴影
目录 前言 分类 1.方向光 2.球面光 3.聚光灯 4.阴影 5.环境光 最后 前言 有一些会需要在cocos creator 3D里面加光照,让模型更好看一些 cocos creator 3D里面 ...
- UE4-(室外光照)距离场阴影
对于级联阴影贴图的局限性(级联距离值较大,阴影模糊,性能消耗较大,级联距离值较小,阴影清晰但是远处的几何体阴影消失),可以考虑使用距离场阴影. 注意:距离场阴影在工程中不会默认开启,需要手动设置,设置 ...
最新文章
- python 字节和字符串区别,Python中字节串和字符串,不是一个概念没有区别之分...
- 1.2.3 Using Option Files
- Js里面IF(var)表示什么意思?js中if的写法、含义
- ImportError: No module named google.protobuf.internal
- RTX5 | 线程管理01 - 创建线程(静态堆栈方式)
- DataTable 数字排序问题
- 开课吧:深入了解人工智能在金融行业中的应用
- JavaWeb之分页代码
- python 成语库_README.md · 天宇之游/一个python的TK猜成语游戏 - Gitee.com
- IIS与CuteFTP进行网站发布
- 基于MThings完成MODBUS设备在线状态扫描
- strom 在linux下部署、基本命令
- 附加题——求n的阶乘和
- 头歌 初识Redis
- 奇怪的小鸭子也增加了Java实现
- 【玩转c++】多态深度刨析
- XGen中使用python进行简单的操作
- 图形学数学基础之1D采样分布计算方法Inverse Method
- 使用正则表达式提取文件中满足条件的内容
- 哪些道理是过了40岁之后才明白的?
热门文章
- opencv4.5.2嵌入式移植
- 【信息安全技术】RSA算法的研究及不同优化策略的比较
- arm 驱动基础:点亮led_firt
- 怎样开发自己的Telegram Bot
- 《日语综合教程》第七册 第四課 読み物 初日影のなかで
- 计算机专硕日语考研学校排名,日语考研院校排名(日语专硕学校排名)
- 明年债券收益率有望延续下行的趋势
- java二进制保存图片_Java中如何把图片转换成二进制流
- Serenity框架官方文档翻译(1-2开始、安装和界面)
- 科普|掀开马甲包的 “神秘面纱”