聚光灯有许多用途,他们可以用来模拟路灯, 壁灯,或许多创意用法,例如模拟手电筒,因为投射区域能精确的控制,因此很适合用来模拟打在角色身上的光或是模拟舞台灯光效果等等

在项目中我们自己实现了一套聚光灯的方法(没有体积光的计算),整体思路是比较像素点与光照原点的位置得到的向量和光照方向的向量点乘,再跟spotAngle的一半的cos做比较。如果大于spot的一半的cos则说明超过了限制,是不属于聚光灯下的。然后再结合点光源的距离计算得到长度的判断和衰减。https://blog.csdn.net/llsansun/article/details/103216534

比较。

其中在管线中传递就好,不需要再gpu来做cos运算。

最终的聚光灯shader运算为

half3 LightingPointAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{half distance = length(posWorld - lightPos);return finalColor / (max(1.0h, (distance * (1.0h / _MainLightRange))));
}
half3 LightingSpotAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
//顶点和光照原点的向量half3 vertexDir = normalize(posWorld - _MainLightPosition);
//光照方向half3 centerPointDir = (_MainLightSpotDir.xyz);//顶点方向和光照方向的点乘half dotPoint = min(1, max(0, (-dot(centerPointDir, vertexDir) - _MainLightAttenuation.w) + 0.8));
//获取点光源范围的光照衰减half3 distanceColor = LightingPointAttenuation(finalColor, posWorld, lightPos);half3 circleColor = distanceColor * dotPoint;half distance = length(posWorld - lightPos);
//超过距离不显示if ((-dot(centerPointDir, vertexDir) - _MainLightAttenuation.w) <  0 || _MainLightRange - distance < 0){return circleColor * DarkColorPercent;}return circleColor;
}half3 LightingAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{
//光照类型选择return (_MainLightType == 1) ? finalColor : (_MainLightType == 2) ?     LightingPointAttenuation(finalColor, posWorld, lightPos) : LightingSpotAttenuation(finalColor, posWorld, lightPos);
}

最终应用的shader改为

//最终颜色做一次衰减运算
finalColor.rgb = LightingAttenuation(c, i.posWorld, mainLight.dir);
return half4(finalColor, s.alpha);

当然还需要修改管线


