游戏的研发大概有3个月了,都是业余时间边学边做,还是觉得在项目里面实操学得扎实牢靠。

目标平台是手机。。。。(优化后,目前测试的是中高端机型)

目前是在unity里面实时光的效果打包到手机里的实机画面,后面等完成再优化性能(shader,草的GPU渲染,灯光烘焙等)。整个项目是用的内置渲染管线,shader采用了顶点片元着色器和表面着色器的写法混用。先上GIF动图效果(美术效果由于压缩严重受损!!):

地形:

unity自带的 terrain 有三个问题:1.模型面数极高

2.功能很全面,但是不利于手机平台做定制化的优化

3.种草功能只能基于terrain,不能基于场景模型构建,模拟风效果不 能利用噪声图,摆动动画效果很单一

为了解决这三个问题,我尝试将terrain的部分功能(种草,地形贴图用MASK分层)提取出来,节省性能,风吹草的功能用shader单独调整,种草可以基于场景任意模型的表面(甚至是有斜度的坡面,也能根据法线的方向去智能化生成,完美贴合地形)。

整个地形制作思路:

1.利用unity自带的terrain来刷大型,后续对其面数在DCC软件(Digital Content Creation的缩写, 即数字内容创作)里进行优化减面,展UV。

terrain面数:

减面后的模型面数:

2.因为模型是带有UV的,所以利用Mask贴图和shader就能实现模拟terrain4种不同材质贴图的层级表现:

下面是我自己模拟unity的terrain的一些贴图功能自制的shader,一共有4层贴图,每层有一套包含固有色,法线,粗糙度贴图,NoScaleOffset让贴图的参数不暴露在Inspector面板里,

