shader编程-RayMarching三维场景下使用交集、并集、差集方法CSG建模(WebGL-Shader开发基础10)

本文主要是介绍shader编程-RayMarching三维场景下使用交集、并集、差集方法CSG建模(WebGL-Shader开发基础10),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

三维场景下使用交集、并集、差集方法CSG建模

  • 1. CSG介绍
  • 2. demo效果
  • 3. 实现过程
    • 3.1 交集、并集、差集函数
    • 3.2 旋转矩阵
    • 3.3 基础形状
    • 3.4 组合过程
      • 3.4.1 互相垂直圆柱实现
      • 3.4.2 相交的球体与立方体
      • 3.4.3 二者取差集
  • 4. demo代码

1. CSG介绍

上一篇文章学习了模型的基本变换(旋转、缩放、平移),这篇简单说说CSG建模,即构造实体形状,它是一种通过交集、并集、差集的运算用简单的几何形状创建复杂几何形状的方法,下面的图片可以清楚的描述这种方式建模的思路和过程
在这里插入图片描述

2. demo效果

sdf-csg


这个demo按照上面的图片中的思路,使用基础形状圆柱、立方体、球体通过交集、并集、差集将它们组合而成

3. 实现过程

3.1 交集、并集、差集函数

之前在二维空间中实现过交集、并集、差集函数,转换到三维空间思路是一样的,不过这一次处理的时候是附带了材质,所以使用x分量即距离进行比较,返回的是输入的其中一个vec2变量,具体如下

//交集
vec2 opI( vec2 d1, vec2 d2 )
{return (d1.x>d2.x) ? d1 : d2;
}   //并集
vec2 opU( vec2 d1, vec2 d2 )
{return (d1.x<d2.x) ? d1 : d2;
}//差集
vec2 opS( vec2 d1, vec2 d2 )
{return opI(d1,-d2);
}

3.2 旋转矩阵

旋转矩阵与上一篇文章中的一模一样,直接搬过来,如下

//绕z轴旋转矩阵
mat4 rotZ(float a) {return mat4(cos(a),-sin(a),0.0,0.0,sin(a),cos(a),0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0);
}//绕x轴旋转矩阵
mat4 rotX(float a) {return mat4(1.0,0.0,0.0,0.0,0.0,cos(a),-sin(a),0.0,0.0,sin(a),cos(a),0.0,0.0,0.0,0.0,1.0);
}//绕y轴旋转矩阵
mat4 rotY(float a) {return mat4(cos(a),0.0,sin(a),0.0,0.0,1.0,0.0,0.0,-sin(a),0.0,cos(a),0.0,0.0,0.0,0.0,1.0);
} 

3.3 基础形状

我们这用到的基础形状是圆柱、立方体、球体,它们的sdf函数如下