using System;
using System.Collections.Generic;
using UnityEngine.Experimental.GlobalIllumination;
using UnityEngine.Rendering;namespace UnityEngine.Experimental.Rendering.LightweightPipeline
{/// <summary>/// Configure the shader constants needed by the render pipeline////// This pass configures constants that LWRP uses when rendering./// For example, you can execute this pass before you render opaque/// objects, to make sure that lights are configured correctly./// </summary>public class SetupLightweightConstanstPass : ScriptableRenderPass{static class LightConstantBuffer{public static int _MainLightPosition;public static int _MainLightColor;public static int _MainLightRange;public static int _MainLightType;
//多给了两个变量,一个是光线衰减,一个是光照方向及范围public static int _MainLightAttenuation;public static int _MainLightSpotDir;public static int _AdditionalLightsCount;public static int _AdditionalLightsPosition;public static int _AdditionalLightsColor;public static int _AdditionalLightsAttenuation;public static int _AdditionalLightsSpotDir;public static int _AdditionalLightsBuffer;}const string k_SetupLightConstants = "Setup Light Constants";MixedLightingSetup m_MixedLightingSetup;Vector4 k_DefaultLightPosition = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);Vector4 k_DefaultLightColor = Color.black;Vector4 k_DefaultLightAttenuation = new Vector4(1.0f, 0.0f, 0.0f, 1.0f);Vector4 k_DefaultLightSpotDirection = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);float k_DefaultLightRange = 10.0f;Vector4[] m_AdditionalLightPositions;Vector4[] m_AdditionalLightColors;Vector4[] m_AdditionalLightAttenuations;Vector4[] m_AdditionalLightSpotDirections;private int maxVisibleAdditionalLights { get; set; }private ComputeBuffer perObjectLightIndices { get; set; }/// <summary>/// Create the pass/// </summary>public SetupLightweightConstanstPass(){LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition");LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor");LightConstantBuffer._MainLightRange = Shader.PropertyToID("_MainLightRange");LightConstantBuffer._MainLightType = Shader.PropertyToID("_MainLightType");LightConstantBuffer._MainLightAttenuation = Shader.PropertyToID("_MainLightAttenuation");LightConstantBuffer._MainLightSpotDir = Shader.PropertyToID("_MainLightSpotDir");LightConstantBuffer._AdditionalLightsCount = Shader.PropertyToID("_AdditionalLightsCount");LightConstantBuffer._AdditionalLightsPosition = Shader.PropertyToID("_AdditionalLightsPosition");LightConstantBuffer._AdditionalLightsColor = Shader.PropertyToID("_AdditionalLightsColor");LightConstantBuffer._AdditionalLightsAttenuation = Shader.PropertyToID("_AdditionalLightsAttenuation");LightConstantBuffer._AdditionalLightsSpotDir = Shader.PropertyToID("_AdditionalLightsSpotDir");LightConstantBuffer._AdditionalLightsBuffer = Shader.PropertyToID("_AdditionalLightsBuffer");m_AdditionalLightPositions = new Vector4[0];m_AdditionalLightColors = new Vector4[0];m_AdditionalLightAttenuations = new Vector4[0];m_AdditionalLightSpotDirections = new Vector4[0];}/// <summary>/// Configure the pass/// </summary>/// <param name="maxVisibleAdditionalLights">Maximum number of visible additional lights</param>/// <param name="perObjectLightIndices">Buffer holding per object light indicies</param>public void Setup(int maxVisibleAdditionalLights, ComputeBuffer perObjectLightIndices){this.maxVisibleAdditionalLights = maxVisibleAdditionalLights;this.perObjectLightIndices = perObjectLightIndices;if (m_AdditionalLightColors.Length != maxVisibleAdditionalLights){m_AdditionalLightPositions = new Vector4[maxVisibleAdditionalLights];m_AdditionalLightColors = new Vector4[maxVisibleAdditionalLights];m_AdditionalLightAttenuations = new Vector4[maxVisibleAdditionalLights];m_AdditionalLightSpotDirections = new Vector4[maxVisibleAdditionalLights];}}void InitializeLightConstants(List<VisibleLight> lights, int lightIndex, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightAttenuation, out Vector4 lightSpotDir, out float lightRange, out int lightType){lightPos = k_DefaultLightPosition;lightColor = k_DefaultLightColor;lightAttenuation = k_DefaultLightAttenuation;lightSpotDir = k_DefaultLightSpotDirection;lightRange = k_DefaultLightRange;lightType = 1;// When no lights are visible, main light will be set to -1.// In this case we initialize it to default values and returnif (lightIndex < 0)return;VisibleLight lightData = lights[lightIndex];if (lightData.lightType == LightType.Directional){Vector4 dir = -lightData.localToWorld.GetColumn(2);lightPos = new Vector4(dir.x, dir.y, dir.z, k_DefaultLightAttenuation.w);}else{Vector4 pos = lightData.localToWorld.GetColumn(3);lightPos = new Vector4(pos.x, pos.y, pos.z, k_DefaultLightAttenuation.w);lightRange = lightData.range;}lightType = (int)lightData.lightType;// VisibleLight.finalColor already returns color in active color spacelightColor = lightData.finalColor;// Directional Light attenuation is initialize so distance attenuation always be 1.0if (lightData.lightType != LightType.Directional){// Light attenuation in lightweight matches the unity vanilla one.// attenuation = 1.0 / distanceToLightSqr// We offer two different smoothing factors.// The smoothing factors make sure that the light intensity is zero at the light range limit.// The first smoothing factor is a linear fade starting at 80 % of the light range.// smoothFactor = (lightRangeSqr - distanceToLightSqr) / (lightRangeSqr - fadeStartDistanceSqr)// We rewrite smoothFactor to be able to pre compute the constant terms below and apply the smooth factor// with one MAD instruction// smoothFactor =  distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)//                 distanceSqr *           oneOverFadeRangeSqr             +              lightRangeSqrOverFadeRangeSqr// The other smoothing factor matches the one used in the Unity lightmapper but is slower than the linear one.// smoothFactor = (1.0 - saturate((distanceSqr * 1.0 / lightrangeSqr)^2))^2float lightRangeSqr = lightData.range * lightData.range;float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightData.range * lightData.range);// On mobile: Use the faster linear smoothing factor.// On other devices: Use the smoothing factor that matches the GI.lightAttenuation.x = Application.isMobilePlatform ? oneOverFadeRangeSqr : oneOverLightRangeSqr;lightAttenuation.y = lightRangeSqrOverFadeRangeSqr;}else{lightRange = 99999;}if (lightData.lightType == LightType.Spot){Vector4 dir = lightData.localToWorld.GetColumn(2);lightSpotDir = new Vector4(-dir.x, -dir.y, -dir.z, 0.0f);// Spot Attenuation with a linear falloff can be defined as// (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)// This can be rewritten as// invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)// SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)// If we precompute the terms in a MAD instructionfloat cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.spotAngle * 0.5f);// We neeed to do a null check for particle lights// This should be changed in the future// Particle lights will use an inline functionfloat cosInnerAngle;if (lightData.light != null)cosInnerAngle = Mathf.Cos(LightmapperUtils.ExtractInnerCone(lightData.light) * 0.5f);elsecosInnerAngle = Mathf.Cos((2.0f * Mathf.Atan(Mathf.Tan(lightData.spotAngle * 0.5f * Mathf.Deg2Rad) * (64.0f - 18.0f) / 64.0f)) * 0.5f);float smoothAngleRange = Mathf.Max(0.001f, cosInnerAngle - cosOuterAngle);float invAngleRange = 1.0f / smoothAngleRange;float add = -cosOuterAngle * invAngleRange;lightAttenuation.z = invAngleRange;lightAttenuation.w = cosOuterAngle;//add;}Light light = lightData.light;// TODO: Add support to shadow maskif (light != null && light.bakingOutput.mixedLightingMode == MixedLightingMode.Subtractive && light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed){if (m_MixedLightingSetup == MixedLightingSetup.None && lightData.light.shadows != LightShadows.None){m_MixedLightingSetup = MixedLightingSetup.Subtractive;// In subtractive light mode, main light direct contribution is baked on lightmap// In this case we setup light position w component as 0.0f so we can remove it's contribution// from realtime light computationif (lightData.lightType == LightType.Directional)lightPos.w = 0.0f;}}}void SetupShaderLightConstants(CommandBuffer cmd, ref LightData lightData){float lightRange = 0;int lightType = 1;// Clear to default all light constant datafor (int i = 0; i < maxVisibleAdditionalLights; ++i)InitializeLightConstants(lightData.visibleLights, -1, out m_AdditionalLightPositions[i],out m_AdditionalLightColors[i],out m_AdditionalLightAttenuations[i],out m_AdditionalLightSpotDirections[i],out lightRange,out lightType);m_MixedLightingSetup = MixedLightingSetup.None;// Main light has an optimized shader path for main light. This will benefit games that only care about a single light.// Lightweight pipeline also supports only a single shadow light, if available it will be the main light.SetupMainLightConstants(cmd, ref lightData);SetupAdditionalLightConstants(cmd, ref lightData);}private int currentLightType;void SetupMainLightConstants(CommandBuffer cmd, ref LightData lightData){Vector4 lightPos, lightColor, lightAttenuation, lightSpotDir;float lightRange = 0.0f;int lightType = 1;InitializeLightConstants(lightData.visibleLights, lightData.mainLightIndex, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir,out lightRange, out lightType);var lights = Light.GetLights(LightType.Spot, 0);if (lightData.visibleLights.Count == 0 && lights.Length != 0){cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lights[0].color);cmd.SetGlobalInt(LightConstantBuffer._MainLightType, 0);}else{cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lightColor);cmd.SetGlobalInt(LightConstantBuffer._MainLightType, lightType);}cmd.SetGlobalVector(LightConstantBuffer._MainLightPosition, lightPos);cmd.SetGlobalFloat(LightConstantBuffer._MainLightRange, lightRange);cmd.SetGlobalVector(LightConstantBuffer._MainLightAttenuation, lightAttenuation);cmd.SetGlobalVector(LightConstantBuffer._MainLightSpotDir, lightSpotDir);}void SetupAdditionalLightConstants(CommandBuffer cmd, ref LightData lightData){List<VisibleLight> lights = lightData.visibleLights;if (lightData.additionalLightsCount > 0){int additionalLightsCount = 0;for (int i = 0; i < lights.Count && additionalLightsCount < maxVisibleAdditionalLights; ++i){VisibleLight light = lights[i];if (light.lightType != LightType.Directional){float lightRange = 0.0f;int lightType = 1;InitializeLightConstants(lights, i, out m_AdditionalLightPositions[additionalLightsCount],out m_AdditionalLightColors[additionalLightsCount],out m_AdditionalLightAttenuations[additionalLightsCount],out m_AdditionalLightSpotDirections[additionalLightsCount],out lightRange,out lightType);additionalLightsCount++;}}cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, new Vector4(lightData.maxPerObjectAdditionalLightsCount,0.0f, 0.0f, 0.0f));// if not using a compute buffer, engine will set indices in 2 vec4 constants// unity_4LightIndices0 and unity_4LightIndices1if (perObjectLightIndices != null)cmd.SetGlobalBuffer(LightConstantBuffer._AdditionalLightsBuffer, perObjectLightIndices);}else{cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, Vector4.zero);}cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsPosition, m_AdditionalLightPositions);cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsColor, m_AdditionalLightColors);cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsAttenuation, m_AdditionalLightAttenuations);cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsSpotDir, m_AdditionalLightSpotDirections);}/// <inheritdoc/>public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData){if (renderer == null)throw new ArgumentNullException("renderer");int additionalLightsCount = renderingData.lightData.additionalLightsCount;bool additionalLightsPerVertex = renderingData.lightData.shadeAdditionalLightsPerVertex;CommandBuffer cmd = CommandBufferPool.Get(k_SetupLightConstants);SetupShaderLightConstants(cmd, ref renderingData.lightData);CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsVertex, additionalLightsCount > 0 && additionalLightsPerVertex);CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsPixel, additionalLightsCount > 0 && !additionalLightsPerVertex);CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MixedLightingSubtractive, renderingData.lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.Subtractive);context.ExecuteCommandBuffer(cmd);CommandBufferPool.Release(cmd);}}
}

