实时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