viewport 视口变换

This commit is contained in:
SepComet 2026-03-19 14:16:29 +08:00
parent 73c1d3a21d
commit 858c0b4d59
9 changed files with 402 additions and 355 deletions

1
.gitignore vendored
View File

@ -44,3 +44,4 @@ compile_commands.json
Thumbs.db Thumbs.db
SDL2-2.32.10-win32-x64.zip SDL2-2.32.10-win32-x64.zip
SDL2-devel-2.32.10-VC.zip SDL2-devel-2.32.10-VC.zip
/.dotnet-home

30
AGENTS.md Normal file
View File

@ -0,0 +1,30 @@
# Repository Guidelines
## Project Structure & Module Organization
The repository is a single Visual Studio C++ project under `CPU-Software-Renderer/`. `main.cpp` is the current executable entry point and demo loop. Core renderer pieces live in `Core/` (`FrameBuffer`, `DepthBuffer`, `Renderer`), math types in `Math/`, rasterization logic in `Rasterizer/`, scene data in `Scene/`, asset loading in `Asset/`, and shader code in `Shading/`. SDL2 is vendored in `CPU-Software-Renderer/libs/SDL2/`. Planning notes live in `CPU-Software-Renderer/TODO.md`. Build outputs appear in `Debug/`, `Release/`, and `x64/`; do not commit those artifacts.
## Build, Test, and Development Commands
Use Visual Studio 2022 or MSBuild, not `dotnet build`, because the project depends on Visual C++ targets.
```powershell
MSBuild.exe CPU-Software-Renderer\CPU-Software-Renderer.vcxproj /p:Configuration=Debug /p:Platform=x64
```
Builds the debug x64 executable at `CPU-Software-Renderer/x64/Debug/CPU-Software-Renderer.exe`.
```powershell
MSBuild.exe CPU-Software-Renderer\CPU-Software-Renderer.vcxproj /p:Configuration=Release /p:Platform=x64
```
Builds the optimized release binary. In Visual Studio, open `CPU-Software-Renderer.slnx` or the `.vcxproj`, then run the selected `Debug|x64` or `Release|x64` target.
## Coding Style & Naming Conventions
Follow the existing C++20/MSVC style: tabs for indentation, braces on the next line for functions and control blocks, and include headers with quoted local paths such as `"FrameBuffer.h"`. Types and namespaces use `PascalCase` (`FrameBuffer`, `Math::Vector3`), methods use `snake_case` (`set_pixel`, `set_position`), and local variables use descriptive lowercase names. Keep modules narrow: math stays in `Math/`, render-domain structs in `RenderData/`, and behavior in `Core/` or `Rasterizer/`.
## Testing Guidelines
There is no automated test project yet. Validate changes by building `Debug|x64`, running the executable, and checking the rendered output or interaction path you changed. When adding nontrivial math or rasterization behavior, prefer small, isolated helper functions so a future test target can cover them easily.
## Commit & Pull Request Guidelines
Recent history uses short, imperative commit subjects, sometimes in English and sometimes in Chinese, for example `add TODO.md` and `完善 SDL 主循环并收紧三角形光栅化边界处理`. Keep the subject concise and focused on one change. Pull requests should describe the rendering behavior changed, list the build configuration used for verification, and include a screenshot or short note when the visual output changes.
Use UTF-8 encoding when reading and writing.

View File

@ -58,24 +58,16 @@
<ImportGroup Label="Shared"> <ImportGroup Label="Shared">
</ImportGroup> </ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
@ -123,6 +115,7 @@
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories> <AdditionalIncludeDirectories>
$(ProjectDir)Asset;$(ProjectDir)Core;$(ProjectDir)Math;$(ProjectDir)Rasterizer;$(ProjectDir)RenderData;$(ProjectDir)Scene;$(ProjectDir)Shading;$(ProjectDir)libs\SDL2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> $(ProjectDir)Asset;$(ProjectDir)Core;$(ProjectDir)Math;$(ProjectDir)Rasterizer;$(ProjectDir)RenderData;$(ProjectDir)Scene;$(ProjectDir)Shading;$(ProjectDir)libs\SDL2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>

View File

@ -72,11 +72,15 @@
<ClCompile Include="Rasterizer\TriangleRasterizer.cpp"> <ClCompile Include="Rasterizer\TriangleRasterizer.cpp">
<Filter>源文件\Rasterizer</Filter> <Filter>源文件\Rasterizer</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Scene\Camera.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="TODO.md"> <None Include="TODO.md">
<Filter>资源文件</Filter> <Filter>资源文件</Filter>
</None> </None>
<None Include="..\README.md" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Core\DepthBuffer.h"> <ClInclude Include="Core\DepthBuffer.h">

View File

@ -18,6 +18,7 @@ namespace Core
{ {
return; return;
} }
// Row-major layout with y = 0 on the first row, matching a top-left screen origin.
size_t index = static_cast<size_t>(y) * width + x; size_t index = static_cast<size_t>(y) * width + x;
buffer.at(index) = color; buffer.at(index) = color;
} }