这里要注意的是lightAttenuation.w,这里改为了只接收cosOuterAngle,因为我们只判断他来决定聚光灯横向范围。

另外就是

var lights = Light.GetLights(LightType.Spot, 0);if (lightData.visibleLights.Count == 0 && lights.Length != 0){cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lights[0].color);cmd.SetGlobalInt(LightConstantBuffer._MainLightType, 0);}else{cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lightColor);cmd.SetGlobalInt(LightConstantBuffer._MainLightType, lightType);}

这段,因为管线拿的是可见光,如果不可见那么我们的光照类型拿不到就会渲染不正确,所以要给默认类型。

当然可以看到这里看到如果能加上一些体积光会更好。但是要渲染比较好的体积光也需要大量的运算。我这次会忽略他。然后用一个聚光的特效代替,只需要把他放到我们的光源位置就好了,想做得比较好看比较细只需要改变这个特效就好。但要注意的是这个特效的渲染队列要放到前面,不能低于里面要显示的角色

如果要做一个渐变的效果也是可以的,只需要根据cos的插值做渐变就好

#define DarkColorPercent 0.4half3 LightingPointAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{half distance = length(posWorld - lightPos);return finalColor / (max(1.0h, (distance * (1.0h / _MainLightRange))));
}
half3 LightingSpotAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{half3 vertexDir = normalize(posWorld - _MainLightPosition);half3 centerPointDir = (_MainLightSpotDir.xyz);half cDotv = -dot(centerPointDir, vertexDir);half cosIntenve = cDotv - _MainLightAttenuation.w;half dotPoint = min(1, max(DarkColorPercent, cosIntenve + 0.8));//half3 distanceColor = LightingPointAttenuation(finalColor, posWorld, lightPos);half3 circleColor = finalColor * dotPoint;half distance = length(posWorld - lightPos);half lightRange = 45;half lightBlurRate = 0.01;return circleColor * ((cosIntenve < 0 || _MainLightRange - distance < 0) ? max(DarkColorPercent, ((cosIntenve + lightBlurRate) * lightRange)) : min(1, (cosIntenve + lightBlurRate) * lightRange));
}half3 LightingAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{return (_MainLightType >= 0 && _MainLightType < 1) ? LightingSpotAttenuation(finalColor, posWorld, lightPos) : (_MainLightType >= 1 && _MainLightType < 2) ? finalColor : LightingPointAttenuation(finalColor, posWorld, lightPos);
}

补充:

另外如果想要多个聚光灯的话,其实也可以一次消耗多个聚光灯效果。主要就是管线传递多个光照的位置,颜色等信息。然后再采样然后叠加。(只是gpu需要多计算一个光,但是drawcall不会增加)


