shader编程-RayMarching三维场景下模型基本变换旋转、缩放、平移(WebGL-Shader开发基础09)

1. demo概述

在之前的一篇文章中讲述了,二维空间中物体的基本变换,即旋转、缩放、平移,现在学习一下三维空间下的基本变换,思路与原理是一样的,只不过是由二维提升为三维, 关于三维场景下的基本变换,请参照这篇文章图形学中的基本变换中的三维图形的基本变换部分

2. 实现过程

2.1 模型的旋转操作

分别绕X轴旋转,Y轴旋转,Z轴旋转的矩阵如下

//绕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
            );
} 

调用过程
我们在计算交点距离和材质的函数中,完成变换过程,首先绘制了一个用来对比的box0,然后设置一个变换方块自己的屏幕坐标,对这个坐标左乘对应的变换矩阵,之后拿变换后的屏幕坐标绘制想要绘制的方块即可,具体如下

vec2 getDistandMaterial(vec3 p){

  float plane = p.y;//地面

  vec2 res = vec2(plane,0.0);

  //未变换前的立方体
  vec2 box0 = vec2(sdBox(p-vec3(-1.5,2,5),vec3(0.6),0.06),1.0);//原方块,材质ID为1.0

  vec3 pos = p-vec3(1.5,2,5);//确定模型的中心
  vec4 boxPos = vec4(pos,1.0);//转为其次坐标
  
  boxPos*=rotX(u_time);
  boxPos*=rotY(u_time);
  boxPos*=rotZ(u_time);
  vec2 box = vec2(sdBox(boxPos.xyz,vec3(0.6),0.06),2.0);//变换方块,材质ID为2.0

  res = opU(res,box0);
  res = opU(res,box);

  return res;
}

执行结果如下,是一张静态图,左侧蓝色方块为为变换的方块,右侧的橙色方块是经过变换的方块,实际运行结果是橙色方块不断的绕三轴旋转
在这里插入图片描述

2.2 模型的缩放操作

获取缩放矩阵的函数

//缩放矩阵
mat4 scaled(vec3 scale){
  //缩放矩阵作用于坐标系,所以放大和缩小刚好相反,为了使用习惯,缩放参数取倒数
  return mat4(1.0/scale.x,0.0,0.0,0.0,
            0.0,1.0/scale.y,0.0,0.0,
            0.0,0.0,1.0/scale.z,0.0,
            0.0,0.0,0.0,1.0
            );
}

调用过程

vec3 scale = vec3(1.2,1.5,2.0);//确定缩放倍数
boxPos *= scaled(scale);
vec2 box = vec2(sdBox(boxPos.xyz,vec3(0.6),0.06),2.0);//变换方块,材质ID为2.0

执行结果
在这里插入图片描述
补充
三维空间中的缩放与二维空间中的缩放一样,也可给坐标相乘或相除一个数值来实现模型的缩放
例如如下代码会使模型在x轴方向缩小两倍,在y轴方向放大1.5倍

boxPos.x *= 2.0;//x轴方向缩小两倍
boxPos.y /= 1.5;//y轴方向放大1.5倍

2.3 模型的平移操作

平移矩阵实现

//平移矩阵
mat4 translation3d(vec3 translate){
  //平移矩阵作用于坐标系,所以平移的方向相反,为了使用习惯,缩放参数取负
  return mat4(1.0,0.0,0.0,-translate.x,
            0.0,1.0,0.0,-translate.y,
            0.0,0.0,1.0,-translate.z,
            0.0,0.0,0.0,1.0
            );
}

调用过程
平移的调用过程与前两种变换不太一样,为了作为参照,需要在变换前把执行平移变换方块的坐标移动到参考方块坐标的位置,然后进行平移变换,具体如下

pos = p-vec3(-1.5,2,5);//平移前将橙色方块移到与蓝色方块重合位置
boxPos = vec4(pos,1.0);//转为其次坐标
vec3 trans = vec3(2.0,1.8,0.2);//确定沿xyz轴的平移距离
boxPos *= translation3d(trans);

vec2 box = vec2(sdBox(boxPos.xyz,vec3(0.6),0.06),2.0);//变换方块,材质ID为2.0

执行结果
在这里插入图片描述
补充
与二维空间中的平移一样,也可给坐标相加或相减一个数值来实现模型的平移
例如如下代码会使模型沿x轴右移2个单位,沿y轴上移1.8个单位

boxPos.x -= 2.0;//右移2个单位
boxPos.y -= 1.8;//上移1.8个单

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
                );
    } 

    //缩放矩阵
    mat4 scaled(vec3 scale){
      //缩放矩阵作用于坐标系,所以放大和缩小刚好相反,为了使用习惯,缩放参数取倒数
      return mat4(1.0/scale.x,0.0,0.0,0.0,
                0.0,1.0/scale.y,0.0,0.0,
                0.0,0.0,1.0/scale.z,0.0,
                0.0,0.0,0.0,1.0
                );
    }

    //平移矩阵
    mat4 translation3d(vec3 translate){
      //平移矩阵作用于坐标系,所以平移的方向相反,为了使用习惯,缩放参数取负
      return mat4(1.0,0.0,0.0,-translate.x,
                0.0,1.0,0.0,-translate.y,
                0.0,0.0,1.0,-translate.z,
                0.0,0.0,0.0,1.0
                );
    }

    vec2 opU( vec2 d1, vec2 d2 )
    {
      return (d1.x<d2.x) ? d1 : d2;
    }

    //立方体
    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;
    }

    vec2 getDistandMaterial(vec3 p){

      float plane = p.y;//地面

      vec2 res = vec2(plane,0.0);

      //未变换前的立方体
      vec2 box0 = vec2(sdBox(p-vec3(-1.5,2,5),vec3(0.6),0.06),1.0);//原方块,材质ID为1.0

      vec3 pos = p-vec3(1.5,2,5);//确定模型的中心
      vec4 boxPos = vec4(pos,1.0);//转为其次坐标

      //  boxPos*=rotX(u_time);
      //  boxPos*=rotY(u_time);
      //  boxPos*=rotZ(u_time);

      // vec3 scale = vec3(1.2,1.5,2.0);//确定缩放倍数
      // boxPos *= scaled(scale);



      pos = p-vec3(-1.5,2,5);//平移前将橙色方块移到与蓝色方块重合位置
      boxPos = vec4(pos,1.0);//转为其次坐标
      vec3 trans = vec3(2.0,1.8,0.2);//确定沿xyz轴的平移距离
      //boxPos *= translation3d(trans);


      //缩放的另一种方式
      boxPos.x *= 2.0;//x轴方向缩小两倍
      boxPos.y /= 1.5;//y轴方向放大1.5倍


      //平移的另一种方式
      boxPos.x -= 2.0;//右移2个单位
      boxPos.y -= 1.8;//上移1.8个单

      vec2 box = vec2(sdBox(boxPos.xyz,vec3(0.6),0.06),2.0);//变换方块,材质ID为2.0

      res = opU(res,box0);
      res = opU(res,box);

      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){
        //   material = -1.0;
        // }

        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.2;//环境光反射系数
      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,2.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, 1.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);
      }

      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>

© 版权声明
THE END
喜欢就支持一下吧
点赞385 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容