Cg Programming/Unity/Soft Shadows of Spheres球体的软阴影

2024-03-11 09:08

本文主要是介绍Cg Programming/Unity/Soft Shadows of Spheres球体的软阴影,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本教程介绍了球体的软阴影。

这里写图片描述

这是有关光照一系列教程中的一章,它已经超出了Phone反射模型的范围,而Phone反射模型是一个局部照明模型并且没有把阴影考虑进来。目前的技术可以在任何网格上渲染单个球体的软阴影,并且跟Orion Sky Lawlor提出的技术多少有点关系(查看下面“扩展阅读”的部分)。着色器可以扩展到以渲染性能为代价来渲染少量球体的阴影;但是,它并不能轻易地被应用到任何种类的阴影投射器上。潜在的应用就是电脑球类游戏(在里面球通常是唯一需要软阴影的物体并且也是向其它所有物体投射动态阴影的唯一物体),具有球形主角的电脑游戏(比如“疯狂弹珠”),只包含球体的可视化(行星可视化,小原子核,原子或分子的球状模型等等。),或者填充着球体并受益于软阴影的测试场景。

软阴影

这里写图片描述
方向光源和点光源会产生硬阴影,而任何面光源会产生软阴影。这对所有真实光源来说也是这样的,特别是太阳和任何灯泡或者台灯。在阴影投射器后面的一些点上,光源的任何部分都是可见的,并且阴影是完全黑暗的:这叫做本影。从其他点来看,光源或多或少是可见的,因此阴影更少或更完整:这叫做半影。光源的整个区域有可见的点:这些点在阴影外面的。

这里写图片描述

在大多数情况下,阴影的柔和度主要取决于阴影投射器跟阴影接收器之前的距离;距离越远,阴影越柔和。这是一个众所周知的艺术效果;参见上面卡拉瓦乔的那幅画。

这里写图片描述

参见上图,我们将要近似地计算表面上某点的阴影,当在S处(跟表面点相关)半径为这里写图片描述的球体不包括L处(相对于表面点)半径这里写图片描述的球形光源。

我们考虑球体上的通过表面点的切线方向T。此外,这条切线在跨越L和S的面之间选择,即平行上图所示的观察平面。最重要的观察在于光源中心点和这条切线之间的最短距离d跟表面点阴影的量有直接关系,因为它决定了从表面点可以看到的光源面积有多大。更精确地说,我们需要一个有符号的距离(如果切线与L在球体的同一边就是正的,否则就是负的)来决定表面点是否在本影(这里写图片描述)、半影(这里写图片描述)或阴影的外面(这里写图片描述)。

对于d的计算,我们要考虑L和S以及T和S之间的角度。这两个角度之间的差值就是L和T之间的角度,它跟d是相关的:
这里写图片描述
这样,我们得到:
这里写图片描述
我们可以使用下式来计算T和S之间的角度:
这里写图片描述
于是:
这里写图片描述
对于L和S之间的角度,我们可以使用叉乘的特性:
这里写图片描述
因此:
这里写图片描述
综合一下,我们得到:
这里写图片描述
目前为止我们所做的跟近似没多大关系;更重要的是它并没有产生渲染。如果性能是个问题,我们可以更近一步,使用arcsin(x) ≈ x;也就是我们可以使用:
这里写图片描述

这就避免了所有三角函数;但是,这会产生渲染工件(特别是如果镜面高光在面向光源的半影中)。无论这些渲染工件是否值得在性能上有所收获,都必须为每一个案例做出决定。

下面我们会看看如何基于d来计算阴影的层次。当d从这里写图片描述增加到这里写图片描述,w应该从0增加到1。换句话说,在这里写图片描述的值为-1和1之间时,我们想要一个从0到1的平滑值。达到这个目的最有效的办法很有可能就是内置Cg函数提供的艾米插值smoothstep(a,b,x) = t*t*(3-2*t),里面t=clamp((x-a)/(b-a),0,1)
这里写图片描述

虽然这并不是w和d之间基于物理关系的一个特别好的近似,它仍然可以得到基本正确的值。

此外,如果光线方向L跟在S相反方向的话w应该等于0;即它们的点乘是负数。这种情况是有点棘手的,因为它会导致L和S垂直的平面上明显的不连续性。为了减弱这种不连续性,我们可以再次使用smoothstep来计算一个改进的值w’:
这里写图片描述

另外,如果光源比遮挡球更接近表面点的话我们必须设置w’为0。这同样也有点棘手,因为球形光源会跟阴影相交 - 投射球体。一种避免太明显工件的解决方案(但示能解决完全交叉问题)是:
这里写图片描述

在方向光源的情况下,我们只需要设置w” = w’。然后(1 - w”)这项,它指定了无阴影照明的等级,应该用光源乘以任何照明。(于是,环境光源就不应该乘以这个系数。)如果多个阴影投射器的阴影被计算,所有阴影投射器的(1-w”)这项都必须跟每个光源结合起来。通常的方法是乘法,尽管这可能不准确(特别是如果本影是重叠的)。

