局部光照模型

GPU渲染管线:
Vertex Shader
GeometryShader
Clipping 可配置
ScreenMapping 动不了
--- 几何阶段
TriangleSetup 动不了
TriangleTraversal 动不了
PixelShader
Merger
--- 光栅化阶段

兰伯特材质

#include "Light.hlsl"
#include "Material.hlsl"

cbuffer MeshConstantBuffer : register(b0) // b0~b14
{
    float4x4 WorldMatrix;
};

cbuffer ViewportConstantBuffer : register(b1)
{
    float4x4 ViewProjectionMatrix;
};

cbuffer MaterialConstantBuffer : register(b2)
{
    float4 BaseColor;
    float4x4 TransformInfo;
};

cbuffer LightConstantBuffer : register(b3)
{
    float3 LightIntensity;
    float3 LightDirection;
};

struct MeshVertexIn
{
    float3 Position : POSITION;
    float4 Color : COLOR;
    float3 Normal : NORMAL;
};

struct MeshVertexOut
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR;
    float3 Normal : NORMAL;
};

MeshVertexOut VSMain(MeshVertexIn MV)
{
    MeshVertexOut Out;

    float4 Position = mul(float4(MV.Position, 1.0f), WorldMatrix);
    Out.Position = mul(Position, ViewProjectionMatrix);
    Out.Normal = mul(MV.Normal, (float3x3)WorldMatrix);
    Out.Color = MV.Color;
    return Out;
}

float4 PSMain(MeshVertexOut MVOut) : SV_TARGET
{
    float4 Ambient = float4(0.05f, 0.05f, 0.15f, 1.0f);

    float3 ModelNormal = normalize(MVOut.Normal);
    float3 ModelLightDirection = normalize(-LightDirection);
    float DotValue = max(dot(ModelNormal, ModelLightDirection), 0.0);

    FMaterial Material;
    Material.BaseColor = BaseColor;
    MVOut.Color = Material.BaseColor * DotValue + Ambient * MVOut.Color;

    return MVOut.Color;
}

法线 点乘 光方向 乘 材质基础颜色 + 环境光 * 材质基础颜色

image.png

半兰伯特

经验模型
点乘算的是cos,是【-1,1】结果 * 0.5 + 0.5 就是半兰伯特

float DotValue = max(dot(ModelNormal, ModelLightDirection) * 0.5 + 0.5, 0.0);

image.png

Phong

为了模拟金属高光
P = 漫反射 + 环境光 + 高光
高光 = BaseColor * COS(视角方向,光反射方向) ^ G (G默认是5)
G 可以调整亮斑的大小

image.png

最上面是Phone

菲涅尔效果

image.png

模拟周围一圈的边缘光

    else if (MaterialType == 100) { // 菲涅尔效果,不是材质
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float4 F0 = float4(0.0f, 0.0f, 0.0f, 0.0f);
        Specular.xyz = FresnelSchlick(F0, ModelNormal, ViewDirection);
    }
float3 FresnelSchlick(float3 InF0, float3 ObjectPointNormal, float3 InDirection)
{
    return 1.f - max(dot(InDirection, ObjectPointNormal), 0.f);
}

调整边缘光的范围用pow

image.png

float3 FresnelSchlick(float3 InF0, float3 ObjectPointNormal, float3 InDirection)
{
    float M = 0.5f;
    return pow(1.f - saturate(dot(InDirection, ObjectPointNormal)), M);
}

image.png
image.png

float3 FresnelSchlick(float3 InF0, float3 ObjectPointNormal, float3 InDirection)
{
    float M = 5.f;
    return (1 - InF0) * pow(1.f - saturate(dot(InDirection, ObjectPointNormal)), M);
}

左边到右边,InF0的值调大了。

image.png
image.png

左边F0。0.02 右边 0.7
加上了基础颜色F0

float3 FresnelSchlick(float3 InF0, float3 ObjectPointNormal, float3 InDirection)
{
    float M = 5.f;
    return InF0 + (1 - InF0) * pow(1.f - saturate(dot(InDirection, ObjectPointNormal)), M);
}

BlinnPhong 布林Phong

目的是反射比较耗

image.png

L是光,V是视角,如果在法线的两边,L+V得到蓝色的可以直接当作反射光线。
但是如果在同一边,显然是错误的。
image.png

但是不管加出来在哪边,都可以和法线进行cos来获得反射强度

image.png
1是BlinnPhong。2,3是Phong。3粗糙度大一点。

    else if (MaterialType == 3) {
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float3 HalfDirection = normalize(ViewDirection + ModelLightDirection);

        DotValue = max(Diffuse, 0.0);
        if (DotValue > 0) { // 为了暗处不发光
            float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
            float M = MaterialShininess * 100.f;
            Specular.xyz = pow(max(dot(HalfDirection, ModelNormal), 0.f), M);
        }
    }

