阴影

计算光照影响,需要重要的参数法线 以及材质属性。 法线长度?

基础修改

  1. 添加 Pass'Tag
Tags {
    "LightMode" = "CustomLit"
}
  1. 添加绘制代码
litShaderTagId = new ShaderTagId("CustomLit");
//...

drawingSettings.SetShaderPassName(1, litShaderTagId);
  1. 着色器结构添加法线属性
struct a2v 
{
    float3 position : POSITION;
    float3 normal : NORMAL;
    //...
    UNITY_VERTEX_INPUT_INSTANCE_ID
}


struct v2f 
{
    float3 position : SV_POSITION;
    float3 normal : VAR_NORMAL;
    //...
    UNITY_VERTEX_INPUT_INSTANCE_ID
}

v2f vert(a2v input)
{
    //...
    output.normal = TransformObjectToWorldNormal(input.normal);
    //...
}

float4 frag(v2f input):SV_Target
{

    float4 base = (1.0, 1.0, 1.0, 1.0);

    // 使用法线长度进行显示
    base.rgb = abs(length(input.normal) - 1.0) * 10.0;
    return base;
}

光照计算

简单使用

  1. 定义光照计算属性结构
struct Surface {
    float3 normal;
    float3 color;
    float alpha;
};
  1. frag 进行属性填充和计算
//...
    Surface surface;
    surface.normal = normalize(input.normal);
    surface.color = base.rgb;
    surface.alpha = base.a;

    return float4(surface.color, surface.alpha);

//...
  1. 计算光照
float3 color = GetLighting(surface);
return float4(color, surface.alpha);
// 基础版本 使用法线y分量 作为影响值。
float3 GetLighting (Surface surface) {
    return surface.normal.y * surface.color;
}

光照计算

  1. 定义光源属性
// 基础版本 颜色和方向。
struct Light {
    float3 color;
    float3 direction;
};
  1. 光照函数

** saturate 截止在0-1 范围, 去掉负值,也就是背面影响。**

// 使用光照在法线的分量 和 光照颜色 乘积作为影响因子

float3 IncomingLight (Surface surface, Light light) {
    return saturate(dot(surface.normal, light.direction)) * light.color;
}

// 融合光照影响和材质颜色属性。
float3 GetLighting (Surface surface, Light light) {
    return IncomingLight(surface, light) * surface.color;
}

// todo, 这个省却光照, 使用太阳光作为默认参数。
float3 GetLighting (Surface surface) {
    return GetLighting(surface, GetDirectionalLight());
}
  1. 添加直线光数据给GPU

Shader部分定义。

CBUFFER_START(_CustomLight)
    float3 _DirectionalLightColor;
    float3 _DirectionalLightDirection;
CBUFFER_END


Light GetDirectionalLight () {
    Light light;
    light.color = _DirectionalLightColor;
    light.direction = _DirectionalLightDirection;
    return light;
}

C# 部分进行传输数据。 使用commandbuffer 进行设置

sing UnityEngine;
using UnityEngine.Rendering;

public class Lighting {

    // Shader 属性
    static int
        dirLightColorId = Shader.PropertyToID("_DirectionalLightColor"),
        dirLightDirectionId = Shader.PropertyToID("_DirectionalLightDirection");

    const string bufferName = "Lighting";

    CommandBuffer buffer = new CommandBuffer {
        name = bufferName
    };
    
    public void Setup (ScriptableRenderContext context) {
        buffer.BeginSample(bufferName);
        SetupDirectionalLight();
        buffer.EndSample(bufferName);
        context.ExecuteCommandBuffer(buffer);
        buffer.Clear();
    }
    
    void SetupDirectionalLight () {
        Light light = RenderSettings.sun; // **默认的太阳光,因此场景中必须至少有一个。也就是说光照设置中的`Sun Source`值不能为空。**
        buffer.SetGlobalVector(dirLightColorId, light.color.linear * light.intensity); //**.linear 这个很重要**
        buffer.SetGlobalVector(dirLightDirectionId, -light.transform.forward);
    }
}

在Render 中进行调用

Lighting lighting = new Lighting();
//...

lighting.Setup(context);

改进完善版本, 使用剔除的可见光 和多光源

  • 改进1,使用剔除的可见光,而不是直接使用 Sun。
  • 改进2,传递光原数组 、颜色数组 和光源数量
  • 改进3,传递数组,Shader也跟着改,并且混合多光源影响
  • 改进4,使用Linear 光照绘制。GraphicsSettings.lightsUseLinearIntensity = true;

