提交 1eea91e3 编写于 作者: M Matt Pharr

Update from book source. Minor changes.

Added built-in eta and k spectra for CuZn (brass)
Fixed some small bugs in --upgrade
Improve SampledWavelengths::ToString()
GPU: issue warning on unsupported options (--force-diffuse, etc.)
上级 61933b85
...@@ -56,57 +56,6 @@ std::string RandomWalkIntegrator::ToString() const { ...@@ -56,57 +56,6 @@ std::string RandomWalkIntegrator::ToString() const {
return StringPrintf("[ RandomWalkIntegrator maxDepth: %d ]", maxDepth); return StringPrintf("[ RandomWalkIntegrator maxDepth: %d ]", maxDepth);
} }
SampledSpectrum RandomWalkIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
SampledSpectrum L = LiRandomWalk(ray, lambda, sampler, scratchBuffer, 0);
return SafeDiv(L, lambda.PDF());
}
SampledSpectrum RandomWalkIntegrator::LiRandomWalk(RayDifferential ray,
SampledWavelengths &lambda,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer,
int depth) const {
SampledSpectrum L(0.f);
// Intersect ray with scene and return if no intersection
pstd::optional<ShapeIntersection> si = Intersect(ray);
if (!si) {
// Return emitted light from infinite light sources
for (LightHandle light : infiniteLights)
L += light.Le(ray, lambda);
return L;
}
SurfaceInteraction &isect = si->intr;
// Get emitted radiance at surface intersection
L = isect.Le(-ray.d, lambda);
// Terminate random walk if maximum depth has been reached
if (depth == maxDepth)
return L;
// Compute BSDF at random walk intersection point
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf)
return L;
// Randomly sample direction leaving surface for random walk
Point2f u = sampler.Get2D();
Vector3f wi = SampleUniformSphere(u);
// Evaluate BSDF at surface for sampled direction
Vector3f wo = -ray.d;
SampledSpectrum beta = bsdf.f(wo, wi) * AbsDot(wi, isect.shading.n) / (1 / (4 * Pi));
if (!beta)
return L;
// Recursively trace ray to estimate incident radiance at surface
ray = isect.SpawnRay(wi);
return L + beta * LiRandomWalk(ray, lambda, sampler, scratchBuffer, depth + 1);
}
// Integrator Method Definitions // Integrator Method Definitions
Integrator::~Integrator() {} Integrator::~Integrator() {}
...@@ -241,7 +190,8 @@ void ImageTileIntegrator::Render() { ...@@ -241,7 +190,8 @@ void ImageTileIntegrator::Render() {
waveEnd = std::min(spp, waveEnd + nextWaveSize); waveEnd = std::min(spp, waveEnd + nextWaveSize);
if (!referenceImage) if (!referenceImage)
nextWaveSize = std::min(2 * nextWaveSize, 64); nextWaveSize = std::min(2 * nextWaveSize, 64);
if (waveStart == spp) progress.Done(); if (waveStart == spp)
progress.Done();
// Optionally write current image to disk // Optionally write current image to disk
if (waveStart == spp || Options->writePartialImages || referenceImage) { if (waveStart == spp || Options->writePartialImages || referenceImage) {
...@@ -299,7 +249,7 @@ void RayIntegrator::EvaluatePixelSample(Point2i pPixel, int sampleIndex, ...@@ -299,7 +249,7 @@ void RayIntegrator::EvaluatePixelSample(Point2i pPixel, int sampleIndex,
DCHECK_LT(Length(cameraRay->ray.d), 1.001f); DCHECK_LT(Length(cameraRay->ray.d), 1.001f);
// Scale camera ray differentials based on sampling rate // Scale camera ray differentials based on sampling rate
Float rayDiffScale = Float rayDiffScale =
std::max<Float>(.125, 1 / std::sqrt((Float)sampler.SamplesPerPixel())); std::max<Float>(.125f, 1 / std::sqrt((Float)sampler.SamplesPerPixel()));
if (!Options->disablePixelJitter) if (!Options->disablePixelJitter)
cameraRay->ray.ScaleDifferentials(rayDiffScale); cameraRay->ray.ScaleDifferentials(rayDiffScale);
......
...@@ -121,9 +121,6 @@ class RandomWalkIntegrator : public RayIntegrator { ...@@ -121,9 +121,6 @@ class RandomWalkIntegrator : public RayIntegrator {
RandomWalkIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler, RandomWalkIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights) PrimitiveHandle aggregate, std::vector<LightHandle> lights)
: RayIntegrator(camera, sampler, aggregate, lights), maxDepth(maxDepth) {} : RayIntegrator(camera, sampler, aggregate, lights), maxDepth(maxDepth) {}
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface = nullptr) const;
static std::unique_ptr<RandomWalkIntegrator> Create( static std::unique_ptr<RandomWalkIntegrator> Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler, const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
...@@ -131,11 +128,56 @@ class RandomWalkIntegrator : public RayIntegrator { ...@@ -131,11 +128,56 @@ class RandomWalkIntegrator : public RayIntegrator {
std::string ToString() const; std::string ToString() const;
SampledSpectrum Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
SampledSpectrum L = LiRandomWalk(ray, lambda, sampler, scratchBuffer, 0);
return SafeDiv(L, lambda.PDF());
}
private: private:
// RandomWalkIntegrator Private Methods // RandomWalkIntegrator Private Methods
SampledSpectrum LiRandomWalk(RayDifferential ray, SampledWavelengths &lambda, SampledSpectrum LiRandomWalk(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer, SamplerHandle sampler, ScratchBuffer &scratchBuffer,
int depth) const; int depth) const {
SampledSpectrum L(0.f);
// Intersect ray with scene and return if no intersection
pstd::optional<ShapeIntersection> si = Intersect(ray);
if (!si) {
// Return emitted light from infinite light sources
for (LightHandle light : infiniteLights)
L += light.Le(ray, lambda);
return L;
}
SurfaceInteraction &isect = si->intr;
// Get emitted radiance at surface intersection
L = isect.Le(-ray.d, lambda);
// Terminate random walk if maximum depth has been reached
if (depth == maxDepth)
return L;
// Compute BSDF at random walk intersection point
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf)
return L;
// Randomly sample direction leaving surface for random walk
Point2f u = sampler.Get2D();
Vector3f wi = SampleUniformSphere(u);
// Evaluate BSDF at surface for sampled direction
Vector3f wo = -ray.d;
SampledSpectrum beta =
bsdf.f(wo, wi) * AbsDot(wi, isect.shading.n) / (1 / (4 * Pi));
if (!beta)
return L;
// Recursively trace ray to estimate incident radiance at surface
ray = isect.SpawnRay(wi);
return L + beta * LiRandomWalk(ray, lambda, sampler, scratchBuffer, depth + 1);
}
// RandomWalkIntegrator Private Members // RandomWalkIntegrator Private Members
int maxDepth; int maxDepth;
......
...@@ -555,9 +555,12 @@ Image RGBFilm::GetImage(ImageMetadata *metadata, Float splatScale) { ...@@ -555,9 +555,12 @@ Image RGBFilm::GetImage(ImageMetadata *metadata, Float splatScale) {
RGB rgb = GetPixelRGB(p, splatScale); RGB rgb = GetPixelRGB(p, splatScale);
if (writeFP16 && std::max({rgb.r, rgb.g, rgb.b}) > 65504) { if (writeFP16 && std::max({rgb.r, rgb.g, rgb.b}) > 65504) {
if (rgb.r > 65504) rgb.r = 65504; if (rgb.r > 65504)
if (rgb.g > 65504) rgb.g = 65504; rgb.r = 65504;
if (rgb.b > 65504) rgb.b = 65504; if (rgb.g > 65504)
rgb.g = 65504;
if (rgb.b > 65504)
rgb.b = 65504;
++nClamped; ++nClamped;
} }
...@@ -748,9 +751,12 @@ Image GBufferFilm::GetImage(ImageMetadata *metadata, Float splatScale) { ...@@ -748,9 +751,12 @@ Image GBufferFilm::GetImage(ImageMetadata *metadata, Float splatScale) {
rgb = outputRGBFromSensorRGB * rgb; rgb = outputRGBFromSensorRGB * rgb;
if (writeFP16 && std::max({rgb.r, rgb.g, rgb.b}) > 65504) { if (writeFP16 && std::max({rgb.r, rgb.g, rgb.b}) > 65504) {
if (rgb.r > 65504) rgb.r = 65504; if (rgb.r > 65504)
if (rgb.g > 65504) rgb.g = 65504; rgb.r = 65504;
if (rgb.b > 65504) rgb.b = 65504; if (rgb.g > 65504)
rgb.g = 65504;
if (rgb.b > 65504)
rgb.b = 65504;
++nClamped; ++nClamped;
} }
......
...@@ -110,9 +110,8 @@ GPUPathIntegrator::GPUPathIntegrator(Allocator alloc, const ParsedScene &scene) ...@@ -110,9 +110,8 @@ GPUPathIntegrator::GPUPathIntegrator(Allocator alloc, const ParsedScene &scene)
scene.camera.cameraTransform, outsideMedium, &light.loc, alloc); scene.camera.cameraTransform, outsideMedium, &light.loc, alloc);
if (l.Is<UniformInfiniteLight>() || l.Is<ImageInfiniteLight>() || if (l.Is<UniformInfiniteLight>() || l.Is<ImageInfiniteLight>() ||
l.Is<PortalImageInfiniteLight>()) { l.Is<PortalImageInfiniteLight>())
envLights.push_back(l); envLights.push_back(l);
}
allLights.push_back(l); allLights.push_back(l);
} }
...@@ -181,6 +180,18 @@ GPUPathIntegrator::GPUPathIntegrator(Allocator alloc, const ParsedScene &scene) ...@@ -181,6 +180,18 @@ GPUPathIntegrator::GPUPathIntegrator(Allocator alloc, const ParsedScene &scene)
regularize = scene.integrator.parameters.GetOneBool("regularize", false); regularize = scene.integrator.parameters.GetOneBool("regularize", false);
maxDepth = scene.integrator.parameters.GetOneInt("maxdepth", 5); maxDepth = scene.integrator.parameters.GetOneInt("maxdepth", 5);
// Warn about unsupported stuff...
if (Options->forceDiffuse)
Warning("The GPU rendering path does not support --force-diffuse.");
if (Options->writePartialImages)
Warning("The GPU rendering path does not support --write-partial-images.");
if (Options->recordPixelStatistics)
Warning("The GPU rendering path does not support --pixelstats.");
if (!Options->mseReferenceImage.empty())
Warning("The GPU rendering path does not support --mse-reference-image.");
if (!Options->mseReferenceOutput.empty())
Warning("The GPU rendering path does not support --mse-reference-out.");
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Allocate storage for all of the queues/buffers... // Allocate storage for all of the queues/buffers...
...@@ -451,13 +462,13 @@ void GPUPathIntegrator::HandleEscapedRays(int depth) { ...@@ -451,13 +462,13 @@ void GPUPathIntegrator::HandleEscapedRays(int depth) {
for (const auto &light : envLights) { for (const auto &light : envLights) {
if (SampledSpectrum Le = light.Le(Ray(w.rayo, w.rayd), w.lambda); Le) { if (SampledSpectrum Le = light.Le(Ray(w.rayo, w.rayd), w.lambda); Le) {
// Compute path radiance contribution from infinite light // Compute path radiance contribution from infinite light
PBRT_DBG("L %f %f %f %f T_hat %f %f %f %f Le %f %f %f %f", L[0], L[1],
PBRT_DBG("L %f %f %f %f T_hat %f %f %f %f Le %f %f %f %f", L[0], L[1], L[2], L[2], L[3], w.T_hat[0], w.T_hat[1], w.T_hat[2], w.T_hat[3],
L[3], w.T_hat[0], w.T_hat[1], w.T_hat[2], w.T_hat[3], Le[0], Le[1], Le[0], Le[1], Le[2], Le[3]);
Le[2], Le[3]);
PBRT_DBG("pdf uni %f %f %f %f pdf nee %f %f %f %f", w.uniPathPDF[0], PBRT_DBG("pdf uni %f %f %f %f pdf nee %f %f %f %f", w.uniPathPDF[0],
w.uniPathPDF[1], w.uniPathPDF[2], w.uniPathPDF[3], w.lightPathPDF[0], w.uniPathPDF[1], w.uniPathPDF[2], w.uniPathPDF[3],
w.lightPathPDF[1], w.lightPathPDF[2], w.lightPathPDF[3]); w.lightPathPDF[0], w.lightPathPDF[1], w.lightPathPDF[2],
w.lightPathPDF[3]);
if (depth == 0 || w.specularBounce) { if (depth == 0 || w.specularBounce) {
L += w.T_hat * Le / w.uniPathPDF.Average(); L += w.T_hat * Le / w.uniPathPDF.Average();
...@@ -475,8 +486,8 @@ void GPUPathIntegrator::HandleEscapedRays(int depth) { ...@@ -475,8 +486,8 @@ void GPUPathIntegrator::HandleEscapedRays(int depth) {
if (L) { if (L) {
L = SafeDiv(L, w.lambda.PDF()); L = SafeDiv(L, w.lambda.PDF());
PBRT_DBG("Added L %f %f %f %f for escaped ray pixel index %d\n", L[0], L[1], PBRT_DBG("Added L %f %f %f %f for escaped ray pixel index %d\n", L[0],
L[2], L[3], w.pixelIndex); L[1], L[2], L[3], w.pixelIndex);
L += pixelSampleState.L[w.pixelIndex]; L += pixelSampleState.L[w.pixelIndex];
pixelSampleState.L[w.pixelIndex] = L; pixelSampleState.L[w.pixelIndex] = L;
......
...@@ -103,7 +103,8 @@ class HomogeneousMedium { ...@@ -103,7 +103,8 @@ class HomogeneousMedium {
F callback) const { F callback) const {
// Normalize ray direction for homogeneous medium sampling // Normalize ray direction for homogeneous medium sampling
tMax *= Length(ray.d); tMax *= Length(ray.d);
if (std::isinf(tMax)) tMax = std::numeric_limits<Float>::max(); if (std::isinf(tMax))
tMax = std::numeric_limits<Float>::max();
ray.d = Normalize(ray.d); ray.d = Normalize(ray.d);
// Compute _SampledSpectrum_ scattering properties for medium // Compute _SampledSpectrum_ scattering properties for medium
......
...@@ -1485,7 +1485,7 @@ void FormattingScene::LightSource(const std::string &name, ParsedParameterVector ...@@ -1485,7 +1485,7 @@ void FormattingScene::LightSource(const std::string &name, ParsedParameterVector
"Please modify your scene file manually."); "Please modify your scene file manually.");
return; return;
} }
dict.RemoveInt("nsamples"); dict.RemoveInt("samples");
if (dict.GetOneString("mapname", "").empty() == false) { if (dict.GetOneString("mapname", "").empty() == false) {
if (name == "infinite" && !upgradeRGBToScale(&dict, "L", &totalScale)) { if (name == "infinite" && !upgradeRGBToScale(&dict, "L", &totalScale)) {
......
...@@ -421,6 +421,8 @@ static ParsedParameterVector parseParameters( ...@@ -421,6 +421,8 @@ static ParsedParameterVector parseParameters(
if (formatting) { // close enough: upgrade... if (formatting) { // close enough: upgrade...
if (param->type == "point") if (param->type == "point")
param->type = "point3"; param->type = "point3";
if (param->type == "vector")
param->type = "vector3";
if (param->type == "color") if (param->type == "color")
param->type = "rgb"; param->type = "rgb";
} }
......
...@@ -280,8 +280,10 @@ class ZSobolSampler { ...@@ -280,8 +280,10 @@ class ZSobolSampler {
void StartPixelSample(const Point2i &p, int index, int dim) { void StartPixelSample(const Point2i &p, int index, int dim) {
dimension = dim; dimension = dim;
bool pow2Samples = log2SamplesPerPixel & 1; bool pow2Samples = log2SamplesPerPixel & 1;
if (pow2Samples) index <<= 1; if (pow2Samples)
mortonIndex = (EncodeMorton2(p.x, p.y) << ((log2SamplesPerPixel + 1) & ~1)) | index; index <<= 1;
mortonIndex =
(EncodeMorton2(p.x, p.y) << ((log2SamplesPerPixel + 1) & ~1)) | index;
} }
PBRT_CPU_GPU PBRT_CPU_GPU
......
...@@ -81,7 +81,7 @@ void PrintStackTrace(); ...@@ -81,7 +81,7 @@ void PrintStackTrace();
#define DCHECK_LT(a, b) EMPTY_CHECK #define DCHECK_LT(a, b) EMPTY_CHECK
#define DCHECK_LE(a, b) EMPTY_CHECK #define DCHECK_LE(a, b) EMPTY_CHECK
#endif // !defined(NDEBUG) #endif
#define CHECK_RARE_TO_STRING(x) #x #define CHECK_RARE_TO_STRING(x) #x
#define CHECK_RARE_EXPAND_AND_TO_STRING(x) CHECK_RARE_TO_STRING(x) #define CHECK_RARE_EXPAND_AND_TO_STRING(x) CHECK_RARE_TO_STRING(x)
......
...@@ -174,10 +174,13 @@ std::string DenselySampledSpectrum::ToString() const { ...@@ -174,10 +174,13 @@ std::string DenselySampledSpectrum::ToString() const {
} }
std::string SampledWavelengths::ToString() const { std::string SampledWavelengths::ToString() const {
std::string r = "["; std::string r = "[ SampledWavelengths lambda: [";
for (size_t i = 0; i < lambda.size(); ++i) for (size_t i = 0; i < lambda.size(); ++i)
r += StringPrintf(" %f%c", lambda[i], i != lambda.size() - 1 ? ',' : ' '); r += StringPrintf(" %f%c", lambda[i], i != lambda.size() - 1 ? ',' : ' ');
r += ']'; r += "] pdf: [";
for (size_t i = 0; i < lambda.size(); ++i)
r += StringPrintf(" %f%c", pdf[i], i != pdf.size() - 1 ? ',' : ' ');
r += "] ]";
return r; return r;
} }
...@@ -1266,6 +1269,28 @@ const Float Cu_k[] = { ...@@ -1266,6 +1269,28 @@ const Float Cu_k[] = {
5.034125, 826.561157, 5.260000, 855.063293, 5.485625, 885.601257, 5.717000, 5.034125, 826.561157, 5.260000, 855.063293, 5.485625, 885.601257, 5.717000,
}; };
const Float CuZn_eta[] = {
290, 1.358, 300, 1.388, 310, 1.419, 320, 1.446, 330, 1.473, 340, 1.494, 350, 1.504,
360, 1.503, 370, 1.497, 380, 1.487, 390, 1.471, 400, 1.445, 410, 1.405, 420, 1.350,
430, 1.278, 440, 1.191, 450, 1.094, 460, 0.994, 470, 0.900, 480, 0.816, 490, 0.745,
500, 0.686, 510, 0.639, 520, 0.602, 530, 0.573, 540, 0.549, 550, 0.527, 560, 0.505,
570, 0.484, 580, 0.468, 590, 0.460, 600, 0.450, 610, 0.452, 620, 0.449, 630, 0.445,
640, 0.444, 650, 0.444, 660, 0.445, 670, 0.444, 680, 0.444, 690, 0.445, 700, 0.446,
710, 0.448, 720, 0.450, 730, 0.452, 740, 0.455, 750, 0.457, 760, 0.458, 770, 0.460,
780, 0.464, 790, 0.469, 800, 0.473, 810, 0.478, 820, 0.481, 830, 0.483, 840, 0.486,
850, 0.490, 860, 0.494, 870, 0.500, 880, 0.507, 890, 0.515};
const Float CuZn_k[] = {
290, 1.688, 300, 1.731, 310, 1.764, 320, 1.789, 330, 1.807, 340, 1.815, 350, 1.815,
360, 1.815, 370, 1.818, 380, 1.818, 390, 1.813, 400, 1.805, 410, 1.794, 420, 1.786,
430, 1.784, 440, 1.797, 450, 1.829, 460, 1.883, 470, 1.957, 480, 2.046, 490, 2.145,
500, 2.250, 510, 2.358, 520, 2.464, 530, 2.568, 540, 2.668, 550, 2.765, 560, 2.860,
570, 2.958, 580, 3.059, 590, 3.159, 600, 3.253, 610, 3.345, 620, 3.434, 630, 3.522,
640, 3.609, 650, 3.695, 660, 3.778, 670, 3.860, 680, 3.943, 690, 4.025, 700, 4.106,
710, 4.186, 720, 4.266, 730, 4.346, 740, 4.424, 750, 4.501, 760, 4.579, 770, 4.657,
780, 4.737, 790, 4.814, 800, 4.890, 810, 4.965, 820, 5.039, 830, 5.115, 840, 5.192,
850, 5.269, 860, 5.346, 870, 5.423, 880, 5.500, 890, 5.575};
const Float MgO_eta[] = { const Float MgO_eta[] = {
309.950012, 1.798000, 330.613007, 1.785000, 351.118988, 1.776800, 355.549011, 309.950012, 1.798000, 330.613007, 1.785000, 351.118988, 1.776800, 355.549011,
1.775500, 360.932007, 1.773200, 361.141998, 1.773180, 364.968994, 1.771860, 1.775500, 360.932007, 1.773200, 361.141998, 1.773180, 364.968994, 1.771860,
...@@ -2661,6 +2686,9 @@ void Init(Allocator alloc) { ...@@ -2661,6 +2686,9 @@ void Init(Allocator alloc) {
SpectrumHandle auk = PiecewiseLinearSpectrum::FromInterleaved(Au_k, false, alloc); SpectrumHandle auk = PiecewiseLinearSpectrum::FromInterleaved(Au_k, false, alloc);
SpectrumHandle cueta = PiecewiseLinearSpectrum::FromInterleaved(Cu_eta, false, alloc); SpectrumHandle cueta = PiecewiseLinearSpectrum::FromInterleaved(Cu_eta, false, alloc);
SpectrumHandle cuk = PiecewiseLinearSpectrum::FromInterleaved(Cu_k, false, alloc); SpectrumHandle cuk = PiecewiseLinearSpectrum::FromInterleaved(Cu_k, false, alloc);
SpectrumHandle cuzneta =
PiecewiseLinearSpectrum::FromInterleaved(CuZn_eta, false, alloc);
SpectrumHandle cuznk = PiecewiseLinearSpectrum::FromInterleaved(CuZn_k, false, alloc);
SpectrumHandle mgoeta = SpectrumHandle mgoeta =
PiecewiseLinearSpectrum::FromInterleaved(MgO_eta, false, alloc); PiecewiseLinearSpectrum::FromInterleaved(MgO_eta, false, alloc);
SpectrumHandle mgok = PiecewiseLinearSpectrum::FromInterleaved(MgO_k, false, alloc); SpectrumHandle mgok = PiecewiseLinearSpectrum::FromInterleaved(MgO_k, false, alloc);
...@@ -2699,6 +2727,8 @@ void Init(Allocator alloc) { ...@@ -2699,6 +2727,8 @@ void Init(Allocator alloc) {
{"metal-Au-k", auk}, {"metal-Au-k", auk},
{"metal-Cu-eta", cueta}, {"metal-Cu-eta", cueta},
{"metal-Cu-k", cuk}, {"metal-Cu-k", cuk},
{"metal-CuZn-eta", cuzneta},
{"metal-CuZn-k", cuznk},
{"metal-MgO-eta", mgoeta}, {"metal-MgO-eta", mgoeta},
{"metal-MgO-k", mgok}, {"metal-MgO-k", mgok},
{"metal-TiO2-eta", tio2eta}, {"metal-TiO2-eta", tio2eta},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册