实时Area Light

作者: 万里_aa3f | 来源:发表于2019-02-21 23:44 被阅读1次

实时Area Light



左边是实现的Area Light 右边是引擎里的PointLight
请忽略上面的球,那个是用raymarching做的,下面还藏了一个,做了个吸附效果

实现参考:

理论方面:

SIGGRAPH 2013 Course:https://blog.selfshadow.com/publications/s2013-shading-course/
Epic Games的也是很好的参考:https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf

实践方面:

shadertoy:https://www.shadertoy.com/view/ldfGWs
unity的CommandBuffer案例

1.实现原理
2.unity案例与此文实现方式
3.实现注意项

1.实现原理

我们的目的,是将Area Light引入我们的PBR计算中。所以先从PBR的参数说起

UnityLight light;
  light.dir=lightDir;
  light.color=color*atten;
UnityIndirect IndirectLight;
  IndirectLight.diffuse=0;
  IndirectLight.specular=0;
UNITY_BRDF_PBS (baseColor, speColor, OneMinusReflectivity, smoothness, normal, viewDir, light, IndirectLight);

可以试着逐个参数排除,与材质相关的都不考虑。还剩下:light与indirectLight
在看一下也就只有lightDir和区域光有关系了。上面的论文中也正是指出了这点。




直接上球灯求lightDir的式子

            half3 CalcSphereLightToLight(float3 pos, float3 lightPos, float3 viewDir, half3 normal, float sphereRad)
            {
                half3 r = reflect (viewDir, normal);
                float3 L = lightPos - pos;
                float3 centerToRay  = dot (L, r) * r - L;
                float3 closestPoint = L + centerToRay * saturate(sphereRad / length(centerToRay));
                return normalize(closestPoint);
            }

2.unity案例与此文实现方式

unity中的案例是用commandBuffer直接加在afterLighting后,读取Gbuffer中的全局材质的属性。然后再经过PBR公式,全局的计算每个灯光对场景的影响。
但AreaLight也有它的弱点(要不然怎么不是默认灯光呢!!),其中最致命的是无法用现有手段计算出准确的实时阴影。
这也在物体交错的场景使用上带来局限。所以为了使用上更加灵活,
此文实现了 只对单个物体影响的AreaLight。如果说unity是延迟渲染的画。此文可以理解为 ForwardAdd 了一个AreaLight的效果。




3.实现注意点

1.因为效果等于ForwardAdd,所以Blend方式设为 One One,并将IndirectLight的diffuse与specular,因为不管之前是延迟还是前向,都已经将这部分渲染好了
2.关于灯光衰减,如果用1/(1+d^2)的公式,实测效果。漫反射会衰减的特别快。个人感觉效果不好用。
所以用的是unity自带的方法:用距离的平方读取_LightTexture0衰减贴图

float length1=length(float3(i.worldPos-_lightPos))  -_lightR; 
float atten = tex2D(_LightTexture0 , float2(length1,length1)).UNITY_ATTEN_CHANNEL;
light.color=_lightColor.xyz * atten * _lightIntensity;

为了简便运算:先求出到灯中心的绝对长度,再减去R,再用该长度当uv读取_LightTexture0.


脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

[ExecuteInEditMode]
public class renderAreaLight : MonoBehaviour
{
    public GameObject renderObj;
    private Renderer targetRenderer=null;

    public Transform lightPos;
    public Color lightColor =Color.yellow;
    public float Intensity=1;
    public float lightR = 1.0f;

    private CommandBuffer commandBuffer;
    public Material material;
    
    private void OnEnable() {
        targetRenderer = this.GetComponent<Renderer>();
        if(lightPos||targetRenderer||material!=null){
            commandBuffer=new CommandBuffer();
            commandBuffer.name="AddAreaLight";
            commandBuffer.DrawRenderer(targetRenderer,material);
            //commandBuffer.DrawMesh(renderObj.GetComponent<Mesh>(),renderObj.);
            Camera.main.AddCommandBuffer(CameraEvent.AfterLighting,commandBuffer);
        }
    }