using System;
using System.Collections.Generic;
using UnityEngine.Experimental.GlobalIllumination;
using UnityEngine.Rendering;namespace UnityEngine.Experimental.Rendering.LightweightPipeline
{/// <summary>/// Configure the shader constants needed by the render pipeline////// This pass configures constants that LWRP uses when rendering./// For example, you can execute this pass before you render opaque/// objects, to make sure that lights are configured correctly./// </summary>public class SetupLightweightConstanstPass : ScriptableRenderPass{static class LightConstantBuffer{public static int _MainLightPosition;public static int _MainLightColor;public static int _MainLightRange;public static int _MainLightType;public static int _MainLightAttenuation;public static int _MainLightSpotDir;public static int _LightCount;//不建立数组,避免堆内存public static int _MainLightColor2;public static int _MainLightType2;public static int _MainLightPosition2;public static int _MainLightRange2;public static int _MainLightAttenuation2;public static int _MainLightSpotDir2;public static int _MainLightColor3;public static int _MainLightType3;public static int _MainLightPosition3;public static int _MainLightRange3;public static int _MainLightAttenuation3;public static int _MainLightSpotDir3;public static int _MainLightColor4;public static int _MainLightType4;public static int _MainLightPosition4;public static int _MainLightRange4;public static int _MainLightAttenuation4;public static int _MainLightSpotDir4;public static int _AdditionalLightsCount;public static int _AdditionalLightsPosition;public static int _AdditionalLightsColor;public static int _AdditionalLightsAttenuation;public static int _AdditionalLightsSpotDir;public static int _AdditionalLightsBuffer;}const string k_SetupLightConstants = "Setup Light Constants";MixedLightingSetup m_MixedLightingSetup;Vector4 k_DefaultLightPosition = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);Vector4 k_DefaultLightColor = Color.black;Vector4 k_DefaultLightAttenuation = new Vector4(1.0f, 0.0f, 0.0f, 1.0f);Vector4 k_DefaultLightSpotDirection = new Vector4(0.0f, 0.0f, 1.0f, 0.0f);const float k_DefaultLightRange = 10.0f;Vector4[] m_AdditionalLightPositions;Vector4[] m_AdditionalLightColors;Vector4[] m_AdditionalLightAttenuations;Vector4[] m_AdditionalLightSpotDirections;private int maxVisibleAdditionalLights { get; set; }private ComputeBuffer perObjectLightIndices { get; set; }/// <summary>/// Create the pass/// </summary>public SetupLightweightConstanstPass(){LightConstantBuffer._LightCount = Shader.PropertyToID("_LightCount");LightConstantBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition");LightConstantBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor");LightConstantBuffer._MainLightRange = Shader.PropertyToID("_MainLightRange");LightConstantBuffer._MainLightType = Shader.PropertyToID("_MainLightType");LightConstantBuffer._MainLightColor2 = Shader.PropertyToID("_MainLightColor2");LightConstantBuffer._MainLightType2 = Shader.PropertyToID("_MainLightType2");LightConstantBuffer._MainLightPosition2 = Shader.PropertyToID("_MainLightPosition2");LightConstantBuffer._MainLightAttenuation2 = Shader.PropertyToID("_MainLightAttenuation2");LightConstantBuffer._MainLightSpotDir2 = Shader.PropertyToID("_MainLightSpotDir2");LightConstantBuffer._MainLightRange2 = Shader.PropertyToID("_MainLightRange2");LightConstantBuffer._MainLightColor3 = Shader.PropertyToID("_MainLightColor3");LightConstantBuffer._MainLightType3 = Shader.PropertyToID("_MainLightType3");LightConstantBuffer._MainLightPosition3 = Shader.PropertyToID("_MainLightPosition3");LightConstantBuffer._MainLightRange3 = Shader.PropertyToID("_MainLightRange3");LightConstantBuffer._MainLightAttenuation3 = Shader.PropertyToID("_MainLightAttenuation3");LightConstantBuffer._MainLightSpotDir3 = Shader.PropertyToID("_MainLightSpotDir3");LightConstantBuffer._MainLightColor4 = Shader.PropertyToID("_MainLightColor4");LightConstantBuffer._MainLightType4 = Shader.PropertyToID("_MainLightType4");LightConstantBuffer._MainLightPosition4 = Shader.PropertyToID("_MainLightPosition4");LightConstantBuffer._MainLightRange4 = Shader.PropertyToID("_MainLightRange4");LightConstantBuffer._MainLightAttenuation4 = Shader.PropertyToID("_MainLightAttenuation4");LightConstantBuffer._MainLightSpotDir4 = Shader.PropertyToID("_MainLightSpotDir4");LightConstantBuffer._MainLightAttenuation = Shader.PropertyToID("_MainLightAttenuation");LightConstantBuffer._MainLightSpotDir = Shader.PropertyToID("_MainLightSpotDir");LightConstantBuffer._AdditionalLightsCount = Shader.PropertyToID("_AdditionalLightsCount");LightConstantBuffer._AdditionalLightsPosition = Shader.PropertyToID("_AdditionalLightsPosition");LightConstantBuffer._AdditionalLightsColor = Shader.PropertyToID("_AdditionalLightsColor");LightConstantBuffer._AdditionalLightsAttenuation = Shader.PropertyToID("_AdditionalLightsAttenuation");LightConstantBuffer._AdditionalLightsSpotDir = Shader.PropertyToID("_AdditionalLightsSpotDir");LightConstantBuffer._AdditionalLightsBuffer = Shader.PropertyToID("_AdditionalLightsBuffer");m_AdditionalLightPositions = new Vector4[0];m_AdditionalLightColors = new Vector4[0];m_AdditionalLightAttenuations = new Vector4[0];m_AdditionalLightSpotDirections = new Vector4[0];}/// <summary>/// Configure the pass/// </summary>/// <param name="maxVisibleAdditionalLights">Maximum number of visible additional lights</param>/// <param name="perObjectLightIndices">Buffer holding per object light indicies</param>public void Setup(int maxVisibleAdditionalLights, ComputeBuffer perObjectLightIndices){this.maxVisibleAdditionalLights = maxVisibleAdditionalLights;this.perObjectLightIndices = perObjectLightIndices;if (m_AdditionalLightColors.Length != maxVisibleAdditionalLights){m_AdditionalLightPositions = new Vector4[maxVisibleAdditionalLights];m_AdditionalLightColors = new Vector4[maxVisibleAdditionalLights];m_AdditionalLightAttenuations = new Vector4[maxVisibleAdditionalLights];m_AdditionalLightSpotDirections = new Vector4[maxVisibleAdditionalLights];}}void InitializeLightConstants(List<VisibleLight> lights, int lightIndex, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightAttenuation, out Vector4 lightSpotDir, out float lightRange, out int lightType){lightPos = k_DefaultLightPosition;lightColor = k_DefaultLightColor;lightAttenuation = k_DefaultLightAttenuation;lightSpotDir = k_DefaultLightSpotDirection;lightRange = k_DefaultLightRange;lightType = 1;// When no lights are visible, main light will be set to -1.// In this case we initialize it to default values and returnif (lightIndex < 0)return;VisibleLight lightData = lights[lightIndex];if (lightData.lightType == LightType.Directional){Vector4 dir = -lightData.localToWorld.GetColumn(2);lightPos = new Vector4(dir.x, dir.y, dir.z, k_DefaultLightAttenuation.w);}else{Vector4 pos = lightData.localToWorld.GetColumn(3);lightPos = new Vector4(pos.x, pos.y, pos.z, k_DefaultLightAttenuation.w);lightRange = lightData.range;}lightType = (int)lightData.lightType;// VisibleLight.finalColor already returns color in active color spacelightColor = lightData.finalColor;// Directional Light attenuation is initialize so distance attenuation always be 1.0if (lightData.lightType != LightType.Directional){// Light attenuation in lightweight matches the unity vanilla one.// attenuation = 1.0 / distanceToLightSqr// We offer two different smoothing factors.// The smoothing factors make sure that the light intensity is zero at the light range limit.// The first smoothing factor is a linear fade starting at 80 % of the light range.// smoothFactor = (lightRangeSqr - distanceToLightSqr) / (lightRangeSqr - fadeStartDistanceSqr)// We rewrite smoothFactor to be able to pre compute the constant terms below and apply the smooth factor// with one MAD instruction// smoothFactor =  distanceSqr * (1.0 / (fadeDistanceSqr - lightRangeSqr)) + (-lightRangeSqr / (fadeDistanceSqr - lightRangeSqr)//                 distanceSqr *           oneOverFadeRangeSqr             +              lightRangeSqrOverFadeRangeSqr// The other smoothing factor matches the one used in the Unity lightmapper but is slower than the linear one.// smoothFactor = (1.0 - saturate((distanceSqr * 1.0 / lightrangeSqr)^2))^2float lightRangeSqr = lightData.range * lightData.range;float fadeStartDistanceSqr = 0.8f * 0.8f * lightRangeSqr;float fadeRangeSqr = (fadeStartDistanceSqr - lightRangeSqr);float oneOverFadeRangeSqr = 1.0f / fadeRangeSqr;float lightRangeSqrOverFadeRangeSqr = -lightRangeSqr / fadeRangeSqr;float oneOverLightRangeSqr = 1.0f / Mathf.Max(0.0001f, lightData.range * lightData.range);// On mobile: Use the faster linear smoothing factor.// On other devices: Use the smoothing factor that matches the GI.lightAttenuation.x = Application.isMobilePlatform ? oneOverFadeRangeSqr : oneOverLightRangeSqr;lightAttenuation.y = lightRangeSqrOverFadeRangeSqr;}else{lightRange = 99999;}if (lightData.lightType == LightType.Spot){Vector4 dir = lightData.localToWorld.GetColumn(2);lightSpotDir = new Vector4(-dir.x, -dir.y, -dir.z, 0.0f);// Spot Attenuation with a linear falloff can be defined as// (SdotL - cosOuterAngle) / (cosInnerAngle - cosOuterAngle)// This can be rewritten as// invAngleRange = 1.0 / (cosInnerAngle - cosOuterAngle)// SdotL * invAngleRange + (-cosOuterAngle * invAngleRange)// If we precompute the terms in a MAD instructionfloat cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.spotAngle * 0.5f);// We neeed to do a null check for particle lights// This should be changed in the future// Particle lights will use an inline functionfloat cosInnerAngle;if (lightData.light != null)cosInnerAngle = Mathf.Cos(LightmapperUtils.ExtractInnerCone(lightData.light) * 0.5f);elsecosInnerAngle = Mathf.Cos((2.0f * Mathf.Atan(Mathf.Tan(lightData.spotAngle * 0.5f * Mathf.Deg2Rad) * (64.0f - 18.0f) / 64.0f)) * 0.5f);float smoothAngleRange = Mathf.Max(0.001f, cosInnerAngle - cosOuterAngle);float invAngleRange = 1.0f / smoothAngleRange;float add = -cosOuterAngle * invAngleRange;lightAttenuation.z = invAngleRange;lightAttenuation.w = cosOuterAngle;//add;}Light light = lightData.light;// TODO: Add support to shadow maskif (light != null && light.bakingOutput.mixedLightingMode == MixedLightingMode.Subtractive && light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed){if (m_MixedLightingSetup == MixedLightingSetup.None && lightData.light.shadows != LightShadows.None){m_MixedLightingSetup = MixedLightingSetup.Subtractive;// In subtractive light mode, main light direct contribution is baked on lightmap// In this case we setup light position w component as 0.0f so we can remove it's contribution// from realtime light computationif (lightData.lightType == LightType.Directional)lightPos.w = 0.0f;}}}void SetupShaderLightConstants(CommandBuffer cmd, ref LightData lightData){float lightRange = 0;int lightType = 1;// Clear to default all light constant datafor (int i = 0; i < maxVisibleAdditionalLights; ++i)InitializeLightConstants(lightData.visibleLights, -1, out m_AdditionalLightPositions[i],out m_AdditionalLightColors[i],out m_AdditionalLightAttenuations[i],out m_AdditionalLightSpotDirections[i],out lightRange,out lightType);m_MixedLightingSetup = MixedLightingSetup.None;// Main light has an optimized shader path for main light. This will benefit games that only care about a single light.// Lightweight pipeline also supports only a single shadow light, if available it will be the main light.SetupMainLightConstants(cmd, ref lightData);SetupAdditionalLightConstants(cmd, ref lightData);}void SetupMainLightConstants(CommandBuffer cmd, ref LightData lightData){Vector4 lightPos, lightColor, lightAttenuation, lightSpotDir;float lightRange = 0.0f;int lightType = 1;InitializeLightConstants(lightData.visibleLights, lightData.mainLightIndex, out lightPos, out lightColor, out lightAttenuation, out lightSpotDir,out lightRange, out lightType);if (lightData.visibleLights.Count == 0){var lights = Light.GetLights(LightType.Spot, 0);if (lights.Length != 0){cmd.SetGlobalVector(LightConstantBuffer._MainLightColor, lights[0].color);cmd.SetGlobalInt(LightConstantBuffer._MainLightType, 0);cmd.SetGlobalVector(LightConstantBuffer._MainLightPosition, lightPos);cmd.SetGlobalFloat(LightConstantBuffer._MainLightRange, lightRange);cmd.SetGlobalVector(LightConstantBuffer._MainLightAttenuation, lightAttenuation);cmd.SetGlobalVector(LightConstantBuffer._MainLightSpotDir, lightSpotDir);}}else{//不存在的灯要设置为默认值for (int i = lightData.visibleLights.Count; i <= 3; i++){SetLightPro(cmd, i);}//if (lightData.visibleLights.Count >= 2){//多光源处理SetLightDataToRealLight(cmd, lightData);}//else//{//    //只是主光源//    SetLightPro(cmd, 0, lightColor,lightType,lightPos,lightRange,lightAttenuation,lightSpotDir);//}}cmd.SetGlobalInt(LightConstantBuffer._LightCount, lightData.visibleLights.Count); }void SetLightDataToRealLight(CommandBuffer cmd, LightData lightData){int directionalType = 0;for (int i = 0; i < lightData.visibleLights.Count; i++){Vector4 pos = lightData.visibleLights[i].localToWorld.GetColumn(3);Vector4 lightPos = new Vector4(pos.x, pos.y, pos.z, 1);Vector4 attenuation = k_DefaultLightAttenuation;float cosOuterAngle = Mathf.Cos(Mathf.Deg2Rad * lightData.visibleLights[i].spotAngle * 0.5f);attenuation.w = cosOuterAngle;Vector4 spotDir = k_DefaultLightSpotDirection;Vector4 dir = lightData.visibleLights[i].localToWorld.GetColumn(2);spotDir.x = -dir.x;spotDir.y = -dir.y;spotDir.z = -dir.z;spotDir.w = 0;//最多只支持一盏平行光if (lightData.visibleLights[i].lightType == LightType.Directional){++directionalType;if (directionalType > 1){continue;}}SetLightPro(cmd, i, lightData.visibleLights[i].finalColor,(int)lightData.visibleLights[i].lightType,lightPos,lightData.visibleLights[i].range,attenuation, spotDir);}}Vector4 zeroVector4 = new Vector4(0, 0, 0, 0);void SetLightPro(CommandBuffer cmd, int index, Vector4 lightColor = default(Vector4), int lightType = 0,Vector4 lightPos = default(Vector4), float lightRange = 10.0f,Vector4 lightAttenuation = default(Vector4),Vector4 lightSpotDir = default(Vector4)){if (lightColor == default(Vector4))lightColor = k_DefaultLightColor;lightType = lightType;if (lightPos == default(Vector4))lightPos = k_DefaultLightPosition;lightRange = k_DefaultLightRange;if (lightAttenuation == default(Vector4))lightAttenuation = k_DefaultLightAttenuation;if (lightSpotDir == default(Vector4))lightSpotDir = k_DefaultLightSpotDirection;int lightColorPro = LightConstantBuffer._MainLightColor;int lightTypePro = LightConstantBuffer._MainLightType;int lightPosPro = LightConstantBuffer._MainLightPosition;int lightRangePro = LightConstantBuffer._MainLightRange;int lightAttenuationPro = LightConstantBuffer._MainLightAttenuation;int lightSpotDirPro = LightConstantBuffer._MainLightSpotDir;if (index == 1){lightColorPro = LightConstantBuffer._MainLightColor2;lightTypePro = LightConstantBuffer._MainLightType2;lightPosPro = LightConstantBuffer._MainLightPosition2;lightRangePro = LightConstantBuffer._MainLightRange2;lightAttenuationPro = LightConstantBuffer._MainLightAttenuation2;lightSpotDirPro = LightConstantBuffer._MainLightSpotDir2;}else if (index == 2){lightColorPro = LightConstantBuffer._MainLightColor3;lightTypePro = LightConstantBuffer._MainLightType3;lightPosPro = LightConstantBuffer._MainLightPosition3;lightRangePro = LightConstantBuffer._MainLightRange3;lightAttenuationPro = LightConstantBuffer._MainLightAttenuation3;lightSpotDirPro = LightConstantBuffer._MainLightSpotDir3;}else if (index == 3){lightColorPro = LightConstantBuffer._MainLightColor4;lightTypePro = LightConstantBuffer._MainLightType4;lightPosPro = LightConstantBuffer._MainLightPosition4;lightRangePro = LightConstantBuffer._MainLightRange4;lightAttenuationPro = LightConstantBuffer._MainLightAttenuation4;lightSpotDirPro = LightConstantBuffer._MainLightSpotDir4;}cmd.SetGlobalVector(lightColorPro, lightColor);cmd.SetGlobalInt(lightTypePro, lightType);cmd.SetGlobalVector(lightPosPro, lightPos);cmd.SetGlobalFloat(lightRangePro, lightRange);cmd.SetGlobalVector(lightAttenuationPro, lightAttenuation);cmd.SetGlobalVector(lightSpotDirPro, lightSpotDir);}void SetupAdditionalLightConstants(CommandBuffer cmd, ref LightData lightData){List<VisibleLight> lights = lightData.visibleLights;if (lightData.additionalLightsCount > 0){int additionalLightsCount = 0;for (int i = 0; i < lights.Count && additionalLightsCount < maxVisibleAdditionalLights; ++i){VisibleLight light = lights[i];if (light.lightType != LightType.Directional){float lightRange = 0.0f;int lightType = 1;InitializeLightConstants(lights, i, out m_AdditionalLightPositions[additionalLightsCount],out m_AdditionalLightColors[additionalLightsCount],out m_AdditionalLightAttenuations[additionalLightsCount],out m_AdditionalLightSpotDirections[additionalLightsCount],out lightRange,out lightType);additionalLightsCount++;}}cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, new Vector4(lightData.maxPerObjectAdditionalLightsCount,0.0f, 0.0f, 0.0f));// if not using a compute buffer, engine will set indices in 2 vec4 constants// unity_4LightIndices0 and unity_4LightIndices1if (perObjectLightIndices != null)cmd.SetGlobalBuffer(LightConstantBuffer._AdditionalLightsBuffer, perObjectLightIndices);}else{cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightsCount, Vector4.zero);}cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsPosition, m_AdditionalLightPositions);cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsColor, m_AdditionalLightColors);cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsAttenuation, m_AdditionalLightAttenuations);cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightsSpotDir, m_AdditionalLightSpotDirections);}/// <inheritdoc/>public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData){if (renderer == null)throw new ArgumentNullException("renderer");int additionalLightsCount = renderingData.lightData.additionalLightsCount;bool additionalLightsPerVertex = renderingData.lightData.shadeAdditionalLightsPerVertex;CommandBuffer cmd = CommandBufferPool.Get(k_SetupLightConstants);SetupShaderLightConstants(cmd, ref renderingData.lightData);CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsVertex, additionalLightsCount > 0 && additionalLightsPerVertex);CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.AdditionalLightsPixel, additionalLightsCount > 0 && !additionalLightsPerVertex);CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MixedLightingSubtractive, renderingData.lightData.supportsMixedLighting && m_MixedLightingSetup == MixedLightingSetup.Subtractive);context.ExecuteCommandBuffer(cmd);CommandBufferPool.Release(cmd);}}
}

