diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 919059d6..00000000 --- a/.gitattributes +++ /dev/null @@ -1,112 +0,0 @@ -* text=auto - -*.cs diff=csharp text -*.cginc text -*.shader text -*.spriteatlas text eol=lf -*.docx diff=word -*.html text eol=lf -#text=auto 让git自行处理左边匹配的文件使用何种换行符格式,这是默认选项。 -#text eol=lf 对左边匹配的文件统一使用LF换行符格式,如果有文件中出现CRLF将会转换成LF。 -#binary 告诉git这不是文本文件,不应该对其中的换行符进行改变。另外,binary和符号-text -diff是等价的。 -# Denote all files that are truly binary and should not be modified. -*.png binary -*.jpg binary -# Unity LFS YAML -*.anim -text merge=unityyamlmerge diff -*.asset -text merge=unityyamlmerge diff -*.controller -text merge=unityyamlmerge diff -*.mat -text merge=unityyamlmerge diff -*.meta -text merge=unityyamlmerge diff -*.physicMaterial -text merge=unityyamlmerge diff -*.physicsMaterial2D -text merge=unityyamlmerge diff -*.prefab -text merge=unityyamlmerge diff -*.unity -text merge=unityyamlmerge diff -## git-lfs ## -# Unity LFS -*.cubemap filter=lfs diff=lfs merge=lfs -text -*.unitypackage filter=lfs diff=lfs merge=lfs -text -#2D formats -*.[aA][iI] filter=lfs diff=lfs merge=lfs -text -*.[dD][dD][sS] filter=lfs diff=lfs merge=lfs -text -*.[eE][xX][rR] filter=lfs diff=lfs merge=lfs -text -*.[hH][dD][rR] filter=lfs diff=lfs merge=lfs -text -*.[iI][fF][fF] filter=lfs diff=lfs merge=lfs -text -*.[pP][iI][cC][tT] filter=lfs diff=lfs merge=lfs -text -*.[pP][sS][dD] filter=lfs diff=lfs merge=lfs -text -*.[tT][gG][aA] filter=lfs diff=lfs merge=lfs -text -*.[tT][iI][fF] filter=lfs diff=lfs merge=lfs -text -*.[tT][iI][fF][fF] filter=lfs diff=lfs merge=lfs -text - -#3D formats -*.3[dD][mM] filter=lfs diff=lfs merge=lfs -text -*.3[dD][sS] filter=lfs diff=lfs merge=lfs -text -*.[aA][bB][cC] filter=lfs diff=lfs merge=lfs -text -*.[bB][lL][eE][nN][dD] filter=lfs diff=lfs merge=lfs -text -*.[cC]4[dD] filter=lfs diff=lfs merge=lfs -text -*.[cC][oO][lL][lL][aA][dD][aA] filter=lfs diff=lfs merge=lfs -text -*.[dD][aA][eE] filter=lfs diff=lfs merge=lfs -text -*.[dD][xX][fF] filter=lfs diff=lfs merge=lfs -text -*.[fF][bB][xX] filter=lfs diff=lfs merge=lfs -text -*.[jJ][aA][sS] filter=lfs diff=lfs merge=lfs -text -*.[lL][wW][oO] filter=lfs diff=lfs merge=lfs -text -*.[lL][wW][oO]2 filter=lfs diff=lfs merge=lfs -text -*.[lL][wW][sS] filter=lfs diff=lfs merge=lfs -text -*.[lL][xX][oO] filter=lfs diff=lfs merge=lfs -text -*.[mM][aA] filter=lfs diff=lfs merge=lfs -text -*.[mM][aA][xX] filter=lfs diff=lfs merge=lfs -text -*.[mM][bB] filter=lfs diff=lfs merge=lfs -text -*.[oO][bB][jJ] filter=lfs diff=lfs merge=lfs -text -*.[pP][lL][yY] filter=lfs diff=lfs merge=lfs -text -*.[sS][kK][pP] filter=lfs diff=lfs merge=lfs -text -*.[sS][tT][lL] filter=lfs diff=lfs merge=lfs -text -*.[zZ][tT][lL] filter=lfs diff=lfs merge=lfs -text -# Audio formats -*.[aA][iI][fF] filter=lfs diff=lfs merge=lfs -text -*.[aA][iI][fF][fF] filter=lfs diff=lfs merge=lfs -text -*.[bB][aA][nN][kK] filter=lfs diff=lfs merge=lfs -text -*.[iI][tT] filter=lfs diff=lfs merge=lfs -text -*.[mM][oO][dD] filter=lfs diff=lfs merge=lfs -text -*.[mM][pP]3 filter=lfs diff=lfs merge=lfs -text -*.[oO][gG][gG] filter=lfs diff=lfs merge=lfs -text -*.[sS]3[mM] filter=lfs diff=lfs merge=lfs -text -*.[wW][aA][vV] filter=lfs diff=lfs merge=lfs -text -*.[xX][mM] filter=lfs diff=lfs merge=lfs -text -# Video formats -*.[aA][sS][fF] filter=lfs diff=lfs merge=lfs -text -*.[aA][vV][iI] filter=lfs diff=lfs merge=lfs -text -*.[fF][lL][vV] filter=lfs diff=lfs merge=lfs -text -*.[mM][oO][vV] filter=lfs diff=lfs merge=lfs -text -*.[mM][pP]4 filter=lfs diff=lfs merge=lfs -text -*.[mM][pP][eE][gG] filter=lfs diff=lfs merge=lfs -text -*.[mM][pP][gG] filter=lfs diff=lfs merge=lfs -text -*.[oO][gG][vV] filter=lfs diff=lfs merge=lfs -text -*.[wW][mM][vV] filter=lfs diff=lfs merge=lfs -text -# Build -*.[dD][lL][lL] filter=lfs diff=lfs merge=lfs -text -*.[mM][dD][bB] filter=lfs diff=lfs merge=lfs -text -*.[pP][dD][bB] filter=lfs diff=lfs merge=lfs -text -*.[sS][oO] filter=lfs diff=lfs merge=lfs -text -*.bin filter=lfs diff=lfs merge=lfs -text -*.lib filter=lfs diff=lfs merge=lfs -text -*.a filter=lfs diff=lfs merge=lfs -text -*.param filter=lfs diff=lfs merge=lfs -text -# Packaging -*.7[zZ] filter=lfs diff=lfs merge=lfs -text -*.[bB][zZ]2 filter=lfs diff=lfs merge=lfs -text -*.[gG][zZ] filter=lfs diff=lfs merge=lfs -text -*.[rR][aA][rR] filter=lfs diff=lfs merge=lfs -text -*.[tT][aA][rR] filter=lfs diff=lfs merge=lfs -text -*.[tT][aA][rR].[gG][zZ] filter=lfs diff=lfs merge=lfs -text -*.[zZ][iI][pP] filter=lfs diff=lfs merge=lfs -text -# Fonts -*.[oO][tT][fF] filter=lfs diff=lfs merge=lfs -text -*.[tT][tT][fF] filter=lfs diff=lfs merge=lfs -text -# Documents -*.[pP][dD][fF] filter=lfs diff=lfs merge=lfs -text -# Collapse Unity-generated files on GitHub -*.asset linguist-generated -*.mat linguist-generated -*.meta linguist-generated -*.prefab linguist-generated -*.unity linguist-generated diff --git a/.github/workflows/deploy-mkdocs.yml b/.github/workflows/deploy-mkdocs.yml deleted file mode 100644 index 5566a1d9..00000000 --- a/.github/workflows/deploy-mkdocs.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Deploy MkDocs to GitHub Pages - -on: - push: - branches: - - master - paths-ignore: - - 'README.md' - - 'docs/all-articles.md' - - 'docs/all-categories.md' - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Configure git for unicode paths - run: git config core.quotepath false - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - - name: Configure Pages - uses: actions/configure-pages@v5 - - - name: Install dependencies - run: | - pip install mkdocs-material mkdocs-git-revision-date-localized-plugin mkdocs-git-authors-plugin gitpython jieba pyyaml mkdocs-glightbox mkdocs-minify-plugin - - - name: Generate README, all-articles, all-categories and mkdocs nav for build - run: python scripts/generate_readme.py --target all - - - name: Check markdown docs and fail with precise file/line info - run: python scripts/check_docs.py --strict --report-json artifacts/check-report.json --summary-md artifacts/check-summary.md - - - name: Prepare docs directory for MkDocs build - run: python scripts/prepare_mkdocs_docs.py --source docs --output build_docs --mkdocs mkdocs.yml - - - name: Build site - run: mkdocs build --strict --verbose - - - name: Append check summary - if: always() - run: | - if [ -f artifacts/check-summary.md ]; then - cat artifacts/check-summary.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./site - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/docs-autofix.yml b/.github/workflows/docs-autofix.yml deleted file mode 100644 index e14b47ef..00000000 --- a/.github/workflows/docs-autofix.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Docs Auto Fix - -on: - push: - branches: - - master - paths: - - 'docs/**' - workflow_dispatch: - -permissions: - contents: write - -jobs: - normalize-images: - if: github.actor != 'github-actions[bot]' || !contains(github.event.head_commit.message, '[skip ci]') - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: pip install pyyaml - - - name: Normalize images - run: python scripts/normalize_images.py --fix --report-json artifacts/image-fix-report.json --summary-md artifacts/image-fix-summary.md - - - name: Append summary - if: always() - run: | - if [ -f artifacts/image-fix-summary.md ]; then - cat artifacts/image-fix-summary.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: docs-autofix-reports - path: artifacts/ - - - name: Check if docs changed - id: check_changes - run: | - if git diff --quiet -- docs; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - - - name: Commit and push changes - if: steps.check_changes.outputs.changed == 'true' - run: | - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add docs - git commit -m "docs: normalize image paths and update references [skip ci]" - git pull --rebase origin master - git push origin master diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml deleted file mode 100644 index 8705b8d9..00000000 --- a/.github/workflows/docs-ci.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Docs CI - -on: - pull_request: - branches: - - master - workflow_dispatch: - -permissions: - contents: read - -jobs: - docs-check: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: pip install pyyaml - - - name: Check markdown docs - run: python scripts/check_docs.py --strict --report-json artifacts/check-report.json --summary-md artifacts/check-summary.md - - - name: Check image references - run: python scripts/normalize_images.py --check --report-json artifacts/image-check-report.json --summary-md artifacts/image-check-summary.md - - - name: Append summaries - if: always() - run: | - if [ -f artifacts/check-summary.md ]; then - cat artifacts/check-summary.md >> "$GITHUB_STEP_SUMMARY" - fi - if [ -f artifacts/image-check-summary.md ]; then - printf '\n\n' >> "$GITHUB_STEP_SUMMARY" - cat artifacts/image-check-summary.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Upload reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: docs-ci-reports - path: artifacts/ diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml deleted file mode 100644 index b3123eff..00000000 --- a/.github/workflows/update-readme.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Update README and All Articles - -on: - push: - branches: - - master - paths-ignore: - - 'README.md' - - 'docs/all-articles.md' - - 'docs/all-categories.md' - - 'mkdocs.yml' - workflow_dispatch: - -jobs: - update-docs: - if: github.actor != 'github-actions[bot]' || !contains(github.event.head_commit.message, '[skip ci]') - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: pip install pyyaml - - - name: Check markdown docs - run: python scripts/check_docs.py --report-json artifacts/check-report.json --summary-md artifacts/check-summary.md || true - - - name: Generate README, all-articles and mkdocs nav - run: python scripts/generate_readme.py --target all - - - name: Append check summary - if: always() - run: | - if [ -f artifacts/check-summary.md ]; then - cat artifacts/check-summary.md >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Check if files changed - id: check_changes - run: | - if git diff --quiet README.md docs/all-articles.md docs/all-categories.md mkdocs.yml; then - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "changed=true" >> $GITHUB_OUTPUT - fi - - - name: Commit and push if changed - if: steps.check_changes.outputs.changed == 'true' - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add README.md docs/all-articles.md docs/all-categories.md mkdocs.yml - git commit -m "docs: auto-update README, all-articles, all-categories and mkdocs nav [skip ci]" - git push diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 770b16f2..00000000 --- a/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -[Ll]ibrary/ -[Tt]emp/ -[Oo]bj/ -[Bb]uild/ -[Bb]uilds/ -Assets/AssetStoreTools* - -# Visual Studio cache directory -.vs/ -.vscode/ - -# Autogenerated VS/MD/Consulo solution and project files -ExportedObj/ -.consulo/ -*.csproj -*.unityproj -*.sln -*.suo -*.tmp -*.user -*.userprefs -*.pidb -*.booproj -*.svd -*.pdb -*.opendb -*.VC.db - -# Unity3D generated meta files -*.pidb.meta -*.pdb.meta -# Unity3D Generated File On Crash Reports -sysinfo.txt - -# Builds -*.apk - -# Crashlytics generated file -Assets/StreamingAssets/crashlytics-build.properties - -resources/_gen/ -.vscode/ -public/ -node_modules/ -/resources -/.history -ignore.log -.hugo_build.lock -.DS_Store -site/ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..59246561 --- /dev/null +++ b/404.html @@ -0,0 +1,8281 @@ + + + +
+ + + + + + + + + + + + + + + + + + + +hello world
+hello
+

+LOD:
+设置:单个设置Shader.maximumLOD、全局设置Shader.globalMaximumLOD、QualitySettings里面的Maximum LODLevel +原理:小于指定值的shader和subshader才能被使用。 +应用:有时候一些显卡虽然支持很多特性,但是效率很低,此时就可以用LOD来进行控制。 +内置shader的LOD值: + VertexLit kind of shaders = 100 + Decal, Reflective VertexLit = 150 + Diffuse = 200 + Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250 + Bumped, Specular = 300 + Bumped Specular = 400 + Parallax = 500 + Parallax Specular = 600
+注释:Shader自身的LOD会覆盖全局的LOD。
+ + public Shader targetShader; private void Start ()
+ { // 全局的值会被Shader本地址覆盖
+ Shader.globalMaximumLOD = 300; if (targetShader != null)
+ {
+ targetShader.maximumLOD = 600;
+ }
+ }
+
+
+RenderQueue:
+mtrl.renderQueue = 1000;+ +
shader中使用ZTest Always,可以让被遮住的物体也渲染。
+AlphaTest:
+固定管线:使用AlphaTest命令
+动态管线:使用clip(alpha - cutoff)指令来实现
+Alpha检测在ps完成计后,即将写入帧之前,通过和一个固定的数值比较,来决定当前ps的计算结果到底要不要写入帧中。
+// inside SubShader
+Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" "IgnoreProjector"="True" } // inside CGPROGRAM in the fragment Shader:
+clip(textureColor.a - alphaCutoffValue);
+
+AlphaTest抗锯齿:
+// inside SubShader
+Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" "IgnoreProjector"="True" } // inside Pass
+AlphaToMask On
+
+AlphaBlend:
+shader渲染的最后一步,决定怎样将当前计算结果写入到帧缓存中。
+命令集:
+Blend BlendOp AlplaToMask
+// inside SubShader
+Tags { "Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True" } // inside Pass
+ZWrite Off
+Blend SrcAlpha OneMinusSrcAlpha
+
+AlphaBlend会有一系列和绘制顺序相关的问题,涉及到的知识点如下:
+(1)Unity通过Queue保证所有的不透明物体Geometry都会在半透明物体Transparent之前被渲染,(Queue标签决定了这个对象的渲染队列);
+(2)Unity保证所有Transparent队列的物体,按distance(物体的远近,不是像素的)从后往前渲染;
+(3)distance的计算方式:使用网格的几何中心点来进行半透明物体的排序。
+对于部分遮挡的物体,还是会产生不正确的遮挡效果。因此我们要么分割网格,要么使用Alpha Test或者开启ZWrite来替代。
+AlphaTest和AlphaBlend的性能比较:
+官方文档在这里:https://docs.unity3d.com/Manual/SL-ShaderPerformance.html
+翻译如下:
+固定管线的AlphaTest和可编程管线的clip()函数,在不同平台有不同性能表现:
+(1)多数平台上,AlphaTest这种整个剔除透明像素的做法能获得一点点性能优势;
+(2)在ios和android这样基于PowerVR GUPs的设备上,AlphaTest是非常耗资源的,不要企图使用它来做性能优化,因为它会让游戏更慢(是因为直接丢掉像素,让GPUs的某些优化策略没法执行了)。
+所以结论是:手机上尽量使用AlphaBlend而不是AlphaTest。
+ColorMask:
+ColorMask RGB | A | 0 | any combination of R, G, B, A
+ColorMask也是耗费比较大的操作,只在确实需要时使用。
+ZWrite/ZTest:
+ZWrite On | Off
+ZTest Less | Greater | LEqual | GEqual | Equal | NotEqual | Always
+关于相机的深度贴图_CameraDepthTexture:
+(1)默认材质都自带RenderType的Tag;
+(2)自定义sahder只有添加RenderType标签才会将深度写到_CameraDepthTexture。
+Offset:
+对Z深度的偏移。
+可以让像素看起来更靠前或更靠后,当两个面重叠时,可以手动指定谁相对靠前一些,而且不会战胜z-fitting。
+Offset只会对ZTest的条件做修正,但是并不会改变最后的Z缓冲。
+GrabPass:
+抓取当前屏幕当做贴图使用。
+ +Shader "James/VP Shader/GrabPass" {
+ Properties
+ {
+ _MainTex("MainTex", 2D) = "white" {}
+ }
+ SubShader
+ { // 在所有不透明几何体之后自画,这一点很重要
+ Tags { "Queue" = "Transparent" }
+ GrabPass { "_MyGrab" }
+ Pass
+ {
+ CGPROGRAM #pragma vertex vs
+ #pragma fragment ps #include "UnityCG.cginc"
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION;
+ float3 color : COLOR0;
+ float2 uv : TEXCOORD0;
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+ sampler2D _MyGrab;
+
+ v2f vs(appdata_base v)
+ {
+ v2f o;
+ o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
+ o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o;
+ }
+
+ float4 ps(v2f i):COLOR
+ {
+ float4 texColor = tex2D(_MainTex, i.uv);
+ float4 grabColor = tex2D(_MyGrab, i.uv); return texColor * grabColor;
+ }
+ ENDCG
+ }
+ }
+ FallBack "Diffuse" }
+
+
+Fog:
+雾效实现的三种方式:
+(1)全局雾
+RenderSettings.fog = true; + RenderSettings.fogColor = Color.red; + RenderSettings.fogMode = FogMode.Linear; + RenderSettings.fogStartDistance = 0; + RenderSettings.fogEndDistance = 10;+ +
(2)Fog指令
+ Fog{ Mode Linear Color(1, 0, 0) Range 0, 10 }
+
+(3)Shader计算方式
+ +Shader "James/VP Shader/Fog" {
+ Properties {
+ _MainTex ("Base (RGB)", 2D) = "white" {}
+ _FogColor("FogColor", Color) = (1, 1, 1, 1)
+ _Density("Density", Range(0, 10)) = 1 _NearDistance("NearDistance", Float) = 0 _FarDistance("FarDistance", Float) = 10 }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ Fog { Mode Off }
+ Pass
+ {
+ CGPROGRAM #pragma vertex vs
+ #pragma fragment ps #include "UnityCG.cginc"
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION;
+ float3 color : COLOR0;
+ float2 uv : TEXCOORD0;
+ float4 depth : TEXCOORD1;
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+ float4 _FogColor; float _Density; float _NearDistance; float _FarDistance;
+
+ v2f vs(appdata_base v)
+ {
+ v2f o;
+ o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
+ o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
+ o.depth = mul(UNITY_MATRIX_MV, v.vertex);
+ o.depth.z = -o.depth.z;
+ o.depth.w = (_FarDistance - _NearDistance) * o.depth.w; return o;
+ }
+
+ float4 ps(v2f i):COLOR
+ {
+ float4 texColor = tex2D(_MainTex, i.uv); float fg = 0; if(i.depth.z > _NearDistance && i.depth.z < _FarDistance)
+ {
+ fg = i.depth.z / i.depth.w;
+ } else if(i.depth.z > _FarDistance)
+ {
+ fg = _FarDistance / i.depth.w;
+ } return fg * _Density * _FogColor * texColor;
+ }
+ ENDCG
+ }
+ }
+ FallBack "Diffuse" }
+
+
+Stencil:
+Stencil-Test在Z-Test和Alpha-Test之前,如果模板检测不通过,则像素直接被丢掉而不会执行fragment函数。
+ +Shader "James/VP Shader/Stencil" {
+ Properties {
+ _MainTex ("Base (RGB)", 2D) = "white" {}
+ _refVal("Stencil Ref Value",int)=0 }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ ZTest Always
+ Stencil
+ {
+ Ref [_refVal]
+ Comp GEqual
+ Pass Replace
+ Fail keep
+ ZFail keep
+ }
+ Pass
+ {
+ CGPROGRAM #pragma vertex vs
+ #pragma fragment ps #include "UnityCG.cginc"
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION;
+ float3 color : COLOR0;
+ float2 uv : TEXCOORD0;
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+
+ v2f vs(appdata_base v)
+ {
+ v2f o;
+ o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
+ o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o;
+ }
+
+ float4 ps(v2f i):COLOR
+ {
+ float4 texColor = tex2D(_MainTex, i.uv); return texColor;
+ }
+ ENDCG
+ }
+ }
+ FallBack "Diffuse" }
+
+[
+
](javascript:void(0); "复制代码")
+++
+- [ ] 绍跳点搜索(JPS)算法
+IL学习:
+
+C#基础拾遗系列之一:先看懂IL代码
+读懂IL代码就这么简单 (一) +c# IL 指令集
+c#的逆向工程-IL指令集
+IL指令详细表
我这里的路径:
+C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ildasm.exe
+

结论:int强转enum和enum正常赋值是一样的,强转失败不会报异常,且赋值成功
+using System;
+namespace EnumTest
+{
+ internal class Program
+ {
+ enum Hello
+ {
+ A=0xfff0,
+ B= 0xfff1,
+ }
+
+ static void Main(string[] args)
+ {
+ int n = 0xfff0;
+ int m = 0xfff2;
+ // int强转enum和enum正常赋值是一样的开销 不做越界处理
+ Hello h1=(Hello)n;
+ int b1 = m;
+
+
+ Console.WriteLine(h1);
+ Console.WriteLine(b1);
+ Console.ReadKey();
+ }
+ }
+}
+
+
+生成exe(Debug模式下,Release模式下代码被优化)后查看id代码
+//Debug模式
+.method private hidebysig static void Main(string[] args) cil managed
+{
+ .entrypoint
+ // 代码大小 43 (0x2b)
+ .maxstack 1
+ .locals init ([0] int32 n,
+ [1] int32 m,
+ [2] valuetype EnumTest.Program/Hello h1,
+ [3] int32 b1) //局部变量索引
+ IL_0000: nop
+ IL_0001: ldc.i4 0xfff0 //入栈
+ IL_0006: stloc.0 // n赋值 栈顶出栈
+ IL_0007: ldc.i4 0xfff2 //入栈
+ IL_000c: stloc.1 // m赋值 栈顶出栈
+ IL_000d: ldloc.0 //加载 入栈 n
+ IL_000e: stloc.2 // h1=n 栈顶出栈
+ IL_000f: ldloc.1 // 加载 入栈 m
+ IL_0010: stloc.3 // b1=m 栈顶出栈
+ IL_0011: ldloc.2 //加载 入栈 h1
+ IL_0012: box EnumTest.Program/Hello //栈顶h1出栈 装箱结果入栈
+ IL_0017: call void [mscorlib]System.Console::WriteLine(object) //使用一个参数,使用后栈顶出栈
+ IL_001c: nop
+ IL_001d: ldloc.3 //加载 入栈 b1
+ IL_001e: call void [mscorlib]System.Console::WriteLine(int32)
+ IL_0023: nop
+ IL_0024: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
+ IL_0029: pop
+ IL_002a: ret
+} // end of method Program::Main
+
+
+//Relase模式
+.method private hidebysig static void Main(string[] args) cil managed
+{
+ .entrypoint
+ // 代码大小 36 (0x24)
+ .maxstack 2
+ .locals init ([0] int32 m,
+ [1] int32 b1)
+ IL_0000: ldc.i4 0xfff0
+ IL_0005: ldc.i4 0xfff2
+ IL_000a: stloc.0 // m=0xfff2
+ IL_000b: ldloc.0
+ IL_000c: stloc.1 // b1=0xfff0
+ IL_000d: box EnumTest.Program/Hello
+ IL_0012: call void [mscorlib]System.Console::WriteLine(object)
+ IL_0017: ldloc.1 //加载b1
+ IL_0018: call void [mscorlib]System.Console::WriteLine(int32)
+ IL_001d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
+ IL_0022: pop
+ IL_0023: ret
+} // end of method Program::Main
+
+
+结论:int强转成enum越界时赋值会成功,但是输出是int的值 +代码
+using System;
+namespace EnumTest
+{
+ internal class Program
+ {
+ enum Hello
+ {
+ A=0xfff0,
+ B= 0xfff1,
+ }
+
+ static void Main(string[] args)
+ {
+ int n = 0xfff0;
+ int m = 0xfff2;
+
+ Hello h1=(Hello)n;
+ int b1 = m;
+
+ Console.WriteLine(h1.ToString());
+ h1 = (Hello)100;
+ //int强转成enum越界时赋值会成功,但是输出是int的值
+ Console.WriteLine(h1.ToString());
+ Console.WriteLine(b1);
+ Console.ReadKey();
+ }
+ }
+}
+
+
+输出:
+A
+100
+65522
+
+++https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct
+
结论:
+✔️ 如果类型的实例很小且通常短暂存在或通常嵌入在其他对象中,请考虑定义结构而不是类。
+❌ 避免定义结构,除非该类型具有以下所有特征: +- 它在逻辑上表示单个值,类似于原始类型(int、double等)。 +- 它的实例大小小于 16 个字节。 +- 它是不可变的。 +- 它不必经常装箱。
+++可以这么理解:值类型的数组是真正的线性结构;而引用类型数组类似树结构。因此相率上存在天然差距!
+引用类型的对象数组,物理上一般是分配指针来指向引用的实例,此时数组的内存块不能涵盖所有要访问的数据。而struct数组在这种情况下所有会用到的数据都在数组的物理内存之中包含,可以直接访问到,无需通过GC堆内存的对象引用来反复的间接查找。同时,如果实例数量非常多时,使用struct数组还能避免大量分散在GC堆中的对象实例,从而减轻GC压力。这里理想化的认为struct的定义中所有字段都是值类型的,不包含string等引用类型。
+
对比:
++--------------------------------------------------+------+----------------------------------------------+
+| Struct | | Class |
++--------------------------------------------------+------+----------------------------------------------+
+| - 1 per Thread. | | - 1 per application. |
+| | | |
+| - Holds value types. | | - Holds reference types. |
+| | | |
+| - Types in the stack are positioned | | - No type ordering (data is fragmented). |
+| using the LIFO principle. | | |
+| | | |
+| - Can't have a default constructor and/or | | - Can have a default constructor |
+| finalizer(destructor). | | and/or finalizer. |
+| | | |
+| - Can be created with or without a new operator. | | - Can be created only with a new operator. |
+| | | |
+| - Can't derive from the class or struct | VS | - Can have only one base class and/or |
+| but can derive from the multiple interfaces. | | derive from multiple interfaces. |
+| | | |
+| - The data members can't be protected. | | - Data members can be protected. |
+| | | |
+| - Function members can't be | | - Function members can be |
+| virtual or abstract. | | virtual or abstract. |
+| | | |
+| - Can't have a null value. | | - Can have a null value. |
+| | | |
+| - During an assignment, the contents are | | - Assignment is happening |
+| copied from one variable to another. | | by reference. |
++--------------------------------------------------+------+----------------------------------------------+
+
+++Windbg调试命令详解 +https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/debugger-download-tools +https://docs.microsoft.com/zh-cn/sysinternals/downloads/procdump +https://www.cnblogs.com/harrychinese/p/winbug.html +https://cloud.tencent.com/developer/article/1045085 +http://fresky.github.io/2015/06/21/how-to-attack-the-memory-leak-issue/ +生产环境诊断利器 WinDbg 帮你快速分析异常情况 Dump 文件 +windbg调试命令
+
使用windows store 搜索 WinDbg preview 然后下载
+ 
下载windows SDK,并且勾选WinDbg;
+如果已经安装windows SDK 使用修复 :
+
+
+
+
添加环境变量
+_NT_SYMBOL_PATH=C:\Symbols;srv*C:\Symbols*http://msdl.microsoft.com/download/symbols;
+
+_NT_ALT_SYMBOL_PATH=cache*C:\Symbols
+
+
#step1
+dotnet tool install -g dotnet-sos
+#step2
+dotnet-sos install
+
+#安装成功后有SOS install succeeded的提示,并且返回有安装路径。
+PS E:\Project> dotnet-sos install
+Installing SOS to C:\Users\coding\.dotnet\sos
+Installing over existing installation...
+Creating installation directory...
+Copying files from C:\Users\coding\.dotnet\tools\.store\dotnet-sos\6.0.328102\dotnet-sos\6.0.328102\tools\netcoreapp3.1\any\win-x64
+Copying files from C:\Users\coding\.dotnet\tools\.store\dotnet-sos\6.0.328102\dotnet-sos\6.0.328102\tools\netcoreapp3.1\any\lib
+Execute '.load C:\Users\coding\.dotnet\sos\sos.dll' to load SOS in your Windows debugger.
+Cleaning up...
+SOS install succeeded
+
+命令 !DumpHeap -stat不可用
.loadby sos clr
+如果失败需要 (sos.dll和clr.dll一般情况下在同一个目录)
+.load 绝对路径的sos.dll
+.load 绝对路径的clr.dll
+
+++https://www.cnblogs.com/Leo_wl/p/3329437.html +https://blog.csdn.net/ghhong1988/article/details/104674837 +想要一次性成功搭建测试环境,那得靠人品。看来我近来人品积累的不够,不断的有小问题出现。比如加载SOS和CLR,就让我不胜其烦。必须得记下来,分享出来,以节省大家的时间。
+问题一:WinDBG分X86和X64两个版本
+如果你用的是32位的WinDBG,那直接打开就行;你如果用的是64位的版本,那么如果调试64位代码也直接打开,如果调试x86的代码,要使用Wow64下的WinDBG.exe。
+问题二:确定SOS和CLR的位置和版本
+如果安装了Visual Studio的机器,可以打开VS的命令行,输入where sos.dll命令,可以找到sos.dll的全路径(需要说明的是,找到的不一定是全部的文件)。它的一般位置在C:\Windows\Microsoft.NET\Framework?\version?\SOS.dll。其中Framework?包括Framework和Framework64两个版本;version?包括v2.0.50727,v3.0,v3.5和v4.0.30319等版本。文件确切路径的选择依据要调试程序的版本而定,一般为C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll,CLR为同一目录下的CLR.dll文件。
+问题三:加载SOS和CLR
+运气好的话,使用命令.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\SOS.dll可以加载成功。如果失败,特别是出现The call to LoadLibrary(C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll) failed, Win32 error 0n193这样的错误,请确认加载sos.dll的版本是否正确。
+此外,加载不出错,并不见得可以直接使用。可以尝试命令
+ +.loadby sos clr。如果命令成功,那么测试环境好了。如果出现了“Unable to find module 'clr'”这样的错误。请键入g让调试程序运行一会儿,停下来的时候再尝试命令.loadby sos clr,这时一般都会成功。 +要加载扩展,有2个命令。一个是.loadby,另一个是.load。对于.loadby,请使用相对路径;对于.load,请使用完整路径。
+对于.loadby,有5个选项: +- loadby sos mscorsvr +- loadby sos mscorwks +- loadby sos clr +- loadby sos coreclr +- loadby sos
+其中mscorsvr确实很旧(.NET CLR 1,服务器版本),mscorwks确实很旧(.NET CLR 1和2,但仍然存在),clr是在当今很常见(.NET CLR 4),coreclr可能正在增加(UWP和Silverlight)
+
当尚未加载.NET运行时时,您正在尝试加载SOS。等待直到加载.NET,然后该命令将起作用。在初始断点处肯定是不可能的。 sxe ld clr 表示让应用程序运行到.NET可用
sxe ld clr
+ sxe ld mscorwks
+ sxe ld coreclr
+ g
+
+方式1
+任务管理器,进程 -> 创建转储文件 ;得到当前进程的dump文件
+方式2
+使用ProcDump 工具
+# mydotNetApp.exe是进程名字
+procdump -ma mydotNetApp.exe d:\myapp.dmp
+# TestCase.exe是进程名字
+.\procdump.exe -ma TestCase.exe e:\test.dmp
+
+方式3
+使用vs调试,断点后,点击 调试菜单-> 将转储另存为
+
Windbg(x86)
+
+C:\Users\coding\AppData\Local\Microsoft\WindowsApps\WinDbgX.exe
+C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe
+
+ -srcpath $(ProjectDir) $(TargetPath)
+
+ $(TargetDir)
+
+
C:\Symbols
+srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
+
+cache*C:\Symbols
+
+ !DumpHeap -stat
+
+0:000> !clrstack
+No export clrstack found
+0:000> !dumpstackobjects
+No export dumpstackobjects found
+0:000> sxe ld clr
+0:000> sxe ld mscorwks
+0:000> sxe ld coreclr
+
+windbg命令分标准命令、元命令和扩展命令。
+标准命令提供基本功能,不区分大小写。比如dt、lm、g、bl、bc、p等。
+提供标准命令没有提供的功能,也内建在调试引擎中,以.开头。如.sympath .reload等。
+扩展命令用于扩展某一方面的调试功能,实现在动态加载的扩展模块中,以!开头。如!analyze等。
+使用;作为分隔符,可以在同一行输入多条命令。
+一次可以执行多条命令,命令间用分号;分隔 【如:bp main;bp view.cpp:120】,一次打2个断点。
+ + ++用windbg调试C#代码是比较麻烦的,因为windbg是针对OS层级的,而C#被CLR隔了一层,很多原生的命令如查看局部变量dv、查看变量类型dt等在CLR的环境中都不能用了。必须使用针对CLR的扩展命令,比如sos、psscor2中的命令。
+ ++
sxe ld clr # 添加中断 (加载指定名称的dll时,调试器中断)
+ # sxe ld mscorwks # 添加中断 (加载指定名称的dll时,调试器中断)
+ # sxe ld coreclr # 添加中断 (加载指定名称的dll时,调试器中断)
+ # sxe ld clrjit # 添加中断 (加载指定名称的dll时,调试器中断)
+ g # 运行,当遇到中断或者断点时 中断
+ # 让应用程序运行到.NET可用(clr等核心初始化后,这时候.net环境初始化完成)
+ .loadby sos clr ## 加载sos模块 (宿主程序必须加载clr后执行,否则会找不到clr)
+
+ !bpmd DotnetTest DotnetTest.Program.Main ## Main函數添加断点
+ !bpmd -md 005e4d90 #在指定的MethodDesc地址处添加断点
+ g # 运行,当遇到中断或者断点时 中断
+ !name2ee DotnetTest DotnetTest.Program.Main
+
+.cordll -ve -u -l
+lmv mclr #(适用于 CLR 4.0 版)
+
+lmv mscorwks #(适用于 CLR 的 1.0 或 2.0 版)
+
+!sos.help
+!sos.Threads
+!sos.DumpDomain
+
+k
+kv
+kb
+x *!
+
+
+.load和.loadby 命令将新的扩展DLL加载到调试器中。¶使用形式
+.load DLLName
+!DLLName.load
+.loadby DLLName ModuleName
+
+参数
+DLLName
+指定要加载的调试器扩展DLL。如果使用.load命令,DLLName应包含完整路径。如果使用.loadby命令,DLLName应仅包含文件名。
+
+ModuleName
+指定与DLLName指定的扩展DLL位于同一目录中的模块的模块名。
+
+
+sxe ld
有些场景需要使用windbg调试某个dll模块,而这个模块加载时机不是很确定。
+通常需要使用sxe ld
lm 和 .chain
查看已经加载模块的列表
+Name2EE <module name> <type or method name>
+++
Name2EE <module name>!<type or method name>
显示指定模块中指定类型或方法的MethodTable结构和结构。指定的模块必须在进程中加载。要获得正确的类型名称,请使用Ildasm.exe (IL Disassembler)浏览模块。您还可以作为模块名称参数传递以搜索所有加载的托管模块。模块名称参数也可以是模块的调试器名称,例如或。此命令支持 < > < > 的 Windows 调试器语法。类型必须是完全限定的。EEClass
+0:000> !name2ee DotnetTest DotnetTest.Program.Main
+Module: 00c44044
+Assembly: DotnetTest.exe
+Token: 06000009
+MethodDesc: 00c44d90
+Name: DotnetTest.Program.Main(System.String[])
+Not JITTED yet. Use !bpmd -md 00c44d90 to break on run.
+
+BPMD [-nofuturemodule] [<module name> <method name>] [-md <MethodDesc>] -list -clear <pending breakpoint number> -clearall
在指定模块中的指定方法处创建断点。
+如果指定的模块和方法尚未加载,则此命令在创建断点之前等待模块已加载和即时 (JIT) 编译的通知。
+您可以使用-list、-clear和-clearall选项管理挂起断点的列表:
+-list选项生成所有挂起断点的列表。如果挂起的断点具有非零模块 ID,则该断点特定于该特定加载模块中的函数。如果挂起的断点的模块 ID 为零,则该断点适用于尚未加载的模块。
+使用-clear或-clearall选项从列表中删除挂起的断点。
+# 添加断点
+!bpmd DotnetTest DotnetTest.Program.Main #[<module name> <method name>]
+!bpmd DotnetTest DotnetTest.HeapStack.New
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 垃圾回收的基本知识:
+https://github.com/dotnet/docs.zh-cn/blob/live/docs/standard/garbage-collection/fundamentals.md
+垃圾回收的基本知识:
+https://docs.microsoft.com/zh-cn/dotnet/standard/garbage-collection/fundamentals
清理未托管资源:
+https://docs.microsoft.com/zh-cn/dotnet/standard/garbage-collection/unmanaged
.NET documentation 中文版:
+https://docs.microsoft.com/zh-cn/dotnet/fundamentals/












[ ] UI生成的view文件,这里执行比较浪费效率,有可能无用的节点也会Find transform 节点
+
+ 方案:使用C#属性的方式去处理,只有使用的时候才get,能有效的减少无用节点的使用
[ ] 字符串拼接问题,使用公用 StringBuilder 进行处理
using System.Text;
+using Sproto;
+
+namespace Skyunion
+{
+ public static class StringBuilderCache
+ {
+ private const int MAX_Size = 512;
+ private static StringBuilder s_Cache = new StringBuilder(MAX_Size, MAX_Size);
+
+ public static StringBuilder Get(int capacity = 0)
+ {
+ if (s_Cache.Capacity < capacity)
+ return new StringBuilder(capacity);
+ s_Cache.Length = 0;
+ return s_Cache;
+ }
+ }
+}
+
+[ ] 所有new 数组的地方建议使用公共方法去管理,或者组成一个共有池
+[ ] 所有使用 Physics.RaycastAll和Physics.Raycast的地方修改成无GC的方法
public static int RaycastNonAlloc(Ray ray, RaycastHit[] results, float maxDistance, int layerMask)
+
+
++方式1:
+
+点击gameObject 根据GameObject的name字符串查找对应的业务对象
+方式2:
+初始化gameObject时添加一个组件,组件内有id字段,点击gameObject时根据gameObject上的组件找到id,然后根据id找到对应业务对象
+方式3:
+点击gameObject,根据gameObject的HashCode找到对应业务对象
对比耗时:
+使用800000个gameObject测试:
+
方式1的耗时:重命名gameObject+通过字符串查找业务对象; 耗时 503+316 毫秒 +方式2的耗时:给gameObject添加组件+通过id查找业务对象; 耗时 3883+191 毫秒 +方式3的耗时:gameObject获取HashCode+通过HashCode查找业务对象;耗时 34+12 毫秒
+结论:
+gameObject的HashCode查找效率最高,其次是字符串查找,添加组件绑定id的方式最慢
设置登录信息,并且派发登录事件(开发模式)
+
+设置登录信息,并且派发登录事件(线上模式)
+
+链接服务器
+
+连接服务器后进行重定向,再次连接服务器,并且验证;重定向且验证后表示登录成功
登录成功后获取角色列表
+
获取角色回调后后使用第一个角色
+
+
使用第一个角色进行登录,登录角色服
+发送: SprotoType.Role_RoleLogin
+
收到登录角色服的回调,返回角色信息
+



相机参数初始化
+

+
++监听
+WorldCamera.Instance().AddViewChange(OnWorldViewChange);+
+
+
+*发送给服务器相机移动的消息public void SendMapMove(long x, long y)*
FogSystemMediator.cs(300):WorldCamera.Instance().AddViewChange(OnMapViewChange);
+GlobalViewLevelMediator.cs(553):WorldCamera.Instance().AddViewChange(OnWorldViewChange);
+MapUIiconMyCityMediator.cs(175):WorldCamera.Instance().AddViewChange(OnWorldViewChange);
+PVPGlobalMediator.cs(468):WorldCamera.Instance().AddViewChange(OnWorldViewChange);
+WorldMgrMediator.cs(312):WorldCamera.Instance().AddViewChange(OnWorldViewChange);
+MainInterfaceMediator.cs(724):WorldCamera.Instance().AddViewChange(OnWorldViewChange);
+WorldPanelMediator.cs(96):WorldCamera.Instance().AddViewChange(OnWorldViewChange);
+
+初始化添加单位及更新单位数据
+
+
+相机视野改变,调整单位
+
单位资源加载
+public static void WorldMapViewObjFactory.SysLoadWorldObj(MapObjectInfoEntity data, string prefabname, Action<GameObject, MapObjectInfoEntity> callback)
+
+public void WorldMgrMediator.OnLoadMapObj(GameObject go,MapObjectInfoEntity data)
+
+public void ReadMapBriefDataFromFile2(string map_data_path, int server_x, int server_y, Action action = null)
+
+
+
int Pack(Sproto.SprotoTypeBase o)
+ {
+ byte[] encodeWarpPackage;
+ int gameSession;
+ long sprotoId;
+ byte[] token = new byte[0];
+ pack.Encode(o, ref token, out encodeWarpPackage, out gameSession, out sprotoId);
+ return encodeWarpPackage.Length;
+ }
+
+ NetMsgPackage pack = new NetMsgPackage();
+ Role_CreateArmy.request req ;
+ public override void Update()
+ {
+ if (Input.GetKeyUp(KeyCode.Y))
+ {
+ req = new Role_CreateArmy.request();
+ Debug.LogError($" 0 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ req = new Role_CreateArmy.request();
+ req.targetType = 1;
+ Debug.LogError($" 1 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ req = new Role_CreateArmy.request();
+ req.targetType = 1;
+ req.isSituStation = true;
+ Debug.LogError($" 2 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ req = new Role_CreateArmy.request();
+ req.targetType = 1;
+ req.isSituStation = true;
+ req.targetArg = new MarchTargetArg { targetName = "hello" };
+ Debug.LogError($" 3 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ }
+ if(Input.GetKeyUp(KeyCode.U))
+ {
+ req = new Role_CreateArmy.request();
+ req.heros = new List<MarchHero>(1);
+ req.heros.Add(new MarchHero() { heroId = 2 });
+ Debug.LogError($" map 1 >>1 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ req = new Role_CreateArmy.request();
+ req.heros = new List<MarchHero>(1);
+ req.heros.Add(new MarchHero(){ heroId = 2 });
+ req.heros.Add(new MarchHero(){ heroId = 3 });
+ req.heros.Add(new MarchHero(){ heroId = 4 });
+ req.heros.Add(new MarchHero(){ heroId = 200 });
+ req.heros.Add(new MarchHero(){ heroId = 300 });
+ req.heros.Add(new MarchHero(){ heroId = 400 });
+ req.heros.Add(new MarchHero(){ heroId = 20 });
+ req.heros.Add(new MarchHero(){ heroId = 30 });
+ req.heros.Add(new MarchHero(){ heroId = 40 });
+ Debug.LogError($" map 1 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ req = new Role_CreateArmy.request();
+ req.heros = new List<MarchHero>(1);
+ req.heros.Add(new MarchHero() { heroId = 2 });
+ req.heros.Add(new MarchHero() { heroId = 3 });
+ req.heros.Add(new MarchHero() { heroId = 4 });
+ req.heros.Add(new MarchHero() { heroId = 200 });
+ req.heros.Add(new MarchHero() { heroId = 300 });
+ req.heros.Add(new MarchHero() { heroId = 400 });
+ req.heros.Add(new MarchHero() { heroId = 20 });
+ req.heros.Add(new MarchHero() { heroId = 30 });
+ req.heros.Add(new MarchHero() { heroId = 40 });
+ req.targetType = 1;
+ req.isSituStation = true;
+ Debug.LogError($" map 4 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ }
+ if (Input.GetKeyUp(KeyCode.I))
+ {
+ req = new Role_CreateArmy.request();
+ req.soldiers = new Dictionary<long, SoldierInfo>();
+ req.soldiers[2] = new SoldierInfo { id = 2 };
+ req.soldiers[3] = new SoldierInfo { id = 3 };
+ req.soldiers[4] = new SoldierInfo { id = 4 };
+ req.soldiers[5] = new SoldierInfo { id = 200 };
+ req.soldiers[6] = new SoldierInfo { id = 300 };
+ req.soldiers[7] = new SoldierInfo { id = 400 };
+ req.soldiers[8] = new SoldierInfo { id = 20 };
+ req.soldiers[9] = new SoldierInfo { id = 30 };
+ req.soldiers[10]= new SoldierInfo { id = 40 };
+ req.heros = new List<MarchHero>(1);
+ req.heros.Add(new MarchHero() { heroId = 2 });
+ req.heros.Add(new MarchHero() { heroId = 3 });
+ req.heros.Add(new MarchHero() { heroId = 4 });
+ req.heros.Add(new MarchHero() { heroId = 200 });
+ req.heros.Add(new MarchHero() { heroId = 300 });
+ req.heros.Add(new MarchHero() { heroId = 400 });
+ req.heros.Add(new MarchHero() { heroId = 20 });
+ req.heros.Add(new MarchHero() { heroId = 30 });
+ req.heros.Add(new MarchHero() { heroId = 40 });
+ req.targetType = 1;
+ req.isSituStation = true;
+ Debug.LogError($" map 5 >>>{Pack(req)}>>>{Codingriver.Dumper.Dump(req)}");
+ }
+ }
+
+
++当前冗余资源 111 ,总资源数 5430 +
+
++shader的ab文件
+all_shader.ab大小1291kb+shader.ab大小302kb+(shader.ab内容) +
(all_shader.ab内容) 应该是把所以shader打包进去,但是Container 异常
+
+
解决办法:
+- 导入Unity内置Shader到项目中
+var arr = AssetDatabase.GetDependencies(path, true); 不能收集到内置Shader依赖。
Shader 中 UsePass和FallBack及其他依赖需要单独处理
+var arr = AssetDatabase.GetDependencies(path, true); 无法收集Shader引用依赖,但是如果shader打包bundle会自动手机依赖,造成引用计数错误,可能会造成冗余
变体收集(keywords),ShaderVariantCollection生成资产.shadervariants,然后收集整个项目的变体
+ 这里需要注意 ShaderVariantCollection.WarmUp,项目使用一个ShaderVariantCollection资产,会处理所有的变体,WarmUp执行可能需要更长时间
m_SavedProperties的数据冗余,无用数据被打包进去
+
Tools/清理材质中的废弃属性 清理+++
AssetDatabase.GetDependencies无法获取unity内置shader的依赖关系,也不能获取Shader依赖其他Shader的依赖关系(FallBack ,UsePass等等)+
EditorUtility.CollectDependencies()可以获取内置Shader的依赖信息,而且能否收集shader依赖其他Shader的关系 (FallBack可以收集,UsePass需要测试)
打包shader使用ShaderVariantCollection收集keyword组合,单独使用list引用所有要使用的shader,这时候发现会有unity 内置shader在list中,需要剔除,Standard变体太多,不能加入list中,不能被打包进去,需要将所有引用Standard的资源分析替换其他shader,()
IPreprocessShaders来收集所以keyword变体,内置shader冗余(AssetDatabase.GetDependencies无法获取unity内置shader的依赖关系)
默认材质球及内置Shader冗余,没有材质球资源的情况,这时候使用内置默认材质球(Default-Material,Sprites-Default,Default-ParticleSystem)
+ > IPreprocessShaders 可能无法处理默认材质球的shader问题,所以在上一步处理内置冗余时,默认材质球的引用没有收集到。
材质球引用的纹理Texture 且Texture是Sprite资源,这时候Texture单独一个AB文件,材质球是另一个AB文件,则会出现冗余
+++使用
+BuildPipeline.BuildAssetBundles打包AB文件会有这个问题 (图中下面绿色框,error文件夹)
UnityEditor.Build.Pipeline.CompatibilityBuildPipeline.BuildAssetBundles会解决这个问题(图中上面蓝色色框,general文件夹)+ ++

材质球m_SavedProperties中的无用属性(有可能引用纹理,但是已经删除了)
++上图中
+RenderTexture #4纹理,实际材质球中已经删除,但是还是会被引用
完全替换内置资源
++ ++
++物体缓存池 优先 +gc检查 +特效 +
+

++GC问题 +-
+String.Format
//TODO:GC严重,调用频繁(一帧内调用多次)
+ view.m_lbl_count_troops_LanguageText.text =string.Format("{0}/{1}", count, m_TroopProxy.GetTroopDispatchNum());
+
+
case RssType.HolyLand_PVP:
+ case RssType.Sanctuary_PVP:
+ case RssType.Altar_PVP:
+ case RssType.Shrine_PVP:
+ case RssType.LostTemple_PVP:
+ case RssType.CheckPoint_PVP:
+ case RssType.Checkpoint_1_PVP:
+ case RssType.Checkpoint_2_PVP:
+ case RssType.Checkpoint_3_PVP:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ++https://git-lfs.github.com/
+
+https://www.jianshu.com/p/493b81544f80
+https://zzz.buzz/zh/2016/04/19/the-guide-to-git-lfs/
# 查看安装的git lfs 环境信息
+git lfs env
+git lfs env | head -2
+
+# 查看当前使用 Git LFS 管理的匹配列表
+git lfs track
+
+# 使用 Git LFS 管理指定的文件
+git lfs track "*.psd"
+
+# 不再使用 Git LFS 管理指定的文件
+git lfs untrack "*.psd"
+
+# 类似 `git status`,查看当前 Git LFS 对象的状态
+git lfs status
+
+# 枚举目前所有被 Git LFS 管理的具体文件
+git lfs ls-files
+
+# 检查当前所用 Git LFS 的版本
+git lfs version
+
+# 针对使用了 LFS 的仓库进行了特别优化的 clone 命令,显著提升获取
+# LFS 对象的速度,接受和 `git clone` 一样的参数。 [1] [2]
+git lfs clone https://github.com/user/repo.git
+
+git lfs install开启lfs功能git lfs track "*.png" 添加大文件追踪,所有的png文件
git lfs track "*.filetype"git lfs track "directory/*"git lfs track "path/to/file"git lfs track 查看所有的lfs文件追踪模式
git lfs ls-files 可以显示当前跟踪的文件列表# 重写 master 分支,将历史提交中的 *.zip 都用 git lfs 进行管理
+git lfs migrate import --include-ref=master --include="*.png"
+
+# 重写所有分支及标签,将历史提交中的 *.rar,*.zip 都用 git lfs 进行管理
+git lfs migrate import --everything --include="*.bin,*.lib,*.so,*.dll,*.a,*.param,*.zip,*.gz,*.png,*.jpg,*.unitypackage,*.hdr,*.HDR"
+
+注意: 重写历史后的提交需执行:
+git push --force --all
+
+注意: 如有迁移至 git lfs 前的仓库的多份拷贝,其他拷贝可能需要执行 git reset --hard origin/master 来重置其本地的分支,注意执行 git reset --hard 命令将会丢失本地的改动。
+如果需要对本地仓库进行清理(非安全的)
+git reflog expire --expire-unreachable=now --all
+git gc --prune=now
+
+git clone命令¶git clone -b ${branch} --depth=1 可以拉对应分支
git clone --depth=1后只有一个分支的解决办法¶+++
git clone --depth=1克隆项目后,使用git branch -r查看远程仓库只显示一个分支,实际远程仓库有多个分支。这种方法克隆的项目只包含最近的一次commit的一个分支,体积很小,即可解决文章开头提到的项目过大导致Timeout的问题,但会产生另外一个问题,他只会把默认分支clone下来,其他远程分支并不在本地,也看不到其他远程分支。
+
checkout 一个新的分支¶$ git clone --depth 1 https://github.com/dogescript/xxxxxxx.git
+$ git remote set-branches origin 'remote_branch_name'
+$ git fetch --depth 1 origin remote_branch_name
+$ git checkout remote_branch_name
+
+++参考:https://blog.csdn.net/qq_29094161/article/details/120649473
+
修改.git/config文件中的
+fetch = +refs/heads/master:refs/remotes/origin/master
+为
+fetch = +refs/heads/*:refs/remotes/origin/*
+然后执行
git fetch --all
++参考https://blog.csdn.net/a924068818/article/details/115725982
+
git remote update origin --prune
+
+++这个命令没有测试
+
git push -f origin master
+git push --force origin master:master
+
+在repository中找到名字为master的branch,使用它去更新local repository中找到名字为mm的branch,,如果local repository下不存在名字是mm的branch,那么新建一个。(如果mm不是当前激活的分支,git pull执行完也不是)
+git push origin master:master
+
+在local repository中找到名字为master的branch,使用它去更新remote repository下名字为master的branch,如果remote repository下不存在名字是master的branch,那么新建一个
+++git clean命令用来从你的工作目录中删除所有没有tracked过的文件
+git clean经常和
+git reset --hard一起结合使用. 记住reset只影响被track过的文件, 所以需要clean来删除没有track过的文件. 结合使用这两个命令能让你的工作目录完全回到一个指定的的状态
git clean -n
+ 是一次clean的演习, 告诉你哪些文件会被删除. 记住他不会真正的删除文件, 只是一个提醒
git clean -f
+ 删除当前目录下所有没有track过的文件. 他不会删除.gitignore文件里面指定的文件夹和文件, 不管这些文件有没有被track过
git clean -f <path>
+ 删除指定路径下的没有被track过的文件
git clean -df
+ 删除当前目录下没有被track过的文件和文件夹
git clean -xf
+ 删除当前目录下所有没有track过的文件. 不管他是否是.gitignore文件里面指定的文件夹和文件
下面的例子要删除所有工作目录下面的修改, 包括新添加的文件. 假设你已经提交了一些快照了, 而且做了一些新的开发
+git reset --hard
+
+git clean -df
+
+运行后, 工作目录和缓存区回到最近一次commit时候一摸一样的状态.
+git status会告诉你这是一个干净的工作目录, 又是一个新的开始了!
++https://www.jianshu.com/p/0b05ef199749 + ## Git Config
+
用git config命令查看配置文件:
+- 查看仓库级的config,即.git/.config,命令:git config –local -l
+- 查看全局级的config,即~\.gitconfig,命令:git config –global -l
+- 查看系统级的config,即C:\Program Files\Git\etc\gitconfig,命令:git config –system -l
+查看当前生效的配置,命令:git config -l,这个时候会显示最终三个配置文件计算后的配置信息
查看当前生效的配置,命令:git config -l,这个时候会显示最终三个配置文件计算后的配置信息,如下图:
+git有几个配置文件呢? 是的,聪明的你,稍微查查资料就知道咯,git里面一共有3个配置文件
+- 仓库级配置文件:.git/config (该文件位于当前仓库下)
[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = true
+[remote "origin"]
+ url = git@github.com:codingriver/codingriver.github.io.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
+[lfs]
+ repositoryformatversion = 0
+
+~/.gitconfig[user]
+ name = codingriver
+ email = codingriver@163.com
+[filter "lfs"]
+ clean = git-lfs clean -- %f
+ smudge = git-lfs smudge -- %f
+ process = git-lfs filter-process
+ required = true
+
+
+
+C:\Program Files\Git\etc\gitconfig (git安装目录)[diff "astextplain"]
+ textconv = astextplain
+[filter "lfs"]
+ clean = git-lfs clean -- %f
+ smudge = git-lfs smudge -- %f
+ process = git-lfs filter-process
+ required = true
+[http]
+ sslBackend = openssl
+ sslCAInfo = C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
+[core]
+ autocrlf = false
+ fscache = true
+ symlinks = false
+[pull]
+ rebase = false
+[credential]
+ helper = manager-core
+[credential "https://dev.azure.com"]
+ useHttpPath = true
+[init]
+ defaultBranch = master
+
+
+sh
+ git config --global user.name “yourname”
+ git config --global user.email “email@email.com ”
+ 案例:
+ sh
+ git config --global user.name "codingriver"
+ git config --global user.email "codingriver@163.com"
## Git 提交空文件夹
+git设计时是不支持空文件夹提交的,这里是在文件夹里面新建.gitignore文件或者.gitkeep空文件来处理的
+++unity也支持忽略以.开头的文件的
+
新建.gitignore文件 +在空文件夹下新建.gitignore文件,文件内容:
+## Ignore everything in this directory
+*
+## Except this file
+!.gitignore
+
+这样就能提交git仓库了 +我这是在windows上操作的,不能直接创建以.开头的文件,参考这篇文章Windows创建.开头的文件或者.开头的文件夹
+新建.gitkeep文件 +在空文件夹下新建.gitkeep文件,是空文件,这样就能提交git仓库了
+假如,要判断的 remote 的名字为 faraway。 +- 第一种方案
+if git config remote.faraway.url > /dev/null; then
+ …
+fi
+
+if git remote | grep faraway > /dev/null; then
+ …
+fi
+
+判断 remote 是否已存在,如果已存在,直接进行 git fetch faraway 等操作,如果不存在,git add remote faraway http://xxxxx 后,再进行操作。
+++参考:
+
+https://www.jianshu.com/p/e4b9c6c6bab7
+https://www.cnblogs.com/ibingshan/p/11126345.html
+https://blog.csdn.net/longerzone/article/details/12948925
举例:判断当前分支是否为master,本地分支master是否存在,远端分支master是否存在
+if git branch --list master | grep "*"; then
+ ## 分支名字完全匹配,不是模糊匹配!只能匹配master,不能匹配mastertest等
+ echo "当前分支是master"
+ git reset --hard
+ git clean -df > /dev/null
+elif git branch --list | grep -w master > /dev/null; then
+ ## 分支名字完全匹配,不是模糊匹配!只能匹配master,不能匹配mastertest等
+ echo "master不是当前分支,但本地分支master存在"
+ git reset --hard
+ git clean -df > /dev/null
+ git checkout master
+elif git branch -r | grep -w master; then
+ ## 分支名字完全匹配,不是模糊匹配,只能匹配origin/master,不能匹配origin/mastertest等
+ echo "本地分支master不存在,远端master分支存在"
+ git reset --hard
+ git clean -df > /dev/null
+ git checkout -b master origin/master
+else
+ echo "本地分支master不存在,远端master分支也不存在"
+fi
+
+
+if [[ -z $(git status -s) ]]; then
+ ## 没有修改的文件和未纳入版本控制的文件(untracked)的文件
+ echo "没有可提交内容"
+ exit 2
+fi
+
++ ++

这里提示的不同,是文件的权限改变了。
+SO,解决方案奏是:不让git检测文件权限的区别
+git config core.filemode false
+
+++参考: https://blog.csdn.net/u012109105/article/details/51252242
+
git 报错:
+warning: LF will be replaced by CRLF in basic-markdown-syntax/index.html.
+The file will have its original line endings in your working directory
+warning: LF will be replaced by CRLF in hugo-loveit-problems/index.html.
+The file will have its original line endings in your working directory
+
+解决方案:
+ git rm -r --cached .
+ git config core.autocrlf false
+
+ git add .
+
++ ++
对仓库使用 .gitattributes文件进行配置
# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto #让git自行处理左边匹配的文件使用何种换行符格式,这是默认选项。
+# text eol=lf
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.c text
+*.h text
+
+# Declare files that will always have CRLF line endings on checkout.
+*.sln text eol=crlf
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
+
+++参考: https://blog.csdn.net/github_30605157/article/details/56680990
+
项目在用的.gitattributes文件
* text=auto
+
+*.cs diff=csharp text
+*.cginc text
+*.shader text
+*.spriteatlas text eol=lf
+# Unity YAML
+*.anim -text merge=unityyamlmerge diff
+*.asset -text merge=unityyamlmerge diff
+*.controller -text merge=unityyamlmerge diff
+*.mat -text merge=unityyamlmerge diff
+*.meta -text merge=unityyamlmerge diff
+*.physicMaterial -text merge=unityyamlmerge diff
+*.physicsMaterial2D -text merge=unityyamlmerge diff
+*.prefab -text merge=unityyamlmerge diff
+*.unity -text merge=unityyamlmerge diff
+
+# Unity LFS
+*.cubemap filter=lfs diff=lfs merge=lfs -text
+*.unitypackage filter=lfs diff=lfs merge=lfs -text
+
+# 2D formats
+*.[aA][iI] filter=lfs diff=lfs merge=lfs -text
+*.[bB][mM][pP] filter=lfs diff=lfs merge=lfs -text
+*.[dD][dD][sS] filter=lfs diff=lfs merge=lfs -text
+*.[eE][xX][rR] filter=lfs diff=lfs merge=lfs -text
+*.[gG][iI][fF] filter=lfs diff=lfs merge=lfs -text
+*.[hH][dD][rR] filter=lfs diff=lfs merge=lfs -text
+*.[iI][fF][fF] filter=lfs diff=lfs merge=lfs -text
+*.[jJ][pP][eE][gG] filter=lfs diff=lfs merge=lfs -text
+*.[jJ][pP][gG] filter=lfs diff=lfs merge=lfs -text
+*.[pP][iI][cC][tT] filter=lfs diff=lfs merge=lfs -text
+*.[pP][nN][gG] filter=lfs diff=lfs merge=lfs -text
+*.[pP][sS][dD] filter=lfs diff=lfs merge=lfs -text
+*.[tT][gG][aA] filter=lfs diff=lfs merge=lfs -text
+*.[tT][iI][fF] filter=lfs diff=lfs merge=lfs -text
+*.[tT][iI][fF][fF] filter=lfs diff=lfs merge=lfs -text
+
+# 3D formats
+*.3[dD][mM] filter=lfs diff=lfs merge=lfs -text
+*.3[dD][sS] filter=lfs diff=lfs merge=lfs -text
+*.[aA][bB][cC] filter=lfs diff=lfs merge=lfs -text
+*.[bB][lL][eE][nN][dD] filter=lfs diff=lfs merge=lfs -text
+*.[cC]4[dD] filter=lfs diff=lfs merge=lfs -text
+*.[cC][oO][lL][lL][aA][dD][aA] filter=lfs diff=lfs merge=lfs -text
+*.[dD][aA][eE] filter=lfs diff=lfs merge=lfs -text
+*.[dD][xX][fF] filter=lfs diff=lfs merge=lfs -text
+*.[fF][bB][xX] filter=lfs diff=lfs merge=lfs -text
+*.[jJ][aA][sS] filter=lfs diff=lfs merge=lfs -text
+*.[lL][wW][oO] filter=lfs diff=lfs merge=lfs -text
+*.[lL][wW][oO]2 filter=lfs diff=lfs merge=lfs -text
+*.[lL][wW][sS] filter=lfs diff=lfs merge=lfs -text
+*.[lL][xX][oO] filter=lfs diff=lfs merge=lfs -text
+*.[mM][aA] filter=lfs diff=lfs merge=lfs -text
+*.[mM][aA][xX] filter=lfs diff=lfs merge=lfs -text
+*.[mM][bB] filter=lfs diff=lfs merge=lfs -text
+*.[oO][bB][jJ] filter=lfs diff=lfs merge=lfs -text
+*.[pP][lL][yY] filter=lfs diff=lfs merge=lfs -text
+*.[sS][kK][pP] filter=lfs diff=lfs merge=lfs -text
+*.[sS][tT][lL] filter=lfs diff=lfs merge=lfs -text
+*.[zZ][tT][lL] filter=lfs diff=lfs merge=lfs -text
+
+# Audio formats
+*.[aA][iI][fF] filter=lfs diff=lfs merge=lfs -text
+*.[aA][iI][fF][fF] filter=lfs diff=lfs merge=lfs -text
+*.[bB][aA][nN][kK] filter=lfs diff=lfs merge=lfs -text
+*.[iI][tT] filter=lfs diff=lfs merge=lfs -text
+*.[mM][oO][dD] filter=lfs diff=lfs merge=lfs -text
+*.[mM][pP]3 filter=lfs diff=lfs merge=lfs -text
+*.[oO][gG][gG] filter=lfs diff=lfs merge=lfs -text
+*.[sS]3[mM] filter=lfs diff=lfs merge=lfs -text
+*.[wW][aA][vV] filter=lfs diff=lfs merge=lfs -text
+*.[xX][mM] filter=lfs diff=lfs merge=lfs -text
+
+# Video formats
+*.[aA][sS][fF] filter=lfs diff=lfs merge=lfs -text
+*.[aA][vV][iI] filter=lfs diff=lfs merge=lfs -text
+*.[fF][lL][vV] filter=lfs diff=lfs merge=lfs -text
+*.[mM][oO][vV] filter=lfs diff=lfs merge=lfs -text
+*.[mM][pP]4 filter=lfs diff=lfs merge=lfs -text
+*.[mM][pP][eE][gG] filter=lfs diff=lfs merge=lfs -text
+*.[mM][pP][gG] filter=lfs diff=lfs merge=lfs -text
+*.[oO][gG][vV] filter=lfs diff=lfs merge=lfs -text
+*.[wW][mM][vV] filter=lfs diff=lfs merge=lfs -text
+
+# Build
+*.[dD][lL][lL] filter=lfs diff=lfs merge=lfs -text
+*.[mM][dD][bB] filter=lfs diff=lfs merge=lfs -text
+*.[pP][dD][bB] filter=lfs diff=lfs merge=lfs -text
+*.[sS][oO] filter=lfs diff=lfs merge=lfs -text
+
+# Packaging
+*.7[zZ] filter=lfs diff=lfs merge=lfs -text
+*.[bB][zZ]2 filter=lfs diff=lfs merge=lfs -text
+*.[gG][zZ] filter=lfs diff=lfs merge=lfs -text
+*.[rR][aA][rR] filter=lfs diff=lfs merge=lfs -text
+*.[tT][aA][rR] filter=lfs diff=lfs merge=lfs -text
+*.[tT][aA][rR].[gG][zZ] filter=lfs diff=lfs merge=lfs -text
+*.[zZ][iI][pP] filter=lfs diff=lfs merge=lfs -text
+
+# Fonts
+*.[oO][tT][fF] filter=lfs diff=lfs merge=lfs -text
+*.[tT][tT][fF] filter=lfs diff=lfs merge=lfs -text
+
+# Documents
+*.[pP][dD][fF] filter=lfs diff=lfs merge=lfs -text
+
+# Collapse Unity-generated files on GitHub
+*.asset linguist-generated
+*.mat linguist-generated
+*.meta linguist-generated
+*.prefab linguist-generated
+*.unity linguist-generated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [ ] C#关于String暂存池(常量池)
+ > C#关于String暂存池(常量池)
+ > C#中字符串的内存分配与暂存池【非常详细】
[ ] CircularBuffer
LockFreeQueue<T>
+ >use LockFreeQueue<MemoryStream>,LockFreeQueue<Const<TCPCommon.NETWORK_STATE>>[ ] GCHandle
+[ ] Sproto
+ > skynet中sproto的简单使用
+ > sproto rpc 的用法
++说明:https://blog.csdn.net/wangjiangrong/article/details/107686954
+
+官方文档:http://puremvc.org/docs/PureMVC_IIBP_Chinese.pdf
+github c#: https://github.com/PureMVC/puremvc-csharp-standard-framework
+github: https://github.com/PureMVC
+官网:http://puremvc.org (提供pdf中文文档)++++如果在 Mediator 里有很多对 View Component 的操作(响应 Event 或Notification),那么应该考虑将这些操作封装为 View Component 的一个方法,提高可重用性。 +如果一个 Mediator 有太多的对 Proxy 及其数据的操作,那么,应该把这些代码重构在 Command 内,简化 Mediator,把业务逻辑(Business Logic)移放到Command 上,这样 Command 可以被 View 的其他部分重用,还会实现 View 和Model 之间的松耦合提高扩展性
+
++游戏内通信协议使用
+Sproto序列化
++优化前:
+
+当前截图为win10 unity editor测试
+编号为1的测试数据,每帧解析30001协议50次的开销,测试数据为Map_ObjectInfo.request,数据包含有60个MapObjectInfo对象
+![]()
协议开销分析
+1. SocketAsyncEventArgs mReceiveEventArgs 异步socket 收到网络数据
+1. 网络数据 写入mReceiveBuffer中
+ >
+1. 然后Copy到mReadQueueBuffer(网络数据拆包)进行传递
+ >
+1. 然后使用packetData进行派发,packetData是新的buffer
+ > 
+ > 
+ >
+1. 通过Socket异步回调线程将packetData 存储到private LockFreeQueue<MemoryStream> mReceivePacketQueue队列中,然后通过unity主线程进行派发,在主线程中进行Decode
+ >
+1. Decode时,MemoryStream.ToArray()进行拷贝,生成新的buffer
+ >
+ > 
+ > 
+ >
+ >
+1. Decode首先进行解压缩,解压缩时先把原始bufferCopy到newBuffer,newBuffer是新的buffer
+ >
+1. 然后进行解压缩decomBuffer,解压缩decomBuffer是新的buffer
+ >
+ >
+1. 解压后进行解密outBuffer,解密是新的buffer
+ >
+1. 解密后进行解包unpack_data,解包是新的buffer
+ >
+1. 解包后解析对象Package
+1. 解析对象后进行解析对象GateMessage,GateMessage对象有新的byte[]字段,该字段有新的buffer
+ > 收到的每个协议先反序列化成GateMessage对象,该对象的字段private List<MessageContent> _content; 带有private byte[] _networkMessage;字段,这个字段是真实协议对象的原始数据,gc严重
+ >
+ >
+1. 将解析对象封装到RpcInfo对象中
+1. 使用GateMessage进行解包unpack_data,解包是新的buffer
+1. 开始解析具体业务对象,同时会封装到RpcInfo中
+1. RpcInfo进行解封,使用具体业务对象进行业务派发
Sproto反序列化流程,使其支持缓存重复利用,将MessageContent的byte[]调整为BufferStream,支持重复利用buffer,减少GCpacketData的MemoryStream 替换成RecyclableMemoryStream,进行缓存利用,且支持跨线程处理调整业务,替换掉MemoryStream.ToArray(),解析过程使用RecyclableMemoryStream
解压缩前使用的decomBuffer,替换为常驻的大buffer,只创建一次,就不销毁了,重复利用
decomBuffer,替换为常驻的大buffer,只创建一次,就不销毁了,重复利用outBuffer,替换为常驻的大buffer,只创建一次,就不销毁了,重复利用unpack_data,这里使用两个大buffer进行处理,只创建一次,就不销毁了,重复利用,
+ > 解析协议GateMessage前执行unpack,解析协议GateMessage后,解析具体业务协议前也会执行unpack,这里剥离具体业务的影响,进行分步处理,使用两个大buffer分别对应两次解析协议RpcInfo、GateMessage.request、GateMessage.response、MessageContent等等对象需要进缓存池,这些对象是为反序列化服务的MessageContent是工具生成的,为了防止冲突,改为MessageContentEx
++ + + + + + + + + + + + + + + + + + + + + + + + + +优化中期参照:
+
+编号为1的测试数据,每帧解析30001协议50次的开销,测试数据为Map_ObjectInfo.request,数据包含有60个MapObjectInfo对象
+
+上面结果是将所有协议都做缓存,包括MapObjectInfo内部字段引用的协议 +
+上面是将MapObjectInfoEntity的Dictionary使用MapObjectInfo,不创建新的,具体业务会有bug +et.heros = new System.Collections.Generic.Dictionary<System.Int64,SprotoType.BattleHeroInfo>();+优化最终结果: +编号为1的测试数据,每帧解析30001协议50次的开销,测试数据为
+Map_ObjectInfo.request,数据包含有60个MapObjectInfo对象
+![]()
+MapObjectInfo的内部字段引用的协议类型没有做缓存(多个协议会引用相同的协议,复杂度太高);
+MapObjectInfoEntity的Dictionary还是创建新的,否则业务有bug;测试数据优化前后
+GC对比 +编号为1的测试数据,每帧解析30001协议50次的开销,测试数据为Map_ObjectInfo.request,数据包含有60个MapObjectInfo对象
+优化前 15.9MB +优化后 4.8MB+
![]()
+游戏中优化前后对比:
+优化前每一次调用GC是255kb+优化后第一次调用GC是114.1kb+优化后第二次调用GC是86.6kb对进入地图场景初始化的
+Map_ObjectInfo.request协议数据50倍测试 +优化前 15.9MB +优化后 4.8MB +PoolMgr的缓存池测试
++
++世界地图优化前的数据
+
+![]()
优化后的数据:
+
+
++参考 : +Unity批处理手游——动态VS静态批处理
+
+Unity3d Tips +Batch Breaking Cause
+Unity-25种合批失败的原因
+
unity版本不同,动态合批限制也不同,需根据对应版本的官方文档去处理。
+(注意:如果您正在开发 Unity PC 游戏,请记住动态批处理会产生 CPU 开销,因此除非您受到繁重的 GPU 渲染时间的瓶颈,否则您可能希望在项目中禁用动态批处理!)
+注意:动态批处理并不总是适用于场景中的所有对象!即使您设法将所有对象减少到 300 个顶点以下,大量动态批处理计算的成本最终也会成为非常昂贵的 CPU 操作。(虽然 GPU 会很高兴!)
+与静态批处理不同,动态批处理的对象可以四处移动,并且具有刚体物理特性。他们也有自己的局限性:
+Sprite Renderer和Particle System不受缩放影响)常见合批失败原因:
+
Unity中Player Settings > Other Settings > Dynamic Batching

注意: Sprite Renderer和Particle System 默认启用Dynamic Batching,不会受到 Player Setting的动态合批开关的影响。
简化模式
+ >- 部队简化显示
+ >- 部队上的默认特效进行移除
+ >- 性能瓶颈时,考虑部分动画进行屏蔽出现动画,消失动画,休闲动画)
+ >- 不同的图集使用不同的sortingorder进行渲染顺序控制,保证动态合批成功率(放弃前后遮挡关系,效率优先)
+ >- 部队阵型及移动插值计算进行简化,特别是移动方向变化后阵型调整的计算(转向)
怪物合批
+ >- 不同的图集使用不同的sortingorder进行渲染顺序控制,保证动态合批成功率(放弃前后遮挡关系,效率优先)
+ >- 性能瓶颈时,考虑部分动画进行屏蔽出现动画,消失动画,休闲动画)
+ >- 怪物上的默认特效进行移除
+ >- 部队阵型及移动插值计算进行简化,特别是移动方向变化后阵型调整的计算(转向)
优化结果:
+地图渲染顺序说明(New) MapSortingOrder
+默认层-Default(地面,山,河流,水,树草等)
+ Land = -100, //地表
+ MountainGrove = -90, // 山 树
+ RiverLake = -80, //河水 湖泊
+ HolyLandRoad = -70, //圣地表 公路
+ Circle = -1, // 圆形的环 当移动到obj,长按空地/主城时出现
+ 行军线
+ 联盟线
+ 资源富集区
+
+怪物层-Monster
+角色层-Army 默认是和建筑一个层级,在性能瓶颈时,单独一个配置,方便部队合批
+建筑层-Building (城堡,资源点,联盟建筑,圣地,关卡,帮会据点等)
+特效层-Effect(战斗特效,静态特效)
+BillText
+3DUI特效层-UIEffect
+天空层-Sky(迷雾等)
地图 建筑单位可以合并图集处理
+
分析场景 map-analyse.unity
分析前的drawcall是 478
+
只显示怪的数据drawcall是 274
+
怪是3转3的图片,带阴影,有8个方向,每个动作都有8个方向,所有动作:攻击,待机,行走,跑步,攻击,休闲
+调整图集方案
+现在图集的方案是每只怪的所有动作作为一个图集
+调整图集:
+所有怪的相同动作作为一个图集,因为图集太大,这里将所有士兵怪按照相同动作生成为一个图集的方案去处理
使用精灵切块工具SpriteDicing +尝试将序列帧中每帧相同部分进行公用。
+一个动作需要为8方向生成序列帧,这里考虑下是否能接受 左右镜像来减少序列帧素材, + > 提供左上,左,左下的序列帧,通过镜像方式作为右上,右,右下的序列帧使用,这样就能减少3方向的素材,减小包体 + >需要考虑阴影单独拆分
+动态合并图集
+shader实现一个材质球代表一个怪物的展示(shader实现几*几的方格子,同时渲染多个图集的数据)
+ >
+ >弊端:怪物和别的怪或者部队有交叉重叠时,不能交叉渲染,前后遮挡关系会有细节问题
怪物里面的每个士兵和英雄 根据图集设置固定的order,分离渲染顺序 + >会丢失阵型前后遮挡关系,也会丢失重叠怪的遮挡关系
+++测试调整后的怪,drawcall只有
+7+
角色和角色阴影分离 + > 减小角色图集 ,阴影可以使用更低的分辨率,使角色图集能容纳更多角色的图 + > 降低overdraw,角色和阴影在一个图中,形成大面积的透明区域,overdraw严重 + > 可以根据角色生成对应的mesh,降低overdraw,带阴影的情况下mesh不能复用 + > 阴影可以再中低平台设备上关闭,或者中端使用一个阴影图片来代替,降低阴影的性能开销,更可控
+
使用第6条方案,每个士兵和英雄 根据图集设置固定的order,分离渲染顺序
+++数据比对 测试场景
+mapbatches.unity+优化前的怪和部队drawcall426
+![]()
优化后的怪和部队drawcall
+36
+


++优化后的数据 (lod 对象这里没有进行图集整合,需要时可以支持) +优化前drawcall是 54,优化后是17,效果非常可观,但需要注意动态合批的CPU及内存开销 +
+

优化后特效后的结果:
+
结论
+1. 给特效物体不同的材质球指定不同的sortingorder
++如果不同材质球的物体使用相同的
+sortingorder,unity内部 +
+drawcall 序号1和3是相同图集相同材质球,序号2和4也是相同图集相同材质球,1,2,3,4使用的相同shader,且,1,2,3,4动态合批的物体互相没有遮挡,
结论
+动态合批时,渲染顺序穿插会造成消耗更多的drawcall,最好指定固定的sortingorder,手动控制顺序,这样能提高动态合批的成功率.
++调整后的drawcall从4个变成2个
+
所有圣地的图片需要调整到一个图集里面
+++调整后的drawcall从4个变成1个
+
占领后需要调整颜色,则使用MaterialPropertyBlock进行处理,不会打断合批(前提是支持GPU Instancing)
这里使用顶点颜色进行颜色调整
+SpriteRenderer针对不同sprite生成不同的mesh,mesh不同,所以不支持GPU Instancing合批,
相机从展示层到lod1层时,策划的需求是可点击业务单位及可以自由行军到业务单位,所以展示层的节点保留了,会把展示层的sprite节点localScale设置为0,但scale为0还是会占用drawcall。
处理方案: 将scale为0时的sprite位置设置到相机外的位置
+
处理后的drawcall

++联盟标记,城堡护盾,冒烟特效,着火特效, 部队移动烟尘,lod高层圣地常驻特效,普攻子弹特效,
+
effect_map_huzhao)本身占用10个drawcall,有两个模型组成,每个模型5个材质球,且模型不能动态合批合批(因为不同材质球的渲染顺序相同)
+
+
+7个护盾特效,占用60个drawcall,视野内随着护盾数量的增长,drawcall也急速增长
结论
+- 建议优化护盾特效,降低材质球数量
+ > 这里半球护盾特效是有两个mesh组成(1/2的半球mesh),这两个mesh是相同的,(思考下是否可以使用1/4的半球mesh?)
+ > eff_banyuan_007_1和eff_banyuan_007_2 mesh是一样的,资源冗余问题
+ >
+- 将材质球的RenderQueue进行调整,不同的材质球使用不同的值,可以进行动态合批
+ >测试后的drawcall 11
+ >
+ >将 Sorting Group组件移除后drawcall 是6 (Sorting Group这里打断动态合批了)
+ >
爆炸特效
+effect_map_tongyong_touzhiwu_baozha 炮弹爆炸特效 有7个材质球,每个爆炸特效 占用7个drawcall。
+24个爆炸特效,同时播放会有 71 drawcall,特效有动态合批,但drawcall还是随着特效数量增长而增长
处理方案:将爆炸特效的sortingorder分别设置不同的值,24个爆炸特效,同时播放会有9个drawcall(一个clear的drawcall,一个天空盒的drawcall),特效完美动态合批,且drawcall不会随着特效数量增加而增加
+++着火和冒烟特效 也是这样的处理方案
+
炮弹特效
+
+
+effect_map_tongyong_touzhiwu 炮弹的顶点数量过多不能动态合批,
+处理方案:调整模型的顶点数,不高于300个顶点
UI_Pop_TroopNameState 部队名字及状态图标
23支部队,只显示UI_Pop_TroopNameState 占用drawcall就达到46
+UI_Pop_TroopNameState节点上添加有Canvas组件,所以打断合批了

处理方案:
+ - 将Canvas组件移除,给状态图标添加SortingGroup组件,将名字和图标层级分离,这样保证Text组件和Image组件分别独立合批,不会被打断
+ - 图标显示分底图和图标,将底图和图标放到同一个图集中。
++优化后: +
+
UI_Pop_TroopSelectHUD 部队选中后的信息显示5支部队,占用20个drawcall
+UI_Pop_TroopSelectHUD节点上添加有Canvas组件,所以打断合批了
+
++关于合批 UGUI的一个缺陷
+
++头像框合批失败,所以是3个drawcall
++白色图片不能合批,一共占用3个drawcall +
+根据上图占用3个drawcall,推断出,Hierachy内的上下关系不能保证临近位置合批,受到前后遮挡关系影响。 +这里有个问题是 第1个drawcall先绘制的左侧的白图,根据Hierachy的顺序,应该先绘制蓝色图片才对? +UGUI是根据Depth进行相邻的Depth合批,不相邻的不能合批,这里可以调整节点关系
UI_Pop_TroopFightInfoHUD 战斗头像10支部队,占用50个drawcall,200支部队占用drawcall为1000 ,200支部队副将头像也显示 drawcall为 1400
+
+200支部队
+
+问题:
+- 每个头像上添加有Canvas组件
++去除Canvas组件后 drawcall 是19,200支部队占用drawcall为375
+
+- 每个头像上的指向线添加有Canvas组件
+这个Canvas组件是保证指向线的层级在头像后面,不能去除。
+这里去除Canvas后使用固定的材质球调整Render Queue来替代处理层级关系,替换材质球后drawcall为16,200支部队占用drawcall为412
+
+- 如果不显示指向线,且把Canvas组件去除,头像的drawcall为9, 200支部队占用drawcall为175
+
+- 指向线通过MultiImage组件合并成一个mesh进行处理
+这时候指向线和头像同时显示的drawcall为10, 200支部队占用drawcall为176
+
处理方案
+- 指向线通过MultiImage组件合并成一个mesh进行处理
战斗头像超过一定数量限制后,处理办法:
+图集调整,将英雄战斗头像这一个尺寸类型单独打包一个图集为duilietouxiang图集

++英雄头像的图集放有多种尺寸类型的,这个图集不合理
+
+![]()
优化前 200支部队,不显示指向线时 drawcall为1200
+ 优化后 200支部队,不显示指向线时 drawcall为 21 (去掉canvs,所有头像相关图片调整到一个图集中)
+
+每个hud上添加有Canvas组件
+移除后的效果:
+
route print
+
+或者
+
+netstat -r
+
+# tracert显示路由耗时
+tracert www.example.com
+或者
+tracert 192.168.1.1
+
+在 Unix/Linux 系统中,你可以使用以下命令来查看路由表:
+netstat -rn
+或者
+
+ip route
+
+这些命令会列出当前系统的路由表,包括目的网络、子网掩码、网关、接口等信息,这对于网络故障排查和理解数据包如何在网络中传输非常有用。
++ + ++
ZeroTier通过自己的多个根服务器帮助我们建立虚拟的局域网,让虚拟局域网内的各台设备可以打洞直连。这些根服务器的功能有些类似于通过域名查询找到服务器地址的DNS服务器,它们被称为Planet。然而这里存在一个非常严重的问题,就是Zerotier的官方行星服务器都部署在国外,从国内访问的时候延迟很大,甚至在网络高峰期的时候都没法访问,这也会导致我们的虚拟局域网变得极不稳定,经常掉链子。
+为了应对网络链接的延迟和不稳定,提高虚拟局域网的速度和可靠性,Zerotier允许我们建立自己的moon卫星中转服务器。
+作为Moon服务器不需要具备太强大的CPU性能/内存空间和存储空间,虚拟机、VPS、或者云服务器甚至一个树莓派都行,当然,这台服务器需要长时间可靠在线并且具有静态IP地址(ZeroTier官网上说公网IP或者内网IP都可以,只是如果用的是内网IP的话,在外网的设备就只能依靠Planet而不能使用moon了)。
+以Linux系统为例配置Moon服务器
+由于ZeroTier本身使用UDP协议,因此如果存在防火墙的话,需要开放UDP,否则无法连接。 +本篇内容仅介绍ZeroTier-Moon服务器的配置。
+下载并安装ZeroTier:
+curl -s https://install.zerotier.com/ | sudo bash
+
+安装完成后得到一个ID:
+*** Success! You are ZeroTier address [ 1c110b9ac2 ]
+
+加入网络(只作为Moon服务的话不用加入网络):
+sudo zerotier-cli join 3efa5cb78a961967
+200 join OK
+
+进入ZeroTier的默认安装目录,生成Moon配置文件:
+cd /var/lib/zerotier-one
+sudo zerotier-idtool initmoon identity.public > moon.json
+vim moon.json
+
+其内容包括id、objtype、roots、signingKey等等。
+生成的文件样式如下:
+ "id": "1c110b9ac2",
+ "objtype": "world",
+ "roots": [
+ {
+ "identity": "9c960b9ac2:0:daca38dfc5f3",
+ "stableEndpoints": []
+ }
+ ],
+ "signingKey": "676f0c29eb8d6f2f00ce22ee2082b3ec",
+ "signingKey_SECRET": "39de9f7ab16d0adb035276b7281f73344",
+ "updatesMustBeSignedBy": "676f0c29eb8d6f2f00ce22ee",
+ "worldType": "moon"
+}
+
+这里我们需要根据自己服务器的公网静态IP,修改stableEndpoints那一行格式如下,其中11.22.33.44为你的公网IP,9993是默认的端口号:
+"stableEndpoints": [ "11.22.33.44/9993" ]
+
+根据moon.json文件生成真正需要的签名文件.moon:
+sudo zerotier-idtool genmoon moon.json
+
+输出:
+wrote 0000009c960b9ac2.moon (signed world with timestamp 1280398410930)
+
+执行该命令以后会在软件目录下生成一个类似000000xxxxxxxxx.moon的文件,妥善保存该文件,因为要使用moon服务器,必须在所有客户端上面都发送一个这个文件。
+移动.moon签名文件到moons.d目录下并且重启服务
+cd /var/lib/zerotier-one
+mkdir moons.d
+mv 000000*.moon moons.d
+service zerotier-one restart
+
+其他机器如果要使用Moon服务器,必须要在本地加入之前生成的.moon签名文件并重启服务才能生效。有两种方法。
+一、在本机的Zerotier安装目录创建moons.d文件夹,然后下载该签名文件放在创建的moons.d目录里,重启服务。
+#For linux
+# 一般是 /var/lib/zerotier-one 目录下
+cd /var/lib/zerotier-one
+mkdir moons.d
+# 将 .moon 文件移动到 moons.d 目录下
+
+
+二、直接使用命令zerotier-cli orbit
+Windows和Linux通用
+# zerotier-cli orbit
+sudo zerotier-cli orbit 9c960b9ac2 9c960b9ac2
+200 orbit OK
+# zerotier-one restart
+sudo service zerotier-one restart
+zerotier-one stop/waiting
+zerotier-one start/running, process 18347
+
+客户端添加.moon文件后需要重启ZeroTier One!Windows要在服务中重启ZeroTier One
+#for linux and windows(windows需要用管理员模式启动cmd输入)
+zerotier-cli listpeers
+
+如果输出中出现一条最后为MOON的记录,说明已经成功连接Moon服务器
+zerotier-cli listpeers
+200 listpeers <ztaddr> <path> <latency> <version> <role>
+200 listpeers id myip/9993;6012;1706 -1 1.8.4 MOON
+200 listpeers 62f865ae71 50.7.252.138/9993;6012;1070 -294 - PLANET
+
+1、需求背景 +docker镜像默认存放在根目录下,而有时候根目录往往比较小或者有时候需要重装系统,将docker镜像放在根目录下有被删除或者根目录被撑爆的风险,因此需要将docker镜像默认存储位置更改为其他数据盘的位置。
+2、解决办法
+2.1、修改/etc/docker/daemon.json文件 +在终端执行以下命令:
+vim /etc/docker/daemon.json
+
+然后添加以下内容:
+{
+ "data-root": "/data/docker" #将docker的默认存储位置在该目录下
+}
+
+2.2、重启docker
+systemctl restart docker
+
+2.3、检测是否生效
+docker info | grep "Docker Root Dir"
+
+如果输出为 Docker Root Dir: /data/docker,则说明更改生效
+如果感觉对你有用,麻烦点击一下小红心,谢谢!
+++https://blog.csdn.net/Monster_WangXiaotu/article/details/122590389
+ +
CentOS9以上 +1. yum安装docker服务
+yum install -y docker
+
+systemctl start docker
+systemctl enable docker
+systemctl status docker
+
+curl -L https://github.com/docker/compose/releases/download/v2.14.1/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
+或者: curl -L http://mirror.azure.cn/docker-toolbox/linux/compose/v2.14.1/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
+
+chmod +x /usr/local/bin/docker-compose
+
+docker run ...
+
+问题描述:在Centos8系统中,使用docker run时,出现如下报错: +Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg. +Error: open /proc/self/uid_map: no such file or directory
+解决办法: +1,卸载podman软件(可以使用rpm -qa|grep docker) +yum remove docker +2,下载docker-ce源 +curl https://download.docker.com/linux/centos/docker-ce.repo -o /etc/yum.repos.d/docker-ce.repo +3,安装docker-ce +yum install docker-ce -y
+问题原因分析: +Centos 8使用yum install docker -y时,默认安装的是podman-docker软件
+大家都知道Centos8于2021年年底停止了服务,大家再在使用yum源安装时候,出现下面错误“错误:Failed to download metadata for repo ‘AppStream’: Cannot prepare internal mirrorlist: No URLs in mirrorlist”
+1、进入yum的repos目录
+cd /etc/yum.repos.d/
+
+2、修改所有的CentOS文件内容
+sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
+
+sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
+
+3、更新yum源为阿里镜像
+wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
+
+yum clean all
+
+yum makecache
+
+4、yum安装测试是否可以yum安装
+yum install wget –y
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ++官网: https://github.com/kingwrcy/moments
+部署后登录不上去
+
++https://www.photoprism.app
+
+https://github.com/photoprism/photoprism
+需要付费 pro版本
++贵
+
++移动端体验太差
+
+ ++
+ ++
负责把Alist的webdav映射到NAS的文件系统内,来把照片喂给PhotoPrism
++ ++
永久收费99元
++ ++
qBittorrent + Jackett + Radarr & Sonarr + ChineseSubFinder + Jellyfin + Jellyseerr
+qBittorrent + Jackett + NASTool + ChineseSubFinder + Jellyfin
+qBittorrent + Movie Robot + ChineseSubFinder + Jellyfin
+ + + + + + + + + + + + + + + + + + + + + + + + + +PBR即基于物理的渲染过程。
+PBR 并不是“一项”技术,它是由一系列技术的集合。
+满足以下条件的光照模型才能称之为PBR光照模型: +1. 基于微平面模型(Be based on the microfacet surface model) +1. 能量守恒(Be energy conserving) +1. 使用基于物理的BRDF(Use a physically based BRDF)
+++参考:https://learnopengl-cn.github.io/07%20PBR/01%20Theory/
+
+PBR,或者用更通俗一些的称呼是指基于物理的渲染(Physically Based Rendering),它指的是一些在不同程度上都基于与现实世界的物理原理更相符的基本理论所构成的渲染技术的集合。正因为基于物理的渲染目的便是为了使用一种更符合物理学规律的方式来模拟光线,因此这种渲染方式与我们原来的Phong或者Blinn-Phong光照算法相比总体上看起来要更真实一些。除了看起来更好些以外,由于它与物理性质非常接近,因此我们(尤其是美术师们)可以直接以物理参数为依据来编写表面材质,而不必依靠粗劣的修改与调整来让光照效果看上去正常。使用基于物理参数的方法来编写材质还有一个更大的好处,就是不论光照条件如何,这些材质看上去都会是正确的,而在非PBR的渲染管线当中有些东西就不会那么真实了。虽然如此,基于物理的渲染仍然只是对基于物理原理的现实世界的一种近似,这也就是为什么它被称为基于物理的着色(Physically based Shading) 而非物理着色(Physical Shading)的原因。
+
这节阐述的是PBR呈现的效果特征,而非底层物理原理的特征。 +相比传统的Lambert着色和Phong着色,PBR着色在效果上有着质的提升,可以表示更多更复杂的材质特征: +- 表面细节 +- 物体粗糙度 +- 区别明显的金属和绝缘体 +- 物体的浑浊程度 +- 菲涅尔现象:不同角度有不同强度的反射光 +- 半透明物体 +- 多层混合材质 +- 清漆效果 +- 其它更复杂的表面特征
+
Phong模型着色效果,只能简单地表现理想模型的漫反射和高光,渲染出的效果跟真实世界相差甚远。
+
PBR材质效果球,它们真实地渲染出各类材质的粗糙、纹理、高光、清漆、边缘光等等表面细节特征。PBR对渲染效果真实感的提升可见一斑。
+PBR(Physically Based Rendering)是一种渲染方式(基于物理渲染),它使用的材质是PBS(Physically Based Shader),中文名:基于物理的渲染技术(基于物理着色)。可以对光和材质之间的行为进行更加真实的建模。PBS只考虑材质在真实物理环境下应该有的效果。PBR包围的范围会更广一些,比如GI/AO/SUN等复杂情况,这些东西加上PBS,才是PBR。
+Unity中的PBS即是把PBR算法封装起来,只用修改PBS的参数就能达到PBR的效果。
+在Unity中,PBS分为两类,一个叫做Standard,一个叫做Standard(Specular Setup),我们把它称为标准着色器的高光版,它们共同组成了一个完整的PBS光照明模型,而且非常易于使用。
+光是一种横向传播的电磁波,电磁波的波长范围非常广,但只有390~760nm之间的一段波谱是人眼可见到的,也就是在图形学里对渲染着色起作用的部分。另外因为光有波粒二象性的缘故,有时候我们在图形学里也会把光做为光子(photon)来处理。
+
当光投射到传感器(眼睛,照相机等)上时,颜色和亮度就会被吸收并感知,而光与物质交互后被感知的,就是物体的颜色。
+物质对光的影响,可以用折射率(refractive index)来描述,当由复数来表示折射率时,它的实部影响速度(相对于真空中的速度),虚部来影响光的衰减(被吸收),折射率会改变光的波长。
我们先根据物质按照内部折射率是否均匀来进行分类: +- 均匀介质(Homogeneous Media)---直线传播 + 内部密度相同的物质,意味着他有唯一的折射率,对应透明的均匀物质来讲(如水,玻璃),光通过时,并不会改变光的颜色或强度,而当物质对某一种可见光谱有吸收率的时候,那么,光就会随着在物质内的传播距离而逐渐被吸收,而光的方向并不发生改变,这就是光被吸收(absorption)。
+光穿透玻璃和水时,方向、颜色和强度都不会发生:
+
+
+
+如果物质对某一种可见光谱有吸收率时,光方向不会发生变化,而光的强度会随着距离丧失强度(改变颜色),也就是光被吸收了:
+
+
+
+非均匀介质(heterogeneous medium)---散射 + 当非均匀物质内部的折射率变化斜率很大时(突变),就会发生散射(Scattering)现象,光会被分割为多个方向,但光的总量并不会发生变化:
+
除了以上两种交互外,物质还可能因为其他能量发出新的光,称为自发光“emission”。
+光与物质交互的三种方式:吸收、散射、自发光
+示意图如下:
+
++散射(Scattering): 由观察尺度划分 +- 漫反射(Diffuse): 观察像素大于散射距离 +- 次表面散射(Subsurface Scattering): 观察像素小于散射距离 +- 透射(Transmission): 入射光经过折射穿过物体后的出射现象, 为次表面散射的特例
+
另外,折射率缓慢的逐渐变化不会导致光线的分离,而是导致其传播路径的弯曲。 当空气密度因温度而变化时,通常可以看到这种效果,例如海市蜃楼(mirages)和热形变(heat distortion)。见下图。
+
上面我们讲了光在密度不同的物质内传递的现象,而渲染中最为典型的,是发生在物体表面时,关于光与空气和物质之间的散射效果。 +这个时候平面散射光会分为两部分:进入平面的部分(折射,在物体内部传播中被吸收或散射),从平面出去的部分(反射);一个假设完美无限光学平坦的平面(简称光学平面)反射效果,平面两侧的空气和物体有各自的折射率:
+
但实际上,平面大多都不是光学平面(除了镜子或镜头等),而是一种微几何体(Microgeometry),表面都会有一些比可见光波长要大的不规则凹凸,但又小到无法覆盖一个像素或者采样点,所以,我们就把这种非光学平面,理解为一组微小光学平面的集合,而可见光的反射,实际上是在平面各个点上轻微不同方向的反射集合,也就是后面会提到的微平面理论(Microsurface Theory)。
+
++Fresnel表现的是材质的反射率和
+入射角(也就是光源入射向量和平面法线向量的夹角)的对应关系 +- 表示观察看到的反射光线的向量与视角相关的现象 +- 光线以不同角度入射会有不同反射率, 掠射角度(90)下反射率最大 +-F0: 平行于表面法向量入射的菲涅尔反射值, 任意角度的菲涅尔反射可由 F0 和入射角度计算得出 +- 光线对于不同物质有不同的反射率, 非金属 F0: 0.02~0.04, 金属 F0: 0.7~1.0 +- 光线入射角越大,镜面反射的比例越大如果要达到真实的fresnel,那么美术对fresnel的控制应该越少越好,需要提供一些参数值来使用,通常是光泽度(Gloss,或粗糙度 Roughness)和反射率(Reflectivity)。提供一个基础反射率,来设置平面最小的反射值,让fresnel曲线从这个最小反射值开始,以满足不同角度的需求。
+
菲涅尔方程定义的是在不同观察方向上,表面上被反射的光除以被折射的光的比例。我们通过该方程可以计算得到被反射的光的能量,根据能量守恒我们就可以得知被折射进物体的光的能量了。那些折射进物体的光也没闲着,他们在物体内进行着称之为次表面散射(Subsurface Scattering)的行为,最终会看似随机地射出表面。如下图所示:
+
其中黄色出射光为直接反射光,红色出射光为次表面反射光。可以看到,在一个平滑的平面上(即不考虑微平面理论),在宏观上来说,反射光即为材质的高光 specular 部分,而次表面反射因为是完全随机出射,所以构成了漫反射 diffuse 部分。
+这里还是得提一句 Subsurface Scattering 的注意事项。先从引擎玩起再来补图形学知识的人可能会疑惑,Subsurface Scattering 不是做皮肤那种,模拟光从一个像素点入射,从另一个像素点出射的物理现象吗?但这里整篇文章模拟的都是一个像素所覆盖的平面所产生的物理属性(还是之前提到的 pixel footprint 的概念),而这种跨越多个像素的、宏观的次表面反射会在后面使用一个单独的物理模型去模拟,即 BSSRDF。
+++当像素小于出射到入射距离时,每个点的着色就会收到其他光入射到其他点的影响,也就是常说的“次级表面散射”技术,很重要的一点是,它和普通的漫反射着色是一种物理现象(都是折射光的次级表面散射),唯一不同的就是散射的距离与观察点大小的关系,一个通常被认为是“次级表面散射”的表现,当在较远的距离观察时,就可以被认为是漫反射着色(例如远距离角色的皮肤),而“正规的漫反射着色”在很近距离观察时,也会有次级表面散射的效果。
+
++上面描述的反射和散射都依赖于表面的朝向。放大来看,这被用来渲染网格的形状,也可以使用法线贴图来描述更小的细节。这样任何渲染系统都可以处理更多的细节,把反射和散射表现得更好。
+然而,任然有一点没有考虑到。大多数真实世界的表面都存在非常小的缺口:凹槽、裂缝、凸块,这些都因为太小了以至于眼睛无法看到,并且使用正常解析度的法线贴图都无法表现出来。尽管无法被肉眼看到,但这些微粒还是影响着反射和折射。
++
为表面细节最容易在反射中被观察到(散射并不会被太多的影响到,这里不会讨论到)。上图中,入射光的平行线当从粗糙的表面反射是发生了交叉,因为每一条射线遇到的表面方向都不一样。就像将球抛向墙面一样,如果墙面非常不平整,球任然会反弹,但是反弹方向是不可预测的。简而言之,表面越粗糙,越多的反射光线会产生交叉,看上去越“模糊”。
+不幸的是,为了着色而评估每一个微表面特征对美术、内存、计算量来说都是非常昂贵的操作。我们该怎么做呢?事实证明,如果我们放弃直接描述微表面,取而代之使用整体的粗糙度,就可以写出相当精确的着色器来产生类似的效果。这个值通常指的是光泽度(Gloss),平滑度(Smoothness)或者粗糙度(Roughness)。可以从纹理或者一个常量值中获取。
+这种微表面细节对于任何材质来说都是非常重要的,因为真实世界到处都是各种各样的微表面特征。光泽贴图并不是一个新的盖面,但是它在基于物理的渲染中起到了关键的作用,因为微表面细节对光反射有着很大的影响。就如我们一会儿会看到的,有几个关于微表面属性基于物理渲染改善的注意事项。
+
微表面(microfacet)粗糙度贴图或高光贴图来表示半角向量(Halfway Vector): 位于光线向量 l 和视线向量 v 之间的中间向量
+ > h=normalize(lightDir+viewDir);微平面的取向方向与半程向量的方向越是一致,镜面反射的效果就越是强烈越是锐利。然后再加上一个介于0到1之间的粗糙度参数(Roughness),这样我们就能概略的估算微平面的取向情况了:
+
我们可以看到,较高的粗糙度值显示出来的镜面反射的轮廓要更大一些。与之相反地,较小的粗糙值显示出的镜面反射轮廓则更小更锐利。
+前文已经提到了微几何体,之所以有微平面的概念,因为从宏观来看,在我们渲染模型网格时,使用法线贴图就可以描述表面小的细节,但这样仍然会有一定的缺失,很多真实世界的平面上,还是有一些微小的凹陷,裂缝或突起,而用肉眼是很难看清楚的,小到连正常大小的法线贴图也无法来表现,虽然肉眼无法看到,但这些微观特征,还是对Diffuse和Specular产生了影响。
+微平面的细节,对反射的影响更多,也就是Specular,因为粗糙的微平面会把反射光分散或者内部遮挡,所以有了下面两个项目来描述这个现象: +- Normal Distribution Function(法线分布函数) + 因为微几何体的所有平面的方向,并不是均匀分布的,如果是分布比较均匀的光滑平面,那么光就会在几乎相同的方向反射,产生清晰的高光,如果粗糙表面则是模糊的高光.
+有多少微平面点的法线更倾向宏观平面的法线方向,我们把这种平面法线方向分布的统计,称之为microgeometry normal distribution function **D()**,和fresenl方程不同的是,**D()** 并没有一个类似0~1的范围,而是来帮助确定微平面法线在某一个给定方向上的集中度。
+
+所以,D()决定了高光的镜面反射高光的大小,亮度和形状,一些D()会提供前面提到的类似”**Roughness(粗糙度)**”的参数(也可以是glossiness),当粗糙度降低时,微几何体平面的法线方向就会更集中在宏观的平面法线方向上,D()的值也会变高。除了指定粗糙度参数外,也可以通过传递一张Glossn map的方式提供更高的细节。
+
+Geometry Function(几何函数)
+shadowing现象 + 因为微几何体的构造缘故,一些入射光的平面点被内部遮挡,成为了内部阴影而无法接受光照(也就不能反射光)。
+masking现象 + 而有一些反射光被内部遮挡,他们的反射光无法被观察到,虽然有反射光可以多次反弹后再被视点观察到,但在微平面理论里可以忽略不计了。
+正因为有这种现象,所以需要有一个Geometry Function G(),来代表反射光的可见度,所以G()是在0~1之间的一个范围值,在着色模型里,有时会和其他参数合并称为V()(Visiblity)。和D()一样,因为微平面有凹凸感,当它的粗糙度提高时,shadow和masking的现象也会增加,粗糙度高的平面会光滑平面更阴暗一些,G()也要收到roughness参数的影响。另外G()也是下面要讲的能量守恒的一个基础,它使得反射光不会高于平面的入射光。
+PBR是如何实现近似的能量守恒呢?
+为了回答这个问题,先弄清楚镜面反射(specular)和漫反射(diffuse)的区别。
+一束光照到材质表面上,通常会分成反射(reflection)部分和折射(refraction)部分。反射部分直接从表面反射出去,而不进入物体内部,由此产生了镜面反射光。折射部分会进入物体内部,被吸收或者散射产生漫反射。
+折射进物体内部的光如果没有被立即吸收,将会持续前进,与物体内部的微粒产生碰撞,每次碰撞有一部分能量损耗转化成热能,直至光线能量全部消耗。有些折射光线在跟微粒发生若干次碰撞之后,从物体表面射出,便会形成漫反射光。
+
照射在平面的光被分成镜面反射和折射光,折射光在跟物体微粒发生若干次碰撞之后,有可能发射出表面,成为漫反射。
+通常情况下,PBR会简化折射光,将平面上所有折射光都视为被完全吸收而不会散开。而有一些被称为 次表面散射(Subsurface Scattering) 技术的着色器技术会计算折射光散开后的模拟,它们可以显著提升一些材质(如皮肤、大理石或蜡质)的视觉效果,不过性能也会随着下降。
+金属(Metallic)材质会立即吸收所有折射光,故而金属只有镜面反射,而没有折射光引起的漫反射。
+回到能量守恒话题。反射光与折射光它们二者之间是互斥的,被表面反射出去的光无法再被材质吸收。故而,进入材质内部的折射光就是入射光减去反射光后余下的能量。
+根据上面的能量守恒关系,可以先计算镜面反射部分,此部分等于入射光线被反射的能量所占的百分比。而折射部分可以由镜面反射部分计算得出。
+float kS = calculateSpecularComponent(...); // 反射/镜面 部分
+float kD = 1.0 - ks; // 折射/漫反射 部分
+
+通过以上代码可以看出,镜面反射部分与漫反射部分的和肯定不会超过1.0,从而近似达到能量守恒的目的。
+渲染方程(Render Equation)是用来模拟光的视觉效果最好的模型。而PBR的渲染方程是用以抽象地描述PBR光照计算过程的特化版本的渲染方程,被称为反射方程。
+PBR的反射方程可抽象成下面的形式:
+
++参数说明: +- fr ( p , ωi , ω0 ) : 双向反射分布函数(BRDF) +- Li ( p , ωi ) : 灯光颜色*灯光强度(
+radiance=lightColor*attenuation) +- n⋅ωi : dot(N,L) +- ωi : 光源方向(L) +- ω0 : 视线方向(V) +- p : 当前点(或者是任意一个点 +- n : 法线(N)++反射方程计算了点𝑝在所有视线方向𝜔0上被反射出来的辐射率𝐿0(𝑝,𝜔0)的总和。换言之:𝐿0计算的是在𝜔i方向的眼睛观察到的𝑝点的总辐照度。
+
反射方程看似很复杂,但如果拆分各个部分加以解析,就可以揭开其神秘的面纱。
+为了更好地理解反射方程,先了解辐射度量学(Radiometry)。辐射度量学是一种用来度量电磁场辐射(包括可见光)的手段。有很多种辐射度量(radiometric quantities)可以用来测量曲面或者某个方向上的光,此处只讨论和反射方程有关的一种量,它就是辐射率(Radiance),用𝐿来表示。
+先用一个表展示辐射度量学涉及的概念、名词、公式等信息,后面会更加详细地介绍。
+
辐射通量(Radiant Flux):辐射通量Φ表示的是一个光源所输出的能量,以瓦特为单位。
立体角(Solid Angle):立体角用ω表示,它可以为我们描述投射到单位球体上的一个截面的大小或者面积。投射到这个单位球体上的截面的面积就被称为立体角(Solid Angle),你可以把立体角想象成为一个带有体积的方向:
+
+可以把自己想象成为一个站在单位球面的中心的观察者,向着投影的方向看。这个投影轮廓的大小就是立体角。
辐射强度(Radiant Intensity):辐射强度(Radiant Intensity)表示的是在单位球面上,一个光源向每单位立体角所投送的辐射通量。举例来说,假设一个全向光源向所有方向均匀的辐射能量,辐射强度就能帮我们计算出它在一个单位面积(立体角)内的能量大小:
+
+计算辐射强度的公式如下所示:
+
+(其中I表示辐射通量Φ除以立体角ω)。
++在理解了辐射通量,辐射强度与立体角的概念之后,我们终于可以开始讨论辐射率的方程式了。这个方程表示的是,一个拥有
+辐射强度Φ的光源在单位面积A,单位立体角ω上的辐射出的总能量:
+![]()
辐射率:是辐射度量学上表示一个区域平面上光线总量的物理量,它受到入射(Incident)(或者来射)光线与平面法线间的夹角θ的余弦值cosθ的影响:当直接辐射到平面上的程度越低时,光线就越弱,而当光线完全垂直于平面时强度最高。cosθ就直接对应于光线的方向向量和平面法向量的点积:
+ c
+ float cosTheta = dot(lightDir, N);
+ 辐射率方程很有用,因为它把大部分我们感兴趣的物理量都包含了进去。如果我们把立体角ω和面积A看作是无穷小的,那么我们就能用辐射率来表示单束光线穿过空间中的一个点的通量。这就使我们可以计算得出作用于单个(片段)点上的单束光线的辐射率,我们实际上把立体角ω转变为方向向量ω然后把面A转换为点p。这样我们就能直接在我们的着色器中使用辐射率来计算单束光线对每个片段的作用了。
事实上,当涉及到辐射率时,我们通常关心的是所有投射到点p上的光线的总和,而这个和就称为辐射照度或者辐照度(Irradiance)。在理解了辐射率和辐照度的概念之后,让我们再回过头来看看反射率方程:
+
我们知道在渲染方程中L代表通过某个无限小的立体角ωi在某个点p的辐射率,而立体角可以视作是入射光方向向量ωi。将用来衡量入射光与平面法线夹角对能量的影响的cos𝜃分量移出辐射率方程,作为反射方程的单独项𝑛⋅𝜔i 。
+反射方程计算了点𝑝在所有视线方向𝜔0上被反射出来的辐射率𝐿0(𝑝,𝜔0)的总和。换言之:𝐿0计算的是在𝜔i方向的眼睛观察到的𝑝点的总辐照度。
+反射方程里面使用的辐照度,必须要包含所有以𝑝点为中心的半球Ω内的入射光,而不单单只是某一个方向的入射光。这个半球指的是围绕面法线𝑛的那一个半球:
+
++笔者注:为什么只计算半球而不计算整个球体呢?
+因为另外一边的半球因与视线方向相反,不能被观察,也就是辐射通量贡献量为0,所以被忽略。
+
为了计算这个区域(半球)内的所有值,在反射方程中使用了一个称作为积分的数学符号 ∫,来计算半球 Ω 内所有的入射向量𝑑𝜔i。
+积分计算面积的方法,有解析(analytically)和渐近(numerically)两种方法。目前尚没有可以满足渲染计算的解析法,所以只能选择离散渐近法来解决这个积分问题。
+具体做法是在半球Ω按一定的步长将反射方程离散地求解,然后再按照步长大小将所得到的结果平均化,这种方法被称为黎曼和(Riemann sum)。
+至此,反射方程中,只剩下𝑓𝑟项未描述。𝑓r就是双向反射分布函数(Bidirectional Reflectance Distribution Function, BRDF),它的作用是基于表面材质属性来对入射辐射度进行缩放或者加权。
+双向反射分布函数(Bidirectional Reflectance Distribution Function,BRDF)是一个使用入射光方向𝜔i作为输入参数的函数,输出参数为出射光𝜔0,表面法线为𝑛,参数𝑎表示的是微平面的粗糙度。
+更笼统地说,它描述了入射光线经过某个表面反射后如何在各个出射方向上分布这可以是从理想镜面反射到漫反射、各向同性或者各向异性的各种反射。
+BRDF函数是近似的计算在一个给定了属性的不透明表面上每个单独的光线对最终的反射光的贡献量。假如表面是绝对光滑的(比如镜子),对于所有入射光𝜔i的BRDF函数都将会返回0.0,除非出射光线𝜔𝑜方向的角度跟入射光线𝜔i方向的角度以面法线为中轴线完全对称,则返回1.0。
+BRDF有好几种模拟表面光照的算法,然而,基本上所有的 实时渲染管线使用的都是Cook-Torrance BRDF。
+Cook-Torrance BRDF分为 漫反射 和 镜面反射 两个部分:
+
+其中𝑘𝑑是入射光中被折射的比例,𝑘𝑠是另外一部分被镜面反射的入射光。BRDF等式左边的𝑓𝑙𝑎𝑚𝑏𝑒𝑟𝑡表示的是漫反射部分,这部分叫做伦勃朗漫反射(Lambertian Diffuse)。它类似于我们之前的漫反射着色,是一个恒定的算式:

+其中𝑐代表的是Albedo或表面颜色,类似漫反射表面纹理。除以𝜋是为了规格化漫反射光,为后期的BRDF积分做准备。
++此处的伦勃朗漫反射跟以前用的漫反射之间的关系:以前的漫反射是用表面的漫反射颜色乘以法线与入射光方向的点积,这个点积依然存在,只不过是被移到了BRDF外面,写作𝑛⋅𝜔i,放在反射方程𝐿𝑜靠后的位置。
+
BRDF的高光(镜面反射)部分更复杂:
+
+Cook-Torrance镜面反射BRDF由3个函数(𝐷,𝐹,𝐺)和一个标准化因子构成。𝐷,𝐹,𝐺符号各自近似模拟了特定部分的表面反射属性:
以上的每一种函数都是用来估算相应的物理参数的,而且你会发现用来实现相应物理机制的每种函数都有不止一种形式。它们有的非常真实,有的则性能高效。你可以按照自己的需求任意选择自己想要的函数的实现方法。
+++Epic Games公司的Brian Karis对于这些函数的多种近似实现方式进行了大量的研究。这里将采用Epic Games在Unreal Engine 4中所使用的函数,其中𝐷使用Trowbridge-Reitz GGX,𝐹使用Fresnel-Schlick近似法(Approximation),而𝐺使用Smith's Schlick-GGX。
+
法线分布函数,从统计学上近似的表示了与某些(如中间)向量ℎ(半角向量)取向一致的微平面的比率。
+目前有很多种NDF都可以从统计学上来估算微平面的总体取向度,只要给定一些粗糙度的参数以及一个我们马上将会要用到的参数Trowbridge-Reitz GGX(GGXTR):
+
这里的ℎ是用来测量微平面的半角向量,𝛼是表面的粗糙度,𝑛是表面法线。 如果将ℎ放到表面法线和光线方向之间,并使用不同的粗糙度作为参数,可以得到下面的效果:
+
+当粗糙度很低(表面很光滑)时,与中间向量ℎ取向一致的微平面会高度集中在一个很小的半径范围内。由于这种集中性,NDF最终会生成一个非常明亮的斑点。但是当表面比较粗糙的时候,微平面的取向方向会更加的随机,与向量ℎ取向一致的微平面分布在一个大得多的半径范围内,但是较低的集中性也会让最终效果显得更加灰暗。
Trowbridge-Reitz GGX的NDF实现代码:
+float DistributionGGX(vec3 N, vec3 H, float a)
+{
+ float a2 = a*a;
+ float NdotH = max(dot(N, H), 0.0);
+ float NdotH2 = NdotH*NdotH;
+
+ float nom = a2;
+ float denom = (NdotH2 * (a2 - 1.0) + 1.0);
+ denom = PI * denom * denom;
+
+ return nom / denom;
+}
+
+
+菲涅尔方程定义的是在不同观察方向上,表面上被反射的光除以被折射的光的比例。在一束光击中了表面的一瞬间,菲涅尔根据表面与观察方向之间的夹角,计算得到光被反射的百分比。根据这个比例和能量守恒定律我们可以直接知道剩余的能量就是会被折射的能量。
+当我们垂直观察每个表面或者材质时都有一个基础反射率,当我们以任意一个角度观察表面时所有的反射现象都会变得更明显(反射率高于基础反射率)。你可以从你身边的任意一件物体上观察到这个现象,当你以90度角观察你的桌子你会法线反射现象将会变得更加的明显,理论上以完美的90度观察任意材质的表面都应该会出现全反射现象(所有物体、材质都有菲涅尔现象)。
+菲涅尔方程同样是个复杂的方程,但是幸运的是菲涅尔方程可以使用Fresnel-Schlick来近似:
+
+𝐹0表示的是表面基础反射率,这个我们可以使用一种叫做Indices of refraction(IOR)的方法计算得到。运用在球面上的效果就是你看到的那样,观察方向越是接近掠射角(grazing angle,又叫切线角,与正视角相差90度),菲涅尔现象导致的反射就越强:
+
菲涅尔方程中有几个微妙的地方,一个是Fresnel-Schlick算法仅仅是为电介质(绝缘体)表面定义的算法。对于金属表面,使用电介质的折射率来计算基础反射率是不合适的,我们需要用别的菲涅尔方程来计算。对于这个问题,我们需要预先计算表面在正视角(即以0度角正视表面)下的反应(𝐹0),然后就可以跟之前的Fresnel-Schlick算法一样,根据观察角度来进行插值。这样我们就可以用一个方程同时计算金属和电介质了。
+表面在正视角下的反映或者说基础反射率可以在这个数据库中找到,下面是Naty Hoffman的在SIGGRAPH公开课中列举的一些常见材质的值:
+
+这里可以观察到的一个有趣的现象,所有电介质材质表面的基础反射率都不会高于0.17,这其实是例外而非普遍情况。导体材质表面的基础反射率起点更高一些并且(大多)在0.5和1.0之间变化。此外,对于导体或者金属表面而言基础反射率一般是带有色彩的,这也是为什么要用RGB三原色来表示的原因(法向入射的反射率可随波长不同而不同)。这种现象我们只能在金属表面观察的到。
++金属表面这些和电介质表面相比所独有的特性引出了所谓的金属工作流的概念。也就是我们需要额外使用一个被称为金属度(Metalness)的参数来参与编写表面材质。金属度用来描述一个材质表面是金属还是非金属的。
+
通过预先计算电介质与导体的值,我们可以对两种类型的表面使用相同的Fresnel-Schlick近似,但是如果是金属表面的话就需要对基础反射率添加色彩。我们一般是按下面这个样子来实现的:
+vec3 F0 = vec3(0.04);
+F0 = mix(F0, surfaceColor.rgb, metalness);
+
+我们为大多数电介质表面定义了一个近似的基础反射率。𝐹0取最常见的电解质表面的平均值,这又是一个近似值。不过对于大多数电介质表面而言使用0.04作为基础反射率已经足够好了,而且可以在不需要输入额外表面参数的情况下得到物理可信的结果。然后,基于金属表面特性,我们要么使用电介质的基础反射率要么就使用𝐹0作来为表面颜色。因为金属表面会吸收所有折射光线而没有漫反射,所以我们可以直接使用表面颜色纹理来作为它们的基础反射率。
+Fresnel Schlick近似可以用GLSL代码实现:
+vec3 fresnelSchlick(float cosTheta, vec3 F0)
+{
+ return F0 + (1.0 - F0) * pow(1.0 - dot(n, v), 5.0);
+}
+
+
+几何函数模拟微平面相互遮挡导致光线的能量减少或丢失的现象。
+
类似NDF,几何函数也使用粗糙度作为输入参数,更粗糙意味着微平面产生自阴影的概率更高。几何函数使用由GGX和Schlick-Beckmann组合而成的模拟函数Schlick-GGX:
+
+这里的𝑘是使用粗糙度𝛼计算而来的,用于直接光照和IBL光照的几何函数的参数:
+
+需要注意的是这里𝛼的值取决于你的引擎怎么将粗糙度转化成𝛼,在接下来的教程中我们将会进一步讨论如何和在什么地方进行这个转换。
为了有效地模拟几何体,我们需要同时考虑两个视角,视线方向(几何遮挡)跟光线方向(几何阴影),我们可以用Smith函数将两部分放到一起:
+
+其中𝑣表示视线向量,𝐺𝑠𝑢𝑏(𝑛,𝑣,𝑘)表示视线方向的几何遮挡;𝑙表示光线向量,𝐺𝑠𝑢𝑏(𝑛,𝑙,𝑘)表示光线方向的几何阴影。使用Smith函数与Schlick-GGX作为𝐺𝑠𝑢𝑏可以得到如下所示不同粗糙度R的视觉效果:
+
+几何函数是一个值域为[0.0, 1.0]的乘数,其中白色(1.0)表示没有微平面阴影,而黑色(0.0)则表示微平面彻底被遮蔽。
使用GLSL编写的几何函数代码如下:
+float GeometrySchlickGGX(float NdotV, float k)
+{
+ float nom = NdotV;
+ float denom = NdotV * (1.0 - k) + k;
+
+ return nom / denom;
+}
+
+float GeometrySmith(vec3 N, vec3 V, vec3 L, float k)
+{
+ float NdotV = max(dot(N, V), 0.0);
+ float NdotL = max(dot(N, L), 0.0);
+ float ggx1 = GeometrySchlickGGX(NdotV, k); // 视线方向的几何遮挡
+ float ggx2 = GeometrySchlickGGX(NdotL, k); // 光线方向的几何阴影
+
+ return ggx1 * ggx2;
+}
+
+Cook-Torrance反射方程中的每一个部分我们我们都用基于物理的BRDF替换,可以得到最终的反射方程:
+
+上面的方程并非完全数学意义上的正确。前面提到菲涅尔项𝐹代表光在表面的反射比率,它直接影响𝑘𝑠因子,意味着反射方程的镜面反射部分已经隐含了因子𝑘𝑠。因此,最终的Cook-Torrance反射方程如下(去掉了𝑘𝑠):
+
这个方程完整地定义了一个基于物理的渲染模型,也就是我们一般所说的基于物理的渲染(PBR)。
+反射率(Albedo):反射率纹理指定了材质表面每个像素的颜色,如果材质是金属那纹理包含的就是基础反射率。这个跟我们之前用过的漫反射纹理非常的类似,但是不包含任何光照信息。漫反射纹理通常会有轻微的阴影和较暗的裂缝,这些在Albedo贴图里面都不应该出现,仅仅只包含材质的颜色(金属材质是基础反射率)。
+法线(Normal):法线纹理跟我们之前使用的是完全一样的。法线贴图可以逐像素指定表面法线,让平坦的表面也能渲染出凹凸不平的视觉效果。
+金属度(Metallic):金属度贴图逐像素的指定表面是金属还是电介质。根据PBR引擎各自的设定,金属程度即可以是[0.0,1.0]区间的浮点值也可以是非0即1的布尔值。
+粗糙度(Roughness):粗糙度贴图逐像素的指定了表面有多粗糙,粗糙度的值影响了材质表面的微平面的平均朝向,粗糙的表面上反射效果更大更模糊,光滑的表面更亮更清晰。有些PBR引擎用光滑度贴图替代粗糙度贴图,因为他们觉得光滑度贴图更直观,将采样出来的光滑度使用(1-光滑度)= 粗糙度 就能转换成粗糙度了。
+环境光遮挡(Ambient Occlusion,AO):AO贴图为材质表面和几何体周边可能的位置,提供了额外的阴影效果。比如有一面砖墙,在两块砖之间的缝隙里Albedo贴图包含的应该是没有阴影的颜色信息,而让AO贴图来指定这一块需要更暗一些,这个地方光线更难照射到。AO贴图在光照计算的最后一步使用可以显著的提高渲染效果,模型或者材质的AO贴图一般是在建模阶段手动生成的。
+上面阐述了Cook-Torrance反射方程的理论和公式意义。这节将探讨如何将前面讲到的理论转化成一个基于直接光照的渲染器:比如点光源,方向光和聚光灯。
+上面解释了Cook-Torrance反射方程的大部分含义,但有一点未提及:具体要怎么处理场景中的辐照度(Irradiance,也就是辐射的总能量𝐿)?在计算机领域,场景的辐射率𝐿度量的是来自光源光线的辐射通量𝜙穿过指定的立体角𝜔,在这里我们假设立体角𝜔无限小,小到辐射度衡量的是光源射出的一束经过指定方向向量的光线的通量。
+有了这个假设,我们又要怎么将之融合到之前教程讲的光照计算里去呢?想象我们有一个辐射通量以RGB表示为(23.47, 21.31, 20.79)的点光源,这个光源的辐射强度等于辐射通量除以所有出射方向。当为平面上某个特定的点𝑝着色的时候,所有可能的入射光方向都会经过半球Ω,但只有一个入射方向𝜔i是直接来自点光源的,又因为我们的场景中只包含有一个光源,且这个光源只是一个点,所以𝑝点所有其它的入射光方向的辐射率都应该是0.
+
+如果我们暂时不考虑点光源的距离衰减问题,且无论光源放在什么地方入射光线的辐射率都一样大(忽略入射光角度cos𝜃对辐射度的影响),又因为点光源朝各个方向的辐射强度都是一样的,那么有效的辐射强度就跟辐射通量完全一样:恒定值(23.47, 21.31, 20.79)。
然而,辐射率需要使用位置𝑝作为输入参数,因为现实中的灯光根据点𝑝和光源之间距离的不同,辐射强度多少都会有一定的衰减。另外,从原始的辐射方程中我们可以发现,面法线𝑛于入射光方向向量𝜔i的点积也会影响结果。
+用更精炼的话来描述:在点光源直接光照的情况里,辐射率函数𝐿计算的是灯光颜色,经过到𝑝点距离的衰减之后,再经过𝑛⋅𝜔i缩放。能击中点𝑝的光线方向𝜔i就是从𝑝点看向光源的方向。把这些写成代码:
+vec3 lightColor = vec3(23.47, 21.31, 20.79);
+vec3 wi = normalize(lightPos - fragPos);
+float cosTheta = max(dot(N, Wi), 0.0);
+// 计算光源在点fragPos的衰减系数
+float attenuation = calculateAttenuation(fragPos, lightPos);
+// 英文原版的radiance类型有误,将它改成了vec3
+vec3 radiance = lightColor * (attenuation * cosTheta);
+
+你应该非常非常熟悉这段代码:这就是以前我们计算漫反射光的算法!在只有单光源直接光照的情况下,辐射率的计算方法跟我们以前的光照算法是类似的。
+++要注意我们这里假设点光源无限小,只是空间中的一个点。如果我们使用有体积的光源模型,那么就有很多的入射光方向的辐射率是非0的。 +对那些基于点的其他类型光源我们可以用类似的方法计算辐射率,比如平行光源的入射角的恒定的且没有衰减因子,聚光灯没有一个固定的辐射强度,而是围绕一个正前方向量来进行缩放的。
+
这也将我们带回了在表面半球Ω的积分∫。我们知道,多个单一位置的光源对同一个表面的同一个点进行光照着色并不需要用到积分,我们可以直接拿出这些数目已知的光源来,分别计算这些光源的辐照度后再加到一起,毕竟每个光源只有一束方向光能影响物体表面的辐射率。这样只需要通过相对简单的循环计算每个光源的贡献就能完成整个PBR光照计算。当我们需要使用IBL将环境光加入计算的时候我们才会需要用到积分,因为环境光可能来自任何方向。
++ ++
++基于图像的光照(IBL)是对光源物体的技巧集合,与直接光照不同,它将周围环境当成一个大光源。IBL通常结合
+cubemap环境贴图,cubemap通常采集自真实的照片或从3D场景生成,这样可以将其用于光照方程:将cubemap的每个像素当成一个光源。这样可以更有效地捕获全局光照和常规感观,使得被渲染的物体更好地融入所处的环境中。
当基于图像的光照算法获得一些(全局的)环境光照时,它的输入被当成更加精密形式的环境光照,甚至是一种粗糙的全局光照的模拟。这使得IBL有助于PBR的渲染,使得物体渲染效果更真实。
+在介绍IBL结合PBR之前,先回顾一下反射方程:
+
如之前所述,我们的主目标是解决所有入射光𝑤𝑖通过半球Ω的积分∫。与直接光照不同的是,在IBL中,每一个来自周围环境的入射光𝜔𝑖都可能存在辐射,这些辐射对解决积分有着重要的作用。为解决积分有两个要求:
+对第一个要求,相对简单,采用环境cubemap。给定一个cubemap,可以假设它的每个像素是一个单独的发光光源。通过任意方向向量𝜔𝑖采样cubemap,可以获得场景在这个方向的辐射。
+获取任意方向向量𝜔𝑖的场景辐射很简单,如下:
+vec3 radiance = texture(_cubemapEnvironment, w_i).rgb;
+对要求二,解决积分能只考虑一个方向的辐射,要考虑环境贴图的半球Ω的所有可能的方向𝜔i,但常规积分方法在片元着色器中开销非常大。为了有效解决积分问题,可采用预计算或预处理的方法。因此,需要深究一下反射方程:
+
可将上述的𝑘𝑑和𝑘𝑠项拆分:
+
+拆分后,可分开处理漫反射和镜面反射的积分。先从漫反射积分开始。
仔细分析上面方程的漫反射积分部分,发现Lambert漫反射是个常量项(颜色𝑐,折射因子𝑘𝑑和𝜋)并且不依赖积分变量。因此,可见常量部分移出漫反射积分:
+
因此,积分只依赖𝜔i(假设𝑝在环境贴图的中心)。据此,可以计算或预计算出一个新的cubemap,这个cubemap存储了用卷积(convolution)计算出的每个采样方向(或像素)𝜔𝑜的漫反射积分结果。
+卷积(convolution)是对数据集的每个入口应用一些计算,假设其它所有的入口都在这个数据集里。此处的数据集就是场景辐射或环境图。因此,对cubemap的每个采样方向,我们可以顾及在半球Ω的其它所有的采样方向。
+为了卷积环境图,我们要解决每个输出𝜔𝑜采样方向的积分,通过离散地采样大量的在半球Ω的方向𝜔i并取它们辐射的平均值。采样方向𝜔i的半球是以点𝑝为中心以𝜔𝑜为法平面的。
+
这个预计算的为每个采样方向𝜔𝑜存储了积分结果的cubemap,可被当成是预计算的在场景中所有的击中平行于𝜔𝑜表面的非直接漫反射的光照之和。这种cubemap被称为 辐照度图(Irradiance map)。
+++辐射方程依赖于位置𝑝,假设它在辐照度图的中心。这意味着所有非直接漫反射光需来自于同一个环境图,它可能打破真实的幻觉(特别是室内)。渲染引擎用放置遍布场景的反射探头(reflection probe)来解决,每个反射探头计算其所处环境的独自的辐照度图。这样,点p的辐射率(和辐射)是与其最近的反射探头的辐照度插值。这里我们假设总是在环境图的中心采样。反射探头将在其它章节探讨。
+
下面是cubemap环境图(下图左)和对应的辐照度图(下图右):
+
通过存储每个cubemap像素卷积的结果,辐照度图有点像环境的平均颜色或光照显示。从这个环境图采样任意方向,可获得这个方向的场景辐照度。
+++辐射度图提供了漫反射部分的积分,该积分表示来自非直接的所有方向的环境光辐射之和。由于辐射度图被当成是无方向性的光源,所以可以将漫反射镜面反射合成环境光。
+
在之前所述的反射方程中,非直接光依旧包含了漫反射和镜面反射两个部分,所以我们需要加个权重给漫反射。依旧使用菲涅尔方程来计算漫反射因子:
+vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
+vec3 kD = 1.0 - kS;
+vec3 irradiance = texture(ambientCubeMap, N).rgb;
+vec3 diffuse = irradiance * albedo;
+vec3 ambient = (kD * diffuse) * ao;
+
+由于环境光来自在半球内所有围绕着法线N的方向,没有单一的半向量去决定菲涅尔因子。为了仍然能模拟菲涅尔,这里采用了法线和视线的夹角。之前的算法采用了受表面粗糙度影响的微平面半向量,作为菲涅尔方程的输入。这里,我们并不考虑粗糙度,表面的反射因子被视作相当大。
+非直接光照将沿用直接光照的相同的属性,所以,期望越粗糙的表面镜面反射越少。由于不考虑表面粗糙度,非直接光照的菲涅尔方程强度被视作粗糙的非金属表面(下图)。
+
为了缓解这个问题,可在Fresnel-Schlick方程注入粗糙度项(该方程的来源):
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
+{
+ return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
+}
+
+考虑了表面粗糙度后,菲涅尔相关计算最终如下:
+vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
+vec3 kD = 1.0 - kS;
+vec3 irradiance = texture(ambientCubeMap, N).rgb;
+vec3 diffuse = irradiance * albedo;
+vec3 ambient = (kD * diffuse) * ao;
+
+如上所述,实际上,基于图片的光照计算非常简单,只需要单一的cubemap纹理采样。大多数的工作在于预计算或卷积环境图到辐射度图。
+加入了IBL的渲染效果如下(竖向是金属度增加,水平是粗糙度增加):
+
上面描述的是IBL的漫反射部分,本节将讨论IBL的镜面反射部分先回顾一下反射方程:
+
+上述的镜面反射部分(被𝑘𝑠相乘)不是恒定的,并且依赖于入射光方向和视线入射方向,尝试实时地计算所有入射光和所有入射视线的积分是几乎不可能的。Epic Games推荐折中地使用预卷积镜面反射部分的方法来解决实时渲染的性能问题,这就是分裂和近似法(split sum approximation)。
分裂和近似法将镜面反射部分从反射方程分离出两个部分,这样可以单独地对它们卷积,后面在PBR的shader中为镜面的非直接IBL将它们结合起来。跟预卷积辐射度图类似,分裂和近似法需要HDR环境图作为输入。为了更好地理解分裂和近似法,下面着重关注反射方程的镜面部分:
+
+出于跟辐射度图相同的性能问题的考虑,我们要预计算类似镜面IBL图的积分,并且用片元的法线采样这个图。辐射度图的预计算只依赖于𝜔i,并且我们可以将漫反射项移出积分。但这次从BRDF可以看出,不仅仅是依赖于𝜔i:
+
如上方程所示,还依赖𝜔𝑜,并且我们不能用两个方向向量来采样预计算的cubemap。预计算所有𝜔i和𝜔𝑜的组合在实时渲染环境中不实际的。
+Epic Games的分裂和近似法将镜面反射部分从反射方程分离出两个部分,这样可以单独地对它们卷积,后面在PBR的shader中为镜面的非直接IBL将它们结合起来。分离后的方程如下:
+
第一部分
是预过滤环境图(pre-filtered environment map),类似于辐射度图的预计算环境卷积图,但会加入粗糙度。随着粗糙度等级的增加,环境图使用更多的散射采样向量来卷积,创建出更模糊的反射。
对每个卷积的粗糙度等级,循环地在预过滤环境图的mimap等级存储更加模糊的结果。下图是5个不同粗糙度等级的预过滤环境图:
+
生成采样向量和它们的散射强度,需要用到Cook-Torrance BRDF的法线分布图(NDF),而其带了两个输入:法线和视线向量。当卷积环境图时并不知道视线向量,Epic Games用了更近一步的模拟法:假设视线向量(亦即镜面反射向量)总是等于输出采样向量𝜔𝑜。所以代码变成如下所示:
+vec3 N = normalize(w_o);
+vec3 R = N;
+vec3 V = R;
+
+
+这种方式预过滤环境图卷积不需要关心视线方向。这就意味着当从某个角度看向下面这张图的镜面表面反射时,无法获得很好的掠射镜面反射(grazing specular reflections)。然而通常这被认为是一个较好的妥协:
+
+第二部分
是镜面积分。假设所有方向的入射辐射率是全白的(那样𝐿(𝑝,𝑥)=1.0),那就可以用给定的粗糙度和一个法线𝑛和光源方向𝜔i之间的角度或𝑛⋅𝜔i来预计算BRDF的值。Epic Games存储了用变化的粗糙度来预计算每一个法线和光源方向组合的BRDF的值,该粗糙度存储于2D采样纹理(LUT)中,它被称为BRDF积分图(BRDF integration map)。
2D采样纹理输出一个缩放(红色)和一个偏移值(绿色)给表面的菲涅尔方程式(Fresnel response),以便提供第二部分的镜面积分:
+
上图水平表示BRDF的输入𝑛⋅𝜔i,竖向表示输入的粗糙度。
+有了预过滤环境图和BRDF积分图,可以在shader中将它们结合起来:
+float lod = getMipLevelFromRoughness(roughness);
+vec3 prefilteredColor = textureCubeLod(PrefilteredEnvMap, refVec, lod);
+vec2 envBRDF = texture2D(BRDFIntegrationMap, vec2(NdotV, roughness)).xy;
+vec3 indirectSpecular = prefilteredColor * (F * envBRDF.x + envBRDF.y)
+
+++示例代码:LearnOpenGL:IBL
+
++基础参数说明: +- Albedo + 可以理解为物体的基础色Base Color,即该物体本身颜色,也是漫反射光的颜色。
+其实该颜是色光的折射被物质吸收并形成漫反射后,成为不同波长的光,这就赋予了物体颜色。比如说物体呈现蓝色,是因为其把除了蓝色的光都吸收了,散射出来的光的波长是在蓝色所在范围内。
++
+- Metallic + 即金属度,表示了反射时发生镜面反射和漫反射的光线的占比。
+Metallic度越大,发生镜面反射的占比越大,漫反射diffuse占比越小,一般金属物体的金属度比较大:70% ~ 100%之间;
+Metallic越小,发生镜面反射的占比越小,漫反射diffuse占比越大,一般非金属物质金属度比较小;2% ~ 5%之间,宝石的大概8%;
+金属是没有漫反射的,折射进表面的光照全部被吸收。但是一些腐蚀性的金属,其腐蚀的部分具有漫反射。
++
+- Specular + 这个没什么好说的,就是镜面反射,对于非金属物质,其镜面反射的sRBG范围在40 ~ 75;对于金属物质,其sRGB范围在155 ~ 255
+其也是表示0度时的菲涅尔系数RF(0°)。
++
+- Roughness + 即粗糙度,其与Smoothness(光滑度,也称为Glossiness光泽度)相反。其表示了物体表面的不规则程度,决定了在发生镜面反射时入射光线与法线的夹角大小。
+粗糙度roughness越大,镜面反射的出射光线分散的角度就越大,光照越模糊;
+粗糙度roughness越小,镜面反射的出射光线分散的角度就越小,光照越尖锐;
+在真实的生活中,视觉效果的呈现,主要取决于:
++
+- 自然光照下,物体呈现的颜色(BasedColor/Albedo);
+- 物体表面对光线的镜面反射角度(Roughness);
+- 物体表面对光线镜面反射和漫反射的比例(Metallic/Specular);
+
++UE4使用该工作流
+
在Metal-Rougnness流程中,分别对应BaseColor,Roughness,Metallic这三个参数;
+在Metal-Roughness流程中,只要按照流程,分别设置好BaseColor,Roughness,Metallic,就可以基本确定物体材质的视觉效果;
+++Unity使用该工作流
+
在Specular-Glossiness流程中,参数发生了变化,分别为Diffuse,Glossiness,Specular三个参数;
+在Specular-Glossiness流程中,Diffuse和Specular共同决定了物体的basecolor,和表面镜面反射和漫反射的比例,与第一种流程的区别在于,此流程直接指定确定的占比值,第一种是根据Metallic属性,自动匹配相应的占比值;
+都需要使用AO、Normal、Height纹理。
+Metal/Roughness工作流程使用:Base Color、Roughness、Metallic三种纹理来作为基础纹理(最常用的工作流)。
+Specular/Glossiness工作流程使用:Diffuse Map、Glossiness、Specular三种纹理来作为基础纹理。
+++ + + + + + + + + + + + + + + + + + + + + + + + + +参考: +PBR原理
+
+由浅入深学习PBR的原理和实现
+LearnOpenGL:PBR理论
+LearnOpenGL:PBR Lighting
UIParticle使用教程¶++Unity中
+UGUI使用Particle System每次需要设置rendererorder和layer等信息来控制排序,非常繁琐,这里使用ParticleEffectForUGUI插件来解决该问题。
+ParticleEffectForUGUI
设置Game窗口的分辨率 1920*1080
将带Particle System组件的物体放到UGUI根节点里面
将带Particle System组件的物体添加 UIParticle 组件,该组件会自动添加到子节点的所有带Particle System组件的物体上

Scale:调整该Particle System的缩放,不影响子物体
+Animatable Properties:如果使用Animation或者其他动画更新(Update)材质球上的属性,则勾选上对应的Shader属性即可
+
删除则需要每个物体去删除 UIParticle 组件
每个带Particle System组件的物体都要增加 UIParticle 组件 (可以挂到prefab根节点物体上,可以是空物体)
ParticleEffectForUGUI会自动合批处理,不被打算的情况下
ParticleEffectForUGUI插件支持 Mask Rect Mask2D等遮罩,需要在使用的Shader上添加模板测试(Stencil)和ClipRect代码进行支持
+++
GPU Instancing是对相同mesh的多份实例进行处理,例如树和草等
+instanceID:语义SV_InstanceID,类型uint,实例对应的数组索引。当启用实例化时,我们现在可以访问顶点程序中的实例 ID。有了它,我们可以在变换顶点位置时使用正确的矩阵。但是,UnityObjectToClipPos没有矩阵参数。它总是使用unity_ObjectToWorld. 为了解决这个问题,UnityInstancing包含unity_ObjectToWorld使用矩阵数组的宏覆盖文件。这可以被认为是一个肮脏的宏黑客,但它无需更改现有着色器代码即可工作,从而确保向后兼容性。(在使用实例化渲染调用时,gl_InstanceID会从0开始,在每个实例被渲染时递增1。比如说,我们正在渲染第43个实例,那么顶点着色器中它的gl_InstanceID将会是42。因为每个实例都有唯一的ID,我们可以建立一个数组,将ID与位置值对应起来,将每个实例放置在世界的不同位置。)
+教程:(Jasper Flick) GPU Instancing
+Jasper Flick详细说明了原理及lod_fade对GPU Instancing的支持
简要概括:cpu将相同mesh的物体相且相同材质球每次次最大化数量提交到GPU,额外增加untiy_ObjectToWorld[]数组,及不同的颜色数组(如果有),顶点属性(instanceID)等等信息,减少了SetPassCall,实际GPU的DrawCall没有减少。
+条件: +- 相同mesh的物体 +- 相同材质球 (可以使用共享材质球)
+支持使用MaterialPropertyBlock修改
++教程:(Jasper Flick)SRP Batcher
+
+这里教程说明了SRP Batcher在2.1节,GPU Instancing(SRP渲染管线) 在2.3节
将属性写在 UnityPerMaterial 和 UnityPerDraw 中
+条件: +- 相同Shader(允许不同材质球,不同mesh) +- 相同Shader的Keywords(变体) +- 对象不可以是粒子或蒙皮网格 +- 位置不相邻且中间夹杂不同的Shader或不同的变体的其他物体,不会同批处理(可以调整Queue来避免该情况)
+不支持使用 ~~MaterialPropertyBlock~~ 修改,使用 MaterialPropertyBlock 对属性进行更改之后,不再合并,然后转到GPUIn stancing,如果支持的话。
+++优先级顺序: SRP Batcher > Static Batching > GPU Instancing > Dynamic Batching
+
| - | +Static Batching | +Dynamic Batching | +GPU Instancing | +SRP Batcher | +
|---|---|---|---|---|
| 优点 | +限制少 | +自动无需处理 | +性能极好 | +多材质加速 | +
| 缺点 | +增加包体大小 增加运行时内存 |
+增加运行时CPU消耗 限制多 |
+限制多 | +只能用于SRP中 | +
| 适用场景 | +静态场景不适合大量重复物体 |
+小物体、特效等 | +大量重复物体 | +较为广泛 | +
++ + + + + + + + + + + + + + + + + + + + + + + + + +参考:https://www.jianshu.com/p/f2a7e9ed9b89
+
+(Unity Doc) GPU Instancing +(Unity Doc) SRP Batcher
ssh-keygen命令用于为“ssh”生成、管理和转换认证密钥,它支持RSA和DSA两种认证密钥.
+ssh-keygen(选项)
+-b:指定密钥长度;
+-e:读取openssh的私钥或者公钥文件;
+-C:添加注释;
+-f:指定用来保存密钥的文件名;
+-i:读取未加密的ssh-v2兼容的私钥/公钥文件,然后在标准输出设备上显示openssh兼容的私钥/公钥;
+-l:显示公钥文件的指纹数据;
+-N:提供一个新密语;
+-P:提供(旧)密语;
+-q:静默模式;
+-t:指定要创建的密钥类型。
+
+++特别注意
+-C参数: +-C:添加注释
+如果在github中配置ssh 则这个参数的值必须是github的邮件名称,不能是其他的
案例:
+ssh-keygen -t rsa -f id_rsa_github -C "codingriver@163.com"
+# -t 指定要创建的密钥类型
+# -f 指定创建的密钥文件名字
+# -C 注释 注意如果是github配置ssh,这里必须是github的邮箱名称
+
+ssh-keygen -t ed25519 -C "codingriver@163.com"
+
+# 验证github ssh配置是否成功
+ssh -vT git@github.com
+# or
+# 验证github ssh配置是否成功
+ssh -vT git@github.com
+
+ssh-keygen -t rsa
+ 生成密钥和公钥,将公钥复制到远程服务器的.ssh目录下c
+ cd ~/.ssh
+ ls //ls你可以看到这个文件
+ touch authorized_keys //生成认证文件,其实已经有了
+ cat id_rsa.pub >> authorized_keys // 将上面生成公钥复制到.ssh后然后复制到authorized_keys文件
+ chmod 600 authorized_keys //设置文件权限
+ chmod 700 -R .ssh //是指文件夹权限/etc/ssh/sshd_config,需要使用管理员权限打开
+ 几步处理:
+ c
+ //使用密钥登录
+ RSAAuthentication yes
+ PubkeyAuthentication yes
+ //禁止空密码和Root密码登录:
+ PermitEmptyPasswords no
+ PasswordAuthentication nossh -i .ssh/remote_rsa root@101.43.160.247
+// - i 指定密钥的文件路径
+// root 登录的用户名
+// 最后是ip
+
+++参考 https://blog.csdn.net/ouzuosong/article/details/52225087
+
+安装ssh https://www.cnblogs.com/wangboyu/articles/11611925.html
# -i 指定私钥地址(私钥和公钥的文件名是一样的,只不过公钥文件有一个 .pub 后缀名。换句话说,如果把本地公钥给删了,只剩下私钥是无法登录的,因为在登录时要将公钥id发送给服务端,这样服务端才知道要选择哪个公钥加密)
+$ ssh -p 22 root@192.168.56.102 -i ~/.ssh/id_rsa_server
+
+# 先添加私钥
+$ ssh-add ~/.ssh/id_rsa_server
+# 查看添加的私钥
+$ ssh-add -l
+# 使用 ssh-agent 代理,ssh-agent 会在 ssh-add 列表中寻找到合适的私钥
+$ ssh root@192.168.56.102
+
+$ vim ~/.ssh/config
+
+Host gateway # 主机别名,使用 ssh gateway 命令可以直接登录该主机
+Protocol 2 # SSH 协议版本
+HostName example.com # 主机地址,支持IP或域名
+Port 22 # SSH 服务端口号
+User ubuntu # 登录用户名,会被 ssh root@gateway 覆盖,除非使用 ssh gateway
+IdentityFile ~/.ssh/id_rsa # 使用的私钥文件
+
+例子:~/.ssh/config
+权限:
# 为.ssh目录设置权限
+chmod 600 ~/.ssh/config
+
+Host server
+ HostName 101.43.160.247
+ IdentityFile ~/.ssh/remote_rsa
+ User root
+ Port 22
+ ServerAliveInterval 60
+ ServerAliveCountMax 14400
+
+Host remote
+ HostName wgqing.com
+ IdentityFile ~/.ssh/remote_rsa
+ User root
+ Port 22
+ ServerAliveInterval 60
+ ServerAliveCountMax 14400
+
+
+++参考:
+
+https://zhuanlan.zhihu.com/p/257430478
+https://blog.csdn.net/baalhuo/article/details/78067621
mrwang@CodingdeMBP .ssh % ssh -T git@github.com
+The authenticity of host 'github.com (20.205.243.166)' can't be established.
+ECDSA key fingerprint is SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM.
+Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
+Warning: Permanently added 'github.com,20.205.243.166' (ECDSA) to the list of known hosts.
+git@github.com: Permission denied (publickey).
+mrwang@CodingdeMBP .ssh % ssh-add ~/.ssh/github
+Identity added: /Users/mrwang/.ssh/github (my mac for github codingriver@163.com)
+mrwang@CodingdeMBP .ssh % ssh -T git@github.com
+Hi codingriver! You've successfully authenticated, but GitHub does not provide shell access.
+
+新建ssh,将公钥添加至github后,使用ssh -T git@github.com验证报ECDSA key fingerprint is xxx 错误。
+是因为新建SSH后没有开启代理
为SSH key 启用SSH代理
+$ ssh-add ~/.ssh/id_rsa
+
+% git push
+git@github.com: Permission denied (publickey).
+fatal: Could not read from remote repository.
+
+Please make sure you have the correct access rights
+and the repository exists.
+
+修改配置文件 /etc/ssh/ssh_config (windows系统 C:\Program Files\Git\etc\ssh\ssh_config),添加如下一行
IdentityFile ~/.ssh/github_rsa # github_rsa是密钥文件的名字
+
+就可以了
+++这里可能是因为我修改密钥文件名字造成的,通配符匹配不到
+

++使用
+chmod 600 id_ed25519就好了,私钥文件权限问题
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+Permissions 0644 for '/Users/mrwang/.ssh/***' are too open.
+It is required that your private key files are NOT accessible by others.
+This private key will be ignored.
+Load key "/Users/mrwang/.ssh/***": bad permissions
+git@codeup.aliyun.com: Permission denied (publickey).
+fatal: Could not read from remote repository.
+
+Please make sure you have the correct access rights
+and the repository exists.
+
+
+++需要执行下
+ssh-agent bash启动代理
$ ssh -vT git@github.com
+OpenSSH_9.1p1, OpenSSL 1.1.1s 1 Nov 2022
+debug1: Reading configuration data /etc/ssh/ssh_config
+debug1: Connecting to github.com [::1] port 22.
+debug1: Connection established.
+debug1: identity file /c/Users/coding/.ssh/id_rsa type -1
+debug1: identity file /c/Users/coding/.ssh/id_rsa-cert type -1
+debug1: identity file /c/Users/coding/.ssh/id_ecdsa type -1
+debug1: identity file /c/Users/coding/.ssh/id_ecdsa-cert type -1
+debug1: identity file /c/Users/coding/.ssh/id_ecdsa_sk type -1
+debug1: identity file /c/Users/coding/.ssh/id_ecdsa_sk-cert type -1
+debug1: identity file /c/Users/coding/.ssh/id_ed25519 type -1
+debug1: identity file /c/Users/coding/.ssh/id_ed25519-cert type -1
+debug1: identity file /c/Users/coding/.ssh/id_ed25519_sk type -1
+debug1: identity file /c/Users/coding/.ssh/id_ed25519_sk-cert type -1
+debug1: identity file /c/Users/coding/.ssh/id_xmss type -1
+debug1: identity file /c/Users/coding/.ssh/id_xmss-cert type -1
+debug1: identity file /c/Users/coding/.ssh/id_dsa type -1
+debug1: identity file /c/Users/coding/.ssh/id_dsa-cert type -1
+debug1: Local version string SSH-2.0-OpenSSH_9.1
+kex_exchange_identification: Connection closed by remote host
+Connection closed by ::1 port 22
+
+
+$ ssh-add id_rsa_github
+Could not open a connection to your authentication agent.
+
+
+$ ssh-add -l
+Could not open a connection to your authentication agent.
+
+
+私钥的权限
+-rw-r--r--@ 1 mrwang staff 432 3 11 13:18 id_ed25519
+-rw-r--r--@ 1 mrwang staff 113 3 11 13:18 id_ed25519.pub
+-rw------- 1 mrwang staff 2602 12 19 22:37 id_rsa
+-rw-r--r--@ 1 mrwang staff 573 12 19 22:37 id_rsa.pub
+
+使用 chmod 600 id_ed25519就好了,私钥文件权限问题
-rw-r--r--@ 1 mrwang staff 627 2 12 18:52 config
+-rw-------@ 1 mrwang staff 432 3 11 13:18 id_ed25519
+-rw-r--r--@ 1 mrwang staff 113 3 11 13:18 id_ed25519.pub
+-rw------- 1 mrwang staff 2602 12 19 22:37 id_rsa
+-rw-r--r--@ 1 mrwang staff 573 12 19 22:37 id_rsa.pub
+
++ ++
如果你也在使用小乌龟客户端,你可能会遇到使用SSH协议的仓库不能成功push的问题,这时你需要确保tortoisegit设置的SSH客户端是Git提供的才对。
+

++需要的贴图:
+
+漫反射贴图;法线贴图;Metal,Roughness,SkinArea通道贴图CompMask(M,R,S);次表面散射LUT查找图;IBL Specular CubeMap环境图;IBL Diffuse 球谐数据(提前读取IBL Diffuse CubeMap数据);头发Aniso各向异性Noise贴图

+漫反射,镜面反射,环境反射,环境高光
组成: +- 直接光漫反射 + Lambert +- 直接光的镜面反射 + Blinn-Phong + 皮肤区域给一个很弱的高光(油光) +- 间接光的漫反射 + SH(球谐) +- 间接光的镜面反射 + CubeMap (Specular) +- LUT SSS次表面散射(皮肤处理) + LUT实现 +- 色彩矫正 + ToneMaping ACES
+Shader "Custom/Role_Standard"
+{
+ Properties
+ {
+ _MainTex ("Texture", 2D) = "white" {}
+ _CompMask("CompMask(R M)",2D) = "white"{}
+ _NormalMap("NormalMap",2D) = "bump"{}
+ _SpecShininess("Spec Shininess",Float)=10
+ _RoughnessAdjust("Roughness Adjust",Range(-1,1))=0
+ _MetalAdjust("Metal Adjust",Range(-1,1))=0
+ _SkinLUT("Skin LUT",2D)="white"{}
+ _CurveOffset("Curve Offset",Range(0,1))=1
+ _LutOffset("LutOffset",Range(0,1))=0
+ [Header(IBL Specular)]
+ _Tint("Tint",Color)=(1,1,1,1)
+ _Expose("Expose",Float)=1.0
+ _EnvMap("Env Cube",Cube)="white"{}
+ _Rotate("Rotate",Range(0,360))=0
+
+ [Toggle(_DIFFUSE_ON)]_DiffuseCheck("Diffuse Check",Float)=0
+ [Toggle(_SPECULAR_ON)]_Specular_Check("Specular Check",Float)=0
+ [Toggle(_SH_ON)]_SHCheck("SH Check",Float)=0
+ [Toggle(_IBL_ON)]_IBLCheck("IBL Check",Float)=0
+ [Toggle(_OILSKIN_ON)]_OilSkinCheck("Oil Skin Check",Float)=0
+
+ [Space(20)]
+ [Header(SH)]
+ [HideInInspector]custom_SHAr("Custom SHAr", Vector) = (0, 0, 0, 0)
+ [HideInInspector]custom_SHAg("Custom SHAg", Vector) = (0, 0, 0, 0)
+ [HideInInspector]custom_SHAb("Custom SHAb", Vector) = (0, 0, 0, 0)
+ [HideInInspector]custom_SHBr("Custom SHBr", Vector) = (0, 0, 0, 0)
+ [HideInInspector]custom_SHBg("Custom SHBg", Vector) = (0, 0, 0, 0)
+ [HideInInspector]custom_SHBb("Custom SHBb", Vector) = (0, 0, 0, 0)
+ [HideInInspector]custom_SHC("Custom SHC", Vector) = (0, 0, 0, 1)
+ }
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+
+ Pass
+ {
+ Tags{"LightMode" = "ForwardBase"}
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_fwdbase
+ #pragma shader_feature _DIFFUSE_ON
+ #pragma shader_feature _SPECULAR_ON
+ #pragma shader_feature _SH_ON
+ #pragma shader_feature _IBL_ON
+ #pragma shader_feature _OILSKIN_ON
+
+ #include "UnityCG.cginc"
+ #include"AutoLight.cginc"
+
+ struct appdata // appdata_full
+ {
+ float4 vertex : POSITION; //模型空间顶点坐标
+ half2 texcoord : TEXCOORD0; //第一套UV(模型最多只能有4套UV)
+ half3 normal : NORMAL; //顶点法线
+ half4 tangent : TANGENT; //顶点切线(模型导入Unity后自动计算得到)
+ };
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION; //输出裁剪空间下的顶点坐标数据,给光栅化使用,必须要写的数据
+ float2 uv : TEXCOORD0; //自定义数据体
+ half3 normal_dir : TEXCOORD1;
+ half3 tangent_dir : TEXCOORD2;
+ half3 binormal_dir : TEXCOORD3;
+ half3 pos_world : TEXCOORD4;
+ //最多可以写16个:TEXCOORD0 ~ TEXCOORD15。
+ LIGHTING_COORDS(5,6) ///SHADOW 第一步
+ };
+
+ sampler2D _MainTex;
+ sampler2D _CompMask;
+ float4 _MainTex_ST;
+ float4 _LightColor0;
+ float _Shininess;
+ float4 _AmbientColor;
+ float _SpecIntensity;
+ sampler2D _AOMap;
+ sampler2D _SpecMask;
+ sampler2D _NormalMap;
+ float _NormalIntensity;
+ sampler2D _ParallaxMap;
+ float _Parallax;
+ float _SpecShininess;
+ float _RoughnessAdjust;
+ float _MetalAdjust;
+ float4 _Tint;
+ float _Expose;
+ float _Rotate;
+ samplerCUBE _EnvMap;
+ float4 _EnvMap_HDR;
+ sampler2D _SkinLUT;
+ float _CurveOffset;
+ float _LutOffset;
+ half4 custom_SHAr;
+ half4 custom_SHAg;
+ half4 custom_SHAb;
+ half4 custom_SHBr;
+ half4 custom_SHBg;
+ half4 custom_SHBb;
+ half4 custom_SHC;
+
+
+ float3 ShadeSH(float3 normal_dir)
+ {
+ float4 normalForSH = float4(normal_dir, 1.0);
+ //SHEvalLinearL0L1
+ half3 x;
+ x.r = dot(custom_SHAr, normalForSH);
+ x.g = dot(custom_SHAg, normalForSH);
+ x.b = dot(custom_SHAb, normalForSH);
+
+ //SHEvalLinearL2
+ half3 x1, x2;
+ // 4 of the quadratic (L2) polynomials
+ half4 vB = normalForSH.xyzz * normalForSH.yzzx;
+ x1.r = dot(custom_SHBr, vB);
+ x1.g = dot(custom_SHBg, vB);
+ x1.b = dot(custom_SHBb, vB);
+
+ // Final (5th) quadratic (L2) polynomial
+ half vC = normalForSH.x*normalForSH.x - normalForSH.y*normalForSH.y;
+ x2 = custom_SHC.rgb * vC;
+
+ float3 sh = max(float3(0.0, 0.0, 0.0), (x + x1 + x2));
+ sh = pow(sh, 1.0 / 2.2);
+ return sh;
+ }
+
+ float3 ACESFilm(float3 x)
+ {
+ float a = 2.51f;
+ float b = 0.03f;
+ float c = 2.43f;
+ float d = 0.59f;
+ float e = 0.14f;
+ return saturate((x*(a*x + b)) / (x*(c*x + d) + e));
+ };
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.pos = UnityObjectToClipPos(v.vertex);
+ o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
+ o.pos_world=mul(unity_ObjectToWorld,v.vertex).xyz;
+ o.normal_dir=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
+ o.tangent_dir=normalize(mul((float3x3)unity_ObjectToWorld,v.tangent.xyz));
+ o.binormal_dir=normalize(cross(o.normal_dir, o.tangent_dir))*v.tangent.w;
+ TRANSFER_VERTEX_TO_FRAGMENT(o); ///SHADOW 第二步
+ return o;
+ }
+
+ half4 frag (v2f i) : SV_Target
+ {
+ // Base Color
+ half4 base_color=tex2D(_MainTex,i.uv);
+ base_color=pow(base_color,2.2);// gamma转Liner空间
+ half3 comp_mask=tex2D(_CompMask,i.uv);
+ half skin_area=1.0-comp_mask.b;
+ half roughness=saturate(comp_mask.r+_RoughnessAdjust);
+ half metal=saturate(comp_mask.g+ _MetalAdjust);
+ half3 spec_color=lerp(0.04,base_color.rgb,metal);
+ base_color=base_color*(1-metal);
+
+ half3 pos_world=i.pos_world;
+ half3 normal_dir=normalize(i.normal_dir);
+ half3 tangent_dir=normalize(i.tangent_dir);
+ half3 binormal_dir=normalize(i.binormal_dir);
+ half3 view_dir=normalize(_WorldSpaceCameraPos.xyz-pos_world);
+ float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);
+
+
+ // Noraml 法线
+ half4 normalmap=tex2D(_NormalMap,i.uv);
+ half3 normal_data=UnpackNormal(normalmap);
+ // normal_data.xy = normal_data.xy * _NormalIntensity;
+ normal_dir=normalize(mul(normal_data,TBN));
+ //normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
+
+ // Light Info
+ half3 light_dir=normalize(_WorldSpaceLightPos0.xyz);
+ half4 light_color=_LightColor0.rgba;
+ half atten=LIGHT_ATTENUATION(i);
+
+
+
+
+
+ // Direct Diffuse 漫反射,Lambert
+ half NdotL=max(0.0,dot(normal_dir,light_dir));
+ half3 diffuse= base_color.rgb*NdotL*light_color.rgb*atten;
+ half2 uv_lut=half2(NdotL*atten+_LutOffset,_CurveOffset);
+ half3 lut_color_gamma=tex2D(_SkinLUT,uv_lut);
+ half3 lut_color=pow(lut_color_gamma,2.2);
+ half3 sss_diffuse=lut_color*base_color*_LightColor0.rgb;
+ #ifdef _DIFFUSE_ON
+ half3 direct_diffuse=lerp(diffuse,sss_diffuse,skin_area);
+ #else
+ half3 direct_diffuse=half3(0,0,0);
+ #endif
+
+ // Direct Specular 镜面反射(高光) Blinn-Phong
+ half smoothness=1.0-roughness;
+ half3 half_dir=normalize(view_dir+light_dir);
+ half NdotH=max(0.0,dot(normal_dir,half_dir));
+ half shininess=lerp(1,_SpecShininess,smoothness);
+ half spec=pow(NdotH,shininess);
+ #ifdef _OILSKIN_ON
+ half3 spec_skin_color=lerp(spec_color,0.1,skin_area); //皮肤油光
+ #else
+ half3 spec_skin_color=spec_color;
+ #endif
+
+ #ifdef _SPECULAR_ON
+ half3 direct_specular=spec*spec_skin_color*light_color.rgb*atten;
+ #else
+ half3 direct_specular=half3(0,0,0);
+ #endif
+
+ //Indirect Diffuse 间接光的漫反射
+ float half_lambert=dot(normal_dir,light_dir)*0.5+0.5;
+ #ifdef _SH_ON
+ half3 env_diffuse= ShadeSH(normal_dir)*base_color*half_lambert;
+ #else
+ half3 env_diffuse=half3(0,0,0);
+ #endif
+
+ // Indirect Specular 间接光的镜面反射
+ half3 rViewDir=reflect(-view_dir,normal_dir);
+ float rad=_Rotate*UNITY_PI/180;
+ float2x2 Rotation=float2x2(cos(rad),sin(rad),-sin(rad),cos(rad));
+ rViewDir.xz=mul(Rotation,rViewDir.xz);
+
+ roughness=roughness*(1.7-0.7*roughness);
+ float mip_level=(roughness)*6.0;
+ half4 color_cube=texCUBElod(_EnvMap,float4(rViewDir,mip_level));
+ half3 env_specular=DecodeHDR(color_cube,_EnvMap_HDR)*_Expose*spec_color*half_lambert*(1-skin_area);
+ #ifdef _IBL_ON
+ #else
+ env_specular=half3(0,0,0);
+ #endif
+
+
+ half3 final_color = (direct_diffuse + direct_specular + env_diffuse+env_specular);
+ half3 tone_color = ACESFilm(final_color);
+ tone_color = pow(tone_color, 1.0 / 2.2);
+ return half4(tone_color,1.0);
+ // return half4(final_color,1.0);
+ }
+ ENDCG
+ }
+
+ }
+ Fallback "Diffuse" ///SHADOW 第四步
+}
+
+
+
+++头发处理:Kajiya-Kay各向异性头发(Kajiya-Kay头发)
+
组成: +- 直接光的漫反射 + Lambert +- 直接光的镜面反射,双层高光 + Kajiya-Kay各向异性头发(Kajiya-Kay头发) +- 间接光的镜面反射 + CubeMap (Specular)
+Shader "Custom/Role_Hair"
+{
+ Properties
+ {
+ _MainTex ("Texture", 2D) = "white" {}
+ _BaseColor("Base Color",Color)=(1,1,1,1)
+ _NormalMap("NormalMap",2D) = "bump"{}
+ _RoughnessAdjust("Roughness Adjust",Range(0,1))=0
+
+ [Header(Specular)]
+ _AnisoMap("Aniso Map",2D)="gray"{}
+ _SpecColor1("Spec Color 1",Color)=(1,1,1,1)
+ _SpecShininess1("Spec Shininess 1",Range(0,1))=0.1
+ _SpecNoise1("Spec Noise 1",float)=1
+ _SpecOffset1("Spec Offset 1",float)=0
+ _SpecColor2("Spec Color 2",Color)=(1,1,1,1)
+ _SpecShininess2("Spec Shininess 2",Range(0,1))=0.1
+ _SpecNoise2("Spec Noise 2",float)=1
+ _SpecOffset2("Spec Offset 2",float)=0
+
+ [Header(IBL Specular)]
+ _Tint("Tint",Color)=(1,1,1,1)
+ _Expose("Expose",Float)=1.0
+ _EnvMap("Env Cube",Cube)="white"{}
+ _Rotate("Rotate",Range(0,360))=0
+
+ [Toggle(_DIFFUSE_ON)]_DiffuseCheck("Diffuse Check",Float)=0
+ [Toggle(_SPECULAR_ON)]_Specular_Check("Specular Check",Float)=0
+ // [Toggle(_SH_ON)]_SHCheck("SH Check",Float)=0
+ [Toggle(_IBL_ON)]_IBLCheck("IBL Check",Float)=0
+ }
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+
+ Pass
+ {
+ Tags{"LightMode" = "ForwardBase"}
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_fwdbase
+ #pragma shader_feature _DIFFUSE_ON
+ #pragma shader_feature _SPECULAR_ON
+ #pragma shader_feature _SH_ON
+ #pragma shader_feature _IBL_ON
+ #pragma shader_feature _OILSKIN_ON
+
+ #include "UnityCG.cginc"
+ #include"AutoLight.cginc"
+
+ struct appdata // appdata_full
+ {
+ float4 vertex : POSITION; //模型空间顶点坐标
+ half2 texcoord : TEXCOORD0; //第一套UV(模型最多只能有4套UV)
+ half3 normal : NORMAL; //顶点法线
+ half4 tangent : TANGENT; //顶点切线(模型导入Unity后自动计算得到)
+ };
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION; //输出裁剪空间下的顶点坐标数据,给光栅化使用,必须要写的数据
+ float2 uv : TEXCOORD0; //自定义数据体
+ half3 normal_dir : TEXCOORD1;
+ half3 tangent_dir : TEXCOORD2;
+ half3 binormal_dir : TEXCOORD3;
+ half3 pos_world : TEXCOORD4;
+ //最多可以写16个:TEXCOORD0 ~ TEXCOORD15。
+ LIGHTING_COORDS(5,6) ///SHADOW 第一步
+ };
+
+ sampler2D _MainTex;
+ sampler2D _CompMask;
+ float4 _MainTex_ST;
+ float4 _LightColor0;
+ float _Shininess;
+ float4 _AmbientColor;
+ float _SpecIntensity;
+ sampler2D _AOMap;
+ sampler2D _SpecMask;
+ sampler2D _NormalMap;
+ float _NormalIntensity;
+ sampler2D _ParallaxMap;
+ float _Parallax;
+ float _SpecShininess;
+ float _RoughnessAdjust;
+ float _MetalAdjust;
+ float4 _Tint;
+ float _Expose;
+ float _Rotate;
+ samplerCUBE _EnvMap;
+ float4 _EnvMap_HDR;
+ sampler2D _SkinLUT;
+ float _CurveOffset;
+ float _LutOffset;
+ half4 custom_SHAr;
+ half4 custom_SHAg;
+ half4 custom_SHAb;
+ half4 custom_SHBr;
+ half4 custom_SHBg;
+ half4 custom_SHBb;
+ half4 custom_SHC;
+ float _BaseColor;
+ sampler2D _AnisoMap;
+ float4 _AnisoMap_ST;
+
+
+ float4 _SpecColor1;
+ float _SpecShininess1;
+ float _SpecNoise1;
+ float _SpecOffset1;
+ float4 _SpecColor2;
+ float _SpecShininess2;
+ float _SpecNoise2;
+ float _SpecOffset2;
+
+ float3 ACESFilm(float3 x)
+ {
+ float a = 2.51f;
+ float b = 0.03f;
+ float c = 2.43f;
+ float d = 0.59f;
+ float e = 0.14f;
+ return saturate((x*(a*x + b)) / (x*(c*x + d) + e));
+ };
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.pos = UnityObjectToClipPos(v.vertex);
+ o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
+ o.pos_world=mul(unity_ObjectToWorld,v.vertex).xyz;
+ o.normal_dir=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
+ o.tangent_dir=normalize(mul((float3x3)unity_ObjectToWorld,v.tangent.xyz));
+ o.binormal_dir=normalize(cross(o.normal_dir, o.tangent_dir))*v.tangent.w;
+ TRANSFER_VERTEX_TO_FRAGMENT(o); ///SHADOW 第二步
+ return o;
+ }
+
+ half4 frag (v2f i) : SV_Target
+ {
+ // Base Color
+ half4 base_color=tex2D(_MainTex,i.uv);
+ base_color=pow(base_color,2.2);// gamma转Liner空间
+ half4 spec_color=base_color;
+
+ half roughness=saturate(_RoughnessAdjust);
+
+ half3 pos_world=i.pos_world;
+ half3 normal_dir=normalize(i.normal_dir);
+ half3 tangent_dir=normalize(i.tangent_dir);
+ half3 binormal_dir=normalize(i.binormal_dir);
+ half3 view_dir=normalize(_WorldSpaceCameraPos.xyz-pos_world);
+ float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);
+
+
+ // Noraml 法线
+ half4 normalmap=tex2D(_NormalMap,i.uv);
+ half3 normal_data=UnpackNormal(normalmap);
+ // normal_data.xy = normal_data.xy * _NormalIntensity;
+ normal_dir=normalize(mul(normal_data,TBN));
+ //normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
+
+ // Light Info
+ half3 light_dir=normalize(_WorldSpaceLightPos0.xyz);
+ half4 light_color=_LightColor0.rgba;
+ half atten=LIGHT_ATTENUATION(i);
+
+
+
+
+
+ // Direct Diffuse 漫反射,Lambert
+
+ half NdotL=max(0.0,dot(normal_dir,light_dir));
+ half half_lambert=NdotL*0.5+0.5;
+ half3 direct_diffuse=base_color;
+
+ // Direct Specular 镜面反射(高光) Blinn-Phong
+ float aniso_noise=tex2D(_AnisoMap,i.uv*_AnisoMap_ST.xy+_AnisoMap_ST.zw).r-0.5;
+
+ half3 half_dir=normalize(view_dir+light_dir);
+ half NdotH=dot(normal_dir,half_dir);
+ half TdotH=dot(tangent_dir,half_dir);
+
+ half NdotV=max(0.0,dot(normal_dir,view_dir));
+ float aniso_atten=saturate(sqrt(max(0.0,half_lambert/NdotV)))*atten;
+
+ // spec1
+ float3 spec_color1=_SpecColor1.rgb+base_color;
+ half3 b_offset1=normal_dir*(_SpecOffset1+_SpecNoise1*aniso_noise);
+ half3 binormal_dir1=normalize(binormal_dir+b_offset1);
+ half BdotH1=dot(binormal_dir1,half_dir)/_SpecShininess1;
+ float3 spec_term1=exp(-(TdotH*TdotH+BdotH1*BdotH1)/(1.0+NdotH));
+ float3 final_spec1=spec_term1*aniso_atten*spec_color1*light_color.rgb;
+
+ //spec2
+ float3 spec_color2=_SpecColor2.rgb+base_color;
+ half3 b_offset2=normal_dir*(_SpecOffset2+_SpecNoise2*aniso_noise);
+ half3 binormal_dir2=normalize(binormal_dir+b_offset2);
+ half BdotH2=dot(binormal_dir2,half_dir)/_SpecShininess2;
+ float3 spec_term2=exp(-(TdotH*TdotH+BdotH2*BdotH2)/(1.0+NdotH));
+ float3 final_spec2=spec_term2*aniso_atten*spec_color2*light_color.rgb;
+
+ // Indirect Specular 间接光的镜面反射
+ half3 rViewDir=reflect(-view_dir,normal_dir);
+ float rad=_Rotate*UNITY_PI/180;
+ float2x2 Rotation=float2x2(cos(rad),sin(rad),-sin(rad),cos(rad));
+ rViewDir.xz=mul(Rotation,rViewDir.xz);
+
+ roughness=roughness*(1.7-0.7*roughness);
+ float mip_level=(roughness)*6.0;
+ half4 color_cube=texCUBElod(_EnvMap,float4(rViewDir,mip_level));
+ float3 color_ibl=DecodeHDR(color_cube,_EnvMap_HDR);
+ half3 env_specular=color_ibl*_Expose*half_lambert*aniso_noise;
+ #ifdef _IBL_ON
+ #else
+ env_specular=half3(0,0,0);
+ #endif
+ #ifdef _DIFFUSE_ON
+ #else
+ direct_diffuse=half3(0,0,0);
+ #endif
+ #ifdef _SPECULAR_ON
+ #else
+ final_spec1=half3(0,0,0);
+ final_spec2=half3(0,0,0);
+ #endif
+
+ half3 final_color = (final_spec1 + final_spec2+direct_diffuse+env_specular);
+ // half3 final_color = (direct_diffuse + direct_specular+env_specular);
+ half3 tone_color = ACESFilm(final_color);
+ tone_color = pow(tone_color, 1.0 / 2.2);
+ return half4(tone_color,1.0);
+ // return half4(final_color,1.0);
+ }
+ ENDCG
+ }
+
+ }
+ Fallback "Diffuse" ///SHADOW 第四步
+}
+
+
+
+漫反射,透射光(高光),环境光
透射光实现:根据光反方向(增加法线的扭曲,表示玉龙表面的粗糙情况影响光线的方向)和视线方向夹角来判定透射光的强度(假设从玉龙后面有个手电筒和人眼的夹角,越小表示人眼看到的光线越多,光越强),然后通过厚度图来模拟玉龙的通透性
+Shader "CS03/Dragon"
+{
+ Properties
+ {
+ _DiffuseColor ("Diffuse Color", Color) = (1,1,1,1)
+ _Opacity("Opacity",Float)=1
+ _AddColor ("Add Color", Color) = (1,1,1,1)
+ _Distort("Distort",Range(0,1))=0
+ _Power("Power",Float)=2
+ _Scale("Scale",Float)=1
+ _ThicknessMap ("Thickness Map", 2D) = "black" {}
+ _CubeMap ("Cube Map", CUBE) = "white" {}
+ _EnvRotate("Env Rotate",Range(0,360))=0
+ }
+ SubShader
+ {
+ Tags { "RenderType"="Opaque" }
+ LOD 100
+
+ Pass
+ {
+ Tags{"LightMode" = "ForwardBase"}
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_fwdbase
+ #include "UnityCG.cginc"
+ #include"AutoLight.cginc"
+
+ struct appdata // appdata_full
+ {
+ float4 vertex : POSITION; //模型空间顶点坐标
+ half2 texcoord : TEXCOORD0; //第一套UV(模型最多只能有4套UV)
+ half2 texcoord1 : TEXCOORD1; //第二套UV
+ half2 texcoord2 : TEXCOORD2; //第三套UV
+ half2 texcoord3 : TEXCOORD3; //第四套UV,模型最多只能有4套UV
+ half4 color : COLOR; //顶点颜色
+ half3 normal : NORMAL; //顶点法线
+ half4 tangent : TANGENT; //顶点切线(模型导入Unity后自动计算得到)
+ };
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION; //输出裁剪空间下的顶点坐标数据,给光栅化使用,必须要写的数据
+ float2 uv : TEXCOORD0; //自定义数据体
+ half3 normal_dir : TEXCOORD1;
+ half3 tangent_dir : TEXCOORD2;
+ half3 binormal_dir : TEXCOORD3;
+ half3 pos_world : TEXCOORD4;
+ //最多可以写16个:TEXCOORD0 ~ TEXCOORD15。
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+ float4 _LightColor0;
+ sampler2D _NormalMap;
+ float _Distort;
+ float _Power;
+ float _Scale;
+ sampler2D _ThicknessMap;
+ samplerCUBE _CubeMap;
+ float4 _CubeMap_HDR;
+ float _EnvRotate;
+ float4 _DiffuseColor;
+ float4 _AddColor;
+ float _Opacity;
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.pos = UnityObjectToClipPos(v.vertex);
+ o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
+ o.pos_world=mul(unity_ObjectToWorld,v.vertex).xyz;
+ o.normal_dir=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
+ o.tangent_dir=normalize(mul((float3x3)unity_ObjectToWorld,v.tangent.xyz));
+ o.binormal_dir=normalize(cross(o.normal_dir, o.tangent_dir))*v.tangent.w;
+ return o;
+ }
+
+ half4 frag (v2f i) : SV_Target
+ {
+ half3 pos_world=i.pos_world;
+ half3 normal_dir=normalize(i.normal_dir);
+ half3 tangent_dir=normalize(i.tangent_dir);
+ half3 binormal_dir=normalize(i.binormal_dir);
+ half3 view_dir=normalize(_WorldSpaceCameraPos.xyz-pos_world);
+ float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);
+ // Light
+ half3 light_dir=normalize(_WorldSpaceLightPos0.xyz);
+ half4 light_color=_LightColor0.rgba;
+ half atten=1.0;
+
+ //漫反射
+ float NdotL=max(0.0,dot(normal_dir,light_dir));
+ float3 color_diffuse=NdotL*_DiffuseColor.rgb*light_color.rgb;
+ float sky_light=(dot(normal_dir,float3(0,1,0))+1.0)*0.5;
+ float3 color_sky=sky_light*_DiffuseColor.rgb;
+ float3 final_diffuse=color_diffuse+_AddColor.rgb+color_sky*_Opacity;
+
+ // 透射光
+ float3 back_dir=-normalize(light_dir+normal_dir*_Distort);
+ float VdotB=max(0.0,dot(view_dir,back_dir));
+ float backlight_term=max(0.0,pow(VdotB,_Power))*_Scale;
+ float thickness=1.0-tex2D(_ThicknessMap,i.uv).r;
+ half3 back_color=backlight_term*light_color*thickness;
+
+ // 光泽反射
+ float3 rView_dir=reflect(-view_dir,normal_dir);
+ float drag=_EnvRotate*UNITY_PI/180.0;
+ float2x2 Roration=float2x2(cos(drag),sin(drag),-sin(drag),cos(drag));
+ rView_dir.xz=mul(Roration,rView_dir.xz);
+ half4 color_cube=texCUBE(_CubeMap,rView_dir);
+ float3 color_env=DecodeHDR(color_cube,_CubeMap_HDR);
+ half fresnel=1.0-max(0.0,dot(normal_dir,view_dir));
+ float3 final_env=color_env*fresnel;
+
+ float3 color_final=final_diffuse+final_env+ back_color;
+
+ // return half4(thickness.xxx,1.0);
+ return half4(color_final,1.0);
+ }
+ ENDCG
+ }
+ Pass
+ {
+ Tags{"LightMode" = "ForwardAdd"}
+ Blend One One
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_fwdadd
+ #include "UnityCG.cginc"
+ #include"AutoLight.cginc"
+
+ struct appdata // appdata_full
+ {
+ float4 vertex : POSITION; //模型空间顶点坐标
+ half2 texcoord : TEXCOORD0; //第一套UV(模型最多只能有4套UV)
+ half2 texcoord1 : TEXCOORD1; //第二套UV
+ half2 texcoord2 : TEXCOORD2; //第三套UV
+ half2 texcoord3 : TEXCOORD3; //第四套UV,模型最多只能有4套UV
+ half4 color : COLOR; //顶点颜色
+ half3 normal : NORMAL; //顶点法线
+ half4 tangent : TANGENT; //顶点切线(模型导入Unity后自动计算得到)
+ };
+
+ struct v2f
+ {
+ float4 pos : SV_POSITION; //输出裁剪空间下的顶点坐标数据,给光栅化使用,必须要写的数据
+ float2 uv : TEXCOORD0; //自定义数据体
+ half3 normal_dir : TEXCOORD1;
+ half3 tangent_dir : TEXCOORD2;
+ half3 binormal_dir : TEXCOORD3;
+ half3 pos_world : TEXCOORD4;
+ //最多可以写16个:TEXCOORD0 ~ TEXCOORD15。
+ LIGHTING_COORDS(5,6)
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+ float4 _LightColor0;
+ sampler2D _NormalMap;
+ float _Distort;
+ float _Power;
+ float _Scale;
+ sampler2D _ThicknessMap;
+ samplerCUBE _CubeMap;
+ float4 _CubeMap_HDR;
+ float _EnvRotate;
+ float4 _DiffuseColor;
+ float4 _AddColor;
+ float _Opacity;
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.pos = UnityObjectToClipPos(v.vertex);
+ o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
+ o.pos_world=mul(unity_ObjectToWorld,v.vertex).xyz;
+ o.normal_dir=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
+ o.tangent_dir=normalize(mul((float3x3)unity_ObjectToWorld,v.tangent.xyz));
+ o.binormal_dir=normalize(cross(o.normal_dir, o.tangent_dir))*v.tangent.w;
+ TRANSFER_VERTEX_TO_FRAGMENT(o);
+ return o;
+ }
+
+ half4 frag (v2f i) : SV_Target
+ {
+ half shadow = SHADOW_ATTENUATION(i); ///SHADOW 第三步
+
+ half3 pos_world=i.pos_world;
+ half3 normal_dir=normalize(i.normal_dir);
+ half3 tangent_dir=normalize(i.tangent_dir);
+ half3 binormal_dir=normalize(i.binormal_dir);
+ half3 view_dir=normalize(_WorldSpaceCameraPos.xyz-pos_world);
+ float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);
+ // Light
+ half3 light_dir=normalize(_WorldSpaceLightPos0.xyz);
+ half4 light_color=_LightColor0.rgba;
+ half atten=LIGHT_ATTENUATION(i);
+
+ // 透射光
+ float3 back_dir=-normalize(light_dir+normal_dir*_Distort);
+ float VdotB=max(0.0,dot(view_dir,back_dir));
+ float backlight_term=max(0.0,pow(VdotB,_Power))*_Scale;
+ float thickness=1.0-tex2D(_ThicknessMap,i.uv).r;
+ half3 back_color=backlight_term*light_color*thickness;
+
+ float3 color_final=back_color;
+ return half4(color_final,1.0);
+ }
+ ENDCG
+ }
+ }
+ Fallback "Diffuse" ///SHADOW 第四步
+}
+
+
+
+
双Pass渲染,一个背面渲染(cubemap的反射和折射),一个前面渲染(折射和菲涅尔)
+ASE
+// Made with Amplify Shader Editor
+// Available at the Unity Asset Store - http://u3d.as/y3X
+Shader "CS07/Diamond"
+{
+ Properties
+ {
+ _ColorA("Color A", Color) = (0.02491992,0.09269338,0.754717,0)
+ _RefractTex("RefractTex", CUBE) = "white" {}
+ _ReflectTex("ReflectTex", CUBE) = "white" {}
+ _ReflectIntensity("ReflectIntensity", Float) = 1
+ _RefractIntensity("RefractIntensity", Float) = 1
+ _RimPower("RimPower", Float) = 5
+ _RimBias("RimBias", Float) = 0
+ _RimScale("RimScale", Float) = 0
+ _RimColor("RimColor", Color) = (0,0,0,0)
+
+ }
+
+ SubShader
+ {
+
+
+ Tags { "RenderType"="Opaque" "Queue"="Geometry" }
+ LOD 100
+
+
+
+
+ Pass
+ {
+ Name "Unlit"
+ Tags { "LightMode"="ForwardBase" }
+
+ CGINCLUDE
+ #pragma target 3.0
+ ENDCG
+ Blend Off
+ AlphaToMask Off
+ Cull Front
+ ColorMask RGBA
+ ZWrite On
+ ZTest LEqual
+ Offset 0 , 0
+
+
+ CGPROGRAM
+
+
+
+ #ifndef UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
+ //only defining to not throw compilation error over Unity 5.5
+ #define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)
+ #endif
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_instancing
+ #include "UnityCG.cginc"
+ #define ASE_NEEDS_FRAG_WORLD_POSITION
+
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float4 color : COLOR;
+ float3 ase_normal : NORMAL;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ };
+
+ struct v2f
+ {
+ float4 vertex : SV_POSITION;
+ #ifdef ASE_NEEDS_FRAG_WORLD_POSITION
+ float3 worldPos : TEXCOORD0;
+ #endif
+ float4 ase_texcoord1 : TEXCOORD1;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ UNITY_VERTEX_OUTPUT_STEREO
+ };
+
+ uniform float4 _ColorA;
+ uniform samplerCUBE _RefractTex;
+ uniform samplerCUBE _ReflectTex;
+ uniform float _RefractIntensity;
+
+
+ v2f vert ( appdata v )
+ {
+ v2f o;
+ UNITY_SETUP_INSTANCE_ID(v);
+ UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
+ UNITY_TRANSFER_INSTANCE_ID(v, o);
+
+ float3 ase_worldNormal = UnityObjectToWorldNormal(v.ase_normal);
+ o.ase_texcoord1.xyz = ase_worldNormal;
+
+
+ //setting value to unused interpolator channels and avoid initialization warnings
+ o.ase_texcoord1.w = 0;
+ float3 vertexValue = float3(0, 0, 0);
+ #if ASE_ABSOLUTE_VERTEX_POS
+ vertexValue = v.vertex.xyz;
+ #endif
+ vertexValue = vertexValue;
+ #if ASE_ABSOLUTE_VERTEX_POS
+ v.vertex.xyz = vertexValue;
+ #else
+ v.vertex.xyz += vertexValue;
+ #endif
+ o.vertex = UnityObjectToClipPos(v.vertex);
+
+ #ifdef ASE_NEEDS_FRAG_WORLD_POSITION
+ o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
+ #endif
+ return o;
+ }
+
+ fixed4 frag (v2f i ) : SV_Target
+ {
+ UNITY_SETUP_INSTANCE_ID(i);
+ UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
+ fixed4 finalColor;
+ #ifdef ASE_NEEDS_FRAG_WORLD_POSITION
+ float3 WorldPosition = i.worldPos;
+ #endif
+ float3 ase_worldNormal = i.ase_texcoord1.xyz;
+ float3 ase_worldViewDir = UnityWorldSpaceViewDir(WorldPosition);
+ ase_worldViewDir = normalize(ase_worldViewDir);
+ float3 ase_worldReflection = reflect(-ase_worldViewDir, ase_worldNormal);
+ float4 texCUBENode7 = texCUBE( _ReflectTex, ase_worldReflection );
+ float4 temp_output_12_0 = ( _ColorA * texCUBE( _RefractTex, ase_worldReflection ) * texCUBENode7 * _RefractIntensity );
+
+
+ finalColor = temp_output_12_0;
+ return finalColor;
+ }
+ ENDCG
+ }
+
+ Pass
+ {
+ Name "Second"
+ Tags { "LightMode"="ForwardBase" }
+
+ CGINCLUDE
+ #pragma target 3.0
+ ENDCG
+ Blend One One
+ AlphaToMask Off
+ Cull Back
+ ColorMask RGBA
+ ZWrite On
+ ZTest LEqual
+ Offset 0 , 0
+
+
+ CGPROGRAM
+
+
+
+ #ifndef UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
+ //only defining to not throw compilation error over Unity 5.5
+ #define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)
+ #endif
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma multi_compile_instancing
+ #include "UnityCG.cginc"
+ #define ASE_NEEDS_FRAG_WORLD_POSITION
+
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float4 color : COLOR;
+ float3 ase_normal : NORMAL;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ };
+
+ struct v2f
+ {
+ float4 vertex : SV_POSITION;
+ #ifdef ASE_NEEDS_FRAG_WORLD_POSITION
+ float3 worldPos : TEXCOORD0;
+ #endif
+ float4 ase_texcoord1 : TEXCOORD1;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ UNITY_VERTEX_OUTPUT_STEREO
+ };
+
+ uniform float4 _ColorA;
+ uniform samplerCUBE _RefractTex;
+ uniform samplerCUBE _ReflectTex;
+ uniform float _RefractIntensity;
+ uniform float _ReflectIntensity;
+ uniform float _RimPower;
+ uniform float _RimScale;
+ uniform float _RimBias;
+ uniform float4 _RimColor;
+
+
+ v2f vert ( appdata v )
+ {
+ v2f o;
+ UNITY_SETUP_INSTANCE_ID(v);
+ UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
+ UNITY_TRANSFER_INSTANCE_ID(v, o);
+
+ float3 ase_worldNormal = UnityObjectToWorldNormal(v.ase_normal);
+ o.ase_texcoord1.xyz = ase_worldNormal;
+
+
+ //setting value to unused interpolator channels and avoid initialization warnings
+ o.ase_texcoord1.w = 0;
+ float3 vertexValue = float3(0, 0, 0);
+ #if ASE_ABSOLUTE_VERTEX_POS
+ vertexValue = v.vertex.xyz;
+ #endif
+ vertexValue = vertexValue;
+ #if ASE_ABSOLUTE_VERTEX_POS
+ v.vertex.xyz = vertexValue;
+ #else
+ v.vertex.xyz += vertexValue;
+ #endif
+ o.vertex = UnityObjectToClipPos(v.vertex);
+
+ #ifdef ASE_NEEDS_FRAG_WORLD_POSITION
+ o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
+ #endif
+ return o;
+ }
+
+ fixed4 frag (v2f i ) : SV_Target
+ {
+ UNITY_SETUP_INSTANCE_ID(i);
+ UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
+ fixed4 finalColor;
+ #ifdef ASE_NEEDS_FRAG_WORLD_POSITION
+ float3 WorldPosition = i.worldPos;
+ #endif
+ float3 ase_worldNormal = i.ase_texcoord1.xyz;
+ float3 ase_worldViewDir = UnityWorldSpaceViewDir(WorldPosition);
+ ase_worldViewDir = normalize(ase_worldViewDir);
+ float3 ase_worldReflection = reflect(-ase_worldViewDir, ase_worldNormal);
+ float4 texCUBENode7 = texCUBE( _ReflectTex, ase_worldReflection );
+ float4 temp_output_12_0 = ( _ColorA * texCUBE( _RefractTex, ase_worldReflection ) * texCUBENode7 * _RefractIntensity );
+ float dotResult21 = dot( ase_worldNormal , ase_worldViewDir );
+ float clampResult23 = clamp( dotResult21 , 0.0 , 1.0 );
+ float temp_output_24_0 = ( 1.0 - clampResult23 );
+ float4 temp_output_16_0 = ( temp_output_12_0 + ( texCUBENode7 * _ReflectIntensity * temp_output_24_0 ) );
+ float saferPower25 = max( temp_output_24_0 , 0.0001 );
+ float temp_output_30_0 = ( ( max( pow( saferPower25 , _RimPower ) , 0.0 ) * _RimScale ) + _RimBias );
+
+
+ finalColor = ( temp_output_16_0 + ( temp_output_16_0 * temp_output_30_0 * ( temp_output_30_0 * _RimColor ) ) );
+ return finalColor;
+ }
+ ENDCG
+ }
+
+ }
+ CustomEditor "ASEMaterialInspector"
+
+
+}
+/*ASEBEGIN
+Version=18500
+2500;488;1639;969;571.4478;525.8101;1.694416;True;False
+Node;AmplifyShaderEditor.CommentaryNode;40;-1788.975,781.5402;Inherit;False;2012.625;729.3613;Fresnel;13;22;19;21;23;26;24;25;27;29;28;31;30;32;;1,1,1,1;0;0
+Node;AmplifyShaderEditor.WorldNormalVector;19;-1738.975,831.5402;Inherit;False;False;1;0;FLOAT3;0,0,1;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
+Node;AmplifyShaderEditor.ViewDirInputsCoordNode;22;-1716.198,1112.462;Inherit;False;World;False;0;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
+Node;AmplifyShaderEditor.DotProductOpNode;21;-1330.5,1006.167;Inherit;False;2;0;FLOAT3;0,0,0;False;1;FLOAT3;0,0,0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.ClampOpNode;23;-1181.688,992.5009;Inherit;False;3;0;FLOAT;0;False;1;FLOAT;0;False;2;FLOAT;1;False;1;FLOAT;0
+Node;AmplifyShaderEditor.OneMinusNode;24;-1000.987,1000.093;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.RangedFloatNode;26;-1107.281,1180.793;Inherit;False;Property;_RimPower;RimPower;5;0;Create;True;0;0;False;0;False;5;10;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.PowerNode;25;-809.6565,994.0192;Inherit;False;True;2;0;FLOAT;0;False;1;FLOAT;1;False;1;FLOAT;0
+Node;AmplifyShaderEditor.WorldReflectionVector;13;-1153.126,32.63672;Inherit;False;False;1;0;FLOAT3;0,0,0;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
+Node;AmplifyShaderEditor.RangedFloatNode;29;-647.2493,1140.901;Inherit;False;Property;_RimScale;RimScale;7;0;Create;True;0;0;False;0;False;0;5;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.SimpleMaxOpNode;27;-617.2493,995.9019;Inherit;False;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.SamplerNode;7;-562.063,172.0891;Inherit;True;Property;_ReflectTex;ReflectTex;2;0;Create;True;0;0;False;0;False;-1;None;7ecb52b18a453124283cee921dc388cf;True;0;False;white;LockedToCube;False;Object;-1;Auto;Cube;8;0;SAMPLERCUBE;;False;1;FLOAT3;0,0,0;False;2;FLOAT;0;False;3;FLOAT3;0,0,0;False;4;FLOAT3;0,0,0;False;5;FLOAT;1;False;6;FLOAT;0;False;7;SAMPLERSTATE;;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.ColorNode;2;-506.5,-301.5;Inherit;False;Property;_ColorA;Color A;0;0;Create;True;0;0;False;0;False;0.02491992,0.09269338,0.754717,0;0.5754717,0.5754717,0.5754717,0;True;0;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.RangedFloatNode;17;-514.6671,98.24844;Inherit;False;Property;_RefractIntensity;RefractIntensity;4;0;Create;True;0;0;False;0;False;1;5;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.SamplerNode;6;-570.063,-90.91089;Inherit;True;Property;_RefractTex;RefractTex;1;0;Create;True;0;0;False;0;False;-1;None;e9efb1d234a58c04cbb0a0c9eb87d214;True;0;False;white;LockedToCube;False;Object;-1;Auto;Cube;8;0;SAMPLERCUBE;;False;1;FLOAT3;0,0,0;False;2;FLOAT;0;False;3;FLOAT3;0,0,0;False;4;FLOAT3;0,0,0;False;5;FLOAT;1;False;6;FLOAT;0;False;7;SAMPLERSTATE;;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.SimpleMultiplyOpNode;28;-399.2493,1052.902;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.RangedFloatNode;31;-469.4467,1211.618;Inherit;False;Property;_RimBias;RimBias;6;0;Create;True;0;0;False;0;False;0;0;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.RangedFloatNode;15;-466.0444,392.8555;Inherit;False;Property;_ReflectIntensity;ReflectIntensity;3;0;Create;True;0;0;False;0;False;1;1;0;0;0;1;FLOAT;0
+Node;AmplifyShaderEditor.SimpleAddOpNode;30;-172.2493,1094.901;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0
+Node;AmplifyShaderEditor.ColorNode;36;255.3527,989.3035;Inherit;False;Property;_RimColor;RimColor;8;0;Create;True;0;0;False;0;False;0,0,0,0;0.8970588,0.8623443,0.4551255,0;True;0;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
+Node;AmplifyShaderEditor.SimpleMultiplyOpNode;18;-131.6671,296.2484;Inherit;False;3;3;0;COLOR;0,0,0,0;False;1;FLOAT;0;False;2;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleMultiplyOpNode;12;-81.06299,-120.9109;Inherit;False;4;4;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;COLOR;0,0,0,0;False;3;FLOAT;0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleAddOpNode;16;119.3329,185.2484;Inherit;False;2;2;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleMultiplyOpNode;35;460.0375,793.4158;Inherit;False;2;2;0;FLOAT;0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleMultiplyOpNode;33;309.5573,294.3183;Inherit;False;3;3;0;COLOR;0,0,0,0;False;1;FLOAT;0;False;2;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.SimpleAddOpNode;34;540.5623,209.1047;Inherit;False;2;2;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;1;COLOR;0
+Node;AmplifyShaderEditor.FresnelNode;32;-708.2494,1308.901;Inherit;False;Standard;WorldNormal;ViewDir;False;False;5;0;FLOAT3;0,0,1;False;4;FLOAT3;0,0,0;False;1;FLOAT;0;False;2;FLOAT;1;False;3;FLOAT;5;False;1;FLOAT;0
+Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;39;977.4616,398.2424;Float;False;False;-1;2;ASEMaterialInspector;100;9;New Amplify Shader;0a6bbb37052a2458b860677ab2960714;True;Second;0;1;Second;2;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;2;RenderType=Opaque=RenderType;Queue=Geometry=Queue=0;False;0;True;4;1;False;-1;1;False;-1;0;1;False;-1;0;False;-1;True;0;False;-1;0;False;-1;False;False;False;False;False;False;True;0;False;-1;True;0;False;-1;True;True;True;True;True;0;False;-1;False;False;False;True;False;255;False;-1;255;False;-1;255;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;True;0;False;-1;True;0;False;-1;True;True;0;False;-1;0;False;-1;True;1;LightMode=ForwardBase;True;2;0;;0;0;Standard;0;False;0
+Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;38;987.6282,-86.66805;Float;False;True;-1;2;ASEMaterialInspector;100;9;CS07/Diamond;0a6bbb37052a2458b860677ab2960714;True;Unlit;0;0;Unlit;2;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;2;RenderType=Opaque=RenderType;Queue=Geometry=Queue=0;False;0;True;0;1;False;-1;0;False;-1;0;1;False;-1;0;False;-1;True;0;False;-1;0;False;-1;False;False;False;False;False;False;True;0;False;-1;True;1;False;-1;True;True;True;True;True;0;False;-1;False;False;False;True;False;255;False;-1;255;False;-1;255;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;7;False;-1;1;False;-1;1;False;-1;1;False;-1;True;0;False;-1;True;0;False;-1;True;True;0;False;-1;0;False;-1;True;1;LightMode=ForwardBase;True;2;0;;0;0;Standard;1;Vertex Position,InvertActionOnDeselection;1;0;2;True;True;False;;False;0
+WireConnection;21;0;19;0
+WireConnection;21;1;22;0
+WireConnection;23;0;21;0
+WireConnection;24;0;23;0
+WireConnection;25;0;24;0
+WireConnection;25;1;26;0
+WireConnection;27;0;25;0
+WireConnection;7;1;13;0
+WireConnection;6;1;13;0
+WireConnection;28;0;27;0
+WireConnection;28;1;29;0
+WireConnection;30;0;28;0
+WireConnection;30;1;31;0
+WireConnection;18;0;7;0
+WireConnection;18;1;15;0
+WireConnection;18;2;24;0
+WireConnection;12;0;2;0
+WireConnection;12;1;6;0
+WireConnection;12;2;7;0
+WireConnection;12;3;17;0
+WireConnection;16;0;12;0
+WireConnection;16;1;18;0
+WireConnection;35;0;30;0
+WireConnection;35;1;36;0
+WireConnection;33;0;16;0
+WireConnection;33;1;30;0
+WireConnection;33;2;35;0
+WireConnection;34;0;16;0
+WireConnection;34;1;33;0
+WireConnection;39;0;34;0
+WireConnection;38;0;12;0
+ASEEND*/
+//CHKSM=D24C8BF7840C56CD057B1AAEC0FA1C82BDCD7B7D
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ++Shader Language目前主要有3种语言 +- 基于OpenGL的OpenGL Shading Language,简称
+GLSL; +- 基于DirectX的High Level Shading Language,简称HLSL; +- 还有NVIDIA公司的C for Graphic,简称Cg语言渲染相关博客及网站 +catlikecoding :render相关博客
+
CPU应用阶段: 视锥体剔除,渲染顺序,提交Drawcall
+顶点处理 : 顶点MVP空间变换,自定义参数
+光栅化操作 : 裁剪,NDC归一化,背面剔除,屏幕坐标,图元装配,光栅化
+片元处理 : 光照着色,纹理着色
+输出合并 : Alpha测试,模版测试,深度测试,颜色混合
+最后输出到帧缓冲区
+
+#### CPU应用程序渲染逻辑 +a. 剔除: +- 视锥体剔除(Frustum Culling) +- 层级剔除(Layer Culling Mask),遮挡剔除(Occlusion Culling)等规则
+b. 渲染排序: +- 渲染队列 RenderQueue +- 不透明队列(RenderQueue < 2500) + 按摄像机 从前往后 排序 +- 半透明队列(RenderQueue >2500) + 按摄像机 从后往前 排序(为了保证效果的正确性)
+c. 打包数据(Batch):大量数据,参数发送到gpu
+模型信息 + - 顶点坐标 + - 法线 + - UV + - 切线 + - 顶点颜色 + - 索引列表
+矩阵 + - 世界变换矩阵 + - VP矩阵:根据射线机位置和fov等参数构建VP矩阵 + - 当矩阵的w分量为0的时候Unity会将其视为向量,而当w分量为1的时候Unity将其视为位置
+灯光,材质参数 + - Shader + - 材质参数 + - 灯光信息
+d. 调用Shader + - SetPassCall(Shader,背面剔除等参数,设置渲染数据),DrawCall
+
+
+
+
+
++CPU端调用DrawCall后 在GPU端启动顶点shader执行顶点处理 +顶点Shader:最主要的处理是将模型空间的顶点变换到裁剪空间 +- 顶点处理 : 顶点MVP空间变换,自定义参数 +- 光栅化操作 : 裁剪,NDC归一化,背面剔除,屏幕坐标,图元装配,光栅化 +- 片元处理 : 光照着色,纹理着色 +- 输出合并 : Alpha测试,模版测试,深度测试,颜色混合
+
+
+
+
+
+
++裁剪操作是在长方体或者正方体范围内进行的,不是视锥体,这里图中只是表达要进行三角形剔除
+![]()
常用颜色混合类型
+- 正常(Alpha Blend),即透明度混合
+Blend SrcAlpha OneMinusSrcAlpha
+- Particle Additive
+Blend SrcAlpha One
+- 柔和叠加(Soft Additive)
+Blend OneMinusDstColor One
+- 线性减淡(Additive,Linear Dodge)
+Blend One One ("LightMode" = "ForwardAdd" 光源叠加Pass使用的混合模式)
+- 正片叠底(Multiply),即相乘
+Blend DstColor Zero
+- 两倍相乘(2x Multiply)
+Blend DstColor SrcColor
+- 变暗(Darken)
+BlendOp Min
+Blend One One
+- 变亮(Lighten)
+BlendOp Max
+Blend One One
+- 滤色(Screen)
+Blend OneMinusDstColor One
+Blend One OneMinusSrcColor
++裁剪空间是正方形或者长方形,下一步ndc归一化是除以w就到正负1范围内,(z轴在opengl 范围是正负1,在dx中范围是从0到1) +NDC归一化后进行背面剔剔除(Back Face Culling)根据三角形的索引顺序进行判定背面(三角形索引顺序是顺时针)或者正面(三角形索引顺序是逆时针),然后剔除对应三角形
+
一般使用fixed存储颜色和单位矢量
++ ++
点线代表线性颜色/亮度值(译注:这表示的是理想状态,Gamma为1),实线代表监视器显示的颜色。
+Gamma校正(Gamma Correction)的思路是在最终的颜色输出上应用监视器Gamma的倒数。回头看前面的Gamma曲线图,你会有一个短划线,它是监视器Gamma曲线的翻转曲线。我们在颜色显示到监视器的时候把每个颜色输出都加上这个翻转的Gamma曲线,这样应用了监视器Gamma以后最终的颜色将会变为线性的。我们所得到的中间色调就会更亮,所以虽然监视器使它们变暗,但是我们又将其平衡回来了。
//为了有效解决color的值域问题,我们可以使用色调映射(Tone Map)和曝光控制(Exposure Map),用它们将color的高动态范围(HDR)映射到LDR之后再做伽马校正:
+color = color / (color + vec3(1.0)); // 色调映射(Tone Map)
+color = pow(color, vec3(1.0/2.2)); // 伽马校正
+
+基于gamma0.45的颜色空间叫做sRGB颜色空间(Gamma 空间)。
+把sRGB纹理变回线性空间:
+float gamma = 2.2;
+vec3 linear_diffuseColor = pow(texture(sRGB_diffuse, texCoords).rgb, vec3(gamma));
+
+变回线性空间后可以进行光照计算等等一系列计算 +线性空间转到Gamma空间:
+float gamma = 2.2;
+vec3 gamma_diffuseColor = pow(linear_color, vec3(1.0/gamma));
+
+++Unity中sRGB(Color Texture)选项说明:
+
+sRGB (Color Texture) 启用此属性可指定将纹理存储在伽马空间中。对于非 HDR 颜色纹理(例如反照率和镜面反射颜色),应始终选中此复选框。如果纹理存储了有特定含义的信息,并且您需要着色器中的确切值(例如,平滑度或金属度),请禁用此属性。默认情况下会启用此属性。
++linear模式¶
+在PBR流程中,环境光线效果需要使用linear模式,原来的gamma模式不再适用。因为gamma模式本身是为了使其他技术渲染出来的模型具有更真实而效果,对环境光进行了修正。但是在pbr中,贴图在设计的时候,完全是按照真实的环境中的光照去计算的,所以此处不再需要修正。
+HDR(High Dynamic Range)¶
+也叫 高动态范围 。 +显示器被限制为只能显示值为0.0到1.0间的颜色(LDR),但是在光照方程中却没有这个限制。通过使片段的颜色超过1.0,我们有了一个更大的颜色范围,这也被称作HDR(High Dynamic Range, 高动态范围)。有了HDR,亮的东西可以变得非常亮,暗的东西可以变得非常暗,而且充满细节。
+
人眼有光线自适应的特性,这样也是能让人在暗的场景里看到更多东西,在亮的场景里能分辨更多东西,这种效果一般从电影院这样昏暗的场所里走出来更能体会,眼睛会有一个慢慢适应的过程。现在摄像头一般也会自适应曝光度,但是工业需求的有些还是需要不自动的,三维渲染之中,如果把这种真实完全模拟的图像给人眼看到,会感觉比我们人眼看到灰暗的多,所以一般会在硬件方面做一个反曲线最后变成srgb的色彩空间让人看到。
+游戏引擎里面最终效果给人看到,当然是这种强化过的适应人眼的色彩空间,但是我做光照计算和贴图就不行,因为会由于多次的色彩强化导致最终画面强烈失真,这时候就是需要linear的色彩空间,计算时候用真实色彩,直到输出这一步把色彩强化一遍以适合人眼。unity很早就是linear的色彩空间,但是由于后期最后一部的矫正方面很多从业人士的素养不足,或者完全没有这个意识,做出来的效果完全是不正确,或者缺乏调整弹性的。而unreal则在工作流上面几乎是整合和后期的色表,去解决这个问题。
+而HDR是模拟人眼的过程,能看到更广范围的光(就是亮的时候能更亮瞎),HDR图像的一般色域也超过普通的RGB色域(但也可以不超过)。HDR在unity中需要勾选摄像机上的HDR选项和使用延迟渲染deferred才能(否则会有滤镜提示The camera is not HDR enabled 这是没使用延迟渲染),要看出效果还要加个bloom之类的
+++法线一般用切线空间存储,优点:自由度高,uv动画扰动,可以重用,可以压缩(只存储两个方向的数据)
+
+切线空间(右手坐标系): 切线方向(X轴)(和uv的u方向相同,有的是和v方向相同),次法线方向(Y轴),法线方向(Z轴)
half3 normal_data=UnpackNormal(normalmap);
+ float3x3 TBN=float3x3(tangent_dir,binormal_dir,normal_dir);
+ normal_dir=normalize(mul(normal_data.xyz,TBN));
+ // 和上面矩阵相乘结果一样
+ //normal_dir=normalize(tangent_dir*normal_data.x+binormal_dir*normal_data.y+normal_dir*normal_data.z);
+
+max(0,dot(n,l))dot(n,l)*0.5+0.5
+diffuse=basecolor*lightcolor*lambert ( or halflambert )pow(max(dot(v,r),0),_Gloss)pow(max(dot(n,h),0),_Gloss) //性能比phong要好float fresnel=_FresnelScale+(1-_FresnelScale)*pow(1-saturate(ndotv),_FresnelPower);float reflectionFactor = _FresnelBias + _FresnelScale * pow(1 + dot(i, n), _FresnelPower);half3 ambient_color = UNITY_LIGHTMODEL_AMBIENT.rgb * base_color.xyz;float2 uv_mapcap=(vNormal*0.5+0.5).xy;使用观察空间下的法线代表uv坐标

++Phong 光照模型:
+max(dot(n,l),0)+pow(max(dot(v,r),0),smoothness)+ambient=Phong
+基础光照模型=直接光漫反射(Direct Diffuse)+直接光镜面反射(Direct Specular)+间接光漫反射(Indirect Diffuse)+间接光镜面反射(Indirect Specular)
+直接光镜面反射: PBR中的GGX光照模型
环境贴图 (存储环境光或者间接光的漫反射和镜面反射的图像载体),环境光可以理解为间接光的一部分。
+环境贴图一般转成立方体贴图(Cubemap)使用,原因:直接采样环境贴图会造成贴图空间的浪费及采样会出现失真情况,所以先转成立方体贴图
+Cubemap立方体贴图的局限性: 只根据方向来采样 Cubemap 会造成采样点错误,这也是为什么Cubemap技术不适合用于平面模型作反射的原因
+立方体贴图(Cubemap)采样:texCUBE(_CubeMap, reflect_dir)

+
+
+
samplerCUBE _CubeMap;
+ float4 _CubeMap_HDR;
+ half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
+ half3 reflect_dir = reflect(-view_dir, normal_dir);
+ half4 color_cubemap = texCUBE(_CubeMap, reflect_dir);
+ half3 env_color = DecodeHDR(color_cubemap, _CubeMap_HDR);//确保在移动端能拿到HDR信息
+
+samplerCUBE _CubeMap;
+float4 _CubeMap_HDR;
+
+ half3 reflect_view_dir = reflect(-view_dir, normal_dir);
+
+ float roughness = tex2D(_RoughnessMap, i.uv);
+ roughness = saturate(pow(roughness, _RoughnessContrast) * _RoughnessBrightness);
+ roughness = lerp(_RoughnessMin, _RoughnessMax, roughness);
+ roughness = roughness * (1.7 - 0.7 * roughness);
+ float mip_level = roughness * 6.0;
+
+ half4 color_cubemap = texCUBElod(_CubeMap, float4(reflect_view_dir, mip_level));
+ half3 env_color = DecodeHDR(color_cubemap, _CubeMap_HDR);//确保在移动端能拿到HDR信息
+
+ samplerCUBE _CubeMap;
+ float4 _CubeMap_HDR;
+
+ float roughness = tex2D(_RoughnessMap, i.uv);
+ roughness = saturate(pow(roughness, _RoughnessContrast) * _RoughnessBrightness);
+ roughness = lerp(_RoughnessMin, _RoughnessMax, roughness);
+ roughness = roughness * (1.7 - 0.7 * roughness);
+ float mip_level = roughness * 6.0;
+ float4 uv_ibl = float4(normal_dir, mip_level);
+ half4 color_cubemap = texCUBElod(_CubeMap, uv_ibl);
+ half3 env_color = DecodeHDR(color_cubemap, _CubeMap_HDR);//确保在移动端能拿到HDR信息
+ half3 final_color = env_color * ao * _Tint.rgb * _Tint.rgb * _Expose;
+
+ half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
+ half3 reflect_view_dir = reflect(-view_dir, normal_dir);
+
+ reflect_view_dir = RotateAround(_Rotate, reflect_view_dir);
+
+ float roughness = tex2D(_RoughnessMap, i.uv);
+ roughness = saturate(pow(roughness, _RoughnessContrast) * _RoughnessBrightness);
+ roughness = lerp(_RoughnessMin, _RoughnessMax, roughness);
+ roughness = roughness * (1.7 - 0.7 * roughness);
+ float mip_level = roughness * 6.0;
+
+
+ half4 color_cubemap = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflect_view_dir, mip_level);
+ half3 env_color = DecodeHDR(color_cubemap, unity_SpecCube0_HDR);//确保在移动端能拿到HDR信息
+
+ half3 env_color = ShadeSH9(float4(normal_dir,1.0)); //unity 内置函数
+
+ float4 normalForSH = float4(normal_dir, 1.0);
+ //SHEvalLinearL0L1
+ half3 x;
+ x.r = dot(custom_SHAr, normalForSH);
+ x.g = dot(custom_SHAg, normalForSH);
+ x.b = dot(custom_SHAb, normalForSH);
+
+ //SHEvalLinearL2
+ half3 x1, x2;
+ // 4 of the quadratic (L2) polynomials
+ half4 vB = normalForSH.xyzz * normalForSH.yzzx;
+ x1.r = dot(custom_SHBr, vB);
+ x1.g = dot(custom_SHBg, vB);
+ x1.b = dot(custom_SHBb, vB);
+
+ // Final (5th) quadratic (L2) polynomial
+ half vC = normalForSH.x*normalForSH.x - normalForSH.y*normalForSH.y;
+ x2 = custom_SHC.rgb * vC;
+
+ float3 sh = max(float3(0.0, 0.0, 0.0), (x + x1 + x2));
+
+
++光照衰减计算量太大,unity使用查找表(LUT,lookup table)纹理存储衰减数据(_LightTexture0),如果光源使用了cookie,则使用衰减查找纹理_LightTextureB0。
+
# ifdef USING_DIRECTIONAL_LIGHT
+ fixed atten=1.0;
+ #else
+ float3 lightcoord = mul(unity_WorldToLight,float4(i.worldPosition,1)).xyz;
+ fixed atten=tex2D(_LightTexture0,dot(lightcoord,lightcoord).rr).r; //r equal UNITY_ATTEN_CHANNEL not cookie
+ #endif
+
+
+一种简单的做法 点光源
+ # ifdef USING_DIRECTIONAL_LIGHT
+ //half3 light_dir=normalize(_WorldSpaceLightPos0.xyz);
+ fixed atten=1.0;
+ #else
+ //half3 light_dir=normalize(_WorldSpaceLightPos0.xyz-i.pos_world);
+ half distance=length(_WorldSpaceLightPos0.xyz-i.pos_world);
+ half range=1.0/untiy_WorldToLight[0][0]; //光源范围
+ fixed atten=saturate((range-distance)/range);
+ #endif
+ half3 light_dir=normalize( lerp(_WorldSpaceLightPos0.xyz,_WorldSpaceLightPos0.xyz-i.pos_world,_WorldSpaceLightPos0.w));
+
+++阴影映射纹理(深度纹理)存储距离光源的深度信息
+
+老版本是在光源空间中计算深度数据
+新版本部分平台是在屏幕空间中计算深度数据,显卡必须支持MRT才行

+
+
+

+
1. 顶点动画贴图(VAT) (风力动画可以用这种)`比较细腻`(旗帜燃烧CS06)
+
++++
+
+
+ 2.
亮度
+fixed3 finalColor=baseCol.rgb*_Brightness
饱和度
+ fixed luminance=0.2125*baseCol.r+0.7154*baseCol.g+0.0721*baseCol.b;
+ fixed3 luminanceCol=fixed(luminance,luminance,luminance);
+ finalCol = lerp(luminanceCol,finalCol,_Saturation);
+
+ fixed3 avgColor=fixed3(0.5,0.5,0.5);
+ finalCol=lerp(avgColor,finalCol,_Constrast);
+
+ //暗角/晕影
+ float2 d=abs(i.uv-half2(0.5,0.5))*_VignetteIntensity;
+ d=pow(saturate(d),_VignetteRoundness);
+ float dist=length(d);
+ float vfactor=pow(saturate(1.0-dist*dist),_VignetteSmoothness);
+
++ ++
++设置
+camera.depthTextureMode=DepthTextureMode.DepthNormals;
+_CameraDepthTexture
用Tone-Mapping压缩高光范围 +HDR颜色通过色调映射转到(0-1)范围内 +一般用于屏幕后处理
+ // Tone-Mapping 需要将x从Gamma空间转到Lear线性空间使用,结果再转到Gamma空间下
+ inline float3 ACESFilm(float3 x)
+ {
+ float a=2.51f;
+ float b= 0.03f;
+ float c=2.43f;
+ float d=0.59f;
+ float e=0.14f;
+ return saturate((x*(a*x+b))/(x*(c*x+d)+e))
+ }
+
+ // Gamma空间 转Lear空间 color_lear=pow(color_gamma,2.2);
+ // Lear空间转Gamma空间 color_gamma=pow(color_lear,1.0/2.2);
+
+
++ + + + + + + + + + + + + + + + + + + + + + + + + +【Unity技巧】Unity中的优化技术 +1. CPU + 1. 静态批处理(static batching)降低drawcall + 2. 动态批处理(顶点属性小于900(如果使用顶点坐标,法线和纹理坐标则顶点数量小于300),lightmap必须参数相同指向同一位置,多pass打断合并)降低drawcall + 3. 使用图集 + 4. 共享材质 +2. GPU + 1. 减少顶点数量 + 1. 优化几何体 + 2. 使用模型lod(Level of Detail)技术(unity中使用LOD Group组件) + 3. 使用遮挡剔除(Occlusion Culling)技术 + 4. 使用mesh压缩 + 2. 减少片元数量(核心降低overdraw) + 1. 控制绘制顺序 + 2. 警惕透明物体 + 3. 减少实时光照和阴影 + 3. 减少计算复杂度 + 1. 使用Shader的LOD技术 + 1. 设置Shader.maximumLDO或者Shader.globalMaximumLOD来允许最大的LOD + 2. Shader代码优化 + 1. 把高斯模糊和边缘计算计算放到顶点shader中 + 2. float存储顶点坐标等变量,half存储一些标量和纹理坐标等信息,fixed适用于大多数颜色变量和归一化的方向矢量 +3. 节省内存带宽 + 1. 减少纹理大小 + 2. mipmap + 3. 关闭readwrite + 4. 纹理压缩(ETC2 8bit,ASTC 4x4 block,PVRTC) + 5. 降低屏幕分辨率
+待处理 +7.4.2 遮罩纹理的使用 data2 +着色器替换技术(Shader Replacement) +
+常用素材资料:
+
+https://sketchfab.com/
++ref tex2Dproj和tex2D的区别 +tex2Dproj和tex2D这两个功能几乎相同。
+
唯一的区别是,在对纹理进行采样之前,tex2Dproj将输入的UV xy坐标除以其w坐标。这是将坐标从正交投影转换为透视投影。
+例如 以下段代码的返回值是相同的.
+float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition)).r;
float existingDepth01 = tex2D(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition.xy / i.screenPosition.w)).r;
++具体什么情况下使用tex2Dproj呢?
+
+我们知道,裁剪空间的坐标经过缩放和偏移后就变成了(0,w),而当分量除以分量W以后,就变成了(0,1),这样在计算需要返回(0,1)值的时候,就可以直接使用tex2Dproj了.
++ref Unity Shader - 深度图基础及应用
+
+ref 神奇的深度图:复杂的效果,不复杂的原理
+ 章节:ShaderLearn-CS61
深度会被保存在 _CameraDepthTexture 中,可以通过screenpos坐标来获取贴图中的值,然后使用 UNITY_SAMPLE_DEPTH 宏来提取深度(经常指向r通道)。
需要注意的是,深度纹理使用了与阴影投射(shadow caster)相同的Shader pass,所以如果一个对象不支持阴影投射,那么它将不会出现在深度纹理当中,并且只有RenderQueue小于等于2500的对象才能被渲染到深度纹理当中去。
+ float2 uv = i.uv;
+ float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, uv));
+
+带有Normals和depth 的的32位贴图,Normals根据Stereographic projection编码到R&G通道,Depth通过映射编码到 B&A 通道。Unity ShaderLab也提供DecodeDepthNormal 方法进行解码,其中深度是0~1范围。
Normals & Depth Texture 是通过 Camera Shader replacement 实现,可以将RenderType为:Opaque、TransparentCutout、TreeBark、TreeLeaf、TreeOpaque、TreeTransparentCutout、TreeBillboard、GrassBillboard、Grass类型才会进行深度渲染,对于Transparent\AlphaTest是不会渲染到这个纹理中。详情可参考:浅析Unity shader中RenderType的作用及_CameraDepthNormalsTexture
在使用中,需提前定义 _CameraDepthNormalsTexture ,使用DecodeDepthNormal解码,需要注意的是:深度是0~1范围,和_CameraDepthTexture 有区别。
float2 uv = i.uv;
+ half depth;
+ half3 norm;
+ DecodeDepthNormal(tex2D(_CameraDepthNormalTexture, uv), depth, norm);
+
+
+使用水的法线做出双层法线然后相加得出世界空间法线
+用计算出的法线除以投影空间下的w(z值),做出近大远小的法线扰动(z值越远越大,作为被除数使得法线近大远小)然后和采样UV相加进行UV扰动
+++CS18 +1. 水体颜色(Water color) +3. 镜面反射(高光) +3. 折射/水底(Refrection) +4. 焦散 +4. 海浪(Wave) +5. 岸边泡沫(Foam) +6. 水体运动(顶点动画)
+
env | less -N
printenv | less -N
打印变量
+echo $?
+
+# mac or linux
+echo $PATH
+
+# windows
+echo %PATH%
+
+set | less -N
打印变量
+echo $?
+
+echo $TERM
+
+++unset 复位变量
+
[root@VM-24-15-centos ~]# whatis cp
+cp (1) - copy files and directories
+[root@VM-24-15-centos ~]# whereis cp
+cp: /usr/bin/cp /usr/share/man/man1/cp.1.gz
+[root@VM-24-15-centos ~]#
+
+
+mrwang@CodingdeMBP hugo-project % who am i
+mrwang ttys000 Feb 3 12:43
+mrwang@CodingdeMBP hugo-project % who are you
+mrwang ttys000 Feb 3 12:44
+mrwang@CodingdeMBP hugo-project % who goes there
+mrwang ttys000 Feb 3 12:44
+mrwang@CodingdeMBP hugo-project % who is god
+mrwang ttys000 Feb 3 12:44
+
+++lsof(list open files)是一个列出当前系统打开文件的工具。
+
lsof 查看端口占用语法格式:
+lsof -i:端口号
+# lsof -i 需要 root 用户的权限来执行
+
+# lsof -i:8000
+COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
+nodejs 26993 root 10u IPv4 37999514 0t0 TCP *:8000 (LISTEN)
+# 可以看到 8000 端口已经被轻 nodejs 服务占用。
+
+更多 lsof 的命令如下:
+lsof -i:8080:查看8080端口占用
+lsof abc.txt:显示开启文件abc.txt的进程
+lsof -c abc:显示abc进程现在打开的文件
+lsof -c -p 1234:列出进程号为1234的进程所打开的文件
+lsof -g gid:显示归属gid的进程情况
+lsof +d /usr/local/:显示目录下被进程开启的文件
+lsof +D /usr/local/:同上,但是会搜索目录下的目录,时间较长
+lsof -d 4:显示使用fd为4的进程
+lsof -i -U:显示所有打开的端口和UNIX domain文件
+
++++
netstat -tunlp用于显示 tcp,udp 的端口和进程等相关情况。网络查询
# netstat 查看端口占用语法格式:
+netstat -tunlp | grep 端口号
+
+netstat -ntlp //查看当前所有tcp端口
+netstat -ntulp | grep 80 //查看所有80端口使用情况
+netstat -ntulp | grep 3306 //查看所有3306端口使用情况
+
+-t (tcp) 仅显示tcp相关选项
+-u (udp)仅显示udp相关选项
+-n 拒绝显示别名,能显示数字的全部转化为数字
+-l 仅列出在Listen(监听)的服务状态
+-p 显示建立相关链接的程序名
+
+
+# netstat -tunlp | grep 8000
+tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 26993/nodejs
+
+++在查到端口占用的进程后,如果你要杀掉对应的进程可以使用 kill 命令:
+
kill -9 PID
+
+# 如上实例,我们看到 8000 端口对应的 PID 为 26993,使用以下命令杀死进程:
+kill -9 26993
+
+解压缩
+tar -zxvf a.tar.gz
+
+| 信号 | +键 | +作用 | +
|---|---|---|
| erase | +< Backspace > / < Delete > | +删除最后一个键入的字符 | +
| werase | +^W | +删除最后一个键入的单词 | +
| kill | +^X / ^U | +删除整行 | +
^X 将光标移动到行的开头位置
+## 显示所有键盘映射当前设置
+stty -a
+
+## 屏蔽显示
+stty -echo #禁止回显
+stty echo #打开回显
+
+## 忽略回车符
+stty igncr # 开启
+stty -igncr # 恢复
+
+## 改变ctrl+D的方法:
+stty eof "string" # 系统默认是ctrl+D来表示文件的结束,而通过这种方法,可以改变!
+
+| 信号 | +键 | +作用 | +
|---|---|---|
| erase | +< Backspace > / < Delete > | +删除最后一个键入的字符 | +
| werase | +^W | +删除最后一个键入的单词 | +
| kill | +^X / ^U | +删除整行 | +
| stop | +^S | +暂停屏幕显示 | +
| start | +^Q | +重新启动屏幕显示 | +
| eof | +^D | +指示已经没有数据 | +
less 与 more 类似,但使用 less 可以随意浏览文件,而 more 仅能向前移动,却不能向后移动,而且 less 在查看之前不会加载整个文件。
+
+b 向上翻一页
+空格键 向下翻一页
+y 向上滚动一行
+回车键 向下滚动一行
+u 向上翻半页
+d 向下翻半页
+
+
+-i 忽略搜索时的大小写
+-N 显示每行的行号
+-o <文件名> 将less 输出的内容在指定文件中保存起来
+-s 显示连续空行为一行
+/字符串:向下搜索“字符串”的功能
+?字符串:向上搜索“字符串”的功能
+n:重复前一个搜索(与 / 或 ? 有关)
+N:反向重复前一个搜索(与 / 或 ? 有关)
+-x <数字> 将“tab”键显示为规定的数字空格
+h 显示帮助界面
+Q 退出less 命令
+[pagedown]: 向下翻动一页
+[pageup]: 向上翻动一页
+
+## -N 显示行号
+ps -aux | less -N
+
+## 可以使用 n 查看下一个,使用 p 查看前一个。
+less 1.log 2.log
+
+++scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时,用scp可以帮你把文件移出来。另外,scp还非常不占资源,不会提高多少系统负荷,在这一点上,rsync就远远不及它了。虽然 rsync比scp会快一点,但当小文件众多的情况下,rsync会导致硬盘I/O非常高,而scp基本不影响系统正常使用。
+
使用scp需要远程用户密码;如果配置远程ssh的私钥则不用密码,自动使用私钥登陆
+命令格式
+scp [参数] [原路径] [目标路径]
+[目标路径]: user@server-ip:server-path
+
+常用命令参数
+-C - 这会在复制过程中压缩文件或目录。
+-P - 如果默认 SSH 端口不是 22,则使用此选项指定 SSH 端口。
+-r - 此选项递归复制目录及其内容。
+-p - 保留文件的访问和修改时间。
+
+scp logs.tar.gz root@192.168.43.137:/root
+
+scp root@192.168.43.137:/root/logs.tar.gz ./
+
+scp -rC syslog root@192.168.43.137:/root
+
+scp -rC root@192.168.43.137:/root syslog
+
+++参考:
+
+https://www.cnblogs.com/peida/archive/2013/03/15/2960802.html
+https://www.linuxprobe.com/scp-cmd-usage.html
++Rsync(remote sync ; remote synchronous)是UNIX 及类UNIX 平台下一款神奇的数据镜像备份软件,它不像FTP 或其他文件传输服务那样需要进行全备份,Rsync 可以根据数据的变化进行差异备份,从而减少数据流量,提高工作效率。你可以使用它进行本地数据或远程数据的复制,Rsync 可以使用SSH 安全隧道进行加密数据传输。Rsync 服务器端定义源数据,Rsync 客户端仅在源数据发生改变后才会从服务器上实际复制数据至本地,如果源数据在服务器端被删除,则客户端数据也会被删除,以确保主机之间的数据是同步的。Rsync 使用TCP
+873端口。 +使用rsync需要远程用户密码;如果配置远程ssh的私钥则不用密码,自动使用私钥登陆 +配置文件:/etc/rsyncd.conf。windows安装 rsync +https://www.itefix.net/cwrsync
+
+https://www.cnblogs.com/zhangweiyi/p/10571273.html
+https://blog.csdn.net/zetion_3/article/details/103575905
常用参数:
+-a 包含-rtplgoD
+-r 同步目录时要加上,类似cp时的-r选项
+-v 同步时显示一些信息,让我们知道同步的过程
+-l 保留软连接
+-L 加上该选项后,同步软链接时会把源文件给同步
+-p 保持文件的权限属性
+-o 保持文件的属主
+-g 保持文件的属组
+-D 保持设备文件信息
+-t 保持文件的时间属性
+--delete 删除DEST中SRC没有的文件
+--exclude 过滤指定文件,如--exclude “logs”会把文件名包含logs的文件或者目录过滤掉,不同步
+-P 显示同步过程,比如速率,比-v更加详细
+-u 加上该选项后,如果DEST中的文件比SRC新,则不同步
+-z 传输时压缩
+
+++使用xshell来操作服务非常方便,传文件也比较方便。 +就是使用rz,sz
+
首先,服务器要安装了rz,sz
+yum install lrzsz
当然你的本地windows主机也通过ssh连接了linux服务器 +运行rz,会将windows的文件传到linux服务器 +运行sz filename,会将文件下载到windows本地
+## -r 递归处理文件夹
+## 文件夹最后必须带斜杠(/),不带则远程会创建文件夹
+## 第一次同步后,第二次及后面的同步都是增量同步,存在的文件且相同的不会传输
+rsync -r ~/pub/ 101.43.160.247:~/public/
+
+## --delete 本地如果删除文件或文件夹,同步后会删除远程的文件或文件夹(exclude 忽略的文件或文件夹除外)
+## --exclude 本地忽略同步的文件或文件夹,例如项目中.git文件夹不需要同步到远程
+rsync -rtvpz --delete --exclude .git ~/pub/ root@101.43.160.247:~/public/
+
+rsync -rt --delete --exclude .git ~/pub/ 101.43.160.247:~/public/
+
+
+## -r 递归处理文件夹
+## 文件夹最后必须带斜杠(/),不带则远程会创建文件夹
+## 第一次同步后,第二次及后面的同步都是增量同步,存在的文件且相同的不会传输
+rsync -r 101.43.160.247:~/public/ ~/pub/
+
+## --delete 本地如果删除文件或文件夹,同步后会删除远程的文件或文件夹(exclude 忽略的文件或文件夹除外)
+## --exclude 本地忽略同步的文件或文件夹,例如项目中.git文件夹不需要同步到远程
+rsync -rtvpz --delete --exclude .git 101.43.160.247:~/public/ ~/pub/
+
+
+rsync ~/pub/a.txt 101.43.160.247:~/public/f.txt
+
+
+rsync 101.43.160.247:~/public/f.txt ~/pub/a.txt
+
+
+++参考:
+
+https://www.jianshu.com/p/5a799b36c7e1
+https://blog.csdn.net/allway2/article/details/103073243
+Rsync同步时删除多余文件
+rsync --exclude 参数
+windows 上rsync客户端使用方法
++centos 默认安装了
+md5sum命令
# -b, --binary Read files in binary mode
+# -t, --text Read files in ASCII mode
+md5sum filename
+
+[root@xyz.com ~]$ echo -n 'hello world!' | md5sum
+fc3ff98e8c6a0d3087d515c0473f8677 -
+
+注:一定要加上'-n'参数,代表去掉控制字符。
+错误命令示例1:
+[root@xyz.com ~]$ echo 'hello world!' | md5sum
+c897d1410af8f2c74fba11b1db511e9e -
+
+错误操作2:将文本hello world!写在文本文件中进行保存test文件,然后对文件进行md5sum。
mrwang@CodingdeMBP Sites % echo 'hello world!' | md5sum
+c897d1410af8f2c74fba11b1db511e9e -
+mrwang@CodingdeMBP Sites % cat test | md5sum
+c897d1410af8f2c74fba11b1db511e9e -
+mrwang@CodingdeMBP Sites % md5sum test
+c897d1410af8f2c74fba11b1db511e9e test
+mrwang@CodingdeMBP Sites %
+
+此错误操作与错误1得到的结果一样,都是因为文本中会自动带上一些控制字符,从而导致最终计算出来的md5值不是纯粹字符串的md5值。
+用vi打开test文件 使用vi 命令":set list",显示如下:
+hello world!$
+
+多了控制字符。
+#!/bin/sh
+
+#获取文件夹下所有文件
+folder="./"
+
+softfiles=$(ls $folder)
+cd ${folder}
+for sfile in ${softfiles}
+do
+ md5sum $sfile >> ../md5sum.txt
+ # md5sum $sfile
+done
+
+++Mac 使用 md5sum:
+
+Mac没有自带md5sum, 需要安装md5sum。
+使用brew安装brew install md5sha1sum参考:
+
+https://www.cnblogs.com/xd502djj/p/7055228.html +http://www.blogjava.net/anchor110/articles/433319.html
查找文件或者文件夹test
# 在当前目录查找文件或文件夹 test
+find test
+# 在根目录查找文件或文件夹 test
+find / -name test
+# 在根目录查找文件或文件夹 以test开头的
+find / -name test*
+find / -name ‘test*’
+# 在 home 目录查找文件或文件夹 包含 test 的
+find /home -name *test*
+find /home -name ‘*test*’
+
+
+查找文件test
# 在当前目录查找文件 test
+find test -type f
+# 在根目录查找文件 test
+find / -name test -type -f
+
+查找文件夹test
# 在当前目录查找文件夹 test
+find test -type f
+# 在根目录查找文件夹 test
+find / -name test -type -f
+
+++参考:
+
+https://www.runoob.com/linux/linux-comm-find.html
+https://blog.csdn.net/l_liangkk/article/details/81294260
Linux中用于查看文件尾部的内容,与head相对应。
+常用来查看日志文件,通过 tail -f 实时查看文件最新内容。
尤其是对于日志文件较大的时候,通过tail指定输出的行数来查看日志。
+// 输出最后10行的内容
+tail test.log
+
+// 输出最后10行的内容,同时监视文件的变化,一旦变化就显示出来
+tail -f test.log
+
+// 输出最后n行的内容,同时监视文件的变化,一旦变化就显示出来
+tail -nf test.log
+
+// 输出文件最后10行的内容
+tail -n 10 filename
+// 除第9行不显示外,显示第10行到末尾行
+tail -n -10 filename
+
+// 从第20行至末尾
+tail +20 test.log
+
+// 显示最后10个字符
+tail -c 10 test.log
+
+// 实时日志查看与grep过滤关键字
+// -A 除显示符合t匹配内容的那一行之外,并显示该行之后的内容
+// -B 除显示符合匹配内容的那一行之外,并显示该行之前的内容
+// -C 除显示符合匹配内容的那一列之外,并显示该列前后的内容
+tail -f test.log | grep 'test' -C 5
+tail -f test.log | grep 'test' -5
+
++ ++
++https://www.cnblogs.com/duhuo/p/5695256.html
+
+https://www.ruanyifeng.com/blog/2019/09/curl-reference.html
常用命令
+# 换行符必须是unix的\n,不能是windows的\r\n,mac是\r结尾
+# `cat img.txt | tr "\r\n" "\n" | tr "\r" "\n"`
+for line in `cat img.txt | tr '\r\n' '\n'`
+do
+ echo $line
+ cp -f "/d/Sites/hugo-project/content/imgs/${line}" "./imgs/${line}"
+done
+
+# 换行符必须是unix的\n,不能是windows的\r\n,mac是\r结尾
+# $(cat img.txt | tr "\r\n" "\n" | tr "\r" "\n")
+for line in $(cat img.txt| tr '\r\n' '\n')
+do
+ echo $line
+ cp -f "/d/Sites/hugo-project/content/imgs/${line}" "./imgs/${line}"
+done
+
+# 换行符必须是unix的\n,不能是windows的\r\n,mac是\r结尾
+while read -r line
+do
+ echo $line
+ cp -f "/d/Sites/hugo-project/content/imgs/${line}" "./imgs/${line}"
+done < filename
+
++ ++
第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化;
+第二,ln的链接分软链接和硬链接两种,软链接就是ln–s ,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接ln,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。
+# 源文件 和 链接名称 尽量使用绝对路径
+ln -s <源文件> <链接名称>
+
+
+ln-s abc cde #建立abc 的软连接
+ln abc cde #建立abc的硬连接
+
+ -f: 链结时先将与dist同档名的档案删除
+
+ -d:允许系统管理者硬链结自己的目录
+
+ -i: 在删除与dist同档名的档案时先进行询问
+
+ -n:在进行软连结时,将dist视为一般的档案
+
+ -s:进行软链结(symboliclink)
+
+ -v:在连结之前显示其档名
+
+ -b:将在链结时会被覆写或删除的档案进行备份
+
+ -SSUFFIX :将备份的档案都加上 SUFFIX的字尾
+
+ -VMETHOD :指定备份的方式
+
+ --help:显示辅助说明
+
+ --version:显示版本
+
+
+++这里总结了一些常用的PC版模拟器连接方法!其他的自行搜索!
+
+MuMu模拟器:adb connect 127.0.0.1:7555
+夜神模拟器:adb connect 127.0.0.1:62001
+雷电模拟器:adb connect 127.0.0.1:5555
+逍遥安卓模拟器:adb connect 127.0.0.1:21503
+天天模拟器:adb connect 127.0.0.1:6555
+海马玩模拟器:adb connect 127.0.0.1:53001https://www.52pojie.cn/thread-1872463-1-1.html
+
adb devices
+ PS E:\Soft> adb devices
+ List of devices attached
+ f8d600b1 device
+ emulator-5554 device指定一个设备安装,apk要用绝对路径(可以直接拽到命令行中)
+PS E:\Soft> adb -s f8d600b1 install E:\Soft\debug.apk
+Performing Streamed Install
+Success
+
+如果devices只有一个设备,apk要用绝对路径(可以直接拽到命令行中)
+PS E:\Soft> adb install E:\Soft\debug.apk
+Performing Streamed Install
+Success
+
+强制安装(覆盖安装时使用)
+adb install -r E:\Soft\debug.apk
+
+情况一:adb devices命令启动了adb服务器,但是设备列表未显示。
+解决方法:
+第一步:先使用adb kill-server命令停止 adb 服务器,再切换目录到android_sdk/tools 目录下,因为emulator 命令位于 android_sdk/tools 目录下。
+第二步:停止 adb 服务器后,输入命令emulator -list-avds获取 AVD 名称列表,再执行命令emulator -avd AVD名称 -port 奇数端口号,最后执行adb devices -l查询设备即可。
+情况二:在下面的命令序列中,adb devices 显示了设备列表,因为先启动了 adb 服务器。
+解决方法:
+第一步:先停止adb服务器,再切换目录到android_sdk/tools 目录下使用命令emulator -avd AVD名称 -port 奇数端口号。
+第二步:在使用adb devices -l命令查询设备之前,使用adb start-server命令重新启动adb服务器。
+但是当我们打开多个夜神的其他多开设备时候, 直接使用以下命令链接,会失败!
+adb connect 127.0.0.1:62001
+那么这个时候我们需要先关闭其他模拟器,只打开要链接的那个多开器,然后去cmd窗口输入
tasklist
+
找到对应的夜神pid,然后输入
+netstat -ano | findstr 5508
+
+找到端口为 62***的,然后去adb链接
+
+连接成功!
+可以使用以下命令,查看一下!
adb devices
+
+
++合批过程是指Canvas合并UI元素的网格,并且生成发送给Unity渲染管线的命令。Canvas使用的网格都是从绑定在Canvas上的CanvasRenderer获得,但是不包含子Canvas的网格。UGUI的层叠顺序是按照Hierarchy中的顺序从上往下进行的,也就是越靠上的组件,就会被绘制在越底部。所有相邻层的可合批的UI元素(具有相同材质和纹理),就会在一个DrawCall中完成。
+
++Win10,
+Unity 2019.4.17f1c1


+8个drawcall,左右的白色图片合批了,其他都没有合批;再次打开场景显示7个drawcall,左右白色图片、绿色图片合批了,其他都没有合批。
+
+
+白色图片不能合批,一共占用3个drawcall。
++根据上图占用3个drawcall,推断出,Hierachy内的上下关系不能保证临近位置合批,受到前后遮挡关系影响。 +这里有个问题是 第1个drawcall先绘制的左侧的白图,根据Hierachy的顺序,应该先绘制蓝色图片才对?
+
++参考: +https://zhuanlan.zhihu.com/p/339387759
+
+https://www.cnblogs.com/moran-amos/p/13878493.html +https://www.cnblogs.com/moran-amos/p/13883818.html
FillCenter。
+
CanvasRenderer的CullTransparentMesh并且控制物体透明度进行剔除,这个一般用于单个ui,如果是多个UI要不显示的话通过设置CanvasGroups的Alpha控制显隐。//不渲染但可以相应点击
+public class NoOverdrawImage : Graphic{ public override void Rebuild(CanvasUpdate update){} }
+
+public class Empty4Raycast : MaskableGraphic {
+ protected Empty4Raycast(){useLegacyMeshGeneration = false;}
+ protected override void OnPopulateMesh(VertexHelper toFill){ toFill.Clear(); } }
+
++ ++
++ui常见的问题: +1. GPU 片段着色器使用过多(即填充率过度使用) +1. 重建 Canvas 批处理所花费的 CPU 时间过多 +1. Canvas 批次的重建数量过多(过度污染) +1. 用于生成顶点的 CPU 时间过多(通常来自文本)
+sprite 被意外引用,造成多加载一个图集 +
+
优化前批次 84
+
+
+
+
+
++红点图标的图集和主面板的icon图集不是一个,所以所有红点图标不能合批
+
++所有按钮使用的图集
+uileitubiao_1
mask使用 用来处理动画
+
mask(没用的),没用用的节点动画
+
底图
+
+这个底图独自占一个批次,没有使用sprite,不能合批

指定一个上层sprite所在图集的一个背景纹理即可减少一个批次,背景图会和上层的icon按钮合批。
+内层mask没有影响合批,后面再测试
位置1 的内层mask和外层mask都移除后,会和邮件按钮进行合批
+活动图标的图集改为位置1用的图集
+位置4 的icon使用的图集 common_banzi,feitongyong_01,uileitubiao_1
+位置5 的icon使用的图集 common_banzi 和 uileitubiao_1
+位置10 的icon使用的图集 common_banzi,uileitubiao_3,feitongyong_03
++单独测试占用12批次
+
icon使用的图集 common_banzi 和 uileitubiao_4
+特效节点 effect_task_huang
+特效没有合批
+有粒子特效(Particle System),需要使用替代方案
红点有canvas,造成红点底图还有红点文字合批失败,这个canvas要控制红点在特效前面 +红点文字的字体和其他文字字体不一致,打断合批
+无用的空Image节点占用一个批次,且打断合批 (这里Image透明度为0)
+
++优化后独立测试占用5批次,特效应该还有优化空间
+
++批次 6
+
icon使用的图集 uileitubiao_1
+
+
+不能合批是因为左侧红点的mesh比较大(红点图标透明区域很大),被右侧图标遮挡了,就会插入到左侧图标和右侧图标中间
++优化后3
+
位置8
+icon使用的图集 uileitubiao_1
位置9
+icon使用的图集 uileitubiao_1
位置20
+icon使用的图集 common_banzi 和 uileitubiao_4
+还有空Image 但是alpha不是0,不能合批
位置11
+++批次 6
+
icon使用的图集 common_banzi 和 daojutubiao_1
位置19
+icon使用的图集 common_banzi
位置 14,15,16,17,18 (活动集合)
+icon使用的图集 uileitubiao_1 ,uileitubiao_4,feitongyong_11,
+有粒子特效(Particle System),需要使用替代方案
+还有其他特效
这里特效有两部分,一部分在ui底下,一部分在ui上面,美术同学做特效时给所在的prefab所有ui节点挂载Canvas组件,直接导致合批失败 且形成大量的drawcall
icon使用的图集 common_banzi
+有循环动画
位置13
+icon使用的图集 feitongyong_12
位置23,24
+icon使用的图集 uileitubiao_3,uileitubiao_4,uileitubiao_6
+有循环动画
位置21
+icon使用的图集 uileitubiao_1
位置22
+icon使用的图集 feitongyong_04,feitongyong_03
位置 搜索坐标, 收藏按钮
+icon使用的图集 feitongyong_05,feitongyong_01,feitongyong_03
+不显示特效的情况下是11个drawcall。
+4号位置占用3个drawcall,头像和框分别是独立的图集,体力条需要压在框的上面,有的框是特效做的,头像是允许玩家上传自定义头像的,所以不能合批也不能做到一个图集里,动态图集可以考虑但是不能处理特效框的情况。
+6号位置的红点有canvas,需要控制红点在特效前面
+12号位置占用2个drawcall,这里是animation做的动画,这里的底图和文字是不能合批的,否则每帧都构建合批的大mesh
+22号位置的头像和状态图标是在一个图集里,这里需要一个drawcall。
+剩下的主界面图片合批成1个drawcall,剩下的文字占用2个drawcall,因为是两种字体。
显示特效的情况下是31个drawcall
+除去9个drawcall,都是特效占用的
+
原始的Overdraw
+
优化后的Overdraw
+


UIParticle去掉后的耗时35毫秒(将particle继续显示通过3d的方式):节省耗时5毫秒,一共移除UIParticle组件66个,共33个prefab。

游戏中实际情况优化前:
+
游戏中实际情况禁用UIParticle后:
+
这里不能参考总耗时,因为游戏业务逻辑在运行,场景有业务在运行,只能参考单项,相对来说也不算准确
+增加额外的2个drawcall
+
SRP全称为Scriptable Render Pipeline(可编程渲染管线/脚本化渲染管线),是Unity提供的新渲染系统,可以在Unity通过C#脚本调用一系列API配置和执行渲染命令的方式来实现渲染流程,SRP将这些命令传递给Unity底层图形体系结构,然后再将指令发送给图形API。
+说白了就是我们可以用SRP的API来创建自定义的渲染管线,可用来调整渲染流程或修改或增加功能。
+它主要把渲染管线拆分成二层:
+一层是比较底层的渲染API层,像OpenGL,D3D等相关的都封装起来。
+另一层是渲染管线上层,上层代码使用C#来编写。在C#这层不需要关注底层在不同平台上渲染API的差别,也不需要关注具体如何做一个Draw Call
+它的全称为Universal Render Pipeline(通用渲染管线),简称 Universal RP, 它是Unity官方基于SRP提供的模板,它的前身是 LWRP(Lightweight RP 即轻量级渲染管线), 在2019.3开始改名为URP,它涵盖了范围广泛的不同平台,是针对跨平台开发而构建的,性能比内置管线要好,另外可以进行自定义,实现不同风格的渲染,通用渲染管线未来将成为在Unity中进行渲染的基础 。
平台范围:可以在Unity当前支持的任何平台上使用
+
它的全称为High Definition Render Pipeline(高清晰度渲染管线),它也是Unity官方基于SRP提供的模板,它更多是针对高端设备,如游戏主机和高端台式机,它更关注于真实感图形和渲染,该管线仅于以下平台兼容:
+
内置渲染管线的缺陷
+目的
+为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。决定在C++端保留一个非常小的渲染内核,让C#端可以通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面可以保证C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就可以在实际项目中调整。
+
++渲染流水线图: +
+ref: https://blog.csdn.net/qq_30259857/article/details/108318528
+
+ref: https://blog.csdn.net/qq_33700123/article/details/114092028
++详解参考官方文档: 可编程渲染管线 SRP Batcher
+
++面板详解: 通用渲染管线(URP)_学习笔记
+
+
+
+++
UnityEngine.Rendering.Universal.UniversalRenderPipeline+public sealed partial class UniversalRenderPipeline : RenderPipeline+不包括XR,SceneView,Camera Preview,只是前向渲染的分析;不包括RenderingMode.Deferred的分析
Render分析¶+++
protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)的执行流程
BeginFrameRendering(renderContext, cameras);GraphicsSettingsSetupPerFrameShaderConstants();SortCameras(cameras); // 根据camera.depth排序RenderCameraStack(renderContext, camera);UpdateVolumeFramework(baseCamera, baseCameraAdditionalData);BeginCameraRendering(renderContext, camera);UpdateVolumeFramework(camera, null);RenderSingleCamera(renderContext, camera);EndCameraRendering(renderContext, camera);EndFrameRendering(renderContext, cameras); protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
+ {
+ BeginFrameRendering(renderContext, cameras);
+ GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);
+ GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;
+ SetupPerFrameShaderConstants();
+ SortCameras(cameras);
+ for (int i = 0; i < cameras.Length; ++i){
+ var camera = cameras[i];
+ if (IsGameCamera(camera))
+ {
+ RenderCameraStack(renderContext, camera);
+ }
+ else
+ {
+ BeginCameraRendering(renderContext, camera);
+ UpdateVolumeFramework(camera, null);
+ RenderSingleCamera(renderContext, camera);
+ EndCameraRendering(renderContext, camera);
+ }
+ }
+ EndFrameRendering(renderContext, cameras);
+ }
+ public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
+ {
+ UniversalAdditionalCameraData additionalCameraData = null;
+ if (IsGameCamera(camera))
+ camera.gameObject.TryGetComponent(out additionalCameraData);
+ InitializeCameraData(camera, additionalCameraData, true, out var cameraData);
+ RenderSingleCamera(context, cameraData, cameraData.postProcessEnabled);
+ }
+ /// <summary>
+ /// Renders a single camera. This method will do culling, setup and execution of the renderer.
+ /// </summary>
+ /// <param name="context">Render context used to record commands during execution.</param>
+ /// <param name="cameraData">Camera rendering data. This might contain data inherited from a base camera.</param>
+ /// <param name="anyPostProcessingEnabled">True if at least one camera has post-processing enabled in the stack, false otherwise.</param>
+ static void RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool anyPostProcessingEnabled)
+ {
+ Camera camera = cameraData.camera;
+ var renderer = cameraData.renderer;
+ if (!TryGetCullingParameters(cameraData, out var cullingParameters))
+ return;
+ ScriptableRenderer.current = renderer;
+ bool isSceneViewCamera = cameraData.isSceneViewCamera;
+ CommandBuffer cmd = CommandBufferPool.Get();
+ renderer.Clear(cameraData.renderType);
+ renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);
+ context.ExecuteCommandBuffer(cmd); // Send all the commands enqueued so far in the CommandBuffer cmd, to the ScriptableRenderContext context
+ cmd.Clear();
+ var cullResults = context.Cull(ref cullingParameters);
+ InitializeRenderingData(asset, ref cameraData, ref cullResults, anyPostProcessingEnabled, out var renderingData);
+ renderer.Setup(context, ref renderingData);
+ renderer.Execute(context, ref renderingData);
+ context.ExecuteCommandBuffer(cmd); // Sends to ScriptableRenderContext all the commands enqueued since cmd.Clear, i.e the "EndSample" command
+ CommandBufferPool.Release(cmd);
+ context.Submit(); // Actually execute the commands that we previously sent to the ScriptableRenderContext context
+ }
+
+ /// <summary>
+ // Renders a camera stack. This method calls RenderSingleCamera for each valid camera in the stack.
+ // The last camera resolves the final target to screen.
+ /// </summary>
+ /// <param name="context">Render context used to record commands during execution.</param>
+ /// <param name="camera">Camera to render.</param>
+ static void RenderCameraStack(ScriptableRenderContext context, Camera baseCamera)
+ {
+ baseCamera.TryGetComponent<UniversalAdditionalCameraData>(out var baseCameraAdditionalData);
+ // Overlay cameras will be rendered stacked while rendering base cameras
+ if (baseCameraAdditionalData != null && baseCameraAdditionalData.renderType == CameraRenderType.Overlay)
+ return;
+ // renderer contains a stack if it has additional data and the renderer supports stacking
+ var renderer = baseCameraAdditionalData?.scriptableRenderer;
+ bool supportsCameraStacking = renderer != null && renderer.supportedRenderingFeatures.cameraStacking;
+ List<Camera> cameraStack = (supportsCameraStacking) ? baseCameraAdditionalData?.cameraStack : null;
+ bool anyPostProcessingEnabled = baseCameraAdditionalData != null && baseCameraAdditionalData.renderPostProcessing;
+ // We need to know the last active camera in the stack to be able to resolve
+ // rendering to screen when rendering it. The last camera in the stack is not
+ // necessarily the last active one as it users might disable it.
+ int lastActiveOverlayCameraIndex = -1;
+ if (cameraStack != null)
+ {
+ var baseCameraRendererType = baseCameraAdditionalData?.scriptableRenderer.GetType();
+ baseCameraAdditionalData.UpdateCameraStack();
+ }
+ // Post-processing not supported in GLES2.
+ anyPostProcessingEnabled &= SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
+ bool isStackedRendering = lastActiveOverlayCameraIndex != -1;
+ // Update volumeframework before initializing additional camera data
+ UpdateVolumeFramework(baseCamera, baseCameraAdditionalData);
+ InitializeCameraData(baseCamera, baseCameraAdditionalData, !isStackedRendering, out var baseCameraData);
+ BeginCameraRendering(context, baseCamera);
+ RenderSingleCamera(context, baseCameraData, anyPostProcessingEnabled);
+ EndCameraRendering(context, baseCamera);
+ if (isStackedRendering)
+ {
+ for (int i = 0; i < cameraStack.Count; ++i)
+ {
+ var currCamera = cameraStack[i];
+ if (!currCamera.isActiveAndEnabled)
+ continue;
+ currCamera.TryGetComponent<UniversalAdditionalCameraData>(out var currCameraData);
+ // Camera is overlay and enabled
+ // Copy base settings from base camera data and initialize initialize remaining specific settings for this camera type.
+ CameraData overlayCameraData = baseCameraData;
+ bool lastCamera = i == lastActiveOverlayCameraIndex;
+ BeginCameraRendering(context, currCamera);
+ UpdateVolumeFramework(currCamera, currCameraData);
+ InitializeAdditionalCameraData(currCamera, currCameraData, lastCamera, ref overlayCameraData);
+ RenderSingleCamera(context, overlayCameraData, anyPostProcessingEnabled);
+ EndCameraRendering(context, currCamera);
+ }
+ }
+ }
+
+++如果Unity版本高
+UNITY_2021_1_OR_NEWER宏生效则BeginContextRendering(renderContext, cameras);替换BeginFrameRendering(renderContext, cameras);,EndContextRendering(renderContext, cameras);替换EndFrameRendering(renderContext, cameras);
RenderSingleCamera 分析¶+++
static void RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool anyPostProcessingEnabled)的执行流程
+ScriptableRender renderer= cameraData.renderer;
剔除操作,返回一个剔除参数(被摄象机渲染的游戏物体和光照的列表 )TryGetCullingParameters(cameraData, out var cullingParameters)
+ > static bool TryGetCullingParameters(CameraData cameraData, out ScriptableCullingParameters cullingParams)
renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);+++
public virtual void SetupCullingParameters(ref ScriptableCullingParameters cullingParameters, + ref CameraData cameraData)
context.ExecuteCommandBuffer(cmd);+++
public void ExecuteCommandBuffer(CommandBuffer commandBuffer);
var cullResults = context.Cull(ref cullingParameters);+++
public CullingResults Cull(ref ScriptableCullingParameters parameters)
RenderingData(灯光数据,阴影数据,后处理数据,是否动态批处理,是否启用后处理,相机数据,剔除结果数据)InitializeRenderingData(asset, ref cameraData, ref cullResults, anyPostProcessingEnabled, out var renderingData);+++
static void InitializeRenderingData(UniversalRenderPipelineAsset settings, ref CameraData cameraData, ref CullingResults cullResults, + bool anyPostProcessingEnabled, out RenderingData renderingData)
renderer.Setup(context, ref renderingData)
+ > public abstract void Setup(ScriptableRenderContext context, ref RenderingData renderingData);
如果获取深度 + ```csharp + ConfigureCameraTarget(BuiltinRenderTextureType.CameraTarget, BuiltinRenderTextureType.CameraTarget); + AddRenderPasses(ref renderingData); + EnqueuePass(m_RenderOpaqueForwardPass);
+// TODO: Do we need to inject transparents and skybox when rendering depth only camera? They don't write to depth.
+EnqueuePass(m_DrawSkyboxPass);
+EnqueuePass(m_RenderTransparentForwardPass);
+``
+ -ConfigureCameraColorTarget(activeColorRenderTargetId);-AddRenderPasses(ref renderingData); // rendererFeatures AddRenderPasses-CreateCameraRenderTarget(context, ref cameraTargetDescriptor, createColorTexture, createDepthTexture); // CameraRenderType.Base-EnqueuePass(m_MainLightShadowCasterPass);-EnqueuePass(m_AdditionalLightsShadowCasterPass);-EnqueuePass(m_DepthNormalPrepass);或者EnqueuePass(m_DepthPrepass);-EnqueuePass(m_ColorGradingLutPass);-EnqueuePass(m_RenderOpaqueForwardPass);-EnqueuePass(m_DrawSkyboxPass);-EnqueuePass(m_CopyDepthPass);-EnqueuePass(m_CopyColorPass);-EnqueuePass(m_TransparentSettingsPass);-EnqueuePass(m_RenderTransparentForwardPass);-EnqueuePass(m_OnRenderObjectCallbackPass);-EnqueuePass(m_PostProcessPass);-EnqueuePass(m_FinalPostProcessPass);-EnqueuePass(m_CapturePass);-EnqueuePass(m_FinalBlitPass);-EnqueuePass(m_PostProcessPass);`
renderer.Execute(context, ref renderingData);
+ > public void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
+ > ref CameraData cameraData = ref renderingData.cameraData;
InternalStartRendering(context, ref renderingData);SetPerCameraShaderVariables(cmd, ref cameraData);SetShaderTimeValues(cmd, time, deltaTime, smoothDeltaTime);context.ExecuteCommandBuffer(cmd);SortStable(m_ActiveRenderPassQueue); // Sort the render pass queueSetupLights(context, ref renderingData);执行所有Pass的Execute方法 ExecuteBlock(RenderPassBlock.BeforeRendering, in renderBlocks, context, ref renderingData);
+ >// Before Render Block. This render blocks always execute in mono rendering.
+ >// Camera is not setup. Lights are not setup.
+ >// Used to render input textures like shadowmaps.
+ csharp
+ void ExecuteBlock(int blockIndex, in RenderBlocks renderBlocks,ScriptableRenderContext context, ref RenderingData renderingData, bool submit = false)
+ { foreach (int currIndex in renderBlocks.GetRange(blockIndex)) //循环所有Pass
+ {var renderPass = m_ActiveRenderPassQueue[currIndex];
+ ExecuteRenderPass(context, renderPass, ref renderingData);}
+ if (submit) context.Submit();}
+ void ExecuteRenderPass(ScriptableRenderContext context, ScriptableRenderPass renderPass, ref RenderingData renderingData){
+ using var profScope = new ProfilingScope(null, renderPass.profilingSampler);
+ ref CameraData cameraData = ref renderingData.cameraData;
+ CommandBuffer cmd = CommandBufferPool.Get();
+ // Track CPU only as GPU markers for this scope were "too noisy".
+ using (new ProfilingScope(cmd, Profiling.RenderPass.configure))
+ { renderPass.Configure(cmd, cameraData.cameraTargetDescriptor); //配置Pass数据
+ SetRenderPassAttachments(cmd, renderPass, ref cameraData);}
+ // Also, we execute the commands recorded at this point to ensure SetRenderTarget is called before RenderPass.Execute
+ context.ExecuteCommandBuffer(cmd);
+ CommandBufferPool.Release(cmd);
+ renderPass.Execute(context, ref renderingData); //执行对应Pass的Execute
+ }
context.SetupCameraProperties(camera);
+ >// This is still required because of the following reasons:
+ >// - Camera billboard properties.
+ >// - Camera frustum planes: unity_CameraWorldClipPlanes[6]
+ >// - _ProjectionParams.x logic is deep inside GfxDevice
+ >// NOTE: The only reason we have to call this here and not at the beginning (before shadows)
+ >// is because this need to be called for each eye in multi pass VR.
+ >// The side effect is that this will override some shader properties we already setup and we will have to
+ >// reset them.
SetCameraMatrices(cmd, ref cameraData, true);
context.ExecuteCommandBuffer(cmd);context.DrawWireOverlay(camera);InternalFinishRendering(context, cameraData.resolveFinalTarget);
+ > context.ExecuteCommandBuffer(cmd);context.ExecuteCommandBuffer(cmd);context.ExecuteCommandBuffer(cmd);context.Submit();LightModeTags 说明¶++Tags{“LightMode” = “XXX”}
+
UniversalForward:前向渲染物件之用,SRPDefaultUnlit也用该pass渲染ShadowCaster: 投射阴影之用DepthOnly:只用来产生深度图Mata:来用烘焙光照图之用Universal2D :做2D游戏用的,用来替代前向渲染UniversalGBuffer : 貌似与延迟渲染相关(开发中), Does GI + emission. All additional lights are done deferred as well as fogUniversalForwardOnly:NormalsRendering:SceneSelectionPass:Scene view outline pass.Picking:Scene picking buffer passDepthNormals: 只是用俩产生法线贴图_CameraNormalsTexture CentOS7
+yum install yum-plugin-copr
+yum copr enable @caddy/caddy
+yum install caddy
+
+win or mac
+https://caddyserver.com/download
😀
+当我们建立Cloudflare团队账户后,就打通了外网访问路径,实现了免费科学上网,但您是不是发现仍然不能正常访问ChatGPT,一会能访问一会又不能了,而且网速还很慢。今天通过对workers脚本的部署,手把手带您创建一个能无障碍访问油管、奈菲,chatgpt的科学上网环境。如果您还缺一个免费科学上网的备选方案,并且想了解worker脚本的部署方法,一定不要错过。
+
v2rayN客户端及workers代码都托管在GitHub上,访问时需要科学上网。
+Cloudflare账户注册小布出过一期Zero Trust团队账户设置视频,其中演示了怎么注册Cloudflare账户,不会注册的朋友可以参考那期视频。
+Wordkers脚本:
+部署中修改两个地方:替换UUID,CF反代IP,反代IP可以使用下面大佬提供的,也可以自己去筛选。如果使用的是[ 3K大佬的 ]worker脚本,替换下伪装域名。
+http \= [80, 8080, 8880, 2052, 2086, 2095, 2082] https \= [443, 8443, 2053, 2096, 2087, 2083]
+大佬提供的CF反代IP:
+
+
+优选域名:
+
+套Tls需要将域名托管到Cloudflare,所以配置之前要想将域名托管设置好
+4.1、添加自定义域
+4.2、获取vless订阅地址
+4.3、v2rayN中添加vless订阅地址
+4.4、修改端口为443,传输层安全(Tls)开启
+


2.1、复制github上的workers脚本
+
2.2、删除默认脚本并粘贴workers脚本
+
UserID可以通过在线生成UUID得到,也可通过v2rayN创建Vless连接中的生成用户ID得到。
+proxyIP可以复制上面大佬提供的反代域名,也可以自己筛选得到
+

4.1、保存并部署部署
+

1.1、点workers.dev (点击此链接前需要科学上网)
+
1.2、如果看到下面的代码说明部署成功
+
在连接后面添加UserID(就是workers代码中UserID),注意添加反斜线 “ / ”,然后回车
+
得到未套Tls的Vless订阅连接
+
操作前需确定域名已经成功托管到Cloudflare上
+1.1、点击刚刚添加的edge应用
+
1.2、点击【查看】,或者点击【触发器】
+
1.3、点击【添加自定义域】
+
1.4、输入一个二级域名,名称随便起。二级域名要填写完整(二级域名.temptemps.top)
+然后点击下面的添加自定义域按钮
+
1.5、添加自定义域后续等待证书申请完成
+
看到有效即证书申请完成
+
2.1、点击刚刚生成的二级域名
+
2.2、看到下面代码说明部署成功
+
3.1、在域名后添加UserID回车,得到以套Tls的vless订阅连接
+
1.1、复制未加密vless订阅连接,粘贴到v2rayN客户端
+
1.2、打开v3rayN客户端,从剪贴板导入批量URL或者直接按Ctrl+V
+

1.3、双击连接打开配置页面,因为是为加密的vless订阅,不能使用443端口,将443端口改为80,传输层安全(Tls):设置为空
+总共有7个端口可选,可以任选其中一个: [80, 8080, 8880, 2052, 2086, 2095, 2082]
+
2.1、复制生成的Vless订阅连接
+
2.2、粘贴到v2rayN中
+
Vless添加后不用修改设置,如果出现不能访问的情况,可将地址修改为优选ip或优选域名
+Tls端口总共有6个端口可选,可以任选其中一个: [443, 8443, 2053, 2096, 2087, 2083]
+
本地IP优选网址: http://ip.flares.cloud/
+在线IP优选网址: https://stock.hostmonit.com/CloudFlareYes
+在线IP优选网址: https://monitor.gacjie.cn/page/cloudflare/ipv4.html
+在线IP优选网址: https://monitor.gacjie.cn/page/cloudflare/cname.html
+在线IP优选网址: https://api.uouin.com/cloudflare.html
*.cloudflare.182682.xyz
+115155.xyz
+cdn.2020111.xyz
+cf.0sm.com
+cf.090227.xyz
+f3058171cad.002404.xyz
+cf.zhetengsha.eu.org
+8.889288.xyz
+cf.zerone-cdn.pp.ua
+cf.cdn.bingbook.cn
+achk.cloudflarest.link
+cfip.1323123.xyz
+cf.877771.xyz
+cnamefuckxxs.yuchen.icu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ mac 安装
+brew update
+brew install go
+
++ ++
设置代理环境变量,再拉去golang.org的时候就不需要墙了。注意GO1.13及之后支持direct的写法
+go env -w GOPROXY=https://goproxy.cn,direct
+
+新建项目后需要在项目根目录执行
+ sh
+ go mod init {项目名}
#### [Go] 解决missing go.sum entry for module providing package
当在代码中使用了第三方库 ,但是go.mod中并没有跟着更新的时候
+如果直接run或者build就会报这个错误
+missing go.sum entry for module providing package
可以使用go mod tidy 来整理依赖
+这个命令会:
+删除不需要的依赖包
+下载新的依赖包
+更新go.sum
+ + + + + + + + + + + + + + + + + + + + + + + + + +这是 ANote 分类栏目页。
+mac 安装目录:/usr/local/Cellar/jenkins-lts/2.235.5/bin
++说明:原文引用的本地资源
+image/jenkins记录/LabRat.gif当前不在仓库中,暂以占位图保留阅读结构,后续如找回原 GIF 可直接替换。
bilibili的 GAMES101-现代计算机图形学入门-闫令琪
+庄懂-BoyanTata +- [ ] 第二节没有完成 (屏幕空间使用,及素描风格)
+ +[ ] z缓冲消隐算法 (投影矩阵提到)
+[ ] Mapcap昆虫最后的颜色为啥是相乘和相加?(第一章第四节)
+[ ] 视差贴图,视差映射 ,视差偏移没有仔细学习(第二章第二节)。
+[ ] 绕y轴旋转的旋转矩阵怎么写是对的,需要和unity Transform旋转一致(第三章第一节 Cubemap旋转) + >https://wgqing.com/unity%E7%9F%A9%E9%98%B5%E5%8F%98%E6%8D%A2%E4%BB%8E%E6%A8%A1%E5%9E%8B%E7%A9%BA%E9%97%B4%E5%88%B0%E5%B1%8F%E5%B9%95%E7%A9%BA%E9%97%B4%E7%9A%84%E8%BD%AC%E6%8D%A2/
+[ ] 次表面散射,Skin_LUT (第四章第二节),皮肤阴影区域的SSS效果调整不理想
+socket测试结果(基于发送和接收缓冲区没有缓存的情况下测试,有缓存也一样) +SocketOptionName.Linger=false 且没有异步或同步监听接受接口,close 后发送Fin包(close没有延时)(LingerState参数无影响) +SocketOptionName.Linger=false 且有异步或同步监听接受接口,close 后发送Rst包(close没有延时)(LingerState参数无影响)(socket的都关闭了还接收数据肯定错误)
+SocketOptionName.Linger=false 且没有异步或同步监听接受接口,close 后发送Fin包(close延时1秒) +SocketOptionName.Linger=false 且有异步或同步监听接受接口,close 后发送Fin包(close延时1秒)
+SocketOptionName.Linger=true 且没有异步或同步监听接受接口,close 后发送Rst包(close没有延时) +SocketOptionName.Linger=true 且有异步或同步监听接受接口,close 后发送Rst包(close没有延时)
+SocketOptionName.Linger=true 且没有异步或同步监听接受接口,close 后发送Fin包(close延时1秒) +SocketOptionName.Linger=true 且有异步或同步监听接受接口,close 后发送Fin包(close延时1秒)
+Socket.Shutdown(SocketShutdown.Receive); 只是通知内核缓冲区接收消息的流,我不收数据了,不给对端任何消息。 +直到对端发消息过来后才给对端发送Rst包告诉他网络重置(出错了)。 (自己这个时候在发送数据提示shutdown错误) +Socket.Shutdown(SocketShutdown.Send)只是关闭了内核缓冲区发送消息的流 给对端发送Fin包 受到ack包后会受到0字节长度的消息。 +(对端收到0长度消息后如果继续Receive消息:内核自己会给用户态返回0长度消息) +shutdown 只是关闭网络连接 并没有关闭socket文件描述符,所以还得用close()关闭文件描述符
+大端小端 +如果自己设置RecvBuffSize=0 网卡接收数据会另外开辟一块缓冲区存放数据直到用户态调用Receive返回给用户
+jit(动态编译)和aot(静态编译)
+ + + + + + + + + + + + + + + + + + + + + + + + + +++ + + + + + + + + + + + + + + + + + + + + + + + + +Odin 收费 +Odin - Inspector and Serializer
+Unity3D研究院编辑器之不影响原有布局拓展Inspector(二十四)
+ +
+https://gist.github.com/liortal53/352fda2d01d339306e03
+拓展RectTransform等 +
请求网址: http://10.24.0.24:8889/executeCmd
+请求方法: POST
+状态代码: 200
+远程地址: 10.24.0.24:8889
+引荐来源网址政策: strict-origin-when-cross-origin
+
+响应Header(response headers)
+Connection: keep-alive
+Content-Length: 6
+Content-Type: text/html;charset=utf-8
+Date: Wed, 30 Mar 2022 08:22:51 GMT
+Keep-Alive: timeout=60
+
+请求Header(request header)
+Accept: */*
+Accept-Encoding: gzip, deflate
+Accept-Language: zh-CN,zh;q=0.9
+Connection: keep-alive
+Content-Length: 56
+Content-Type: application/json
+Host: 10.24.0.24:8889
+Origin: http://10.24.0.24:8889
+Referer: http://10.24.0.24:8889/cmd
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36
+X-Requested-With: XMLHttpRequest
+
+++Accept-Language: 支持的语言类型,类似于一个数组,每种语言对应一个权重(0-1的数字),一般用来做多语言用
+Content-Type: 请求体的类型
+
+设置响应内容的类型和编码, 经常配合mime模块使用
// 常用几种类型
+
+application/x-www-form-urlencoded // a=1&b=2
+application/json // {"a": "1", "b": "2"}
+multipart/form-data //文件类型
+
+application/x-www-form-urlencoded;charset=utf-8
+
+++Referer: 资源在哪个网站中被使用,来源。可篡改,重要的东西不可依赖于他
+Accept-Encoding: 浏览器支持的格式
+Origin: 当前网站的来源,只支持post请求
+Cookie: 每次发请求,都会带到服务端
+If-Modified-Since: 缓存相关,详见下文的 Last-Modified
+If-None-Match: 缓存相关,详见下文的 Etag
+Range: Range:bytes=n-m, 请求内容的第n 到m 的字节
+
服务端设置res.setHeader('Content-Range', 'bytes n-m/total'),
+并设置状态码res.statusCode = 206
+
+++Content-Type: 设置响应内容的类型和编码, 经常配合mime模块使用
+
res.setHeader('Content-Type', mime.getType(req.url) + ';charset=utf-8' )
+
+++Cache-Control: 强制缓存,设置缓存有效时长(比expires靠谱)
+
res.setHeader('Cache-Control', 'no-cache') //不设置强制缓存
+res.setHeader('Cache-Control', 'max-age=3600') //强制缓存1小时, 单位:秒
+
+++Expires: 强制缓存 单位:毫秒,设置缓存失效的一个时间点
+
res.setHeader('Expires', new Date( Date.now() + 3600 * 1000 ).toLocaleString())
+
+++Last-Modified: 上次修改时间
+
// 通过fs.stat 拿到 statRes, 拿到上次修改的时间
+// 设置给Last-Modified, 浏览器下次访问这个文件时候
+// 会带上一个头If-Modified-Since, 取出值old
+// 再次通过fs.stat获取修改时间,和old进行比较
+// 若相同,则设置304,并end,否则重新设置Last-Modified并重新返回文件
+res.setHeader('Last-Modified', statRes.ctime.toGMTString())
+
+++Etag: 存放根据文件内容计算出的价签字符串
+
// sign的计算可以根据实际情况来定,可以用md5文件内容加签得到一个值,
+// 常用的是文件内容的 修改时间的16进制+内容长度的16进制值,
+// 也可以通过其他算法, 得到一个不规则字符串,
+// 设置到Etag以后,浏览器下次访问这个文件,会带上一个头If-None-Match
+// 取到值old以后,重新根据文件内容用相同算法计算一个sign,和old比较,
+// 若相同,则设置304,并end,否则重新设置Etag并重新返回文件
+res.setHeader('Etag', sign);
+
+
+3种缓存可以同时使用, Last-Modified和Etag是并且关系,有一个没对上,就重新发送文件
+++Content-Range: 返回部分内容,和响应头Range配合使用
+Content-Length: 响应内容的字节长度
+Access-Control-Allow-Origin: 允许哪个源来访问
+
可以设置成 *(所有网站) ,也可以设置一个白名单,当当前访问的域名在白名单中,
+就设置成当前访问的域名
+
+++ + + + + + + + + + + + + + + + + + + + + + + + + +Access-Control-Allow-Methods: 允许哪些方法访问,GET,POST,PUT,DELETE。。。
+Access-Control-Allow-Credentials:是否可携带cookie
+
+需要前端ajax也设置xhr.withCredentials = true;Content-Encoding: 告诉浏览器,按照那种方式解析文件
+
数学公式在线:https://www.codecogs.com/latex/eqneditor.php
+数学公式在线:https://latex.codecogs.com
++ + +一些记录¶
++
+- bsdiff差分算法
+- 出正式包怎么剥离无用的dll,比如log插件dll,Debug库,绘制插件dll(仅dev使用)参考
++
+- 这里可以在打包正式包时将dll删除后打包
+
主要问题:纹理,Shader,FBX动画
+ +
+使用第三方无损压缩,减小文件大小,这里文件大小是原来的1/3。
使用图片无损压缩打包测试,图片属性:RGBA 2048*2048,使用RGBA Crunched ETC2内存压缩
+| |压缩尺寸|压缩前|压缩后
+|-|-|-|-|
+|图片文件||1564kb|481kb|
+|AB文件|2048|268kb|260kb|
+|AB文件|1024|96kb|89kb|
+可以发现压缩后的图片打包AB文件比压缩前小大概7kb左右
++纹理的压缩格式影响AB包的大小 (Android平台测试)
+
+使用ETC2压缩生成AB包比使用 Crunched ETC生成AB包大3倍(这里只是说明压缩格式影响AB包文件大小,不说明压缩格式和包体的具体关系)。
Android平台
+
使用ETC 4Bit压缩后
+
使用 Crunched ETC 压缩(需要真机测试性能及是否支持该压缩格式),这个格式消耗cpu,进入GPU处理阶段会解压成etc压缩,GPU本身不支持 Crunched ETC压缩
+
++ +推荐 Android 和IOS都用ASTC;
+
+参考:ASTC纹理压缩格式详解
+++
+主要是animation和纹理占用比较大,每个带动画的角色都是这样的情况 +
++压缩大小比10倍,待测,不知道animation压缩怎么样
+
设置FBX Animation Compress格式
+使用R220002@ui_comeout.fbx动画文件测试
现在的FBX配置
+
+
+文件大小是4322kb,打包AB文件大小是1553kb
修改FBX配置(没有勾选 Resample Curves)
+
+
+文件大小是4322kb,打包AB文件大小是499kb (这里fbx文件本身大小没变,untiy不修改源文件)
+
+修改后的内存大小是原来的1/4,Ab文件是原来的1/3 (动画表现一致)
使用脚本剔除无用scale数据及修改数据精度(没有勾选 Resample Curves)
+这种方式每次animation导入修改后都不会保持,因为untiy默认重新导入animation并计算数据,需要在animation后处理中处理,每次变化都要执行一遍
+
+
+文件大小是4322kb,打包AB文件大小是310kb
+修改后内存更低了和原始比是原来的不到1/4,AB文件是原来的1/5 (这里fbx文件本身大小没变,untiy不修改源文件) (动画表现一致)
如果所有的角色动画都进行调整,包体可以明显减小
+fbx导入到Unity之后是不会变化的,就是说Unity里面的所有编辑都不会保存在FBX文件里,可能在meta文件中,这里没有测试。
+注意压缩后需要真机测试看看动画是否一致,这里是win editor测试的,没有真机测试
+++ + +参考:
+
+https://blog.uwa4d.com/archives/Optimization_Animation.html
+https://zhuanlan.zhihu.com/p/353402448
+https://www.bzetu.com/344/.html
+https://blog.uwa4d.com/archives/UWA_Pipeline22.html处理Animation时遇到的问题:
+
+
+
+一个fbx上出现两个animation 动画,__preview_Take 001应该是美术没有删除干净 +打包AB文件没有发现__preview_Take 001资源打包进去
++现有加载策略: +1. 根据manifest配置文件进行加载资源并且找到依赖进行加载 +2. 只有同步加载没有异步加载 +3. 加载的Bundle和LoadAsset资源都做了缓存,但是没有引用计数
+现有资源卸载策略:
+
+1. lua Bundle加载资源后直接卸载bundle; +1. 场景加载后将bundle卸载,在切换场景时: + - 执行LuaGC处理 + - 清理所有Asset的引用(将缓存的Dict清空)(资源泄露:bundle没有卸载,清空了asset引用,再次加载资源时会再次LoadAsset,会存在多份相同asset;加载效率利用率低,没有区分常驻内存资源和非常驻内存资源,bundle管理粗放) + +1. 非场景资源加载后没有卸载流程,全部依靠场景卸载时清理 +1. 除了上述bundle卸载之外,游戏运行中没有卸载bundle,内存占用过高容易崩溃 +1. 业务层只管加载,不处理卸载 +1.Resources.UnloadUnusedAssets()没有调用(场景加载时会自动调用,记不清了)
调整方案: +1. 同步和异步加载 +2. 使用引用计数方案对Bundle和Asset管理 +2. 增加图集管理(图集制作细分方案待定) +2. 使用加载和卸载成对管理,或者做内置管理和实例对象绑定
+Universal Render Pipeline/Lit has too many Shader variants(150994944)
+
+URP 自带Lit Shader变体太多问题
Assertion failed on expression:`m_UserPathRemap.count(pathStr) == 0`
+
可能因为在Project Setting-->Graphing设置All include 然后打包又打进去所以报错了
+
+左侧蓝色是优化后的,右侧红色是优化前的
关闭Bundle文件的type information数据写入,这是为了使用Unity版本不同做的兼容(标记数据,unity版本相同无用)
//BuildAssetBundleOptions.DisableWriteTypeTree
+BuildPipeline.BuildAssetBundles(dir,ls.ToArray(),BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.ChunkBasedCompression|BuildAssetBundleOptions.DisableWriteTypeTree, BuildTarget.Android);
+
+
+红色是开启默认type information写入,asset classes就是写入的数据,左侧蓝色是关闭type information写入的大小
+bundle文件大小差5k多(每个bundle文件内容不一样,大小也不一样),还是很可观的
+ ++
Graphics APIs 只保留OpenGLEL3就可以了,否则打包Shader时每个平台生成一套代码导致AB包过大
+
Graphics -> Tier Settings默认三种配置,每个shader会生成对应三套代码,这里将Tier三种配置设置成相同的,则Shader只会生成一套代码,减小包体
+
#pragma multi_compile和#pragma shader_feature测试 #pragma multi_compile COM_M COM_N
+ #pragma multi_compile COM_X COM_Y COM_Z
+ #pragma shader_feature _ A B
+ #pragma shader_feature E F
+
+打包的AB包中的Shader keywords数量
+
+multi_compile两行的keywords组合相乘是最后的shader代码的数量,注意数量
+这里#pragma shader_feature 如果第一项是_则认为没有keywords,如果不是_,则默认打包进第一个选项(这里是E)
#pragma multi_compile COM_M COM_N
+ #pragma multi_compile COM_X COM_Y COM_Z
+ #pragma shader_feature _ A B
+ #pragma shader_feature E F
+
+打包的AB包中的Shader keywords数量
+材质球keywords默认:
+
+
材质球指定keywords后
+
+
+指定keywords后等于选择shader_feature的变体,multi_compile进行全部组合
总结:材质球指定shader_feature keywords后和shader打包到AB文件中,shader的keywords是受材质球的keywords配置影响的;加入了GraphicsSetting-> always included shader后,会将它所有的keywords变体打包到游戏中
可以用Shader Variant Collection单独控制keywords,把collection和shader打包到一个AB文件中
+
++参考: +https://zhuanlan.zhihu.com/p/68888831
+
+https://blog.csdn.net/eevee_1/article/details/118632371
+https://zhuanlan.zhihu.com/p/392004640 +https://blog.csdn.net/danteshenqu/article/details/78170745 +https://zhuanlan.zhihu.com/p/83780152关于
+GraphicsSetting-> always included shader说明:
+如果打AB时不想shader被打包进AB包,则用always included shader添加shader,单只能是unity buildin 内置shader(Packages内的shader不是内置的)。
+
+
+cube11和cube1使用的shader是Unlit/Color,Cube22和Cube2使用的shader是custom/Cube2;每个ab文件内只有一个cube物体的prefab,引用一个材质球和对应的shader
+AB文件cube11和cube22是在always included shader添加前打包的,AB文件Cube1和Cube2是在always included shader添加后打包的
+根据上面打包测试分析出结果:
+- Cube22和Cube2使用自定义shadercustom/Cube2,不管always included shader内是否添加该shader,打AB包都会把该shader打包进去
+- Cube11和Cube1使用内置shaderUnlit/Color,always included shader内添加该shader,打包AB包不会把该内置shader打包进去;否则会打包进AB包内 +- URP内Shader算是自定义shader,不管always included shader内是否添加,都会将使用的URP Shader打包到对应AB包中
++ + + + + + + + + + + + + + + + + + + + + + + + + +由于
+URP内部的Shader在Packages中,不能使用Inspector面板设置AssetBundleName及脚本代码设置AssetBundleName。在Graphics面板中添加URP shader,但是打包还是会被打进去 +解决方案: + 1. 使用AssetBundleBuild方式打包可以控制Packages内的资源 + 2. 使用Addressables官方插件打包可以设置Packages内的资源材质球和shader打包到一起,会根据材质球引用的keywords变体打包,而加入了
+GraphicsSetting-> always included shader后,会将它所有的keywords变体打包到游戏中关于shader变体说明:https://blog.csdn.net/kuangben2000/article/details/105400835
+
++线程是调度的基本单位,进程则是资源分配的基本单位
+
pipe¶++所谓的管道,就是内核⾥⾯的⼀串缓存
+
+管道这种通信⽅式效率低,不适合进程间频繁地交换数据
匿名管道 |
ps auxf | grep mysql
+
+命名管道 FIFO
# 创建命名管道
+mkfifo myPipe
+
+mrwang@CodingdeMBP learn % mkfifo myPipe
+# linux 一切皆是文件,管道也是文件 类型 p
+mrwang@CodingdeMBP learn % ls -l
+total 0
+prw-r--r-- 1 mrwang staff 0 2 13 14:55 myPipe
+mrwang@CodingdeMBP learn %
+
+++消息队列是保存在内核中的消息链表 +在发送数据时,会分成⼀个⼀个独⽴的数据单元,也就是消 +息体(数据块),消息体是⽤户⾃定义的数据类型,消息的发送⽅和接收⽅要约定好消息体的数据类型,所以每个消息体都是固定⼤⼩的存储块,不像管道是⽆格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
+消息队列⽣命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会⼀直存在,⽽前⾯提到的匿名管道的⽣命周期,是随进程的创建⽽建⽴,随进程的结束⽽销毁。
+消息队列不适合⽐较⼤数据的传输,因为在内核中每个消息体都有⼀个最⼤⻓度的限制,同时所有队列所包含的全部消息体的总⻓度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAX 和 MSGMNB ,它们以字节为单位,分别定义了⼀条消息的最⼤⻓度和⼀个队列的最⼤⻓度。
+消息队列通信过程中,存在⽤户态与内核态之间的数据拷⻉开销,因为进程写⼊数据到内核中的消息队列时,会发⽣从⽤户态拷⻉数据到内核态的过程,同理另⼀进程读取内核中的消息数据时,会发⽣从内核态拷⻉数据到⽤户态的过程。
+
++共享内存的机制,就是拿出⼀块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写⼊的东⻄,另外⼀个进程⻢上就能看到了,都不需要拷⻉来拷⻉去,传来传去,⼤⼤提⾼了进程间通信的速度。
+⽤了共享内存通信⽅式,带来新的问题,那就是如果多个进程同时修改同⼀个共享内存,很有可能就冲突了。例如两个进程都同时写⼀个地址,那先写的那个进程会发现内容被别⼈覆盖了。
+
++为了防⽌多进程竞争共享资源,⽽造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被⼀个进程访问。正好,信号量就实现了这⼀保护机制。
+信号量其实是⼀个整型的计数器,主要⽤于实现进程间的互斥与同步,⽽不是⽤于缓存进程间通信的数 +据。
+
++上⾯说的进程间通信,都是常规状态下的⼯作模式。对于异常情况下的⼯作模式,就需要⽤「信号」的⽅
+信号是进程间通信机制中唯⼀的异步通信机制,因为可以在任何时候发送信号给某⼀进程,⼀旦有信号产⽣,我们就有下⾯这⼏种,⽤户进程对信号的处理⽅式 +式来通知进程。
+
+1.执⾏默认操作。Linux 对每种信号都规定了默认操作,例如,上⾯列表中的 SIGTERM 信号,就是终⽌进程的意思。
+2.捕捉信号。我们可以为信号定义⼀个信号处理函数。当信号发⽣时,我们就执⾏相应的信号处理函数。
+3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应⽤进程⽆法捕捉和忽略的,即 SIGKILL 和 SEGSTOP ,它们⽤于在任何时候中断或结束某⼀进程。
++前⾯提到的管道、消息队列、共享内存、信号量和信号都是在同⼀台主机上进⾏进程间通信,那要想跨⽹络与不同主机上的进程之间通信,就需要 Socket 通信了。
+
+实际上,Socket 通信不仅可以跨⽹络与不同主机的进程间通信,还可以在同主机上进程间通信。
根据创建 socket 类型的不同,通信的⽅式也就不同:
+- 实现 TCP 字节流通信: socket 类型是 AF_INET 和 SOCK_STREAM;
+- 实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
+- 实现 同一台主机上进程间通信: 「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以AF_UNIX 也属于本地 socket;
本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端⼝,⽽是绑定⼀个本地⽂件,这也就是它们之间的最⼤区别。
+由于每个进程的⽤户空间都是独⽴的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享⼀个内核空间。
+Linux 内核提供了不少进程间通信的⽅式,其中最简单的⽅式就是管道,管道分为「匿名管道」和「命名管道」。
+匿名管道顾名思义,它没有名字标识,匿名管道是特殊⽂件只存在于内存,没有存在于⽂件系统中,shell命令中的「 | 」竖线就是匿名管道,通信的数据是⽆格式的流并且⼤⼩受限,通信的⽅式是单向的,数据只能在⼀个⽅向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能⽤于存在⽗⼦关系的进程间通信,匿名管道的⽣命周期随着进程创建⽽建⽴,随着进程终⽌⽽消失。
+命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使⽤命名管道的前提,需要在⽂件系统创建⼀个类型为 p 的设备⽂件,那么毫⽆关系的进程就可以通过这个设备⽂件进⾏通信。另外,不管是匿名管道还是命名管道,进程写⼊的数据都是缓存在内核中,另⼀个进程读取数据时候⾃然也是从内核中获取,同时通信数据都遵循先进先出原则,不⽀持 lseek 之类的⽂件定位操作。
+消息队列克服了管道通信的数据是⽆格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以⽤户⾃定义的数据类型,发送数据时,会被分成⼀个⼀个独⽴的消息体,当然接收数据时,也要与发送⽅发送的消息体的数据类型保持⼀致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写⼊和读取都需要经过⽤户态与内核态之间的拷⻉过程。
+共享内存可以解决消息队列通信中⽤户态与内核态之间数据拷⻉过程带来的开销,它直接分配⼀个共享空间,每个进程都可以直接访问,就像访问进程⾃⼰的空间⼀样快捷⽅便,不需要陷⼊内核态或者系统调⽤,⼤⼤提⾼了通信的速度,享有最快的进程间通信⽅式之名。但是便捷⾼效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。
+那么,就需要信号量来保护共享资源,以确保任何时刻只能有⼀个进程访问共享资源,这种⽅式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是⼀个计数器,表示的是资源个数,其值可以通过两个原⼦操作来控制,分别是 P 操作和 V 操作。
+与信号量名字很相似的叫信号,它俩名字虽然相似,但功能⼀点⼉都不⼀样。信号是进程间通信机制中唯⼀的异步通信机制,信号可以在应⽤进程和内核之间直接交互,内核也可以利⽤信号来通知⽤户空间的进程发⽣了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令),⼀旦有信号发⽣,进程有三种⽅式响应信号 1. 执⾏默认操作、2. 捕捉信号、3. 忽略信号。有两个信号是应⽤进程⽆法捕捉和忽略的,即 SIGKILL 和 SEGSTOP ,这是为了⽅便我们能在任何时候结束或停⽌某个进程。
+前⾯说到的通信机制,都是⼯作于同⼀台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅⽤于不同的主机进程间通信,还可以⽤于本地主机进程间通信,可根据创建Socket 的类型不同,分为三种常⻅的通信⽅式,⼀个是基于 TCP 协议的通信⽅式,⼀个是基于 UDP 协议的通信⽅式,⼀个是本地进程间通信⽅式。
+++线程之间是可以共享进程的资源,⽐如代码段、堆空间、数据段、打开的⽂件等资源,但每个线程都有⾃⼰独⽴的栈空间。
+
+那么问题就来了,多个线程如果竞争共享资源,如果不采取有效的措施,则会造成共享数据的混乱。
当获取不到锁时,线程就会⼀直 wile 循环,不做任何事情,所以就被称为「忙等待锁」,也被称为 ⾃旋锁(spin lock)。 +那当没获取到锁的时候,就把当前线程放⼊到锁的等待队列,然后执⾏调度程序,把 CPU让给其他线程执⾏,称为 「⽆等待锁」。
+++最底层的两种就是会「互斥锁和⾃旋锁」,有很多⾼级的锁都是基于它们实现的,你可以认为它们是各种锁的地基,所以我们必须清楚它俩之间的区别和应⽤。 +加锁的⽬的就是保证共享资源在任意时间⾥,只有⼀个线程访问,这样就可以避免多线程导致共享数据错乱的问题。
+
当已经有⼀个线程加锁后,其他线程加锁则就会失败,互斥锁和⾃旋锁对于加锁失败后的处理⽅式是不⼀样的:
+互斥锁是⼀种「独占锁」,⽐如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放⼿中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,⾃然线程 B 加锁的代码就会被阻塞。
+对于互斥锁加锁失败⽽阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为「睡眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执⾏。如下图:
+
+所以,互斥锁加锁失败时,会从⽤户态陷⼊到内核态,让内核帮我们切换线程,虽然简化了使⽤锁的难度,但是存在⼀定的性能开销成本。
那这个开销成本是什么呢?会有两次线程上下⽂切换的成本:
+- 当线程加锁失败时,内核会把线程的状态从「运⾏」状态设置为「睡眠」状态,然后把 CPU 切换给其他线程运⾏;
+- 接着,当锁被释放时,之前「睡眠」状态的线程会变为「就绪」状态,然后内核会在合适的时间,把CPU 切换给该线程运⾏。
线程的上下⽂切换的是什么?当两个线程是属于同⼀个进程,因为虚拟内存是共享的,所以在切换时,虚 +拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。 +上下切换的耗时有⼤佬统计过,⼤概在⼏⼗纳秒到⼏微秒之间,如果你锁住的代码执⾏时间⽐较短,那可 +能上下⽂切换的时间都⽐你锁住的代码执⾏时间还要⻓。 +所以,如果你能确定被锁住的代码执⾏时间很短,就不应该⽤互斥锁,⽽应该选⽤⾃旋锁,否则使⽤互斥 +锁。 +⾃旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「⽤户态」完成加锁和解锁操作,不 +会主动产⽣线程上下⽂切换,所以相⽐互斥锁来说,会快⼀些,开销也⼩⼀些。 +⼀般加锁的过程,包含两个步骤: +第⼀步,查看锁的状态,如果锁是空闲的,则执⾏第⼆步; +第⼆步,将锁设置为当前线程持有; +CAS 函数就把这两个步骤合并成⼀条硬件级指令,形成原⼦指令,这样就保证了这两个步骤是不可分割 +的,要么⼀次性执⾏完两个步骤,要么两个步骤都不执⾏。 +使⽤⾃旋锁的时候,当发⽣多线程竞争锁的情况,加锁失败的线程会「忙等待」,直到它拿到锁。这⾥的 +「忙等待」可以⽤ while 循环等待实现,不过最好是使⽤ CPU 提供的 PAUSE 指令来实现「忙等 +待」,因为可以减少循环等待时的耗电量。 +⾃旋锁是最⽐较简单的⼀种锁,⼀直⾃旋,利⽤ CPU 周期,直到锁可⽤。需要注意,在单核 CPU 上,需 +要抢占式的调度器(即不断通过时钟中断⼀个线程,运⾏其他线程)。否则,⾃旋锁在单 CPU 上⽆法使 +⽤,因为⼀个⾃旋的线程永远不会放弃 CPU。 +⾃旋锁开销少,在多核系统下⼀般不会主动产⽣线程切换,适合异步、协程等在⽤户态切换请求的编程⽅ +式,但如果被锁住的代码执⾏时间过⻓,⾃旋的线程会⻓时间占⽤ CPU 资源,所以⾃旋的时间和被锁住的 +代码执⾏的时间是成「正⽐」的关系,我们需要清楚的知道这⼀点。 +⾃旋锁与互斥锁使⽤层⾯⽐较相似,但实现层⾯上完全不同:当加锁失败时,互斥锁⽤「线程切换」来应 +对,⾃旋锁则⽤「忙等待」来应对。 +它俩是锁的最基本处理⽅式,更⾼级的锁都会选择其中⼀个来实现,⽐如读写锁既可以选择互斥锁实现, +也可以基于⾃旋锁实现。
+https://ckey.run/ + 激活: + Windows:复制命令 irm ckey.run|iex使用Win + X按键,选择WindowsPowerShell(管理员)运行即可全自动激活! + Linux:复制命令 wget -q ckey.run -O ckey.run && bash ckey.run使用终端执行即可全自动激活! + Mac:复制命令 curl -Ls ckey.run -o ckey.run && bash ckey.run使用终端执行即可全自动激活!
+++已经失效 +1. 打开链接 https://3.jetbra.in/ +2. 随便点击下图中一个可用的链接,进入之后看下一步。 +
++3. 先先下载
jetbra.zip+
下载后解压,解压文件夹放到一个文件夹下(目录没有空格和中文)
++
关闭rider,然后运行
+jetbra\scripts\install-all-users.vbs脚本,等待执行完,出现Done +4. 点击Rider图标,复制key+
然后粘贴激活 +
++
jdk的下载可以直接在官网进行,链接为http://www.oracle.com/technetwork/java/javase/downloads/index.html
+至于版本下载8还是9请自行决定,推荐8版本
+
添加环境变量
+
+
+
JAVA_HOME
+C:\Program Files\Java\jdk1.8.0_121
+
+CLASSPATH
+%JAVA_HOME%\lib;%JAVA_HOME%\lib\tool.jar
+
+在变量Path中追加
+%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
+
+++ + + + + + + + + + + + + + + + + + + + + + + + + +https://doc.sun-panel.top/zh_cn/
+
+https://registry.hub.docker.com/r/hslr/sun-panel/
+https://www.zhihu.com/question/603336478/answer/3406593764
网络 json zip压缩,10000字节压缩后大约是2000字节,100字节压缩后大约100或者大约100
+热更新 apple store 审查到发布 到玩家看到版本包有一 +个过程 (审查版本,发布后打开后热更(审查屏蔽的东西),老版本热更(不更整个包)支持) + LZMA 4倍,,LZ4 3倍,
+加密 streamassetpath加载到内存中在android上只有WWW,不能用别的,ab文件的字节流没有办法加载到内存中然后解密,所以直接加密lua文件,在加载lua时解密
+设计模式
+文字加载(斜体)
+ + + + + + + + + + + + + + + + + + + + + + + + + +按行优先填充的按列优先填充的++Matrix4x4中,
+matrix.m12这个表示第一行二列,
+
//一个带有由所传递的平面坐标定义的视锥截面的投影矩阵
+public static Matrix4x4 Frustum(float left, float right, float bottom, float top, float zNear, float zFar);
+//构造正交矩阵(这个在之前的贴花着色器脚本代码中用到过)
+public static Matrix4x4 Ortho(float left, float right, float bottom, float top, float zNear, float zFar);
+//构造透视投影矩阵
+public static Matrix4x4 Perspective(float fov, float aspect, float zNear, float zFar);
+//构造旋转矩阵
+public static Matrix4x4 LookAt(Vector3 from, Vector3 to, Vector3 up);
+public static Matrix4x4 Rotate(Quaternion q);
+//构造缩放矩阵
+public static Matrix4x4 Scale(Vector3 vector);
+//从位置,旋转,缩放构建矩阵
+public static Matrix4x4 TRS(Vector3 pos, Quaternion q, Vector3 s);
+
+//转置
+public Matrix4x4 transpose { get; }
+//是否为单位矩阵
+public bool isIdentity { get; }
+//行列式
+public float determinant { get; }
+//逆
+public Matrix4x4 inverse { get; }
+
+LookAt 实现¶ /// <summary>
+ /// LookAt
+ /// 同时也是基变换的一种矩阵
+ /// </summary>
+ /// <param name="from">eye</param>
+ /// <param name="to">target</param>
+ /// <param name="up"></param>
+ /// <returns></returns>
+ public static Matrix4x4 LookAt(Vector3 from,Vector3 to,Vector3 up)
+ {
+ // link: https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
+ Vector3 z=Vector3.Normalize(to-from); // cam dir , cam forward 前(z轴)
+ Vector3 x=Vector3.Normalize(GeometryTools.Cross(up,z)); // cam right dir 右(x轴)
+ Vector3 y=GeometryTools.Cross(z,x); // cam up dir 上(y轴)
+ Matrix4x4 mat=new Matrix4x4();
+ mat.SetColumn(0,x);
+ mat.SetColumn(1,y);
+ mat.SetColumn(2,z);
+ mat.SetColumn(3,new Vector4(from.x,from.y,from.z,1));
+ return mat;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ffmpeg -i "concat:1.mp4|2.mp4|3.mp4" -codec copy out_mp4.mp4
+
+ffmpeg -f concat -safe 0 -i join.txt -c copy output.mp4
+
+join.txt文件
file /home/sk/myvideos/part1.mp4
+file /home/sk/myvideos/part2.mp4
+file '/home/sk/myvideos/part3.mp4'
+file '/home/sk/myvideos/part4.mp4'
+
+# 切分视频文件为多个部分
+ffmpeg -i part.mp4 -t 00:40:22 -c copy part1-1.mp4 -ss 00:40:22 -t 01:37:15 -codec copy part1-2.mp4 -ss 02:17:37 -codec copy part1-3.mp4
+
+
+
+
+ffmpeg -i audio.mp3 -ss 00:01:54 -to 00:06:53 -c copy output.mp3
+
+ffmpeg -i input.mp4 -ss 00:00:50 -codec copy -t 50 output.mp4
+
+# –s – 表示视频剪辑的开始时间。在我们的示例中,开始时间是第 50 秒。
+# -t – 表示总的持续时间。
+
+给mkv文件添加srt格式的软字幕:
+ffmpeg -i input.mkv -i input.srt -c copy -c:s srt output.mkv
+
+给mp4文件添加srt格式的软字幕(mkv格式本身支持字幕轨道,但mp4需要使用mov_text)
+ffmpeg -i input.mp4 -i input.srt -c copy -c:s mov_text output.mp4
+
+给mkv文件添加ass软字幕(mp4不支持ass编码格式的字幕流,mkv支持)
+ffmpeg -i input.mkv -i input.ass -c copy -c:s ass output.mkv
+
+增加字幕流
+ffmpeg -i video.avi -i sub.ass -map 0:0 -map 0:1 -map 1 -c:a copy -c:v copy -c:s copy video.mkv
+
+提取字幕流 +1)原始文本输出
+ffmpeg -i output.mkv -an -vn -bsf:s mov2textsub -scodec copy -f rawvideo sub.txt
+ffmpeg -i output.mkv -an -vn -c:s copy -f rawvideo -map 0:s sub2.txt
+
+2)ass格式输出
+ffmpeg -i output.mkv -an -vn -scodec copy sub3.ass
+
+ffmpeg -ss 00:43:55 -i video.mp4 -f image2 -vframes 1 -y frame.png
+# 注意将ss放到最前面可以加快速度, -y代表覆盖文件 -vframes代表帧数 -i代表输入,即in;-ss也可以使用单个数字,代表秒数,从0开始计算。
+
+ffmpeg -i video.mp4 -vf "delogo=x=1680:y=60:w=160:h=55" -y new_1.mp4
+
+
+这里-vf表示video filter, 其中delogo的参数代表水印的坐标和大小,把视频左上角作为坐标原点,横向为x轴,纵向为y轴。这种情况除非预先知道水印的位置和大小,否则不是特别方便,当然,准确识别水印位置也是一个难点,不是很轻易能实现的。 +可能根据某些ffmpeg版本不同,需要加-strict experimental 参数,一种情况是比较老的版本音频ACC属于实验阶段,可以按情况设置或者升级ffmpeg版本。
+++ + + + + + + + + + + + + + + + + + + + + + + + + +测试
+
+ffmpeg -i "你需要转换格式的文件(附带绝对路径).mkv" -vcodec copy -acodec aac "你想要转换成的格式(附带绝对路径).mp4"
面试 socket整理一下资料 +fin ,rst包分别在什么时候发 +Linger=true为啥close(0)发rst包
+断线重连
+zlib,loom,sevenlib,ngui,a*,fingergesture,t4m,besthttp,itween,dotween,uniwebview,easytouch,meshbaker,spine,minijson
+贴图压缩(在屏幕显示范围大的不压缩,显示小的压缩)
+assetbundle压缩(BuildAssetBundleOptions.ChunkBasedCompression)
+assetbundle压缩比例 lzma 4倍,lz4 3倍
+navigation寻路(性能测试)
+a*寻路
+5.3.2动画问题 动态加载烘培问题
+assetbundle +--资源卸载(Resources.UnloadUnusedAssets() 异步) +--AssetBundle.LoadFromMemory() 加载比较慢
+ui适配 等比缩放 +NGUi: +Flexible:这样方式保存的场景和控件大小是固定的,如果你设置的像素值为300*200那么在屏幕比较小的设备上,它会显的比较大;屏幕比较大的设备上会显得比较小;其实就是大小不会根据屏幕大小变化而变化。这个不符合我们屏幕自适应的要求,应该也不会常用。 +Constrained:这个与上面那种方式是完全相反的,他不会固定一个值,而是随着设备屏幕大小的变化而变化,大小是依据百分比,但是要注意,在根据百分比缩放的同时 画面有可能会失真。 +ConstrainedOnMobiles:这个我没太理解,大概意思应该是在桌面设备上它会以 Flexible 的方式进行显示,如果是移动设备的话它将以 Constrained 的方式显示。
+aspect(宽高比) +standardAspect=1280/720=1.78 +aspect1=1.3 +aspect2=1.8 +aspect1<standardAspect<aspect2 +---适配高度 +aspect1:高度太大,适配高度后,宽方向ui出去了 。所以适配宽度方向(unity可以调整Camera的size=standardAspect/aspect1,在standardAspect时 size=1。这样等于适配宽度方向,高度有黑边然后把高度方向的背景图做大1280*960,aspect=1.3,一般没有小于1.3得了) +aspect2:宽度太大,适配高度后,宽方向ui有黑边 (但是一般没有大于1.7的比例了) +---适配宽度 +aspect1:高度太大,适配宽度后,高方向ui有黑边 +aspect2:宽度太大,适配宽度后,高方向ui出去了
+etc etc2
+fbx 的read/write 可以关闭 但是粒子特效的mesh需要打开,粒子系统通常需要动态地修改其粒子的顶点属性 ,将FBX上的Read/Write Enabled关闭后,内存中便不再保存其Mesh的副本(只存在显存中),因此其属性就不可再被访问和修改。
+=> 拉姆达表达式
+断点续传
+正则表达式 平衡组/递归匹配 固化分组 (几层括号嵌套);@"{([^{}]+|{(?
ftp C#工程ftp上传没问题,unity工程里面上传失败(ftp 的socket 没有优雅的关闭,先shutdown 再 close 注意有个选项Linger 有数据是否在关闭时逗留 )
+打包删除场景,进入场景怎么进去的??
+MemoryStream 在调用Close()和Dispose()后 仍然可以使用ToArray()获取数据的字节数组
+Finalize Dispose Close 用于非托管资源 ,其它时候没有意义 +http://www.cnblogs.com/liuning8023/archive/2012/07/22/2603819.html +---Finalize方法(C#中是析构函数,以下称析构函数)是用于释放非托管资源的,而托管资源会由GC自动回收。 +---Finalize 在C#中就是指析构函数,该方法默认存在,只有在定义析构函数后在析构函数中会隐式的调用Finalize,只有定义析构函数后终结器才能看到Finalize或者析构函数 +您无法从 C# 或 C++ 编程语言的托管扩展中调用或重写 Object.Finalize 方法。C# 和托管扩展提供析构函数作为编写终止代码的机制。在 C# 和托管扩展中,您必须使用析构函数语法来执行清理操作。因为该语法隐式地为对象基类调用 Finalize 方法,所以十分方便易用。这保证了对当前类从其导出的所有级别的析构函数都调用了 Finalize。 +---Dispose 只是用于显式的释放对象,在Dispose中默认会先调用Close,dispose是主动释放,一般用using语法可以代为实现 +---using语句块执行完默认会执行Dispose +---GC.SuppressFinalize(this); 这个方法是被客户直接调用的,告诉垃圾回收器从Finalization队列中清除自己,从而阻止垃圾回收器调用 析构函数 方法 +---在.net framework 里面,close()被设计成public的,并且在close()里面调用被隐藏的dispose(),而后dispose()再去调用另一个virtual的dispose(bool)
+终结器(Finalize方法): +垃圾回收器使用名为“终止队列”的内部结构跟踪具有 Finalize 方法的对象。每次您的应用程序创建具有 Finalize 方法的对象时,垃圾回收器都在终止队列中放置一个指向该对象的项。托管堆中所有需要在垃圾回收器回收其内存之前调用它们的终止代码的对象都在终止队列中含有项。(实现Finalize方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。用Finalize方法回收对象使用的内存需要至少两次垃圾回收。当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些对象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。垃圾回收器为此列表中的对象调用Finalize方法,然后将这些项从列表中移除。后来的垃圾回收将确定终止的对象确实是垃圾,因为标为准备终止对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。概括而言,就是将垃圾回收分为了三个阶段,第一个阶段回收没有Finalize方法或者析构函数的对象,第二个阶段调用那些析构函数或者Finalize方法,第三个阶段回收那些刚调用了析构函数或者Finalize方法的对象。)
+终结器(finalizer)是在回收过程中,由垃圾回收器调用的方法。如何含有终结器的对象到了G2中,那么就会需要非常长的时间来回收。事实上,根据应用程序运行时间的长短,对象很有机会直到应用程序退出之前都不会被回收(特别是其中包含的重要的资源得不得释放,将会对性能产生很大的影响,比如说数据库连接得不到释放。)
+实现模型: +1、由于大多数的非托管资源都要求可以手动释放,所以,我们应该专门为释放非托管资源公开一个方法。实现IDispose接口的Dispose方法是最好的模型,因为C#支持using语句快,可以在离开语句块时自动调用Dispose方法。 +2、虽然可以手动释放非托管资源,我们仍然要在析构函数中释放非托管资源,这样才是安全的应用程序。否则如果因为程序员的疏忽忘记了手动释放非托管资源, 那么就会带来灾难性的后果。所以说在析构函数中释放非托管资源,是一种补救的措施,至少对于大多数类来说是如此。 +3、由于析构函数的调用将导致GC对对象回收的效率降低,所以如果已经完成了析构函数该干的事情(例如释放非托管资源),就应当使用SuppressFinalize方法告诉GC不需要再执行某个对象的析构函数。 +4、析构函数中只能释放非托管资源而不能对任何托管的对象/资源进行操作。因为你无法预测析构函数的运行时机,所以,当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。这样将导致严重的后果。 +5、(这是一个规则)如果一个类拥有一个实现了IDispose接口类型的成员,并创建(注意是创建,而不是接收,必须是由类自己创建)它的实例对象,则 这个类也应该实现IDispose接口,并在Dispose方法中调用所有实现了IDispose接口的成员的Dispose方法。 +只有这样的才能保证所有实现了IDispose接口的类的对象的Dispose方法能够被调用到,确保可以手动释放任何需要释放的资源。
+Finalize()特性:
+重写Finalize()的唯一原因是,c#类通过PInvoke或复杂的COM互操作性任务使用了非托管资源(典型的情况是通过System.Runtime.InteropServices.Marshal类型定义的各成员)注:PInvoke是平台调用服务。 +object中有finalize方法,但创建的类不能重写此方法,若Overide会报错,只能通过析构函数来达到同样的效果。 +Finalize方法的作用是保证.NET对象能在垃圾回收时清除非托管资源。 +在CLR在托管堆上分配对象时,运行库自动确定该对象是否提供一个自定义的Finalize方法。如果是这样,对象会被标记为可终结的,同时一个指向这个对象的指针被保存在名为终结队列的内部队列中。终结队列是一个由垃圾回收器维护的表,它指向每一个在从堆上删除之前必须被终结的对象。 +注意:Finalize虽然看似手动清除非托管资源,其实还是由垃圾回收器维护,它的最大作用是确保非托管资源一定被释放。 +在结构上重写Finalize是不合法的,因为结构是值类型,不在堆上,Finalize是垃圾回收器调用来清理托管堆的,而结构不在堆上。 +Dispose()特性:
+为了更快更具操作性进行释放,而非让垃圾回收器(即不可预知)来进行,可以使用Dispose,即实现IDispose接口. +结构和类类型都可以实现IDispose(与重写Finalize不同,Finalize只适用于类类型),因为不是垃圾回收器来调用Dispose方法,而是对象本身释放非托管资源,如Car.Dispose().如果编码时没有调用Dispose方法,以为着非托管资源永远得不到释放。 +如果对象支持IDisposable,总是要对任何直接创建的对象调用Dispose(),即有实现IDisposable接口的类对象都必须调用Dispose方法。应该认为,如果类设计者选择支持Dispose方法,这个类型就需要执行清除工作。记住一点,如果类型实现了IDisposable接口,调用Dispose方法总是正确的。 +.net基类库中许多类型都实现IDisposable接口,并使用了Dispose的别名,其中一个别名如IO中的Close方法,等等别名。使得看起来更自然。 +using关键字,实际内部也是实现IDisposable方法,用ildasm.exe查看使用了using的代码的CIL,会发现是用try/finally去包含using中的代码,并且在finally中调用dispose方法。 +个人总结:
+相同点:
+都是为了确保非托管资源得到释放。 +不同点:
+finalize由垃圾回收器调用;dispose由对象调用。 +finalize无需担心因为没有调用finalize而使非托管资源得不到释放,而dispose必须手动调用。 +finalize虽然无需担心因为没有调用finalize而使非托管资源得不到释放,但因为由垃圾回收器管理,不能保证立即释放非托管资源;而dispose一调用便释放非托管资源。 +只有类类型才能重写finalize,而结构不能;类和结构都能实现IDispose.原因请看Finalize()特性。
+class Car
+{
+ ///
+ }
+}
+该析构函数隐式调用对象基类的 Finalize 方法。因此,该析构函数被隐式地转换为如下代码:
protected override void Finalize()
+{
+ try
+ {
+ // Cleanup statements...
+ }
+ finally
+ {
+ base.Finalize();
+ }
+}
+这意味着,对继承链中的所有实例递归调用 Finalize 方法。
+说明:不要使用空的析构函数。如果类包含析构函数,则 Finalize 队列中则会创建一个项。当调用析构函数时,将调用垃圾回收器(GC)来处理该队列。如果析构函数为空,只会导致不必要的性能损失。
+Object.Finalize 方法
允许 Object 在“垃圾回收”回收 Object 之前,尝试释放资源并执行其他清理操作。Finalize 是受保护的,因此只能通过此类或派生类访问它。
+对象变为不可访问后,将自动调用此方法,除非已通过 GC.SuppressFinalize 调用使对象免除了终结。在应用程序域的关闭过程中,对没有免除终结的对象将自动调用 Finalize,即使那些对象仍是可访问的。对于给定的实例仅自动调用 Finalize 一次,除非使用 GC.ReRegisterForFinalize重新注册该对象,并且后面没有调用 GC.SuppressFinalize。
+派生类型中的每个 Finalize 实现都必须调用其基类型的 Finalize 实现。这是唯一一种允许应用程序代码调用 Finalize 的情况。
+注意:C# 编译器不允许你直接实现 Finalize 方法,因此 C# 析构函数自动调用其基类的析构函数。
+Finalize 操作具有下列限制:
+1) 垃圾回收过程中执行终结器的准确时间是不确定的。不保证资源在任何特定的时间都能释放,除非调用 Close 方法或 Dispose 方法。
+2) 即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定的顺序运行。即,如果对象 A 具有对对象 B 的引用,并且两者都有终结器,则当对象 A 的终结器启动时,对象 B 可能已经终结了。
+3) 运行终结器的线程是未指定的。
+在下面的异常情况下,Finalize 方法可能不会运行完成或可能根本不运行:
+1) 另一个终结器无限期地阻止(进入无限循环,试图获取永远无法获取的锁,诸如此类)。由于运行时试图运行终结器来完成,所以如果一个终结器无限期地阻止,则可能不会调用其他终结器。
+2) 进程终止,但不给运行时提供清理的机会。在这种情况下,运行时的第一个进程终止通知是 DLL_PROCESS_DETACH 通知。
+在关闭过程中,只有当可终结对象的数目继续减少时,运行时才继续 Finalize 对象。
+如果 Finalize 或 Finalize 的重写引发异常,并且运行库并非寄宿在重写默认策略的应用程序中,则运行库将终止进程,并且不执行任何活动的 try-finally 块或终结器。如果终结器无法释放或销毁资源,此行为可以确保进程完整性。
+说明:默认情况下,Object.Finalize 不执行任何操作。只有在必要时才必须由派生类重写它,因为如果必须运行 Finalize 操作,垃圾回收过程中的回收往往需要长得多的时间。如果 Object 保存了对任何资源的引用,则 Finalize 必须由派生类重写,以便在垃圾回收过程中,在放弃 Object 之前释放这些资源。当类型使用文件句柄或数据库连接这类在回收使用托管对象时必须释放的非托管资源时,该类型必须实现 Finalize。Finalize 可以采取任何操作,包括在垃圾回收过程中清理了对象后使对象复活(即,使对象再次可访问)。但是,对象只能复活一次;在垃圾回收过程中,不能对复活对象调用 Finalize。
+析构函数是执行清理操作的 C# 机制。析构函数提供了适当的保护措施,如自动调用基类型的析构函数。在 C# 代码中,不能调用或重写 Object.Finalize。
+析构函数和Finalize不能同时存在一个类的定义中 +假设有三个类 A->B->C;C继承B,B继承A A有析构函数,C有析构函数 B有Finalize方法 创建C一个实例查看析构情况 +protected void Finalize(){...} C B执行 +protected virtual void Finalize(){...} A执行 +public void Finalize(){...} A C执行 +public virtual void Finalize(){...} 运行直接崩溃(都没有执行) +private void Finalize(){...} A C执行 +private virtual void Finalize() 编译错误(虚拟成员或抽象成员不能是私有的)
+如果把 Finalize写在C中 AB的析构执行 C的Finalize不执行 +base.Finalize() 调用必须父类中有Finalize定义(父类用析构不行,编译报错,Finalize方法不存在)
+MemoryStream Position重置为0 但是 Length 不变 Length和Position没有必然关系
+封箱和拆箱 +---装箱在值类型向引用类型转换时发生 +---拆箱在引用类型向值类型转换时发生
+List,IList,Array,ArrayList
+蒙皮网格原理
+哈希算法
+动画融合
+a*算法
+设计模式
+ui上的粒子特效
+Navigation寻路 +-------加速度控制不方便 不能为0,可以设置Vector3的速度变量 +-------不能取寻路数据的高度值 +-------OffMeshLink手动控制路径,一次只能通过一个agent,如果有agent正在通过OffMeshLink路径,其他agent只能在入口等着 不能通过
+C# WebRequest 超时bug 21秒左右
+C# http ServicePointManager.Expect100Continue = false; +-----100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。在现实应用中,通过在POST大数据时,才会使用100-continue协议。
+socket shutdown和close的区别,除了Fork进程引用计数之外还有什么 FTP上传文件后必须用ShutDown(如果设置了Linger为ture)
+UTF-8, UTF-16, UTF-16LE, UTF-16BE的区别(UTF-16LE 在C#里面是Encoding.unicode) +首先, 我们说的unicode, 其实就是utf-16, 但最通用的却是utf-8, +原因: 我猜大概是英文占的比例比较大, 这样utf-8的存储优势比较明显, 因为utf-16是固定16位的(双字节), 而utf-8则是看情况而定, 即可变长度, 常规的128个ASCII只需要8位(单字节), 而汉字需要24位 +UTF-16, UTF-16LE, UTF-16BE, 及其区别BOM +同样都是unicode, 为什么要搞3种这么麻烦? +先说UTF-16BE (big endian), 比较好理解的, 俗称大头,比如说char 'a', ascii为0x61; 那么它的utf-8, 则为 [0x61], 但utf-16是16位的, 所以为[0x00, 0x61];再说UTF-16LE(little endian), 俗称小头, 这个是比较常用的 .还是char'a', 它的代码却反过来: [0x61, 0x00], 据说是为了提高速度而迎合CPU的胃口, CPU就是这到倒着吃数据的, 这里面有汇编的知识, 不多说 +然后说UTF-16, 要从代码里自动判断一个文件到底是UTF-16LE还是BE, 对于单纯的英文字符来说还比较好办, 但要有特殊字符, 图形符号, 汉字, 法文, 俄语, 火星语之类的话, 相信各位都很头痛吧, 所以, unicode组织引入了BOM的概念, 即byte order mark, 顾名思义, 就是表名这个文件到底是LE还是BE的, +其方法就是, 在UTF-16文件的头2个字节里做个标记: LE [0xFF, 0xFE], BE [0xFE, 0xFF]
+理解了这个后, 在java里遇到utf-16还是会遇到麻烦, 因为要在文件里面单独判断头2个再字节是很不流畅的 +InputStreamReader reader=new InputStreamReader(fin, charset) (Java代码) +1. 如果这个UTF-16文件里带有BOM的话, charset就用"UTF-16", java会自动根据BOM判断LE还是BE, 如果你在这里指定了"UTF-16LE"或"UTF-16BE"的话, 猜错了会生成乱七八糟的文件, 哪怕猜对了, java也会把头2个字节当成文本输出给你而不会略过去, 因为[FF FE]或[FE FF]这2个代码没有内容, 所以, windows会用"?"代替给你 +2. 如果这个UTF-16文件里不带BOM的话, 则charset就要用"UTF-16LE"或"UTF-16BE"来指定LE还是BE的编码方式 +另外, UTF-8也有BOM的, [0xEF, 0xBB, 0xBF], 但可有可无, 但用windows的notepad另存为时会自动帮你加上这个, 而很多非windows平台的UTF8文件又没有这个BOM, 真是难为我们这些程序员啊 +http://www.iteye.com/topic/583064
+ + + + + + + + + + + + + + + + + + + + + + + + + +++设计模式分为三种类型,共23类。
+(1)创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
+(2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
+(3)行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
+
对类的现实化进行了抽象,能够使软件模块做到与对象的创建和组织无关。
+功能:类的创建
+++单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式
+
描述类和对象之间如何进行有效的组织,以形成良好的软件体系结构,主要的方式是使用继承关系来组织各个类。
+功能: 组合代替、类与类之间的关系
+++适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
+
描述 类和对象之间如何交互以及如何分配职责
+功能:关注对象与行为的分离、就是要把行为分离到类里面
+++观察者模式、中介者模式、状态模式、解释器模式、策略模式、职责链模式、访问者模式、迭代器模式、命令模式、迭代器模式、备忘录模式
+
server和client通信,每条通信消息都带消息号。消息号就是命令传递,该消息是什么消息,有消息号(命令)的消息体(命令对应解析具体的内容)。
+手机游戏中玩家每次点击或者手势等操作都是下达一条条命令 (典型) + >屏幕上有多个按钮(每个按钮代表一条命令,更具体些,每个按钮代表一个商品),玩家点击某个按钮,下达某个命令,这个命令是要购买某个商品,对应的响应是一个方法,方法有个参数是商品id,命令就变成把商品id传递到方法内,执行方法
+使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
+大型游戏中,server和client通信会有大量的协议定义(每个协议号代表一条命令)
+++中介者模式就是迪米特法则的一个典型应用
+
中介者承担两方面的职责:
+中介者模式包含四个角色:抽象中介者用于定义一个接口,该接口用于与各同事对象之间的通信;具体中介者是抽象中介者的子类,通过协调各个同事对象来实现协作行为,了解并维护它的各个同事对象的引用;抽象同事类定义各同事的公有方法;具体同事类是抽象同事类的子类,每一个同事对象都引用一个中介者对象;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中定义的方法。
+++类似于Control ,拥有各个具体业务对象的引用,进行各个对象的交互控制
+
状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
+状态模式包含三个角色:环境类又称为上下文类,它是拥有状态的对象,在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象,可以定义初始状态;抽象状态类用于定义一个接口以封装与环境类的一个特定状态相关的行为;具体状态类是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
+策略模式包含三个角色:环境类在解决某个问题时可以采用多种策略,在环境类中维护一个对抽象策略类的引用实例;抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类;具体策略类实现了在抽象策略类中定义的算法。
+MVC模式是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。观察者模式可以用来实现MVC模式,观察者模式中的观察目标就是MVC模式中的模型(Model),而观察者就是MVC中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。
+++ + + + + + + + + + + + + + + + + + + + + + + + + +引用: +https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/command.html
+https://blog.csdn.net/WuLex/article/details/116431864
+https://blog.csdn.net/jason_jiahongfei/article/details/105108532
+ +
dll,mdb,pdb说明 +https://gulu-dev.com/post/2016-08-30-unity-external-dll-debugging +http://anferneehardaway.pixnet.net/blog/post/6273453 +http://anferneehardaway.pixnet.net/blog/post/3994168-.pdb-%E7%9A%84%E7%94%A8%E8%99%95
+蒙皮网格原理
+哈希算法
+动画融合
+登陆id生成
+shader
+矩阵
+网络通信量每秒
+C#模板类 重命名 +C# GC +ugui +设计模式 +数据结构与算法 +行为树 +有限状态机 +反射和ios拒绝反射有何不同 +C#特性 +异步编程 async await
+++ + + + + + + + + + + + + + + + + + + + + + + + + +mesh转ui绘制后出现遮罩bug,在scrollview中使用时上层增加了一个mask2D遮罩,但是对于scrollview的mesh不生效,20200804
+
+import android.net.NetworkInfo;
+import android.net.Uri;
+
+ public void OpenWebView(String url)
+ {
+ Log.i("pay",url);
+ Intent intent = new Intent();
+ intent.setAction("android.intent.action.VIEW");
+ Uri content_url = Uri.parse(url);
+ intent.setData(content_url);
+ intent.setClassName("com.android.browser","com.android.browser.BrowserActivity");
+ startActivity(intent);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+++参考文章:https://www.cnblogs.com/jasonkent27/p/5113184.html
+
/**
+ * bitmap中的透明色用白色替换
+ *
+ * @param bitmap
+ * @return
+ */
+ public static Bitmap changeColor(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int[] colorArray = new int[w * h];
+ int n = 0;
+ for (int i = 0; i < h; i++) {
+ for (int j = 0; j < w; j++) {
+ int color = getMixtureWhite(bitmap.getPixel(j, i));
+ colorArray[n++] = color;
+ }
+ }
+ return Bitmap.createBitmap(colorArray, w, h, Bitmap.Config.ARGB_8888);
+ }
+
+ /**
+ * 获取和白色混合颜色
+ *
+ * @return
+ */
+ private static int getMixtureWhite(int color) {
+ int alpha = Color.alpha(color);
+ int red = Color.red(color);
+ int green = Color.green(color);
+ int blue = Color.blue(color);
+ return Color.rgb(getSingleMixtureWhite(red, alpha), getSingleMixtureWhite
+
+(green, alpha),
+ getSingleMixtureWhite(blue, alpha));
+ }
+
+ /**
+ * 获取单色的混合值
+ *
+ * @param color
+ * @param alpha
+ * @return
+ */
+ private static int getSingleMixtureWhite(int color, int alpha) {
+ int newColor = color * alpha / 255 + 255 - alpha;
+ return newColor > 255 ? 255 : newColor;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+https://www.cnblogs.com/sexintercourse/p/5898242.html +Android的android:scheme不要设置成http,否则打不开 +IOS的scheme 在浏览器中用window.location.href=设置有时候没有反应,不知道是不是bug
+ + + + + + + + + + + + + + + + + + + + + + + + + +++IDE支持
+DirectX11和DirectX12
vs2019自带了DirectX,所以不要很复杂且繁琐的配置。但是如果安装的时候,没有勾选正确的包,后面就无法找到自带的DirectX。
+勾选下图所示的两个安装包
+
+ 第二个安装包,如果不够选也是无法看到DirectX的。
在通用Windows平台开发中,需要勾选如下所示包
+ 
+ ++ + + + + + + + + + + + + + + + + + + + + + + + + +
这是 Graphics 分类栏目页。
+
+++引用文章: http://blog.csdn.net/hicui/article/details/51146969
+
framework是ios开发中经常使用到的一个组件,但是有些情况下拿到第三方提供的framework,导入自己的项目后会发现,Headers目录无法识别,编译出错的情况,比如这里:http://tieba.baidu.com/p/4405458569#
+常见IOS framework的目录结构为:
+sdk.framework目录
+但是有些第三方framework生成时,脚本有问题,生成的目录结构为:
+sdk.framework目录
+Versions目录 + -current软链接 + -A目录
+软链接在不同的电脑上移动时可能会出现路径不存在,因此就导致xcode无法识别Headers目录的情况。
+将sdk.framework/Versions/A/ 下的Headers和SDK文件移动到sdk.framework目录下,将其他文件删除,重新编译即可。
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+project_base.json等所有以project_开头的文件的配置信息; +这里的url最后的版本文件要和ios在线的版本一致,不一致则先修改
+
检查资源版本号和线上的是否相同 +如果是发布版本则点击Publish Packer,如果是热更新资源则点击Publish Bundle +如果选择Publish Packer 发布版本:
+
检查界面显示的版本是否为当前线上的版本 +所有版本号加1,然后点击Publish Bundle; +然后导出Xcode工程
+如果选择Publish Bundler 热更新版本: +检查界面显示的版本是否为当前线上的版本
+
清理缓存 +版本号加1,然后点击Publish Bundle; +然后导出Xcode工程
+1.打开Capabilities面板 +打开Push Notifications选项
+
打开Background Modes选项,勾选Remote notifications
+
2.配置打开Notifications后会添加该文件
+
这样工程配置完成了,然后开始打包
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+1.连接你的设备,在Xcode下点击 Window —> Device(cmd + shift + 2) 弹出窗口,选择你的设备,找到你已安装的APP,选中你想要查看沙盒的APP。
+2.点击底部有个类似设置的按钮,出现几个选项,选择Download Container ,下载文件到本地,将会看到一个后缀为xcappdata的文件,选择这个文件并显示包内容查看对应的沙盒文件。
+
*下载失败或者Show Container没有内容 解决方案: 卸载app,重启手机设备,安装app就好了
+
++ ++ios配置开发者证书及添加AppID 参考https://www.jianshu.com/p/ee83dc090b20
+
+ios 配置ItunesConnect添加应用 参考https://www.jianshu.com/p/12ccfa566ae2
+这里使用Xcode的版本为Version 9.2 (9C40b)
Product --> Archive,然后等待构建完成。
然后在iTunesConnect上选择刚上传的包
+
hoc模式不是上传appstore的,是给自己测试用的
+ + + + + + + + + + + + + + + + + + + + + + + + + +
这个错误是因为Xcode缺少对应ios版本的supoort文件
+++iOS 升级到11之后,你会发现无法进行真机测试了。这种情况我在iOS 10.0更新的时候也遇到过。原因是Xcode 的DeviceSupport里面缺少了对应iOS系统版本的SDK。所以你可以选择将Xcode更新到最新版本。
+
或者从新版的Xcode目录支持文件复制到自己的Xcode目录中 +文件路径:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport +打开这个路径,然后看到ios 系统列表
+
找到自己需要的版本(我用的是11.1)将整个文件夹拷贝到自己的Xcode目录中
+ + + + + + + + + + + + + + + + + + + + + + + + + +这是 IOS 分类栏目页。
+
+1.等比缩放
+
+
+- (UIImage *) scaleImage:(UIImage *)image toScale:(float)scaleSize {
+UIGraphicsBeginImageContext(CGSizeMake(image.size.width * scaleSize, image.size.height * scaleSize);
+[image drawInRect:CGRectMake(0, 0, image.size.width * scaleSize, image.size.height * scaleSize)];
+UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
+UIGraphicsEndImageContext();
+return scaledImage;
+}
+
+2.自定义大小
+- (UIImage *) reSizeImage:(UIImage *)image toSize:(CGSize)reSize {
+UIGraphicsBeginImageContext(CGSizeMake(reSize.width, reSize.height));
+[image drawInRect:CGRectMake(0, 0, reSize.width, reSize.height)];
+UIImage *reSizeImage = UIGraphicsGetImageFromCurrentImageContext();
+UIGraphicsEndImageContext();
+return reSizeImage;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+``` +//获取appIconName +-(NSString)GetAppIconName{ + NSDictionary infoPlist = [[NSBundle mainBundle] infoDictionary];
+NSString *icon = [[infoPlist valueForKeyPath:@"CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles"] lastObject];
+NSLog(@"GetAppIconName,icon:%@",icon);
+return icon;
+
+} +···
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+++关于怎么配置开发者证书和添加AppID 参考 http://www.jianshu.com/writer#/notebooks/6185424/notes/23554406/preview
+
1.打开 https://itunesconnect.apple.com/login 并且登录
+
2.点击我的App
+
3.点击+====>新建App
+
4.配置信息
+
平台:根据需求选择,这里选择iOS
+应用名字
+语言
+套装ID:通过开发者账户配置添加的AppID,参考引用的连接
+SKU :这里我写的bundleID,随意
+点击创建,这样就创建完成
1.配置应用收费是否收费下载,这里配置是免费
+
2.添加商品
+
点击应用的功能===>点击+添加商品
+
根据自己需求选择添加商品的类型
+
配置商品的信息,添加商城截图,完成后点击存储
+
然后看到该条商品的信息了,状态是准备提交
+
在提审前打开提审的版本1.0配置界面,向下拉,找到App内购项目,点击+号,然后勾选自己的商品(可以选多个),这里是一小袋钻石,点击完成
+提审的时候商品也需要审核的!提审的时候商品也需要审核的!提审的时候商品也需要审核的!重要的事情说三遍
+到这里完成了
+ + + + + + + + + + + + + + + + + + + + + + + + + +
++ ++
一. 有两种配置开发者证书的方法
+1.打开钥匙串
+
2
+
2.创建请求文件
+
打开:钥匙串访问===>证书助理===>从证书颁发机构请求证书...
+
请求是:存储到磁盘 +设置电邮地址 +点击继续
+
存储请求文件
+1.打开https://developer.apple.com 点击Account 登录
+![[图片上传中...(创建开发者证书2.png-c69c56-1517639491122-0)]
+](https://cdn.jsdelivr.net/gh/codingriver/cdn/texs/1095643-f017dfd4c9ea901a.png)
点击:Certificates, Identifiers & Profiles
+
点击Certificates 的+号,准备创建开发者证书
+
这里根据自己的需求选择开发证书还是生产证书(如果是自己测试可以选择Development开发证书,如果发布appstore提审,则需要Production生产证书)
+
拖到屏幕最下面,选择继续
+
选择继续
+
这里选择刚才生成的创建开发者证书的请求文件
+
选择继续
+
这里选择下载,证书已经配置完成,下载到本地,然后点击Done
+证书下载完成后双击自动导入证书,检查证书是否成功导入! +创建开发者证书12.png
+我之前在创建证书请求文件时配置的常用名称wang_develop 这里看到表示成功了
+1.打开Xcode,这里就不说怎么打开了 +2.点击左上角Xcode按钮===>Preferences...
+
4.点击+号
+
这里点击+号后展开菜单有Development和App Store(这里根据自己的需求选择开发证书还是生产证书(如果是自己测试可以选择Development开发证书,如果发布appstore提审,则需要App Store生产证书)) +等待证书添加完成
+
这里证书添加完成,然后重启Xcode,防止出现未知bug
+打开开发者账户网站(上面网页中配置证书的地方) +1.App IDs==>+号 +选择左侧的appIds
+
2.
+
添加AppId的名字和BundleID +点击继续,(在屏幕最下面) +3.选择选项点击继续
+
4.点击Register完成
+
+unity 导出xcode后接环信libHyphenateSDK时build报错Undefined symbols for architecture armv7: +也有Undefined symbols for architecture arm64:
+Undefined symbols for architecture armv7:
+ "_CGImageDestinationCreateWithURL", referenced from:
+ -[EMVideoMessageBody initWithLocalPath:displayName:] in libHyphenateSDK.a(EMVideoMessageBody.o)
+ "_CGImageDestinationAddImage", referenced from:
+ -[EMVideoMessageBody initWithLocalPath:displayName:] in libHyphenateSDK.a(EMVideoMessageBody.o)
+ "_CGImageDestinationFinalize", referenced from:
+ -[EMVideoMessageBody initWithLocalPath:displayName:] in libHyphenateSDK.a(EMVideoMessageBody.o)
+ld: symbol(s) not found for architecture armv7
+clang: error: linker command failed with exit code 1 (use -v to see invocation)
+
+
这个错误以为sdk或者framework不支持armv7或者arm64,我这实际上是缺少framework +这个错误是工程里面没有引用 ImageIO.framework造成的,
+
环信的libHyphenateSDK.a库依赖ImageIO.framework,结果报错让人无语
+ + + + + + + + + + + + + + + + + + + + + + + + + +本文档用于说明当前博客从 Hugo 迁移到 MkDocs 之后的标准使用方式,包括本地环境部署、日常写作、主题选择、GitHub Pages 部署、gh-pages 分支使用方式,以及搜索、评论、Git 更新时间等增强能力方案。
建议使用 Python 3.10 及以上版本。
+检查版本:
+python --version
+
+当前站点使用 Material for MkDocs 主题。
+安装命令:
+pip install mkdocs mkdocs-material
+
+如果要支持 Git 更新时间、作者信息等功能,建议同时安装以下插件:
+pip install mkdocs-git-revision-date-localized-plugin mkdocs-git-authors-plugin gitpython jieba
+
+说明:
+mkdocs-git-revision-date-localized-plugin:显示页面最后更新时间、创建时间mkdocs-git-authors-plugin:显示页面作者信息gitpython:部分 Git 相关插件依赖jieba:增强中文搜索分词在项目根目录执行:
+mkdocs serve
+
+默认访问地址:
+http://127.0.0.1:8000
+
+mkdocs build
+
+构建产物默认输出到:
+site/
+
+title: 我的随笔 +date: 2026-03-21 +categories: + - ANote +tags: + - 随笔 + - 记录 +status: published
+记录一下今天的想法。
+正文内容。
+一句话总结。
+
+说明:
+
+- 文件名尽量简洁,必要时可带日期
+- `ANote` 比较适合杂记、随笔、草稿整理、阶段记录
+- 如果后续启用标签插件,上述 front matter 可以直接复用
+
+### 3.3 如何添加分类
+
+MkDocs 没有 Hugo 那种内建 taxonomy 分类体系,当前项目推荐使用:
+
+## 目录即分类
+
+例如:
+
+- `docs/ANote/`
+- `docs/u3d/`
+- `docs/shader/`
+- `docs/other/`
+
+分类的标准做法:
+
+1. 在 `docs/` 下创建目录
+2. 在目录中放一个 `index.md` 作为栏目首页
+3. 在 `mkdocs.yml` 的 `nav` 中加入该目录
+4. 将该类文章都放在此目录下
+
+例如新增一个 `essay` 分类:
+
+```text
+docs/essay/
+docs/essay/index.md
+
+index.md 示例:
# essay
+
+这里收录个人随笔、阶段总结与非技术长文。
+
+然后在 mkdocs.yml 中添加:
- essay:
+ - 栏目首页: essay/index.md
+
+当前项目可以先采用“文档中写标签 + 后续逐步增强”的方式。
+推荐在文章头部添加 front matter:
+---
+title: Shader 学习记录
+tags:
+ - Shader
+ - Unity
+ - 图形学
+categories:
+ - shader
+---
+
+当前阶段标签主要有两个用途:
+如果后续要把标签真正展示成可点击页面,有两种方案:
+优点:自动化程度高。
+缺点:配置复杂度更高。
例如建立:
+docs/tags/index.md
+
+手动按标签整理文章链接。
+优点:简单稳定。
+缺点:需要人工维护。
对于你当前项目,建议先采用:
+当前站点已经使用:
+Material for MkDocs在 mkdocs.yml 中基本配置形式如下:
theme:
+ name: material
+ language: zh
+
+Material 主题常用能力包括:
+theme:
+ name: material
+ language: zh
+ features:
+ - navigation.tabs
+ - navigation.sections
+ - navigation.expand
+ - navigation.top
+ - search.suggest
+ - search.highlight
+ - content.code.copy
+
+markdown_extensions:
+ - admonition
+ - tables
+ - toc:
+ permalink: true
+ - pymdownx.highlight
+ - pymdownx.superfences
+ - pymdownx.inlinehilite
+ - pymdownx.tabbed:
+ alternate_style: true
+
+适用场景:
+优点:
+适用场景:
+优点:
+缺点:
+如果后续想尝试更博客化的风格,可以考虑:
+对于当前项目,不建议频繁换主题,建议继续以 Material 为主。
+推荐方式:
+master:源码分支gh-pages:静态站点发布分支GitHub Pages 读取 gh-pages 分支内容进行站点发布。
gh-pages 分支如何使用¶gh-pages 是专门用来放静态网站产物的分支。
它的特点:
+因此:
+gh-pages 上改文章master 上进行gh-pages 只作为发布分支使用master 分支,如何手工将产物推送到 gh-pages¶最简单方式:
+mkdocs gh-deploy --clean
+
+这个命令会自动:
+gh-pages如果要强制覆盖远端:
+mkdocs gh-deploy --clean --force
+
+如果不使用 mkdocs gh-deploy,理论上也可以手工发布:
master 执行 mkdocs buildsite/ 目录gh-pagessite/ 内容复制到分支根目录这种方式可行,但不推荐长期使用,因为容易出错。
+在 GitHub 仓库页面中:
+SettingsPagesGitHub ActionsDeploy from a branchgh-pages推荐优先使用:
+当前仓库已经存在工作流:
+.github/workflows/deploy-mkdocs.yml
+
+当前逻辑是:
+master 分支有新的 push 时触发mkdocs gh-deploy --force --cleangh-pages这意味着:
+mastergh-pagessite/ 提交到源码分支name: Deploy MkDocs to GitHub Pages
+
+on:
+ push:
+ branches:
+ - master
+
+permissions:
+ contents: write
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Install dependencies
+ run: |
+ pip install mkdocs-material mkdocs-git-revision-date-localized-plugin gitpython jieba
+
+ - name: Deploy
+ run: mkdocs gh-deploy --force --clean
+
+对于当前项目,推荐:
+master 维护源码,使用 GitHub Actions 自动发布到 gh-pages¶这是最省心、最稳定的方式。
+MkDocs 默认构建输出目录为:
+site/
+
+这个目录不应该提交到 master 分支。
因此必须在 .gitignore 中加入:
site/
+
+这样可以避免:
+master推荐原则:
+master 只放源码gh-pages 只放构建产物jieba 依赖)¶当前站点继续使用 Material 主题自带搜索能力,推荐在 mkdocs.yml 中显式保留:
plugins:
+ - search
+
+如果中文内容较多,建议继续保留 jieba 依赖,用于增强中文分词搜索效果。
安装示例:
+pip install jieba
+
+如果使用 GitHub Actions 自动部署,也应在工作流安装依赖时保留 jieba:
pip install mkdocs-material mkdocs-git-revision-date-localized-plugin mkdocs-git-authors-plugin gitpython jieba
+
+推荐结论:
+jieba 依赖保留推荐使用插件:
+pip install mkdocs-git-revision-date-localized-plugin
+
+推荐配置:
+plugins:
+ - search
+ - git-revision-date-localized:
+ enable_creation_date: true
+ type: datetime
+ timezone: Asia/Shanghai
+ locale: zh
+
+这个插件可以提供:
+适合技术博客和知识库,因为读者可以快速判断文档是否过时。
+推荐使用插件:
+pip install mkdocs-git-authors-plugin
+
+推荐配置:
+plugins:
+ - search
+ - git-revision-date-localized:
+ enable_creation_date: true
+ type: datetime
+ timezone: Asia/Shanghai
+ locale: zh
+ - git-authors
+
+作用:
+对于单人博客,它的价值主要体现在:
+评论系统建议统一采用:
+原因:
+接入步骤:
+Discussionshttps://giscus.app/zh-CN建议映射方式:
+推荐结论:
+Utterances 作为备选,但优先级低于 Giscus当前项目已经建议在文章 front matter 中保留标签字段,例如:
+---
+title: Shader 学习记录
+tags:
+ - Shader
+ - Unity
+ - 图形学
+categories:
+ - shader
+---
+
+推荐的标签系统落地分两步:
+所有新文章都尽量补充 tags 字段,先把标签数据沉淀下来。
优点:
+推荐两种可选方案:
+例如建立:
+docs/tags/index.md
+
+再按标签整理文章入口。
+优点:
+缺点:
+适合未来更博客化的展示需求,例如:
+优点:
+缺点:
+当前项目推荐结论:
+tags 元数据,展示层先采用手工标签页,后续再评估是否插件化¶MkDocs 默认不直接展示每篇文章的完整 Git 提交历史。
+推荐分两档:
+优点:
+在 CI 中通过脚本执行 git log,自动生成一份全站或每篇文章的 changelog 页面,例如:
docs/changelog/index.md
+
+优点:
+缺点:
+当前推荐先采用:
+后续建议逐步增强 mkdocs.yml:
theme:
+ name: material
+ language: zh
+ features:
+ - navigation.tabs
+ - navigation.sections
+ - navigation.expand
+ - navigation.top
+ - search.suggest
+ - search.highlight
+ - content.code.copy
+
+plugins:
+ - search
+ - git-revision-date-localized:
+ enable_creation_date: true
+ type: datetime
+ timezone: Asia/Shanghai
+ locale: zh
+ - git-authors
+
+如果要支持“编辑此页”:
+repo_url: <你的 GitHub 仓库地址>
+edit_uri: edit/master/docs/
+
+这样每篇文章都能直接跳转到 GitHub 编辑页面。
+建议:
+image/文章名/ 中建议分类保持稳定,不要频繁改目录名。
+建议标签数量控制在 3 到 5 个以内,避免标签泛滥。
+mastermastergh-pages对于当前博客,最推荐的组合是:
+searchmkdocs-git-revision-date-localized-pluginmaster 保存源码,GitHub Actions 自动部署到 gh-pagesmaster 分支忽略 site/这套方案最适合当前仓库的结构,也最容易长期维护。
+ + + + + + + + + + + + + + + + + + + + + + + + + + +你好,这里是王国庆的个人技术博客。
+本站主要记录这些内容:
+文章以长期积累的项目经验、学习笔记和问题排查记录为主,尽量做到:
+本站已完成从 Hugo 到 MkDocs 的主体迁移,后续会继续优化:
+0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o