同样问题, 至少有一个直线光。

// 渲染管线设置
`GraphicsSettings.lightsUseLinearIntensity = true;`
// Render调用修改
lighting.Setup(context, cullingResults);
    // 光源设置数据修改


    const int maxDirLightCount = 4;

    // 对应Shader 属性
    static int
        dirLightCountId = Shader.PropertyToID("_DirectionalLightCount"),    
        dirLightColorsId = Shader.PropertyToID("_DirectionalLightColors"),
        dirLightDirectionsId = Shader.PropertyToID("_DirectionalLightDirections");

    static Vector4[]
        dirLightColors = new Vector4[maxDirLightCount],
        dirLightDirections = new Vector4[maxDirLightCount];
    //
    CullingResults cullingResults;

    public void Setup (
        ScriptableRenderContext context, CullingResults cullingResults
    ) {
        this.cullingResults = cullingResults;
        buffer.BeginSample(bufferName);
        //SetupDirectionalLight();
        // 修改为使用多光源
        SetupLights();
        //…
    }

    //...

    //
    void SetupDirectionalLight (int index, VisibleLight visibleLight) {
        dirLightColors[index] = visibleLight.finalColor;
        dirLightDirections[index] = -visibleLight.localToWorldMatrix.GetColumn(2);
    }

    void SetupLights () {
        NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;
        for (int i = 0; i < visibleLights.Length; i++) {
            VisibleLight visibleLight = visibleLights[i];

            // 只处理直线光影响 和最大数量支持以内的直线光。
            if (visibleLight.lightType == LightType.Directional) {

                ** 值类型拷贝会引起内存增长,因此改为ref形式 **
                SetupDirectionalLight(dirLightCount++, ref visibleLight);
                if (dirLightCount >= maxDirLightCount) {
                    break;
                }
            }
        }

        buffer.SetGlobalInt(dirLightCountId, visibleLights.Length);
        buffer.SetGlobalVectorArray(dirLightColorsId, dirLightColors);
        buffer.SetGlobalVectorArray(dirLightDirectionsId, dirLightDirections);
    }

Shader部分修改

#define MAX_DIRECTIONAL_LIGHT_COUNT 4

CBUFFER_START(_CustomLight)
    //float4 _DirectionalLightColor;
    //float4 _DirectionalLightDirection;
    int _DirectionalLightCount;
    float4 _DirectionalLightColors[MAX_DIRECTIONAL_LIGHT_COUNT];
    float4 _DirectionalLightDirections[MAX_DIRECTIONAL_LIGHT_COUNT];
CBUFFER_END


int GetDirectionalLightCount () {
    return _DirectionalLightCount;
}

Light GetDirectionalLight (int index) {
    Light light;
    light.color = _DirectionalLightColors[index].rgb;
    light.direction = _DirectionalLightDirections[index].xyz;
    return light;
}

// 使用 多光源混合
float3 GetLighting (Surface surface) {
    float3 color = 0.0;
    for (int i = 0; i < GetDirectionalLightCount(); i++) {
        color += GetLighting(surface, GetDirectionalLight(i));
    }
    return color;
}

由于 GetDirectionalLightCount(); 作为循环长度。 而老版本OpenGL ES 2.0和WebGL 1.0图形api在默认情况下不能处理这样的循环, 以及线性光照。所以使用 #pragma target 3.5指令将着色器的目标级别提升到3.5。

HLSLPROGRAM
#pragma target 3.5
//…
ENDHLSL

BRDF

前面使用简化的光照模型只能处理理想的细节,使用 BRDF(双向反射分布函数) 可以处理更多样化的细节表面。 有很多种BRDF 的实现,性能上有所不同, 可以使用 Universal RP版本的 BRDF 实现。

理想的光线反射按照镜面反射。但是如果表面有更多的细节,不够平坦,那么就可能会向四周进行反射。因此无论相机在哪里,从表面接收到的漫射光的量是相同的。但这意味着我们观测到的光能远小于到达表面碎片的光能。

  1. 添加细节控制属性
_Metallic ("Metallic", Range(0, 1)) = 0 // 金属度
_Smoothness ("Smoothness", Range(0, 1)) = 0.5 //光滑度
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
    UNITY_DEFINE_INSTANCED_PROP(float4, _BaseMap_ST)
    UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
    UNITY_DEFINE_INSTANCED_PROP(float, _Cutoff)

    // 添加至 Instanced 属性
    UNITY_DEFINE_INSTANCED_PROP(float, _Metallic)
    UNITY_DEFINE_INSTANCED_PROP(float, _Smoothness)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