实现

这个实现会计算lightDirectionsphereDirection向量的长度,然后会用归一化的向量来处理。这样,这些向量的长度必须只被计算一次,并且我们甚至要避免除法因为我们使用了归一化向量。这里是片元着色器最重要的一部分:

            // computation of level of shadowing w  float3 sphereDirection = _SpherePosition.xyz - input.posWorld.xyz;float sphereDistance = length(sphereDirection);sphereDirection = sphereDirection / sphereDistance;float d = lightDistance * (asin(min(1.0, length(cross(lightDirection, sphereDirection)))) - asin(min(1.0, _SphereRadius / sphereDistance)));float w = smoothstep(-1.0, 1.0, -d / _LightSourceRadius);w = w * smoothstep(0.0, 0.2, dot(lightDirection, sphereDirection));if (0.0 != _WorldSpaceLightPos0.w) // point light source?{w = w * smoothstep(0.0, _SphereRadius, lightDistance - sphereDistance);}

asin(min(1.0, ...))的使用是为了保证asin的变量在允许的范围内。

完整的着色器代码

完整的源代码为阴影投射体和光源半径定义了属性。所有的值都应该在世界坐标系中。对于额外的光源,光源半径应该用弧度(1 rad = 180° / π)来指定。设置阴影投射体位置和半径的最好办法是一段简短的脚本,它被附着到使用着色器的所有阴影接收物体上,举例来说:

@script ExecuteInEditMode()var occluder : GameObject;function Update () {if (null != occluder) {GetComponent(Renderer).sharedMaterial.SetVector("_SpherePosition", occluder.transform.position);GetComponent(Renderer).sharedMaterial.SetFloat("_SphereRadius", occluder.transform.localScale.x / 2.0);}
}

这个脚本有一个公共变量occluder,它应该被设置到阴影投射器上去。然后它设置了下面着色器中的属性_SpherePostion_SphereRadius(它应该跟脚本一样被挂载到相同的阴影接收物体上)。