WrapLight

皮肤模拟,早期经验模型。

半兰伯特是这样的 max(dot(L, N) * 0.5 + 0.5, 0)
WrapLight 公式: max(dot(L, N) + w / (1 + w), 0);
模拟皮肤的通透效果

image.png
右边是改成10的效果
image.png

1的时候就是兰伯特

    else if (MaterialType == 4) {
        float WrapValue = 1.f; // 1的时候就是兰伯特
        DotValue = max((Diffuse + WrapValue) / (1.f + WrapValue), 0.0);
    }

Minnaert

解决月球材质,天鹅绒,丝袜

公式:
dot(L, N) dot(N, V)
dot(L, N)
pow(dot(L, N) * dot(N, V), R)
一个基础,一个效果加深。R是粗糙度。

    else if (MaterialType == 5) { // Minnaert
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float DotLight = max(Diffuse, 0.0);
        float DotView = max(dot(ViewDirection, ModelNormal), 0.0);

        DotValue = DotLight * DotView;
    }

image.png

为了让他亮的更亮,暗的更暗,所以加个pow

    else if (MaterialType == 5) { // Minnaert
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float DotLight = max(Diffuse, 0.0);
        float DotView = max(dot(ViewDirection, ModelNormal), 0.0);

        float R = 2.f;
        DotValue = pow(DotLight * DotView, R);
    }

image.png
更黑了一点

    else if (MaterialType == 5) { // Minnaert
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float DotLight = max(Diffuse, 0.0);
        float DotView = max(dot(ViewDirection, ModelNormal), 0.0);

        float R = 2.f;
        DotValue = saturate(DotLight * pow(DotLight * DotView, R));
    }

image.png
image.png

加强了光的影响

    else if (MaterialType == 5) { // Minnaert
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float DotLight = max(Diffuse, 0.0);
        float DotView = max(dot(ViewDirection, ModelNormal), 0.0);

        float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
        float M = MaterialShininess * 20.f;
        DotValue = saturate(DotLight * pow(DotLight * DotView, M));
    }

然后把Paw的倍数和粗糙度关联

卡通材质 Banded

    else if (MaterialType == 6) { // Banded
        DotValue = max(Diffuse, 0.0);
        float Layered = 3.f;
        DotValue = floor(Diffuse);
    }

我们直接向上取整。在兰伯特的基础上,这样就分层了

image.png

    else if (MaterialType == 6) { // Banded
        DotValue = max(Diffuse, 0.0);
        float Layered = 3.f;
        DotValue = floor(Diffuse * Layered) / Layered;
    }

image.png

兰伯特黑处太黑了,所以我们要半兰伯特。

    else if (MaterialType == 6) { // Banded
        Diffuse = (Diffuse + 1.f) * 0.5f; // 限制范围0~1
        float Layer = 4.f;
        DotValue = floor(Diffuse * Layer) / Layer;
    }

image.png

image.png

加上菲涅尔

image.png

渐变的卡通材质 Banded

image.png

    else if (MaterialType == 7) { // GradualBanded
        float LightDotValue = Diffuse; // 先存一下
        Diffuse = (Diffuse + 1.f) * 0.5f; // 限制范围0~1
        float Layer = 4.f;

        float4 Color2 = float4(0.1f, 0.5f, 0.7f, 1.f);
        DotValue = floor(Diffuse * Layer) / Layer;

        Material.BaseColor = lerp(Color2, Material.BaseColor, LightDotValue);
    }

最终版卡通材质

image.png

    else if (MaterialType == 8) { // FinalBanded
        // 半兰伯特思想
        Diffuse = (Diffuse + 1.f) * 0.5f; // 限制范围0~1

        // 分层
        float Layered = 4.f;
        DotValue = floor(Diffuse * Layered) / Layered;

        // 菲涅尔
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
        float3 F0 = float3(0.05f, 0.05f, 0.05f);
        Specular.xyz = FresnelSchlick(F0, ModelNormal, ViewDirection, 3.f).xyz;

        // 高光
        if (DotValue > 0.f)
        {
            float3 ReflectDirection = normalize(-reflect(ModelLightDirection, ModelNormal));
            float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
            float M = MaterialShininess * 60.f;
            Specular = Specular + pow(max(dot(ViewDirection, ReflectDirection), 0.f), M) / 0.032f; // 经验值

        }
    }

Back模型

玉的透色效果
正面是Phong或者warp。
背面

    else if (MaterialType == 9) { // Back玉器具
        // Color(2, 214, 17)
        float SSSValue = 0.3f;
        float3 LightNormalizeValue = -normalize(ModelNormal * SSSValue  + ModelLightDirection); // 效果做到背面

        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);

        DotValue = saturate(dot(LightNormalizeValue, ViewDirection));

    }

image.png

SSS改成1.3

