Unity3d ddx ddy 法线

最近摸到一个很神奇的API -> DDX, DDY (DX) 或者 FDX, FDY (Opengl)

ddxddy normal

三角面片这么明显的法线有木有

在Nvidia 文档说, ddx/ddy 是求一个近似偏导函数, 以对齐屏幕分辨率的值进行取值

看了好几遍,不懂,然后仔细一想一阶求导不就是斜率嘛, 那么斜率是什么呢,就是平面向量啊。
于是测试了 ddx(worldSpaceVertex.xyz) ddy(worldSpaceVertex.xyz) 可以求出 ddx[(x,x) – (x+1,x)] , ddy[ (y,y) – (y-y)] 即隔壁像素所在三角形和当前像素的斜率差,或者说当前像素到隔壁像素的斜率

x轴 斜率表示前进方向向量, 和 y 轴斜率表示右手方向向量,就可以求 三角面的法线
normalize(ddx) cross normalize(ddy) = normal;
normal = normalize(normal);

这个时候,如果直接输出fixed4(normal,1.0) 就是这样
ddxddy normal fragment

请不要无脑复制转载本文, 由 dreamfairy 原创, 转载请注明出处 本文地址 http://www.dreamfairy.cn/blog/2016/06/15/unity3d-ddx-ddy-normal/
ddx,ddy 只能在 Fragment Shader下使用
Shader如下

			v2f vert (appdata v)
			{
				v2f o;

				float4x4 modelMat = _Object2World;
				float4 worldPos = mul(modelMat, v.vertex);

				o.worldPos = worldPos;
				o.vertex = mul(UNITY_MATRIX_VP, worldPos);
				o.uv = v.uv;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				float3 dx = ddx(i.worldPos);
				float3 dy = ddy(i.worldPos);
				float3 normal = normalize(cross(dx, dy));

				normal = -normal * 0.5 + 0.5;
				return fixed4(normal,1.0);
			}

为什么三角形的面片感这么强呢, 因为计算斜率是通过面片计算的, 而传统的法线是通过共面顶点法线进行求平均得出,因此传统的转角处的法线会更加平滑。

有了法线,切线, 副切线,就可以构建切线空间

float3x3 tangleMat = float3x3(normalize(dx), normalize(-dy), normal);
float3 bumpNormal = tex2D(_NormalTex, i.uv);
bumpNormal = bumpNormal * 2 - 1;// range to -1~1 from 0~1
bumpNormal = mul(tangleMat, bumpNormal);
float diff = max(0, dot(i.worldLightDir, bumpNormal));
col *= col * diff;
return fixed4(col,1.0);

有了切线空间,就可以变换凹凸纹理咯。

ddxddy bump

ddx/ddy 还有一个 float2 的参数形式
这个一般是用来 求相邻像素的 uv 值差 ddx(i.uv.xy)。 因此当一个平面和平面平行, 所得值应该就是 1.0/纹理宽(高) 既uv递进值

那么ddx,ddy 可以用在哪里呢。
1. 计算mipmap level 计算当前纹理在屏幕分辨率下的大小是否小于某个mipmap等级的纹理大小,是的话就切换纹理
2. 视差贴图 从视野方向计算像素挤出的位置的uv值
3. and so on…

最后的最后
使用ddx, 和ddy 时, 发现 ddy 的数值在 编辑器模式 和 Game 模式下是相反的, 不知道为什么, 查询了官网也有人有发现这个问题
http://forum.unity3d.com/threads/cg-ddy-function-returning-negated-vector-in-game-view-macro-to-check.407630/

官网的答复是 ddy * _ProjectionParams.x; 然后我查询了 _ProjectionParams.x 文档说 -1 时渲染使用了反矩阵。 好吧,我也是基本没看懂, 不过这么使用后 编辑器和游戏模式的表现都一致了,但是也仅此而已,因为当你生成apk 后, 展示的效果却是 不处理之前的 编辑器里的效果 (我试了 nexus7, motorola me525 等上古神机,还有俺的 华为6p 都是如此)
之后查询 _ProjectionParams 的使用地方, 有提到 UNITY_HALF_TEXEL_OFFSET 下使用, 但是我无论dx, 还是opengl 设备都无法触发这个宏。

这个问题留待以后解决吧:)

最后的最后的最后, 我发现, 在创建切线空间时, x轴表示前进, y 轴表示右侧, z 轴表示朝上 是在 0,0 点以左上角为基准的 (DX), 在Opengl 应以 y 轴为前进,x轴表示右手, 于是最后测试渲染正确。 <- 可能是无解,暂时删除

发表评论

电子邮件地址不会被公开。 必填项已用*标注