其中

//多光源处理SetLightDataToRealLight(cmd, lightData);

这里是对多个光照传递的数据处理。

还得做平衡光限制

//最多只支持一盏平行光if (lightData.visibleLights[i].lightType == LightType.Directional){++directionalType;if (directionalType > 1){continue;}}
half _LightCount;half4 _MainLightColor2;
half _MainLightType2;
half4 _MainLightPosition2;
half _MainLightRange2;
half4 _MainLightAttenuation2;
half4 _MainLightSpotDir2;
half4 _MainLightColor3;
half _MainLightType3;
half4 _MainLightPosition3;
half _MainLightRange3;
half4 _MainLightAttenuation3;
half4 _MainLightSpotDir3;
half4 _MainLightColor4;
half _MainLightType4;
half4 _MainLightPosition4;
half _MainLightRange4;
half4 _MainLightAttenuation4;
half4 _MainLightSpotDir4;half DarkColorPercentHandle(half num)
{return 0.5 - (num * 0.08);
}half3 LightingPointAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{half distance = length(posWorld - lightPos);return finalColor / (max(1.0h, (distance * (1.0h / _MainLightRange))));
}
half3 LightingSpotAttenuation(half3 finalColor, half4 lightColor, half3 posWorld, half3 lightPos, half3 spotDir, half4 attenuation, half range)
{half3 vertexDir = normalize(posWorld - lightPos);half3 centerPointDir = (spotDir.xyz);half cDotv = -dot(centerPointDir, vertexDir);half cosIntenve = cDotv - attenuation.w;half DarkColorPercent = DarkColorPercentHandle(_LightCount);half dotPoint = min(1, max(DarkColorPercent, cosIntenve + 0.8));//half3 distanceColor = LightingPointAttenuation(finalColor, posWorld, lightPos);half3 circleColor = finalColor * dotPoint * lightColor.rgb;half distance = length(posWorld - lightPos);half lightRange = 45;half lightBlurRate = 0.01;return circleColor * ((cosIntenve < 0 || range - distance < 0) ? max(DarkColorPercent, ((cosIntenve + lightBlurRate) * lightRange)) : min(1, (cosIntenve + lightBlurRate) * lightRange));
}half3 LightingAttenuationHandle(half type, half4 lightColor, half3 finalColor, half3 posWorld, half3 lightPos, half3 spotDir, half4 attenuation, half range)
{//(lightColor.r == 0 && lightColor.g == 0 && lightColor.b == 0)这个是因为如果是黑色就不需要运算了,直接返回return (lightColor.r == 0 && lightColor.g == 0 && lightColor.b == 0) ? 0 : (type >= 0 && type < 1) ? LightingSpotAttenuation(finalColor, lightColor, posWorld, lightPos, spotDir, attenuation, range) : (type >= 1 && type < 2) ? finalColor : LightingPointAttenuation(finalColor, posWorld, lightPos);
}
half3 LightingAttenuation(half3 finalColor, half3 posWorld, half3 lightPos)
{return LightingAttenuationHandle(_MainLightType, _MainLightColor, finalColor, posWorld, _MainLightPosition.xyz, _MainLightSpotDir, _MainLightAttenuation, _MainLightRange) +max(0, _LightCount - 1) * LightingAttenuationHandle(_MainLightType2, _MainLightColor2, finalColor, posWorld, _MainLightPosition2.xyz, _MainLightSpotDir2, _MainLightAttenuation2, _MainLightRange2) +max(0, _LightCount - 2) * LightingAttenuationHandle(_MainLightType3, _MainLightColor3, finalColor, posWorld, _MainLightPosition3.xyz, _MainLightSpotDir3, _MainLightAttenuation3, _MainLightRange3) +max(0, _LightCount - 3) * LightingAttenuationHandle(_MainLightType4, _MainLightColor4, finalColor, posWorld, _MainLightPosition4.xyz, _MainLightSpotDir4, _MainLightAttenuation4, _MainLightRange4);
}

