Unity3D 海水多线程渲染算法实现

原创 2017年07月21日 17:27:54

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

海水仿真渲染一直是比较困难的事情,虽然市面上有了各种海水的渲染算法,但是真正做到仿真渲染的少之又少。大多停留在试验阶段,达到了仿真的要求,但是硬件配置要求高,不利于推广。本篇博客给读者介绍的是关于海水的实施渲染以及通过算法实现船只航行轨迹效果,真实的达到了海水的渲染,海水的网格采用了多个面片网格拼接的方式,网格采用的是LOD处理的,这样就优化了效率。同时将海水的绘制以及算法实现放到C++中进行,这样对于复杂算法的实现效率明显提升。先给读者看几幅效果图:


船在海水中航行的轨迹效果图,轨迹是实时绘制的。再来一副海水到岸边产生的泡沫效果图:


除了岸边的效果图外,船在水中周围也会产生泡沫效果。最后一副效果图如下:


海水的反射折射效果。下面开始给读者介绍实现该海水的原理以及核心代码,最后把整个工程奉献给读者。

第一步:海水网格的实现,海水网格采用的是面片拼接的方式,并且面片采用了LOD运算,其在Unity中的效果如下所示:


从里向外,面片的数量逐步减少,它是根据摄像机的远近处理的,对应的核心代码如下所示:

	//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();
	}
在Unity中生成的效果如下所示:


第二步,海水的渲染Shader,根据不同的LOD等级实行不同的Shader渲染,举个例子,LOD等级2的Shader代码如下所示:

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;
	}

}


在Unity编辑器中的表现如下所示:


运行效果如下所示:


效果非常绚丽。。。。。。。。

第四步,除了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


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Unity描边[Unity Shader]

本文详细的讲解使用shader进行描边处理的思路和具体实现。

博客搬到CSDN了~[顺带吐槽下技术博客的选择]

原始博客地址:blog.sina.com/zhongxiaoqiu 使用新浪博客已经好几年了,

Unity Shader-描边效果

简介 描边效果是游戏里面非常常用的一种效果,一般在选中物体或者NPC的时候,被选中的对象就会显示描边效果。比如最近又跑回去玩了玩《剑灵》,虽然出了三年了,在现在的网游里面画面仍然算很好的。 还...

史上最全的Unity面试题(含答案)

一:什么是协同程序? 在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,Unity的协程实在每帧结束之后去检测yield的条件是否满足。 二:Unity3...

Unity3D教你制作Bezier和Spine曲线编辑器三

继续接着介绍曲线编辑器的制作,上篇博客介绍了关于Bezier曲线的制作,接下来给读者介绍Spine B样条曲线之作。 如果要创建复杂的曲线,我们需要连接多个曲线,这样的构造称为样条。让我们通过复制Be...

网路游戏之物理模拟

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等...
  • jxw167
  • jxw167
  • 2017-07-24 11:41
  • 1473

Unity3D快速实现UI架构设计二

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》...

Unity3D 屏幕空间雪场景Shader渲染

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等...
  • jxw167
  • jxw167
  • 2017-07-14 22:38
  • 2515

Unity3D引擎之Shader Forge应用

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》...
  • jxw167
  • jxw167
  • 2017-04-04 17:52
  • 2549
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)