实时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
}
}
}
网友评论