zoukankan      html  css  js  c++  java
  • Render Flow of Divinity II (part 1)

    Render Flow of Divinity II (part 1)

    作者:clayman
    仅供个人学习使用,转载请保留作者以及原文链接,勿用于任何商业用途。

        当爱好变成职业后,爱好就被毁了,这几年一直没有太多时间玩游戏,却迷恋上了把新游戏都 pix一遍的习惯... 和各种源码党相比,一直觉得通过 pix/perfHUD/PerfStudio来了解一个游戏/引擎更快,更实际,特别仅就渲染而言,有经验的程序员看到 pix数据,大概就知道应该怎么来写了。

        早就想写一系列关于各种游戏的 pix分析文章,今天终于忙里偷闲开个头,来分析一下 << Divinity II: Ego Draconis神界 2>> 的渲染流程。之所以分析这个游戏并没有特殊的原因,只是因为手头刚好有这游戏的 pix文件,如果时间允许,会有更多这系列的分析文章:)   猛击>>这里<<可以下载到文章所用的pix文件。本文假设你已经对pix有所了解,熟悉基本用法。

          Divinity II是 09 年的一个单机魔幻 rpg游戏,貌似是 Larian Studios自己开发的引擎,基于 DX9, light-pre pass 构架。Shader 管理应该是在 D3DEffect上做了一个轻量级的wrapper,可以看到 Effect函数和SetShaderConstant 混用的代码。基于 Effect框架的缺点就是太多冗余 render state设置。

              每帧渲染的开始,首先是生成一些动态贴图,可以看到 pix eid 3848之前的代码先渲染了一张树叶纹理。

              从EID 3876 -- 14896进入 pre-pass阶段,渲染normal 和depth buffer。 Render Target使用了A16B16G16R16 格式,后面分析代码可以看到 blue和alpha 通道保存了 view space的法线,R/G 通道是深度信息。后面通过分析shader,再来解释为什么图片看起来是这样子。

              来仔细分析 pre-pass中一个简单静态物体的渲染: EID4174处的路灯。首先在EID 4097和 4147处可以看到渲染使用了2张贴图,一张普通纹理,一张 normal map,注意, normal map使用了G/A 通道保存数据,使用 DTX5格式时常见的提高精度的技巧。

    以下为顶点格式,这里使用了short4n保存法线,其实顶点还有进一步压缩的空间,纹理坐标也可以用short2n来保存,或者分别放到norml和binormal的w通道,这样每个顶点可以节约8byte。

    struct
    {
       Float3 position
       Float2 texcoord
       Short4n  normal
       Short4n binormal
    }

     下面是vertex shader,我已经为每段asm代码做了详细注释:

     1 //   float4x4 g_Proj;
     2 //   float4x4 g_View;
     3 //   float4x4 g_World;
     4 //   float4x4 g_WorldView;
     5 
     6 // Registers:
     7 //   Name         Reg   Size
     8 //   ------------ ----- ----
     9 //   g_World      c0       4
    10 //   g_WorldView  c4       3
    11 //   g_View       c8       4
    12 //   g_Proj       c12      4
    13 
    14     vs_3_0
    15 
    16 def c7, 1, 0, 3.05185094e-005, 0        //note 1/32767 =  3.05185094e-005
    17 //input data format
    18 dcl_position v0
    19 dcl_normal v1
    20 dcl_texcoord v2
    21 dcl_binormal v3
    22 
    23 //vertex shader output
    24 dcl_position o0
    25 dcl_texcoord o1
    26 dcl_texcoord1 o2.xyz
    27 dcl_texcoord2 o3.xyz
    28 dcl_texcoord3 o4.xyz
    29 dcl_texcoord4 o5.xy
    30 
    31 //start vs..
    32 mul r0.xyz, c7.z, v1                //normal = vsIn.normal * (1 / 32767);
    33 dp3 r1.x, r0, c4                     //viewSpaceNormal = mul(normal,xyz, g_worldView)
    34 dp3 r1.y, r0, c5
    35 dp3 r1.z, r0, c6
    36 
    37 mul r0.xyz, c7.z, v3                //binormal = vsIn.binormal * (1 / 32767)
    38
    dp3 r2.x, r0, c4       //viewSpaceBinormal = mul(binormal.xyz * g_worldView 39 dp3 r2.y, r0, c5 40 dp3 r2.z, r0, c6 41 42 mul r0.xyz, r1.zxyw, r2.yzxw    //tangent = cross(normal,binormal 43 mad r0.xyz, r1.yzxw, r2.zxyw, -r0 44 45 mov o2.xyz, r1         //vsOut.normal = viewSpaceNormal 46 mov o3.xyz, r2         //vsOut.binormal = viewSpaceNormal 47 mov o4.xyz, -r0         //vsOut.tangent = -tangent 48 49 mad r0, v0.xyzx, c7.xxxy, c7.yyyx   //position.xyz = position.xyz * 1 + 0; 50 51 dp4 r1.x, r0, c0 //worldPos = mul(position,g_world); 52 dp4 r1.y, r0, c1 53 dp4 r1.z, r0, c2 54 dp4 r1.w, r0, c3 55 56 dp4 r0.x, r1, c8        //viewPos = mul(position,g_view); 57 dp4 r0.y, r1, c9 58 dp4 r0.z, r1, c10 59 dp4 r0.w, r1, c11 60 61 dp4 r1.x, r0, c12         //worldViewProjPos = mul(viewPos,g_proj) 62 dp4 r1.y, r0, c13 63 dp4 r1.z, r0, c14 64 dp4 r1.w, r0, c15 65 66 mov o0, r1           //vsOut.projPos = worldViewProjPos 67 mov o1, r1           //vsOut.screenPos = worldViewProjPos 68 mov o5.xy, v2            //vsOut.texcoord = vsIn.texcoord

       对很多初次看shader asm的人来说,其实并不难,有很多规律可以寻找,比如1,连续的2~4个dpx指令,一定是矢量和matrix相乘;2. mul和mad连续出现,并且变量以42,43行的模式出现,必然是cross。有了这些基础知识,上面的代码就非常容易看懂了,都是简单的坐标变换而已。不过有2个地方比较奇怪,第一,32和37行,short4n的格式在输入vs时会自动除以32767,这里又除了32768似乎有些多余;第二,49行是缩放和位移坐标的公式,但在这里出现似乎也是多余的。最后注意,vs分别输出了position,normal,binormal,tangent和texcoord到ps中,接下来看ps代码:

     1 //   sampler2D Base;
     2 //   sampler2D Normal;
     3 //   float3 g_AlphaTestFunction;
     4 //   float g_AlphaTestRef;
     5 //   float g_fDiffFarNearClip;
     6 //   float g_fNearClip;
     7 
     8 // Registers:
     9 //   Name                Reg   Size
    10 //   ------------------- ----- ----
    11 //   g_AlphaTestFunction c0       1
    12 //   g_AlphaTestRef      c1       1
    13 //   g_fNearClip         c2       1
    14 //   g_fDiffFarNearClip  c3       1
    15 //   Base                s0       1
    16 //   Normal              s1       1
    17 ps_3_0
    18 def c4, 2, -1, 1, 0
    19 def c5, 0.5, 3, 65535, 0
    20 dcl_texcoord v0.z               position.z
    21 dcl_texcoord1 v1.xyz          normal
    22 dcl_texcoord2 v2.xyz          binormal
    23 dcl_texcoord3 v3.xyz          tangent
    24 dcl_texcoord4 v4.xy           texcoord
    25 dcl_2d s0
    26 dcl_2d s1
    27 
    28  
    29 
    30 //sample and unpack normal map
    31 texld r0, v4, s1                                   //normalColor = tex2D(normalMap,vsOut.texcoord);
    32 mad r0.xy, r0.ywzw, c4.x, c4.y                     //normalMap.xy = normalColor.yw * 2 - 1;
    33 
    34 //tansform normal
    35 nrm r1.xyz, v2                                   //binormal = normalize(vsOut.binormal);
    36 mul r1.xyz, r0.x, r1                              //r1.xyz = normalMap.x * binormal;
    37 
    38 nrm r2.xyz, v3                                    //tangent = normalize(vsOut.tangent);
    39 mad r1.xyz, r2, r0.y, r1                          //r1.xyz = normalMap.y *  normalMap.y + r1.xyz;
    40 
    41 // z = sqrt( 1 – x*x – y*y)
    42 mad r0.y, r0.y, -r0.y, c4.z                       //r0.y = normalMap.y * (-normalMap.y) + 1;
    43 mad r0.x, r0.x, -r0.x, r0.y                       //ro.x = normalMap.x * (-normalMap.x) + normalMap.y;
    44 rsq r0.x, r0.x                                    //z = sqrt(r0.x);
    45 rcp r0.x, r0.x
    46 
    47 nrm r2.xyz, v1                                    //normal = normalize(vsOut.normal);
    48 mad r0.xyz, r2, r0.x, r1                          //r0.xyz = normal * z + r1.xyz;
    49 nrm r1.xyz, r0                                    //finalNormal = normalize(r0.xyz);
    50 
    51 // r0 = 1 / normalLength
    52 dp3 r0.x, r1, r1                                //r0.x = dot(finalNormal,finalNormal);
    53 rsq r0.x, r0.x                                  //r0.x = 1/sqrt(r0.x);
    54 
    55 mad r0.yz, r1.xxyw, r0.x, c4.z                   //r0.yz = finalNormal.xy * r0.x + 1
    56 mul r0.x, r1.z, r0.x                             //finalNormalZ = finalNormal.z * r0.x
    57 
    58 cmp r0.x, r0.x, c4.z, c4.w                       //if(r0.x >=0) 1; else 0
    59 mul_sat r0.yz, r0, c5.x                          //r0.yz = sat(r0.yz * 0.5f);
    60 mad oC0.z, r0.x, c5.y, r0.y                      //out.z = r0.x * 3 + r0.y;
    61 mov oC0.w, r0.z                                  //out.w = r0.z;
    62 
    63 add r0.x, c2.x, v0.z                           //r0.x = g_fNearClip + position.z;
    64 rcp r0.y, c3.x                                 //r0.y = 1 / g_fDiffFarNearClip;
    65 mul r0.x, r0.x, r0.y                           //r0.x = r0.x * r0.y;
    66 mul r0.x, r0.x, c5.z                           //r0.x = r0.x * 65535;
    67 
    68 //clamp(r0.x,0,65535);
    69 max r1.x, r0.x, c4.w                           //r1.x = max(r0.x,0);
    70 min r0.x, r1.x, c5.z                           //r0.x = min(r1.x,65536);
    71                                                                          
    72 frc r0.y, r0.x                                   //r0.y = frc(r0.x);
    73 add oC0.x, r0.x, -r0.y                          //out.x = r0.x - r0.y;
    74 mov oC0.y, r0.y                                  //out.y = r0.y;
    75 
    76 //alpha test
    77 texld r0, v4, s0                              //baseColor = tex2d(baseTex,vsOut.texcoord);
    78 add r0.x, r0.w, -c1.x                         //deltaAlpha = baseColor.a - g_AlphaTestRef;
    79 
    80 cmp r0.y, -r0_abs.x, c4.z, c4.w               //if(-abs(deltaAlpha) >= 0)r0.y =-1 else r0.y =0;
    81
    if_ne r0.y, -r0.y if(r0.y != 0) 82 mov r0.y, c4.y c0.y = -1 83 else 84 mov r0.y, c4.w 85 endif 86 87 88 mul r1, -r0.x, c0.x r1.x = -deltaAlpha * 0; 89 texkill r1 texkill(r1); 90 mul r1, r0.x, c0.y r1 = deltaAlpha * 1; 91 texkill r1 texkill(r1); 92 mul r0, r0.y, c0.z r0 = r0.y * 1; 93 texkill r0 texkill(r0);

         与vs相比ps就复杂很多了. 首先,这段代码里也有几个常见的asm规律rsq,rcp连续出现就等于sqrt函数,dp3,rsq,mul连续出现就等于normalize。其实上面代码的逻辑并不复杂,只是在编译器编译以后,代码顺序有些混乱。ps主要干了几件事情,首先,读取normal map,应为normal map只记录了2个分量的值,41~45行的代码计算了第三个分量,此外,56行以上的代码还把normal从tangent space变换到了view space。面代码中52,53,56中的代码都有些冗余,49行已经对normal进行过归一化,这几行代码相当于又计算了一次。58~61行把viewspace的法线x,y值从[-1,1]压缩到[0,1]范围,并且输出到z,w通道中。这里要特别注意60行的代码,当normal的z值为正,也就是法线指向屏幕里面时(这里用的右手系,背对观察者的面,确保后面的pass正确处理back face),会把法线x值设置为一个大于1的值,这里是程序3,再以后的shader中可以看到程序用这个值的大小来判断法线方向。
        63~74行的代码实现了顶点深度信息的输出,这里g_fNearClip是near plane的距离,大约是0.3,g_fDiffFarNearClip是near/far plane之间的距离,大约是2999.7, 代码对深度进行了量化,并分别把整数和小数部分保存到x,y通道中,因为x是整数部分大部分值大于1,所以最上面的图中r通道几乎为白色,y通道由于是小数部分,对于连续的深度来说总是从0~1重复出现,因此g通道纹理看起来是渐变条纹状。

    --------------------------未完待续--------------------------------

    分析,截图和排版实在太费时间,下一部分怎么着也得等周末了....

  • 相关阅读:
    Github 中使用认证签名
    临时邮箱
    devexpress 中的chart(图表)根据窗口大小缩放
    提交特殊字符取值的处理 HttpUtility.UrlEncode
    git 中 add 操作
    Linux随笔
    Oracle复杂多条件排序
    jdk内置工具jstack查询有问题代码(具体到哪一行)
    2018最新Web前端经典面试试题及答案
    打包bat
  • 原文地址:https://www.cnblogs.com/clayman/p/2851958.html
Copyright © 2011-2022 走看看