加载中…

加载中...

U3D 海底焦散模拟(一)

转载 2015-04-06 00:59:20
 1-1

        焦散是一种光学现象,是指当光线穿过不规则的透明物体时,发生折射,在投影表面产生或聚拢或分散的光子分布。如图1-1。

        在游戏中,一般是用贴图来模拟焦散,如果你想知道的是有关于实时焦散计算,那么这篇文章不适合你。

1-2

​        图1-2是一张焦散贴图,这张贴图是我在某个工程里面找到的,也是用来模拟焦散的,只不过那个项目里好像是直接用它叠加在海浪上面了,而我要的是海底下的焦散效果。这是一张存储了4X4X3,一共48张焦散贴图,这48张贴图组成了一个连续循环的焦散帧动画。至于为什么用一张贴图来存,是因为其实在模拟焦散时,我们只需要一个通道来保存某个点的光子强弱,所以对一张RBG贴图,我们完全可以用三个通道分别存储三张不同焦散贴图。

        首先,在U3D中,至少有两种方法可以模拟焦散,第一,使用U3D自带的projector组件,你要做的仅仅是对shader做一点点修改。​1-3填写图片摘摘要(选填)

1-3-gif

        图1-3是效果,下面是部分代码:

v2f vert(appdata v)

{

v2f o;

//calculate uv

o.pos = mul(_Projector, v.vertex);

o.uv = float2(o.pos.x/o.pos.w, o.pos.y/o.pos.w);//ignore z

o.uv = TRANSFORM_TEX(o.uv, _MainTex);

//

float3 lightDir = ObjSpaceLightDir(v.vertex);

o.intensity = dot(normalize(v.normal), normalize(lightDir));

o.intensity *= (1-mul(_ProjectorClip, v.vertex).x)*5;

//

o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

return o;

}

以上是顶点着色器的代码,_Projector这个矩阵是由projector组件提供的,源码我没找到,大概的作用是将输入的顶点转换到projector的cvv空间中,跟MVP差不多,但是有细微的差别,至于有什么差别,我就不知道了,没看到源码不好说,猜测可能是做了一些优化,我们只需要知道输出的是什么就行了。输出的是什么?当然是顶点在projector空间中的坐标。

o.uv = float2(o.pos.x/o.pos.w, o.pos.y/o.pos.w);//ignore z​

这句是计算焦散的uv值,直接用xy来表示uv。

o.uv = TRANSFORM_TEX(o.uv, _MainTex);​

这个是考虑到了外面设置焦散贴图的offset和tiling值。如果不加这句也可以,但是在属性面板上调整offset和tiling时就不起作用了,因为你没对它进行计算。

​float3 lightDir = ObjSpaceLightDir(v.vertex);

这句是计算光照方向,具体查看u3d内置函数ObjSpaceLightDir:

// Computes object space light direction

inline float3 ObjSpaceLightDir( in float4 v )

{

float3 objSpaceLightPos = mul(_World2Object, _WorldSpaceLightPos0).xyz;

#ifndef USING_LIGHT_MULTI_COMPILE

return objSpaceLightPos.xyz - v.xyz * _WorldSpaceLightPos0.w;

#else

#ifndef USING_DIRECTIONAL_LIGHT

return objSpaceLightPos.xyz * unity_Scale.w - v.xyz;

#else

return objSpaceLightPos.xyz;

#endif

#endif

}

​上面的代码应该不是很难懂,_WorldSpaceLightPos0是保存的光源坐标,至于具体在u3d中存取光源的规则,下次再写吧,老实说还是有点复杂。

​o.intensity *= (1-mul(_ProjectorClip, v.vertex).x)*5;

这句中主要令人困惑的是_ProjectorClip这个矩阵,从命名上可以看出它还是projector组件的内置变量,主要的功能就是用来计算深度值,类似于LinearTo01Depth函数,所以mul(_ProjectorClip, v.vertex).x,这就是计算深度值了。而这整个代码的功能就是计算这一点的光强,离projector越近,焦散就越亮,越远,焦散就越淡。

然后是像素着色器的代码:

int imod(int x, int f)

{

return x-x/f*f;

}

half4 frag(v2f i):COLOR

