当前位置: 首页 > news >正文

定义一个3D cube,并计算cube每个顶点的像素坐标

定义一个3D cube,并计算cube每个顶点的像素坐标

scratch a pixel课程:Your Starting Point!


3D场景中物体所有点与坐标系原点的连线,该连线与像素平面canvas的交点就是场景中3D点其投影点的位置

3D场景中的点均由这个坐标系描述

相似三角形ABC和A’B’C’,由相似性得到:

像素平面为z=-1处的平面

3D顶点在像素平面上的y’坐标=3D点的y坐标/该点深度z(也就是透视除法perspective divide)

同理,3D顶点在像素平面上的x’坐标=3D点的x坐标/该点深度z

3D顶点P的z值为负值,若P的x值为正,则投影后变为了负值,因为除以了z值(负数),若P的x值为负,则投影后变为了正值

这种投影是正交投影,也就是没有近大远小的效果

#include<iostream>
typedef float Point[3]; //定义了一个类型别名point,它表示一个包含3个float类型元素的数组,一个点三个float
int main() {//represent the box,每个元素为Point类型Point corners[8] = {{ 1, -1, -5},{ 1, -1, -3},{ 1,  1, -5},{ 1,  1, -3},{-1, -1, -5},{-1, -1, -3},{-1,  1, -5},{-1,  1, -3}};//projectionfor (int i=0; i < 8; i++) {float x_proj = corners[i][0] / -corners[i][2];float y_proj = corners[i][1] / -corners[i][2];printf("Projected corner %d: x:%f, y:%f\n", i, x_proj, y_proj);}return 0;
}


我们发现投影点坐标有正有负,因为像素坐标都是正的,所以我们需要对以上坐标进行重新映射

#include<iostream>
typedef float Point[3]; //定义了一个类型别名point,它表示一个包含3个float类型元素的数组,一个点三个float
int main() {//represent the box,每个元素为Point类型Point corners[8] = {{ 1, -1, -5},{ 1, -1, -3},{ 1,  1, -5},{ 1,  1, -3},{-1, -1, -5},{-1, -1, -3},{-1,  1, -5},{-1,  1, -3}};//projectionfor (int i=0; i < 8; i++) {//坐标范围[-1,1]float x_proj = corners[i][0] / -corners[i][2];float y_proj = corners[i][1] / -corners[i][2];//printf("Projected corner %d: x:%f, y:%f\n", i, x_proj, y_proj);//坐标范围重映射[-1,1] -> + 1 -> [0,2] -> /2 -> [0,1]float x_proj_remap = (1 + x_proj)/2;float y_proj_remap = (1 + y_proj)/2;printf("After remapping,Projected corner %d: x:%f, y:%f\n", i, x_proj_remap, y_proj_remap);}return 0;
}

坐标范围从[-1,1](屏幕空间,screen space)重映射到了[0,1](NDC空间,Normalized Device Coordinates)

重映射到NDC空间有什么好处?
为了后续可以适应不同大小的图片,我们首先将投影点映射到一个标准的空间,随后根据不同的图片大小进行再次适应性的变换。个人理解:想要呈现的图片大小可能是不一样的,我写一个渲染程序要同时适应这些不同大小的图片,就需要首先将它们统一到一个标准的空间,随后根据不同的图片大小来适应性的做出改变。


假设图片大小为512*512,我们对NDC空间中的点坐标进行变换

#include<iostream>
typedef float Point[3]; //定义了一个类型别名point,它表示一个包含3个float类型元素的数组,一个点三个float
int main() {//represent the box,每个元素为Point类型Point corners[8] = {{ 1, -1, -5},{ 1, -1, -3},{ 1,  1, -5},{ 1,  1, -3},{-1, -1, -5},{-1, -1, -3},{-1,  1, -5},{-1,  1, -3}};//define image width and heightconst unsigned int image_width = 512, image_height = 512;//projectionfor (int i=0; i < 8; i++) {//坐标范围[-1,1]float x_proj = corners[i][0] / -corners[i][2];float y_proj = corners[i][1] / -corners[i][2];//printf("Projected corner %d: x:%f, y:%f\n", i, x_proj, y_proj);//坐标范围重映射[-1,1] -> + 1 -> [0,2] -> /2 -> [0,1]float x_proj_remap = (1 + x_proj)/2;float y_proj_remap = (1 + y_proj)/2;//printf("After remapping,Projected corner %d: x:%f, y:%f\n", i, x_proj_remap, y_proj_remap);float x_proj_pix = x_proj_remap * image_width;float y_proj_pix = y_proj_remap * image_height;printf("After transforming to image size,Projected corner %d: x:%f, y:%f\n", i, x_proj_pix, y_proj_pix);}return 0;
}

上述计算结果还有小数点,而真实的像素坐标是整数,所以需要对结果进行round off

#include<cmath>
float x_proj_pix = round(x_proj_remap * image_width);
float y_proj_pix = round(y_proj_remap * image_height);


