992 lines
74 KiB
HTML
992 lines
74 KiB
HTML
<!DOCTYPE html><html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。"><link rel="icon" href="/images/Bi.ico"><script defer src="https://umami.biss.click/script.js" data-website-id="3d97b310-b241-4fc6-bc9c-68e317fc7d42"></script><script>window.SITE_CONFIG = {"site":{"url":"https://blog.biss.click","name":"Bi's Blog","description":"Personal blog by biss.","language":"zh-CN","domain":"blog.biss.click","slogan":"好奇心转化为代码的空间。","startTime":"2025-01-01T00:00:00+08:00","copyrightStartYear":2025,"license":"内容采用 CC BY-NC-SA 4.0 协议"},"author":{"name":"biss","email":"bishsh2006@gmail.com","github":"https://github.com/bishshi","avatar":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"comments":{"envId":"https://comment.biss.click","region":"","script":"https://cdn.jsdelivr.net/npm/twikoo@1.7.7/dist/twikoo.min.js"},"analytics":{"umami":{"script":"https://umami.biss.click/script.js","websiteId":"3d97b310-b241-4fc6-bc9c-68e317fc7d42","statsEndpoint":"https://blogumami.biss.click"}},"talks":{"apiUrl":"https://mm.biss.click/api/echo/page","pageSize":30,"homeLimit":6,"cacheKey":"talksCache","cacheTimeKey":"talksCacheTime","cacheDurationMs":1800000,"fallbackAvatar":"https://free.picui.cn/free/2025/08/10/689845496a283.png"},"search":{"apiKey":"2uxQzk7eD2GBCljdSmIiuU3ause7UK9n","server":{"host":"typesense.biss.click","port":"443","protocol":"https"},"indexName":"blogs","searchParams":{"query_by":"title,content","highlight_full_fields":"title,content","per_page":8,"num_typos":1,"typo_tokens_threshold":1,"prefix":true},"ui":{"maxRetries":10,"retryDelay":100,"animationDuration":300,"maxInitRetries":30,"placeholder":"输入关键词寻找故事..."}},"externalLinks":{"waitSeconds":10,"whitelist":["blog.biss.click","biss.click","github.com","localhost","127.0.0.1"]}};</script><script>
|
|
(() => {
|
|
const storageKey = 'site-theme';
|
|
const savedTheme = localStorage.getItem(storageKey);
|
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
document.documentElement.dataset.theme = savedTheme || systemTheme;
|
|
})();
|
|
</script><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/instantsearch.css/themes/reset-min.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fortawesome/fontawesome-free@6.7.2/css/all.min.css"><title>OpenGL-基础程序 | Bi's Blog</title><link rel="stylesheet" href="/_astro/BaseLayout.D7kFxpxP.css"></head> <body> <header class="site-header"> <nav class="nav"> <a class="brand" href="/">Bi's Blog</a> <button class="nav-menu-toggle" type="button" aria-label="打开菜单" aria-expanded="false" data-nav-toggle> <i class="fa-solid fa-bars nav-menu-open" aria-hidden="true"></i> <i class="fa-solid fa-xmark nav-menu-close" aria-hidden="true"></i> </button> <div class="nav-menu" data-nav-menu> <div class="nav-links nav-center"> <a href="/archives/"><i class="fa-solid fa-box-archive nav-icon" aria-hidden="true"></i><span>归档</span></a><a href="/categories/"><i class="fa-solid fa-folder-open nav-icon" aria-hidden="true"></i><span>分类</span></a><a href="/tags/"><i class="fa-solid fa-tags nav-icon" aria-hidden="true"></i><span>标签</span></a><a href="/shuoshuo/"><i class="fa-regular fa-comments nav-icon" aria-hidden="true"></i><span>说说</span></a><a href="/about/"><i class="fa-solid fa-heart nav-icon" aria-hidden="true"></i><span>关于</span></a><a href="/link/"><i class="fa-solid fa-link nav-icon" aria-hidden="true"></i><span>友链</span></a> </div> <div class="nav-links nav-actions"> <button class="nav-random" type="button" onclick="randomPost()" aria-label="前往随机文章" title="前往随机文章"> <i class="fa-solid fa-shuffle nav-icon" aria-hidden="true"></i> <span>随机文章</span> </button> <button class="nav-search search-typesense-trigger" type="button" aria-label="搜索" title="搜索"> <i class="fa-solid fa-magnifying-glass nav-icon" aria-hidden="true"></i> <span>搜索</span> </button> <button class="theme-toggle" type="button" aria-label="切换夜间模式" title="切换夜间模式" data-theme-toggle> <i class="fa-solid fa-sun theme-toggle-sun nav-icon" aria-hidden="true"></i> <i class="fa-solid fa-moon theme-toggle-moon nav-icon" aria-hidden="true"></i> </button> </div> </div> </nav> </header> <main> <section class="post-layout"> <article class="article post-article"> <header class="article-header article-header-cover"> <img class="article-header-cover-img" src="https://pic.biss.click/image/2fcb9566-f7f6-4132-81cb-4cd646967519.webp" alt="" loading="eager" decoding="async"> <div class="article-header-content"> <time datetime="2026-04-11T18:20:34.000Z">2026/04/12</time> <h1>OpenGL-基础程序</h1> <div class="article-hero-meta" aria-label="文章信息"> <span>发布于 2026/04/12</span> <a href="/categories/tech/">技术</a> <span>总字数:1.3k</span> <span>阅读时长:4 分钟</span> </div> </div> </header> <section class="article-summary-card" aria-label="AI 文章摘要"> <div class="article-summary-dots" aria-hidden="true"> <span></span> <span></span> <span></span> </div> <p class="article-summary-content"> <span class="article-summary-text" data-summary-text="这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。">这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。</span> <span class="article-summary-caret" aria-hidden="true"></span> </p> <div class="article-summary-footer"> <span class="article-summary-brand"> <span aria-hidden="true">✣</span>
|
|
AI摘要
|
|
</span> <span class="article-summary-model">QWEN</span> </div> </section> <div class="prose" data-toc="[{"depth":1,"text":"代码展示","id":"代码展示"},{"depth":1,"text":"代码解释","id":"代码解释"},{"depth":2,"text":"1. 窗口管理与初始化 (GLUT 库)","id":"1-窗口管理与初始化-glut-库"},{"depth":2,"text":"2. 状态设置与缓冲区操作","id":"2-状态设置与缓冲区操作"},{"depth":2,"text":"3. 矩阵与坐标变换","id":"3-矩阵与坐标变换"},{"depth":2,"text":"4. 几何图形绘制","id":"4-几何图形绘制"}]"> <h1 id="代码展示">代码展示</h1>
|
|
<p>我们先从基本的OpenGL程序开始吧,这是一个简单的OpenGL程序:</p>
|
|
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;" tabindex="0" data-language="cpp"><code><span class="line"><span style="color:#F97583">#include</span><span style="color:#9ECBFF"><GL/glut.h></span></span>
|
|
<span class="line"><span style="color:#F97583">using</span><span style="color:#F97583"> namespace</span><span style="color:#B392F0"> std</span><span style="color:#E1E4E8">;</span></span>
|
|
<span class="line"></span>
|
|
<span class="line"><span style="color:#6A737D">// 回调函数</span></span>
|
|
<span class="line"><span style="color:#F97583">void</span><span style="color:#B392F0"> myDisplay</span><span style="color:#E1E4E8">()</span></span>
|
|
<span class="line"><span style="color:#E1E4E8">{</span></span>
|
|
<span class="line"><span style="color:#6A737D"> // 清除缓冲区</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glClearColor</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glClear</span><span style="color:#E1E4E8">(GL_COLOR_BUFFER_BIT);</span></span>
|
|
<span class="line"><span style="color:#6A737D"> // 正交模式</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glMatrixMode</span><span style="color:#E1E4E8">(GL_PROJECTION);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> gluOrtho2D</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">500.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">500.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glColor4f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glRectf</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">50.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">50.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">400.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">400.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"></span>
|
|
<span class="line"><span style="color:#6A737D"> // 划线</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glColor3f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">1.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glBegin</span><span style="color:#E1E4E8">(GL_LINES);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">50.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">50.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">400.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">400.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">400.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">50.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">50.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">400.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glEnd</span><span style="color:#E1E4E8">();</span></span>
|
|
<span class="line"></span>
|
|
<span class="line"><span style="color:#6A737D"> // 画点</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glColor3f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">1.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glPointSize</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">20.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glBegin</span><span style="color:#E1E4E8">(GL_POINTS);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">15.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">15.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glEnd</span><span style="color:#E1E4E8">();</span></span>
|
|
<span class="line"></span>
|
|
<span class="line"><span style="color:#6A737D"> // 画三角形</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glBegin</span><span style="color:#E1E4E8">(GL_TRIANGLES);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glColor3f</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">0.0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1.0</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2i</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">200</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">300</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2i</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glVertex2i</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">300</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glEnd</span><span style="color:#E1E4E8">();</span></span>
|
|
<span class="line"></span>
|
|
<span class="line"></span>
|
|
<span class="line"><span style="color:#B392F0"> glFlush</span><span style="color:#E1E4E8">();</span></span>
|
|
<span class="line"></span>
|
|
<span class="line"><span style="color:#E1E4E8">}</span></span>
|
|
<span class="line"><span style="color:#F97583">int</span><span style="color:#B392F0"> main</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">int</span><span style="color:#FFAB70"> argc</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">char*</span><span style="color:#FFAB70"> argv</span><span style="color:#E1E4E8">[])</span></span>
|
|
<span class="line"><span style="color:#E1E4E8">{</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutInit</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">argc, argv);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutInitDisplayMode</span><span style="color:#E1E4E8">(GLUT_RGB </span><span style="color:#F97583">|</span><span style="color:#E1E4E8"> GLUT_SINGLE);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutInitWindowPosition</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">100</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutInitWindowSize</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">500</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">500</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutCreateWindow</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">""</span><span style="color:#E1E4E8">);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutDisplayFunc</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">&</span><span style="color:#E1E4E8">myDisplay);</span></span>
|
|
<span class="line"><span style="color:#B392F0"> glutMainLoop</span><span style="color:#E1E4E8">();</span></span>
|
|
<span class="line"><span style="color:#F97583"> return</span><span style="color:#79B8FF"> 0</span><span style="color:#E1E4E8">;</span></span>
|
|
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
|
|
<p>这是运行结果图:
|
|
<img src="https://pic.biss.click/image/0ef5fbe7-0338-4cd0-94c7-1874273461f7.png" alt="第一个OpenGL程序"></p>
|
|
<h1 id="代码解释">代码解释</h1>
|
|
<p>这段代码展示了经典的 OpenGL (GLUT) 固定渲染管线基本操作。为了方便理解,我将这些函数按照 <strong>初始化与窗口管理</strong>、<strong>状态设置</strong>、<strong>坐标变换</strong> 以及 <strong>图形绘制</strong> 四个类别进行了整理。</p>
|
|
<h2 id="1-窗口管理与初始化-glut-库">1. 窗口管理与初始化 (GLUT 库)</h2>
|
|
<p>这类函数主要用于配置窗口系统和处理程序的运行流程。</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<table><thead><tr><th align="left">函数名称</th><th align="left">功能描述</th><th align="left">核心参数说明</th></tr></thead><tbody><tr><td align="left"><code>glutInit</code></td><td align="left">初始化 GLUT 库</td><td align="left">接收 <code>main</code> 函数的命令行参数</td></tr><tr><td align="left"><code>glutInitDisplayMode</code></td><td align="left">设置显示模式</td><td align="left"><code>GLUT_RGB</code> (使用颜色模式), <code>GLUT_SINGLE</code> (单缓冲区)</td></tr><tr><td align="left"><code>glutInitWindowPosition</code></td><td align="left">设置窗口在屏幕上的初始位置</td><td align="left">窗口左上角坐标 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="5.169ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 2284.7 1000" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="MJX-1-TEX-N-28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path><path id="MJX-1-TEX-I-1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path><path id="MJX-1-TEX-N-2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path><path id="MJX-1-TEX-I-1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path><path id="MJX-1-TEX-N-29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><use data-c="28" xlink:href="#MJX-1-TEX-N-28"></use></g><g data-mml-node="mi" transform="translate(389,0)"><use data-c="1D465" xlink:href="#MJX-1-TEX-I-1D465"></use></g><g data-mml-node="mo" transform="translate(961,0)"><use data-c="2C" xlink:href="#MJX-1-TEX-N-2C"></use></g><g data-mml-node="mi" transform="translate(1405.7,0)"><use data-c="1D466" xlink:href="#MJX-1-TEX-I-1D466"></use></g><g data-mml-node="mo" transform="translate(1895.7,0)"><use data-c="29" xlink:href="#MJX-1-TEX-N-29"></use></g></g></g></svg></mjx-container></td></tr><tr><td align="left"><code>glutInitWindowSize</code></td><td align="left">设置窗口的宽度和高度</td><td align="left">像素值 (Width, Height)</td></tr><tr><td align="left"><code>glutCreateWindow</code></td><td align="left">创建窗口</td><td align="left">字符串参数作为窗口标题</td></tr><tr><td align="left"><code>glutDisplayFunc</code></td><td align="left">注册显示回调函数</td><td align="left">传入负责绘图的函数指针</td></tr><tr><td align="left"><code>glutMainLoop</code></td><td align="left">进入 GLUT 事件处理循环</td><td align="left">让程序持续运行,等待重绘或交互</td></tr></tbody></table>
|
|
<hr>
|
|
<h2 id="2-状态设置与缓冲区操作">2. 状态设置与缓冲区操作</h2>
|
|
<p>这些函数用于配置 OpenGL 的全局状态(如颜色、点大小)或清理画布。</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<table><thead><tr><th align="left">函数名称</th><th align="left">功能描述</th><th align="left">核心参数说明</th></tr></thead><tbody><tr><td align="left"><code>glClearColor</code></td><td align="left">设置清除颜色(背景色)</td><td align="left">RGBA 值 (0.0~1.0),此处设为黑色</td></tr><tr><td align="left"><code>glClear</code></td><td align="left">清除指定的缓冲区</td><td align="left"><code>GL_COLOR_BUFFER_BIT</code> 表示清除颜色缓存</td></tr><tr><td align="left"><code>glColor4f</code></td><td align="left">设置当前的绘制颜色 (带透明度)</td><td align="left">RGBA 分量</td></tr><tr><td align="left"><code>glColor3f</code></td><td align="left">设置当前的绘制颜色 (不带透明度)</td><td align="left">RGB 分量</td></tr><tr><td align="left"><code>glPointSize</code></td><td align="left">设置点的像素大小</td><td align="left">浮点数,数值越大点越粗</td></tr><tr><td align="left"><code>glFlush</code></td><td align="left">强制刷新缓冲区</td><td align="left">确保绘图命令立即执行并输出到显示设备</td></tr></tbody></table>
|
|
<hr>
|
|
<h2 id="3-矩阵与坐标变换">3. 矩阵与坐标变换</h2>
|
|
<p>用于定义物体是如何投影到屏幕上的。</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<table><thead><tr><th align="left">函数名称</th><th align="left">功能描述</th><th align="left">核心参数说明</th></tr></thead><tbody><tr><td align="left"><code>glMatrixMode</code></td><td align="left">设置当前矩阵模式</td><td align="left"><code>GL_PROJECTION</code> 切换到投影矩阵堆栈</td></tr><tr><td align="left"><code>gluOrtho2D</code></td><td align="left">定义二维正交投影裁剪区域</td><td align="left">定义视野的左、右、下、上边界范围</td></tr></tbody></table>
|
|
<hr>
|
|
<h2 id="4-几何图形绘制">4. 几何图形绘制</h2>
|
|
<p>OpenGL 的核心绘图逻辑,通过指定顶点来构建形状。</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<table><thead><tr><th align="left">函数名称</th><th align="left">功能描述</th><th align="left">核心参数说明</th></tr></thead><tbody><tr><td align="left"><code>glRectf</code></td><td align="left">绘制一个实心矩形</td><td align="left">传入左下角坐标和右上角坐标 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="14.109ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 6236 1000" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="MJX-2-TEX-N-28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path><path id="MJX-2-TEX-I-1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path><path id="MJX-2-TEX-N-31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path><path id="MJX-2-TEX-N-2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path><path id="MJX-2-TEX-I-1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path><path id="MJX-2-TEX-N-32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path><path id="MJX-2-TEX-N-29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><use data-c="28" xlink:href="#MJX-2-TEX-N-28"></use></g><g data-mml-node="mi" transform="translate(389,0)"><use data-c="1D465" xlink:href="#MJX-2-TEX-I-1D465"></use></g><g data-mml-node="mn" transform="translate(961,0)"><use data-c="31" xlink:href="#MJX-2-TEX-N-31"></use></g><g data-mml-node="mo" transform="translate(1461,0)"><use data-c="2C" xlink:href="#MJX-2-TEX-N-2C"></use></g><g data-mml-node="mi" transform="translate(1905.7,0)"><use data-c="1D466" xlink:href="#MJX-2-TEX-I-1D466"></use></g><g data-mml-node="mn" transform="translate(2395.7,0)"><use data-c="31" xlink:href="#MJX-2-TEX-N-31"></use></g><g data-mml-node="mo" transform="translate(2895.7,0)"><use data-c="2C" xlink:href="#MJX-2-TEX-N-2C"></use></g><g data-mml-node="mi" transform="translate(3340.3,0)"><use data-c="1D465" xlink:href="#MJX-2-TEX-I-1D465"></use></g><g data-mml-node="mn" transform="translate(3912.3,0)"><use data-c="32" xlink:href="#MJX-2-TEX-N-32"></use></g><g data-mml-node="mo" transform="translate(4412.3,0)"><use data-c="2C" xlink:href="#MJX-2-TEX-N-2C"></use></g><g data-mml-node="mi" transform="translate(4857,0)"><use data-c="1D466" xlink:href="#MJX-2-TEX-I-1D466"></use></g><g data-mml-node="mn" transform="translate(5347,0)"><use data-c="32" xlink:href="#MJX-2-TEX-N-32"></use></g><g data-mml-node="mo" transform="translate(5847,0)"><use data-c="29" xlink:href="#MJX-2-TEX-N-29"></use></g></g></g></svg></mjx-container></td></tr><tr><td align="left"><code>glBegin</code></td><td align="left">标记图元绘制的开始</td><td align="left"><code>GL_LINES</code> (线), <code>GL_POINTS</code> (点), <code>GL_TRIANGLES</code> (三角形)</td></tr><tr><td align="left"><code>glVertex2f</code></td><td align="left">指定一个二维顶点 (浮点型)</td><td align="left">坐标 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="5.169ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 2284.7 1000" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="MJX-3-TEX-N-28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path><path id="MJX-3-TEX-I-1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path><path id="MJX-3-TEX-N-2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path><path id="MJX-3-TEX-I-1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path><path id="MJX-3-TEX-N-29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><use data-c="28" xlink:href="#MJX-3-TEX-N-28"></use></g><g data-mml-node="mi" transform="translate(389,0)"><use data-c="1D465" xlink:href="#MJX-3-TEX-I-1D465"></use></g><g data-mml-node="mo" transform="translate(961,0)"><use data-c="2C" xlink:href="#MJX-3-TEX-N-2C"></use></g><g data-mml-node="mi" transform="translate(1405.7,0)"><use data-c="1D466" xlink:href="#MJX-3-TEX-I-1D466"></use></g><g data-mml-node="mo" transform="translate(1895.7,0)"><use data-c="29" xlink:href="#MJX-3-TEX-N-29"></use></g></g></g></svg></mjx-container></td></tr><tr><td align="left"><code>glVertex2i</code></td><td align="left">指定一个二维顶点 (整型)</td><td align="left">坐标 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="5.169ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 2284.7 1000" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="MJX-4-TEX-N-28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path><path id="MJX-4-TEX-I-1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path><path id="MJX-4-TEX-N-2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path><path id="MJX-4-TEX-I-1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path><path id="MJX-4-TEX-N-29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><use data-c="28" xlink:href="#MJX-4-TEX-N-28"></use></g><g data-mml-node="mi" transform="translate(389,0)"><use data-c="1D465" xlink:href="#MJX-4-TEX-I-1D465"></use></g><g data-mml-node="mo" transform="translate(961,0)"><use data-c="2C" xlink:href="#MJX-4-TEX-N-2C"></use></g><g data-mml-node="mi" transform="translate(1405.7,0)"><use data-c="1D466" xlink:href="#MJX-4-TEX-I-1D466"></use></g><g data-mml-node="mo" transform="translate(1895.7,0)"><use data-c="29" xlink:href="#MJX-4-TEX-N-29"></use></g></g></g></svg></mjx-container></td></tr><tr><td align="left"><code>glEnd</code></td><td align="left">标记图元绘制的结束</td><td align="left">必须与 <code>glBegin</code> 成对出现</td></tr></tbody></table>
|
|
<hr><style>
|
|
mjx-container[jax="SVG"] {
|
|
direction: ltr;
|
|
}
|
|
|
|
mjx-container[jax="SVG"] > svg {
|
|
overflow: visible;
|
|
min-height: 1px;
|
|
min-width: 1px;
|
|
}
|
|
|
|
mjx-container[jax="SVG"] > svg a {
|
|
fill: blue;
|
|
stroke: blue;
|
|
}
|
|
|
|
mjx-container[jax="SVG"][display="true"] {
|
|
display: block;
|
|
text-align: center;
|
|
margin: 1em 0;
|
|
}
|
|
|
|
mjx-container[jax="SVG"][display="true"][width="full"] {
|
|
display: flex;
|
|
}
|
|
|
|
mjx-container[jax="SVG"][justify="left"] {
|
|
text-align: left;
|
|
}
|
|
|
|
mjx-container[jax="SVG"][justify="right"] {
|
|
text-align: right;
|
|
}
|
|
|
|
g[data-mml-node="merror"] > g {
|
|
fill: red;
|
|
stroke: red;
|
|
}
|
|
|
|
g[data-mml-node="merror"] > rect[data-background] {
|
|
fill: yellow;
|
|
stroke: none;
|
|
}
|
|
|
|
g[data-mml-node="mtable"] > line[data-line], svg[data-table] > g > line[data-line] {
|
|
stroke-width: 70px;
|
|
fill: none;
|
|
}
|
|
|
|
g[data-mml-node="mtable"] > rect[data-frame], svg[data-table] > g > rect[data-frame] {
|
|
stroke-width: 70px;
|
|
fill: none;
|
|
}
|
|
|
|
g[data-mml-node="mtable"] > .mjx-dashed, svg[data-table] > g > .mjx-dashed {
|
|
stroke-dasharray: 140;
|
|
}
|
|
|
|
g[data-mml-node="mtable"] > .mjx-dotted, svg[data-table] > g > .mjx-dotted {
|
|
stroke-linecap: round;
|
|
stroke-dasharray: 0,140;
|
|
}
|
|
|
|
g[data-mml-node="mtable"] > g > svg {
|
|
overflow: visible;
|
|
}
|
|
|
|
[jax="SVG"] mjx-tool {
|
|
display: inline-block;
|
|
position: relative;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
[jax="SVG"] mjx-tool > mjx-tip {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
|
|
mjx-tool > mjx-tip {
|
|
display: inline-block;
|
|
padding: .2em;
|
|
border: 1px solid #888;
|
|
font-size: 70%;
|
|
background-color: #F8F8F8;
|
|
color: black;
|
|
box-shadow: 2px 2px 5px #AAAAAA;
|
|
}
|
|
|
|
g[data-mml-node="maction"][data-toggle] {
|
|
cursor: pointer;
|
|
}
|
|
|
|
mjx-status {
|
|
display: block;
|
|
position: fixed;
|
|
left: 1em;
|
|
bottom: 1em;
|
|
min-width: 25%;
|
|
padding: .2em .4em;
|
|
border: 1px solid #888;
|
|
font-size: 90%;
|
|
background-color: #F8F8F8;
|
|
color: black;
|
|
}
|
|
|
|
foreignObject[data-mjx-xml] {
|
|
font-family: initial;
|
|
line-height: normal;
|
|
overflow: visible;
|
|
}
|
|
|
|
mjx-container[jax="SVG"] path[data-c], mjx-container[jax="SVG"] use[data-c] {
|
|
stroke-width: 3;
|
|
}
|
|
</style> </div> <section class="post-copyright-card" aria-label="文章版权信息"> <div class="post-copyright-mark" aria-hidden="true"> <span>CC</span> </div> <div class="post-copyright-main"> <h2>OpenGL-基础程序</h2> <a class="post-copyright-link" href="/posts/437a5198/">/posts/437a5198/</a> <dl class="post-copyright-meta"> <div> <dt>作者</dt> <dd>biss</dd> </div> <div> <dt>发布于</dt> <dd><time datetime="2026-04-11T18:20:34.000Z">2026/04/12</time></dd> </div> <div> <dt>许可协议</dt> <dd><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noreferrer">CC BY-NC-SA 4.0</a></dd> </div> </dl> </div> <div class="post-copyright-footer"> <div class="post-copyright-tags" aria-label="文章标签"> <a href="/tags/OpenGL/">OpenGL</a> </div> <div class="post-copyright-actions" aria-label="文章操作"> <button class="post-copyright-action is-primary post-share-button" type="button" data-share-title="OpenGL-基础程序" data-share-url="https://blog.biss.click/posts/437a5198/" title="分享文章"> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"></path> <path d="M12 16V4"></path> <path d="m7 9 5-5 5 5"></path> </svg> <span>分享</span> </button> <a class="post-copyright-action" href="/rss.xml" title="订阅 RSS"> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M5 5a14 14 0 0 1 14 14"></path> <path d="M5 11a8 8 0 0 1 8 8"></path> <path d="M6 18h.01"></path> </svg> <span>订阅</span> </a> <a class="post-copyright-action" href="/about/" title="打赏作者"> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M7 8h10"></path> <path d="M8 8V5h8v3"></path> <path d="M6 8h12l-1.5 11h-9L6 8Z"></path> <path d="M10 12h4"></path> </svg> <span>打赏</span> </a> <a class="post-copyright-icon" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.biss.click%2Fposts%2F437a5198%2F&text=OpenGL-%E5%9F%BA%E7%A1%80%E7%A8%8B%E5%BA%8F" target="_blank" rel="noreferrer" title="分享到 X"> <span aria-hidden="true">x</span> </a> <a class="post-copyright-icon" href="https://service.weibo.com/share/share.php?url=https%3A%2F%2Fblog.biss.click%2Fposts%2F437a5198%2F&title=OpenGL-%E5%9F%BA%E7%A1%80%E7%A8%8B%E5%BA%8F" target="_blank" rel="noreferrer" title="分享到微博"> <span aria-hidden="true">微</span> </a> </div> </div> </section> <script>
|
|
(() => {
|
|
document.querySelectorAll('.post-share-button').forEach((button) => {
|
|
button.addEventListener('click', async () => {
|
|
const title = button.dataset.shareTitle || document.title;
|
|
const url = button.dataset.shareUrl || window.location.href;
|
|
const label = button.querySelector('span');
|
|
const originalText = label?.textContent || '分享';
|
|
|
|
try {
|
|
if (navigator.share) {
|
|
await navigator.share({ title, url });
|
|
return;
|
|
}
|
|
|
|
await navigator.clipboard.writeText(url);
|
|
if (label) label.textContent = '已复制';
|
|
} catch {
|
|
if (label) label.textContent = '复制失败';
|
|
} finally {
|
|
window.setTimeout(() => {
|
|
if (label) label.textContent = originalText;
|
|
}, 1800);
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script> <section id="post-comment" class="twikoo-comments" aria-label="Comments" data-env-id="https://comment.biss.click" data-region data-path="/posts/437a5198/" data-script="https://cdn.jsdelivr.net/npm/twikoo@1.7.7/dist/twikoo.min.js"> <div class="twikoo-comments-head"> <span class="twikoo-comments-icon" aria-hidden="true"> <svg viewBox="0 0 24 24"> <path d="M21 12a8 8 0 0 1-8 8H7l-4 3v-5a8 8 0 1 1 18-6Z"></path> </svg> </span> <div> <h2>Comments</h2> <p>OpenGL-基础程序</p> </div> </div> <div id="twikoo-wrap" class="twikoo-wrap"></div> </section> <script>
|
|
(() => {
|
|
const root = document.getElementById('post-comment');
|
|
const wrap = document.getElementById('twikoo-wrap');
|
|
if (!root || !wrap || root.dataset.twikooLoaded === 'true') return;
|
|
|
|
const envId = root.dataset.envId;
|
|
const path = root.dataset.path || window.location.pathname;
|
|
const region = root.dataset.region || undefined;
|
|
if (!envId) return;
|
|
|
|
root.dataset.twikooLoaded = 'true';
|
|
|
|
const initTwikoo = () => {
|
|
if (!window.twikoo || typeof window.twikoo.init !== 'function') return;
|
|
|
|
window.twikoo.init({
|
|
el: wrap,
|
|
envId,
|
|
region,
|
|
path,
|
|
});
|
|
};
|
|
|
|
if (window.twikoo) {
|
|
initTwikoo();
|
|
return;
|
|
}
|
|
|
|
const script = document.createElement('script');
|
|
script.src = root.dataset.script;
|
|
script.defer = true;
|
|
script.onload = initTwikoo;
|
|
script.onerror = () => {
|
|
root.dataset.twikooLoaded = 'false';
|
|
};
|
|
document.head.appendChild(script);
|
|
})();
|
|
</script> </article> <aside class="article-sidebar" aria-label="文章侧边栏"> <section class="sidebar-card article-author-card"> <img class="profile-avatar" src="https://free.picui.cn/free/2025/08/10/689845496a283.png" alt="biss" loading="lazy"> <div class="profile-name">biss</div> <div class="article-author-meta"> <span>2026/04/12</span> <a href="/categories/tech/">技术</a> </div> </section> <section class="sidebar-card article-tags-card"> <h2> <span aria-hidden="true">#</span>
|
|
文章标签
|
|
<em>1</em> </h2> <div class="article-tag-list" aria-label="文章标签"> <a href="/tags/OpenGL/">#OpenGL</a> </div> </section> <div class="article-sticky-sidebar"> <section class="sidebar-card toc-card"> <h2> <span aria-hidden="true">☰</span>
|
|
文章目录
|
|
<em>6</em> </h2> <nav class="toc-list toc-tree" aria-label="文章目录" data-toc-tree> <div class="toc-group is-open" data-toc-group data-heading-id="代码展示"> <a class="toc-link toc-link-main" href="#代码展示" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>代码展示</span> </a> </div><div class="toc-group" data-toc-group data-heading-id="代码解释"> <a class="toc-link toc-link-main" href="#代码解释" data-toc-link data-toc-top> <span class="toc-marker" aria-hidden="true"></span> <span>代码解释</span> </a> <div class="toc-children" data-toc-children> <a class="toc-link toc-link-child toc-depth-2" href="#1-窗口管理与初始化-glut-库" data-toc-link> <span>1. 窗口管理与初始化 (GLUT 库)</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#2-状态设置与缓冲区操作" data-toc-link> <span>2. 状态设置与缓冲区操作</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#3-矩阵与坐标变换" data-toc-link> <span>3. 矩阵与坐标变换</span> </a><a class="toc-link toc-link-child toc-depth-2" href="#4-几何图形绘制" data-toc-link> <span>4. 几何图形绘制</span> </a> </div> </div> </nav> </section> <section class="sidebar-card same-category-card"> <h2> <span aria-hidden="true">▣</span>
|
|
同类文章
|
|
<em>8</em> </h2> <div class="same-category-list"> <a href="/posts/3f8de22/"> <strong>GPT Image2 初试</strong> <time datetime="2026-04-25T15:00:00.000Z">2026/04/25</time> </a><a href="/posts/8712baaa/"> <strong>电子价签玩法</strong> <time datetime="2026-04-19T13:46:25.000Z">2026/04/19</time> </a><a href="/posts/9b2ef00a/"> <strong>OpenGL- Cohen-Sutherland 编码裁剪算法</strong> <time datetime="2026-04-15T19:34:23.000Z">2026/04/16</time> </a><a href="/posts/7207243b/"> <strong>OpenGL-直线的扫描转换</strong> <time datetime="2026-04-11T19:01:02.000Z">2026/04/12</time> </a><a href="/posts/c4477b0c/"> <strong>配置OpenGL环境</strong> <time datetime="2026-04-02T04:52:16.000Z">2026/04/02</time> </a><a href="/posts/b559997d/"> <strong>opencv应用-算术运算</strong> <time datetime="2026-03-17T13:13:08.000Z">2026/03/17</time> </a><a href="/posts/e8f95ead/"> <strong>opencv应用-基础操作</strong> <time datetime="2026-03-15T09:56:51.000Z">2026/03/15</time> </a><a href="/posts/6e3332b/"> <strong>配置opencv</strong> <time datetime="2026-03-14T11:48:36.000Z">2026/03/14</time> </a> </div> </section> <section class="sidebar-card site-info-card" data-site-info-card data-stats-endpoint="https://blogumami.biss.click"> <h2><i class="fa-solid fa-chart-line" aria-hidden="true"></i> 网站信息</h2> <dl class="site-info-list"> <div> <dt>文章数目</dt> <dd>43</dd> </div><div> <dt>本站总字数</dt> <dd>32,569</dd> </div><div> <dt>本站访客数</dt> <dd data-stat-key="visitors">--</dd> </div><div> <dt>本站访问量</dt> <dd data-stat-key="pageviews">--</dd> </div><div> <dt>最后更新时间</dt> <dd>2026/06/19</dd> </div> </dl> </section> <script>
|
|
(() => {
|
|
const cacheDuration = 10 * 60 * 1000;
|
|
const formatter = new Intl.NumberFormat('en-US');
|
|
const cards = [...document.querySelectorAll('[data-site-info-card]')];
|
|
const endpoint = cards.find((card) => card.dataset.statsEndpoint)?.dataset.statsEndpoint;
|
|
|
|
if (!endpoint) return;
|
|
|
|
const cacheKey = `site-info-stats:${endpoint}`;
|
|
|
|
const updateCards = (stats) => {
|
|
const visitors = Number(stats?.visitors);
|
|
const pageviews = Number(stats?.pageviews);
|
|
|
|
cards.forEach((card) => {
|
|
const visitorsEl = card.querySelector('[data-stat-key="visitors"]');
|
|
const pageviewsEl = card.querySelector('[data-stat-key="pageviews"]');
|
|
if (visitorsEl && Number.isFinite(visitors)) visitorsEl.textContent = formatter.format(visitors);
|
|
if (pageviewsEl && Number.isFinite(pageviews)) pageviewsEl.textContent = formatter.format(pageviews);
|
|
});
|
|
};
|
|
|
|
try {
|
|
const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null');
|
|
if (cached?.time && Date.now() - cached.time < cacheDuration) {
|
|
updateCards(cached.data);
|
|
return;
|
|
}
|
|
} catch {
|
|
localStorage.removeItem(cacheKey);
|
|
}
|
|
|
|
fetch(endpoint, { headers: { Accept: 'application/json' } })
|
|
.then((response) => {
|
|
if (!response.ok) throw new Error(`Stats request failed: ${response.status}`);
|
|
return response.json();
|
|
})
|
|
.then((data) => {
|
|
localStorage.setItem(cacheKey, JSON.stringify({ time: Date.now(), data }));
|
|
updateCards(data);
|
|
})
|
|
.catch(() => {
|
|
});
|
|
})();
|
|
</script> </div> </aside> </section> </main> <footer class="site-footer" aria-label="站点页脚"> <div class="footer-inner"> <section class="footer-top" aria-label="站点信息"> <a class="footer-brand" href="/" aria-label="Bi's Blog 首页"> <img src="/images/Bi.ico" alt="" loading="lazy"> <span>Bi's Blog</span> </a> <p class="footer-slogan">好奇心转化为代码的空间。</p> </section> <section class="footer-contact" aria-label="联系方式"> <p>联系方式</p> <div class="footer-socials"> <a href="mailto:bishsh2006@gmail.com" aria-label="发送邮件" title="发送邮件"> <i class="fa-solid fa-envelope" aria-hidden="true"></i> </a><a href="/rss.xml" aria-label="订阅 RSS" title="订阅 RSS"> <i class="fa-solid fa-rss" aria-hidden="true"></i> </a><a href="https://github.com/bishshi" target="_blank" rel="noreferrer" aria-label="GitHub" title="GitHub"> <i class="fa-brands fa-github" aria-hidden="true"></i> </a> </div> <nav class="footer-contact-links" aria-label="页脚链接"> <a href="/archives/">发展历程</a> <span aria-hidden="true">|</span><a href="/privacy/">隐私政策</a> </nav> </section> <section class="footer-bottom" aria-label="版权信息"> <div> <p>© 2025 - 2026 Bi's Blog</p> <p>blog.biss.click | 内容采用 CC BY-NC-SA 4.0 协议</p> </div> <div class="footer-meta"> <p>本站已运行:<span id="site-runtime">计算中...</span></p> <p> <a href="https://fastly.com/" target="_blank" rel="noreferrer">Fastly</a> <span aria-hidden="true">|</span><a href="https://astro.build/" target="_blank" rel="noreferrer">ASTRO</a> </p> </div> </section> </div> </footer> <aside class="hitokoto-card" aria-label="一言" data-hitokoto-card data-astro-cid-4cgfuthx> <div class="hitokoto-card__head" data-astro-cid-4cgfuthx> <button class="hitokoto-card__toggle" type="button" aria-label="展开一言" aria-expanded="true" title="一言" data-hitokoto-toggle data-astro-cid-4cgfuthx> <i class="fa-solid fa-quote-left" aria-hidden="true" data-astro-cid-4cgfuthx></i> <span data-astro-cid-4cgfuthx>一言</span> </button> <div class="hitokoto-card__actions" data-astro-cid-4cgfuthx> <button class="hitokoto-card__refresh" type="button" aria-label="换一句" title="换一句" data-hitokoto-refresh data-astro-cid-4cgfuthx> <i class="fa-solid fa-rotate" aria-hidden="true" data-astro-cid-4cgfuthx></i> </button> <button class="hitokoto-card__collapse" type="button" aria-label="收起一言" title="收起" data-hitokoto-collapse data-astro-cid-4cgfuthx> <i class="fa-solid fa-chevron-down" aria-hidden="true" data-astro-cid-4cgfuthx></i> </button> </div> </div> <a class="hitokoto-card__text" href="https://hitokoto.cn/" target="_blank" rel="noreferrer" data-hitokoto-text data-astro-cid-4cgfuthx>
|
|
获取中...
|
|
</a> <p class="hitokoto-card__from" data-hitokoto-from data-astro-cid-4cgfuthx>来自 Hitokoto</p> </aside> <script type="module">(()=>{const o=document.querySelector("[data-hitokoto-card]"),s=o?.querySelector("[data-hitokoto-toggle]"),r=o?.querySelector("[data-hitokoto-text]"),l=o?.querySelector("[data-hitokoto-from]"),k=o?.querySelector("[data-hitokoto-refresh]"),m=o?.querySelector("[data-hitokoto-collapse]");if(!(o instanceof HTMLElement)||!(s instanceof HTMLButtonElement)||!(r instanceof HTMLAnchorElement)||!(l instanceof HTMLElement))return;const g="https://yiyan.biss.click/api?encode=json",u="hitokoto-card-cache",y=360*60*1e3,d={sentence:"生活明朗,万物可爱。",source:"本地缓存",url:"https://hitokoto.cn/"},L=["/","/index.html"].includes(window.location.pathname);function c(t){o.classList.toggle("is-collapsed",!t),s.setAttribute("aria-expanded",String(t)),s.setAttribute("aria-label",t?"收起一言":"展开一言")}function f(){try{const t=localStorage.getItem(u);if(!t)return null;const e=JSON.parse(t);return!e||typeof e.expiresAt!="number"||e.expiresAt<=Date.now()?null:e.data}catch{return null}}function x(t){try{localStorage.setItem(u,JSON.stringify({data:t,expiresAt:Date.now()+y}))}catch{}}function i(t){r.textContent=t.sentence,r.href=t.url||"https://hitokoto.cn/",l.textContent=t.source?`出自 ${t.source}`:"来自 Hitokoto"}async function h({force:t=!1}={}){if(!t){const e=f();if(e){i(e);return}}o.classList.add("is-loading"),r.textContent="获取中...";try{const e=await fetch(g);if(!e.ok)throw new Error(`Hitokoto request failed: ${e.status}`);const n=await e.json(),a=n.hitokoto||n.content||n.sentence||n.text,S=typeof a=="string"&&a.trim()?a.trim():d.sentence,E=typeof n.from=="string"&&n.from.trim()?n.from.trim():"Hitokoto",w=n.uuid?`https://hitokoto.cn/?uuid=${encodeURIComponent(n.uuid)}`:"https://hitokoto.cn/",p={sentence:S,source:E,url:w};x(p),i(p)}catch(e){console.error(e),i(f()||d)}finally{o.classList.remove("is-loading")}}c(L),s.addEventListener("click",()=>{c(o.classList.contains("is-collapsed"))}),k?.addEventListener("click",t=>{t.stopPropagation(),c(!0),h({force:!0})}),m?.addEventListener("click",t=>{t.stopPropagation(),c(!1)}),o.addEventListener("click",t=>{o.classList.contains("is-collapsed")&&(t.target instanceof Element&&t.target.closest("a, button")||c(!0))}),h()})();</script> <div class="custom-context-menu" data-context-menu role="menu" aria-hidden="true"> <div class="context-menu-nav" aria-label="页面导航"> <button type="button" data-context-action="back" aria-label="后退上一页" title="后退上一页"> <i class="fa-solid fa-arrow-left" aria-hidden="true"></i> </button> <button type="button" data-context-action="forward" aria-label="前进下一页" title="前进下一页"> <i class="fa-solid fa-arrow-right" aria-hidden="true"></i> </button> <button type="button" data-context-action="reload" aria-label="刷新页面" title="刷新页面"> <i class="fa-solid fa-rotate-right" aria-hidden="true"></i> </button> <a href="/" aria-label="返回首页" title="返回首页"> <i class="fa-solid fa-house" aria-hidden="true"></i> </a> </div> <div class="context-menu-group" data-selection-group hidden> <button type="button" data-context-action="copy-selection" data-selection-item> <i class="fa-regular fa-copy" aria-hidden="true"></i> <span>复制选中文字</span> </button> <button type="button" data-context-action="search-selection" data-selection-item> <i class="fa-brands fa-microsoft" aria-hidden="true"></i> <span>用 Bing 搜索</span> </button> <button type="button" data-context-action="quote-selection" data-selection-item data-comments-item> <i class="fa-regular fa-comment-dots" aria-hidden="true"></i> <span>引用到评论区</span> </button> </div> <div class="context-menu-group" data-image-group hidden> <button type="button" data-context-action="copy-image"> <i class="fa-regular fa-image" aria-hidden="true"></i> <span>复制图片</span> </button> <button type="button" data-context-action="open-image"> <i class="fa-solid fa-up-right-from-square" aria-hidden="true"></i> <span>新页面打开图片</span> </button> <button type="button" data-context-action="copy-image-url"> <i class="fa-solid fa-link" aria-hidden="true"></i> <span>复制图片地址</span> </button> </div> <div class="context-menu-group"> <a href="/archives/"> <i class="fa-solid fa-box-archive" aria-hidden="true"></i> <span>时间线</span> </a> <a href="/categories/"> <i class="fa-solid fa-folder" aria-hidden="true"></i> <span>分类</span> </a> <a href="/tags/"> <i class="fa-solid fa-hashtag" aria-hidden="true"></i> <span>标签</span> </a> <button type="button" data-context-action="comments" data-comments-item> <i class="fa-regular fa-comment-dots" aria-hidden="true"></i> <span>跳转评论区</span> </button> <button type="button" data-context-action="print"> <i class="fa-solid fa-print" aria-hidden="true"></i> <span>打印页面</span> </button> <a href="/cc/"> <i class="fa-solid fa-circle-info" aria-hidden="true"></i> <span>网站版权</span> </a> </div> </div> <script>
|
|
(() => {
|
|
const menu = document.querySelector('[data-context-menu]');
|
|
if (!menu) return;
|
|
|
|
const selectionItems = [...menu.querySelectorAll('[data-selection-item]')];
|
|
const selectionGroup = menu.querySelector('[data-selection-group]');
|
|
const commentsItems = [...menu.querySelectorAll('[data-comments-item]')];
|
|
const imageGroup = menu.querySelector('[data-image-group]');
|
|
const focusableSelector = 'a[href], button:not([disabled])';
|
|
let lastSelectionText = '';
|
|
let lastImage = null;
|
|
|
|
const isNativeContextTarget = (target) =>
|
|
target instanceof Element &&
|
|
Boolean(target.closest('input, textarea, select, [contenteditable="true"], [contenteditable=""]'));
|
|
|
|
const getCommentsTarget = () =>
|
|
document.querySelector('.twikoo-comments, #twikoo-wrap, #twikoo, .tk-comments-container');
|
|
|
|
const getSelectionText = () => String(window.getSelection?.() || '').trim();
|
|
|
|
const getImageTarget = (target) => {
|
|
if (!(target instanceof Element)) return null;
|
|
const image = target.closest('img');
|
|
return image instanceof HTMLImageElement ? image : null;
|
|
};
|
|
|
|
const getImageUrl = () => {
|
|
if (!lastImage) return '';
|
|
return lastImage.currentSrc || lastImage.src || lastImage.getAttribute('src') || '';
|
|
};
|
|
|
|
const copyText = async (text) => {
|
|
if (navigator.clipboard?.writeText) {
|
|
await navigator.clipboard.writeText(text);
|
|
return;
|
|
}
|
|
|
|
const textarea = document.createElement('textarea');
|
|
textarea.value = text;
|
|
textarea.setAttribute('readonly', '');
|
|
textarea.style.position = 'fixed';
|
|
textarea.style.inset = '0 auto auto -9999px';
|
|
document.body.appendChild(textarea);
|
|
textarea.select();
|
|
document.execCommand('copy');
|
|
textarea.remove();
|
|
};
|
|
|
|
const setMenuState = () => {
|
|
const hasSelection = lastSelectionText.length > 0;
|
|
const hasComments = Boolean(getCommentsTarget());
|
|
const hasImage = Boolean(getImageUrl());
|
|
|
|
selectionItems.forEach((item) => {
|
|
item.hidden = !hasSelection;
|
|
});
|
|
if (selectionGroup) selectionGroup.hidden = !hasSelection;
|
|
|
|
commentsItems.forEach((item) => {
|
|
item.hidden = !hasComments || (item.hasAttribute('data-selection-item') && !hasSelection);
|
|
});
|
|
|
|
if (imageGroup) imageGroup.hidden = !hasImage;
|
|
};
|
|
|
|
const closeMenu = () => {
|
|
menu.classList.remove('is-open');
|
|
menu.setAttribute('aria-hidden', 'true');
|
|
};
|
|
|
|
const openMenu = (x, y) => {
|
|
setMenuState();
|
|
menu.classList.add('is-open');
|
|
menu.setAttribute('aria-hidden', 'false');
|
|
|
|
const rect = menu.getBoundingClientRect();
|
|
const gap = 10;
|
|
const left = Math.min(Math.max(gap, x), window.innerWidth - rect.width - gap);
|
|
const top = Math.min(Math.max(gap, y), window.innerHeight - rect.height - gap);
|
|
|
|
menu.style.left = `${left}px`;
|
|
menu.style.top = `${top}px`;
|
|
};
|
|
|
|
const jumpToComments = () => {
|
|
const target = getCommentsTarget();
|
|
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
};
|
|
|
|
const quoteToComments = () => {
|
|
jumpToComments();
|
|
window.setTimeout(() => {
|
|
const textarea = document.querySelector('#twikoo textarea, .twikoo-comments textarea, .tk-input textarea');
|
|
if (!(textarea instanceof HTMLTextAreaElement) || !lastSelectionText) return;
|
|
|
|
const quote = lastSelectionText
|
|
.split(/\r?\n/)
|
|
.map((line) => `> ${line}`)
|
|
.join('\n');
|
|
const current = textarea.value.trim();
|
|
textarea.value = current ? `${current}\n\n${quote}\n` : `${quote}\n`;
|
|
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
textarea.focus();
|
|
}, 500);
|
|
};
|
|
|
|
const searchSelection = () => {
|
|
if (!lastSelectionText) return;
|
|
const url = `https://www.bing.com/search?q=${encodeURIComponent(lastSelectionText)}`;
|
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
};
|
|
|
|
const copyImage = async () => {
|
|
const imageUrl = getImageUrl();
|
|
if (!imageUrl) return;
|
|
|
|
try {
|
|
if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined') {
|
|
throw new Error('Image clipboard is not available.');
|
|
}
|
|
|
|
const response = await fetch(imageUrl, { mode: 'cors' });
|
|
const blob = await response.blob();
|
|
await navigator.clipboard.write([
|
|
new ClipboardItem({ [blob.type || 'image/png']: blob }),
|
|
]);
|
|
} catch {
|
|
await copyText(new URL(imageUrl, window.location.href).toString());
|
|
}
|
|
};
|
|
|
|
const openImage = () => {
|
|
const imageUrl = getImageUrl();
|
|
if (!imageUrl) return;
|
|
window.open(new URL(imageUrl, window.location.href).toString(), '_blank', 'noopener,noreferrer');
|
|
};
|
|
|
|
document.addEventListener('contextmenu', (event) => {
|
|
if (isNativeContextTarget(event.target)) return;
|
|
|
|
lastSelectionText = getSelectionText();
|
|
lastImage = getImageTarget(event.target);
|
|
event.preventDefault();
|
|
openMenu(event.clientX, event.clientY);
|
|
});
|
|
|
|
document.addEventListener('click', (event) => {
|
|
const actionTarget = event.target instanceof Element ? event.target.closest('[data-context-action]') : null;
|
|
if (!actionTarget || !menu.contains(actionTarget)) {
|
|
closeMenu();
|
|
return;
|
|
}
|
|
|
|
const action = actionTarget.dataset.contextAction;
|
|
closeMenu();
|
|
|
|
if (action === 'back') history.back();
|
|
if (action === 'forward') history.forward();
|
|
if (action === 'reload') location.reload();
|
|
if (action === 'copy-selection' && lastSelectionText) copyText(lastSelectionText);
|
|
if (action === 'search-selection') searchSelection();
|
|
if (action === 'quote-selection') quoteToComments();
|
|
if (action === 'copy-image') copyImage();
|
|
if (action === 'open-image') openImage();
|
|
if (action === 'copy-image-url') copyText(new URL(getImageUrl(), window.location.href).toString());
|
|
if (action === 'comments') jumpToComments();
|
|
if (action === 'print') window.print();
|
|
});
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Escape') closeMenu();
|
|
if (event.key === 'Tab' && menu.classList.contains('is-open')) {
|
|
const items = [...menu.querySelectorAll(focusableSelector)].filter((item) => !item.closest('[hidden]'));
|
|
if (items.length === 0) return;
|
|
const first = items[0];
|
|
const last = items[items.length - 1];
|
|
|
|
if (event.shiftKey && document.activeElement === first) {
|
|
event.preventDefault();
|
|
last.focus();
|
|
} else if (!event.shiftKey && document.activeElement === last) {
|
|
event.preventDefault();
|
|
first.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
window.addEventListener('scroll', closeMenu, { passive: true });
|
|
window.addEventListener('resize', closeMenu);
|
|
})();
|
|
</script> <script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script> <script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script> <script src="https://cdn.jsdmirror.com/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script> <script src="/js/random.js"></script> <script src="/js/search/typesense-search.js"></script> <script src="/js/external-links.js"></script> <script>
|
|
(() => {
|
|
const nav = document.querySelector('.nav');
|
|
const toggle = document.querySelector('[data-nav-toggle]');
|
|
const menu = document.querySelector('[data-nav-menu]');
|
|
if (!nav || !toggle || !menu) return;
|
|
|
|
function setMenu(open) {
|
|
nav.classList.toggle('is-open', open);
|
|
toggle.setAttribute('aria-expanded', String(open));
|
|
toggle.setAttribute('aria-label', open ? '关闭菜单' : '打开菜单');
|
|
}
|
|
|
|
toggle.addEventListener('click', () => {
|
|
setMenu(!nav.classList.contains('is-open'));
|
|
});
|
|
|
|
menu.addEventListener('click', (event) => {
|
|
if (event.target instanceof Element && event.target.closest('a')) setMenu(false);
|
|
});
|
|
|
|
window.addEventListener('resize', () => {
|
|
if (window.innerWidth > 720) setMenu(false);
|
|
});
|
|
})();
|
|
|
|
(() => {
|
|
const storageKey = 'site-theme';
|
|
const root = document.documentElement;
|
|
const button = document.querySelector('[data-theme-toggle]');
|
|
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
const getSystemTheme = () => (media.matches ? 'dark' : 'light');
|
|
const getTheme = () => localStorage.getItem(storageKey) || getSystemTheme();
|
|
|
|
function applyTheme(theme) {
|
|
root.dataset.theme = theme;
|
|
button?.setAttribute('aria-label', theme === 'dark' ? '切换到日间模式' : '切换到夜间模式');
|
|
button?.setAttribute('title', theme === 'dark' ? '切换到日间模式' : '切换到夜间模式');
|
|
}
|
|
|
|
applyTheme(getTheme());
|
|
|
|
button?.addEventListener('click', () => {
|
|
const nextTheme = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
localStorage.setItem(storageKey, nextTheme);
|
|
applyTheme(nextTheme);
|
|
});
|
|
|
|
media.addEventListener('change', () => {
|
|
if (!localStorage.getItem(storageKey)) applyTheme(getSystemTheme());
|
|
});
|
|
})();
|
|
|
|
(() => {
|
|
const runtime = document.getElementById('site-runtime');
|
|
if (!runtime) return;
|
|
|
|
const start = new Date(window.SITE_CONFIG.site.startTime).getTime();
|
|
const pad = (value) => String(value).padStart(2, '0');
|
|
|
|
function updateRuntime() {
|
|
const diff = Math.max(0, Date.now() - start);
|
|
const days = Math.floor(diff / 86400000);
|
|
const hours = Math.floor((diff / 3600000) % 24);
|
|
const minutes = Math.floor((diff / 60000) % 60);
|
|
const seconds = Math.floor((diff / 1000) % 60);
|
|
runtime.textContent = `${days} 天 ${pad(hours)} 时 ${pad(minutes)} 分 ${pad(seconds)} 秒`;
|
|
}
|
|
|
|
updateRuntime();
|
|
window.setInterval(updateRuntime, 1000);
|
|
})();
|
|
</script> </body> </html> <script>
|
|
(() => {
|
|
const prose = document.querySelector('.prose[data-toc]');
|
|
if (!prose) return;
|
|
|
|
let toc = [];
|
|
try {
|
|
toc = JSON.parse(prose.dataset.toc || '[]');
|
|
} catch {
|
|
toc = [];
|
|
}
|
|
|
|
prose.querySelectorAll('h1, h2, h3').forEach((heading, index) => {
|
|
const item = toc[index];
|
|
if (item?.id && !heading.id) heading.id = item.id;
|
|
});
|
|
|
|
const tocTree = document.querySelector('[data-toc-tree]');
|
|
if (tocTree && toc.length > 0) {
|
|
const links = [...tocTree.querySelectorAll('[data-toc-link]')];
|
|
const groups = [...tocTree.querySelectorAll('[data-toc-group]')];
|
|
const headings = toc
|
|
.map((item) => document.getElementById(item.id))
|
|
.filter(Boolean);
|
|
|
|
const setActiveToc = (id) => {
|
|
links.forEach((link) => {
|
|
const isActive = link.getAttribute('href') === `#${id}`;
|
|
link.classList.toggle('is-active', isActive);
|
|
if (isActive) link.setAttribute('aria-current', 'location');
|
|
else link.removeAttribute('aria-current');
|
|
});
|
|
|
|
const activeLink = tocTree.querySelector(`[data-toc-link][href="#${CSS.escape(id)}"]`);
|
|
const activeGroup = activeLink?.closest('[data-toc-group]');
|
|
groups.forEach((group) => group.classList.toggle('is-open', group === activeGroup));
|
|
};
|
|
|
|
const updateToc = () => {
|
|
const anchorOffset = 132;
|
|
let activeId = headings[0]?.id;
|
|
|
|
headings.forEach((heading) => {
|
|
if (heading.getBoundingClientRect().top <= anchorOffset) activeId = heading.id;
|
|
});
|
|
|
|
if (activeId) setActiveToc(activeId);
|
|
};
|
|
|
|
tocTree.addEventListener('click', (event) => {
|
|
const link = event.target.closest?.('[data-toc-link]');
|
|
if (!link) return;
|
|
|
|
const group = link.closest('[data-toc-group]');
|
|
groups.forEach((item) => item.classList.toggle('is-open', item === group));
|
|
});
|
|
|
|
updateToc();
|
|
window.addEventListener('scroll', updateToc, { passive: true });
|
|
window.addEventListener('resize', updateToc);
|
|
}
|
|
|
|
const summaryText = document.querySelector('.article-summary-text');
|
|
if (!summaryText) return;
|
|
|
|
const fullText = summaryText.dataset.summaryText || summaryText.textContent || '';
|
|
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
if (reduceMotion) {
|
|
summaryText.textContent = fullText;
|
|
return;
|
|
}
|
|
|
|
summaryText.textContent = '';
|
|
let index = 0;
|
|
|
|
function typeSummary() {
|
|
index += 1;
|
|
summaryText.textContent = fullText.slice(0, index);
|
|
if (index < fullText.length) {
|
|
window.setTimeout(typeSummary, 34);
|
|
}
|
|
}
|
|
|
|
window.setTimeout(typeSummary, 280);
|
|
})();
|
|
</script> <script>
|
|
(() => {
|
|
const prose = document.querySelector('.post-article .prose');
|
|
if (!prose) return;
|
|
|
|
const collapsedLineCount = 8;
|
|
const copyLabels = {
|
|
idle: '复制',
|
|
done: '已复制',
|
|
error: '复制失败',
|
|
};
|
|
|
|
const languageNames = {
|
|
bash: 'Bash',
|
|
c: 'C',
|
|
cpp: 'C++',
|
|
cs: 'C#',
|
|
css: 'CSS',
|
|
html: 'HTML',
|
|
js: 'JavaScript',
|
|
json: 'JSON',
|
|
jsx: 'JSX',
|
|
md: 'Markdown',
|
|
powershell: 'PowerShell',
|
|
py: 'Python',
|
|
shell: 'Shell',
|
|
sh: 'Shell',
|
|
ts: 'TypeScript',
|
|
tsx: 'TSX',
|
|
txt: 'Text',
|
|
yaml: 'YAML',
|
|
yml: 'YAML',
|
|
};
|
|
|
|
function getCodeLanguage(pre, code) {
|
|
const rawLanguage =
|
|
pre.dataset.language ||
|
|
code.dataset.language ||
|
|
[...pre.classList, ...code.classList]
|
|
.find((className) => className.startsWith('language-'))
|
|
?.replace(/^language-/, '') ||
|
|
'';
|
|
|
|
const normalized = rawLanguage.trim().toLowerCase();
|
|
return languageNames[normalized] || normalized.toUpperCase() || 'Code';
|
|
}
|
|
|
|
function getCodeText(code, lines) {
|
|
if (lines.length > 0) return lines.map((line) => line.textContent || '').join('\n');
|
|
return code.textContent || '';
|
|
}
|
|
|
|
prose.querySelectorAll('img').forEach((image) => {
|
|
if (image.closest('a, .article-header-cover')) return;
|
|
|
|
const src = image.currentSrc || image.getAttribute('src');
|
|
if (!src) return;
|
|
|
|
const link = document.createElement('a');
|
|
link.className = 'fancybox';
|
|
link.href = src;
|
|
link.dataset.fancybox = 'article-gallery';
|
|
link.dataset.caption = image.getAttribute('alt') || '';
|
|
image.parentNode?.insertBefore(link, image);
|
|
link.appendChild(image);
|
|
});
|
|
|
|
prose.querySelectorAll('table').forEach((table) => {
|
|
if (table.closest('.table-scroll')) return;
|
|
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'table-scroll';
|
|
table.parentNode?.insertBefore(wrapper, table);
|
|
wrapper.appendChild(table);
|
|
});
|
|
|
|
prose.querySelectorAll('.butterfly-hide-inline').forEach((item) => {
|
|
const button = item.querySelector('.butterfly-hide-toggle');
|
|
if (!button) return;
|
|
|
|
button.addEventListener('click', () => {
|
|
item.classList.toggle('is-open');
|
|
});
|
|
});
|
|
|
|
if (window.Fancybox) {
|
|
window.Fancybox.bind('[data-fancybox="article-gallery"]', {
|
|
compact: false,
|
|
dragToClose: true,
|
|
Images: {
|
|
zoom: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function copyText(text) {
|
|
if (navigator.clipboard?.writeText) {
|
|
await navigator.clipboard.writeText(text);
|
|
return;
|
|
}
|
|
|
|
const textarea = document.createElement('textarea');
|
|
textarea.value = text;
|
|
textarea.setAttribute('readonly', '');
|
|
textarea.style.position = 'fixed';
|
|
textarea.style.top = '-9999px';
|
|
textarea.style.opacity = '0';
|
|
document.body.appendChild(textarea);
|
|
textarea.select();
|
|
document.execCommand('copy');
|
|
textarea.remove();
|
|
}
|
|
|
|
prose.querySelectorAll('pre').forEach((pre) => {
|
|
if (pre.closest('.code-block')) return;
|
|
|
|
const code = pre.querySelector('code');
|
|
if (!code) return;
|
|
|
|
const wrapper = document.createElement('div');
|
|
wrapper.className = 'code-block';
|
|
pre.parentNode?.insertBefore(wrapper, pre);
|
|
wrapper.appendChild(pre);
|
|
|
|
const header = document.createElement('div');
|
|
header.className = 'code-block-header';
|
|
|
|
const language = document.createElement('span');
|
|
language.className = 'code-block-language';
|
|
language.textContent = getCodeLanguage(pre, code);
|
|
|
|
const toolbar = document.createElement('div');
|
|
toolbar.className = 'code-block-toolbar';
|
|
|
|
const copyButton = document.createElement('button');
|
|
copyButton.className = 'code-block-copy';
|
|
copyButton.type = 'button';
|
|
copyButton.textContent = copyLabels.idle;
|
|
copyButton.setAttribute('aria-label', '复制代码');
|
|
|
|
const toggleButton = document.createElement('button');
|
|
toggleButton.className = 'code-block-toggle';
|
|
toggleButton.type = 'button';
|
|
toggleButton.textContent = '展开';
|
|
toggleButton.setAttribute('aria-expanded', 'false');
|
|
|
|
toolbar.append(copyButton, toggleButton);
|
|
header.append(language, toolbar);
|
|
wrapper.appendChild(header);
|
|
|
|
const codeLines = [...code.querySelectorAll(':scope > .line')];
|
|
if (codeLines.length > 0) {
|
|
wrapper.classList.add('has-line-numbers');
|
|
wrapper.style.setProperty('--line-number-width', `${String(codeLines.length).length + 1}ch`);
|
|
codeLines.forEach((line, index) => {
|
|
line.dataset.line = String(index + 1);
|
|
});
|
|
}
|
|
|
|
const lineCount = codeLines.length || (code.textContent || '').split(/\r?\n/).length;
|
|
if (lineCount > collapsedLineCount) wrapper.classList.add('is-collapsible');
|
|
|
|
copyButton.addEventListener('click', async () => {
|
|
try {
|
|
await copyText(getCodeText(code, codeLines));
|
|
copyButton.textContent = copyLabels.done;
|
|
window.setTimeout(() => {
|
|
copyButton.textContent = copyLabels.idle;
|
|
}, 1400);
|
|
} catch {
|
|
copyButton.textContent = copyLabels.error;
|
|
window.setTimeout(() => {
|
|
copyButton.textContent = copyLabels.idle;
|
|
}, 1600);
|
|
}
|
|
});
|
|
|
|
toggleButton.addEventListener('click', () => {
|
|
const isExpanded = wrapper.classList.toggle('is-expanded');
|
|
toggleButton.textContent = isExpanded ? '收起' : '展开';
|
|
toggleButton.setAttribute('aria-expanded', String(isExpanded));
|
|
});
|
|
});
|
|
})();
|
|
</script> |