Unity3D 海水多线程渲染算法实现
海水仿真渲染一直是比较麻烦,虽然市面上有了各种海水的渲染算法,但是真正做到仿真渲染的少之又少。大多停留在试验阶段,达到了仿真的要求,但是硬件配置要求高,不利于推广。本篇文章就给大家介绍下关于海水的实施渲染以及通过算法实现船只航行轨迹效果,真实的达到了海水的渲染,海水的网格采用了多个面片网格拼接的方式,网格采用的是LOD处理的,这样就优化了效率。同时将海水的绘制以及算法实现放到C 中进行,这样对于复杂算法的实现效率明显提升。先给读者看几幅效果图:
船在海水中航行的轨迹效果图,轨迹是实时绘制的。再来一副海水到岸边产生的泡沫效果图:
除了岸边的效果图外,船在水中周围也会产生泡沫效果。最后一副效果图如下:
海水的反射折射效果。下面开始给读者介绍实现该海水的原理以及核心代码,最后把整个工程奉献给读者。
第一步:海水网格的实现,海水网格采用的是面片拼接的方式,并且面片采用了LOD运算,其在Unity中的效果如下所示:
从里向外,面片的数量逐步减少,它是根据摄像机的远近处理的,对应的核心代码如下所示: copy
- //update the meshes with the final calculated mesh data
- void updateTiles(int a, int b) {
- if(skipLods) {
- lodSkip ;
- if(lodSkip >= lodSkipFrames 1) lodSkip=0;
- }
- for (int L0D=a; L0D<b; L0D ) {
- //if(L0D>
- //this will skip one update of the tiles higher then Lod0
- if(L0D>0 && lodSkip==0 && !ticked && skipLods) { break; }
- //this will skip one update of the LOD0 tiles because they got updated earlier when they should.
- if(ticked2 && L0D==0) { ticked2=false; continue; }
- #if !NATIVE
- int den = MyIntPow (2, L0D);
- int idx = 0;
- for (int y=0; y<g_height; y =den) {
- for (int x=0; x<g_width; x =den) {
- int idx2 = g_width * y x;
- verticesLOD[L0D] [idx] = vertices [idx2];
- //lower the far lods to eliminate gaps in the horizon when having big waves
- if(L0D>0) {
- if(farLodOffset!=0) {
- verticesLOD[L0D] [idx].y = flodoffset[L0D] * flodFact;
- }
- }
- tangentsLOD[L0D] [idx] = tangents [idx2];
- normalsLOD[L0D] [idx ] = normals [idx2];
- }
- }
- #else
- uocean._updateTilesA(verticesLOD[L0D], vertices, tangentsLOD[L0D], tangents, normalsLOD[L0D], normals, L0D, farLodOffset, flodoffset, flodFact);
- #endif
- btiles_LOD[L0D].vertices = verticesLOD[L0D];
- btiles_LOD[L0D].normals = normalsLOD[L0D];
- btiles_LOD[L0D].tangents = tangentsLOD[L0D];
- }
- if(ticked) ticked = false;
- }
- void GenerateTiles() {
- int chDist, nmaxLod=0; // Chebychev distance
- for (int y=0; y<tiles; y ) {
- for (int x=0; x<tiles; x ) {
- chDist = System.Math.Max (System.Math.Abs (tiles / 2 - y), System.Math.Abs (tiles / 2 - x));
- chDist = chDist > 0 ? chDist - 1 : 0;
- if(nmaxLod<chDist) nmaxLod = chDist;
- }
- }
- max_LOD = nmaxLod 1;
- flodoffset = new float[max_LOD 1];
- float ffact = farLodOffset/max_LOD;
- for(int i=0; i<max_LOD 1; i ) {
- flodoffset[i] = i*ffact;
- }
- btiles_LOD = new List<Mesh>();
- tiles_LOD = new List<List<Mesh>>();
- for (int L0D=0; L0D<max_LOD; L0D ) {
- btiles_LOD.Add(new Mesh());
- tiles_LOD.Add (new List<Mesh>());
- }
- GameObject tile;
- int ntl = LayerMask.NameToLayer ("Water");
- for (int y=0; y<tiles; y ) {
- for (int x=0; x<tiles; x ) {
- chDist = System.Math.Max (System.Math.Abs (tiles / 2 - y), System.Math.Abs (tiles / 2 - x));
- chDist = chDist > 0 ? chDist - 1 : 0;
- if(nmaxLod<chDist) nmaxLod = chDist;
- float cy = y - Mathf.Floor(tiles * 0.5f);
- float cx = x - Mathf.Floor(tiles * 0.5f);
- tile = new GameObject ("Lod_" chDist.ToString() ":" y.ToString() "x" x.ToString());
- Vector3 pos=tile.transform.position;
- pos.x = cx * size.x;
- pos.y = transform.position.y;
- pos.z = cy * size.z;
- tile.transform.position=pos;
- tile.AddComponent <MeshFilter>();
- tile.AddComponent <MeshRenderer>();
- Renderer renderer = tile.GetComponent<Renderer>();
- tile.GetComponent<MeshFilter>().mesh = btiles_LOD[chDist];
- //tile.isStatic = true;
- //shader/material lod (needs improvement)
- if(useShaderLods && numberLods>1) {
- if(numberLods==2) {
- if(chDist <= sTilesLod) { if(material) renderer.material = material; }
- if(chDist > sTilesLod) { if(material1) renderer.material = material1; }
- }else if(numberLods==3){
- if(chDist <= sTilesLod ) { if(material) renderer.material = material; }
- if(chDist == sTilesLod 1) { if(material1) renderer.material = material1; }
- if(chDist > sTilesLod 1) { if(material2) renderer.material = material2; }
- }
- } else {
- renderer.material = material;
- }
- //Make child of this object, so we don't clutter up the
- //scene hierarchy more than necessary.
- tile.transform.parent = transform;
- //Also we don't want these to be drawn while doing refraction/reflection passes,
- //so we'll add the to the water layer for easy filtering.
- tile.layer = ntl;
- tiles_LOD[chDist].Add( tile.GetComponent<MeshFilter>().mesh);
- }
- }
- //enable/disable the fixed disc
- initDisc();
- }
第二步,海水的渲染Shader,根据不同的LOD等级实行不同的Shader渲染,举个例子,LOD等级2的Shader代码如下所示: copy
- Shader "Mobile/OceanL2" {
- Properties {
- _SurfaceColor ("SurfaceColor", Color) = (1,1,1,1)
- _WaterColor ("WaterColor", Color) = (1,1,1,1)
- _Specularity ("Specularity", Range(0.01,1)) = 0.3
- _SpecPower("Specularity Power", Range(0,1)) = 1
- [HideInInspector] _SunColor ("SunColor", Color) = (1,1,0.901,1)
- _Bump ("Bump (RGB)", 2D) = "bump" {}
- _Size ("UVSize", Float) = 0.015625//this is the best value (1/64) to have the same uv scales of normal and foam maps on all ocean sizes
- [HideInInspector] _SunDir ("SunDir", Vector) = (0.3, -0.6, -1, 0)
- _FakeUnderwaterColor ("Water Color LOD1", Color) = (0.196, 0.262, 0.196, 1)
- _DistanceCancellation ("Distance Cancellation", Float) = 2000
- }
- //water bump
- SubShader {
- Tags { "RenderType" = "Opaque" "Queue"="Geometry"}
- LOD 2
- Pass {
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- //#pragma multi_compile_fog
- #pragma multi_compile FOGON FOGOFF
- #pragma multi_compile DCON DCOFF
- #pragma target 2.0
- #include "UnityCG.cginc"
- struct v2f {
- float4 pos : SV_POSITION;
- half3 floatVec : TEXCOORD0;
- float2 bumpTexCoord : TEXCOORD1;
- //half3 viewDir : TEXCOORD2;
- half3 lightDir : TEXCOORD2;
- half2 buv : TEXCOORD3;
- half3 normViewDir : TEXCOORD4;
- //UNITY_FOG_COORDS(7)
- #ifdef FOGON
- half dist : TEXCOORD5;
- #ifdef DCON
- half distCancellation : TEXCOORD6;
- #endif
- #endif
- };
- half _Size;
- half4 _SunDir;
- half4 _FakeUnderwaterColor;
- #ifdef FOGON
- uniform half4 unity_FogStart;
- uniform half4 unity_FogEnd;
- uniform half4 unity_FogDensity;
- #ifdef DCON
- half _DistanceCancellation;
- #endif
- #endif
- v2f vert (appdata_tan v) {
- v2f o;
- UNITY_INITIALIZE_OUTPUT(v2f, o);
- o.bumpTexCoord.xy = v.vertex.xz*_Size;///float2(_Size.x, _Size.z)*5;
- o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
- half3 objSpaceViewDir = ObjSpaceViewDir(v.vertex);
- half3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) );
- half3x3 rotation = half3x3( v.tangent.xyz, binormal, v.normal );
- half3 viewDir = mul(rotation, objSpaceViewDir);
- o.lightDir = mul(rotation, half3(_SunDir.xyz));
- o.buv = float2(o.bumpTexCoord.x _CosTime.x * 0.2, o.bumpTexCoord.y _SinTime.x * 0.3);
- o.normViewDir = normalize(viewDir);
- o.floatVec = normalize(o.normViewDir - normalize(o.lightDir));
- #ifdef FOGON
- //manual fog
- half fogDif = 1.0/(unity_FogEnd.x - unity_FogStart.x);
- o.dist = (unity_FogEnd.x - length(o.pos.xyz)) * fogDif;
- #ifdef DCON
- o.distCancellation = (unity_FogEnd.x - _DistanceCancellation) * fogDif;
- #endif
- #endif
- //autofog
- //UNITY_TRANSFER_FOG(o, o.pos);
- return o;
- }
- sampler2D _Bump;
- half4 _WaterColor;
- half4 _SurfaceColor;
- half _Specularity;
- half _SpecPower;
- half4 _SunColor;
- half4 frag (v2f i) : COLOR {
- #ifdef FOGON
- #ifdef DCON
- if(i.dist>i.distCancellation){
- #endif
- #endif
- half3 tangentNormal0 = (tex2D(_Bump, i.buv.xy) * 2.0) -1;
- half3 tangentNormal = normalize(tangentNormal0);
- half4 result = half4(0, 0, 0, 1);
- //half fresnelLookup = dot(tangentNormal,i. normViewDir);
- //float bias = 0.06;
- //float power = 4.0;
- //half fresnelTerm = 0.06 (1.0-0.06)*pow(1.0 - fresnelLookup, 4.0);
- half fresnelTerm = 1.0 - saturate(dot (i.normViewDir, tangentNormal0));
- half specular = pow(max(dot(i.floatVec, tangentNormal) , 0.0), 250.0 * _Specularity ) * _SpecPower;
- result.rgb = lerp(_WaterColor*_FakeUnderwaterColor, _SunColor.rgb*_SurfaceColor*0.85, fresnelTerm*0.6) specular*_SunColor.rgb;
- //fog
- //UNITY_APPLY_FOG(i.fogCoord, result);
- #ifdef FOGON
- //manual fog (linear) (reduces instructions on d3d9)
- float ff = saturate(i.dist);
- result.rgb = lerp(unity_FogColor.rgb, result.rgb, ff);
- #endif
- return result;
- #ifdef FOGON
- #ifdef DCON
- }else{
- return unity_FogColor;
- }
- #endif
- #endif
- }
- ENDCG
- }
- }
- }
远近海水很有层次感效果。
第三步:船只在海水中航行的轨迹效果以及随着海水起伏效果实现代码
- using UnityEngine;
- using System.Collections.Generic;
- public class Boyancy : MonoBehaviour {
- private Ocean ocean;
- public int renderQueue;
- public bool useFixedUpdate = false;
- public bool moreAccurate = false;
- public float magnitude = 2f;
- public float ypos = 0.0f;
- private List<Vector3> blobs;
- private List<float[]> prevBoya;
- //private bool engine = false;
- private List<float> sinkForces;
- public float CenterOfMassOffset = -1f;
- public float dampCoeff = .1f;
- //buoyancy slices. (Cannot be smaller then 2)
- //Raise these numbers if you want more accurate simulation. However it will add overhead. So keep it as small as possible.
- public int SlicesX = 2;
- public int SlicesZ = 2;
- public int interpolation = 3;
- private int intplt;
- public bool ChoppynessAffectsPosition = false;
- public float ChoppynessFactor = 0.2f;
- public bool WindAffectsPosition = false;
- public float WindFactor = 0.1f;
- public bool xAngleAddsSliding = false;
- public float slideFactor = 0.1f;
- public bool cvisible, wvisible, svisible;
- public Renderer _renderer ;
- public bool sink = false;
- public float sinkForce = 3;
- private float iF;
- private bool interpolate = false;
- private Rigidbody rrigidbody;
- private int tick, tack;
- private Vector3 wpos, cpos;
- private bool useGravity;
- private float accel;
- private int prevAngleX, currAngleX;
- private float bbboyancy;
- private float prevBuoyancy;
- void Start () {
- if(!_renderer) {
- _renderer = GetComponent<Renderer>();
- if(!_renderer) {
- _renderer = GetComponentInChildren<Renderer>();
- }
- }
- if(_renderer && renderQueue>0) _renderer.material.renderQueue = renderQueue;
- if(!_renderer) {
- if(cvisible) { Debug.Log("Renderer to check visibility not assigned."); cvisible = false; }
- if(wvisible) { Debug.Log("Renderer to check visibility not assigned."); wvisible = false; }
- if(svisible) { Debug.Log("Renderer to check visibility not assigned."); svisible = false; }
- }
- if(dampCoeff<0) dampCoeff = Mathf.Abs(dampCoeff);
- rrigidbody = GetComponent<Rigidbody>();
- useGravity = rrigidbody.useGravity;
- if(interpolation>0) {
- interpolate = true;
- iF = 1/(float)interpolation;
- intplt = interpolation;
- }
- if(SlicesX<2) SlicesX=2;
- if(SlicesZ<2) SlicesZ=2;
- ocean = Ocean.Singleton;
- rrigidbody.centerOfMass = new Vector3 (0.0f, CenterOfMassOffset, 0.0f);
- Vector3 bounds = GetComponent<BoxCollider> ().size;
- float length = bounds.z;
- float width = bounds.x;
- blobs = new List<Vector3> ();
- prevBoya = new List<float[]>();
- int i = 0;
- float xstep = 1.0f / ((float)SlicesX - 1f);
- float ystep = 1.0f / ((float)SlicesZ - 1f);
- sinkForces = new List<float>();
- float totalSink = 0;
- for (int x=0; x<SlicesX; x ) {
- for (int y=0; y<SlicesX; y ) {
- blobs.Add (new Vector3 ((-0.5f x * xstep) * width, 0.0f, (-0.5f y * ystep) * length) Vector3.up * ypos);
- if(interpolate) { prevBoya.Add(new float[interpolation]); }
- float force = Random.Range(0f,1f);
- force = force * force;
- totalSink = force;
- sinkForces.Add(force);
- i ;
- }
- }
- // normalize the sink forces
- for (int j=0; j< sinkForces.Count; j ) {
- sinkForces[j] = sinkForces[j] / totalSink * sinkForce;
- }
- }
- void Update() {
- if(!useFixedUpdate) update();
- }
- void FixedUpdate() {
- if(useFixedUpdate) update();
- }
- bool visible, lastvisible;
- int lastFrame=-15;
- void update() {
- if (ocean != null) {
- visible = _renderer.isVisible;
- //put object on the correct height of the sea surface when it has visibilty checks on and it became visible again.
- if(visible != lastvisible) {
- if(visible && !lastvisible) {
- if(Time.frameCount-lastFrame>15) {
- float off = ocean.GetChoppyAtLocation(transform.position.x, transform.position.z);
- float y = ocean.GetWaterHeightAtLocation2 (transform.position.x-off, transform.position.z);
- transform.position = new Vector3(transform.position.x, y, transform.position.z);
- lastFrame = Time.frameCount;
- }
- }
- lastvisible = visible;
- }
- //prevent use of gravity when buoyancy is disabled
- if(cvisible) {
- if(useGravity) {
- if(!visible) {
- rrigidbody.useGravity=false;
- if(wvisible && svisible) return;
- } else {
- rrigidbody.useGravity = true;
- }
- }else {
- if(!visible) { if(wvisible && svisible) return;}
- }
- }
- float coef = dampCoeff;
- int index = 0, k=0;
- int ran = (int)Random.Range(0, blobs.Count-1);
- for(int j = 0; j<blobs.Count; j ) {
- wpos = transform.TransformPoint (blobs[j]);
- //get a random blob to apply a force with the choppy waves
- if(ChoppynessAffectsPosition) { if(j == ran) cpos = wpos; }
- if(!cvisible || visible) {
- float buyancy = magnitude * (wpos.y);
- if (ocean.enabled) {
- if(ocean.canCheckBuoyancyNow[0]==1) {
- float off = 0;
- if(ocean.choppy_scale>0) off = ocean.GetChoppyAtLocation(wpos.x, wpos.z);
- if(moreAccurate) {
- buyancy = magnitude * (wpos.y - ocean.GetWaterHeightAtLocation2 (wpos.x-off, wpos.z));
- }else {
- buyancy = magnitude * (wpos.y - ocean.GetWaterHeightAtLocation (wpos.x-off, wpos.z));
- buyancy = Lerp(prevBuoyancy, buyancy, 0.5f);
- prevBuoyancy = buyancy;
- }
- bbboyancy = buyancy;
- } else {
- buyancy = bbboyancy;
- }
- }
- if (sink) { buyancy = System.Math.Max(buyancy, -3) sinkForces[index ]; }
- float damp = rrigidbody.GetPointVelocity (wpos).y;
- float bbuyancy = buyancy;
- //interpolate last (int interpolation) frames to smooth out the jerkiness
- //interpolation will be used only if the renderer is visible
- if(interpolate) {
- if(visible) {
- prevBoya[k][tick] = buyancy;
- bbuyancy=0;
- for(int i=0; i<intplt; i ) { bbuyancy = prevBoya[k][i]; }
- bbuyancy *= iF;
- }
- }
- rrigidbody.AddForceAtPosition (-Vector3.up * (bbuyancy coef * damp), wpos);
- k ;
- }
- }
- if(interpolate) { tick ; if(tick==intplt) tick=0; }
- tack ; if (tack == (int)Random.Range(2, 9) ) tack=0;
- if(tack>9) tack =1;
- //if the boat has high speed do not influence it (choppyness and wind)
- //if it has lower then fact then influence it depending on the speed .
- float fact = rrigidbody.velocity.magnitude * 0.02f;
- //this code is quick and dirty
- if(fact<1) {
- float fact2 = 1-fact;
- //if the object gets its position affected by the force of the choppy waves. Useful for smaller objects).
- if(ChoppynessAffectsPosition) {
- if(!cvisible || visible) {
- if(ocean.choppy_scale>0) {
- if(moreAccurate) {
- if(tack==0) rrigidbody.AddForceAtPosition (-Vector3.left * (ocean.GetChoppyAtLocation2Fast() * ChoppynessFactor*Random.Range(0.5f,1.3f))*fact2, cpos);
- else rrigidbody.AddForceAtPosition (-Vector3.left * (ocean.GetChoppyAtLocation2Fast() * ChoppynessFactor*Random.Range(0.5f,1.3f))*fact2, transform.position);
- } else {
- if(tack==0) rrigidbody.AddForceAtPosition (-Vector3.left * (ocean.GetChoppyAtLocationFast() * ChoppynessFactor*Random.Range(0.5f,1.3f))*fact2, cpos);
- else rrigidbody.AddForceAtPosition (-Vector3.left * (ocean.GetChoppyAtLocationFast() * ChoppynessFactor*Random.Range(0.5f,1.3f))*fact2, transform.position);
- }
- }
- }
- }
- //if the object gets its position affected by the wind. Useful for smaller objects).
- if(WindAffectsPosition) {
- if(!wvisible || visible) {
- if(tack==1) rrigidbody.AddForceAtPosition(new Vector3(ocean.pWindx, 0 , ocean.pWindy) * WindFactor*fact2, cpos);
- else rrigidbody.AddForceAtPosition(new Vector3(ocean.pWindx, 0 , ocean.pWindy) * WindFactor*fact2, transform.position);
- }
- }
- }
- //the object will slide down a steep wave
- //modify it to your own needs since it is a quick and dirty method.
- if(xAngleAddsSliding) {
- if(!svisible || visible) {
- float xangle = transform.localRotation.eulerAngles.x;
- currAngleX = (int)xangle;
- if(prevAngleX != currAngleX) {
- float fangle=0f;
- if(xangle>270 && xangle<355) {
- fangle = (360-xangle)*0.1f;
- accel -= fangle* slideFactor; if(accel<-20) accel=-20;
- }
- if(xangle>5 && xangle<90) {
- fangle = xangle*0.1f;
- accel = fangle* slideFactor; if(accel>20) accel=20;
- }
- prevAngleX = currAngleX;
- }
- if((int)accel!=0) rrigidbody.AddRelativeForce (Vector3.forward * accel, ForceMode.Acceleration);
- if(accel>0) { accel-= 0.05f; if(accel<0) accel=0; }
- if(accel<0) { accel = 0.05f; if(accel>0) accel=0; }
- }
- }
- }
- }
- public void Sink(bool isActive) { sink = isActive; }
- static float Lerp (float from, float to, float value) {
- if (value < 0.0f) return from;
- else if (value > 1.0f) return to;
- return (to - from) * value from;
- }
- }
运行效果如下所示:
效果非常绚丽。。。。。。。。
第四步,除了LOD海水网格渲染外,还提供了整个海水平面的渲染,以及可视化的界面操作,效果如下所示:
在该操作界面中有OceanMaterial材质对应的Shader脚本,在这里就不一一列举了。
第五步,多线程渲染主要是用于初始化海水网格以及算法的计算:
- uocean.setThreads(2);
- if(SystemInfo.processorCount == 1) uocean.setThreads(1);
- //--------------------------------------------------------------------------------------------------------------------------------------------
- uocean.UoceanInit(width, height, pWindx, pWindy, speed, waveScale, choppy_scale, size.x, size.y, size.z, waveDistanceFactor);
- uocean._calcComplex(data, t_x, Time.time, 0, height);
- uocean._fft1(data);
- uocean._fft2(t_x);
- uocean._calcPhase3(data, t_x, vertices, baseHeight, normals, tangents, reflectionRefractionEnabled, canCheckBuoyancyNow, waveScale);
其他的读者可以通过Demo去查看。
代码链接地址: http://pan.baidu.com/s/1pK91RYV 密码:xdx6