如果图片大小不是1:1,而是16:9,那么坐标应该如何变换?
一般来说,我们会用到投影矩阵(projection matrix)进行投影,投影矩阵会考虑图像的长宽比(aspect),也就是图像宽度和高度之间的比例。如果这个比率不是1(图像不是正方形的),那么投影矩阵就会正确地考虑到这图片不是方形的,也就是说投影矩阵已经考虑了图片的长宽比
aspect ratio = width height \text{aspect ratio}=\frac{\text{width}}{\text{height}} aspect ratio=heightwidth

可以通过两种方式实现图片为非正方形时的坐标变换
1.您可以计算图像的宽高比,如前所述:宽度除以高度(假设这里的宽度大于高度),然后将投影点的x坐标除以该宽高比(x/aspect),或者将y投影坐标乘以宽高比(y*aspect),这两种操作会给你不同的图像。
x / a s p e c t (可以用这种)  y ∗ a s p e c t (或者用这种) x/aspect(可以用这种) ~\\ y*aspect(或者用这种) x/aspect(可以用这种) yaspect(或者用这种)
在这两种操作方式下,立方体都会正确地投影到图像表面上,而在第二种情况下,立方体会显得更大,如下图所示。

在该图像中,白色区域代表原始的正方形图像,灰色区域代表非正方形图像。正如您在左边的第一张图像中看到的,正方形图像的顶部和底部边缘与非正方形图像的顶部和底部边缘相匹配。在第二种情况下,在右边,正方形图像的左右边缘分别与非正方形图像的左右边缘相匹配,导致里面的立方体看起来更大。

#include<iostream>
#include<cmath>
typedef float Point[3]; //定义了一个类型别名point,它表示一个包含3个float类型元素的数组,一个点三个float
int main() {//represent the box,每个元素为Point类型Point corners[8] = {{ 1, -1, -5},{ 1, -1, -3},{ 1,  1, -5},{ 1,  1, -3},{-1, -1, -5},{-1, -1, -3},{-1,  1, -5},{-1,  1, -3}};//define image width and heightconst unsigned int image_width = 640, image_height = 480;float aspect_ratio = image_width / image_height;//projectionfor (int i=0; i < 8; i++) {//坐标范围[-1,1]float x_proj = corners[i][0] / -corners[i][2] / aspect_ratio; //option1:divide by aspectfloat y_proj = corners[i][1] / -corners[i][2];//(或者这里*aspect)//printf("Projected corner %d: x:%f, y:%f\n", i, x_proj, y_proj);//坐标范围重映射[-1,1] -> + 1 -> [0,2] -> /2 -> [0,1]float x_proj_remap = (1 + x_proj)/2;float y_proj_remap = (1 + y_proj)/2;//printf("After remapping,Projected corner %d: x:%f, y:%f\n", i, x_proj_remap, y_proj_remap);float x_proj_pix = round(x_proj_remap * image_width);float y_proj_pix = round(y_proj_remap * image_height);printf("After transforming to image size,Projected corner %d: x:%f, y:%f\n", i, x_proj_pix, y_proj_pix);}return 0;
}

使用 x/aspect 的结果

使用 y*aspect 的结果

相关文章:

  • 怎么查看数据库容量
  • PDF本地化开源项目推荐
  • Android面试总结之GC算法篇
  • 使用scipy求解优化问题
  • 【经管数据】A股上市公司资产定价效率数据(2000-2023年)
  • 数据结构学习之顺序表
  • 【Quest开发】极简版!透视环境下抠出身体并能遮挡身体上的服装
  • Python实例题:Python实现Python解释器
  • Three.js + React 实战系列-3D 个人主页:构建 About 组件 (响应式 + 互动 + 动效)✨
  • 华为云服务器VoceChat在线聊天室部署
  • 注册登录页面项目
  • Ocelot\Consul\.NetCore的微服务应用案例
  • 构建现代分布式云架构的三大支柱:服务化、Service Mesh 与 Serverless
  • 微软推出数款Phi 4“开放式”人工智能模型
  • 系统架构设计师:设计模式——创建型设计模式
  • 微软与Meta大幅增加人工智能基础设施投入
  • 普通 html 项目也可以支持 scss_sass
  • Anaconda中配置Pyspark的Spark开发环境
  • 【中间件】bthread_数据结构_学习笔记
  • terraform 删除资源前先校验资源是否存在关联资源
  • 山东省委组织部办公室主任吴宪利已任德州市委常委、组织部部长
  • 空调+零食助顶级赛马备战,上海环球马术冠军赛将焕新登场
  • 迪卡侬回应出售中国业务30%股份传闻:始终扎根中国长期发展
  • 魔都眼|静安光影派对五一启幕:苏河湾看徐悲鸿艺术画作
  • 游客曝九寨沟打网约车被出租车围堵,景区回应:当地无合规网约车
  • 北京动物园:大熊猫“萌兰”没有参加日本大阪世博会的计划