    private void OnDisable() {
        if(targetRenderer){
            Camera.main.RemoveCommandBuffer(CameraEvent.AfterLighting,commandBuffer);
            commandBuffer.Clear();
        }
    }

    private void Update() {
        material.SetVector("_lightPos",lightPos.position);
        material.SetColor("_lightColor",lightColor);
        material.SetFloat("_lightR",lightR);
        material.SetFloat("_lightIntensity",Intensity);
    }
}

shader

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/MyAreaLight"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Metallic("Metallic",2D)="white"{}
        _smoothness("Smoothness",float)=1.0
        _NormalMap("NormalMap",2D)="bump"{}
        _NormalScale("NormalScale",Range(0,2))=1

        _lightPos("LightPos",vector)=(0,0,0,1)
        _lightR("LightR",float)=1
        _lightColor("LightColor",Color)=(1,1,1,1)
        _lightIntensity("LightIntensity",float)=1

    }
    SubShader
    {
        Blend one one 

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityPBSLighting.cginc"
            #include "UnityDeferredLibrary.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldNormal:TEXCOORD1;
                float3 worldTangent:TEXCOORD2;
                float3 biNormal:TEXCOORD3;
                float3 worldPos:TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _Metallic;
            float _smoothness;
            sampler2D _NormalMap;
            float _NormalScale;

            float4 _lightPos;
            float _lightR;
            float4 _lightColor;
            float _lightIntensity;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal=UnityObjectToWorldNormal(v.normal);
                o.worldTangent=UnityObjectToWorldDir(v.tangent.xyz);
                o.biNormal=cross(o.worldNormal,o.worldTangent.xyz) * v.tangent.w * unity_WorldTransformParams.w;
                o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                return o;
            }

            half3 CalcSphereLightToLight(float3 pos, float3 lightPos, float3 viewDir, half3 normal, float sphereRad)
            {
                half3 r = reflect (viewDir, normal);

                float3 L = lightPos - pos;
                float3 centerToRay  = dot (L, r) * r - L;
                float3 closestPoint = L + centerToRay * saturate(sphereRad / length(centerToRay));
                return normalize(closestPoint);
            }


            fixed4 frag (v2f i) : SV_Target
            {
                //data
                float3 viewDir= normalize(UnityWorldSpaceViewDir(i.worldPos));
                
                float3 tangent=normalize(i.worldTangent);
                float3 biNormal=normalize(i.biNormal);
                float3 worldNormal=normalize(i.worldNormal);
                float3 normal=UnpackScaleNormal(tex2D(_NormalMap,i.uv) , _NormalScale);
                normal=normalize(normal.x * tangent +
                                normal.y * biNormal +
                                normal.z * worldNormal);
                
                float3 baseColor=tex2D(_MainTex,i.uv).xyz;
                float metallic=tex2D(_Metallic,i.uv).x;
                float3 speColor=float3(0,0,0);
                float OneMinusReflectivity;
                DiffuseAndSpecularFromMetallic(baseColor,metallic,speColor,OneMinusReflectivity);
                float smoothness =  tex2D(_Metallic,i.uv).w *_smoothness;

                UnityLight light;
                float3 lightDir = CalcSphereLightToLight(i.worldPos,_lightPos,viewDir,normal,_lightR);
                light.dir=lightDir;
                float length1=length(float3(i.worldPos-_lightPos))  -_lightR; 
                float atten = tex2D(_LightTexture0 , float2(length1,length1)).UNITY_ATTEN_CHANNEL;
                light.color=_lightColor.xyz * atten * _lightIntensity;

                UnityIndirect ind;
                ind.diffuse = 0;
                ind.specular = 0;
                
                half4 res = UNITY_BRDF_PBS (baseColor, speColor, OneMinusReflectivity, smoothness, normal, viewDir, light, ind);
                return res;
            }
            ENDCG
        }
    }
}

相关文章

网友评论

    本文标题:实时Area Light

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