16 Commits
Author SHA1 Message Date
xsl 96c24da077 添加双纹理采样 2025-10-22 18:13:10 +08:00
xsl ee9e70c53a 赴叶跟新的第二版 2025-10-19 22:17:04 +08:00
xsl bfb061d88a 傅叶跟新的第二版 2025-10-19 22:15:07 +08:00
xsl e85f78ece2 发版本更新资源 2025-10-15 22:07:24 +08:00
xsl e6f7d1efb3 完成新模型输入,发延时优化版本。 2025-10-15 22:02:49 +08:00
xsl 4a45c4dd43 更新资源文件, 2025-10-15 17:10:19 +08:00
xsl ca4bb5c34b 添加了顶点匹配脚本生成 2025-10-15 17:09:42 +08:00
xsl 42cd8265f5 1、修改效果切换名称为demo0
2、1秒钟后无检测点就不画线,添加了平台判断
3、添加了背面剔除判断
2025-10-15 17:08:09 +08:00
xsl fd25180989 添加原始的face_picture.obj 2025-10-15 11:01:35 +08:00
xsl b55d4b9600 添加背面剔除模式。 2025-10-15 10:06:28 +08:00
xsl 463b4724c9 修改背景暗,改为非srgb模式。 2025-10-15 09:28:25 +08:00
xsl 5e851505bb 删除video检测的代码,添加延时输出的代码。 2025-10-14 17:17:29 +08:00
xsl 5b9ca62976 加入人脸真实纹理 2025-10-01 19:13:17 +08:00
xsl 346de14e00 加入纹理创建的srgb 2025-10-01 19:11:52 +08:00
xsl 8d1f34604e 真人照片纹理 2025-10-01 19:10:47 +08:00
xsl 3f4dcfa0a7 图片的obj生成 2025-10-01 18:56:26 +08:00
26 changed files with 11619 additions and 232 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