Shader "Custom/test"
{Properties{_Color ("Grass Color", Color) = (1,1,1,1)//草地[NoScaleOffset]_MainTex ("Albedo 01", 2D) = "white" {}[NoScaleOffset]_Normal01("Normal 01", 2D) = "bump" {}_TextureScale01("Texture Scale 01", Float) = 1.0_range01("Smooth Range01",Range(0,1)) = 1//路[NoScaleOffset]_Albedo02 ("Albedo 02", 2D) = "white" {}[NoScaleOffset]_Normal02("Normal 02", 2D) = "bump" {}_TextureScale02("Texture Scale 02", Float) = 1.0_range02("Smooth Range02",Range(0,1)) = 1//沙子[NoScaleOffset]_Albedo03 ("Albedo 03", 2D) = "white" {}[NoScaleOffset]_Normal03("Normal 03", 2D) = "bump" {}_TextureScale03("Texture Scale 03", Float) = 1.0_range03("Smooth Range03",Range(0,1)) = 1//泥土[NoScaleOffset]_Albedo04 ("Albedo 04", 2D) = "white" {}[NoScaleOffset]_Normal04("Normal 04", 2D) = "bump" {}_TextureScale04("Texture Scale 04", Float) = 1.0_range04("Smooth Range04",Range(0,1)) = 1//遮罩[NoScaleOffset]_Mask("Mask", 2D) = "white" {}}SubShader{Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM// Physically based Standard lighting model, and enable shadows on all light types#pragma surface surf Standard fullforwardshadows #pragma target 5.0#include "UnityStandardUtils.cginc"#include "UnityCG.cginc" #include "AutoLight.cginc"#include "Tessellation.cginc"#if defined(INTERNAL_DATA) && (defined(UNITY_PASS_FORWARDBASE) || defined(UNITY_PASS_FORWARDADD) || defined(UNITY_PASS_DEFERRED) || defined(UNITY_PASS_META))#define WorldToTangentNormalVector(data,normal) mul(normal, half3x3(data.internalSurfaceTtoW0, data.internalSurfaceTtoW1, data.internalSurfaceTtoW2))#else#define WorldToTangentNormalVector(data,normal) normal#endifsampler2D _MainTex, _Normal01;sampler2D _Albedo02, _Normal02;sampler2D _Albedo03, _Normal03;sampler2D _Albedo04, _Normal04,_Mask;half _TextureScale01, _TextureScale02, _TextureScale03, _TextureScale04, _range01, _range02, _range03, _range04;half4 _Color;struct Input{float2 uv_MainTex;};// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.// #pragma instancing_options assumeuniformscalingUNITY_INSTANCING_BUFFER_START(Props)// put more per-instance properties hereUNITY_INSTANCING_BUFFER_END(Props)void surf (Input IN, inout SurfaceOutputStandard o){fixed3 MaskColor=tex2D(_Mask,IN.uv_MainTex);//草地fixed4 Albedo01 = tex2D(_MainTex,IN.uv_MainTex* _TextureScale01)*_Color*(1-MaskColor.r-MaskColor.g-MaskColor.b);half3 Normal01 = UnpackNormal(tex2D(_Normal01, IN.uv_MainTex * _TextureScale01)) *(1-MaskColor.r-MaskColor.g-MaskColor.b);//路fixed4 Albedo02 = tex2D(_Albedo02,IN.uv_MainTex* _TextureScale02)*MaskColor.r;  half3 Normal02 = UnpackNormal(tex2D(_Normal02, IN.uv_MainTex * _TextureScale02))*MaskColor.r;//沙子fixed4 Albedo03 = tex2D(_Albedo03,IN.uv_MainTex* _TextureScale03)*MaskColor.g;half3 Normal03 = UnpackNormal(tex2D(_Normal03, IN.uv_MainTex * _TextureScale03))*MaskColor.g;//泥土fixed4 Albedo04 = tex2D(_Albedo04,IN.uv_MainTex* _TextureScale04)*MaskColor.b;half3 Normal04 = UnpackNormal(tex2D(_Normal04, IN.uv_MainTex * _TextureScale04)) *MaskColor.b;o.Albedo = Albedo01+Albedo02+Albedo03+Albedo04;o.Metallic = 0;o.Smoothness =Albedo01.a*(1-MaskColor.r-MaskColor.g-MaskColor.b)* _range01 +Albedo02.a* _range02+Albedo03.a* _range03+Albedo04.a* _range04;o.Normal =Normal01*(1-MaskColor.r-MaskColor.g-MaskColor.b)+Normal02+Normal03+Normal04; }ENDCG}FallBack "Diffuse"
}

种草脚本:

问题 :自带的terrain 是只能在地形上种草,并且草是不方便提取成单独的模型文件来进行编辑

开发一种地形种植的工具来得到种植的就是模型文件,并且有一定随机性,所有模型后续可以放进资源里面,还可以单独编辑,下面看看效果:

在运行的模式下,可以模拟unity种草一样,利用射线方法适用于斜坡的表面垂直种植。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Syj_GrassBrush : MonoBehaviour
{List<trans> posList;GameObject grassObj;struct trans{public Vector3 pos;public Quaternion rot;public Vector3 scale;}trans tr;void Start(){tr = new trans();posList = new List<trans>();grassObj=Resources.Load("grass") as GameObject;Debug.Log(grassObj.name);}// Update is called once per framevoid Update(){if (Input.GetMouseButtonDown(0)){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){Vector3 point = hit.point;for (int i = 0; i < 4; i++){tr.pos = point + new Vector3(Random.insideUnitCircle.x, 0, Random.insideUnitCircle.y);RaycastHit hit1;Ray ray1 = new Ray(tr.pos + Vector3.up * 10, Vector3.down);Physics.Raycast(ray1, out hit1);tr.pos = hit1.point;tr.rot = Quaternion.AngleAxis(Random.Range(0, 6.28f), hit1.normal);tr.scale = new Vector3(1, 1, 1) * Random.Range(0.5f, 1);GameObject go=Instantiate(grassObj,tr.pos,tr.rot) as GameObject;go.transform.LookAt(hit1.point+hit1.normal);   //斜坡种草go.transform.localScale = tr.scale;go.transform.SetParent(this.transform);posList.Add(tr);}}}}
}

草的shader:

最开始草的效果是黑色的,没有卡通的通透感,原因在于模型的顶点法线影响。如下图:

在shader里面改变模型的Y方向法线就可以去掉黑色效果:

float3 worldPos = mul (unity_ObjectToWorld, v.vertex).xyz;
v.normal =float3(0,0,1);  //修改Y方向法线

草的顶点动画:

模型的顶点色有四个通道,a通道用来控制每根草根部不能摆动:

rgb通道用来扰动每根草的摆动方向:

配合的shader代码如下:

void vert (inout appdata_full v, out Input o) {UNITY_INITIALIZE_OUTPUT(Input,o);float3 worldPos = mul (unity_ObjectToWorld, v.vertex).xyz;v.normal =float3(0,0,1);//修改法线float2 UV = worldPos.xz;UV.xy += _Time*2;float3 windNoise = tex2Dlod(_WindNoise, float4(UV, 0, 0) * _NoiseScale).rgb;v.vertex.z += sin(_Time*20) * v.color.a * _NoiseAmount.z * v.color.r * windNoise.g;v.vertex.z += sin(_Time*15) * v.color.a * _NoiseAmount.z * v.color.g * windNoise.g;v.vertex.z += sin(_Time*25) * v.color.a * _NoiseAmount.z * v.color.b * windNoise.g;v.vertex.x += sin(_Time*20) * v.color.a * _NoiseAmount.x * v.color.r * windNoise.r;v.vertex.x += sin(_Time*15) * v.color.a * _NoiseAmount.x * v.color.g * windNoise.r;v.vertex.x += sin(_Time*25) * v.color.a * _NoiseAmount.x * v.color.b * windNoise.r;v.vertex.y += windNoise.r * _NoiseAmount.y * v.color.a * v.color.r;v.vertex.y += windNoise.g * _NoiseAmount.y * v.color.a * v.color.g;v.vertex.y += windNoise.b * _NoiseAmount.y * v.color.a * v.color.b;}

效果如下:

草的GPU渲染:

gpu instancing:这是最新渲染api提供的一种技术,如果绘制1000个物体,它将一个模型的vbo提交给一次给显卡,至于1000个物体不同的位置,状态,颜色等等将他们整合成一个per instance attribute的buffer给gpu,在显卡上区别绘制,它大大减少提交次数,它在不同平台的实现有差异,例如gles是将per instance attribute也当成一个vbo提交,然后gles3.0支持一种per instance步进读取的vbo特性,来实现不同的instance得到不同的顶点数据,这种技术对于绘制大量的相同模型的物体由于有硬件实现,所以效率最高,最为灵活,避免合批的内存浪费,并且原则上可以做gpu skinning来实现骨骼动画的instancing。

注意:unity在条件合适的情况下自动instance,但是注意这种限制非常多,如不能static batch,不能liaghtmap,不能改变mat,不能带动作,不能cull,等等。

关于gpu instancing技术连接:

https://blog.csdn.net/mango9126/article/details/120561912?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167515551616800188524117%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167515551616800188524117&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-120561912-null-null.142^v71^control_1,201^v4^add_ask&utm_term=unity%20GPU%E6%B8%B2%E6%9F%93&spm=1018.2226.3001.4187

下面是我测试有草和没草的数据:

通过 saved by batching可以看出,性能还是有极大提升!!

瀑布shader:

瀑布shader是参考许多卡通写实的效果用顶点片元着色器写出来的,为了模拟PBR的效果,加了cubemap(烘焙的场景环境)来采样reflection,性能和效果都能兼顾到。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "Unity Shaders Book/Common/Bumped Specular" {Properties {_Color ("Color Tint", Color) = (1, 1, 1, 1)_NoiseColor ("NoiseColor ", Color) = (1, 1, 1, 1)_MainTex ("Main Tex", 2D) = "white" {}_Noise01 ("Noise01 ", 2D) = "white" {}_Noise02 ("Noise02 ", 2D) = "white" {}_BumpMap ("Normal Map", 2D) = "bump" {}_Move("Move", Range(0, 2)) = 0.5_Treshold("Treshold", Range(0, 2)) = 0.5_Specular ("Specular Color", Color) = (1, 1, 1, 1)_Gloss ("Gloss", Range(8.0, 256)) = 20_NormalStrength("NormalStrength", Range(0,1)) = 1_FresnelScale("Fresnel Scale", Range(0, 1)) = 0.5_Cubemap("Reflection Cubemap", Cube) = "_Skybox" {}}SubShader {Tags { "RenderType"="Opaque" "Queue"="Geometry"}Pass { Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma multi_compile_fwdbase    #pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"fixed4 _Color;fixed4 _NoiseColor;sampler2D _MainTex;float4 _MainTex_ST;sampler2D _Noise01;float4 _Noise01_ST;sampler2D _Noise02;float4 _Noise02_ST;sampler2D _BumpMap;float4 _BumpMap_ST;fixed4 _Specular;float _Gloss;float _Move;float _Treshold;float _NormalStrength;fixed _FresnelScale;samplerCUBE _Cubemap;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;float4 tangent : TANGENT;float4 texcoord : TEXCOORD0;};struct v2f {float4 pos : SV_POSITION;float4 uv : TEXCOORD0;float4 uv1 : TEXCOORD9;float4 TtoW0 : TEXCOORD1;  float4 TtoW1 : TEXCOORD2;  float4 TtoW2 : TEXCOORD3; float3 worldPos : TEXCOORD4;fixed3 worldNormal : TEXCOORD5;fixed3 worldViewDir : TEXCOORD6;fixed3 worldRefl : TEXCOORD7;SHADOW_COORDS(8)};v2f vert(a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv1.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;o.uv1.zw = v.texcoord.xy * _Noise02_ST.xy + _Noise02_ST.zw + float2(0, _Time.y*0.5 * _Move);o.uv.xy = v.texcoord.xy * _Noise01_ST.xy + _Noise01_ST.zw + float2(0, _Time.y * _Move);o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw+ float2(0, _Time.y* _Move);o.worldNormal = UnityObjectToWorldNormal(v.normal);TANGENT_SPACE_ROTATION;float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  TRANSFER_SHADOW(o);return o;}fixed4 frag(v2f i) : SV_Target {float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));fixed3 bump = UnpackScaleNormal(tex2D(_BumpMap, i.uv.zw),_NormalStrength);bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));i.worldRefl = reflect(-viewDir, bump);fixed3 albedo = _Color.rgb;fixed3 noiseAlbedo01 = tex2D(_Noise01, i.uv.xy).rgb ;fixed3 noiseAlbedo02 = tex2D(_Noise01, i.uv1.zw).rgb ;_Treshold = lerp(0, _Treshold, i.uv1.y);fixed cutOff01 = step(_Treshold, noiseAlbedo01);fixed cutOff02 = step(_Treshold, noiseAlbedo02);fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb;fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(viewDir, bump), 5);albedo = cutOff01*cutOff02 * _NoiseColor + albedo;fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));fixed3 halfDir = normalize(lightDir + viewDir);fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);UNITY_LIGHT_ATTENUATION(atten, i, worldPos);//return fixed4(ambient + (diffuse + specular) * atten, 1.0);return fixed4(ambient + lerp(diffuse , reflection, saturate(fresnel)) * atten, 1.0);}ENDCG}} FallBack "Specular"
}

制作RPG独立游戏练习(二)内置渲染管线中实现风格化PBR效果相关推荐

  1. 制作RPG独立游戏练习(一)美术简介篇

    本人是次时代角色模型师,自从进入游戏研发,断断续续接触了到了unity材质和shader,慢慢去了解编程,没有了原本的刻板印象,觉得在虚拟世界里能实现自己的功能和需求,有着不同于建模的成就感和兴奋感. ...

  2. 【工具篇】Unity内置渲染管线和URP以及HDRP轮廓外发光边缘光高亮显示演示

    目录 一.内置渲染管线(Built_IN) 一. 效果展示 二.使用教学 Quick Outline 二.URP渲染管线

  3. python update函数会调用哪些内置函数_Python字典的内置函数中没有 append()操作,可以用 update()来更新字典内容...

    Python字典的内置函数中没有 append()操作,可以用 update()来更新字典内容 答:√ 在同一代昭穆的兄弟中,以年岁序位. 答:对 <舆服志>主要描写了唐代的流行服饰样式, ...

  4. 分享链接在微信内置浏览器中无法打开也无法下载怎么办(Mindjump实现自动跳转浏览器)

    现如今有时候分享链接在微信内置浏览器中无法打开也无法下载,而且转发的话经常会被拦截,一旦被拦截用户在微信中识别二维码就会提示"已停止访问该网页",如此一来对外分享的二维码基本就作废 ...

  5. 微信内置浏览器中的cookie很诡异呀

    微信内置浏览器中的cookie很诡异呀 这是设置和删除COOKIE的代码 function set_cookie($var ,$value = '' ,$expire = 0){ $path = '/ ...

  6. php微信浏览器清空cookie,微信内置浏览器中的cookie很诡异呀

    微信内置浏览器中的cookie很诡异呀 这是设置和删除COOKIE的代码 function set_cookie($var ,$value = '' ,$expire = 0){ $path = '/ ...

  7. 微信php 客户端cookie,微信内置浏览器中的cookie很诡异呀

    微信内置浏览器中的cookie很诡异呀 这是设置和删除COOKIE的代码 function set_cookie($var ,$value = '' ,$expire = 0){ $path = '/ ...

  8. 安卓非微信内置浏览器中的网页调起微信支付的方案研究

    问题来源 之前在app中集成过微信支付,此种微信支付方式为app支付,即在我们自己的应用中嵌入微信支付SDK,由Native代码调起微信支付. 后来由于业务需要在我们app的WebView中打开第三方 ...

  9. vue支付宝html,vue 解决在微信内置浏览器中调用支付宝支付的情况

    我的思路大概是这样的 1. 验证是否是在微信内置浏览器中调用支付宝 2.给支付页面的url加上调用接口所需的参数(因为在微信里是不能直接调用支付宝的需要调用外部浏览器) 3.在外部浏览器中完成支付跳转 ...

最新文章

  1. 关于C++中的虚拟继承的一些总结
  2. 网络推广方案中解析SEO优化文章的标题设置技巧
  3. Django model进阶
  4. 博易大师 行情服务器文件,博易大师目录
  5. drf5 版本和认证组件
  6. 以太网RJ45 接线标准 线序(备忘)
  7. 在应用程序级别以外使用注册为 allowDefinition='MachineToApplication' 的节是错误
  8. 官宣!2020年,这5类程序员要过苦日子!网友:明年咋活?!
  9. Flutter ScrollController not attached to any scroll views 异常
  10. springboot + mybatis +easyUI整合案例
  11. chromium之message_pump_win之二
  12. Fresco几处不太好的地方
  13. js动态生成表格(添加删除行操作)
  14. 沧小海基于xilinx srio核的学习笔记之第三章 xilinx srio核介绍(三)核配置
  15. 人民日报:今天,大学培养的人才合格吗?
  16. 脚本(script)——通俗易懂去理解
  17. 等价多米诺骨牌对的数量
  18. 电商兴头上的丁磊请注意,阿里云在用AI养猪了
  19. ARM实验板移植,linux点阵字库的使用
  20. 离线状态下IDEA导入Maven依赖爆红解决

热门文章

  1. 全网最硬核 JVM TLAB 分析(额外加菜) 7. TLAB 相关 JVM 日志解析
  2. OpenGL学习笔记:(三)异或操作-橡皮条程序
  3. C语言编译时无法打开文件,在VS2010下编译无法打开包括文件:“GL/glaux.h”: No such file or director...
  4. 计算机操作系统(汤小丹、梁红兵)第四版课后习题答案(七)
  5. Python 计算 n 个自然数的立方和
  6. 5款App帮你管理收据
  7. gradle从7.0.2降级到6.5 报错
  8. 2022年版中国水环境治理行业发展规划研究与投资前景分析报告
  9. 文献阅读_基于线上评论的区域消费环境放心度与空间特征研究(中文文献)
  10. 可视化神器Plotly玩转甘特图