shader编程-RayMarching与SDF结合开始三维探索(WebGL-Shader开发基础07)
RayMarching与SDF结合开始三维探索
- 1. 初识RayMarching
- 2. 实现过程
- 2.1 光线步进函数
- 2.2 获取物体表面距离
- 2.3 计算光照阴影
- 2.4 物体表面距离计算
- 3 demo运行结果
- 4 demo代码
1. 初识RayMarching
人们之所以可以看到物体,是因为光线从光源发出照射到物体,之后反射进入人们的眼睛,这样就看到了物体,在三维模型中使模型呈现在场景中,光线从光源出发,照射到物体,反射后到达相机,反之,同样可以从相机出发光线摄像物体之后反射到光源,RayMarching 算法就是从相机发射光线,具体请看下图
从相机出发,向屏幕中的每一个像素发射一条光线,以其中一条光线为例,光线开始以一定的步长前进,每前进一步判断是否达到物体表面,如果没有继续前进,一直到到达物体表面或者没有与物体相交一直到我们设定的最大距离,则光线停止前进。结束后可以得到光线与物体相交的坐标,然后通过光照信息与交点信息计算物体表面颜色,接下来一步一步实现
2. 实现过程
2.1 光线步进函数
如上图,光线一次一次步进,直到达到物体表面停止,每次步进的距离是当前点与物体表面的最小距离,图中的dist0是第一次步进的距离,该距离是视点与物体表面的最小距离,第二次步进的距离dist1是第一次步进结束点与物体表面的最近距离,依次类推直到沿着视线到达物体表面
开始书写光线步进函数,我们定义了三个常量分别表示:MAX_STEPS(最大步进步数)、MAX_DIST (最大步进距离)、SURF_DIST (相交检测临近表面距离),最大步进步数用来控制光线步进的次数,为了限定一个循环范围,光线可以向远处无限传播,但是我们不能无止休的计算下去,超出物体范围没有任何意义,只会浪费计算机性能,最大步进距离也是用来限制光线步进,这一次是按步进距离限制,当光线累计步进超过这个距离时也停止前进,SURF_DIST用来检测光线与物体表面的距离,光线沿着一个方向步进,与物体表面的距离越来越近,当达到某一个较小的值时,我们就可以认为它们相交了,光线同样停止前进。
具体请参照以下代码
const int MAX_STEPS = 100;//最大步进步数
const float MAX_DIST = 100.0;//最大步进距离
const float SURF_DIST = 0.01;//相交检测临近表面距离float rayMarch(vec3 rayStart, vec3 rayDirection) {float depth=0.;for(int i=0; i<MAX_STEPS; i++) {vec3 p = rayStart + rayDirection*depth;//上一次步进结束后的坐标也就是这一次步进出发点float dist = getDist(p);//获取当前步进出发点与物体相交时距离depth += dist; //步进长度累加if(depth>MAX_DIST || dist<SURF_DIST) break;//步进距离大于最大步进距离或与物体表面距离小于最小表面距离(光线进入物体)停止前进}return depth;
}
2.2 获取物体表面距离
上一步中用到一个函数getDist,计算物体表面距离,在这里简单说一下,这个函数实际上是给场景中添加物体,主要添加了一个球体和一个地面,之前学习了二维了一些SDF实现,从最简单圆开始学习,三维的SDF也从比较简单的球体开始,与二维的相比,只是输入参数由二维变成三维,即通过length计算空间中点到中心点的距离,返回满足指定距离的集合,就是球体的SDF函数,还有一个平面函数,直接取y值为特定值即可,最后使用min函数对它们求并集返回,具体如下
float sdSphere( vec3 p, float s )
{return length(p)-s;
}float getDist(vec3 p){vec3 spherrCenter = vec3(0,1,5);float sphereRadius = 1.0;float dist = sdSphere(p-spherrCenter,sphereRadius);//球体float plane = p.y;//地面return min(dist,plane);// min 函数求并集然后返回
}
2.3 计算光照阴影
这里计算光照只计算一下漫反射,getLight函数的前半部分就是用来计算漫反射的,首先定义了一个光源坐标,为了让光源旋转给光源的x分量和z分量分别累加sin(u_time)和cos(u_time),光线的方向用物体用光源坐标减去物体表面坐标即可,然后通过getNormal函数获取物体表面的法线,之后用光线与法线做点乘运算,并使用clamp函数将结果限制在0~1范围内
计算阴影部分,这一次光线需要从物体表面出发,向着光源方向做步进,若步进过程中被物体物体遮挡,则计算阴影,具体实现过程如下
//计算表面法线
vec3 getNormal(vec3 p){return normalize(vec3(getDist(vec3(p.x + SURF_DIST, p.y, p.z)) - getDist(vec3(p.x - SURF_DIST, p.y, p.z)),getDist(vec3(p.x, p.y + SURF_DIST, p.z)) - getDist(vec3(p.x, p.y - SURF_DIST, p.z)),getDist(vec3(p.x, p.y, p.z + SURF_DIST)) - getDist(vec3(p.x, p.y, p.z - SURF_DIST))));}float getLight(vec3 p){vec3 lightPos = vec3(0,5,7);lightPos.xz += vec2(sin(u_time),cos(u_time));vec3 light = normalize(lightPos-p);vec3 normal = getNormal(p);float diffuse = clamp(dot(normal,light),0.0,1.0);//限制在0~1//计算阴影float d = rayMarch(p + normal*SURF_DIST*2.0,light); if(d<length(lightPos-p)){diffuse*=0.1;}return diffuse;
}
2.4 物体表面距离计算
物体表面距离在main函数中实现,首先需要定义视点(摄像机) 坐标ro,与视线方向rd,然后调用写好的步进函数rayMarch(ro,rd),然后通过返回的结果计算出光线与物体的相交点坐标,用相交点坐标调用getLight函数计算光照和阴影,具体如下
void main( void ) {//窗口坐标调整为[-1,1],坐标原点在屏幕中心vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;vec3 color = vec3(0.6);//背景色vec3 ro = vec3(0.0,1.0,0.0);//视点vec3 rd = normalize(vec3(st.x,st.y,1.0));//光线方向float d = rayMarch(ro,rd);vec3 p = ro + rd * d;float diffuse = getLight(p);color = vec3(diffuse);gl_FragColor = vec4(color, 1.0);}
3 demo运行结果
运行结果贴了图片,实际上光照会旋转,结果比较简单,但也算是从二维转向三维了,这只是揭开了序幕,只是三维shader的hello world版,革命尚未成功,同志仍需努力啊
4 demo代码
<body><div id="container"></div><script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script><script>var container;var camera, scene, renderer;var uniforms;var vertexShader = `void main() {gl_Position = vec4( position, 1.0 );} `var fragmentShader = `#ifdef GL_ESprecision mediump float;#endifuniform float u_time;uniform vec2 u_mouse;uniform vec2 u_resolution;const int MAX_STEPS = 100;//最大步进步数const float MAX_DIST = 100.0;//最大步进距离const float SURF_DIST = 0.01;//相交检测临近表面距离float sdSphere( vec3 p, float s ){return length(p)-s;}float getDist(vec3 p){vec3 spherrCenter = vec3(0,1,5);float sphereRadius = 1.0;float dist = sdSphere(p-spherrCenter,sphereRadius);//球体float plane = p.y;//地面return min(dist,plane);// min 函数求并集然后返回}float rayMarch(vec3 rayStart, vec3 rayDirection) {float depth=0.;for(int i=0; i<MAX_STEPS; i++) {vec3 p = rayStart + rayDirection*depth;//上一次步进结束后的坐标也就是这一次步进出发点float dist = getDist(p);//获取当前步进出发点与物体相交时距离depth += dist; //步进长度累加if(depth>MAX_DIST || dist<SURF_DIST) break;//步进距离大于最大步进距离或与物体表面距离小于最小表面距离(光线进入物体)停止前进}return depth;}vec3 getNormal(vec3 p){return normalize(vec3(getDist(vec3(p.x + SURF_DIST, p.y, p.z)) - getDist(vec3(p.x - SURF_DIST, p.y, p.z)),getDist(vec3(p.x, p.y + SURF_DIST, p.z)) - getDist(vec3(p.x, p.y - SURF_DIST, p.z)),getDist(vec3(p.x, p.y, p.z + SURF_DIST)) - getDist(vec3(p.x, p.y, p.z - SURF_DIST))));}float getLight(vec3 p){vec3 lightPos = vec3(0,5,7);lightPos.xz += vec2(sin(u_time),cos(u_time));vec3 light = normalize(lightPos-p);vec3 normal = getNormal(p);float diffuse = clamp(dot(normal,light),0.0,1.0);//限制在0~1//计算阴影float d = rayMarch(p + normal*SURF_DIST*2.0,light); if(d<length(lightPos-p)){diffuse*=0.1;}return diffuse;}void main( void ) {//窗口坐标调整为[-1,1],坐标原点在屏幕中心vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;vec3 color = vec3(0.6);//背景色vec3 ro = vec3(0.0,1.0,0.0);//视点vec3 rd = normalize(vec3(st.x,st.y,1.0));//视线方向float d = rayMarch(ro,rd);vec3 p = ro + rd * d;float diffuse = getLight(p);//漫反射光计算color = vec3(diffuse);gl_FragColor = vec4(color, 1.0);}`init();animate();function init() {container = document.getElementById('container');camera = new THREE.Camera();camera.position.z = 1;scene = new THREE.Scene();var geometry = new THREE.PlaneBufferGeometry(2, 2);uniforms = {u_time: {type: "f",value: 1.0},u_resolution: {type: "v2",value: new THREE.Vector2()},u_mouse: {type: "v2",value: new THREE.Vector2()}};var material = new THREE.ShaderMaterial({uniforms: uniforms,vertexShader: vertexShader,fragmentShader: fragmentShader});var mesh = new THREE.Mesh(geometry, material);scene.add(mesh);renderer = new THREE.WebGLRenderer();//renderer.setPixelRatio(window.devicePixelRatio);container.appendChild(renderer.domElement);onWindowResize();window.addEventListener('resize', onWindowResize, false);document.onmousemove = function (e) {uniforms.u_mouse.value.x = e.pageXuniforms.u_mouse.value.y = e.pageY}}function onWindowResize(event) {renderer.setSize(800, 800);uniforms.u_resolution.value.x = renderer.domElement.width;uniforms.u_resolution.value.y = renderer.domElement.height;}function animate() {requestAnimationFrame(animate);render();}function render() {uniforms.u_time.value += 0.02;renderer.render(scene, camera);}</script>
</body>
shader编程-RayMarching与SDF结合开始三维探索(WebGL-Shader开发基础07)相关推荐
- 【浅墨Unity3D Shader编程】之一 游戏场景的创建 第一个Shader的书写
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/40723789 作者:毛星云(浅墨) ...
- 【Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/42215079 作者:毛星云(浅墨) ...
- 【Unity3D Shader编程】之六 暗黑城堡篇 表面着色器 Surface Shader 的写法 一
分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 本系列文章由@浅墨 ...
- Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/42215079 作者:毛星云(浅墨) ...
- 【浅墨Unity3D Shader编程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的写法(一)
本文主要讲解了Unity中SurfaceShader的具体写法,以及几个常用的CG函数的用法. 在这里先说明一下,表面着色器将分为两次讲解,本文介绍表面着色器的基本概念和一些写法,用内置的兰伯特光照模 ...
- 【浅墨Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 amp; 纹理混合...
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://hpw123.net/a/C__/kongzhitaichengxu/2014/1117/120.html 作者:毛星 ...
- 手把手教你成为Shader编程实战达人—GPU
GPU编程市场应用 GPU全称是Graphics Processing Unit,中文成为图形处理器,所以GPU编程也叫图形学编程,它是针对的显卡中的芯片编程,游戏引擎的更新换代发展的一个重要阶段是显 ...
- 【浅墨Unity3D Shader编程】之四 热带雨林篇: 剔除、深度测试、Alpha测试以及基本雾效合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41923661 作者:毛星云(浅墨) ...
- 【Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 纹理混合
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/41175585 作者:毛星云(浅墨) ...
最新文章
- 慕课袁春风老师《计算机系统基础》一二三部分练习题
- 广州的11个辖区_广州“受欢迎”的2个区,相距仅20公里,若“合并”将超黄埔区...
- Java进阶:Semaphore信号量基本使用
- ITK:从Seed开始迭代图像
- java设置并行度_控制Java并行流的并行度
- 使用jclouds库在Amazon S3上上传
- 【加密解密】密码学学习
- 数据和判定(二)------运算符
- 什么是java OOM Out Of Memory 内存溢出?如何分析及解决oom问题?
- VMware虚拟机的安装与创建
- DC888 : worklist slovers
- VUE-地区选择器(V-Distpicker)
- 宏定义超过字长的一些问题
- python 实现抖音视频无水印解析
- uniapp MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 upgrade listeners
- Fiddler简单的使用教程(入门级)
- 什么是云计算?什么是边缘计算?为什么需要云边协同?
- 终极孵化器:仿生婴儿的美丽新世界
- VBS病毒(弹窗病毒)
- 取得地址栏地址(转)
热门文章
- Win7开机自动拨号连接宽带
- 浙江大学计算机保研条件_浙江大学保研有哪些步骤?
- inline-block元素设置overflow:hidden属性导致相邻行内元素向下偏移
- JAVA之IO流、异常、File文件类
- 初学C语言:判断输入的数的奇偶性。
- linux篇-Linux逻辑卷详解总结
- 戴维营第八天上课总结
- 用计算机解锁ipad密码忘了怎么办,iPad密码忘记了怎么办_iPad密码忘记解锁办法-太平洋IT百科手机版...
- Unity ParticleSystem制作脚印效果(记录)
- ? PHP WBEM