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

相关文章

(超详细)YOLOV7改进-Soft-NMS(支持多种IoU变种选择)

1.在until/general.py文件最后加上下面代码 2.在general.py里面找到这代码,修改这两个地方 3.之后直接运行即可

【Unity Shader】片段着色器(Fragment Shader)的概念及其使用方法

在Unity和图形编程中,片段着色器(Fragment Shader)是渲染管线中的一个阶段,负责计算屏幕上每个像素(片段)的颜色和特性。片段着色器通常在顶点着色器和任何几何处理之后运行,是决定最终像素颜色的关键步骤。 Fragment Shader的概念: 像素处理:片段着色器处理经过顶点着色器和几何着色器处理后,映射到屏幕空间的像素。颜色计算:它计算每个像素的颜色值,这可能包括纹理采样、光

【Unity Shader】Alpha Blend(Alpha混合)的概念及其使用示例

在Unity和图形编程中,Alpha Blend(也称为Alpha混合)是一种用于处理像素透明度的技术。它允许像素与背景像素融合,从而实现透明或半透明的效果。Alpha Blend在渲染具有透明度的物体(如窗户、玻璃、水、雾等)时非常重要。 Alpha Blend的概念: Alpha值:Alpha值是一个介于0(完全透明)和1(完全不透明)的数值,用于表示像素的透明度。混合模式:Alpha B

Apple - Media Playback Programming Guide

本文翻译整理自:Media Playback Programming Guide(Updated: 2018-01-16 https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/Introduction

OSG学习:阴影代码示例

效果图: 代码示例: #include <osgViewer/Viewer>#include <osg/Node>#include <osg/Geode>#include <osg/Group>#include <osg/Camera>#include <osg/ShapeDrawable>#include <osg/ComputeBoundsVisitor>#include

Unity Meta Quest 开发:关闭 MR 应用的安全边界

社区链接: SpatialXR社区:完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子 📕教程说明 这期教程我将介绍如何在应用中关闭 Quest 系统的安全边界。 视频讲解: https://www.bilibili.com/video/BV1Gm42157Zi 在 Unity 中导入 Meta XR SDK,进行环境配置后,打开 Assets > Plugins > An

Unity 字体创建时候容易导致字体文件不正确的一种情况

上面得到了两种字体格式,一种是TextMeshPro的,另一种是Unity UI系统中默认使用的字体资源。其原因是创建的位置不同导致的。 1.下面是TextMeshPro字体创建的位置 2:下面是Unity UI系统中默认使用的字体资源

摄像头画面显示于unity场景

🐾 个人主页 🐾 🪧阿松爱睡觉,横竖醒不来 🏅你可以不屠龙,但不能不磨剑🗡 目录 一、前言二、UI画面三、显示于场景四、结语 一、前言 由于标题限制,这篇文章主要是讲在unity中调用摄像头,然后将摄像头捕捉到的画面显示到场景中,无论是UI画面还是场景中的某个物体上;至于应用的场景可以用于AR增强现实。 那么话不多说,直接开始今

unity开发 --------- NGUI (UITable)

unity开发 --------- NGUI UITable与UIGrid相似,都是实现自动排序的。但UIGrid的元素大小是由我们来指定的,而Table中的元素的大小是根据元素本身计算出来的。 UITable还保存了元素的顺序List<Transform>。每次重排序,都会更新此List。除了要计算元素的Bound和保存List外,其他基本与UIGrid一致。 unity开发 ---

unity开发 --------- NGUI (UIGrid)

unity开发 --------- NGUI  UIGrid可以实现多个gameobject自动排序。可以设定其排序方向、每个元素的宽度,高度等。 public Arrangement arrangement = Arrangement.Horizontal;public int maxPerLine = 0;public float cellWi