shader编程-二维空间中使用矩阵实现物体的旋转、缩放、平移变换
- 1. 变换前物体的绘制
- 2. 物体旋转的实现
- 3. 物体缩放的实现
- 4. 物体平移的实现
- 5. 平移与缩放另外一种实现方式
- 6. 所有示例代码
1. 变换前物体的绘制
在进行变换操前先绘制出要操作的物体,我们就简单的绘制一个矩形来代表,利用之前的绘制矩形的代码稍作修改,之前用的是step函数,现在调整为smoothstep函数,因为旋转时使用step函数绘制的矩形边缘会出现锯齿,具体如下
float box(vec3 st){
float right = 0.15;float top = 0.1;float blur = 0.002;//边缘模糊系数//通过右上角绘制原点对称的四边形vec2 bl = 1.0-smoothstep(vec2(right,top)-blur,vec2(right,top)+blur,abs(st.xy));float pct = bl.x * bl.y;return pct;
}
绘制的结果如下,一个小矩形出现在绘制区域中央
2. 物体旋转的实现
我们使用矩阵来实现物体的旋转,先来看看齐次坐标下的旋转矩阵长什么样
上面的矩阵用于二维平面下对物体进行旋转变换,旋转矩阵的相关知识,请参照这篇文章图形学中的基本变换
接下来看看获取旋转矩阵函数的代码
mat3 rotate2d(float _angle){
float angle = radians(_angle);//角度转为弧度return mat3(cos(angle),-sin(angle),0.0,sin(angle),cos(angle),0.0,0.0,0.0,1.0);}
调用的时候需要注意一下,齐次坐标下返回的旋转矩阵是一个三维矩阵,所以先将屏幕坐标转为三维坐标,以匹配旋转矩阵,与旋转矩阵相乘后相当于将整个屏幕坐标旋转了,接着绘图,绘出的图就带旋转效果了,具体参照如下代码
void main( void ) {
//窗口坐标调整为[-1,1],坐标原点在屏幕中心vec2 _st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;vec3 line_color = vec3(1.0,1.0,0.0);vec3 color = vec3(0.6);//背景色float pct = 0.0;//将屏幕坐标转为三维坐标,使之在和矩阵相乘时相匹配vec3 st = vec3(_st,1.0);//使用旋转矩阵使矩形顺时针旋转10度st *= rotate2d(10.0);pct = box(st);color = mix(color,line_color,pct);gl_FragColor = vec4(color, 1);
}
看看运行结果,矩形在原来的基础上顺时针旋转了10度
如果想逆时针旋转10度,只需要在参数前添加个负号
st *= rotate2d(-10.0);
如果你想让它不停旋转,需要用到u_time变量,之所以乘以30.0,是为了让物体旋转的更快一些
st *= rotate2d(u_time*30.0);
3. 物体缩放的实现
同样先来看看齐次坐标下缩放矩阵的模样
该矩阵相关知识还是参照这篇文章图形学中的基本变换
接着我们编写返回缩放矩阵的函数,如下
mat3 scale2d(vec2 scale){
//缩放矩阵作用于坐标系,所以放大和缩小刚好相反,为了使用习惯,缩放参数取倒数return mat3(1.0/scale.x,0.0,0.0,0.0,1.0/scale.y,0.0,0.0,0.0,1.0);
}
绘制物体前,将屏幕坐标与该矩阵相乘
st *= scale2d(vec2(2.0,4.0));
执行上面的代码物体x轴方向放大2倍,y轴方向放大4倍,结果如下
4. 物体平移的实现
下图是齐次坐标下平移矩阵
该矩阵相关知识还是参照这篇文章图形学中的基本变换
开始编写返回平移矩阵的函数,如下
mat3 translation2d(vec2 translate){
//平移矩阵作用于坐标系,所以平移的方向相反,为了使用习惯,缩放参数取负return mat3(1.0,0.0,-translate.x,0.0,1.0,-translate.y,0.0,0.0,1.0);
}
绘图前屏幕坐标与平移坐标相乘
st *= translation2d(vec2(0.2,0.4));
执行结果如下图,与原物体相比,在x轴方向平移0.2,y轴方向平移0.4
如果将代码调整成下面的代码,则物体沿着y轴像弹簧一样做上下往复运动
st *= translation2d(vec2(0.0,sin(u_time)*0.5));
如果替换为下面代码,则物体会沿着半径为0.5的圆环上做环形运动
st *= translation2d(vec2(cos(u_time)*0.5,sin(u_time)*0.5));
至此,如果你已经掌握物体的基本变换就,其实各种变换是可以叠加的,只需要不断地乘上变换矩阵,例如让物体既自己旋转,又通过平移矩阵实现环形运动,可使用以下代码
st *= rotate2d(u_time*30.0);
st *= translation2d(vec2(cos(u_time)*0.5,sin(u_time)*0.5));
5. 平移与缩放另外一种实现方式
平移与缩放可以通过另外一种不使用矩阵的方式实现,原理就是改变屏幕坐标,物体则以相反的变换变化,例如将屏幕坐标乘以2,相当于屏幕坐标放大两倍,而绘制物体时大小不变,则物体看起来像缩小为原来的一半,如果你对屏幕坐标的x轴减0.5个单位,绘制的结果是物体向右移动0.5个单位
一些简单的平移与缩放示例代码
//非矩阵的放大缩小方式
//st *=3.0;//屏幕坐标放大3倍,相当于物体缩小3倍
//st /=3.0;//屏幕坐标缩小3倍,相当于物体放大3倍//非矩阵的平移方式
//st.x -=0.2;//物体向右移0.2个单位
//st -=0.4;//物体向右向上移0.4个单位
//st.x +=0.3;//物体向左移0.3个单位
6. 所有示例代码
<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;#define sat(x) clamp(x, 0.0, 1.0)#endifuniform float u_time;uniform vec2 u_mouse;uniform vec2 u_resolution;mat3 rotate2d(float _angle){float angle = radians(_angle);//角度转为弧度return mat3(cos(angle),-sin(angle),0.0,sin(angle),cos(angle),0.0,0.0,0.0,1.0);}mat3 scale2d(vec2 scale){//缩放矩阵作用于坐标系,所以放大和缩小刚好相反,为了使用习惯,缩放参数取倒数return mat3(1.0/scale.x,0.0,0.0,0.0,1.0/scale.y,0.0,0.0,0.0,1.0);}mat3 translation2d(vec2 translate){//平移矩阵作用于坐标系,所以平移的方向相反,为了使用习惯,缩放参数取负return mat3(1.0,0.0,-translate.x,0.0,1.0,-translate.y,0.0,0.0,1.0);}float box(vec3 st){float right = 0.15;float top = 0.1;float blur = 0.002;//边缘模糊系数//通过右上角绘制原点对称的四边形vec2 bl = 1.0-smoothstep(vec2(right,top)-blur,vec2(right,top)+blur,abs(st.xy));float pct = bl.x * bl.y;return pct;}void main( void ) {//窗口坐标调整为[-1,1],坐标原点在屏幕中心vec2 _st = (gl_FragCoord.xy * 2. - u_resolution) / u_resolution.y;//窗口坐标调整为[0,1],坐标原点在屏幕左下角//vec2 st = gl_FragCoord.xy/u_resolution;vec3 line_color = vec3(1.0,1.0,0.0);vec3 color = vec3(0.6);//背景色float pct = 0.0;//将屏幕坐标转为三维坐标,使之在和矩阵相乘时相匹配vec3 st = vec3(_st,1.0);//使用旋转矩阵使矩形顺时针旋转10度//st *= rotate2d(10.0);//st *= rotate2d(u_time*30.0);//缩放//st *= scale2d(vec2(2.0,4.0));//平移//st *= translation2d(vec2(0.2,0.4)); //st *= translation2d(vec2(0.0,sin(u_time)*0.5));//st *= translation2d(vec2(cos(u_time)*0.5,sin(u_time)*0.5));//非矩阵的放大缩小方式//st *=3.0;//屏幕坐标放大3倍,相当于物体缩小3倍//st /=3.0;//屏幕坐标缩小3倍,相当于物体放大3倍//非矩阵的平移方式//st.x -=0.2;//物体向右移0.2个单位//st -=0.4;//物体向右向上移0.4个单位//st.x +=0.3;//物体向左移0.3个单位pct = box(st);color = mix(color,line_color,pct);gl_FragColor = vec4(color, 1);}`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>