shader部分主要是下面这个把两个光源叠加

return LightingAttenuationHandle(_MainLightType, _MainLightColor, finalColor, posWorld, _MainLightPosition.xyz, _MainLightSpotDir, _MainLightAttenuation, _MainLightRange) +max(0, _LightCount - 1) * LightingAttenuationHandle(_MainLightType2, _MainLightColor2, finalColor, posWorld, _MainLightPosition2.xyz, _MainLightSpotDir2, _MainLightAttenuation2, _MainLightRange2) +max(0, _LightCount - 2) * LightingAttenuationHandle(_MainLightType3, _MainLightColor3, finalColor, posWorld, _MainLightPosition3.xyz, _MainLightSpotDir3, _MainLightAttenuation3, _MainLightRange3) +max(0, _LightCount - 3) * LightingAttenuationHandle(_MainLightType4, _MainLightColor4, finalColor, posWorld, _MainLightPosition4.xyz, _MainLightSpotDir4, _MainLightAttenuation4, _MainLightRange4);

lwrp聚光灯实现方式相关推荐

  1. 3dmax基础教程:聚光灯的创建及调整方式

    聚光灯具有一定的照射范围和照射角度,会被物体所遮蔽而产生阴影效果.本教程属于3dmax的基础知识:针对聚光灯的创建及调整方式进行了具体介绍,主要从聚光灯的创建.聚光灯的调整方式两个方面进行讲解. 一. ...

  2. Unity3d轻量渲染管线(LWRP)民间文档

    转载:https://blog.csdn.net/weixin_42163773/article/details/84317223 近日在学习Unity3d的SRP,由于官方未正式发布,故几乎没有文档 ...

  3. opengl 教程(21) 聚光灯

    原帖地址:http://ogldev.atspace.co.uk/www/tutorial21/tutorial21.html 本篇教程中,我们来学习聚光灯的的光照效果,聚光灯有光源位置,也会随着传播 ...

  4. element selection选中变颜色_Excel | 聚光灯效果(阅读模式)——改变当前行和列的颜色...

    问题来源: 一位朋友说他经常做一些数据量很大的表格,问我能不能把工作表做成阅读模式,即鼠标点到哪个单元格,该单元格对应的行和列都同时变成一种颜色.这种模式能帮他快速准确定位和修改相应数据. 这种阅读模 ...

  5. 走进LWRP(Universal RP)的世界

    走进LWRP(Universal RP)的世界 原文:https://connect.unity.com/p/zou-jin-lwrp-universal-rp-de-shi-jie LWRP自Uni ...

  6. WPF实现聚光灯效果

    WPF开发者QQ群: 340500857  | 微信群 -> 进入公众号主页 加入组织 欢迎转发.分享.点赞.在看,谢谢~. 前言 效果仿照 CSS聚光灯效果 https://www.jians ...

  7. unity 烘焙参数 设置_Unity通用渲染管线(URP)系列(九)——点光源和聚光灯

    200+篇教程总入口,欢迎收藏: 放牛的星星:[教程汇总+持续更新]Unity从入门到入坟--收藏这一篇就够了​zhuanlan.zhihu.com 本文重点内容: 1.支持更多类型的灯光 2.包含实 ...

  8. unity产生阴影的几种方式

    这里说一下unity中产生阴影的几种常见方式,性能不一,效果不一: 1:贴图方式 在模型下方贴一张阴影贴图作为子物体,因为不涉及光照,故其性能消耗几乎可以省略.但是当模型要投影到水平面不一样高的地面上 ...

  9. 【three.js:语法】光源使用详解2-3(聚光灯 SpotLight、平行光 DirectionLight 、环境光 HemisphereLight、镜头光晕 LensFlare)

    注意点:SpotLight.target 的使用. 1.SpotLight.target= object 或者是 THREE.Object3D()才行.不能只是一个position. 2.target ...

