关于顶点受cos(x)波上下偏移的平面表面法向量计算及代码中dx、dy分量推导的技术问询
咱们先把你的顶点着色器代码整理成更易读的格式,方便后续分析:
public: void SetTime(float time) { t = time; } typename BaseVertexShader::Output operator()(const Vertex& v) const { // calculate some triggy bois const auto angle = wrap_angle(v.pos.x * freq + t * wavelength); const auto cosx = std::cos(angle); const auto sinx = std::sin(angle); // sine wave amplitude from position w/ time variant phase animation const auto dz = amplitude * cosx; const auto pos = Vec4{ v.pos.x, v.pos.y, v.pos.z + dz, 1.0f }; // normal derived base on cross product of partial dx x dy auto n = Vec4{ -freq * amplitude * sinx, 0.0f, -1.0f, 0.0f }; n.Normalize(); return { pos * worldViewProj, n * worldView, pos * worldView, v.t }; } private: static constexpr float wavelength = PI; static constexpr float freq = 45.0f; static constexpr float amplitude = 0.02f; float t = 0.0f; };
接下来逐个解答你的技术疑问:
1. 如何计算dx和dy分量?
这里的dx和dy指的是参数化曲面的偏导数向量——我们把这个波纹平面看作是由局部坐标系的x和y(对应顶点的v.pos.x和v.pos.y)作为参数的曲面,时间t是动画控制参数。
先明确曲面的位置函数:
平面上任意一点的空间位置可以表示为:P(x, y, t) = (x, y, z0 + amplitude * cos(freq*x + wavelength*t), 1)
其中z0是平面的初始z坐标(对应代码里的v.pos.z)。
基于这个函数,两个偏导数向量的计算如下:
- dx分量(对x的偏导数):固定y和t,对x求导。x自身的导数为1,y的导数为0,z分量对x的导数是
-amplitude * freq * sin(freq*x + wavelength*t)(复合函数求导:cos(u)的导数是-sin(u)u’,这里u=freqx + ...,u’=freq)。最终dx向量为:dx = (1, 0, -amplitude*freq*sin(angle), 0)(angle就是代码中计算的角度) - dy分量(对y的偏导数):固定x和t,对y求导。x和z分量对y的导数都是0,y自身的导数为1,所以dy向量为:
dy = (0, 1, 0, 0)
2. 代码中法向量n的x分量为-freq * amplitude * sinx,该值是如何通过dx与dy的叉乘推导而来的?
参数曲面的法向量由两个偏导数向量的叉乘得到,不过要注意叉乘的顺序和坐标系方向(你的场景采用右手坐标系)。
先回忆右手坐标系下的叉乘公式:对于向量a=(a_x,a_y,a_z)和b=(b_x,b_y,b_z),叉乘a × b的结果为:(a_y*b_z - a_z*b_y, a_z*b_x - a_x*b_z, a_x*b_y - a_y*b_x)
如果我们取dy × dx(而非dx × dy)来计算叉乘,代入dx=(1,0,-Afreqsinθ)、dy=(0,1,0)(这里用θ代替angle,A代替amplitude):
- x分量:
1*0 - (-A*freq*sinθ)*1 = A*freq*sinθ?不对,等下,叉乘顺序是dy×dx的话,应该把dy作为第一个向量,dx作为第二个:
dy=(0,1,0),dx=(1,0,-Afreqsinθ)
x分量:1*(-A*freq*sinθ) - 0*0 = -A*freq*sinθ
y分量:0*1 - 0*(-A*freq*sinθ) = 0
z分量:0*0 - 1*1 = -1
这个结果正好和代码里的初始法向量(-freq * amplitude * sinx, 0.0f, -1.0f)完全一致。
之所以选择dy × dx的顺序,是为了适配你的坐标系设定:平面局部z轴向下、世界坐标系z轴指向屏幕内部,这个叉乘顺序能得到符合光照计算预期的法向量方向(正确区分正面/背面)。最后代码对向量做归一化,得到单位法向量用于后续渲染。
内容的提问来源于stack exchange,提问作者Simon Balfe