View File

@ -5,7 +5,7 @@
namespace Math namespace Math
{ {
static class MathUtil class MathUtil
{ {
public: public:
static Matrix4x4 get_translate_matrix(const float x, const float y, const float z) { return get_translate_matrix(Vector3(x, y, z)); } static Matrix4x4 get_translate_matrix(const float x, const float y, const float z) { return get_translate_matrix(Vector3(x, y, z)); }

View File

@ -3,6 +3,7 @@
#include "Camera.h" #include "Camera.h"
#include <Vector3.h> #include <Vector3.h>
#include "MathUtil.h" #include "MathUtil.h"
#include <cmath>
namespace Scene namespace Scene
{ {
@ -56,4 +57,16 @@ namespace Scene
0, 0, -1, 0 0, 0, -1, 0
); );
} }
Math::Matrix4x4 Camera::get_viewport_matrix(float width, float height) const
{
using namespace Math;
return Matrix4x4(
(width - 1) / 2, 0, 0, (width - 1) / 2,
0, -(height - 1) / 2, 0, (height - 1) / 2,
0, 0, 1, 0,
0, 0, 0, 1
);
}
} }

View File

@ -41,5 +41,7 @@ namespace Scene
Math::Matrix4x4 get_orthographic_projection_matrix(float width, float height) const; Math::Matrix4x4 get_orthographic_projection_matrix(float width, float height) const;
Math::Matrix4x4 get_perspective_projection_matrix(float aspectRatio) const; Math::Matrix4x4 get_perspective_projection_matrix(float aspectRatio) const;
Math::Matrix4x4 get_viewport_matrix(float width, float height) const;
}; };
} }

View File

@ -17,6 +17,8 @@
#include "Camera.h" #include "Camera.h"
#include "SDL_events.h" #include "SDL_events.h"
#include "SDL_keycode.h" #include "SDL_keycode.h"
#include "SDL_timer.h"
#include <cstdlib>
const uint32_t SDL_INIT_FLAGS = SDL_INIT_VIDEO; const uint32_t SDL_INIT_FLAGS = SDL_INIT_VIDEO;
const int32_t width = 800; const int32_t width = 800;
@ -128,8 +130,7 @@ struct CubeFace
static ProjectedVertex ProjectToScreen( static ProjectedVertex ProjectToScreen(
const Math::Vector3& vertex, const Math::Vector3& vertex,
const Math::Matrix4x4& mvp, const Math::Matrix4x4& mvp,
const int32_t screenWidth, const Math::Matrix4x4& viewport)
const int32_t screenHeight)
{ {
using namespace Math; using namespace Math;
@ -149,8 +150,9 @@ static ProjectedVertex ProjectToScreen(
return {}; return {};
} }
const float screenX = (ndcX * 0.5f + 0.5f) * static_cast<float>(screenWidth - 1); const Vector4 screen = viewport * Vector4(ndcX, ndcY, ndcZ, 1.0f);
const float screenY = (1.0f - (ndcY * 0.5f + 0.5f)) * static_cast<float>(screenHeight - 1); const float screenX = screen.x;
const float screenY = screen.y;
return { Vector2(screenX, screenY).to_vector2Int(), true }; return { Vector2(screenX, screenY).to_vector2Int(), true };
} }
@ -247,6 +249,7 @@ int main(int argc, char* argv[])
const Math::Matrix4x4 view = camera.get_view_matrix(); const Math::Matrix4x4 view = camera.get_view_matrix();
const Math::Matrix4x4 modelView = view * model; const Math::Matrix4x4 modelView = view * model;
const Math::Matrix4x4 projection = camera.get_perspective_projection_matrix(aspectRatio); const Math::Matrix4x4 projection = camera.get_perspective_projection_matrix(aspectRatio);
const Math::Matrix4x4 viewport = camera.get_viewport_matrix(static_cast<float>(width), static_cast<float>(height));
const Math::Matrix4x4 mvp = projection * modelView; const Math::Matrix4x4 mvp = projection * modelView;
std::array<Math::Vector3, 8> viewSpaceVertices; std::array<Math::Vector3, 8> viewSpaceVertices;
@ -254,7 +257,7 @@ int main(int argc, char* argv[])
for (size_t i = 0; i < cubeVertices.size(); ++i) for (size_t i = 0; i < cubeVertices.size(); ++i)
{ {
viewSpaceVertices[i] = (modelView * Math::Vector4::Point(cubeVertices[i])).to_vector3(); viewSpaceVertices[i] = (modelView * Math::Vector4::Point(cubeVertices[i])).to_vector3();
projectedVertices[i] = ProjectToScreen(cubeVertices[i], mvp, width, height); projectedVertices[i] = ProjectToScreen(cubeVertices[i], mvp, viewport);
} }
std::array<bool, 12> visibleEdges = {}; std::array<bool, 12> visibleEdges = {};