@@ -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,152 +253,57 @@ 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 finishTimeMs = SystemClock.uptimeMillis()
val inferenceTime = finishTimeMs - result.timestampMs()
// 计算各种延时
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
faceLandmarkerHelperListener?.onResults(
ResultBundle(
result,
inferenceTime,
input.height,
input.width
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,
inferenceTime,
input.height,
input.width
)
)
}
}
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;
@@ -36,6 +37,7 @@ 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;
@@ -66,8 +68,8 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
// return;
// }
AssetsCopyUtil.copyAssetsToAppFiles(this, "assets");
AssetsCopyUtil.copyAssetsToAppFiles(this, "shaders");
//AssetsCopyUtil.copyAssetsToAppFiles(this, "assets");
//AssetsCopyUtil.copyAssetsToAppFiles(this, "shaders");
String native_lib_name = getResources().getString(R.string.native_lib_name);
@@ -105,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: ");
@@ -178,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);
detectFace(image);
if(frameCount %2 == 0)
{
detectFace(image);
}
else
{
image.close();
}
} finally {
//image.close(); // 确保在这里关闭
//Log.d("Camera", "Image closed, ready for next frame");
@@ -315,7 +349,7 @@ 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<>();
@@ -325,8 +359,8 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
// }
// StringBuilder sb = new StringBuilder();
// sb.append("[");
//StringBuilder sb = new StringBuilder();
//sb.append("[");
int index = 0;
// Iterate through each detected face
@@ -337,7 +371,6 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
for (NormalizedLandmark p : faceLandmarks)
{
//sb.append("[");
//Log.i(TAG, "Index" + index + " x:"+p.x() + " y:"+p.y() + " z:"+p.z());
points[arrayIndex++] = p.x();
//sb.append(""+p.x()+",");
points[arrayIndex++] = p.y();
@@ -359,7 +392,10 @@ public class MainActivity extends GameActivity implements FaceLandmarkerHelper.L
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;
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

File diff suppressed because it is too large Load Diff
+161
View File
@@ -0,0 +1,161 @@
import numpy as np
def read_obj_vertices(obj_file_path):
"""读取OBJ文件中的顶点数据"""
vertices = []
with open(obj_file_path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('v ') and not line.startswith('vt ') and not line.startswith('vn '):
parts = line.split()
if len(parts) >= 4:
# 提取顶点坐标,转换为浮点数
vertex = [float(parts[1]), float(parts[2]), float(parts[3])]
vertices.append(vertex)
return np.array(vertices)
def find_vertex_mapping(original_vertices, modified_vertices, tolerance=1e-4):
"""
找到修改后顶点在原始顶点中的索引映射
使用严格的数值匹配,匹配失败或重复匹配都会报错
"""
mapping = []
used_indices = set()
for i, mod_vertex in enumerate(modified_vertices):
matches = []
# 查找所有在容差范围内的匹配顶点
for j, orig_vertex in enumerate(original_vertices):
if np.allclose(mod_vertex, orig_vertex, atol=tolerance):
matches.append(j)
# 检查匹配结果
if len(matches) == 0:
# 没有找到匹配
print(f"错误: 无法为修改后顶点 {i} ({mod_vertex}) 找到匹配的原始顶点")
print(f" 最近的原始顶点:")
# 找到最近的几个顶点供参考
distances = []
for j, orig_vertex in enumerate(original_vertices):
dist = np.linalg.norm(mod_vertex - orig_vertex)
distances.append((dist, j, orig_vertex))
distances.sort()
for dist, j, vertex in distances[:3]: # 显示最近的3个
print(f" 索引 {j}: {vertex}, 距离: {dist:.6f}")
raise ValueError(f"顶点 {i} 匹配失败")
elif len(matches) > 1:
# 找到多个匹配
print(f"错误: 为修改后顶点 {i} ({mod_vertex}) 找到多个匹配的原始顶点:")
for match_idx in matches:
print(f" 原始顶点索引 {match_idx}: {original_vertices[match_idx]}")
raise ValueError(f"顶点 {i} 匹配到多个原始顶点")
else:
# 找到一个匹配
match_idx = matches[0]
if match_idx in used_indices:
print(f"错误: 原始顶点 {match_idx} 已经被多个修改后顶点匹配")
print(f" 当前修改后顶点 {i}: {mod_vertex}")
# 找出哪个修改后顶点已经匹配了这个原始顶点
for k, mapped_idx in enumerate(mapping):
if mapped_idx == match_idx:
print(f" 已经被修改后顶点 {k}: {modified_vertices[k]} 匹配")
raise ValueError(f"原始顶点 {match_idx} 被重复匹配")
mapping.append(match_idx)
used_indices.add(match_idx)
return mapping
def validate_mapping_completeness(mapping, original_vertices, modified_vertices):
"""验证映射的完整性"""
print("\n验证映射完整性...")
# 检查是否有未匹配的原始顶点
all_original_indices = set(range(len(original_vertices)))
used_original_indices = set(mapping)
unused_original_indices = all_original_indices - used_original_indices
if unused_original_indices:
print(f"警告: 有 {len(unused_original_indices)} 个原始顶点未被使用:")
for idx in list(unused_original_indices)[:5]: # 只显示前5个
print(f" 原始顶点 {idx}: {original_vertices[idx]}")
if len(unused_original_indices) > 5:
print(f" ... 还有 {len(unused_original_indices) - 5}")
# 检查顶点数量是否一致
if len(modified_vertices) != len(original_vertices):
print(f"警告: 顶点数量不一致 - 原始: {len(original_vertices)}, 修改后: {len(modified_vertices)}")
return len(unused_original_indices) == 0
def generate_header_file(mapping, output_file="map.h"):
"""生成C++头文件"""
with open(output_file, 'w') as f:
f.write("#ifndef VERTEX_MAP_H\n")
f.write("#define VERTEX_MAP_H\n\n")
f.write("// 顶点索引映射表\n")
f.write("// 用于将face_picture_3dmax.obj的顶点索引映射到face_picture.obj的顶点索引\n")
f.write("// 生成说明: modified_vertex_index -> original_vertex_index\n")
f.write("// 注意: 这个映射是严格的一对一匹配,匹配容差为1e-4\n")
f.write(f"const int indexMap[{len(mapping)}] = {{\n")
# 每行输出10个元素
for i in range(0, len(mapping), 10):
line_indices = mapping[i:i+10]
line = " " + ", ".join(f"{idx:3d}" for idx in line_indices)
if i + 10 < len(mapping):
line += ","
f.write(line + "\n")
f.write("};\n\n")
f.write("#endif // VERTEX_MAP_H\n")
def main():
# 文件路径
original_obj = "../assets/face_picture.obj"
modified_obj = "../assets/face_picture_3dmax.obj"
output_header = "map.h"
# 匹配容差 - 根据你的数据精度调整这个值
tolerance = 1e-4
print("正在读取OBJ文件...")
try:
# 读取顶点数据
original_vertices = read_obj_vertices(original_obj)
modified_vertices = read_obj_vertices(modified_obj)
print(f"原始文件顶点数: {len(original_vertices)}")
print(f"修改文件顶点数: {len(modified_vertices)}")
print(f"匹配容差: {tolerance}")
# 找到顶点映射
print("正在严格匹配顶点...")
mapping = find_vertex_mapping(original_vertices, modified_vertices, tolerance)
# 验证完整性
is_complete = validate_mapping_completeness(mapping, original_vertices, modified_vertices)
if is_complete:
print("✓ 所有原始顶点都被正确使用")
else:
print("⚠ 部分原始顶点未被使用")
print(f"✓ 成功匹配所有 {len(mapping)} 个顶点")
# 生成头文件
print(f"生成头文件: {output_header}")
generate_header_file(mapping, output_header)
print("\n完成!映射文件已生成。")
except Exception as e:
print(f"\n❌ 错误: {e}")
print("映射生成失败,请检查数据或调整匹配容差")
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
import json
def create_triangular_obj_from_json(vertices_file, indices_file, output_file):
# 读取顶点数据
with open(vertices_file, 'r') as f:
vertices = json.load(f)
# 读取线索引数据
with open(indices_file, 'r') as f:
edges = json.load(f)
print(f"读取到 {len(vertices)} 个顶点")
print(f"读取到 {len(edges)} 条边")
# 根据边的连接关系自动识别三角形面
triangles = edges_to_triangles(edges)
print(f"识别到 {len(triangles)} 个三角形面")
# 创建OBJ文件
with open(output_file, 'w') as f:
# 写入文件头
f.write("# OBJ文件 - 三角形面网格\n")
f.write(f"# 顶点数: {len(vertices)}, 面数: {len(triangles)}\n\n")
# 写入顶点信息 (v x y z)
for i, vertex in enumerate(vertices):
#f.write(f"# 顶点 {i}\n")
f.write(f"v {vertex[0]:.6f} {vertex[1]:.6f} {vertex[2]:.6f}\n")
f.write("\n")
# 写入面信息 (f v1 v2 v3)
for triangle in triangles:
# OBJ文件索引从1开始,所以需要+1
#f.write(f"# 面 {triangle[0]} {triangle[1]} {triangle[2]}\n")
f.write(f"f {triangle[0]+1} {triangle[1]+1} {triangle[2]+1}\n")
def edges_to_triangles(edges):
"""
将edges数组转换为triangles数组
每三个元素的第一值组成一个三角形
Args:
edges: 从JSON读取的边数组
Returns:
triangles: 三角形数组,每个元素包含三个点的索引
"""
triangles = []
# 确保边的数量是3的倍数(每个三角形由3条边组成)
if len(edges) % 3 != 0:
print(f"警告: 边的数量({len(edges)})不是3的倍数")
# 每3条边组成一个三角形,取每条边的第一个点
for i in range(0, len(edges), 3):
if i + 2 < len(edges): # 确保有足够的边
triangle = [
edges[i][0], # 第一条边的第一个点
edges[i+1][0], # 第二条边的第一个点
edges[i+2][0] # 第三条边的第一个点
]
triangles.append(triangle)
return triangles
# 使用示例
if __name__ == "__main__":
# 输入文件
vertices_file = "face_picture_point.json" # 顶点数据文件
indices_file = "point_connection.json" # 线索引数据文件
# 输出文件
output_file = "face_picture.obj" # 生成的OBJ文件
# 生成OBJ文件
create_triangular_obj_from_json(vertices_file, indices_file, output_file)
print(f"OBJ文件已生成: {output_file}")
+58
View File
@@ -0,0 +1,58 @@
#ifndef VERTEX_MAP_H
#define VERTEX_MAP_H
// 顶点索引映射表
// 用于将face_picture_3dmax.obj的顶点索引映射到face_picture.obj的顶点索引
// 生成说明: modified_vertex_index -> original_vertex_index
// 注意: 这个映射是严格的一对一匹配,匹配容差为1e-4
const int indexMap[468] = {
127, 34, 139, 11, 0, 37, 232, 231, 120, 72,
39, 128, 121, 47, 104, 69, 67, 175, 171, 148,
118, 50, 101, 73, 40, 9, 151, 108, 48, 115,
131, 194, 204, 211, 74, 185, 80, 42, 183, 92,
186, 230, 229, 202, 212, 214, 83, 18, 17, 76,
61, 146, 160, 29, 30, 56, 157, 173, 106, 135,
192, 203, 165, 98, 21, 71, 68, 51, 45, 4,
144, 24, 23, 77, 91, 205, 187, 201, 200, 182,
90, 181, 85, 84, 206, 36, 140, 193, 189, 244,
159, 158, 28, 247, 246, 161, 236, 3, 196, 54,
168, 8, 117, 228, 31, 55, 97, 99, 126, 100,
166, 79, 218, 155, 154, 26, 209, 49, 136, 150,
217, 223, 52, 53, 134, 170, 43, 119, 226, 130,
63, 238, 20, 242, 46, 70, 156, 78, 62, 96,
143, 227, 123, 111, 44, 125, 19, 216, 153, 22,
167, 208, 142, 57, 60, 35, 113, 27, 210, 225,
137, 116, 41, 38, 129, 64, 240, 102, 207, 184,
169, 149, 176, 105, 66, 122, 6, 147, 65, 107,
89, 180, 93, 15, 86, 14, 87, 145, 88, 179,
95, 138, 172, 215, 58, 219, 81, 195, 199, 82,
163, 110, 234, 109, 235, 191, 222, 141, 221, 197,
25, 7, 33, 220, 237, 245, 162, 188, 174, 2,
241, 164, 12, 13, 198, 133, 112, 243, 239, 190,
32, 178, 132, 177, 1, 213, 59, 94, 75, 224,
233, 114, 124, 356, 389, 368, 302, 267, 452, 350,
349, 303, 269, 357, 343, 277, 453, 333, 332, 297,
152, 377, 347, 348, 330, 304, 270, 336, 337, 278,
279, 360, 418, 262, 431, 408, 409, 310, 415, 407,
410, 450, 422, 430, 434, 313, 314, 306, 307, 375,
387, 388, 260, 286, 414, 398, 335, 406, 364, 367,
416, 423, 358, 327, 251, 284, 298, 281, 5, 373,
374, 253, 320, 321, 425, 427, 411, 421, 405, 404,
315, 16, 426, 266, 400, 369, 322, 391, 417, 465,
464, 386, 257, 258, 466, 456, 399, 419, 285, 346,
340, 261, 413, 441, 460, 328, 355, 371, 329, 392,
439, 438, 382, 341, 256, 429, 420, 394, 379, 437,
443, 444, 283, 275, 440, 363, 338, 273, 451, 446,
342, 467, 293, 334, 282, 458, 461, 462, 276, 353,
383, 308, 324, 325, 300, 372, 345, 447, 352, 274,
248, 436, 381, 252, 393, 428, 287, 250, 384, 265,
259, 424, 292, 366, 271, 294, 455, 272, 432, 395,
299, 351, 280, 319, 295, 296, 403, 323, 454, 316,
380, 318, 402, 365, 435, 397, 344, 311, 291, 396,
268, 445, 254, 339, 449, 264, 10, 442, 370, 263,
255, 359, 412, 301, 378, 326, 457, 362, 459, 463,
354, 401, 361, 309, 376, 433, 289, 305, 448, 290,
288, 249, 103, 385, 331, 317, 312, 390
};
#endif // VERTEX_MAP_H
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -49,7 +49,7 @@ public:
uint32_t mip_levels;
};
Texture texture = { 0 };
Texture tex_demo0 = { 0 };
Texture tex_demo1 = { 0 };
Texture tex_demo2 = { 0 };
Texture tex_demo3 = { 0 };
@@ -183,8 +183,8 @@ public:
virtual void view_changed() override;
virtual void on_update_ui_overlay(vkb::Drawer& drawer) override;
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& out_texture);
void createTexture(VkDevice device, VkPhysicalDevice physicalDevice, int width, int height, Texture& texture);
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& out_texture, bool srgb);
void createTexture(VkDevice device, VkPhysicalDevice physicalDevice, int width, int height, Texture& texture, bool srgb);
void updateTexture(VkDevice device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue queue, uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& texture);
uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties);
VkCommandBuffer beginSingleTimeCommands(VkDevice device, VkCommandPool commandPool);
@@ -206,6 +206,7 @@ private:
std::vector<TextureLoadingVertexStructure> obj_vertices;
std::vector<uint32_t> obj_indices;
std::map<int, int> obj_vertices_map;
std::map<int, int> vertices_map_3dmax;
std::thread workerThread;
bool running = false;
+16 -4
View File
@@ -1,6 +1,7 @@
#version 450
layout (binding = 1) uniform sampler2D samplerColor;
layout (binding = 2) uniform sampler2D samplerColor_ex;
layout (location = 0) in vec2 inUV;
layout (location = 1) in vec3 inNormal;
@@ -11,16 +12,27 @@ layout (location = 0) out vec4 outFragColor;
void main()
{
vec4 color = texture(samplerColor, inUV);
vec4 color_ex = texture(samplerColor_ex, inUV);
vec3 N = normalize(inNormal);
vec4 textureColor = color;
if (textureColor.a < 0.1) {
//if (textureColor.a < 0.1)
//{
//textureColor = vec4(0.2, 0.2, 0.2, 0.5);
discard;
}
// discard;
//}
// ¼ÆËãÓë (0,0,1) µÄµã³Ë
float ndot = dot(N, vec3(0.0, 0.0, -1.0));
// ±£Ö¤½á¹û >= 0
ndot = max(ndot, 0.0);
outFragColor = textureColor;
vec4 blendedColor = mix(color_ex, color, ndot);
outFragColor = blendedColor;
// outFragColor = vec4(1, 0, 0, 1);
}
Binary file not shown.