image.png

希望它边缘更加pow一点
image.png

    else if (MaterialType == 9) { // Back玉器具
        // Color(2, 214, 17)
        float SSSValue = 1.3f;
        float3 LightNormalizeValue = -normalize(ModelNormal * SSSValue  + ModelLightDirection); // 效果做到背面

        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);

        DotValue = pow(saturate(dot(LightNormalizeValue, ViewDirection)), 2.f);

    }

发现光收紧了,但是变暗了,所以再最后再乘一个系数 2

    else if (MaterialType == 9) { // Back玉器具
        // Color(2, 214, 17)
        float SSSValue = 1.3f;
        float3 LightNormalizeValue = -normalize(ModelNormal * SSSValue  + ModelLightDirection); // 效果做到背面

        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);

        DotValue = pow(saturate(dot(LightNormalizeValue, ViewDirection)), 2.f) * 2.f;

    }

image.png

直接把Phone粘过来。正面和背面效果

image.png
image.png
image.png

    else if (MaterialType == 9) { // Back玉器具
        // Color(2, 214, 17)
        float SSSValue = 1.3f;
        float3 LightNormalizeValue = -normalize(ModelNormal * SSSValue  + ModelLightDirection); // 效果做到背面
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);

        float TransmissionIntensity = 2.f;
        float TransmissionScope = 1.5f;

        DotValue = pow(saturate(dot(LightNormalizeValue, ViewDirection)), TransmissionScope) * TransmissionIntensity;

        // Phone 部分
        float3 ReflectDirection = normalize(-reflect(ModelLightDirection, ModelNormal));

        DotValue += max(Diffuse, 0.0);
        if (DotValue > 0) { // 为了暗处不发光
            float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
            float M = MaterialShininess * 100.f;
            Specular.xyz = pow(max(dot(ViewDirection, ReflectDirection), 0.f), M);
        }
    }

image.png

加上wrap

    else if (MaterialType == 9) { // Back玉器具
        // Color(2, 214, 17)
        float SSSValue = 1.3f;
        float3 LightNormalizeValue = -normalize(ModelNormal * SSSValue  + ModelLightDirection); // 效果做到背面
        float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);

        float TransmissionIntensity = 2.f;
        float TransmissionScope = 1.5f;

        DotValue = pow(saturate(dot(LightNormalizeValue, ViewDirection)), TransmissionScope) * TransmissionIntensity;

        // Phone 部分
        float3 ReflectDirection = normalize(-reflect(ModelLightDirection, ModelNormal));

        DotValue += max(Diffuse, 0.0);
        if (DotValue > 0) { // 为了暗处不发光
            float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
            float M = MaterialShininess * 100.f;
            Specular.xyz = pow(max(dot(ViewDirection, ReflectDirection), 0.f), M);
        }

        float WrapValue = 1.5f; // 1的时候就是兰伯特
        DotValue = max((Diffuse + WrapValue) / (1.f + WrapValue), 0.0);
    }

各项异性

做头发的效果,

TBN

OrenNayar

粗糙表面,沙面

兰伯特多灯光渲染的问题

image.png

image.png

两灯光方向如图,黄色部分收到两个灯光影响。在交界处,灯光垂直,变成0了。出现了一条线。

解决方案是平方一下

        if (MaterialType == 0) { // 兰伯特
            DotValue = pow(max(Diffuse, 0.0), 2.f);
        }

image.png

效果更平滑一线。

多灯光与布林Phong

image.png

原来

        else if (MaterialType == 3) { // Blinn-Phone
            float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
            float3 HalfDirection = normalize(ViewDirection + ModelLightDirection);

            DotValue = max(Diffuse, 0.0);
            if (DotValue > 0) { // 为了暗处不发光
                float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
                float M = MaterialShininess * 100.f;
                Specular.xyz = pow(max(dot(HalfDirection, ModelNormal), 0.f), M);
            }
        }

现在

        else if (MaterialType == 3) { // Blinn-Phone
            float3 ViewDirection = normalize(ViewportPosition.xyz - MVOut.WorldPosition.xyz);
            float3 HalfDirection = normalize(ViewDirection + ModelLightDirection);

            DotValue = pow(max(Diffuse, 0.0), 2.f);

            float MaterialShininess = 1.01f - saturate(MaterialRoughness); // saturate 限制在0~1
            float M = MaterialShininess * 100.f;
            Specular.xyz = pow(max(dot(HalfDirection, ModelNormal), 0.f), M);
        }

image.png

归一化模型:
没听懂

点光源

image.png

放大地面后
image.png

强度加倍

image.png

image.png

发现上上张图有问题,中间黑了,调整了下公式。

另一种点光源算法:
https://zhuanlan.zhihu.com/p/54855509

image.png

image.png

image.png

image.png

聚光灯

image.png

上一篇
下一篇