游戏渲染效果逆向分析
前篇
都是为了抄别人的效果, 使用 snapdragon profiler 等软件将别人游戏渲染某帧时截取出来, 通过 glsl 逆向还原成对应引擎的 shader 代码.
还原时得有个对比, 比如还原的是 unity 做的游戏, 可以先随便写点 shader, 打包后用 snapdragon 看看 unity shader 的函数最终在渲染时的是怎么样的 glsl, 然后对着要逆向的 游戏 反推回 unity 的 shader.
各种函数的逆向
normalize 归一化
1 2 3 4 5
|
u_xlat0.x = dot(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz); u_xlat0.x = inversesqrt(u_xlat0.x); u_xlat0.xyz = u_xlat0.xxx * _WorldSpaceLightPos0.xyz;
|
UnityObjectToWorldNormal
1 2 3 4 5 6 7 8
|
u_xlat1.x = dot(in_NORMAL0.xyz, unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_WorldToObjectArray[0].xyz); u_xlat1.y = dot(in_NORMAL0.xyz, unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_WorldToObjectArray[1].xyz); u_xlat1.z = dot(in_NORMAL0.xyz, unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_WorldToObjectArray[2].xyz); u_xlat0.x = dot(u_xlat1.xyz, u_xlat1.xyz); u_xlat0.x = inversesqrt(u_xlat0.x); u_xlat0.xyz = u_xlat0.xxx * u_xlat1.xyz;
|
UnityObjectToClipPos
1 2 3 4 5 6 7 8 9 10 11
|
u_xlat1 = in_POSITION0.yyyy * unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_ObjectToWorldArray[1]; u_xlat1 = unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_ObjectToWorldArray[0] * in_POSITION0.xxxx + u_xlat1; u_xlat1 = unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_ObjectToWorldArray[2] * in_POSITION0.zzzz + u_xlat1; u_xlat1 = u_xlat1 + unity_Builtins0Array[u_xlati0 / 8].hlslcc_mtx4x4unity_ObjectToWorldArray[3]; u_xlat2 = u_xlat1.yyyy * hlslcc_mtx4x4unity_MatrixVP[1]; u_xlat2 = hlslcc_mtx4x4unity_MatrixVP[0] * u_xlat1.xxxx + u_xlat2; u_xlat2 = hlslcc_mtx4x4unity_MatrixVP[2] * u_xlat1.zzzz + u_xlat2; u_xlat1 = hlslcc_mtx4x4unity_MatrixVP[3] * u_xlat1.wwww + u_xlat2; gl_Position = u_xlat1;
|
saturate
1 2 3 4 5 6
| #ifdef UNITY_ADRENO_ES3 u_xlat0.x = min(max(u_xlat0.x, 0.0), 1.0); #else u_xlat0.x = clamp(u_xlat0.x, 0.0, 1.0); #endif
|
大小比较
1 2 3 4 5 6
| #ifdef UNITY_ADRENO_ES3 u_xlatb1 = !!(u_xlat16_14<u_xlat16_0.x); #else u_xlatb1 = u_xlat16_14<u_xlat16_0.x; #endif
|
变量
精读
uniform mediump float -> half (中等精读)
输入输出
1 2 3 4 5 6 7 8 9 10
| in mediump vec4 in_POSITION0; in mediump vec3 in_NORMAL0; in mediump vec2 in_TEXCOORD0;
struct appdata_t { half4 vertex : POSITION; half3 normal : NORMAL; half2 texcoord : TEXCOORD0; };
|
输出
1 2 3 4 5 6 7 8 9
| out mediump vec2 vs_TEXCOORD0; out mediump vec3 vs_TEXCOORD1;
struct v2f { half4 vertex : SV_POSITION; half2 texcoord : TEXCOORD0; half3 tex02 : TEXCOORD1; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| uniform vec4 _WorldSpaceLightPos0;
uniform mediump vec4 _Tint; uniform mediump vec4 _ColorLight; uniform mediump vec4 _ColorUnlight; uniform mediump float _SplitThreshold;
_Tint("Tint", Color) = (1,1,1,1) _ColorLight("ColorLight", Color) = (1,1,0,1) _ColorUnlight("ColorUnlight", Color) = (0,0,0,1) _SplitThreshold("SplitThreshold", float) = 1 half4 _Tint; half4 _ColorLight; half4 _ColorUnlight; half _SplitThreshold;
|
实例01 - 雀魂
房间打牌示意图

测试 shader git 地址: git@gitee.com:yangxuan0261/tdmj_shader.git
主要看的是一下几部分的处理
- 向光/背光 部分
- 描边
- 阴影
1. 先找出牌牌体的渲染
直接修改 fs rgb 值为某个值, 就很容易找到牌体的渲染批次

可以看到 牌体的渲染 和 描边分开的
2. 描边

将牌体放大, shader 处理时剔除掉正面, 只渲染背面, 由于深度关系, 被正常牌体遮挡住, 描边效果就出来了.
而且所有这种牌体放大的模型也可以一个批次处理完成.

3. 阴影