//球体
float sdSphere( vec3 p, float s )
{return length(p)-s;
}//立方体
float sdBox( vec3 p, vec3 b,float rad )
{vec3 d = abs(p) - b;return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)) - rad;
}//圆柱
float sdCylinder( vec3 p, float h, float r )
{vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(r,h);return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

3.4 组合过程

3.4.1 互相垂直圆柱实现

相交圆柱的实现就是绘制三根相互垂直的圆柱,然后用并集函数将它们合并,绘制过程是首先绘制出一根圆柱,然后将坐标系绕Z轴旋转90度绘制第二个圆柱,接着再将坐标系绕X轴旋转90度绘制第三根圆柱,最后合并它们,实现过程如下

//绘制三根垂直相交的圆柱
vec3 pos = p-vec3(0,2,5);//确定模型的中心
vec4 cylindersPos = vec4(pos,1.0);//转为其次坐标
cylindersPos*=rotY(u_time);//旋转vec2 cylinder1 = vec2(sdCylinder(cylindersPos.xyz,2.0,0.6),1.0);float angle = radians(90.0);//角度转为弧度cylindersPos*=rotZ(angle);
vec2 cylinder2 = vec2(sdCylinder(cylindersPos.xyz,2.0,0.6),1.0);cylindersPos*=rotX(angle);
vec2 cylinder3 = vec2(sdCylinder(cylindersPos.xyz,2.0,0.6),1.0);// 三根圆柱取交集
vec2 cylinderRes  = opU(cylinder1,cylinder2);
cylinderRes  = opU(cylinderRes,cylinder3);

绘制结果
在这里插入图片描述

3.4.2 相交的球体与立方体

这一步比较简单,就是绘制球体与立方体然后求交集

pos = p-vec3(0,2,5);//屏幕坐标重置
vec4 boxPos = vec4(pos,1.0);//转为其次坐标
boxPos*=rotY(u_time);//旋转vec2 box = vec2(sdBox(boxPos.xyz,vec3(1.0),0.06),2.0);//方块,材质ID为2.0vec2 sphere = vec2(sdSphere(boxPos.xyz,1.4),3.0);//方块,材质ID为2.0vec2 boxSphere = opI(box,sphere);//方块和球取交集

绘制结果
在这里插入图片描述

3.4.3 二者取差集

这一步只需要将上面两步取差集,返回即可

 vec2 res = opS(boxSphere,cylinderRes);//方块和球交集结果与三个垂直圆柱取差集res = opU(plane,res); //差集结果与地板取交集return res;

绘制结果
在这里插入图片描述
你会发现被掏空的地方是粉色,这个色来自哪里呢,来自main函数中的默认材质色

vec3 materialColor = vec3(1.0, 0.0, 1.0);//默认材质色,使用差集计算出来的内壁会使用该色填充

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;//相交检测临近表面距离//绕z轴旋转矩阵mat4 rotZ(float a) {return mat4(cos(a),-sin(a),0.0,0.0,sin(a),cos(a),0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0);}//绕x轴旋转矩阵mat4 rotX(float a) {return mat4(1.0,0.0,0.0,0.0,0.0,cos(a),-sin(a),0.0,0.0,sin(a),cos(a),0.0,0.0,0.0,0.0,1.0);}//绕y轴旋转矩阵mat4 rotY(float a) {return mat4(cos(a),0.0,sin(a),0.0,0.0,1.0,0.0,0.0,-sin(a),0.0,cos(a),0.0,0.0,0.0,0.0,1.0);} //交集vec2 opI( vec2 d1, vec2 d2 ){return (d1.x>d2.x) ? d1 : d2;}   //并集vec2 opU( vec2 d1, vec2 d2 ){return (d1.x<d2.x) ? d1 : d2;}//差集vec2 opS( vec2 d1, vec2 d2 ){return opI(d1,-d2);}//球体float sdSphere( vec3 p, float s ){return length(p)-s;}//立方体float sdBox( vec3 p, vec3 b,float rad ){vec3 d = abs(p) - b;return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)) - rad;}//圆柱float sdCylinder( vec3 p, float h, float r ){vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(r,h);return min(max(d.x,d.y),0.0) + length(max(d,0.0));}vec2 getDistandMaterial(vec3 p){vec2 plane = vec2(p.y,0.0);//地面//绘制三根垂直相交的圆柱vec3 pos = p-vec3(0,2,5);//确定模型的中心vec4 cylindersPos = vec4(pos,1.0);//转为其次坐标cylindersPos*=rotY(u_time);//旋转vec2 cylinder1 = vec2(sdCylinder(cylindersPos.xyz,2.0,0.6),1.0);float angle = radians(90.0);//角度转为弧度cylindersPos*=rotZ(angle);vec2 cylinder2 = vec2(sdCylinder(cylindersPos.xyz,2.0,0.6),1.0);cylindersPos*=rotX(angle);vec2 cylinder3 = vec2(sdCylinder(cylindersPos.xyz,2.0,0.6),1.0);// 三根圆柱取交集vec2 cylinderRes  = opU(cylinder1,cylinder2);cylinderRes  = opU(cylinderRes,cylinder3);pos = p-vec3(0,2,5);//屏幕坐标重置vec4 boxPos = vec4(pos,1.0);//转为其次坐标boxPos*=rotY(u_time);//旋转vec2 box = vec2(sdBox(boxPos.xyz,vec3(1.0),0.06),2.0);//方块,材质ID为2.0vec2 sphere = vec2(sdSphere(boxPos.xyz,1.4),3.0);//方块,材质ID为2.0vec2 boxSphere = opI(box,sphere);//方块和球取交集vec2 res = opS(boxSphere,cylinderRes);//方块和球交集结果与三个垂直圆柱取差集res = opU(plane,res); //差集结果与地板取交集return res;}vec2 rayMarch(vec3 rayStart, vec3 rayDirection) {float depth=0.;float material=0.;for(int i=0; i<MAX_STEPS; i++) {vec3 p = rayStart + rayDirection*depth;//上一次步进结束后的坐标也就是这一次步进出发点vec2 dm = getDistandMaterial(p);float dist = dm.x;//获取当前步进出发点与物体相交时距离material = dm.y;depth += dist; //步进长度累加if(depth>MAX_DIST || dist<SURF_DIST) break;//步进距离大于最大步进距离或与物体表面距离小于最小表面距离(光线进入物体)停止前进}return vec2(depth,material);}vec3 getNormal(vec3 p){return normalize(vec3(getDistandMaterial(vec3(p.x + SURF_DIST, p.y, p.z)).x - getDistandMaterial(vec3(p.x - SURF_DIST, p.y, p.z)).x,getDistandMaterial(vec3(p.x, p.y + SURF_DIST, p.z)).x - getDistandMaterial(vec3(p.x, p.y - SURF_DIST, p.z)).x,getDistandMaterial(vec3(p.x, p.y, p.z  + SURF_DIST)).x - getDistandMaterial(vec3(p.x, p.y, p.z - SURF_DIST)).x));}//Blinn-Phong模型光照计算vec3 calcBlinnPhongLight( vec3 materialColor, vec3 p, vec3 ro) {vec3 lightPos = vec3(5.0 * sin(u_time), 20.0, 10.0*cos(u_time)-18.);//光源坐标//计算环境光float k_a = 0.3;//环境光反射系数vec3 ambientLight = 0.6 * vec3(1.0, 1.0, 1.0);vec3 ambient = k_a*ambientLight;vec3 N = getNormal(p); //法线vec3 L = normalize(lightPos - p); //光照方向vec3 V = normalize(ro - p); //视线vec3 H = normalize(V+L); //半程向量float r = length(lightPos - p);//计算漫反射光float k_d = 0.6;//漫反射系数float dotLN = clamp(dot(L, N),0.0,1.0);//点乘,并将结果限定在0~1vec3 diffuse = k_d * (materialColor/r*r) * dotLN;//计算高光反射光float k_s = 0.8;//镜面反射系数float shininess = 160.0;vec3 specularColor = vec3(1.0, 1.0, 1.0);vec3 specular = k_s * (specularColor/r*r)* pow(clamp(dot(N, H), 0.0, 1.0), shininess);//计算高光//计算阴影vec2 res = rayMarch(p + N*SURF_DIST*2.0,L); if(res.x<length(lightPos-p)-0.001){diffuse*=0.1;}//颜色 =  环境光 + 漫反射光 + 镜面反射光return ambient +diffuse + specular;}void main( void ) {//窗口坐标调整为[-1,1],坐标原点在屏幕中心vec2 st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;vec3 ro = vec3(0.0,2.0,0.0);//视点vec3 rd = normalize(vec3(st.x,st.y,1.0));//视线方向vec2 res = rayMarch(ro,rd);//反向光线追踪求交点距离与材质IDfloat d = res.x;//物体与视点的距离float m = res.y;//材质IDvec3 p = ro + rd * d;vec3 materialColor = vec3(1.0, 0.0, 1.0);//默认材质色,使用差集计算出来的内壁会使用该色填充//为不同物体设置不同的材质颜色if(m==0.0){materialColor = vec3(.2, 0.0, 0.0);}if(m==1.0){materialColor = vec3(.2, 0.0, 1.0);}if(m==2.0){materialColor = vec3(.7, 0.2, 0.0);}if(m==3.0){materialColor = vec3(.8, .9, 0.0);}vec3 color = vec3(1.0,1.0,1.0);//使用Blinn-Phong模型计算光照color *= calcBlinnPhongLight( materialColor, p, ro);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三维场景下使用交集、并集、差集方法CSG建模(WebGL-Shader开发基础10)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/308488

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

判断PyTorch是GPU版还是CPU版的方法小结

《判断PyTorch是GPU版还是CPU版的方法小结》PyTorch作为当前最流行的深度学习框架之一,支持在CPU和GPU(NVIDIACUDA)上运行,所以对于深度学习开发者来说,正确识别PyTor... 目录前言为什么需要区分GPU和CPU版本?性能差异硬件要求如何检查PyTorch版本?方法1:使用命

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

使用Python和Pyecharts创建交互式地图

《使用Python和Pyecharts创建交互式地图》在数据可视化领域,创建交互式地图是一种强大的方式,可以使受众能够以引人入胜且信息丰富的方式探索地理数据,下面我们看看如何使用Python和Pyec... 目录简介Pyecharts 简介创建上海地图代码说明运行结果总结简介在数据可视化领域,创建交互式地

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD