Metal入门,使用Metal实现灯光效果和噪点效果
灯光效果
首先了解一下X,Y,Z在3D着色器中的概念:
x轴:水平方向(从左到右)
y轴:垂直方向(从下到上)
z轴:垂直于屏幕的方向(从屏幕指向观察者)
然后我们是使用的内核进行渲染
kernel void compute(texture2d<float, access::write> output [[texture(0)]],constant float &timer [[buffer(1)]],constant float2 &clickPos [[buffer(2)]],uint2 gid [[thread_position_in_grid]])
{// gid 可以理解为像素坐标int width = output.get_width();int height = output.get_height();float2 uv = float2(gid) / float2(width, height);// 调整UV坐标以考虑屏幕宽高比float aspect = float(width) / float(height);float2 centered = uv - 0.5;// 如果宽度大于高度,调整x坐标if (aspect > 1.0) {centered.x *= aspect;} // 如果高度大于宽度,调整y坐标else {centered.y /= aspect;}float radius = 0.4; // 稍微缩小半径确保在所有设备上都可见float distance = length(centered);float z = sqrt(radius * radius - centered.x * centered.x - centered.y * centered.y);//法线向量float3 normal = normalize(float3(centered.x, centered.y, z));//光源float3 source = normalize(float3(cos(timer), sin(timer), 0.5));float light = dot(normal,source);output.write(distance <= radius ? float4(float3(light),1) : float4(0),gid);
}
- 一开始的操作都比较基础,获取长宽,然后计算uv坐标
- 通过屏幕宽高比,对uv坐标进行调整
- 计算每个点的z坐标(高度)值,其用于构建法线向量
- 法线向量决定了表面如何反射光线
- 光线反射(通过点积计算)决定了表面的明暗
光源方向向量的几何理解
在3D图形中,我们使用方向向量来表示光源的照射方向。在代码中:
float3 source = normalize(float3(cos(timer), sin(timer), 0.5));
这个向量有三个分量:
- x = cos(timer):水平方向
- y = sin(timer):垂直方向
- z = 0.5:深度方向(从屏幕向内)
normalize
函数确保这个向量的长度为1,但保持其方向不变。
为什么z值影响照射角度?
想象一个单位球体在坐标原点,光源方向向量从球心指向外部:
-
z值与方向角度的关系:
- 当z=0时,向量完全在xy平面内(水平面),与z轴垂直(90°角)
- 当z增大时,向量逐渐倾向于z轴方向,与z轴的夹角减小
- 当z=1且x=y=0时,向量完全沿z轴方向(0°角)
-
具体数值的影响:
- z=0.5时:向量与z轴的夹角约为60°,更多地倾向于水平方向
- z=1时:向量与z轴的夹角约为45°,在水平和垂直方向上均衡
- z=2时:向量与z轴的夹角约为27°,更多地倾向于垂直方向
通过归一化的向量值计算
当x=cos(0)=1, y=sin(0)=0时的三种情况:
-
z=0.5:
- 未归一化的向量: (1, 0, 0.5)
- 向量长度: √(1² + 0² + 0.5²) = √1.25 ≈ 1.118
- 归一化后: (1/1.118, 0, 0.5/1.118) ≈ (0.894, 0, 0.447)
- 这个向量与z轴的夹角: arccos(0.447) ≈ 63.4°
-
z=1:
- 未归一化的向量: (1, 0, 1)
- 向量长度: √(1² + 0² + 1²) = √2 ≈ 1.414
- 归一化后: (1/1.414, 0, 1/1.414) ≈ (0.707, 0, 0.707)
- 这个向量与z轴的夹角: arccos(0.707) ≈ 45°
-
z=2:
- 未归一化的向量: (1, 0, 2)
- 向量长度: √(1² + 0² + 2²) = √5 ≈ 2.236
- 归一化后: (1/2.236, 0, 2/2.236) ≈ (0.447, 0, 0.894)
- 这个向量与z轴的夹角: arccos(0.894) ≈ 26.6°
对光照效果的影响
在着色器中,光照强度通过点积计算:
float light = dot(normal, source);
-
侧面照射(z较小):
- 当z=0.5时,光源更多地从侧面照射
- 球体边缘的法线主要在xy平面内,与这种光源方向更容易形成较大的点积
- 结果:球体边缘在光源方向上的部分会很亮,对面的部分很暗,形成强烈的侧面光照效果
-
正面照射(z较大):
- 当z=2时,光源更多地从正面(z轴方向)照射
- 球体中心区域的法线主要沿z轴方向,与这种光源方向形成较大的点积
- 结果:球体中心区域较亮,边缘普遍较暗,形成聚光灯效果
简言之,z值决定了光源"高度"的感觉 - z值小时光源像是在球体侧面旋转,z值大时光源像是从球体上方照射。这直接影响了球体表面的明暗分布,塑造了不同的3D视觉效果。
噪点效果
float random(float2 p)
{return fract(sin(dot(p, float2(15.79, 81.93)) * 45678.9123));
}float noise(float2 p)
{float2 i = floor(p);float2 f = fract(p);f = f * f * (3.0 - 2.0 * f);//可以消除随机噪声的锯齿状边缘,产生自然过渡的纹理效果,生成连续平滑噪声float bottom = mix(random(i + float2(0)), random(i + float2(1.0, 0.0)), f.x);float top = mix(random(i + float2(0.0, 1.0)), random(i + float2(1)), f.x);float t = mix(bottom, top, f.y);return t;
}float fbm(float2 uv)
{float sum = 0;float amp = 0.7;for(int i = 0; i < 4; ++i){sum += noise(uv) * amp;uv += uv * 1.2;amp *= 0.4;}return sum;
}kernel void compute(texture2d<float, access::write> output [[texture(0)]],constant float &timer [[buffer(1)]],constant float2 &clickPos [[buffer(2)]],uint2 gid [[thread_position_in_grid]])
{int width = output.get_width();int height = output.get_height();float2 uv = float2(gid) / float2(width, height);// 调整UV坐标以考虑屏幕宽高比float aspect = float(width) / float(height);float2 centered = uv - 0.5;// 如果宽度大于高度,调整x坐标if (aspect > 1.0) {centered.x *= aspect;} // 如果高度大于宽度,调整y坐标else {centered.y /= aspect;}float radius = 0.4; // 稍微缩小半径确保在所有设备上都可见float distance = length(centered);// 通过将坐标沿x轴随时间移动(timer*0.2)创建动画效果
// 使用fmod函数确保坐标值不会超出纹理范围
// 相当于让噪声图案向右缓慢移动,速度由0.2系数控制
// 只在x方向移动(第二个分量为0)centered = fmod(centered + float2(timer*0.2,0), float2(width,height));// 对处理后的坐标应用fbm(分形布朗运动)函数生成噪声值
// 乘以3是缩放因子,让噪声纹理更细致(频率更高)float t = fbm(centered * 3);output.write(distance <= radius ? float4(float3(t),1) : float4(0),gid);
}