{

half4 result = float4(1.0, 1.0, 1.0, 1.0);

int frameCount = imod((int)(_Time.w*10), 48);

int mask = frameCount / 16;

int row =  imod(frameCount, 16) / 4 ;

int col =  imod(imod(frameCount, 16), 4);

float2 aniUV = float2(0.25*(col), 0.25*(row));

aniUV = frac(i.uv)*float2(0.25, 0.25)+aniUV;

float4 causticCol = tex2D(_MainTex, aniUV);

float causticIntensity = 1.0;

if(mask == 0)

{

causticIntensity = causticCol.r;

}

else if(mask == 1)

{

causticIntensity = causticCol.g;

}

else if(mask == 2)

{

causticIntensity = causticCol.b;

}

causticIntensity=i.intensity*causticIntensity*_CausticIntensity;

return result*causticIntensity;

}

​像素着色器里面主要就是帧动画的代码,将焦散贴图叠加到目标上的代码其实是

​Blend DstColor one

注意!!!像素着色器里面的帧动画计算的代码其实​可以放到外面的脚本中进行计算。然后再传到shader里面来,因为像素着色器是每帧都要计算所有的像素,非常耗时。

        至于在​u3d中实现模拟焦散的另外一种方法,就是完全自己写shader,不依赖projector,相当于我要自己实现projector的某些功能。那么既然projector已经可以实现模拟焦散了,为什么又要另外自己实现?原因很简单,肯定是因为这种方法有缺点。

        至于缺点是什么?以及怎么克服它带来的问题,我们下次见!​

PS:这里补上完整代码,如果要用在项目里,记得把帧动画的计算提出来。

Shader "Custom/Caustic_projector" {

Properties

{

_MainTex("cautics texture", 2D) = "caustic" {}

_CausticIntensity("caustics strength", Range(0.0, 1.0)) = 0.5

}

Subshader

{

//Opaque

Tags {"RenderType"="Opaque"  "LightMode"="ForwardBase"}

Pass

{

Blend DstColor one

//Blend one OneMinusSrcAlpha

//Blend one one

offset -1, -1

CGPROGRAM

#pragma vertex vert

#pragma fragment frag

#include "UnityCG.cginc"

sampler2D _MainTex;

float4 _MainTex_ST;

float _CausticIntensity;

float4x4 _Projector;

float4x4 _ProjectorClip;

struct appdata

{

float4 vertex:POSITION;

float2 texcoord:TEXCOORD0;

float4 normal : NORMAL;

};

struct v2f

{

float4 pos : SV_POSITION;

float2 uv : TEXCOORD0;

half  intensity : TEXCOORD1;

};

v2f vert(appdata v)

{

v2f o;

//calculate uv

o.pos = mul(_Projector, v.vertex);

o.uv = float2(o.pos.x/o.pos.w, o.pos.y/o.pos.w);//ignore z

o.uv = TRANSFORM_TEX(o.uv, _MainTex);

//

float3 lightDir = ObjSpaceLightDir(v.vertex);

o.intensity = dot(normalize(v.normal), normalize(lightDir));

o.intensity *= (1-mul(_ProjectorClip, v.vertex).x)*5;

//

o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

return o;

}

int imod(int x, int f)

{

return x-x/f*f;

}

half4 frag(v2f i):COLOR

{

half4 result = float4(1.0, 1.0, 1.0, 1.0);

int frameCount = imod((int)(_Time.w*10), 48);

int mask = frameCount / 16;

int row =  imod(frameCount, 16) / 4 ;

int col =  imod(imod(frameCount, 16), 4);

float2 aniUV = float2(0.25*(col), 0.25*(row));

aniUV = frac(i.uv)*float2(0.25, 0.25)+aniUV;

float4 causticCol = tex2D(_MainTex, aniUV);

float causticIntensity = 1.0;

if(mask == 0)

{

causticIntensity = causticCol.r;

}

else if(mask == 1)

{

causticIntensity = causticCol.g;

}

else if(mask == 2)

{

causticIntensity = causticCol.b;

}

causticIntensity=i.intensity*causticIntensity*_CausticIntensity;

return result*causticIntensity;

}

ENDCG

}

}

}


阅读(0) 评论(0) 收藏(0) 转载(0) 举报/Report

评论

重要提示:警惕虚假中奖信息
0条评论展开
相关阅读
加载中,请稍后
鍒欏嵎澶ф槑
  • 博客等级:
  • 博客积分:0
  • 博客访问:7,100
  • 关注人气:0
  • 荣誉徽章:

相关博文

推荐博文

新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

新浪公司 版权所有