Shader "Cg shadow of sphere" {Properties {_Color ("Diffuse Material Color", Color) = (1,1,1,1) _SpecColor ("Specular Material Color", Color) = (1,1,1,1) _Shininess ("Shininess", Float) = 10_SpherePosition ("Sphere Position", Vector) = (0,0,0,1)_SphereRadius ("Sphere Radius", Float) = 1_LightSourceRadius ("Light Source Radius", Float) = 0.005}SubShader {Pass {      Tags { "LightMode" = "ForwardBase" } // pass for ambient light and first light sourceCGPROGRAM#pragma vertex vert  #pragma fragment frag #pragma target 3.0#include "UnityCG.cginc"uniform float4 _LightColor0; // color of light source (from "Lighting.cginc")// User-specified propertiesuniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess;uniform float4 _SpherePosition; // center of shadow-casting sphere in world coordinatesuniform float _SphereRadius; // radius of shadow-casting sphereuniform float _LightSourceRadius; // in radians for directional light sourcesstruct vertexInput {float4 vertex : POSITION;float3 normal : NORMAL;};struct vertexOutput {float4 pos : SV_POSITION;float4 posWorld : TEXCOORD0;float3 normalDir : TEXCOORD1;};vertexOutput vert(vertexInput input) {vertexOutput output;float4x4 modelMatrix = _Object2World;float4x4 modelMatrixInverse = _World2Object;output.posWorld = mul(modelMatrix, input.vertex);output.normalDir = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);output.pos = mul(UNITY_MATRIX_MVP, input.vertex);return output;}float4 frag(vertexOutput input) : COLOR{float3 normalDirection = normalize(input.normalDir);float3 viewDirection = normalize(_WorldSpaceCameraPos - input.posWorld.xyz);float3 lightDirection;float lightDistance;float attenuation;if (0.0 == _WorldSpaceLightPos0.w) // directional light?{attenuation = 1.0; // no attenuationlightDirection = normalize(_WorldSpaceLightPos0.xyz);lightDistance = 1.0;} else // point or spot light{lightDirection = _WorldSpaceLightPos0.xyz - input.posWorld.xyz;lightDistance = length(lightDirection);attenuation = 1.0 / lightDistance; // linear attenuationlightDirection = lightDirection / lightDistance;}// computation of level of shadowing w  float3 sphereDirection = _SpherePosition.xyz - input.posWorld.xyz;float sphereDistance = length(sphereDirection);sphereDirection = sphereDirection / sphereDistance;float d = lightDistance * (asin(min(1.0, length(cross(lightDirection, sphereDirection)))) - asin(min(1.0, _SphereRadius / sphereDistance)));float w = smoothstep(-1.0, 1.0, -d / _LightSourceRadius);w = w * smoothstep(0.0, 0.2, dot(lightDirection, sphereDirection));if (0.0 != _WorldSpaceLightPos0.w) // point light source?{w = w * smoothstep(0.0, _SphereRadius, lightDistance - sphereDistance);}float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb* max(0.0, dot(normalDirection, lightDirection));float3 specularReflection;if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?{specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection}else // light source on the right side{specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), _Shininess);}return float4(ambientLighting + (1.0 - w) * (diffuseReflection + specularReflection), 1.0);}ENDCG}Pass {      Tags { "LightMode" = "ForwardAdd" } // pass for additional light sourcesBlend One One // additive blending CGPROGRAM#pragma vertex vert  #pragma fragment frag #pragma target 3.0#include "UnityCG.cginc"uniform float4 _LightColor0; // color of light source (from "Lighting.cginc")// User-specified propertiesuniform float4 _Color; uniform float4 _SpecColor; uniform float _Shininess;uniform float4 _SpherePosition; // center of shadow-casting sphere in world coordinatesuniform float _SphereRadius; // radius of shadow-casting sphereuniform float _LightSourceRadius; // in radians for directional light sourcesstruct vertexInput {float4 vertex : POSITION;float3 normal : NORMAL;};struct vertexOutput {float4 pos : SV_POSITION;float4 posWorld : TEXCOORD0;float3 normalDir : TEXCOORD1;};vertexOutput vert(vertexInput input) {vertexOutput output;float4x4 modelMatrix = _Object2World;float4x4 modelMatrixInverse = _World2Object;output.posWorld = mul(modelMatrix, input.vertex);output.normalDir = normalize(mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);output.pos = mul(UNITY_MATRIX_MVP, input.vertex);return output;}float4 frag(vertexOutput input) : COLOR{float3 normalDirection = normalize(input.normalDir);float3 viewDirection = normalize(_WorldSpaceCameraPos - input.posWorld.xyz);float3 lightDirection;float lightDistance;float attenuation;if (0.0 == _WorldSpaceLightPos0.w) // directional light?{attenuation = 1.0; // no attenuationlightDirection = normalize(_WorldSpaceLightPos0.xyz);lightDistance = 1.0;} else // point or spot light{lightDirection =_WorldSpaceLightPos0.xyz - input.posWorld.xyz;lightDistance = length(lightDirection);attenuation = 1.0 / lightDistance; // linear attenuationlightDirection = lightDirection / lightDistance;}// computation of level of shadowing w  float3 sphereDirection = _SpherePosition.xyz - input.posWorld.xyz;float sphereDistance = length(sphereDirection);sphereDirection = sphereDirection / sphereDistance;float d = lightDistance * (asin(min(1.0, length(cross(lightDirection, sphereDirection)))) - asin(min(1.0, _SphereRadius / sphereDistance)));float w = smoothstep(-1.0, 1.0, -d / _LightSourceRadius);w = w * smoothstep(0.0, 0.2, dot(lightDirection, sphereDirection));if (0.0 != _WorldSpaceLightPos0.w) // point light source?{w = w * smoothstep(0.0, _SphereRadius, lightDistance - sphereDistance);}float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb* max(0.0, dot(normalDirection, lightDirection));float3 specularReflection;if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?{specularReflection = float3(0.0, 0.0, 0.0); // no specular reflection}else // light source on the right side{specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), _Shininess);}return float4((1.0 - w) * (diffuseReflection + specularReflection), 1.0);}ENDCG}} Fallback "Specular"
}

总结

恭喜!我希望你已经成功地渲染了一些漂亮的软阴影。我们看到了:

  • 什么是软阴影、什么是半影以及什么是本影。
  • 如何计算球体的软阴影。
  • 如何实现这个计算,包括在其它GameObject上用JavaScript的脚本设置一些属性。

这篇关于Cg Programming/Unity/Soft Shadows of Spheres球体的软阴影的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/797360

相关文章

UE5 半透明阴影 快速解决方案

Step 1: 打开该选项 Step 2: 将半透明材质给到模型后,设置光照的Shadow Resolution Scale,越大,阴影的效果越好

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Google Earth Engine——高程数据入门和山体阴影和坡度的使用

目录 山体阴影和坡度 对图像应用计算 应用空间减速器 高程数据 通过从“重置”按钮下拉菜单中选择“清除脚本”来清除脚本。搜索“elevation”并单击 SRTM Digital Elevation Data 30m 结果以显示数据集描述。单击导入,将变量移动到脚本顶部的导入部分。将默认变量名称“image”重命名为“srtm”。使用脚本将图像对象添加到地图: Map

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj

在Unity环境中使用UTF-8编码

为什么要讨论这个问题         为了避免乱码和更好的跨平台         我刚开始开发时是使用VS开发,Unity自身默认使用UTF-8 without BOM格式,但是在Unity中创建一个脚本,使用VS打开,VS自身默认使用GB2312(它应该是对应了你电脑的window版本默认选取了国标编码,或者是因为一些其他的原因)读取脚本,默认是看不到在VS中的编码格式,下面我介绍一种简单快

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)-CSDN博客 做完了数据结构类,该做一个存储类了,也就是生成一个字典类(只是声明)  实现和上一节的数据结构类的方式大同小异,所