zoukankan      html  css  js  c++  java
  • 在场景中添加光线——添加HLSL镜面高光

    问题

    你想使用自定义的HLSL effect在场景中添加镜面高光。镜面高光是位于反光位置的高亮度区域,如图6-11所示。

    解决方案

    下面的讨论将帮助你判断哪个像素具有高光分量。

    图6-11的左图显示了一条光线L,从光源指向三角形中的一个像素。在左图中还显示了 eye向量,从相机指向像素。如果L的反射向量与E相同,那么这个像素就有一个高光分量。

    image

    图6-11 使用靠近eye向量的光线方向检测像素

    你可以通过求L关于像素法线的镜像获取L的反射向量。如果镜像方向与eye向量夹角很小则这两个方向几乎是相同的。你可以通过点乘这两个向量检测两者的夹角(可参见教程 6-8)。

    如果角度为0,则这两个方向是相同的,你需要添加一个高光分量,这时点乘的结果为1。如果两个方向不同,则点乘结果小于1。

    注意:两个向量A和B的点乘结果等于(A的长度)*(B的长度)*(两者夹角的余弦)。如果A和B都已经进行了归一化,点乘结果会变为(两者夹角的余弦)。如果A和B的夹角为0,则余弦值为1。如果两者垂直,夹角为90度,余弦值为0,如图6-11的右图所示。如果两个向量方向相反,夹角为180度,余弦值为-1。 当反射的方向与eye向量的方向夹角小于90度时,点乘结果为正。

    你还不能立即使用这个值判断高光,因为这样做会在所有反射向量与eye向量的夹角小于90度的像素上添加高光,而你想在夹角小于10度时才添加高光。

    这可以通过对点乘结果进行一个高次幂实现。例如,将点乘结果进行12次方的操作,会使角度小于10度的情况下这个值才会大于0,如图6-11右下图所示。

    每个像素的运算结果是一个single值,表示高光强度。

    工作原理

    和以往一样,你需要首先设置World,View和Projection矩阵将3D位置转换到2D屏幕位置。因为这个教程用的是一个点光源,你还需指定它的位置。要计算eye向量,你需要知道相机的位置。你还需能够设置光照强度控制高光大小。因为光照强度可能大于1,因此需要缩小光照强度避免饱和(saturation)。

    注意:在大多数情况中,你需要缩小光源的强度。在多光源的情况中大多数像素的光照会饱和,浪费光照effect。

    float4x4 xWorld; 
    float4x4 xView;
    float4x4 xProjection; 
    float3 xLightPosition; 
    float3 xCameraPos; 
    float xAmbient; 
    float xSpecularPower; 
    float xLightStrength; 
    
    struct SLVertexToPixel 
    {
        float4 Position : POSITION; 
        float3 Normal : TEXCOORD0; 
        float3 LightDirection : TEXCOORD1; 
        float3 EyeDirection : TEXCOORD2; 
    }; 
    
    struct SLPixelToFrame 
    {
        float4 Color : COLOR0; 
    };

    vertex shader还计算了EyeDirection并进行插值。pixel shader仍然只输出每个像素的颜色。

    Vertex Shader

    vertex shader与前面的教程没有太大的不同。唯一一个新的东西就是eye向量在vertex shader中进行计算。从一个点指向另一个点的向量可以通过将终点减去起点实现。

    SLVertexToPixel SLVertexShader(float4 inPos: POSITION0, float3 inNormal: NORMAL0)
    {
        SLVertexToPixel Output = (SLVertexToPixel)0; 
        float4x4 preViewProjection = mul(xView, xProjection); 
        float4x4 preWorldViewProjection = mul(xWorld, preViewProjection); 
        Output.Position = mul(inPos, preWorldViewProjection); 
        float3 final3DPos = mul(inPos, xWorld); 
        
        Output.LightDirection = final3DPos - xLightPosition; 
        Output.EyeDirection = final3DPos - xCameraPos; 
        
        float3x3 rotMatrix = (float3x3)xWorld; 
        float3 rotNormal = mul(inNormal, rotMatrix); 
        Output.Normal = rotNormal; 
        
        return Output; 
    }
    Pixel Shader

    pixel shader更加有趣。基本颜色是蓝色的,无需关注太多。在pixel shader中归一化每个方向,因为它的长度可能不是1 (见教程6-3)。

    与以往一样,你计算了光照,将它乘以xLightStrength缩小一点(xLightStrength小于1)。

    SLPixelToFrame SLPixelShader(SLVertexToPixel PSIn) : COLOR0 
    {
        SLPixelToFrame Output = (SLPixelToFrame)0; 
        
        float4 baseColor = float4(0,0,1,1); 
        float3 normal = normalize(PSIn.Normal); 
        float3 lightDirection = normalize(PSIn.LightDirection); 
        float shading = dot(normal, -lightDirection); 
        shading *= xLightStrength; 
        
        float3 reflection = -reflect(lightDirection, normal); 
        float3 eyeDirection = normalize(PSIn.EyeDirection); 
        float specular = dot(reflection, eyeDirection); 
        specular = pow(specular, xSpecularPower); 
        specular *= xLightStrength; 
        
        Output.Color = baseColor*(shading+xAmbient)+specular; 
        
        return Output; 
    }

    然后,使用reflect 函数计算光线方向的镜像。因为光线方向是指向像素的,它的反射方向将指向眼睛,反射方向与eye向量相反,所以需要取负值。

    Specular的值可以通过点乘eye向量和反射方向获取,将这个值进行高次幂计算,使这两个向量的夹角小于10度的像素高光值才会大于0。这个值需要通过乘以xLightStrength 变得小一点。

    最后,ambient,shading和specular分量组合在一起获得像素最后的颜色。

    注意:specular分量在最终颜色中添加白色。如果光线有不同的颜色,你需要将specular值乘以光线的颜色。

    定义Technique

    下面是technique定义:

    technique SpecularLighting 
    {
        pass Pass0
        {
            VertexShader = compile vs_2_0 SLVertexShader(); 
            PixelShader = compile ps_2_0 SLPixelShader(); 
        }
    }
    代码

    因为所有HLSL代码前面已经写过了,下面只是XNA代码:

    effect.CurrentTechnique = effect.Techniques["SpecularLighting"]; 
    effect.Parameters["xWorld"].SetValue(Matrix.Identity); 
    effect.Parameters["xView"].SetValue(fpsCam.ViewMatrix); 
    effect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix); 
    effect.Parameters["xAmbient"].SetValue(0.0f); 
    effect.Parameters["xLightStrength"].SetValue(0.5f); 
    effect.Parameters["xLightPosition"].SetValue(new Vector3(5.0f, 2.0f, -15.0f)); 
    effect.Parameters["xCameraPos"].SetValue(fpsCam.Position); 
    effect.Parameters["xSpecularPower"].SetValue(128.0f); 
    
    effect.Begin(); 
    foreach (EffectPass pass in effect.CurrentTechnique.Passes) 
    {
        pass.Begin(); 
        device.VertexDeclaration = myVertexDeclaration; 
        device.DrawUserPrimitives<VertexPositionNormalTexture> (PrimitiveType.TriangleStrip, vertices, 0, 6); 
        pass.End(); 
    }
    effect.End();

    image

  • 相关阅读:
    c# 使用MySql的MySqlBulkCopy 出现异常 Loading local data is disabled; this must be enabled on both the client and server sides
    DB2 使用EF Core 查询数据 报错 Object reference not set to an instance of an object.
    关于iis部署的网站访问类型设置
    ABP put与delete类型请求异常 TypeErorr: Failed to fetch
    This request has been blocked; the content must be served over HTTPS.
    Mysql Select的字段必须包含在Group By中如何解决
    企业微信小程序-临时登录凭证校验(code2Session)中获取的userid是加密的
    ABP System.ObjectDisposedException: Cannot access a disposed object.
    SQL server数据库文件(mdfldf)的迁移
    A second operation started on this context before a previous operation completed--ABP
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120094.html
Copyright © 2011-2022 走看看