最新文章

  1. 01_创建一个新的activityactivity配置清单文件
  2. TensorFlow人工智能引擎入门教程之十 最强网络 RSNN深度残差网络 平均准确率96-99%
  3. Sleep()和wait()方法的区别
  4. 办公室自动化系统_RPA:办公自动化的下一站
  5. 作为JBoss AS 7模块运行Drools 5.4.0 Final
  6. mysql自增id获取失败
  7. php5.3 sql server,php5.3连接sqlserver2005
  8. 用jQuery和css3实现的一个模仿淘宝ued博客左边的菜单切换动画效果
  9. TensorFlow 入门 | iBooker·ApacheCN
  10. 电脑常见问题_解决PC常见问题 篇二十:垃圾佬手把手教你如何正确缩电脑配置砍预算...
  11. 基于麻雀算法优化的Tsallis相对熵图像多阈值分割 -附代码
  12. 电磁波中的波段划分:L波段、S波段、C波段、X波段、Ku波段、K波段、Ka波段 等等
  13. windows共享时出现“指定网络名不再可用”解决办法
  14. Graphics2D绘制图片,线段、矩形、圆形
  15. AutoIT - 加域工具
  16. lay和lied_高考英语词汇辨析:lie, lay, lain, laid, lying等用法
  17. 小程序在线客服完整实现
  18. tkinter如何绑定鼠标和键盘等事件
  19. python所有内置函数的简单使用
  20. 运维-系统监控方案:基于Grafana的TDengine零依赖监控解决方案

热门文章

  1. 哪些浏览器功能泄漏浏览器隐私
  2. 微软的服务器抽风,微软抽风对Windows 10弹窗:用户都尴尬了!
  3. C++最经典打怪游戏代码,(高仿),完美复原原游戏,好玩到停不下来
  4. 05-数据库_JDBC
  5. 手机连接电脑传文件的两种方法详解
  6. ensp改变交换机接口类型
  7. ruby-prepend
  8. 游戏运营平台的盈利方式有哪些
  9. 魅蓝e android版本,魅蓝E有几个版本?魅蓝E有电信版吗?
  10. Linux 的md5sum命令