我发现着色器代码具有扭曲特定点周围空间的效果。这是一个很酷的效果,但它缺少一些动画,所以我添加了一些内容:
Shader "Marek/BlackHoleDistortion"
{
Properties {
_DistortionStrength ("Distortion Strength", Range(0, 10)) = 0
_Timer("Timer", Range(0, 10)) = 0
_HoleSize ("Hole Size", Range(0, 1)) = 0.1736101
_HoleEdgeSmoothness ("Hole Edge Smoothness", Range(1, 4)) = 4
_ObjectEdgeArtifactFix ("Object Edge Artifact Fix", Range(1, 10)) = 1
}
SubShader {
Tags {
"IgnoreProjector"="True"
"Queue"="Transparent"
"RenderType"="Transparent"
}
GrabPass{ }
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#pragma only_renderers d3d9 d3d11 glcore gles
#pragma target 3.0
uniform sampler2D _GrabTexture;
uniform float _DistortionStrength;
uniform float _HoleSize;
uniform float _HoleEdgeSmoothness;
uniform float _ObjectEdgeArtifactFix;
uniform float _Timer;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float4 posWorld : TEXCOORD0;
float3 normalDir : TEXCOORD1;
float4 projPos : TEXCOORD2;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.normalDir = UnityObjectToWorldNormal(v.normal);
o.posWorld = mul(unity_ObjectToWorld, v.vertex);
o.pos = UnityObjectToClipPos(v.vertex);
o.projPos = ComputeScreenPos(o.pos);
COMPUTE_EYEDEPTH(o.projPos.z);
return o;
}
float4 frag(VertexOutput i) : COLOR {
i.normalDir = normalize(i.normalDir);
float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
float3 normalDirection = i.normalDir;
float2 sceneUVs = (i.projPos.xy / i.projPos.w);
float node_9892 = (_HoleSize * -1.0 + 1.0);
float node_3969 = (1.0 - pow(1.0 - max(0, dot(normalDirection, viewDirection)), clamp(_DistortionStrength - _Timer, 0, _DistortionStrength)));
float node_9136 = (length(float2(ddx(node_3969), ddy(node_3969))) * _HoleEdgeSmoothness);
float node_4918 = pow(node_3969, 6.0);
float node_1920 = (1.0 - smoothstep((node_9892 - node_9136), (node_9892 + node_9136), node_4918));
float3 finalColor = (
lerp(
float4(node_1920, node_1920, node_1920, node_1920),
float4(1, 1, 1, 1),
pow(
pow(1.0 - max(0, dot(normalDirection, viewDirection)), 1.0),
_ObjectEdgeArtifactFix
)
) * tex2D(_GrabTexture, ((node_4918 * (sceneUVs.rg * _Time * -2.0 + 1.0)) + sceneUVs.rg)).rgb).rgb;
return fixed4(finalColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
现在,问题是为了使失真在一定时间后消失,我需要在等式中加入一些变量 - 这里我称之为_Timer
。由于显而易见的原因,我没有使用内置的_Time
- 它是一个不断增长的值,每次使用此着色器的对象生效时,我需要从0开始的东西。传递该参数的C#代码处理如下所示:
public void Update() {
_timeElapsed += Time.deltaTime;
_renderer.material.SetFloat("_Timer", _timeElapsed);
}
问题是 - 我可以做得更好吗?我希望这个着色器的代码更像是一个自包含的东西 - 无需将参数从cs脚本传递给它。
答案 0 :(得分:4)
我可以做得更好吗?
简而言之,是的,不是。如果希望着色器每material
行为不同,则无法避免从C#传递属性。但是,您可以通过传递Update
时间并在着色器中计算start
时间来避免在elapse
中执行此操作。
<强> C#强>
void OnEnable ()
{
_renderer.material.SetFloat("_StartTime", Time.timeSinceLevelLoad);
}
<强>着色强>
uniform float _StartTime;
float4 frag(VertexOutput i) : COLOR
{
float elapse = _Time.y - _StartTime;
}
现在,虽然这将直接与您当前使用的设置相关联,但应注意访问.material
属性将克隆材料(可能会破坏批处理等)。
最近引入MaterialPropertyBlock可以避免这种情况。
答案 1 :(得分:1)
Unity为着色器提供了一些内置值:当前对象的转换矩阵,时间等等。
你只需在ShaderLab中使用它们,就像你使用任何其他属性一样,唯一的区别是你不必在某处声明它 - 它们是“内置的”。
https://docs.unity3d.com/455/Documentation/Manual/SL-BuiltinValues.html
有一种巧妙的方法可以为您提供4种变化值,通过为每个渲染像素重新使用预乘值,可以为您节省乘法运算。有4个值可供使用。
_Time.x = time / 20
_Time.y = time
_Time.z = time * 2
_Time.w = time * 3
这是一个简单的例子,向您展示它是如何工作的:
Shader "Example/Circle"
{
Properties
{
}
SubShader
{
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
float circle(in float2 _st, in float _radius){
float2 dist = distance(_st,float2(0.5,0.5));
float result = step(dist,_radius);
return result;
}
fixed4 frag (v2f i) : SV_Target
{
float WaveTime = sin(_Time.z);
float3 color = float3(1,1,1)*circle(i.uv,WaveTime);
return float4( color, 1.0 );
}
ENDCG
}
}
}
在评论中,您提到要在启用时重置时间值,因此您需要使用脚本初始化时间值。
因此您应该使用自己的时间着色器:
Properties
{
_Timer("Timer",Float) = 0
}
float WaveTime = sin(_Timer);
using System.Collections;
using UnityEngine;
public class Circle : MonoBehaviour {
public float _timeElapsed;
void OnEnable(){
_timeElapsed = 0;
}
public void Update() {
_timeElapsed += Time.deltaTime;
var _renderer = GetComponent<MeshRenderer>();
_renderer.material.SetFloat("_Timer", _timeElapsed);
}
}