美文网首页
《GPU GEMS》16.次表面散射的实时近似

《GPU GEMS》16.次表面散射的实时近似

作者: Orini | 来源:发表于2017-08-12 00:24 被阅读0次

    次表面散射的通俗理解,即对于某些半透明物体,如蜡烛,皮肤,牛奶等。光线进入物体,在物体内部发生散射现象,最终从入射点不同的位置射出。
    这使得对于物体表面的每个像素着色来讲,不止受到该点入射光的影响,还会收到表面其余散射出来的光线的影响。这让次表面散射与漫反射区别开来。

    1.简单的散射近似
    简单的散射近似使用了环绕光照(wrap lighting)
    Lambert(理想的漫反射模型)在光线与表面法线方向垂直时,值为0
    环绕光照修改Lambert,使得Lambert的值域在缩小,a=环绕光照值
    效果:使得物体原本应该黑暗的地方拥有一定的光照,即相比Lambert模型,阴影部分后退。
    环绕光照函数
    y=(x+wrap)/(1+wrap)=1-(1-x)/(1+wrap),x<=1.0

    常规漫反射计算:
    float diffuse=max(0,dot(L,N));
    环绕光照漫反射计算:
    float wrap_diffuse=max(0,(dot(L,N)+wrap)/(1+wrap));
    L 光照方向
    N 表面法线

    颜色漂移

    Paste_Image.png

    根据环绕光照公式,生成皮肤着色查找表,然后根据皮肤查找表进行着色。
    思路:提高暗部亮度,阴影过渡处颜色替换。
    使用查找表是为了加速计算。

    // Generate 2D lookup table for skin shading
    //input:p(x,y)
    //output:color(r,g,b,a)
    float4 GenerateSkinLUT(float2 P : POSITION) : COLOR
    {
      //wrap环绕系数
      float wrap = 0.2;
      //散射宽度
      float scatterWidth = 0.3;
      //散射颜色
      float4 scatterColor = float4(0.15, 0.0, 0.0, 1.0);
      //光泽度
      float shininess = 40.0;
      //NdotL:表面法线Normal与光照方向LightDir的点积
      //NdotH:表面法线Normal与半角向量HalfVec的点积
      float NdotL = P.x * 2 - 1;  // remap from [0, 1] to [-1, 1]
      float NdotH = P.y * 2 - 1;
      //进行环绕光照方程计算后的值
      float NdotL_wrap = (NdotL + wrap) / (1 + wrap); // wrap lighting
      //使用环绕光照的漫反射
       float diffuse = max(NdotL_wrap, 0.0);
    
      // add color tint at transition from light to dark
      //smoothstep(start,end,t);
      //t<start,return 0
      //t>end,return 1
       float scatter = smoothstep(0.0, scatterWidth, NdotL_wrap) *
                        smoothstep(scatterWidth * 2.0, scatterWidth,
                                   NdotL_wrap);
      //计算高光Blinn-Phong模型
      float specular = pow(NdotH, shininess);
      if (NdotL_wrap <= 0) specular = 0;
      float4 C;
      C.rgb = diffuse + scatter * scatterColor;
      C.a = specular;
      return C;
    }
    
    // Shade skin using lookup table
    
       half3 ShadeSkin(sampler2D skinLUT,
                    half3 N,
                    half3 L,
                    half3 H,
                    half3 diffuseColor,
                    half3 specularColor) : COLOR
    {
      half2 s;
      s.x = dot(N, L);
      s.y = dot(N, H);
      //s*0.5+0.5,将点积的值域从[-1,1]重新映射到[0,1]
      half4 light = tex2D(skinLUT, s * 0.5 + 0.5);
      return diffuseColor * light.rgb + specularColor * light.a;
    }
    

    方法解析:
    第一个函数是生成2D查找表的方程,根据第二个函数的输入,最终应该是生成一张texture2D的贴图,贴图的每个像素点记录了我们需要的颜色信息。
    第二个函数即根据点积结果查找表中的颜色值,和漫反射颜色进行计算叠加‘。
    实际操作上应该是提前烘焙好皮肤2D查找表贴图,然后在shader里直接使用该贴图读取颜色值和diffuse做运算。
    理论很简单,甚至没有什么技术含量,效果根据上图来讲也比较一般。

    2.用深度映射模拟吸收
    比起上一个的改进是,这个技术模拟光在物体内传播的吸收过程,这使得我们需要计算光在物体中前进的距离,因为距离越长,被物体吸收的光线也就越多。表现就是,对于一个半透明的物体,光源位于物体背后时,我们在物体前方可以看到物体部分区域被照亮,照亮程度与厚度(光在物体内部传播的距离)有关。这个技术不考虑光进入物体内部产生的折射现象,同时只适用于凸面物体


    距离的计算思路是和阴影映射的方法相似。关于阴影映射参考:
    http://blog.csdn.net/xiaoge132/article/details/51458489
    以光源为视点进行烘焙(将摄像机的位置移动到光源位置进行烘焙),获得表面上的点与光源之间的距离,存储到一张texture中。 using standard projective texture mapping将图像投射到场景中。然后在渲染pass中,给需要渲染的点,从texture中我们获得它到光源的距离,同时获得他背后的点,即光的入射点的距离,两者距离相减即得到光在物体的传播距离。然后根据这个距离,按照自己预先设定的衰减查找表,进行着色。
    The Vertex Program for the Depth Pass
    //深度Pass程序
    //顶点着色器的输入结构体
    struct a2v {
      float4 pos    : POSITION;
      float3 normal : NORMAL;
    };
    //顶点着色器的输出,即片段着色器的输入
    struct v2f {
      float4 hpos : POSITION;
      float  dist : TEXCOORD0; // distance from light
    };
    //顶点着色器程序
    //输入:a2v结构体,模型视图投影矩阵,模型视图矩阵,grow值
    v2f main(a2v IN,
             uniform float4x4 modelViewProj,
             uniform float4x4 modelView,
             uniform float    grow)
    {
      v2f OUT;
      float4 P = IN.pos;
      //沿法线方向缩放顶点
      P.xyz += IN.normal * grow;  // scale vertex along normal
      //矩阵变换,返回一个顶点
      OUT.hpos = mul(modelViewProj, P);
      //距离计算,返回一个距离的浮点值
      OUT.dist = length(mul(modelView, IN.pos));
      return OUT;
    }
    
    The Fragment Program for the Depth Pass
    
    float4 main(float dist : TEX0) : COLOR
    {
      return dist;  // return distance
    }
    
    The Fragment Program Function for Calculating Penetration Depth Using Depth Map
    
    // Given a point in object space, lookup into depth textures
    
       // returns depth
    
       float trace(float3 P,
                uniform float4x4  lightTexMatrix, // to light texture space
                
       uniform float4x4  lightMatrix,    // to light space
                
       uniform sampler2D lightDepthTex,
                )
    {
      // transform point into light texture space
      
       float4 texCoord = mul(lightTexMatrix, float4(P, 1.0));
    
      // get distance from light at entry point
      
       float d_i = tex2Dproj(lightDepthTex, texCoord.xyw);
    
      // transform position to light space
      
       float4 Plight = mul(lightMatrix, float4(P, 1.0));
    
      // distance of this pixel from light (exit)
      
       float d_o = length(Plight);
    
      // calculate depth
      
       float s = d_o - d_i;
      return s;
    }
    

    技术缺陷是没有考虑到光的漫反射,光源在物体背后时会显示背面细节

    相关文章

      网友评论

          本文标题:《GPU GEMS》16.次表面散射的实时近似

          本文链接:https://www.haomeiwen.com/subject/zgokmttx.html