struct Surface {
    float3 normal;
    float3 color;
    float alpha;
    // 细节表面添加 金属度和光滑度 属性。
    float metallic;
    float smoothness;
};
float4 frag(v2f input): SV_Target
{

    Surface surface;
    surface.normal = normalize(input.normalWS);
    surface.color = base.rgb;
    surface.alpha = base.a;

    // 赋值添加金属度和光滑度
    surface.metallic = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Metallic);
    surface.smoothness =
        UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Smoothness);

        // ...
}

  1. 测试代码,进行金属度和光滑度赋值

static int
        baseColorId = Shader.PropertyToID("_BaseColor"),
        cutoffId = Shader.PropertyToID("_Cutoff"),
        metallicId = Shader.PropertyToID("_Metallic"),
        smoothnessId = Shader.PropertyToID("_Smoothness");

        //...

        block.SetFloat(metallicId, metallic);
        block.SetFloat(smoothnessId, smoothness);
        GetComponent<Renderer>().SetPropertyBlock(block);
  1. 添加BRDF 结构,结构化 BRDF影响值
struct BRDF {
    float3 diffuse;
    float3 specular;
    float roughness;
};
  1. 添加 BRDF 光照函数
//简单使用, 只用 漫反射进行影响。

BRDF GetBRDF (Surface surface) {
    BRDF brdf;
    brdf.diffuse = surface.color;
    brdf.specular = 0.0;
    brdf.roughness = 1.0;
    return brdf;
}

float3 GetLighting (Surface surface, BRDF brdf, Light light) {
    return IncomingLight(surface, light) * brdf.diffuse;
}

float3 GetLighting (Surface surface, BRDF brdf) {
    float3 color = 0.0;
    for (int i = 0; i < GetDirectionalLightCount(); i++) {
        color += GetLighting(surface, brdf, GetDirectionalLight(i));
    }
    return color;
}

// Shader中使用。
BRDF brdf = GetBRDF(surface);
float3 color = GetLighting(surface, brdf);
  1. 使用反射率 BRDF

漫反射 和 高光


#define MIN_REFLECTIVITY 0.04

float OneMinusReflectivity (float metallic) {
    float range = 1.0 - MIN_REFLECTIVITY;
    return range - metallic * range;
}

//...
float oneMinusReflectivity = OneMinusReflectivity(surface.metallic);

brdf.diffuse = surface.color * oneMinusReflectivity;
//brdf.specular = surface.color - brdf.diffuse; // 简单使用进出相等。

// 使用金属度 进行差值 color
brdf.specular = lerp(MIN_REFLECTIVITY, surface.color, surface.metallic);
//...

粗糙度

// 使用 Universal RP 粗糙度转换
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"

// 光滑度 转换为粗糙度 1 - 光滑度。
float perceptualRoughness =
        PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);

    // 粗糙度平方
    brdf.roughness = PerceptualRoughnessToRoughness(perceptualRoughness);

高光部分需要使用到 视线方向,原理是光照在 视线的分量。

// 相机位置,后面会在cs中进行设置
float3 _WorldSpaceCameraPos;
//表面细节添加 视线属性

struct Surface {
    float3 normal;
    // 视线
    float3 viewDirection;
    float3 color;
    float alpha;
    float metallic;
    float smoothness;
};
struct v2f 
{
    float3 position : SV_POSITION;
    float3 positionWorld : VAR_POSITION; //储存worldPos
    //...
}

v2f vert(a2v input)
{
    //...
    output.positionWorld = TransformObjectToWorld(input.position);
    output.position = TransformWorldToHClip(output.positionWorld);
    //...

}


float4 frag(v2f input): SV_Target
{

    surface.viewDirection = normalize(_WorldSpaceCameraPos - input.positionWorld);
}

高光强度计算, 使用简化版本的 CookTorrance BRDF,公式: BRDF TODO

ShaderGUI

通过在 Shader 最后中指定 自定义的ShaderGUI


Shader "ShaderName" {
    //...

    CustomEditor "CustomShaderGUI"
}

C#

public class CustomShaderGUI : ShaderGUI 
{
    public override void OnGUI (MaterialEditor materialEditor, MaterialProperty[] properties)
    {
        //base.OnGUI (materialEditor, properties);
        Material targetMat = materialEditor.target as Material;
        //...
    }
}

文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录