如何在fragment shader中正确实现旋转、缩放等坐标变换
片元着色器仿射变换实现解答
问题原因
- 坐标映射逻辑颠倒:片元着色器中的
uv是当前屏幕像素对应的纹理采样坐标,要实现纹理放大,需要缩小uv的取值范围,让更大的屏幕区域对应到更小的纹理区间,而不是把uv放大。 fract()函数的作用是提取数值的小数部分,你将01范围的uv乘2后得到02的取值,取小数后00.5和0.51的uv都会映射到0~1的纹理区间,x、y两个方向各重复一次,自然会生成4个重复的缩小纹理块。
实现方案
完全可以在片元着色器中实现缩放、旋转等仿射变换,核心逻辑是对uv做反向变换,以下是具体实现示例:
2倍放大实现
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // 归一化uv并偏移到以画布中心为原点的-0.5~0.5区间 vec2 uv = fragCoord / iResolution.xy - 0.5; // uv缩小2倍,对应纹理放大2倍 uv /= 2.0; // 偏移回0~1的纹理采样区间 uv += 0.5; // 超出纹理范围的区域填充黑色,也可以自定义平铺逻辑 if(uv.x < 0. || uv.x > 1. || uv.y < 0. || uv.y > 1.) { fragColor = vec4(0.0); return; } fragColor = texture(iChannel0, uv); }
旋转+缩放组合实现
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord / iResolution.xy - 0.5; // 适配屏幕宽高比,避免变换后纹理拉伸 uv.x *= iResolution.x / iResolution.y; // 旋转逻辑,角度单位为弧度,这里示例为旋转30度 float rotateAngle = 3.1415926 / 6.0; float cosVal = cos(rotateAngle); float sinVal = sin(rotateAngle); uv = vec2(uv.x * cosVal - uv.y * sinVal, uv.x * sinVal + uv.y * cosVal); // 恢复宽高比后做缩放 uv.x /= iResolution.x / iResolution.y; uv /= 2.0; // 放大2倍 uv += 0.5; if(uv.x < 0. || uv.x > 1. || uv.y < 0. || uv.y > 1.) { fragColor = vec4(0.0); return; } fragColor = texture(iChannel0, uv); }
核心逻辑说明
和顶点着色器对顶点坐标做正向变换不同,片元着色器的变换是基于当前像素位置反向推导对应的纹理采样坐标,只要掌握这个逻辑,所有顶点着色器支持的平移、缩放、旋转、错切等仿射变换都可以在片元着色器中实现。
内容的提问来源于stack exchange,提问作者kalan
相关产品推荐
相关产品推荐

