對模型進行扭曲、彎曲、裁剪、掏空操作
- 1. demo效果
- 2. 實作要點
- 2.1 模型扭曲
- 2.2 模型彎曲
- 2.3 模型裁剪與掏空
- 2.3.1 球體裁剪與掏空
- 2.3.2 圓柱裁剪與掏空
- 2.3.3 甜圈圈裁剪與掏空
- 3. demo代碼
1. demo效果



如上圖所示,第一張是對方形圓柱進行扭曲操作的效果圖,第二張圖是對扁平立方體面進行彎曲操作的效果圖,第三張圖是從內到外依次是原物體,裁剪后的物體,裁剪后掏空物體的效果,從左到右依次對圓柱、球體、甜圈圈進行同樣變換的效果
2. 實作要點
先回顧一下之前學習的SDF建模相關的一些操作,首先學習了基本變換旋轉、縮放、平移,之后學習了通過布爾運算(交集、并集、差集)組合模型 ,進而拓展了平滑布爾運算組合模型,今天解鎖一點點新的技能,對模型進行扭曲、彎曲、裁剪、掏空操作
2.1 模型扭曲
通過前面的demo效果展示知道,我們首先繪制了一個長條狀的立方體,然后又繪制了一個以同樣立方體扭曲后的物體,實作的核心思想就是變換用來繪制扭曲立方體的坐標,即依據y分量的變化使x分量和z分量轉圈圈,具體代碼如下
vec3 opTwistY( in vec3 p ,float k)
{
float c = cos(k*p.y);
float s = sin(k*p.y);
mat2 m = mat2(c,-s,s,c);
p.xz *=m;
return p;
}
如果你想物體繞z軸或x軸扭曲,兩種辦法,一是先按y軸扭曲然后將物體旋轉,二是將上面的函式中的分量替換為對應的坐標軸
扭曲的坐標處理完接著就是像之前一樣繪制圖形,為了方便使用我們將繪制程序封裝成了一個函式,具體如下
vec2 twistBox (vec4 pos){
//繪制原立方體
pos.x += 2.5;
float box1 = sdBox(pos.xyz,vec3(0.4,1.2,0.4),0.06);//方塊
vec2 mBox1 = vec2(box1,1.0);
//繪制扭曲立方體
pos.x -= 5.0;
pos.xyz=opTwistY(pos.xyz,3.0);//扭曲
float box2 = sdBox(pos.xyz,vec3(0.4,1.2,0.4),0.06);//方塊
vec2 mBox2 = vec2(box2,1.0);
vec2 res = opU(mBox1,mBox2);
return res;
}
呼叫程序如下
vec2 res = vec2(p.y,0.0);//地面
vec3 pos = p-vec3(0,2,5);//確定模型的中心
vec4 oriPos = vec4(pos,1.0);//轉為其次坐標
//扭曲
vec2 twistBox = twistBox(oriPos);
res = opU(res,twistBox);//扭曲立方體
2.2 模型彎曲
彎曲也是同樣的思想,通過改變繪制彎曲模型的坐標來實作,回傳彎曲坐標的函式如下
vec3 opCheapBend(in vec3 p ,float k)
{
float c = cos(k*p.x);
float s = sin(k*p.x);
mat2 m = mat2(c,-s,s,c);
vec3 q = vec3(m*p.xy,p.z);
return q;
}
像上一次一樣,使用處理好的坐標繪制圖形即可,這次也將繪制程序封裝成了一個函式,具體如下
vec2 cheapBendBox (vec4 pos){
//繪制原立方體面
pos.x += 2.5;
float box1 = sdBox(pos.xyz,vec3(1.0,0.2,0.6),0.06);//方塊
vec2 mBox1 = vec2(box1,1.0);
//繪制彎曲立方體面
pos.x -= 5.0;
pos.xyz=opCheapBend(pos.xyz,0.5);//彎曲
float box2 = sdBox(pos.xyz,vec3(1.0,0.2,0.6),0.06);//方塊
vec2 mBox2 = vec2(box2,1.0);
vec2 res = opU(mBox1,mBox2);
return res;
}
呼叫程序如下
vec2 res = vec2(p.y,0.0);//地面
vec3 pos = p-vec3(0,2,5);//確定模型的中心
vec4 oriPos = vec4(pos,1.0);//轉為其次坐標
//扭曲
vec2 twistBox = twistBox(oriPos);
//彎曲
vec2 cheapBendBox = cheapBendBox(oriPos);
//res = opU(res,twistBox);//扭曲立方體
res = opU(res,cheapBendBox); //彎曲立方體
2.3 模型裁剪與掏空
2.3.1 球體裁剪與掏空
掏空函式
//掏空
float opOnion(float sdf, in float thickness )
{
return abs(sdf)-thickness;
}
繪制程序是,我們首先繪制了一個完整的球體,然后繪制了一個裁剪的球體,最后繪制一個掏空且裁剪的球體,之所以也裁剪是因為不裁剪的畫我們看不到內部結構,具體如下
vec2 onionSphere (vec4 pos){
//繪制球體
vec4 pos1 = pos;
pos1.z -= 6.0;
float sphere1 = sdSphere(pos1.xyz,0.6);
vec2 mSphere1 = vec2(sphere1,2.0);
//繪制裁剪球體
vec4 pos2 = pos;
pos2.z -= 2.5;
float sphere2 = sdSphere(pos2.xyz,0.6);
sphere2 = max( sphere2, pos2.y );//裁剪
vec2 mSphere2 = vec2(sphere2,2.0);
//繪制裁剪掏空球體
vec4 pos3 = pos;
float sphere3 = sdSphere(pos3.xyz,0.6);
sphere3 = opOnion(sphere3,0.01);//掏空
sphere3 = max( sphere3, pos3.y );//裁剪
vec2 mSphere3 = vec2(sphere3,2.0);
vec2 res = opU(mSphere1,mSphere2);
res = opU(res,mSphere3);
return res;
}
關于裁剪,不知你有沒有聯想到,這里的裁剪效果是裁掉了y軸大于0 的部分,如果想裁掉x軸或z上的大于0的部分,只需把max( sphere3, pos3.y );//裁剪此處的分量y替換,如果你想要留下y軸上部分,即demo中裁掉和留下的部分互換,那么在y分量前添加負號即可,max( sphere3, -pos3.y );//裁剪,其他軸中的裁剪同理
2.3.2 圓柱裁剪與掏空
圓柱的實作與球體的思路一樣,只不過把球體換成圓柱,如下
vec2 onionCylinder (vec4 pos){
//繪制圓柱
vec4 pos1 = pos;
pos1.z -= 6.0;
float cylinder1 = sdCylinder(pos1.xyz,0.4,0.6);
vec2 mCylinder1 = vec2(cylinder1,1.0);
//繪制裁剪圓柱
vec4 pos2 = pos;
pos2.z -= 2.5;
float cylinder2 = sdCylinder(pos2.xyz,0.4,0.6);
cylinder2 = max( cylinder2, pos2.y );//裁剪
vec2 mCylinder2 = vec2(cylinder2,1.0);
//繪制裁剪掏空圓柱
vec4 pos3 = pos;
float cylinder3 = sdCylinder(pos3.xyz,0.4,0.6);
cylinder3 = opOnion(cylinder3,0.01);//掏空
cylinder3 = max( cylinder3, pos3.y );//裁剪
vec2 mCylinder3 = vec2(cylinder3,1.0);
vec2 res = opU(mCylinder1,mCylinder2);
res = opU(res,mCylinder3);
return res;
}
2.3.3 甜圈圈裁剪與掏空
甜圈圈裁剪與掏空也是同樣的思路,繪制程序如下
vec2 onionTorus (vec4 pos){
//繪制甜圈圈
vec4 pos1 = pos;
pos1.z -= 6.0;
float torus1 = sdTorus(pos1.xyz,vec2(0.6,0.2));
vec2 mTorus1 = vec2(torus1,3.0);
//繪制裁剪甜圈圈
vec4 pos2 = pos;
pos2.z -= 2.5;
float torus2 = sdTorus(pos2.xyz,vec2(0.6,0.2));
torus2 = max( torus2, pos2.y );//裁剪
vec2 mTorus2 = vec2(torus2,3.0);
//繪制裁剪掏空甜圈圈
vec4 pos3 = pos;
float torus3 = sdTorus(pos3.xyz,vec2(0.6,0.2));
torus3 = opOnion(torus3,0.01);//掏空
torus3 = max( torus3, pos3.y );//裁剪
vec2 mTorus3 = vec2(torus3,3.0);
vec2 res = opU(mTorus1,mTorus2);
res = opU(res,mTorus3);
return res;
}
3. 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_ES
precision mediump float;
#endif
uniform 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
);
}
//平滑交集
float opSmoothI( float d1, float d2, float k )
{
float h = max(k-abs(d1-d2),0.0);
return max(d1, d2) + h*h*0.25/k;
}
//平滑并集
float opSmoothU( float d1, float d2, float k )
{
float h = max(k-abs(d1-d2),0.0);
return min(d1, d2) - h*h*0.25/k;
}
//平滑差集
float opSmoothS( float d1, float d2, float k )
{
float h = max(k-abs(-d1-d2),0.0);
return max(-d1, d2) + h*h*0.25/k;
}
//掏空
float opOnion(float sdf, in float thickness )
{
return abs(sdf)-thickness;
}
//并集
vec2 opU( vec2 d1, vec2 d2 )
{
return (d1.x<d2.x) ? 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));
}
//甜圈圈
float sdTorus( vec3 p, vec2 t )
{
vec2 q = vec2(length(p.xz)-t.x,p.y);
return length(q)-t.y;
}
vec3 opTwistY( in vec3 p ,float k)
{
float c = cos(k*p.y);
float s = sin(k*p.y);
mat2 m = mat2(c,-s,s,c);
p.xz *=m;
return p;
}
vec3 opCheapBend(in vec3 p ,float k)
{
float c = cos(k*p.x);
float s = sin(k*p.x);
mat2 m = mat2(c,-s,s,c);
vec3 q = vec3(m*p.xy,p.z);
return q;
}
vec2 twistBox (vec4 pos){
//繪制原立方體
pos.x += 2.5;
float box1 = sdBox(pos.xyz,vec3(0.4,1.2,0.4),0.06);//方塊
vec2 mBox1 = vec2(box1,1.0);
//繪制扭曲立方體
pos.x -= 5.0;
pos.xyz=opTwistY(pos.xyz,3.0);//扭曲
float box2 = sdBox(pos.xyz,vec3(0.4,1.2,0.4),0.06);//方塊
vec2 mBox2 = vec2(box2,1.0);
vec2 res = opU(mBox1,mBox2);
return res;
}
vec2 onionCylinder (vec4 pos){
//繪制圓柱
vec4 pos1 = pos;
pos1.z -= 6.0;
float cylinder1 = sdCylinder(pos1.xyz,0.4,0.6);
vec2 mCylinder1 = vec2(cylinder1,1.0);
//繪制裁剪圓柱
vec4 pos2 = pos;
pos2.z -= 2.5;
float cylinder2 = sdCylinder(pos2.xyz,0.4,0.6);
cylinder2 = max( cylinder2, pos2.y );//裁剪
vec2 mCylinder2 = vec2(cylinder2,1.0);
//繪制裁剪掏空圓柱
vec4 pos3 = pos;
float cylinder3 = sdCylinder(pos3.xyz,0.4,0.6);
cylinder3 = opOnion(cylinder3,0.01);//掏空
cylinder3 = max( cylinder3, pos3.y );//裁剪
vec2 mCylinder3 = vec2(cylinder3,1.0);
vec2 res = opU(mCylinder1,mCylinder2);
res = opU(res,mCylinder3);
return res;
}
vec2 onionSphere (vec4 pos){
//繪制球體
vec4 pos1 = pos;
pos1.z -= 6.0;
float sphere1 = sdSphere(pos1.xyz,0.6);
vec2 mSphere1 = vec2(sphere1,2.0);
//繪制裁剪球體
vec4 pos2 = pos;
pos2.z -= 2.5;
float sphere2 = sdSphere(pos2.xyz,0.6);
sphere2 = max( sphere2, pos2.y );//裁剪
vec2 mSphere2 = vec2(sphere2,2.0);
//繪制裁剪掏空球體
vec4 pos3 = pos;
float sphere3 = sdSphere(pos3.xyz,0.6);
sphere3 = opOnion(sphere3,0.01);//掏空
sphere3 = max( sphere3, pos3.y );//裁剪
vec2 mSphere3 = vec2(sphere3,2.0);
vec2 res = opU(mSphere1,mSphere2);
res = opU(res,mSphere3);
return res;
}
vec2 onionTorus (vec4 pos){
//繪制甜圈圈
vec4 pos1 = pos;
pos1.z -= 6.0;
float torus1 = sdTorus(pos1.xyz,vec2(0.6,0.2));
vec2 mTorus1 = vec2(torus1,3.0);
//繪制裁剪甜圈圈
vec4 pos2 = pos;
pos2.z -= 2.5;
float torus2 = sdTorus(pos2.xyz,vec2(0.6,0.2));
torus2 = max( torus2, pos2.y );//裁剪
vec2 mTorus2 = vec2(torus2,3.0);
//繪制裁剪掏空甜圈圈
vec4 pos3 = pos;
float torus3 = sdTorus(pos3.xyz,vec2(0.6,0.2));
torus3 = opOnion(torus3,0.01);//掏空
torus3 = max( torus3, pos3.y );//裁剪
vec2 mTorus3 = vec2(torus3,3.0);
vec2 res = opU(mTorus1,mTorus2);
res = opU(res,mTorus3);
return res;
}
vec2 cheapBendBox (vec4 pos){
//繪制原立方體面
pos.x += 2.5;
float box1 = sdBox(pos.xyz,vec3(1.0,0.2,0.6),0.06);//方塊
vec2 mBox1 = vec2(box1,1.0);
//繪制彎曲立方體面
pos.x -= 5.0;
pos.xyz=opCheapBend(pos.xyz,0.5);//彎曲
float box2 = sdBox(pos.xyz,vec3(1.0,0.2,0.6),0.06);//方塊
vec2 mBox2 = vec2(box2,1.0);
vec2 res = opU(mBox1,mBox2);
return res;
}
vec2 getDistandMaterial(vec3 p){
vec2 res = vec2(p.y,0.0);//地面
vec3 pos = p-vec3(0,2,5);//確定模型的中心
vec4 oriPos = vec4(pos,1.0);//轉為其次坐標
//扭曲
vec2 twistBox = twistBox(oriPos);
//彎曲
vec2 cheapBendBox = cheapBendBox(oriPos);
//裁剪掏空
vec2 onionSphere = onionSphere(oriPos);
vec2 onionCylinder = onionCylinder(oriPos+vec4(2.0,0.0,0.0,1.0));
vec2 onionTorus = onionTorus(oriPos-vec4(2.0,0.0,0.0,1.0));
//res = opU(res,twistBox);//扭曲立方體
//res = opU(res,cheapBendBox); //彎曲立方體
res = opU(res,onionSphere);//裁剪掏空球體
res = opU(res,onionCylinder);//裁剪掏空圓柱
res = opU(res,onionTorus);//裁剪掏空甜圈圈
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~1
vec3 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,4.0,0.0);//視點
vec3 rd = normalize(vec3(st.x,st.y,1.0));//視線方向
vec2 res = rayMarch(ro,rd);//反向光線追蹤求交點距離與材質ID
float d = res.x;//物體與視點的距離
float m = res.y;//材質ID
vec3 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.pageX
uniforms.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>
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/390485.html
標籤:其他
上一篇:Python文章合集 | (入門到實戰、游戲、Turtle、案例等)
下一篇:關于網路游戲的影響(騰訊游戲)
