Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96c24da077 | ||
|
|
ee9e70c53a | ||
|
|
bfb061d88a | ||
|
|
e85f78ece2 | ||
|
|
e6f7d1efb3 | ||
|
|
4a45c4dd43 | ||
|
|
ca4bb5c34b | ||
|
|
42cd8265f5 | ||
|
|
fd25180989 | ||
|
|
b55d4b9600 | ||
|
|
463b4724c9 | ||
|
|
5e851505bb | ||
|
|
5b9ca62976 | ||
|
|
346de14e00 | ||
|
|
8d1f34604e | ||
|
|
3f4dcfa0a7 | ||
|
|
f64e345e9b | ||
|
|
2e83be8414 | ||
|
|
e7b5f24cf7 | ||
|
|
f4665a4c9f | ||
|
|
72f21a21af | ||
|
|
f677d522b4 | ||
|
|
13d3372fc4 | ||
|
|
55b9c00292 | ||
|
|
7b41c64a59 | ||
|
|
bf43130c0d | ||
|
|
65c561c440 | ||
|
|
d6818e8816 | ||
|
|
cbe0056ab2 | ||
|
|
9aa28ca830 | ||
|
|
db128c8f49 | ||
|
|
3e33975e2a | ||
|
|
86889c334e | ||
|
|
0db09f12c3 | ||
|
|
481bdefa74 | ||
|
|
41adba285d | ||
|
|
3f7faff423 | ||
|
|
4721726618 | ||
|
|
aa9a524c33 | ||
|
|
1d1a1a4907 | ||
|
|
58f8c42b04 | ||
|
|
701187ec11 | ||
|
|
cb7b600eda | ||
|
|
e7834d7032 | ||
|
|
f7fd225bc6 | ||
|
|
dfb973bed0 | ||
|
|
326593eb4f | ||
|
|
57ac3a674c | ||
|
|
db5fb1e04a | ||
|
|
c17ac7e6b5 | ||
|
|
3c0dda0e28 | ||
|
|
7d3427e4c0 | ||
|
|
75c20ecfed | ||
|
|
5455da4a57 | ||
|
|
67404122fb | ||
|
|
1a621bd5bf | ||
|
|
8d195515c3 | ||
|
|
a9239fc39f | ||
|
|
8dc5aab8fe | ||
|
|
c711a90592 | ||
|
|
41081bf62d | ||
|
|
acd9eea405 | ||
|
|
a112cecfbf | ||
|
|
ceba7f90ac | ||
|
|
7ff02db939 | ||
|
|
599e041732 | ||
|
|
48517e685d | ||
|
|
a64a6715ee | ||
|
|
847918884e | ||
|
|
2acce4ad70 | ||
|
|
b5f1e076a0 | ||
|
|
0318224673 | ||
|
|
610537fe9d | ||
|
|
cb59a5c1c8 | ||
|
|
f7d41908dc | ||
|
|
e1f4c01ce6 | ||
|
|
526e8d22f2 | ||
|
|
3207c47dd2 | ||
|
|
60afc59582 | ||
|
|
12b26a0555 | ||
|
|
f36ec52f4a | ||
|
|
4f0be3ecc1 | ||
|
|
1070b2f098 | ||
|
|
51616af8ce | ||
|
|
ded9642c4d | ||
|
|
86e903fc86 | ||
|
|
628597fe59 | ||
|
|
365387b8d9 | ||
|
|
09b7bddc84 | ||
|
|
cf07788d74 | ||
|
|
8c39579e1c |
@@ -20,8 +20,10 @@ android {
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters.addAll( 'arm64-v8a', 'x86_64')
|
||||
abiFilters.addAll( 'arm64-v8a')
|
||||
arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_static', '-DVKB_VALIDATION_LAYERS=OFF'
|
||||
//arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_static', '-DVKB_VALIDATION_LAYERS=OFF', "-DCMAKE_BUILD_TYPE=Release"
|
||||
//cppFlags "-DRELEASE_MODE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 139 KiB |
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"max" : [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min" : [
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"type" : "VEC4"
|
||||
},
|
||||
{
|
||||
"bufferView" : 3,
|
||||
"componentType" : 5126,
|
||||
"count" : 24,
|
||||
"type" : "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView" : 4,
|
||||
"componentType" : 5121,
|
||||
"count" : 36,
|
||||
"type" : "SCALAR"
|
||||
}
|
||||
],
|
||||
"asset" : {
|
||||
"generator" : "Khronos Blender glTF 2.0 I/O",
|
||||
"version" : "2.0"
|
||||
},
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 288,
|
||||
"byteOffset" : 0
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 288,
|
||||
"byteOffset" : 288
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 384,
|
||||
"byteOffset" : 576
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 192,
|
||||
"byteOffset" : 960
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 36,
|
||||
"byteOffset" : 1152
|
||||
}
|
||||
],
|
||||
"buffers" : [
|
||||
{
|
||||
"byteLength" : 1188,
|
||||
"uri" : "data:application/octet-stream;base64,AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAgAAAAAAAAIC/AAAAgAAAAAAAAIC/AAAAgAAAAAAAAIC/AACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAgAAAAIAAAIA/AAAAgAAAAIAAAIA/AAAAgAAAAIAAAIA/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAIAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAACAvwAAgD8AAIC/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAPwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgD8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAAAAAAIC/AACAvwAAAAAAAAAAAACAvwAAgL8AAAAAAAAAAAAAgL8AAIC/AACAPwAAAAAAAACAAACAvwAAgD8AAAAAAAAAgAAAgL8AAIA/AAAAAAAAAIAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAvwAAgD8AAAAAAAAAAAAAgL8AAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAvwAAgD8AAAAAAAAAgAAAgL8AAIA/AAAAAAAAAIAAAIC/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AAAAAAAAAAAAAIC/AACAvwAAgD8AAAAAAAAAgAAAgL8AAAAAAAAAAAAAgL8AAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgL8AAIA/AAAAAAAAAIAAAIA/AAAAAAAAgD8AAIC/AAAAQAAAgL8AAIA/AAAAAAAAgD8AAIC/AAAAQAAAgL8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAgD8AAIA/AAAAAAAAgD8AAIA/AAAAQAAAAAAAAABAAAAAAAAAgD8AAIC/AAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAIA/AACAPwAAgL8AAIA/AAECAwQFBgcICQoLDA0ODxARABIBAxMEBhQHCRUKDBYNDxcQ"
|
||||
}
|
||||
],
|
||||
"extensions" : {},
|
||||
"materials" : [
|
||||
{
|
||||
"name" : "Material",
|
||||
"pbrMetallicRoughness" : {}
|
||||
}
|
||||
],
|
||||
"meshes" : [
|
||||
{
|
||||
"name" : "Cube.001",
|
||||
"primitives" : [
|
||||
{
|
||||
"attributes" : {
|
||||
"NORMAL" : 0,
|
||||
"POSITION" : 1,
|
||||
"TANGENT" : 2,
|
||||
"TEXCOORD_0" : 3
|
||||
},
|
||||
"indices" : 4,
|
||||
"material" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0,
|
||||
"name" : "Cube"
|
||||
}
|
||||
],
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"name" : "Scene",
|
||||
"nodes" : [
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,34 @@
|
||||
////
|
||||
- Copyright (c) 2024-2025, Sascha Willems
|
||||
-
|
||||
- SPDX-License-Identifier: Apache-2.0
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 the "License";
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-
|
||||
////
|
||||
= Shaders
|
||||
|
||||
== Shader languages
|
||||
|
||||
This folder contains the textual shaders for the samples. All samples come with GLSL shaders and some optionally with HLSL shaders. For samples that support both shader language this is a good way to compare GLSL to HLSL syntax when targeting SPIR-V for Vulkan.
|
||||
|
||||
== Compiling shaders
|
||||
|
||||
The samples load offline compiled SPIR-V variants of the GLSL/HLSL shaders. If you have the appropriate compiler installed, e.g. via the LunarG VUlkan SDK, shaders will be automatically compiled when building the samples project.
|
||||
|
||||
== Further information
|
||||
|
||||
The xref:guide:ROOT:index.adoc[Vulkan Guide] contains further information on how to use HLSL with Vulkan and how it compares to GLSL:
|
||||
|
||||
* xref:guide::hlsl.adoc[HLSL in Vulkan]
|
||||
* xref:guide::high_level_shader_language_comparison.adoc[High level shading language comparison]
|
||||
@@ -16,8 +16,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define MAX_LIGHT_COUNT 8
|
||||
|
||||
precision highp float;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D base_color_texture;
|
||||
@@ -28,21 +26,31 @@ layout(location = 2) in vec3 in_normal;
|
||||
|
||||
layout(location = 0) out vec4 o_color;
|
||||
|
||||
layout(set = 0, binding = 1) uniform MVPUniform
|
||||
layout(set = 0, binding = 1) uniform GlobalUniform
|
||||
{
|
||||
mat4 model;
|
||||
mat4 view_proj;
|
||||
mat4 scale;
|
||||
mat4 padding;
|
||||
} mvp_uniform;
|
||||
vec3 camera_position;
|
||||
}
|
||||
global_uniform;
|
||||
|
||||
// Push constants come with a limitation in the size of data.
|
||||
// The standard requires at least 128 bytes
|
||||
layout(push_constant, std430) uniform PBRMaterialUniform
|
||||
{
|
||||
vec4 base_color_factor;
|
||||
float metallic_factor;
|
||||
float roughness_factor;
|
||||
}
|
||||
pbr_material_uniform;
|
||||
|
||||
#include "lighting.h"
|
||||
|
||||
layout(set = 0, binding = 4) uniform LightsInfo
|
||||
{
|
||||
Light directional_lights[MAX_LIGHT_COUNT];
|
||||
Light point_lights[MAX_LIGHT_COUNT];
|
||||
Light spot_lights[MAX_LIGHT_COUNT];
|
||||
Light directional_lights[48];
|
||||
Light point_lights[48];
|
||||
Light spot_lights[48];
|
||||
}
|
||||
lights_info;
|
||||
|
||||
@@ -15,11 +15,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
precision mediump float;
|
||||
|
||||
layout (location = 0) out vec2 outUV;
|
||||
layout (binding = 0) uniform sampler2D fontSampler;
|
||||
|
||||
layout (location = 0) in vec2 inUV;
|
||||
layout (location = 1) in vec4 inColor;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
|
||||
outColor = inColor * texture(fontSampler, inUV);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2021-2024, Sascha Willems
|
||||
#version 320 es
|
||||
/* Copyright (c) 2019, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -16,19 +16,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
layout (location = 0) in vec3 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
precision mediump float;
|
||||
|
||||
layout (binding = 0) uniform UBO
|
||||
{
|
||||
mat4 projection;
|
||||
mat4 model;
|
||||
} ubo;
|
||||
layout (location = 0) in vec2 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
layout (location = 2) in vec4 inColor;
|
||||
|
||||
layout (push_constant) uniform PushConstants {
|
||||
mat4 transform;
|
||||
} pushConstants;
|
||||
|
||||
layout (location = 0) out vec2 outUV;
|
||||
layout (location = 1) out vec4 outColor;
|
||||
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
outUV = inUV;
|
||||
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
|
||||
outColor = inColor;
|
||||
gl_Position = pushConstants.transform * vec4(inPos.xy, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Copyright (c) 2020-2025, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec4 position; // position.w represents type of light
|
||||
vec4 color; // color.w represents light intensity
|
||||
vec4 direction; // direction.w represents range
|
||||
vec2 info; // (only used for spot lights) info.x represents light inner cone angle, info.y represents light outer cone angle
|
||||
};
|
||||
|
||||
vec3 apply_directional_light(Light light, vec3 normal)
|
||||
{
|
||||
vec3 world_to_light = -light.direction.xyz;
|
||||
world_to_light = normalize(world_to_light);
|
||||
float ndotl = clamp(dot(normal, world_to_light), 0.0, 1.0);
|
||||
return ndotl * light.color.w * light.color.rgb;
|
||||
}
|
||||
|
||||
vec3 apply_point_light(Light light, vec3 pos, vec3 normal)
|
||||
{
|
||||
vec3 world_to_light = light.position.xyz - pos;
|
||||
float dist = length(world_to_light) * 0.005;
|
||||
float atten = 1.0 / (dist * dist);
|
||||
world_to_light = normalize(world_to_light);
|
||||
float ndotl = clamp(dot(normal, world_to_light), 0.0, 1.0);
|
||||
return ndotl * light.color.w * atten * light.color.rgb;
|
||||
}
|
||||
|
||||
vec3 apply_spot_light(Light light, vec3 pos, vec3 normal)
|
||||
{
|
||||
vec3 light_to_pixel = normalize(pos - light.position.xyz);
|
||||
float theta = dot(light_to_pixel, normalize(light.direction.xyz));
|
||||
float inner_cone_angle = light.info.x;
|
||||
float outer_cone_angle = light.info.y;
|
||||
float intensity = (theta - outer_cone_angle) / (inner_cone_angle - outer_cone_angle);
|
||||
return smoothstep(0.0, 1.0, intensity) * light.color.w * light.color.rgb;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2023-2024, Sascha Willems
|
||||
/* Copyright (c) 2022-2024, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -16,13 +16,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
layout (set = 2, binding = 0) uniform sampler2D samplerColorMap;
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
|
||||
layout (set = 0, binding = 1) uniform sampler2D textures[];
|
||||
|
||||
layout (location = 0) in vec2 inUV;
|
||||
layout (location = 1) flat in int inTexIndex;
|
||||
|
||||
layout (location = 0) out vec4 outFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outFragColor = texture(samplerColorMap, inUV);
|
||||
outFragColor = texture(textures[nonuniformEXT(inTexIndex)], inUV);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2023-2024, Sascha Willems
|
||||
/* Copyright (c) 2022-2024, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -18,19 +18,20 @@
|
||||
|
||||
layout (location = 0) in vec3 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
layout (location = 2) in vec3 inNormal;
|
||||
layout (location = 2) in int inTexIndex;
|
||||
|
||||
layout (binding = 0) uniform UBO
|
||||
{
|
||||
mat4 projection;
|
||||
mat4 model;
|
||||
vec4 viewPos;
|
||||
mat4 view;
|
||||
} ubo;
|
||||
|
||||
layout (location = 0) out vec2 outUV;
|
||||
layout (location = 1) flat out int outTexIndex;
|
||||
|
||||
void main()
|
||||
{
|
||||
outUV = inUV;
|
||||
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
|
||||
outTexIndex = inTexIndex;
|
||||
gl_Position = ubo.projection * ubo.view * vec4(inPos.xyz, 1.0);
|
||||
}
|
||||
@@ -16,17 +16,18 @@
|
||||
*/
|
||||
|
||||
[[vk::binding(1, 0)]]
|
||||
Texture2D textureColor : register(t1, space0);
|
||||
Texture2D textures[] : register(t1, space0);
|
||||
[[vk::binding(1, 0)]]
|
||||
SamplerState samplerColor : register(s1, space0);
|
||||
SamplerState samplers[] : register(s1, space0);
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 Pos : SV_POSITION;
|
||||
[[vk::location(0)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
|
||||
};
|
||||
|
||||
float4 main(VSOutput input) : SV_TARGET0
|
||||
float4 main(VSOutput input) : SV_TARGET
|
||||
{
|
||||
return textureColor.Sample(samplerColor, input.UV);
|
||||
return textures[NonUniformResourceIndex(input.TexIndex)].Sample(samplers[NonUniformResourceIndex(input.TexIndex)], input.UV);
|
||||
}
|
||||
@@ -15,18 +15,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
[[vk::location(0)]] float3 Pos : POSITION0;
|
||||
[[vk::location(1)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(2)]] float3 Normal : NORMAL0;
|
||||
[[vk::location(2)]] int TexIndex : TEXCOORD1;
|
||||
};
|
||||
|
||||
struct UBO
|
||||
{
|
||||
float4x4 projection;
|
||||
float4x4 model;
|
||||
float4 viewPos;
|
||||
float4x4 view;
|
||||
};
|
||||
[[vk::binding(0, 0)]]
|
||||
ConstantBuffer<UBO> ubo : register(b0, space0);
|
||||
@@ -35,12 +35,14 @@ struct VSOutput
|
||||
{
|
||||
float4 Pos : SV_POSITION;
|
||||
[[vk::location(0)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
|
||||
};
|
||||
|
||||
VSOutput main(VSInput input)
|
||||
{
|
||||
VSOutput output = (VSOutput) 0;
|
||||
output.UV = input.UV;
|
||||
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
|
||||
output.TexIndex = input.TexIndex;
|
||||
output.Pos = mul(ubo.projection, mul(ubo.view, float4(input.Pos.xyz, 1.0)));
|
||||
return output;
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2024, Sascha Willems
|
||||
/* Copyright (c) 2025, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -15,16 +15,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
Texture2D textureColor : register(t1);
|
||||
SamplerState samplerColor : register(s1);
|
||||
[[vk::binding(1, 0)]]
|
||||
Sampler2D textures[] : register(t1, space0);
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 Pos : SV_POSITION;
|
||||
[[vk::location(0)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
|
||||
};
|
||||
|
||||
[shader("fragment")]
|
||||
float4 main(VSOutput input) : SV_TARGET
|
||||
{
|
||||
return textureColor.Sample(samplerColor, input.UV);
|
||||
return textures[NonUniformResourceIndex(input.TexIndex)].Sample(input.UV);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2024, Sascha Willems
|
||||
/* Copyright (c) 2025, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -15,34 +15,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
struct VSInput
|
||||
{
|
||||
[[vk::location(0)]] float3 Pos : POSITION0;
|
||||
[[vk::location(1)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(2)]] int TexIndex : TEXCOORD1;
|
||||
};
|
||||
|
||||
struct UBO
|
||||
{
|
||||
float4x4 projection;
|
||||
float4x4 view;
|
||||
float4x4 model;
|
||||
};
|
||||
[[vk::binding(0, 0)]]
|
||||
ConstantBuffer<UBO> ubo : register(b0);
|
||||
ConstantBuffer<UBO> ubo : register(b0, space0);
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 Pos : SV_POSITION;
|
||||
[[vk::location(0)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(1)]] nointerpolation uint ColorOffset : TEXCOORD1;
|
||||
[[vk::location(1)]] nointerpolation int TexIndex : TEXCOORD1;
|
||||
};
|
||||
|
||||
[shader("vertex")]
|
||||
VSOutput main(VSInput input)
|
||||
{
|
||||
VSOutput output = (VSOutput) 0;
|
||||
output.UV = input.UV;
|
||||
output.ColorOffset = input.Pos.z == 1.0 ? 4 : 0;
|
||||
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
|
||||
|
||||
output.TexIndex = input.TexIndex;
|
||||
output.Pos = mul(ubo.projection, mul(ubo.view, float4(input.Pos.xyz, 1.0)));
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#version 450
|
||||
|
||||
// 输入变量(与顶点着色器输出对应)
|
||||
layout(location = 0) in vec2 inTexCoord;
|
||||
|
||||
// 输出颜色
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
// 纹理采样器
|
||||
layout(binding = 0) uniform sampler2D texSampler;
|
||||
|
||||
void main() {
|
||||
// 采样纹理
|
||||
outColor = texture(texSampler, inTexCoord);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#version 450
|
||||
|
||||
// 硬编码的顶点数据 - 4个顶点组成三角形带覆盖整个屏幕
|
||||
// const vec2 positions[6] = vec2[6](
|
||||
// vec2( 1.0, 1.0), // 右上 - 三角形1
|
||||
// vec2(-1.0, 1.0), // 左上 - 三角形1
|
||||
// vec2(-1.0, -1.0), // 左下 - 三角形1
|
||||
|
||||
// vec2(-1.0, -1.0), // 左下 - 三角形2
|
||||
// vec2( 1.0, -1.0), // 右下 - 三角形2
|
||||
// vec2( 1.0, 1.0) // 右上 - 三角形2
|
||||
// );
|
||||
|
||||
const float offset = (640-480)/(480.0);
|
||||
|
||||
|
||||
const vec2 positions[6] = vec2[6](
|
||||
vec2( 1.0, -1.0 -offset), // 右下
|
||||
vec2(1.0, 1.0 + offset), // 右上 - 三角形1
|
||||
vec2(-1.0, 1.0 + offset), // 左上 - 三角形1
|
||||
|
||||
vec2(-1.0, 1.0 + offset), // 左上 - 三角形2
|
||||
vec2( -1.0, -1.0-offset), // 左下 - 三角形2
|
||||
vec2( 1.0, -1.0-offset) // 右下 - 三角形2
|
||||
);
|
||||
|
||||
|
||||
|
||||
const vec2 texCoords[6] = vec2[6](
|
||||
vec2(1.0, 1.0), // 右上
|
||||
vec2(0.0, 1.0), // 左上
|
||||
vec2(0.0, 0.0), // 左下
|
||||
|
||||
vec2(0.0, 0.0), // 左下
|
||||
vec2(1.0, 0.0), // 右下
|
||||
vec2(1.0, 1.0) // 右上
|
||||
);
|
||||
|
||||
// 定义 Push Constant,只有一个 float
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float myValue;
|
||||
} pushConstants;
|
||||
|
||||
|
||||
// 输出变量
|
||||
layout(location = 0) out vec2 outTexCoord;
|
||||
|
||||
void main() {
|
||||
// 获取顶点位置
|
||||
vec2 position = positions[gl_VertexIndex];
|
||||
position = position * pushConstants.myValue;
|
||||
|
||||
// 设置输出位置(Vulkan使用不同的坐标系)
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
|
||||
// 输出纹理坐标
|
||||
outTexCoord = texCoords[gl_VertexIndex];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
layout(location = 1) in vec3 fragLineStart;
|
||||
layout(location = 2) in vec3 fragLineEnd;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(binding = 0) uniform UniformBufferObject {
|
||||
mat4 proj;
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
} ubo;
|
||||
|
||||
layout(binding = 1) uniform DashParameters {
|
||||
float dashSize;
|
||||
float gapSize;
|
||||
float dashOffset;
|
||||
float uAASize;
|
||||
int useWorldSpace;
|
||||
} dashParams;
|
||||
|
||||
void main() {
|
||||
vec2 fragPos = gl_FragCoord.xy;
|
||||
|
||||
// 网格参数
|
||||
float gridSize = 14.0; // 网格大小
|
||||
float dotRadius = 6.0; // 点状半径
|
||||
|
||||
// 计算当前片段所在的网格和网格中心坐标
|
||||
vec2 gridCoord = fragPos / gridSize;
|
||||
vec2 gridCenter = (floor(gridCoord) + 0.5) * gridSize;
|
||||
|
||||
// 计算片段到网格中心的距离
|
||||
float distanceToCenter = length(fragPos - gridCenter);
|
||||
|
||||
// 如果距离超过点状半径,丢弃(创建点状虚线)
|
||||
if (distanceToCenter > dotRadius && fragColor.y > 0.5) {
|
||||
discard;
|
||||
}
|
||||
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inColor;
|
||||
layout(location = 2) in vec3 inLineStart;
|
||||
layout(location = 3) in vec3 inLineEnd;
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
layout(location = 1) out vec3 fragLineStart;
|
||||
layout(location = 2) out vec3 fragLineEnd;
|
||||
|
||||
|
||||
layout(binding = 0) uniform UniformBufferObject {
|
||||
mat4 proj;
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
} ubo;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float myValue;
|
||||
} pushConstants;
|
||||
|
||||
void main() {
|
||||
mat4 worldviewproj = ubo.proj * ubo.view * ubo.model;
|
||||
//gl_Position = worldviewproj * vec4(inPosition, 1.0);
|
||||
|
||||
vec2 position = vec2(inPosition.xy*2 -1);
|
||||
//gl_Position = vec4(inPosition.xy*2 -1, 0.5, 1.0);
|
||||
position = position * pushConstants.myValue;
|
||||
gl_Position = vec4(position, 0.5, 1.0);
|
||||
|
||||
fragColor = inColor;
|
||||
|
||||
fragLineStart = (worldviewproj * vec4(inLineStart, 1.0)).xyz;
|
||||
fragLineEnd = (worldviewproj * vec4(inLineEnd, 1.0)).xyz;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
|
||||
void main() {
|
||||
outColor = vec4(1, 0, 0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inColor;
|
||||
layout(location = 2) in vec3 inLineStart;
|
||||
layout(location = 3) in vec3 inLineEnd;
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
|
||||
layout(binding = 0) uniform UniformBufferObject {
|
||||
mat4 proj;
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
} ubo;
|
||||
|
||||
void main() {
|
||||
//mat4 worldviewproj = ubo.proj * ubo.view * ubo.model;
|
||||
mat4 mvp = ubo.proj * ubo.view * ubo.model;
|
||||
//gl_Position = worldviewproj * vec4(inPosition, 1.0);
|
||||
gl_Position = mvp * vec4(inPosition, 1.0);
|
||||
|
||||
fragColor = inColor;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 450
|
||||
|
||||
// 从顶点着色器接收的颜色
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
|
||||
// 输出颜色
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(fragColor, 1.0); // 使用传入的颜色
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inColor;
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
|
||||
layout(binding = 0) uniform UniformBufferObject {
|
||||
mat4 proj;
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
} ubo;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float myValue;
|
||||
} pushConstants;
|
||||
|
||||
void main() {
|
||||
// 预计算MVP矩阵以减少乘法次数
|
||||
//mat4 mvp = ubo.proj * ubo.view * ubo.model;
|
||||
//vec3 pos = vec3(2.0, inPosition.y, inPosition.z);
|
||||
//gl_Position = mvp * vec4(inPosition, 1.0);
|
||||
|
||||
vec2 position = vec2(inPosition.xy*2 -1);
|
||||
//gl_Position = vec4(inPosition.xy*2 -1, 0.5, 1.0);
|
||||
position = position * pushConstants.myValue;
|
||||
gl_Position = vec4(position, 0.5, 1.0);
|
||||
|
||||
fragColor = inColor;
|
||||
gl_PointSize = 2.0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#version 450
|
||||
|
||||
layout (binding = 1) uniform sampler2D samplerColor;
|
||||
|
||||
layout (location = 0) in vec2 inUV;
|
||||
layout (location = 1) in vec3 inNormal;
|
||||
|
||||
|
||||
layout (location = 0) out vec4 outFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(samplerColor, inUV);
|
||||
|
||||
vec3 N = normalize(inNormal);
|
||||
|
||||
vec4 textureColor = color;
|
||||
if (textureColor.a < 0.1) {
|
||||
//textureColor = vec4(0.2, 0.2, 0.2, 0.5);
|
||||
discard;
|
||||
}
|
||||
|
||||
outFragColor = textureColor;
|
||||
|
||||
// outFragColor = vec4(1, 0, 0, 1);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#version 450
|
||||
|
||||
|
||||
layout (location = 0) in vec3 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
layout (location = 2) in vec3 inNormal;
|
||||
|
||||
layout (binding = 0) uniform UBO
|
||||
{
|
||||
mat4 projection;
|
||||
mat4 model;
|
||||
vec4 viewPos;
|
||||
float lodBias;
|
||||
} ubo;
|
||||
|
||||
layout (location = 0) out vec2 outUV;
|
||||
layout (location = 1) out vec3 outNormal;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float myValue;
|
||||
} pushConstants;
|
||||
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
outUV = inUV;
|
||||
|
||||
vec2 position = vec2(inPos.xy*2 -1);
|
||||
position = position * pushConstants.myValue;
|
||||
gl_Position = vec4(position, 0.5, 1.0);
|
||||
|
||||
vec4 pos = ubo.model * vec4(inPos, 1.0);
|
||||
outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
|
||||
|
||||
}
|
||||
@@ -15,23 +15,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
[[vk::binding(1, 0)]]
|
||||
Texture2D textureColor : register(t1);
|
||||
[[vk::binding(1, 0)]]
|
||||
SamplerState samplerColor : register(s1);
|
||||
|
||||
struct VSOutput
|
||||
{
|
||||
float4 Pos : SV_POSITION;
|
||||
[[vk::location(0)]] float3 Normal : NORMAL0;
|
||||
[[vk::location(1)]] float3 Color : COLOR0;
|
||||
[[vk::location(2)]] float3 ViewVec : TEXCOORD0;
|
||||
[[vk::location(3)]] float3 LightVec : TEXCOORD1;
|
||||
[[vk::location(0)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(1)]] float LodBias : FLOAT0;
|
||||
[[vk::location(2)]] float3 Normal : NORMAL0;
|
||||
[[vk::location(3)]] float3 ViewVec : VECTOR0;
|
||||
[[vk::location(4)]] float3 LightVec : VECTOR1;
|
||||
};
|
||||
|
||||
float4 main(VSOutput input) : SV_TARGET0
|
||||
{
|
||||
float4 color = textureColor.SampleBias(samplerColor, input.UV, input.LodBias);
|
||||
float3 N = normalize(input.Normal);
|
||||
float3 L = normalize(input.LightVec);
|
||||
float3 V = normalize(input.ViewVec);
|
||||
float3 R = reflect(-L, N);
|
||||
float3 ambient = (0.25).xxx;
|
||||
float3 diffuse = max(dot(N, L), 0.0).xxx;
|
||||
float3 specular = (pow(max(dot(R, V), 0.0), 16.0)).xxx * 0.75;
|
||||
return float4((ambient + diffuse) * input.Color + specular, 1.0);
|
||||
float3 specular = (pow(max(dot(R, V), 0.0), 16.0)).xxx * color.a;
|
||||
return float4(diffuse * color.rgb + specular, 1.0);
|
||||
}
|
||||
@@ -27,8 +27,7 @@ struct UBO
|
||||
float4x4 projection;
|
||||
float4x4 model;
|
||||
float4 viewPos;
|
||||
float locSpeed;
|
||||
float globSpeed;
|
||||
float lodBias;
|
||||
};
|
||||
[[vk::binding(0, 0)]]
|
||||
ConstantBuffer<UBO> ubo : register(b0);
|
||||
@@ -37,12 +36,25 @@ struct VSOutput
|
||||
{
|
||||
float4 Pos : SV_POSITION;
|
||||
[[vk::location(0)]] float2 UV : TEXCOORD0;
|
||||
[[vk::location(1)]] float LodBias : FLOAT0;
|
||||
[[vk::location(2)]] float3 Normal : NORMAL0;
|
||||
[[vk::location(3)]] float3 ViewVec : VECTOR0;
|
||||
[[vk::location(4)]] float3 LightVec : VECTOR1;
|
||||
};
|
||||
|
||||
VSOutput main(VSInput input)
|
||||
{
|
||||
VSOutput output = (VSOutput) 0;
|
||||
|
||||
float3 vecPos = mul((float3x3) ubo.model, input.Pos.xyz).xyz;
|
||||
float3 lightPos = mul((float3x3) ubo.model, (1.0).xxx);
|
||||
|
||||
output.UV = input.UV;
|
||||
output.LodBias = ubo.lodBias;
|
||||
output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
|
||||
output.Normal = mul((float3x3) ubo.model, input.Normal);
|
||||
output.LightVec = lightPos - vecPos;
|
||||
output.ViewVec = ubo.viewPos.xyz - vecPos;
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2024-2025, Huawei Technologies Co., Ltd.
|
||||
#version 320 es
|
||||
/* Copyright (c) 2019, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -16,6 +16,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) in vec3 in_color;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2023-2024, Mobica Limited
|
||||
#version 320 es
|
||||
/* Copyright (c) 2019-2023, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -16,10 +16,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
precision mediump float;
|
||||
|
||||
layout(location = 0) out vec3 out_color;
|
||||
|
||||
vec2 triangle_positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
@@ -32,6 +34,7 @@ vec3 triangle_colors[3] = vec3[](
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(triangle_positions[gl_VertexIndex], 0.0, 1.0);
|
||||
gl_Position = vec4(triangle_positions[gl_VertexIndex], 0.5, 1.0);
|
||||
|
||||
out_color = triangle_colors[gl_VertexIndex];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2020-2024, Sascha Willems
|
||||
/* Copyright (c) 2019, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -16,14 +16,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
layout (binding = 0) uniform sampler2D samplerColor0;
|
||||
layout (binding = 1) uniform sampler2D samplerColor1;
|
||||
layout (binding = 0) uniform sampler2D fontSampler;
|
||||
|
||||
layout (location = 0) in vec2 inUV;
|
||||
layout (location = 1) in vec4 inColor;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = texture(samplerColor0, inUV);
|
||||
outColor = inColor * texture(fontSampler, inUV);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#version 450
|
||||
/* Copyright (c) 2021-2024, Sascha Willems
|
||||
/* Copyright (c) 2019, Sascha Willems
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
@@ -16,21 +16,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
layout (location = 0) in vec3 inPos;
|
||||
layout (location = 0) in vec2 inPos;
|
||||
layout (location = 1) in vec2 inUV;
|
||||
layout (location = 2) in vec3 inNormal;
|
||||
layout (location = 2) in vec4 inColor;
|
||||
|
||||
layout (set = 0, binding = 0) uniform UBO
|
||||
{
|
||||
mat4 projection;
|
||||
mat4 model;
|
||||
vec4 viewPos;
|
||||
} ubo;
|
||||
layout (push_constant) uniform PushConstants {
|
||||
mat4 transform;
|
||||
} pushConstants;
|
||||
|
||||
layout (location = 0) out vec2 outUV;
|
||||
layout (location = 1) out vec4 outColor;
|
||||
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
outUV = inUV;
|
||||
gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
|
||||
outColor = inColor;
|
||||
gl_Position = pushConstants.transform * vec4(inPos.xy, 0.0, 1.0);
|
||||
}
|
||||
@@ -1,22 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
- Copyright (c) 2019-2024, Arm Limited and Contributors
|
||||
-
|
||||
- SPDX-License-Identifier: Apache-2.0
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 the "License";
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="1"
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
package com.khronos.vulkan_samples;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class AssetsCopyUtil {
|
||||
private static final String TAG = "AssetsCopyUtil";
|
||||
private static final String PREF_NAME = "assets_copy_pref";
|
||||
private static final String PREF_KEY_COPIED_FILES = "copied_files";
|
||||
|
||||
/**
|
||||
* 复制 assets 到应用专属外部存储的 files 目录(递归复制子文件夹)
|
||||
*/
|
||||
public static void copyAssetsToAppFiles(Context context, String assetsPath) {
|
||||
try {
|
||||
AssetManager assetManager = context.getAssets();
|
||||
|
||||
// 获取应用专属外部存储的 files 目录
|
||||
File externalFilesDir = context.getExternalFilesDir(null);
|
||||
if (externalFilesDir == null) {
|
||||
Log.e(TAG, "External files directory is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// 递归复制
|
||||
copyAssetsRecursive(context, assetManager, assetsPath, externalFilesDir.getAbsolutePath());
|
||||
|
||||
Log.i(TAG, "Assets copy completed for: " + assetsPath);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error copying assets from: " + assetsPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归复制 assets 中的文件和文件夹
|
||||
*/
|
||||
private static void copyAssetsRecursive(Context context, AssetManager assetManager,
|
||||
String assetsPath, String targetBasePath) {
|
||||
try {
|
||||
// 获取 assets 路径下的所有项目
|
||||
String[] items = assetManager.list(assetsPath);
|
||||
if (items == null || items.length == 0) {
|
||||
Log.w(TAG, "No items found in assets path: " + assetsPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建目标目录
|
||||
File targetDir = new File(targetBasePath, assetsPath);
|
||||
if (!targetDir.exists() && !targetDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create target directory: " + targetDir.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取已复制的文件列表
|
||||
Set<String> copiedFiles = getCopiedFiles(context);
|
||||
boolean hasNewFiles = false;
|
||||
|
||||
for (String item : items) {
|
||||
String assetItemPath = assetsPath.isEmpty() ? item : assetsPath + "/" + item;
|
||||
String fileKey = assetItemPath;
|
||||
|
||||
// 检查是否已经复制过
|
||||
if (copiedFiles.contains(fileKey)) {
|
||||
Log.d(TAG, "Item already copied: " + assetItemPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 判断是文件还是目录的正确方法
|
||||
boolean isFile = isAssetFile(assetManager, assetsPath, item);
|
||||
|
||||
if (isFile) {
|
||||
// 复制文件
|
||||
if (copySingleFile(assetManager, assetItemPath, targetDir, item)) {
|
||||
copiedFiles.add(fileKey);
|
||||
hasNewFiles = true;
|
||||
Log.d(TAG, "Successfully copied file: " + assetItemPath);
|
||||
}
|
||||
} else {
|
||||
// 递归复制子目录
|
||||
copyAssetsRecursive(context, assetManager, assetItemPath, targetBasePath);
|
||||
// 将目录也标记为已处理
|
||||
copiedFiles.add(fileKey);
|
||||
hasNewFiles = true;
|
||||
Log.d(TAG, "Processed directory: " + assetItemPath);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error processing: " + assetItemPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有新文件被复制,更新记录
|
||||
if (hasNewFiles) {
|
||||
saveCopiedFiles(context, copiedFiles);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in recursive copy for path: " + assetsPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 正确判断 assets 中的项目是文件还是目录
|
||||
*/
|
||||
private static boolean isAssetFile(AssetManager assetManager, String basePath, String item) {
|
||||
try {
|
||||
// 方法1:尝试列出子项,如果能列出且不为空,说明是目录
|
||||
String testPath = basePath.isEmpty() ? item : basePath + "/" + item;
|
||||
String[] subItems = assetManager.list(testPath);
|
||||
|
||||
// 如果子项为空,说明是文件或者是空目录
|
||||
if (subItems == null) {
|
||||
return true; // 应该是文件
|
||||
}
|
||||
|
||||
// 如果有子项,说明是目录
|
||||
if (subItems.length > 0) {
|
||||
return false; // 是目录
|
||||
}
|
||||
|
||||
// 如果子项为空数组,需要进一步判断
|
||||
// 尝试打开它,如果能打开说明是文件,不能打开说明是空目录
|
||||
try {
|
||||
InputStream test = assetManager.open(testPath);
|
||||
test.close();
|
||||
return true; // 能打开,是文件
|
||||
} catch (Exception e) {
|
||||
return false; // 不能打开,是空目录
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error checking asset type: " + item, e);
|
||||
return true; // 默认当作文件处理
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更可靠的递归复制方法
|
||||
*/
|
||||
public static void copyAssetsToAppFilesSimple(Context context, String assetsPath) {
|
||||
try {
|
||||
AssetManager assetManager = context.getAssets();
|
||||
File externalFilesDir = context.getExternalFilesDir(null);
|
||||
if (externalFilesDir == null) {
|
||||
Log.e(TAG, "External storage not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建目标目录
|
||||
File targetBaseDir = new File(externalFilesDir, assetsPath);
|
||||
if (!targetBaseDir.exists() && !targetBaseDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create base directory");
|
||||
return;
|
||||
}
|
||||
|
||||
copyAssetsRecursiveSimple(assetManager, assetsPath, targetBaseDir.getAbsolutePath());
|
||||
Log.i(TAG, "Simple copy completed for: " + assetsPath);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in simple copy", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyAssetsRecursiveSimple(AssetManager assetManager, String assetsPath, String targetPath) {
|
||||
try {
|
||||
String[] items = assetManager.list(assetsPath);
|
||||
if (items == null || items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String item : items) {
|
||||
String fullAssetPath = assetsPath.isEmpty() ? item : assetsPath + "/" + item;
|
||||
|
||||
// 判断是文件还是目录
|
||||
boolean isFile = isAssetFileSimple(assetManager, fullAssetPath);
|
||||
|
||||
if (isFile) {
|
||||
// 复制文件
|
||||
copySingleFileSimple(assetManager, fullAssetPath, targetPath, item);
|
||||
Log.d(TAG, "Copied file: " + fullAssetPath + " to " + targetPath);
|
||||
} else {
|
||||
// 创建子目录并递归复制
|
||||
File subDir = new File(targetPath, item);
|
||||
if (!subDir.exists()) {
|
||||
subDir.mkdirs();
|
||||
}
|
||||
copyAssetsRecursiveSimple(assetManager, fullAssetPath, subDir.getAbsolutePath());
|
||||
Log.d(TAG, "Processed directory: " + fullAssetPath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in simple recursive copy: " + assetsPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAssetFileSimple(AssetManager assetManager, String path) {
|
||||
try {
|
||||
String[] subItems = assetManager.list(path);
|
||||
if (subItems == null) return true; // 应该是文件
|
||||
|
||||
if (subItems.length > 0) return false; // 有子项,是目录
|
||||
|
||||
// 空数组,尝试打开判断
|
||||
try {
|
||||
InputStream test = assetManager.open(path);
|
||||
test.close();
|
||||
return true; // 能打开,是文件
|
||||
} catch (Exception e) {
|
||||
return false; // 不能打开,是空目录
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return true; // 出错时默认当作文件
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制单个文件
|
||||
*/
|
||||
private static boolean copySingleFile(AssetManager assetManager, String assetFilePath, File targetDir, String filename) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
|
||||
try {
|
||||
File outFile = new File(targetDir, filename);
|
||||
in = assetManager.open(assetFilePath);
|
||||
out = new FileOutputStream(outFile);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
Log.d(TAG, "File copied: " + outFile.getAbsolutePath());
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to copy file: " + assetFilePath, e);
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error closing streams", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copySingleFileSimple(AssetManager assetManager, String assetFilePath,
|
||||
String targetPath, String filename) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
|
||||
try {
|
||||
File outFile = new File(targetPath, filename);
|
||||
in = assetManager.open(assetFilePath);
|
||||
out = new FileOutputStream(outFile);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error copying: " + assetFilePath, e);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error closing streams", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调试方法:打印 assets 结构
|
||||
*/
|
||||
public static void printAssetsStructure(Context context, String path) {
|
||||
try {
|
||||
Log.d(TAG, "=== Assets Structure for: " + path + " ===");
|
||||
AssetManager am = context.getAssets();
|
||||
printAssetsRecursive(am, path, 0);
|
||||
Log.d(TAG, "=== End of Assets Structure ===");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error printing assets structure", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printAssetsRecursive(AssetManager am, String path, int depth) {
|
||||
try {
|
||||
String indent = "";
|
||||
for (int i = 0; i < depth; i++) indent += " ";
|
||||
|
||||
String[] items = am.list(path);
|
||||
if (items != null) {
|
||||
for (String item : items) {
|
||||
String fullPath = path.isEmpty() ? item : path + "/" + item;
|
||||
boolean isFile = isAssetFileSimple(am, fullPath);
|
||||
|
||||
if (isFile) {
|
||||
Log.d(TAG, indent + "📄 " + item + " (file)");
|
||||
} else {
|
||||
Log.d(TAG, indent + "📁 " + item + " (dir)");
|
||||
printAssetsRecursive(am, fullPath, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in recursive print", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已复制的文件列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Set<String> getCopiedFiles(Context context) {
|
||||
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.getStringSet(PREF_KEY_COPIED_FILES, new HashSet<String>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存已复制的文件列表
|
||||
*/
|
||||
private static void saveCopiedFiles(Context context, Set<String> copiedFiles) {
|
||||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putStringSet(PREF_KEY_COPIED_FILES, new HashSet<>(copiedFiles))
|
||||
.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空复制记录
|
||||
*/
|
||||
public static void clearCopyRecords(Context context) {
|
||||
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.remove(PREF_KEY_COPIED_FILES)
|
||||
.apply();
|
||||
Log.d(TAG, "Copy records cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查外部存储是否可用
|
||||
*/
|
||||
public static boolean isExternalStorageAvailable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
return Environment.MEDIA_MOUNTED.equals(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目标文件的完整路径
|
||||
*/
|
||||
public static String getFilePath(Context context, String relativePath) {
|
||||
File externalFilesDir = context.getExternalFilesDir(null);
|
||||
if (externalFilesDir == null) return null;
|
||||
|
||||
File targetFile = new File(externalFilesDir, relativePath);
|
||||
return targetFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
public static boolean fileExists(Context context, String relativePath) {
|
||||
File externalFilesDir = context.getExternalFilesDir(null);
|
||||
if (externalFilesDir == null) return false;
|
||||
|
||||
File targetFile = new File(externalFilesDir, relativePath);
|
||||
return targetFile.exists();
|
||||
}
|
||||
}
|
||||
@@ -18,20 +18,24 @@ public class CameraDataFetcher {
|
||||
|
||||
// 数据模型类
|
||||
public static class CameraData {
|
||||
public float fov;
|
||||
public float x;
|
||||
public float y;
|
||||
public float fov = 47;
|
||||
public float x = -0.5f;
|
||||
public float y = -0.51f;
|
||||
public float z;
|
||||
public float sx;
|
||||
public float sy;
|
||||
public float sz;
|
||||
public float sx = 1;
|
||||
public float sy = 1;
|
||||
public float sz = 1;
|
||||
public float rotx;
|
||||
public float roty;
|
||||
public float rotz;
|
||||
public float camx;
|
||||
public float camy;
|
||||
public float camz;
|
||||
public float z_rate;
|
||||
public float z_rate = -1;
|
||||
public float dashSize = 5;
|
||||
public float gapSize = 3;
|
||||
public float dashOffset = 0;
|
||||
public float uAASize = 1.5f;
|
||||
|
||||
// 从JSONObject解析数据
|
||||
public static CameraData fromJson(JSONObject json) {
|
||||
@@ -51,6 +55,10 @@ public class CameraDataFetcher {
|
||||
data.camy = (float)json.getDouble("camy");
|
||||
data.camz = (float)json.getDouble("camz");
|
||||
data.z_rate = (float)json.getDouble("z_rate");
|
||||
data.dashSize = (float)json.getDouble("dashSize");
|
||||
data.gapSize = (float)json.getDouble("gapSize");
|
||||
data.dashOffset = (float)json.getDouble("dashOffset");
|
||||
data.uAASize = (float)json.getDouble("uAASize");
|
||||
} catch (Exception e) {
|
||||
Log.e("CameraData", "解析JSON失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.khronos.vulkan_samples
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.net.Uri
|
||||
@@ -103,7 +104,8 @@ class FaceLandmarkerHelper(
|
||||
.setMinTrackingConfidence(minFaceTrackingConfidence)
|
||||
.setMinFacePresenceConfidence(minFacePresenceConfidence)
|
||||
.setNumFaces(maxNumFaces)
|
||||
.setOutputFaceBlendshapes(true)
|
||||
.setOutputFaceBlendshapes(false)
|
||||
.setOutputFacialTransformationMatrixes(false)
|
||||
.setRunningMode(runningMode)
|
||||
|
||||
// The ResultListener and ErrorListener only use for LIVE_STREAM mode.
|
||||
@@ -138,6 +140,51 @@ class FaceLandmarkerHelper(
|
||||
}
|
||||
}
|
||||
|
||||
private var frameId: Long = 0
|
||||
// 简化的延时跟踪
|
||||
private val frameTimings = mutableMapOf<Long, FrameTiming>()
|
||||
private var lastFpsUpdateTime: Long = 0
|
||||
private var frameCount: Int = 0
|
||||
private var currentFps: Double = 0.0
|
||||
|
||||
// 内部数据类用于存储时间信息
|
||||
private data class FrameTiming(
|
||||
var captureTime: Long = 0,
|
||||
var preprocessStartTime: Long = 0,
|
||||
var preprocessEndTime: Long = 0,
|
||||
var detectionStartTime: Long = 0
|
||||
)
|
||||
|
||||
// 辅助方法:记录帧时间信息
|
||||
private fun recordFrameTiming(frameId: Long, block: (FrameTiming) -> Unit) {
|
||||
val timing = frameTimings.getOrPut(frameId) { FrameTiming() }
|
||||
block(timing)
|
||||
}
|
||||
|
||||
// 辅助方法:更新FPS计算
|
||||
private fun updateFps() {
|
||||
frameCount++
|
||||
val currentTime = SystemClock.uptimeMillis()
|
||||
|
||||
if (lastFpsUpdateTime == 0L) {
|
||||
lastFpsUpdateTime = currentTime
|
||||
return
|
||||
}
|
||||
|
||||
val timeDiff = currentTime - lastFpsUpdateTime
|
||||
if (timeDiff >= 1000) { // 每秒更新一次FPS
|
||||
currentFps = frameCount * 1000.0 / timeDiff
|
||||
frameCount = 0
|
||||
lastFpsUpdateTime = currentTime
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:清理旧的时间记录
|
||||
private fun cleanupOldTimings(currentFrameId: Long) {
|
||||
val framesToRemove = frameTimings.keys.filter { it < currentFrameId - 10 }
|
||||
framesToRemove.forEach { frameTimings.remove(it) }
|
||||
}
|
||||
|
||||
// Convert the ImageProxy to MP Image and feed it to FacelandmakerHelper.
|
||||
fun detectLiveStream(
|
||||
imageProxy: ImageProxy,
|
||||
@@ -151,6 +198,15 @@ class FaceLandmarkerHelper(
|
||||
}
|
||||
val frameTime = SystemClock.uptimeMillis()
|
||||
|
||||
val currentFrameId = frameId++
|
||||
val captureTime = SystemClock.uptimeMillis()
|
||||
|
||||
// 记录捕获时间
|
||||
recordFrameTiming(currentFrameId) {
|
||||
it.captureTime = captureTime
|
||||
it.preprocessStartTime = SystemClock.uptimeMillis()
|
||||
}
|
||||
|
||||
// Copy out RGB bits from the frame to a bitmap buffer
|
||||
val bitmapBuffer =
|
||||
Bitmap.createBitmap(
|
||||
@@ -180,6 +236,9 @@ class FaceLandmarkerHelper(
|
||||
matrix, true
|
||||
)
|
||||
|
||||
//val bitmap = BitmapFactory.decodeFile("/sdcard/Android/data/com.khronos.vulkan_samples/files/assets/face480.png")
|
||||
//val mpImage = BitmapImageBuilder(bitmap).build()
|
||||
|
||||
// Convert the input Bitmap object to an MPImage object to run inference
|
||||
val mpImage = BitmapImageBuilder(rotatedBitmap).build()
|
||||
|
||||
@@ -194,144 +253,48 @@ class FaceLandmarkerHelper(
|
||||
// be returned in returnLivestreamResult function
|
||||
}
|
||||
|
||||
// Accepts the URI for a video file loaded from the user's gallery and attempts to run
|
||||
// face landmarker inference on the video. This process will evaluate every
|
||||
// frame in the video and attach the results to a bundle that will be
|
||||
// returned.
|
||||
fun detectVideoFile(
|
||||
videoUri: Uri,
|
||||
inferenceIntervalMs: Long
|
||||
): VideoResultBundle? {
|
||||
if (runningMode != RunningMode.VIDEO) {
|
||||
throw IllegalArgumentException(
|
||||
"Attempting to call detectVideoFile" +
|
||||
" while not using RunningMode.VIDEO"
|
||||
)
|
||||
}
|
||||
|
||||
// Inference time is the difference between the system time at the start and finish of the
|
||||
// process
|
||||
val startTime = SystemClock.uptimeMillis()
|
||||
|
||||
var didErrorOccurred = false
|
||||
|
||||
// Load frames from the video and run the face landmarker.
|
||||
val retriever = MediaMetadataRetriever()
|
||||
retriever.setDataSource(context, videoUri)
|
||||
val videoLengthMs =
|
||||
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||
?.toLong()
|
||||
|
||||
// Note: We need to read width/height from frame instead of getting the width/height
|
||||
// of the video directly because MediaRetriever returns frames that are smaller than the
|
||||
// actual dimension of the video file.
|
||||
val firstFrame = retriever.getFrameAtTime(0)
|
||||
val width = firstFrame?.width
|
||||
val height = firstFrame?.height
|
||||
|
||||
// If the video is invalid, returns a null detection result
|
||||
if ((videoLengthMs == null) || (width == null) || (height == null)) return null
|
||||
|
||||
// Next, we'll get one frame every frameInterval ms, then run detection on these frames.
|
||||
val resultList = mutableListOf<FaceLandmarkerResult>()
|
||||
val numberOfFrameToRead = videoLengthMs.div(inferenceIntervalMs)
|
||||
|
||||
for (i in 0..numberOfFrameToRead) {
|
||||
val timestampMs = i * inferenceIntervalMs // ms
|
||||
|
||||
retriever
|
||||
.getFrameAtTime(
|
||||
timestampMs * 1000, // convert from ms to micro-s
|
||||
MediaMetadataRetriever.OPTION_CLOSEST
|
||||
)
|
||||
?.let { frame ->
|
||||
// Convert the video frame to ARGB_8888 which is required by the MediaPipe
|
||||
val argb8888Frame =
|
||||
if (frame.config == Bitmap.Config.ARGB_8888) frame
|
||||
else frame.copy(Bitmap.Config.ARGB_8888, false)
|
||||
|
||||
// Convert the input Bitmap object to an MPImage object to run inference
|
||||
val mpImage = BitmapImageBuilder(argb8888Frame).build()
|
||||
|
||||
// Run face landmarker using MediaPipe Face Landmarker API
|
||||
faceLandmarker?.detectForVideo(mpImage, timestampMs)
|
||||
?.let { detectionResult ->
|
||||
resultList.add(detectionResult)
|
||||
} ?: {
|
||||
didErrorOccurred = true
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"ResultBundle could not be returned" +
|
||||
" in detectVideoFile"
|
||||
)
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
didErrorOccurred = true
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"Frame at specified time could not be" +
|
||||
" retrieved when detecting in video."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
retriever.release()
|
||||
|
||||
val inferenceTimePerFrameMs =
|
||||
(SystemClock.uptimeMillis() - startTime).div(numberOfFrameToRead)
|
||||
|
||||
return if (didErrorOccurred) {
|
||||
null
|
||||
} else {
|
||||
VideoResultBundle(resultList, inferenceTimePerFrameMs, height, width)
|
||||
}
|
||||
}
|
||||
|
||||
// Accepted a Bitmap and runs face landmarker inference on it to return
|
||||
// results back to the caller
|
||||
fun detectImage(image: Bitmap): ResultBundle? {
|
||||
if (runningMode != RunningMode.IMAGE) {
|
||||
throw IllegalArgumentException(
|
||||
"Attempting to call detectImage" +
|
||||
" while not using RunningMode.IMAGE"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Inference time is the difference between the system time at the
|
||||
// start and finish of the process
|
||||
val startTime = SystemClock.uptimeMillis()
|
||||
|
||||
// Convert the input Bitmap object to an MPImage object to run inference
|
||||
val mpImage = BitmapImageBuilder(image).build()
|
||||
|
||||
// Run face landmarker using MediaPipe Face Landmarker API
|
||||
faceLandmarker?.detect(mpImage)?.also { landmarkResult ->
|
||||
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
|
||||
return ResultBundle(
|
||||
landmarkResult,
|
||||
inferenceTimeMs,
|
||||
image.height,
|
||||
image.width
|
||||
)
|
||||
}
|
||||
|
||||
// If faceLandmarker?.detect() returns null, this is likely an error. Returning null
|
||||
// to indicate this.
|
||||
faceLandmarkerHelperListener?.onError(
|
||||
"Face Landmarker failed to detect."
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
// Return the landmark result to this FaceLandmarkerHelper's caller
|
||||
private fun returnLivestreamResult(
|
||||
result: FaceLandmarkerResult,
|
||||
input: MPImage
|
||||
) {
|
||||
val resultTime = SystemClock.uptimeMillis()
|
||||
val frameId = result.timestampMs()
|
||||
if( result.faceLandmarks().size > 0 ) {
|
||||
// 计算各种延时
|
||||
val timings = frameTimings[frameId]
|
||||
if (timings != null) {
|
||||
val totalLatency = resultTime - timings.captureTime
|
||||
val preprocessLatency = timings.preprocessEndTime - timings.preprocessStartTime
|
||||
val detectionLatency = resultTime - timings.detectionStartTime
|
||||
val captureToDetectionLatency = timings.detectionStartTime - timings.captureTime
|
||||
|
||||
Log.i("TimeLatency",
|
||||
"总延时: ${totalLatency}ms | " +
|
||||
"预处理: ${preprocessLatency}ms | " +
|
||||
"检测: ${detectionLatency}ms | " +
|
||||
"捕获到检测: ${captureToDetectionLatency}ms | " +
|
||||
"FPS: ${String.format("%.1f", currentFps)}"
|
||||
)
|
||||
|
||||
val finishTimeMs = SystemClock.uptimeMillis()
|
||||
val inferenceTime = finishTimeMs - result.timestampMs()
|
||||
|
||||
faceLandmarkerHelperListener?.onResults(
|
||||
ResultBundle(
|
||||
result,
|
||||
inferenceTime, // 主要是检测时间
|
||||
input.height,
|
||||
input.width,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val finishTimeMs = SystemClock.uptimeMillis()
|
||||
val inferenceTime = finishTimeMs - result.timestampMs()
|
||||
Log.i("TimeLatency",
|
||||
"总延时: ${inferenceTime}ms | "
|
||||
)
|
||||
faceLandmarkerHelperListener?.onResults(
|
||||
ResultBundle(
|
||||
result,
|
||||
@@ -341,6 +304,7 @@ class FaceLandmarkerHelper(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
faceLandmarkerHelperListener?.onEmpty()
|
||||
}
|
||||
@@ -375,13 +339,6 @@ class FaceLandmarkerHelper(
|
||||
val inputImageWidth: Int,
|
||||
)
|
||||
|
||||
data class VideoResultBundle(
|
||||
val results: List<FaceLandmarkerResult>,
|
||||
val inferenceTime: Long,
|
||||
val inputImageHeight: Int,
|
||||
val inputImageWidth: Int,
|
||||
)
|
||||
|
||||
interface LandmarkerListener {
|
||||
fun onError(error: String, errorCode: Int = OTHER_ERROR)
|
||||
fun onResults(resultBundle: ResultBundle)
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
@@ -23,6 +24,7 @@ import com.google.androidgamesdk.GameActivity;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.mediapipe.tasks.components.containers.NormalizedLandmark;
|
||||
import com.google.mediapipe.tasks.vision.core.RunningMode;
|
||||
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarker;
|
||||
import com.google.mediapipe.tasks.vision.facelandmarker.FaceLandmarkerResult;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -30,10 +32,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MainActivity extends GameActivity implements FaceLandmarkerHelper.LandmarkerListener, CameraDataFetcher.CameraDataCallback {
|
||||
private ProcessCameraProvider cameraProvider;
|
||||
@@ -58,6 +62,16 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
// if (!AssetsCopyUtil.isExternalStorageAvailable()) {
|
||||
// Log.w(TAG, "External storage not available, skipping assets copy");
|
||||
// return;
|
||||
// }
|
||||
|
||||
//AssetsCopyUtil.copyAssetsToAppFiles(this, "assets");
|
||||
//AssetsCopyUtil.copyAssetsToAppFiles(this, "shaders");
|
||||
|
||||
|
||||
String native_lib_name = getResources().getString(R.string.native_lib_name);
|
||||
try {
|
||||
System.loadLibrary(native_lib_name);
|
||||
@@ -93,16 +107,16 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
});
|
||||
|
||||
// 初始化Runnable
|
||||
cameraDataRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fetchCameraData();
|
||||
handler.postDelayed(this, INTERVAL); // 再次调度
|
||||
}
|
||||
};
|
||||
// cameraDataRunnable = new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// fetchCameraData();
|
||||
// handler.postDelayed(this, INTERVAL); // 再次调度
|
||||
// }
|
||||
// };
|
||||
|
||||
// 开始周期性调用
|
||||
startPeriodicFetching();
|
||||
// startPeriodicFetching();
|
||||
|
||||
|
||||
Log.i("MainActivity", "onCreate: ");
|
||||
@@ -166,13 +180,45 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
|
||||
|
||||
imageAnalyzer.setAnalyzer(backgroundExecutor, new ImageAnalysis.Analyzer() {
|
||||
private long lastCaptureTime = 0;
|
||||
private int frameCount = 0;
|
||||
|
||||
@Override
|
||||
public void analyze(@NonNull ImageProxy image) {
|
||||
frameCount++;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
try {
|
||||
//Log.d("Camera", "Received image: " + image.getWidth() + "x" + image.getHeight());
|
||||
long captureTimeNs = image.getImageInfo().getTimestamp();
|
||||
long analyzeTime = System.currentTimeMillis();
|
||||
|
||||
// 简单直接的计算方法
|
||||
long captureTimeMs = TimeUnit.NANOSECONDS.toMillis(captureTimeNs);
|
||||
long latency = analyzeTime - captureTimeMs;
|
||||
|
||||
// 帧率计算
|
||||
if (lastCaptureTime > 0) {
|
||||
long frameInterval = captureTimeMs - lastCaptureTime;
|
||||
double fps = 1000.0 / frameInterval;
|
||||
|
||||
if (frameCount % 10 == 0) { // 每30帧打印一次
|
||||
Log.i("TimeLatency", String.format(
|
||||
"Frame %d - Latency: %d ms, FPS: %.1f",
|
||||
frameCount, latency, fps
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
lastCaptureTime = captureTimeMs;
|
||||
|
||||
processImageForVulkan(image);
|
||||
if(frameCount %2 == 0)
|
||||
{
|
||||
detectFace(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.close();
|
||||
}
|
||||
} finally {
|
||||
//image.close(); // 确保在这里关闭
|
||||
//Log.d("Camera", "Image closed, ready for next frame");
|
||||
@@ -254,6 +300,41 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
float[] points = new float[480 * 3];
|
||||
|
||||
|
||||
public class Vector {
|
||||
public float x, y, z;
|
||||
|
||||
public Vector(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
public static String toObjString(ArrayList<Vector> vertices, ArrayList<Integer> triangleIndices) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 写入顶点数据 (v x y z)
|
||||
for (Vector v : vertices) {
|
||||
sb.append(String.format("v %.6f %.6f %.6f\n", v.x, v.y, v.z));
|
||||
}
|
||||
|
||||
// 写入面数据 (f i1 i2 i3),OBJ索引从1开始,因此每个索引+1
|
||||
int size = triangleIndices.size();
|
||||
for (int i = 0; i < size; i += 3) {
|
||||
if (i + 2 < size) {
|
||||
int i1 = triangleIndices.get(i) + 1;
|
||||
int i2 = triangleIndices.get(i + 1) + 1;
|
||||
int i3 = triangleIndices.get(i + 2) + 1;
|
||||
sb.append(String.format("f %d %d %d\n", i1, i2, i3));
|
||||
} else {
|
||||
// 如果剩余索引不足3个,说明数据不完整
|
||||
System.err.println("Warning: Incomplete triangle face at the end of index list.");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResults(FaceLandmarkerHelper.@NotNull ResultBundle resultBundle) {
|
||||
FaceLandmarkerResult faceLandmarkerResult = resultBundle.getResult();
|
||||
@@ -268,6 +349,19 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
nativeBuffer.order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
long start_time = System.currentTimeMillis();
|
||||
|
||||
// ArrayList<Vector> vertexs = new ArrayList<>();
|
||||
// ArrayList<Integer> triangle_index = new ArrayList<>();
|
||||
//
|
||||
// for (var conn : FaceLandmarker.FACE_LANDMARKS_TESSELATION) {
|
||||
// triangle_index.add(conn.start());
|
||||
// }
|
||||
|
||||
|
||||
//StringBuilder sb = new StringBuilder();
|
||||
//sb.append("[");
|
||||
|
||||
int index = 0;
|
||||
// Iterate through each detected face
|
||||
for (List<NormalizedLandmark> faceLandmarks : faceLandmarkerResult.faceLandmarks()) {
|
||||
@@ -276,32 +370,47 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
|
||||
int arrayIndex = 0;
|
||||
for (NormalizedLandmark p : faceLandmarks)
|
||||
{
|
||||
//Log.i(TAG, "Index" + index + " x:"+p.x() + " y:"+p.y() + " z:"+p.z());
|
||||
//sb.append("[");
|
||||
points[arrayIndex++] = p.x();
|
||||
//sb.append(""+p.x()+",");
|
||||
points[arrayIndex++] = p.y();
|
||||
//sb.append(""+p.y()+",");
|
||||
points[arrayIndex++] = p.z() * z_rate;
|
||||
//sb.append(""+p.z());
|
||||
//vertexs.add(new Vector(p.x(), p.y(), p.z()));
|
||||
//sb.append("],");
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
//sb.append("]");
|
||||
//String objStr = sb.toString();
|
||||
|
||||
//String objStr = toObjString(vertexs, triangle_index);
|
||||
|
||||
// 转换为FloatBuffer并填充数据
|
||||
FloatBuffer floatBuffer = nativeBuffer.asFloatBuffer();
|
||||
floatBuffer.put(points);
|
||||
floatBuffer.position(0);
|
||||
long cur_time = System.currentTimeMillis();
|
||||
Log.i("TimeLatency","Result Data ProcessTime:" + (cur_time-start_time));
|
||||
passDataToNative(nativeBuffer, index, width, height);
|
||||
Log.i("TimeLatency","passDataToNative ProcessTime:" + (System.currentTimeMillis() - cur_time));
|
||||
}
|
||||
|
||||
public float z_rate = 1.0f;
|
||||
public float z_rate = -1.0f;
|
||||
|
||||
public native void passDataToNative(ByteBuffer buffer, int pointCount, int width, int height);
|
||||
public native void upDateCameraData(float fov, float x, float y, float z, float sx, float sy, float sz, float rotx, float roty, float rotz, float camx, float camy, float camz);
|
||||
public native void upDateCameraData(float fov, float x, float y, float z, float sx, float sy, float sz, float rotx, float roty, float rotz,
|
||||
float camx, float camy, float camz, float dashSize, float gapSize, float dashOffset, float uAASize);
|
||||
|
||||
@Override
|
||||
public void onSuccess(CameraDataFetcher.CameraData data) {
|
||||
z_rate = data.z_rate;
|
||||
upDateCameraData(data.fov, data.x, data.y, data.z, data.sx, data.sy, data.sz,
|
||||
data.rotx, data.roty, data.rotz,
|
||||
data.camx, data.camy, data.camz
|
||||
data.camx, data.camy, data.camz,
|
||||
data.dashSize, data.gapSize, data.dashOffset, data.uAASize
|
||||
);
|
||||
canFetch = true;
|
||||
Log.e(TAG, "GetCameraData " + data.toString());
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 157 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- 底层:CameraX 预览 -->
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/previewView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- 上层:GameActivity 的容器 -->
|
||||
<FrameLayout
|
||||
android:id="@+id/gameActivityContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 30 KiB |